This is RFC 7230 section 3.3.2 requirement.
}
int
-HttpHeader::parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz)
+HttpHeader::parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz, Http::ContentLengthInterpreter &clen)
{
const char *parse_start = buf;
const char *blk_start, *blk_end;
blk_end = blk_start + strlen(blk_start);
}
- if (parse(blk_start, blk_end - blk_start)) {
+ if (parse(blk_start, blk_end - blk_start, clen)) {
hdr_sz = parse_start - buf;
return 1;
}
}
int
-HttpHeader::parse(const char *header_start, size_t hdrLen)
+HttpHeader::parse(const char *header_start, size_t hdrLen, Http::ContentLengthInterpreter &clen)
{
const char *field_ptr = header_start;
const char *header_end = header_start + hdrLen; // XXX: remove
return 0;
}
- Http::ContentLengthInterpreter clen(warnOnError);
/* common format headers are "<name>:[ws]<value>" lines delimited by <CRLF>.
* continuation lines start with a (single) space or tab */
while (field_ptr < header_end) {
Raw("header", header_start, hdrLen));
}
- if (chunked()) {
+ if (clen.prohibitedAndIgnored()) {
+ // RFC 7230 section 3.3.2: A server MUST NOT send a Content-Length
+ // header field in any response with a status code of 1xx (Informational)
+ // or 204 (No Content). And RFC 7230 3.3.3#1 tells recipients to ignore
+ // such Content-Lengths.
+ if (delById(Http::HdrType::CONTENT_LENGTH))
+ debugs(55, 3, "Content-Length is " << clen.prohibitedAndIgnored());
+ } else if (chunked()) {
// RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
// RFC 7230 section 3.3.3 #3: Transfer-Encoding overwrites Content-Length
delById(Http::HdrType::CONTENT_LENGTH);
void append(const HttpHeader * src);
bool update(HttpHeader const *fresh);
void compact();
- int parse(const char *header_start, size_t len);
+ int parse(const char *header_start, size_t len, Http::ContentLengthInterpreter &interpreter);
/// Parses headers stored in a buffer.
/// \returns 1 and sets hdr_sz on success
/// \returns 0 when needs more data
/// \returns -1 on error
- int parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz);
+ int parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz, Http::ContentLengthInterpreter &interpreter);
void packInto(Packable * p, bool mask_sensitive_info=false) const;
HttpHeaderEntry *getEntry(HttpHeaderPos * pos) const;
HttpHeaderEntry *findEntry(Http::HdrType id) const;
#include "acl/FilledChecklist.h"
#include "base/EnumIterator.h"
#include "globals.h"
+#include "http/ContentLengthInterpreter.h"
#include "HttpBody.h"
#include "HttpHdrCc.h"
#include "HttpHdrContRange.h"
return sline.parse(protoPrefix, blk_start, blk_end);
}
+void
+HttpReply::configureContentLengthInterpreter(Http::ContentLengthInterpreter &interpreter)
+{
+ interpreter.applyStatusCodeRules(sline.status());
+}
+
+bool
+HttpReply::parseHeader(Http1::Parser &hp)
+{
+ Http::ContentLengthInterpreter clen;
+ return Message::parseHeader(hp, clen);
+}
+
/* handy: resets and returns -1 */
int
HttpReply::httpMsgParseError()
return date < them->date;
}
+void
+HttpReply::removeIrrelevantContentLength() {
+ if (Http::ProhibitsContentLength(sline.status()))
+ if (header.delById(Http::HdrType::CONTENT_LENGTH))
+ debugs(58, 3, "Removing unexpected Content-Length header");
+}
+
/// \returns false if any information is missing
bool olderThan(const HttpReply *them) const;
+ /// Some response status codes prohibit sending Content-Length (RFC 7230 section 3.3.2).
+ void removeIrrelevantContentLength();
+
+ virtual void configureContentLengthInterpreter(Http::ContentLengthInterpreter &);
+ /// parses reply header using Parser
+ bool parseHeader(Http1::Parser &hp);
+
private:
/** initialize */
void init();
#include "globals.h"
#include "gopher.h"
#include "http.h"
+#include "http/ContentLengthInterpreter.h"
#include "http/one/RequestParser.h"
#include "http/Stream.h"
#include "HttpHdrCc.h"
return true;
}
+bool
+HttpRequest::parseHeader(Http1::Parser &hp)
+{
+ Http::ContentLengthInterpreter clen;
+ return Message::parseHeader(hp, clen);
+}
+
+bool
+HttpRequest::parseHeader(const char *buffer, const size_t size)
+{
+ Http::ContentLengthInterpreter clen;
+ return header.parse(buffer, size, clen);
+}
+
ConnStateData *
HttpRequest::pinnedConnection()
{
NotePairs::Pointer notes();
bool hasNotes() const { return bool(theNotes) && !theNotes->empty(); }
+ virtual void configureContentLengthInterpreter(Http::ContentLengthInterpreter &) {}
+
+ /// Parses request header using Parser.
+ /// Use it in contexts where the Parser object is available.
+ bool parseHeader(Http1::Parser &hp);
+ /// Parses request header from the buffer.
+ /// Use it in contexts where the Parser object not available.
+ bool parseHeader(const char *buffer, const size_t size);
+
private:
mutable int64_t rangeOffsetLimit; /* caches the result of getRangeOffsetLimit */
#include "comm.h"
#include "comm/Connection.h"
#include "err_detail_type.h"
+#include "http/ContentLengthInterpreter.h"
#include "http/one/TeChunkedParser.h"
#include "HttpHeaderTools.h"
#include "HttpReply.h"
}
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;
}
reply->header.removeHopByHopEntries();
+ // paranoid: ContentLengthInterpreter has cleaned non-generated replies
+ reply->removeIrrelevantContentLength();
// if (request->range)
// clientBuildRangeHeader(http, reply);
assert(serverState() == fssConnected ||
serverState() == fssHandleUploadRequest);
- if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ if (Is1xx(ctrl.replycode))
forwardPreliminaryReply(&Ftp::Relay::scheduleReadControlReply);
else
forwardReply();
{
assert(serverState() == fssHandleFeat);
- if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ if (Is1xx(ctrl.replycode))
return; // ignore preliminary replies
forwardReply();
{
assert(serverState() == fssHandlePasv || serverState() == fssHandleEpsv || serverState() == fssHandlePort || serverState() == fssHandleEprt);
- if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ if (Is1xx(ctrl.replycode))
return; // ignore preliminary replies
if (handlePasvReply(updateMaster().clientDataAddr))
void
Ftp::Relay::readEpsvReply()
{
- if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ if (Is1xx(ctrl.replycode))
return; // ignore preliminary replies
if (handleEpsvReply(updateMaster().clientDataAddr)) {
debugs(9, 5, "got code " << ctrl.replycode << ", msg: " << ctrl.last_reply);
- if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ if (Is1xx(ctrl.replycode))
return;
if (weAreTrackingDir()) { // we are tracking
void
Ftp::Relay::readUserOrPassReply()
{
- if (100 <= ctrl.replycode && ctrl.replycode < 200)
+ if (Is1xx(ctrl.replycode))
return; //Just ignore
if (weAreTrackingDir()) { // we are tracking
const SBuf &cmdStou();
const SBuf &cmdUser();
+/// whether this is an informational 1xx response status code
+inline bool Is1xx(const int sc) { return Http::scContinue <= sc && sc < Http::scOkay; }
+
} // namespace Ftp
#endif /* SQUID_FTP_ELEMENTS_H */
#include "globals.h"
#include "htcp.h"
#include "http.h"
+#include "http/ContentLengthInterpreter.h"
#include "HttpRequest.h"
#include "icmp/net_db.h"
#include "ip/tools.h"
return;
}
- if (!checkHitRequest->header.parse(req_hdrs, reqHdrsSz)) {
+ if (!checkHitRequest->parseHeader(req_hdrs, reqHdrsSz)) {
debugs(31, 3, "htcpCheckHit: NO; failed to parse request headers");
checkHitRequest = nullptr;
checkedHit(nullptr);
}
/* Parse request headers */
- if (!request->header.parse(s->req_hdrs, s->reqHdrsSz)) {
+ if (!request->parseHeader(s->req_hdrs, s->reqHdrsSz)) {
debugs(31, 2, "htcpClrStore: failed to parse request headers");
return -1;
}
memset(&cto, 0, sizeof(cto));
}
+bool
+HtcpReplyData::parseHeader(const char *buffer, const size_t size)
+{
+ Http::ContentLengthInterpreter interpreter;
+ // no applyStatusCodeRules() -- HTCP replies lack cached HTTP status code
+ return hdr.parse(buffer, size, interpreter);
+}
+
static void
htcpHandleTstResponse(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from)
}
if ((t = d->resp_hdrs))
- htcpReply.hdr.parse(t, d->respHdrsSz);
+ htcpReply.parseHeader(t, d->respHdrsSz);
if ((t = d->entity_hdrs))
- htcpReply.hdr.parse(t, d->entityHdrsSz);
+ htcpReply.parseHeader(t, d->entityHdrsSz);
if ((t = d->cache_hdrs))
- htcpReply.hdr.parse(t, d->cacheHdrsSz);
+ htcpReply.parseHeader(t, d->cacheHdrsSz);
}
debugs(31, 3, "htcpHandleTstResponse: key (" << key << ") " << storeKeyText(key));
public:
HtcpReplyData();
+
+ /// parses request header from the buffer
+ bool parseHeader(const char *buffer, const size_t size);
+
int hit;
HttpHeader hdr;
uint32_t msg_id;
newrep->removeStaleWarnings();
- if (newrep->sline.protocol == AnyP::PROTO_HTTP && newrep->sline.status() >= 100 && newrep->sline.status() < 200) {
+ if (newrep->sline.protocol == AnyP::PROTO_HTTP && Http::Is1xx(newrep->sline.status())) {
handle1xx(newrep);
ctx_exit(ctx);
return;
#include "SquidString.h"
#include "StrList.h"
-Http::ContentLengthInterpreter::ContentLengthInterpreter(const int aDebugLevel):
+Http::ContentLengthInterpreter::ContentLengthInterpreter():
value(-1),
headerWideProblem(nullptr),
- debugLevel(aDebugLevel),
+ debugLevel(Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2),
sawBad(false),
needsSanitizing(false),
- sawGood(false)
+ sawGood(false),
+ prohibitedAndIgnored_(nullptr)
{
}
#ifndef SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H
#define SQUID_SRC_HTTP_CONTENTLENGTH_INTERPRETER_H
+#include "http/StatusCode.h"
+
class String;
namespace Http
class ContentLengthInterpreter
{
public:
- explicit ContentLengthInterpreter(const int aDebugLevel);
+ ContentLengthInterpreter();
/// updates history based on the given message-header field
/// \return true iff the field should be added/remembered for future use
bool checkField(const String &field);
+ /// prohibits Content-Length in 1xx and 204 responses
+ void applyStatusCodeRules(const StatusCode code) {
+ if (!prohibitedAndIgnored_ && ProhibitsContentLength(code))
+ prohibitedAndIgnored_ = (code == scNoContent) ? "prohibited and ignored in the 204 response" :
+ "prohibited and ignored the 1xx response";
+ }
+
+ // TODO: implement
+ /// prohibits Content-Length in GET/HEAD requests
+ // void applyRequestMethodRules(const Http::MethodType method);
+
+ /// prohibits Content-Length in trailer
+ void applyTrailerRules() {
+ if (!prohibitedAndIgnored_)
+ prohibitedAndIgnored_ = "prohibited in trailers";
+ }
+
+ const char *prohibitedAndIgnored() const { return prohibitedAndIgnored_; }
+
/// intended Content-Length value if sawGood is set and sawBad is not set
/// meaningless otherwise
int64_t value;
bool goodSuffix(const char *suffix, const char * const end) const;
bool checkValue(const char *start, const int size);
bool checkList(const String &list);
+
+private:
+ /// whether and why Content-Length is prohibited
+ const char *prohibitedAndIgnored_;
};
} // namespace Http
#include "squid.h"
#include "Debug.h"
+#include "http/ContentLengthInterpreter.h"
#include "http/Message.h"
#include "http/one/Parser.h"
#include "HttpHdrCc.h"
*/
if (pstate == Http::Message::psReadyToParseHeaders) {
size_t hsize = 0;
- const int parsed = header.parse(parse_start, parse_len, atEnd, hsize);
+ Http::ContentLengthInterpreter interpreter;
+ configureContentLengthInterpreter(interpreter);
+ const int parsed = header.parse(parse_start, parse_len, atEnd, hsize, interpreter);
if (parsed <= 0) {
PROF_stop(HttpMsg_httpMsgParseStep);
return !parsed ? 0 : httpMsgParseError();
}
bool
-Http::Message::parseHeader(Http1::Parser &hp)
+Http::Message::parseHeader(Http1::Parser &hp, Http::ContentLengthInterpreter &clen)
{
// HTTP/1 message contains "zero or more header fields"
// zero does not need parsing
// XXX: c_str() reallocates. performance regression.
- if (hp.headerBlockSize() && !header.parse(hp.mimeHeader().c_str(), hp.headerBlockSize())) {
+ configureContentLengthInterpreter(clen);
+ if (hp.headerBlockSize() && !header.parse(hp.mimeHeader().c_str(), hp.headerBlockSize(), clen)) {
pstate = Http::Message::psError;
return false;
}
virtual int httpMsgParseError();
- // Parser-NG transitional parsing of mime headers
- bool parseHeader(Http1::Parser &); // TODO move this function to the parser
-
virtual bool expectingBody(const HttpRequestMethod&, int64_t&) const = 0;
void firstLineBuf(MemBuf&);
virtual bool parseFirstLine(const char *blk_start, const char *blk_end) = 0;
virtual void hdrCacheInit();
+
+ /// configures the interpreter as needed
+ virtual void configureContentLengthInterpreter(Http::ContentLengthInterpreter &) = 0;
+
+ // Parser-NG transitional parsing of mime headers
+ bool parseHeader(Http1::Parser &, Http::ContentLengthInterpreter &); // TODO move this function to the parser
};
} // namespace Http
} StatusCode;
const char *StatusCodeString(const Http::StatusCode status);
+/// whether this is an informational 1xx response status code
+inline bool Is1xx(const int sc) { return scContinue <= sc && sc < scOkay; }
+/// whether this response status code prohibits sending Content-Length
+inline bool ProhibitsContentLength(const StatusCode sc) { return sc == scNoContent || Is1xx(sc); }
} // namespace Http
namespace Http
{
+class ContentLengthInterpreter;
+
class Message;
typedef RefCount<Http::Message> MessagePointer;
// apply selected clientReplyContext::buildReplyHeader() mods
// it is not clear what headers are required for control messages
rep->header.removeHopByHopEntries();
+ // paranoid: ContentLengthInterpreter has cleaned non-generated replies
+ rep->removeIrrelevantContentLength();
rep->header.putStr(Http::HdrType::CONNECTION, "keep-alive");
httpHdrMangleList(&rep->header, http->request, http->al, ROR_REPLY);
#include "ErrorDetail.h"
#include "ErrorDetailManager.h"
#include "errorpage.h"
+#include "http/ContentLengthInterpreter.h"
#include "mime_header.h"
void Ssl::errorDetailInitialize()
if ( s != e) {
DetailEntryParser parser;
- if (!parser.parse(s, e - s)) {
+ Http::ContentLengthInterpreter interpreter;
+ // no applyStatusCodeRules() -- error templates lack HTTP status code
+ if (!parser.parse(s, e - s, interpreter)) {
debugs(83, DBG_IMPORTANT, HERE <<
"WARNING! parse error on:" << s);
return false;
bool HttpReply::updateOnNotModified(HttpReply const*) STUB_RETVAL(false)
int64_t HttpReply::bodySize(const HttpRequestMethod&) const STUB_RETVAL(0)
const HttpHdrContRange *HttpReply::contentRange() const STUB_RETVAL(nullptr)
+void HttpReply::configureContentLengthInterpreter(Http::ContentLengthInterpreter &) STUB