/*
- * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "comm.h"
#include "comm/Connection.h"
#include "err_detail_type.h"
-#include "http/one/TeChunkedParser.h"
+#include "http/ContentLengthInterpreter.h"
#include "HttpHeaderTools.h"
-#include "HttpMsg.h"
#include "HttpReply.h"
-#include "HttpRequest.h"
+#include "MasterXaction.h"
+#include "parser/Tokenizer.h"
+#include "sbuf/Stream.h"
#include "SquidTime.h"
-#include "URL.h"
// flow and terminology:
// HTTP| --> receive --> encode --> write --> |network
static const size_t TheBackupLimit = BodyPipe::MaxCapacity;
+const SBuf Adaptation::Icap::ChunkExtensionValueParser::UseOriginalBodyName("use-original-body");
+
Adaptation::Icap::ModXact::State::State()
{
memset(this, 0, sizeof(*this));
}
-Adaptation::Icap::ModXact::ModXact(HttpMsg *virginHeader,
+Adaptation::Icap::ModXact::ModXact(Http::Message *virginHeader,
HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::Icap::ServiceRep::Pointer &aService):
AsyncJob("Adaptation::Icap::ModXact"),
Adaptation::Icap::Xaction("Adaptation::Icap::ModXact", aService),
{
return
!act.active() || // did all (assuming it was originally planned)
- !virgin.body_pipe->expectMoreAfter(act.offset()); // wont have more
+ !virgin.body_pipe->expectMoreAfter(act.offset()); // will not have more
}
// the size of buffered virgin body data available for the specified activity
setOutcome(service().cfg().method == ICAP::methodReqmod ?
xoSatisfied : xoModified);
} else if (gotEncapsulated("req-hdr")) {
- adapted.setHeader(new HttpRequest);
+ adapted.setHeader(new HttpRequest(virginRequest().masterXaction));
setOutcome(xoModified);
} else
throw TexcHere("Neither res-hdr nor req-hdr in maybeAllocateHttpMsg()");
echoMore();
else {
// If we are not using the virgin HTTP object update the
- // HttpMsg::sources flag.
+ // Http::Message::sources flag.
// The state.sending may set to State::sendingVirgin in the case
- // of 206 responses too, where we do not want to update HttpMsg::sources
+ // of 206 responses too, where we do not want to update Http::Message::sources
// flag. However even for 206 responses the state.sending is
// not set yet to sendingVirgin. This is done in later step
// after the parseBody method called.
trailerParser = new TrailerParser;
}
- if (httpHeaderHasConnDir(&icapReply->header, "close")) {
+ static SBuf close("close", 5);
+ if (httpHeaderHasConnDir(&icapReply->header, close)) {
debugs(93, 5, HERE << "found connection close");
reuseConnection = false;
}
setOutcome(xoEcho);
// We want to clone the HTTP message, but we do not want
- // to copy some non-HTTP state parts that HttpMsg kids carry in them.
+ // to copy some non-HTTP state parts that Http::Message kids carry in them.
// Thus, we cannot use a smart pointer, copy constructor, or equivalent.
// Instead, we simply write the HTTP message and "clone" it by parsing.
- // TODO: use HttpMsg::clone()!
+ // TODO: use Http::Message::clone()!
- HttpMsg *oldHead = virgin.header;
+ Http::Message *oldHead = virgin.header;
debugs(93, 7, HERE << "cloning virgin message " << oldHead);
MemBuf httpBuf;
// allocate the adapted message and copy metainfo
Must(!adapted.header);
{
- HttpMsg::Pointer newHead;
- if (dynamic_cast<const HttpRequest*>(oldHead)) {
- newHead = new HttpRequest;
+ Http::MessagePointer newHead;
+ if (const HttpRequest *r = dynamic_cast<const HttpRequest*>(oldHead)) {
+ newHead = new HttpRequest(r->masterXaction);
} else if (dynamic_cast<const HttpReply*>(oldHead)) {
newHead = new HttpReply;
}
- Must(newHead != NULL);
+ Must(newHead);
newHead->inheritProperties(oldHead);
// parse the buffer back
Http::StatusCode error = Http::scNone;
- httpBuf.terminate(); // HttpMsg::parse requires nil-terminated buffer
+ httpBuf.terminate(); // Http::Message::parse requires nil-terminated buffer
Must(adapted.header->parse(httpBuf.content(), httpBuf.contentSize(), true, &error));
Must(adapted.header->hdr_sz == httpBuf.contentSize()); // no leftovers
debugs(93, 5, "have " << readBuf.length() << ' ' << description << " bytes to parse; state: " << state.parsing);
Http::StatusCode error = Http::scNone;
// XXX: performance regression. c_str() data copies
- // XXX: HttpMsg::parse requires a terminated string buffer
+ // XXX: Http::Message::parse requires a terminated string buffer
const char *tmpBuf = readBuf.c_str();
const bool parsed = part->parse(tmpBuf, readBuf.length(), commEof, &error);
debugs(93, (!parsed && error) ? 2 : 5, description << " parsing result: " << parsed << " detail: " << error);
}
// parses both HTTP and ICAP headers
-bool Adaptation::Icap::ModXact::parseHead(HttpMsg *head)
+bool
+Adaptation::Icap::ModXact::parseHead(Http::Message *head)
{
if (!parsePart(head, "head")) {
head->reset();
bool Adaptation::Icap::ModXact::expectIcapTrailers() const
{
String trailers;
- const bool promisesToSendTrailer = icapReply->header.getByIdIfPresent(Http::HdrType::TRAILER, trailers);
+ const bool promisesToSendTrailer = icapReply->header.getByIdIfPresent(Http::HdrType::TRAILER, &trailers);
const bool supportsTrailers = icapReply->header.hasListMember(Http::HdrType::ALLOW, "trailers", ',');
// ICAP Trailer specs require us to reject transactions having either Trailer
// header or Allow:trailers
state.parsing = State::psBody;
replyHttpBodySize = 0;
bodyParser = new Http1::TeChunkedParser;
+ bodyParser->parseExtensionValuesWith(&extensionParser);
makeAdaptedBodyPipe("adapted response from the ICAP server");
Must(state.sending == State::sendingAdapted);
} else {
}
if (parsed) {
- if (state.readyForUob && bodyParser->useOriginBody >= 0)
- prepPartialBodyEchoing(static_cast<uint64_t>(bodyParser->useOriginBody));
+ if (state.readyForUob && extensionParser.sawUseOriginalBody())
+ prepPartialBodyEchoing(extensionParser.useOriginalBody());
else
stopSending(true); // the parser succeeds only if all parsed data fits
if (trailerParser)
al.adapted_request = adapted_request_;
HTTPMSGLOCK(al.adapted_request);
- if (adapted_reply_) {
- al.reply = adapted_reply_;
- HTTPMSGLOCK(al.reply);
- } else
- al.reply = NULL;
+ // XXX: This reply (and other ALE members!) may have been needed earlier.
+ al.reply = adapted_reply_;
if (h->rfc931.size())
al.cache.rfc931 = h->rfc931.termedBuf();
#endif
al.cache.code = h->logType;
- const HttpMsg *virgin_msg = dynamic_cast<HttpReply*>(virgin.header);
+ const Http::Message *virgin_msg = dynamic_cast<HttpReply*>(virgin.header);
if (!virgin_msg)
virgin_msg = virgin_request_;
assert(virgin_msg != virgin.cause);
if (replyHttpBodySize >= 0)
al.cache.highOffset = replyHttpBodySize;
//don't set al.cache.objectSize because it hasn't exist yet
-
- MemBuf mb;
- mb.init();
- adapted_reply_->header.packInto(&mb);
- al.headers.reply = xstrdup(mb.buf);
- mb.clean();
}
prepareLogWithRequestDetails(adapted_request_, alep);
Xaction::finalizeLogInfo();
} else if (request->extacl_user.size() > 0 && request->extacl_passwd.size() > 0) {
struct base64_encode_ctx ctx;
base64_encode_init(&ctx);
- uint8_t base64buf[base64_encode_len(MAX_LOGIN_SZ)];
+ char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
size_t resultLen = base64_encode_update(&ctx, base64buf, request->extacl_user.size(), reinterpret_cast<const uint8_t*>(request->extacl_user.rawBuf()));
resultLen += base64_encode_update(&ctx, base64buf+resultLen, 1, reinterpret_cast<const uint8_t*>(":"));
resultLen += base64_encode_update(&ctx, base64buf+resultLen, request->extacl_passwd.size(), reinterpret_cast<const uint8_t*>(request->extacl_passwd.rawBuf()));
}
if (ICAP::methodRespmod == m)
- if (const HttpMsg *prime = virgin.header)
+ if (const Http::Message *prime = virgin.header)
encapsulateHead(buf, "res-hdr", httpBuf, prime);
if (!virginBody.expected())
const bool allow204 = allow204in || allow204out;
const bool allow206 = allow206in || allow206out;
- if (!allow204 && !allow206 && !allowTrailers)
- return; // nothing to do
-
- if (virginBody.expected()) // if there is a virgin body, plan to send it
- virginBodySending.plan();
+ if ((allow204 || allow206) && virginBody.expected())
+ virginBodySending.plan(); // if there is a virgin body, plan to send it
// writing Preview:... means we will honor 204 inside preview
// writing Allow/204 means we will honor 204 outside preview
if (value) {
if (TheConfig.client_username_encode) {
- uint8_t base64buf[base64_encode_len(MAX_LOGIN_SZ)];
+ char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
size_t resultLen = base64_encode_update(&ctx, base64buf, strlen(value), reinterpret_cast<const uint8_t*>(value));
resultLen += base64_encode_final(&ctx, base64buf+resultLen);
buf.appendf("%s: %.*s\r\n", TheConfig.client_username_header, (int)resultLen, base64buf);
#endif
}
-void Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const HttpMsg *head)
+void
+Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const Http::Message *head)
{
// update ICAP header
icapBuf.appendf("%s=%d, ", section, (int) httpBuf.contentSize());
// begin cloning
- HttpMsg::Pointer headClone;
+ Http::MessagePointer headClone;
if (const HttpRequest* old_request = dynamic_cast<const HttpRequest*>(head)) {
- HttpRequest::Pointer new_request(new HttpRequest);
- // copy the requst-line details
+ HttpRequest::Pointer new_request(new HttpRequest(old_request->masterXaction));
+ // copy the request-line details
new_request->method = old_request->method;
new_request->url = old_request->url;
new_request->http_ver = old_request->http_ver;
new_reply->sline = old_reply->sline;
headClone = new_reply.getRaw();
}
- Must(headClone != NULL);
+ Must(headClone);
headClone->inheritProperties(head);
HttpHeaderPos pos = HttpHeaderInitPos;
- HttpHeaderEntry* p_head_entry = NULL;
- while (NULL != (p_head_entry = head->header.getEntry(&pos)) )
+ while (HttpHeaderEntry* p_head_entry = head->header.getEntry(&pos))
headClone->header.addEntry(p_head_entry->clone());
// end cloning
headClone->header.delById(Http::HdrType::PROXY_AUTHENTICATE);
headClone->header.removeHopByHopEntries();
+ // TODO: modify HttpHeader::removeHopByHopEntries to accept a list of
+ // excluded hop-by-hop headers
+ if (head->header.has(Http::HdrType::UPGRADE)) {
+ const auto upgrade = head->header.getList(Http::HdrType::UPGRADE);
+ headClone->header.putStr(Http::HdrType::UPGRADE, upgrade.termedBuf());
+ }
+
// pack polished HTTP header
packHead(httpBuf, headClone.getRaw());
// headClone unlocks and, hence, deletes the message we packed
}
-void Adaptation::Icap::ModXact::packHead(MemBuf &httpBuf, const HttpMsg *head)
+void
+Adaptation::Icap::ModXact::packHead(MemBuf &httpBuf, const Http::Message *head)
{
head->packInto(&httpBuf, true);
}
bool Adaptation::Icap::ModXact::gotEncapsulated(const char *section) const
{
- return icapReply->header.getByNameListMember("Encapsulated",
- section, ',').size() > 0;
+ return !icapReply->header.getByNameListMember("Encapsulated",
+ section, ',').isEmpty();
}
// calculate whether there is a virgin HTTP body and
{
// note: lack of size info may disable previews and 204s
- HttpMsg *msg = virgin.header;
+ Http::Message *msg = virgin.header;
Must(msg);
HttpRequestMethod method;
void Adaptation::Icap::ModXact::updateSources()
{
Must(adapted.header);
- adapted.header->sources |= (service().cfg().connectionEncryption ? HttpMsg::srcIcaps : HttpMsg::srcIcap);
+ adapted.header->sources |= (service().cfg().connectionEncryption ? Http::Message::srcIcaps : Http::Message::srcIcap);
}
/* Adaptation::Icap::ModXactLauncher */
-Adaptation::Icap::ModXactLauncher::ModXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::ServicePointer aService):
+Adaptation::Icap::ModXactLauncher::ModXactLauncher(Http::Message *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::ServicePointer aService):
AsyncJob("Adaptation::Icap::ModXactLauncher"),
Adaptation::Icap::Launcher("Adaptation::Icap::ModXactLauncher", aService),
al(alp)
}
bool Adaptation::Icap::TrailerParser::parse(const char *buf, int len, int atEnd, Http::StatusCode *error) {
- const int parsed = trailer.parse(buf, len, atEnd, hdr_sz);
+ Http::ContentLengthInterpreter clen;
+ // RFC 7230 section 4.1.2: MUST NOT generate a trailer that contains
+ // a field necessary for message framing (e.g., Transfer-Encoding and Content-Length)
+ clen.applyTrailerRules();
+ const int parsed = trailer.parse(buf, len, atEnd, hdr_sz, clen);
if (parsed < 0)
*error = Http::scInvalidHeader; // TODO: should we add a new Http::scInvalidTrailer?
return parsed > 0;
}
+void
+Adaptation::Icap::ChunkExtensionValueParser::parse(Tokenizer &tok, const SBuf &extName)
+{
+ if (extName == UseOriginalBodyName) {
+ useOriginalBody_ = tok.udec64("use-original-body");
+ assert(useOriginalBody_ >= 0);
+ } else {
+ Ignore(tok, extName);
+ }
+}
+