+static void GCC_FMT_ATTR(2, 3)
+qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
+ const char *resmsg,
+ ...)
+{
+ va_list vargs;
+ char *response;
+ size_t responselen;
+
+ va_start(vargs, resmsg);
+ response = g_strdup_vprintf(resmsg, vargs);
+ responselen = strlen(response);
+ buffer_reserve(&ioc->encoutput, responselen);
+ buffer_append(&ioc->encoutput, response, responselen);
+ va_end(vargs);
+}
+
+static gchar *qio_channel_websock_date_str(void)
+{
+ struct tm tm;
+ time_t now = time(NULL);
+ char datebuf[128];
+
+ gmtime_r(&now, &tm);
+
+ strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S GMT", &tm);
+
+ return g_strdup(datebuf);
+}
+
+static void qio_channel_websock_handshake_send_res_err(QIOChannelWebsock *ioc,
+ const char *resdata)
+{
+ char *date = qio_channel_websock_date_str();
+ qio_channel_websock_handshake_send_res(ioc, resdata, date);
+ g_free(date);
+}
+
+enum {
+ QIO_CHANNEL_WEBSOCK_STATUS_NORMAL = 1000,
+ QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR = 1002,
+ QIO_CHANNEL_WEBSOCK_STATUS_INVALID_DATA = 1003,
+ QIO_CHANNEL_WEBSOCK_STATUS_POLICY = 1008,
+ QIO_CHANNEL_WEBSOCK_STATUS_TOO_LARGE = 1009,
+ QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR = 1011,
+};
+
+static size_t
+qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
+ char *buffer,
+ QIOChannelWebsockHTTPHeader *hdrs,
+ size_t nhdrsalloc,
+ Error **errp)
+{
+ char *nl, *sep, *tmp;
+ size_t nhdrs = 0;
+
+ /*
+ * First parse the HTTP protocol greeting of format:
+ *
+ * $METHOD $PATH $VERSION
+ *
+ * e.g.
+ *
+ * GET / HTTP/1.1
+ */
+
+ nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
+ if (!nl) {
+ error_setg(errp, "Missing HTTP header delimiter");
+ goto bad_request;
+ }
+ *nl = '\0';
+ trace_qio_channel_websock_http_greeting(ioc, buffer);
+
+ tmp = strchr(buffer, ' ');
+ if (!tmp) {
+ error_setg(errp, "Missing HTTP path delimiter");
+ return 0;
+ }
+ *tmp = '\0';
+
+ if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) {
+ error_setg(errp, "Unsupported HTTP method %s", buffer);
+ goto bad_request;
+ }
+
+ buffer = tmp + 1;
+ tmp = strchr(buffer, ' ');
+ if (!tmp) {
+ error_setg(errp, "Missing HTTP version delimiter");
+ goto bad_request;
+ }
+ *tmp = '\0';
+
+ if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_PATH)) {
+ qio_channel_websock_handshake_send_res_err(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND);
+ error_setg(errp, "Unexpected HTTP path %s", buffer);
+ return 0;
+ }
+
+ buffer = tmp + 1;
+
+ if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) {
+ error_setg(errp, "Unsupported HTTP version %s", buffer);
+ goto bad_request;
+ }
+
+ buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
+
+ /*
+ * Now parse all the header fields of format
+ *
+ * $NAME: $VALUE
+ *
+ * e.g.
+ *
+ * Cache-control: no-cache
+ */
+ do {
+ QIOChannelWebsockHTTPHeader *hdr;
+
+ nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
+ if (nl) {
+ *nl = '\0';
+ }
+
+ sep = strchr(buffer, ':');
+ if (!sep) {
+ error_setg(errp, "Malformed HTTP header");
+ goto bad_request;
+ }
+ *sep = '\0';
+ sep++;
+ while (*sep == ' ') {
+ sep++;
+ }
+
+ if (nhdrs >= nhdrsalloc) {
+ error_setg(errp, "Too many HTTP headers");
+ goto bad_request;
+ }
+
+ hdr = &hdrs[nhdrs++];
+ hdr->name = buffer;
+ hdr->value = sep;
+
+ /* Canonicalize header name for easier identification later */
+ for (tmp = hdr->name; *tmp; tmp++) {
+ *tmp = g_ascii_tolower(*tmp);
+ }
+
+ if (nl) {
+ buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
+ }
+ } while (nl != NULL);
+
+ return nhdrs;
+
+ bad_request:
+ qio_channel_websock_handshake_send_res_err(
+ ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
+ return 0;
+}
+
+static const char *
+qio_channel_websock_find_header(QIOChannelWebsockHTTPHeader *hdrs,
+ size_t nhdrs,
+ const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < nhdrs; i++) {
+ if (g_str_equal(hdrs[i].name, name)) {
+ return hdrs[i].value;