void respond(int code, const QByteArray &reason, const HttpHeaders &headers, const QByteArray &body) { if(state != WriteBody) return; HttpHeaders outHeaders = headers; outHeaders.removeAll("Connection"); outHeaders.removeAll("Transfer-Encoding"); outHeaders.removeAll("Content-Length"); outHeaders += HttpHeader("Connection", "close"); outHeaders += HttpHeader("Content-Length", QByteArray::number(body.size())); QByteArray respData = "HTTP/"; if(version1dot0) respData += "1.0 "; else respData += "1.1 "; respData += QByteArray::number(code) + " " + reason + "\r\n"; foreach(const HttpHeader &h, outHeaders) respData += h.first + ": " + h.second + "\r\n"; respData += "\r\n"; respData += body; state = WaitForWritten; pendingWritten += respData.size(); sock->write(respData); }
void setup(const QUrl &_uri, const HttpHeaders &_headers, const QHostAddress &connectAddr = QHostAddress(), int connectPort = -1, int _maxRedirects = -1, const QString &connectHostToTrust = QString()) { assert(!method.isEmpty()); QUrl uri = _uri; if(connectPort != -1) uri.setPort(connectPort); else if(uri.port() == -1) { if(uri.scheme() == "https") uri.setPort(443); else uri.setPort(80); } HttpHeaders headers = _headers; checkHosts += uri.host(QUrl::FullyEncoded); if(!connectAddr.isNull()) { curl_slist_free_all(dnsCache); if(!connectHostToTrust.isEmpty()) checkHosts += connectHostToTrust; QByteArray cacheEntry = uri.host(QUrl::FullyEncoded).toUtf8() + ':' + QByteArray::number(uri.port()) + ':' + connectAddr.toString().toUtf8(); dnsCache = curl_slist_append(dnsCache, cacheEntry.data()); curl_easy_setopt(easy, CURLOPT_RESOLVE, dnsCache); } curl_easy_setopt(easy, CURLOPT_URL, uri.toEncoded().data()); bool chunked = false; if(headers.contains("Content-Length")) { curl_off_t content_len = (curl_off_t)headers.get("Content-Length").toLongLong(); /*if(method == "POST") curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE_LARGE, content_len); else*/ curl_easy_setopt(easy, CURLOPT_INFILESIZE_LARGE, content_len); // curl will set this for us headers.removeAll("Content-Length"); } else { if(expectBody) chunked = true; else if(alwaysSetBody) curl_easy_setopt(easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t)0); } curl_slist_free_all(headersList); foreach(const HttpHeader &h, headers) { QByteArray i = h.first + ": " + h.second; headersList = curl_slist_append(headersList, i.data()); }
// return true if jsonp applied bool tryApplyJsonp(const DomainMap::JsonpConfig &config, bool *ok, QString *errorMessage) { *ok = true; // must be a GET if(requestData.method != "GET") return false; QString callbackParam = QString::fromUtf8(config.callbackParam); if(callbackParam.isEmpty()) callbackParam = "callback"; QString bodyParam; if(!config.bodyParam.isEmpty()) bodyParam = QString::fromUtf8(config.bodyParam); QUrl uri = requestData.uri; QUrlQuery query(uri); // two ways to activate JSON-P: // 1) callback param present // 2) default callback specified in configuration and body param present if(!query.hasQueryItem(callbackParam) && (config.defaultCallback.isEmpty() || bodyParam.isEmpty() || !query.hasQueryItem(bodyParam))) { return false; } QByteArray callback; if(query.hasQueryItem(callbackParam)) { callback = parsePercentEncoding(query.queryItemValue(callbackParam, QUrl::FullyEncoded).toUtf8()); if(callback.isEmpty()) { log_debug("requestsession: id=%s invalid callback parameter, rejecting", rid.second.data()); *ok = false; *errorMessage = "Invalid callback parameter."; return false; } query.removeAllQueryItems(callbackParam); } else callback = config.defaultCallback; QString method; if(query.hasQueryItem("_method")) { method = QString::fromLatin1(parsePercentEncoding(query.queryItemValue("_method", QUrl::FullyEncoded).toUtf8())); if(!validMethod(method)) { log_debug("requestsession: id=%s invalid _method parameter, rejecting", rid.second.data()); *ok = false; *errorMessage = "Invalid _method parameter."; return false; } query.removeAllQueryItems("_method"); } HttpHeaders headers; if(query.hasQueryItem("_headers")) { QJsonParseError e; QJsonDocument doc = QJsonDocument::fromJson(parsePercentEncoding(query.queryItemValue("_headers", QUrl::FullyEncoded).toUtf8()), &e); if(e.error != QJsonParseError::NoError || !doc.isObject()) { log_debug("requestsession: id=%s invalid _headers parameter, rejecting", rid.second.data()); *ok = false; *errorMessage = "Invalid _headers parameter."; return false; } QVariantMap headersMap = doc.object().toVariantMap(); QMapIterator<QString, QVariant> vit(headersMap); while(vit.hasNext()) { vit.next(); if(vit.value().type() != QVariant::String) { log_debug("requestsession: id=%s invalid _headers parameter, rejecting", rid.second.data()); *ok = false; *errorMessage = "Invalid _headers parameter."; return false; } QByteArray key = vit.key().toUtf8(); // ignore some headers that we explicitly set later on if(qstricmp(key.data(), "host") == 0) continue; if(qstricmp(key.data(), "accept") == 0) continue; headers += HttpHeader(key, vit.value().toString().toUtf8()); } query.removeAllQueryItems("_headers"); } QByteArray body; if(!bodyParam.isEmpty()) { if(query.hasQueryItem(bodyParam)) { body = parsePercentEncoding(query.queryItemValue(bodyParam, QUrl::FullyEncoded).toUtf8()); if(body.isNull()) { log_debug("requestsession: id=%s invalid body parameter, rejecting", rid.second.data()); *ok = false; *errorMessage = "Invalid body parameter."; return false; } headers.removeAll("Content-Type"); headers += HttpHeader("Content-Type", "application/json"); query.removeAllQueryItems(bodyParam); } } else { if(query.hasQueryItem("_body")) { body = parsePercentEncoding(query.queryItemValue("_body").toUtf8()); if(body.isNull()) { log_debug("requestsession: id=%s invalid _body parameter, rejecting", rid.second.data()); *ok = false; *errorMessage = "Invalid _body parameter."; return false; } query.removeAllQueryItems("_body"); } } uri.setQuery(query); // if we have no query items anymore, strip the '?' if(query.isEmpty()) { QByteArray tmp = uri.toEncoded(); if(tmp.length() > 0 && tmp[tmp.length() - 1] == '?') { tmp.truncate(tmp.length() - 1); uri = QUrl::fromEncoded(tmp, QUrl::StrictMode); } } if(method.isEmpty()) method = config.defaultMethod; requestData.method = method; requestData.uri = uri; headers += HttpHeader("Host", uri.host().toUtf8()); headers += HttpHeader("Accept", "*/*"); // carry over the rest of the headers foreach(const HttpHeader &h, requestData.headers) { if(qstricmp(h.first.data(), "host") == 0) continue; if(qstricmp(h.first.data(), "accept") == 0) continue; headers += h; } requestData.headers = headers; in += body; jsonpCallback = callback; jsonpExtendedResponse = (config.mode == DomainMap::JsonpConfig::Extended); return true; }
void start(const QVariant &vrequest, Mode mode) { outSeq = 0; outCredits = 0; quiet = false; ZhttpRequestPacket request; if(!request.fromVariant(vrequest)) { log_warning("failed to parse zurl request"); QVariantHash vhash = vrequest.toHash(); rid = vhash.value("id").toByteArray(); toAddress = vhash.value("from").toByteArray(); QByteArray type = vhash.value("type").toByteArray(); if(!toAddress.isEmpty() && type != "error" && type != "cancel") { QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); } else { cleanup(); QMetaObject::invokeMethod(q, "finished", Qt::QueuedConnection); } return; } rid = request.id; toAddress = request.from; userData = request.userData; sentHeader = false; stuffToRead = false; bytesReceived = 0; ignorePolicies = request.ignorePolicies; if(request.uri.isEmpty()) { log_warning("missing request uri"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } QString scheme = request.uri.scheme(); if(scheme == "https" || scheme == "http") { transport = HttpTransport; } else if(scheme == "wss" || scheme == "ws") { transport = WebSocketTransport; } else { log_warning("unsupported scheme"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } if(transport == WebSocketTransport && mode != Worker::Stream) { log_warning("websocket must be used from stream interface"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } int defaultPort; if(scheme == "https" || scheme == "wss") defaultPort = 443; else // http || wss defaultPort = 80; HttpHeaders headers = request.headers; if(transport == HttpTransport) { // fire and forget if(mode == Worker::Stream && (rid.isEmpty() || toAddress.isEmpty())) quiet = true; // streaming only allowed on streaming interface if(mode == Worker::Stream) outStream = request.stream; else outStream = false; if(request.method.isEmpty()) { log_warning("missing request method"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } log_info("IN id=%s, %s %s", request.id.data(), qPrintable(request.method), request.uri.toEncoded().data()); // inbound streaming must start with sequence number of 0 if(mode == Worker::Stream && request.more && request.seq != 0) { log_warning("streamed input must start with seq 0"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } // can't use these two together if(mode == Worker::Single && request.more) { log_warning("cannot use streamed input on router interface"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } bodySent = false; inSeq = request.seq; if(!isAllowed(request.uri.host()) || (!request.connectHost.isEmpty() && !isAllowed(request.connectHost))) { QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "policy-violation")); return; } QByteArray hostHeader = request.uri.host().toUtf8(); // only tack on the port if it isn't being overridden int port = request.uri.port(defaultPort); if(request.connectPort == -1 && port != defaultPort) hostHeader += ":" + QByteArray::number(port); headers.removeAll("Host"); headers += HttpHeader("Host", hostHeader); hreq = new HttpRequest(dns, this); connect(hreq, SIGNAL(nextAddress(const QHostAddress &)), SLOT(req_nextAddress(const QHostAddress &))); connect(hreq, SIGNAL(readyRead()), SLOT(req_readyRead())); connect(hreq, SIGNAL(bytesWritten(int)), SLOT(req_bytesWritten(int))); connect(hreq, SIGNAL(error()), SLOT(req_error())); maxResponseSize = request.maxSize; sessionTimeout = request.timeout; if(!request.connectHost.isEmpty()) hreq->setConnectHost(request.connectHost); if(request.connectPort != -1) request.uri.setPort(request.connectPort); hreq->setIgnoreTlsErrors(request.ignoreTlsErrors); if(request.followRedirects) hreq->setFollowRedirects(8); if(request.credits != -1) outCredits += request.credits; } else // WebSocketTransport { log_info("IN id=%s, %s", request.id.data(), request.uri.toEncoded().data()); // inbound streaming must start with sequence number of 0 if(request.seq != 0) { log_warning("websocket input must start with seq 0"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } if(toAddress.isEmpty()) { log_warning("websocket input must provide from address"); QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request")); return; } inSeq = request.seq; if(!isAllowed(request.uri.host()) || (!request.connectHost.isEmpty() && !isAllowed(request.connectHost))) { QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "policy-violation")); return; } QByteArray hostHeader = request.uri.host().toUtf8(); // only tack on the port if it isn't being overridden int port = request.uri.port(defaultPort); if(request.connectPort == -1 && port != defaultPort) hostHeader += ":" + QByteArray::number(port); headers.removeAll("Host"); headers += HttpHeader("Host", hostHeader); ws = new WebSocket(dns, this); connect(ws, SIGNAL(nextAddress(const QHostAddress &)), SLOT(req_nextAddress(const QHostAddress &))); connect(ws, SIGNAL(connected()), SLOT(ws_connected())); connect(ws, SIGNAL(readyRead()), SLOT(ws_readyRead())); connect(ws, SIGNAL(framesWritten(int)), SLOT(ws_framesWritten(int))); connect(ws, SIGNAL(peerClosing()), SLOT(ws_peerClosing())); connect(ws, SIGNAL(closed()), SLOT(ws_closed())); connect(ws, SIGNAL(error()), SLOT(ws_error())); if(!request.connectHost.isEmpty()) ws->setConnectHost(request.connectHost); if(request.connectPort != -1) request.uri.setPort(request.connectPort); ws->setIgnoreTlsErrors(request.ignoreTlsErrors); ws->setMaxFrameSize(config->sessionBufferSize); if(request.credits != -1) outCredits += request.credits; } httpActivityTimer = new QTimer(this); connect(httpActivityTimer, SIGNAL(timeout()), SLOT(httpActivity_timeout())); httpActivityTimer->setSingleShot(true); httpActivityTimer->start(config->activityTimeout * 1000); if(sessionTimeout != -1) { httpSessionTimer = new QTimer(this); connect(httpSessionTimer, SIGNAL(timeout()), SLOT(httpSession_timeout())); httpSessionTimer->setSingleShot(true); httpSessionTimer->start(sessionTimeout); } if(transport == WebSocketTransport || (transport == HttpTransport && mode == Worker::Stream)) { expireTimer = new QTimer(this); connect(expireTimer, SIGNAL(timeout()), SLOT(expire_timeout())); expireTimer->setSingleShot(true); expireTimer->start(SESSION_EXPIRE); keepAliveTimer = new QTimer(this); connect(keepAliveTimer, SIGNAL(timeout()), SLOT(keepAlive_timeout())); keepAliveTimer->start(SESSION_EXPIRE / 2); } if(transport == HttpTransport) { if(!request.body.isEmpty() && !request.more && !headers.contains("Content-Length")) headers += HttpHeader("Content-Length", QByteArray::number(request.body.size())); bool hasOrMightHaveBody = (!request.body.isEmpty() || request.more); hreq->start(request.method, request.uri, headers, hasOrMightHaveBody); if(hasOrMightHaveBody) { if(!request.body.isEmpty()) hreq->writeBody(request.body); if(!request.more) { bodySent = true; hreq->endBody(); } } else bodySent = true; if(mode == Stream) { if(request.more) { // send cts ZhttpResponsePacket resp; resp.type = ZhttpResponsePacket::Credit; resp.credits = config->sessionBufferSize; writeResponse(resp); } else { // send ack ZhttpResponsePacket resp; resp.type = ZhttpResponsePacket::KeepAlive; writeResponse(resp); } } } else // WebSocketTransport { ws->start(request.uri, headers); } }