2323
2424#include " filter.h"
2525
26+ #include < QJsonDocument>
27+ #include < QJsonObject>
2628#include " log.h"
2729#include " format.h"
2830#include " idformat.h"
31+ #include " zhttpmanager.h"
32+ #include " zhttprequest.h"
2933
3034namespace {
3135
@@ -304,6 +308,202 @@ class VarSubstFilter : public Filter, public Filter::MessageFilter
304308 }
305309};
306310
311+ class HttpFilter : public Filter ::MessageFilter
312+ {
313+ public:
314+ enum Mode
315+ {
316+ Check,
317+ Modify
318+ };
319+
320+ Mode mode;
321+ std::unique_ptr<ZhttpRequest> req;
322+ boost::signals2::scoped_connection readyReadConnection;
323+ boost::signals2::scoped_connection errorConnection;
324+ QByteArray origContent;
325+ bool haveResponseHeader;
326+ QByteArray responseBody;
327+
328+ HttpFilter (Mode _mode) :
329+ mode (_mode),
330+ haveResponseHeader (false )
331+ {
332+ }
333+
334+ virtual void start (const Filter::Context &context, const QByteArray &content)
335+ {
336+ QUrl url = QUrl (context.subscriptionMeta .value (" url" ), QUrl::StrictMode);
337+ if (!url.isValid ())
338+ {
339+ Result r;
340+ r.errorMessage = " invalid or missing url value" ;
341+ finished (r);
342+ return ;
343+ }
344+
345+ QUrl currentUri = context.currentUri ;
346+ if (currentUri.scheme () == " wss" )
347+ currentUri.setScheme (" https" );
348+ else if (currentUri.scheme () == " ws" )
349+ currentUri.setScheme (" http" );
350+
351+ QUrl destUri = currentUri.resolved (url);
352+
353+ origContent = content;
354+
355+ req.reset (context.zhttpOut ->createRequest ());
356+ readyReadConnection = req->readyRead .connect (boost::bind (&HttpFilter::req_readyRead, this ));
357+ errorConnection = req->error .connect (boost::bind (&HttpFilter::req_error, this ));
358+
359+ int currentPort = currentUri.port (currentUri.scheme () == " https" ? 443 : 80 );
360+ int destPort = destUri.port (destUri.scheme () == " https" ? 443 : 80 );
361+
362+ QVariantHash passthroughData;
363+
364+ passthroughData[" route" ] = context.route .toUtf8 ();
365+
366+ // if dest link points to the same service as the current request,
367+ // then we can assume the network would send the request back to
368+ // us, so we can handle it internally. if the link points to a
369+ // different service, then we can't make this assumption and need
370+ // to make the request over the network. note that such a request
371+ // could still end up looping back to us
372+ if (destUri.scheme () == currentUri.scheme () && destUri.host () == currentUri.host () && destPort == currentPort)
373+ {
374+ // tell the proxy that we prefer the request to be handled
375+ // internally, using the same route
376+ passthroughData[" prefer-internal" ] = true ;
377+ }
378+
379+ // needed in case internal routing is not used
380+ if (context.trusted )
381+ passthroughData[" trusted" ] = true ;
382+
383+ req->setPassthroughData (passthroughData);
384+
385+ HttpHeaders headers;
386+
387+ {
388+ QVariantMap vmap;
389+ QHashIterator<QString, QString> it (context.subscriptionMeta );
390+ while (it.hasNext ())
391+ {
392+ it.next ();
393+ vmap[it.key ()] = it.value ();
394+ }
395+
396+ QJsonDocument doc = QJsonDocument (QJsonObject::fromVariantMap (vmap));
397+ headers += HttpHeader (" Sub-Meta" , doc.toJson (QJsonDocument::Compact));
398+ }
399+
400+ {
401+ QVariantMap vmap;
402+ QHashIterator<QString, QString> it (context.publishMeta );
403+ while (it.hasNext ())
404+ {
405+ it.next ();
406+ vmap[it.key ()] = it.value ();
407+ }
408+
409+ QJsonDocument doc = QJsonDocument (QJsonObject::fromVariantMap (vmap));
410+ headers += HttpHeader (" Pub-Meta" , doc.toJson (QJsonDocument::Compact));
411+ }
412+
413+ {
414+ QHashIterator<QString, QString> it (context.prevIds );
415+ while (it.hasNext ())
416+ {
417+ it.next ();
418+ const QString &name = it.key ();
419+ const QString &prevId = it.value ();
420+
421+ if (!prevId.isNull ())
422+ headers += HttpHeader (" Grip-Last" , name.toUtf8 () + " ; last-id=" + prevId.toUtf8 ());
423+ }
424+ }
425+
426+ req->start (" POST" , destUri, headers);
427+
428+ if (mode == Modify)
429+ req->writeBody (content);
430+
431+ req->endBody ();
432+ }
433+
434+ void req_readyRead ()
435+ {
436+ if (!haveResponseHeader)
437+ {
438+ haveResponseHeader = true ;
439+
440+ int code = req->responseCode ();
441+ switch (code)
442+ {
443+ case 200 :
444+ case 204 :
445+ break ;
446+ default :
447+ Result r;
448+ r.errorMessage = QString (" unexpected network request status: code=%1" ).arg (code);
449+ finished (r);
450+ return ;
451+ }
452+ }
453+
454+ QByteArray body = req->readBody ();
455+
456+ if (mode == Modify)
457+ responseBody += body;
458+
459+ if (!req->isFinished ())
460+ return ;
461+
462+ Result r;
463+
464+ if (req->responseHeaders ().get (" Action" ) == " drop" )
465+ {
466+ // drop
467+ r.sendAction = Filter::Drop;
468+ }
469+ else
470+ {
471+ // accept
472+ r.sendAction = Filter::Send;
473+
474+ switch (mode)
475+ {
476+ case Check:
477+ // as-is
478+ r.content = origContent;
479+ break ;
480+ case Modify:
481+ switch (req->responseCode ())
482+ {
483+ case 204 :
484+ // as-is
485+ r.content = origContent;
486+ break ;
487+ default :
488+ // replace content
489+ r.content = responseBody;
490+ break ;
491+ }
492+ break ;
493+ }
494+ }
495+
496+ finished (r);
497+ }
498+
499+ void req_error ()
500+ {
501+ Result r;
502+ r.errorMessage = " network request failed" ;
503+ finished (r);
504+ }
505+ };
506+
307507}
308508
309509Filter::MessageFilter::~MessageFilter () = default ;
@@ -371,6 +571,10 @@ Filter::MessageFilter *Filter::createMessageFilter(const QString &name)
371571 return new BuildIdFilter;
372572 else if (name == " var-subst" )
373573 return new VarSubstFilter;
574+ else if (name == " http-check" )
575+ return new HttpFilter (HttpFilter::Check);
576+ else if (name == " http-modify" )
577+ return new HttpFilter (HttpFilter::Modify);
374578 else
375579 return 0 ;
376580}
@@ -382,7 +586,9 @@ QStringList Filter::names()
382586 << " skip-users"
383587 << " require-sub"
384588 << " build-id"
385- << " var-subst" );
589+ << " var-subst"
590+ << " http-check"
591+ << " http-modify" );
386592}
387593
388594Filter::Targets Filter::targets (const QString &name)
@@ -397,6 +603,10 @@ Filter::Targets Filter::targets(const QString &name)
397603 return Filter::Targets (Filter::MessageContent | Filter::ResponseContent);
398604 else if (name == " var-subst" )
399605 return Filter::MessageContent;
606+ else if (name == " http-check" )
607+ return Filter::MessageDelivery;
608+ else if (name == " http-modify" )
609+ return Filter::Targets (Filter::MessageDelivery | Filter::MessageContent);
400610 else
401611 return Filter::Targets (0 );
402612}
0 commit comments