示例#1
0
	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);
	}
示例#2
0
	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());
		}
示例#3
0
	// 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;
	}
示例#4
0
	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);
		}
	}