diff --git a/src/core/packet/wscontrolpacket.cpp b/src/core/packet/wscontrolpacket.cpp index c8786005..ae545d5f 100644 --- a/src/core/packet/wscontrolpacket.cpp +++ b/src/core/packet/wscontrolpacket.cpp @@ -218,6 +218,9 @@ QVariant WsControlPacket::toVariant() const if(item.logLevel >= 0) vitem["log-level"] = item.logLevel; + if(item.trusted) + vitem["trusted"] = true; + if(!item.channel.isEmpty()) vitem["channel"] = item.channel; @@ -395,6 +398,14 @@ bool WsControlPacket::fromVariant(const QVariant &in) item.logLevel = vitem["log-level"].toInt(); } + if(vitem.contains("trusted")) + { + if(typeId(vitem["trusted"]) != QMetaType::Bool) + return false; + + item.trusted = vitem["trusted"].toBool(); + } + if(vitem.contains("channel")) { if(typeId(vitem["channel"]) != QMetaType::QByteArray) diff --git a/src/core/packet/wscontrolpacket.h b/src/core/packet/wscontrolpacket.h index ab8cf7f7..b3ddc6a2 100644 --- a/src/core/packet/wscontrolpacket.h +++ b/src/core/packet/wscontrolpacket.h @@ -65,6 +65,7 @@ class WsControlPacket bool separateStats; QByteArray channelPrefix; int logLevel; + bool trusted; QByteArray channel; int ttl; int timeout; @@ -76,6 +77,7 @@ class WsControlPacket code(-1), separateStats(false), logLevel(-1), + trusted(false), ttl(-1), timeout(-1) { diff --git a/src/handler/filter.cpp b/src/handler/filter.cpp index cfb46623..d04e1e1b 100644 --- a/src/handler/filter.cpp +++ b/src/handler/filter.cpp @@ -23,9 +23,13 @@ #include "filter.h" +#include +#include #include "log.h" #include "format.h" #include "idformat.h" +#include "zhttpmanager.h" +#include "zhttprequest.h" namespace { @@ -304,6 +308,192 @@ class VarSubstFilter : public Filter, public Filter::MessageFilter } }; +class HttpFilter : public Filter::MessageFilter +{ +public: + enum Mode + { + Check, + Modify + }; + + Mode mode; + std::unique_ptr req; + boost::signals2::scoped_connection readyReadConnection; + boost::signals2::scoped_connection errorConnection; + QByteArray origContent; + bool haveResponseHeader; + QByteArray responseBody; + + HttpFilter(Mode _mode) : + mode(_mode), + haveResponseHeader(false) + { + } + + virtual void start(const Filter::Context &context, const QByteArray &content) + { + QUrl url = QUrl(context.subscriptionMeta.value("url"), QUrl::StrictMode); + if(!url.isValid()) + { + Result r; + r.errorMessage = "invalid or missing url value"; + finished(r); + return; + } + + QUrl currentUri = context.currentUri; + if(currentUri.scheme() == "wss") + currentUri.setScheme("https"); + else if(currentUri.scheme() == "ws") + currentUri.setScheme("http"); + + QUrl destUri = currentUri.resolved(url); + + origContent = content; + + req.reset(context.zhttpOut->createRequest()); + readyReadConnection = req->readyRead.connect(boost::bind(&HttpFilter::req_readyRead, this)); + errorConnection = req->error.connect(boost::bind(&HttpFilter::req_error, this)); + + int currentPort = currentUri.port(currentUri.scheme() == "https" ? 443 : 80); + int destPort = destUri.port(destUri.scheme() == "https" ? 443 : 80); + + QVariantHash passthroughData; + + passthroughData["route"] = context.route.toUtf8(); + + // if dest link points to the same service as the current request, + // then we can assume the network would send the request back to + // us, so we can handle it internally. if the link points to a + // different service, then we can't make this assumption and need + // to make the request over the network. note that such a request + // could still end up looping back to us + if(destUri.scheme() == currentUri.scheme() && destUri.host() == currentUri.host() && destPort == currentPort) + { + // tell the proxy that we prefer the request to be handled + // internally, using the same route + passthroughData["prefer-internal"] = true; + } + + // needed in case internal routing is not used + if(context.trusted) + passthroughData["trusted"] = true; + + req->setPassthroughData(passthroughData); + + HttpHeaders headers; + + { + QVariantMap vmap; + QHashIterator it(context.subscriptionMeta); + while(it.hasNext()) + { + it.next(); + vmap[it.key()] = it.value(); + } + + QJsonDocument doc = QJsonDocument(QJsonObject::fromVariantMap(vmap)); + headers += HttpHeader("Sub-Meta", doc.toJson(QJsonDocument::Compact)); + } + + { + QVariantMap vmap; + QHashIterator it(context.publishMeta); + while(it.hasNext()) + { + it.next(); + vmap[it.key()] = it.value(); + } + + QJsonDocument doc = QJsonDocument(QJsonObject::fromVariantMap(vmap)); + headers += HttpHeader("Pub-Meta", doc.toJson(QJsonDocument::Compact)); + } + + { + QHashIterator it(context.prevIds); + while(it.hasNext()) + { + it.next(); + const QString &name = it.key(); + const QString &prevId = it.value(); + + if(!prevId.isNull()) + headers += HttpHeader("Grip-Last", name.toUtf8() + "; last-id=" + prevId.toUtf8()); + } + } + + req->start("POST", destUri, headers); + + if(mode == Modify) + req->writeBody(content); + + req->endBody(); + } + + void req_readyRead() + { + if(!haveResponseHeader) + { + haveResponseHeader = true; + + switch(req->responseCode()) + { + case 200: + case 204: + break; + default: + Result r; + r.errorMessage = "unexpected network request status"; + finished(r); + return; + } + } + + QByteArray body = req->readBody(); + + if(mode == Modify) + responseBody += body; + + if(!req->isFinished()) + return; + + Result r; + + if(req->responseHeaders().get("Action") == "drop") + { + // drop + r.sendAction = Filter::Drop; + } + else + { + // accept + r.sendAction = Filter::Send; + + switch(req->responseCode()) + { + case 204: + // as-is + r.content = origContent; + break; + default: + // replace content + r.content = responseBody; + break; + } + } + + finished(r); + } + + void req_error() + { + Result r; + r.errorMessage = "network request failed"; + finished(r); + } +}; + } Filter::MessageFilter::~MessageFilter() @@ -375,6 +565,10 @@ Filter::MessageFilter *Filter::createMessageFilter(const QString &name) return new BuildIdFilter; else if(name == "var-subst") return new VarSubstFilter; + else if(name == "http-check") + return new HttpFilter(HttpFilter::Check); + else if(name == "http-modify") + return new HttpFilter(HttpFilter::Modify); else return 0; } @@ -386,7 +580,9 @@ QStringList Filter::names() << "skip-users" << "require-sub" << "build-id" - << "var-subst"); + << "var-subst" + << "http-check" + << "http-modify"); } Filter::Targets Filter::targets(const QString &name) @@ -401,6 +597,10 @@ Filter::Targets Filter::targets(const QString &name) return Filter::Targets(Filter::MessageContent | Filter::ResponseContent); else if(name == "var-subst") return Filter::MessageContent; + else if(name == "http-check") + return Filter::MessageDelivery; + else if(name == "http-modify") + return Filter::Targets(Filter::MessageDelivery | Filter::MessageContent); else return Filter::Targets(0); } diff --git a/src/handler/filter.h b/src/handler/filter.h index e614497c..a3114813 100644 --- a/src/handler/filter.h +++ b/src/handler/filter.h @@ -28,8 +28,11 @@ #include #include #include +#include #include +class ZhttpManager; + class Filter { public: @@ -52,6 +55,18 @@ class Filter QHash prevIds; QHash subscriptionMeta; QHash publishMeta; + + // for network access + ZhttpManager *zhttpOut; + QUrl currentUri; + QString route; + bool trusted; + + Context() : + zhttpOut(0), + trusted(false) + { + } }; class MessageFilter diff --git a/src/handler/handlerengine.cpp b/src/handler/handlerengine.cpp index 0fd96140..371771ec 100644 --- a/src/handler/handlerengine.cpp +++ b/src/handler/handlerengine.cpp @@ -1813,6 +1813,10 @@ class HandlerEngine::Private : public QObject Filter::Context fc; fc.subscriptionMeta = s->meta; fc.publishMeta = item.meta; + fc.zhttpOut = zhttpOut; + fc.currentUri = s->requestData.uri; + fc.route = s->route; + fc.trusted = s->targetTrusted; FilterStack filters(fc, s->channelFilters[item.channel]); @@ -2701,6 +2705,7 @@ class HandlerEngine::Private : public QObject s->route = item.route; s->statsRoute = item.separateStats ? item.route : QString(); + s->targetTrusted = item.trusted; s->channelPrefix = QString::fromUtf8(item.channelPrefix); if(item.logLevel >= 0) s->logLevel = item.logLevel; diff --git a/src/handler/httpsession.cpp b/src/handler/httpsession.cpp index d97c3396..cbd8028e 100644 --- a/src/handler/httpsession.cpp +++ b/src/handler/httpsession.cpp @@ -468,6 +468,10 @@ class HttpSession::Private : public QObject fc.prevIds = prevIds; fc.subscriptionMeta = instruct.meta; fc.publishMeta = item.meta; + fc.zhttpOut = outZhttp; + fc.currentUri = currentUri; + fc.route = adata.route; + fc.trusted = adata.trusted; FilterStack fs(fc, channels[item.channel].filters); @@ -875,6 +879,10 @@ class HttpSession::Private : public QObject fc.prevIds = prevIds; fc.subscriptionMeta = instruct.meta; fc.publishMeta = item.meta; + fc.zhttpOut = outZhttp; + fc.currentUri = currentUri; + fc.route = adata.route; + fc.trusted = adata.trusted; FilterStack fs(fc, channels[item.channel].filters); @@ -1229,7 +1237,7 @@ class HttpSession::Private : public QObject passthroughData["prefer-internal"] = true; } - // these fields are needed in case proxy routing is not used + // needed in case internal routing is not used if(adata.trusted) passthroughData["trusted"] = true; diff --git a/src/handler/wssession.cpp b/src/handler/wssession.cpp index fc646596..d1bf9ff7 100644 --- a/src/handler/wssession.cpp +++ b/src/handler/wssession.cpp @@ -32,7 +32,9 @@ WsSession::WsSession(QObject *parent) : QObject(parent), nextReqId(0), - logLevel(LOG_LEVEL_DEBUG) + logLevel(LOG_LEVEL_DEBUG), + targetTrusted(false), + ttl(0) { expireTimer = new QTimer(this); expireTimer->setSingleShot(true); diff --git a/src/handler/wssession.h b/src/handler/wssession.h index efd684fd..b502cc91 100644 --- a/src/handler/wssession.h +++ b/src/handler/wssession.h @@ -48,6 +48,7 @@ class WsSession : public QObject HttpRequestData requestData; QString route; QString statsRoute; + bool targetTrusted; QString sid; QHash meta; QHash channelFilters; // k=channel, v=list(filters) diff --git a/src/proxy/proxyutil.cpp b/src/proxy/proxyutil.cpp index ec39ee2a..41f9d8dc 100644 --- a/src/proxy/proxyutil.cpp +++ b/src/proxy/proxyutil.cpp @@ -177,7 +177,7 @@ void manipulateRequestHeaders(const char *logprefix, void *object, HttpRequestDa requestData->headers.removeAll("Grip-Feature"); requestData->headers += HttpHeader("Grip-Feature", - "status, session, link:next, link:gone, filter:skip-self, filter:skip-users, filter:require-sub, filter:build-id, filter:var-subst"); + "status, session, link:next, link:gone, filter:skip-self, filter:skip-users, filter:require-sub, filter:build-id, filter:var-subst, filter:http-check, filter:http-modify"); if(!idata.sid.isEmpty()) { diff --git a/src/proxy/wscontrolsession.cpp b/src/proxy/wscontrolsession.cpp index 0fc108ab..934ef4fc 100644 --- a/src/proxy/wscontrolsession.cpp +++ b/src/proxy/wscontrolsession.cpp @@ -54,6 +54,7 @@ class WsControlSession::Private : public QObject QByteArray channelPrefix; int logLevel; QUrl uri; + bool targetTrusted; Connection requestTimerConnection; Private(WsControlSession *_q) : @@ -62,7 +63,8 @@ class WsControlSession::Private : public QObject manager(0), nextReqId(0), separateStats(false), - logLevel(-1) + logLevel(-1), + targetTrusted(false) { requestTimer = std::make_unique(); requestTimerConnection = requestTimer->timeout.connect(boost::bind(&Private::requestTimer_timeout, this)); @@ -103,6 +105,7 @@ class WsControlSession::Private : public QObject i.channelPrefix = channelPrefix; i.logLevel = logLevel; i.uri = uri; + i.trusted = targetTrusted; i.ttl = SESSION_TTL; write(i, true); } @@ -326,13 +329,14 @@ QByteArray WsControlSession::cid() const return d->cid; } -void WsControlSession::start(const QByteArray &routeId, bool separateStats, const QByteArray &channelPrefix, int logLevel, const QUrl &uri) +void WsControlSession::start(const QByteArray &routeId, bool separateStats, const QByteArray &channelPrefix, int logLevel, const QUrl &uri, bool targetTrusted) { d->route = routeId; d->separateStats = separateStats; d->channelPrefix = channelPrefix; d->logLevel = logLevel; d->uri = uri; + d->targetTrusted = targetTrusted; d->start(); } diff --git a/src/proxy/wscontrolsession.h b/src/proxy/wscontrolsession.h index 1c1a933e..a2ac08fd 100644 --- a/src/proxy/wscontrolsession.h +++ b/src/proxy/wscontrolsession.h @@ -45,7 +45,7 @@ class WsControlSession : public QObject QByteArray peer() const; QByteArray cid() const; - void start(const QByteArray &routeId, bool separateStats, const QByteArray &channelPrefix, int logLevel, const QUrl &uri); + void start(const QByteArray &routeId, bool separateStats, const QByteArray &channelPrefix, int logLevel, const QUrl &uri, bool targetTrusted); void sendGripMessage(const QByteArray &message); void sendNeedKeepAlive(); void sendSubscribe(const QByteArray &channel); diff --git a/src/proxy/wsproxysession.cpp b/src/proxy/wsproxysession.cpp index aa659e03..ace6a189 100644 --- a/src/proxy/wsproxysession.cpp +++ b/src/proxy/wsproxysession.cpp @@ -949,7 +949,7 @@ private slots: wsControl->cancelEventReceived.connect(boost::bind(&Private::wsControl_cancelEventReceived, this)), wsControl->error.connect(boost::bind(&Private::wsControl_error, this)) }; - wsControl->start(route.id, route.separateStats, channelPrefix, route.logLevel, inSock->requestUri()); + wsControl->start(route.id, route.separateStats, channelPrefix, route.logLevel, inSock->requestUri(), target.trusted); foreach(const QString &subChannel, target.subscriptions) {