2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 73 HTTP Request */
12 #include "AccessLogEntry.h"
13 #include "acl/AclSizeLimit.h"
14 #include "acl/FilledChecklist.h"
15 #include "CachePeer.h"
16 #include "client_side.h"
17 #include "client_side_request.h"
18 #include "dns/LookupDetails.h"
19 #include "Downloader.h"
20 #include "error/Detail.h"
24 #include "http/ContentLengthInterpreter.h"
25 #include "http/one/RequestParser.h"
26 #include "http/Stream.h"
27 #include "HttpHdrCc.h"
28 #include "HttpHeaderRange.h"
29 #include "HttpRequest.h"
30 #include "log/Config.h"
32 #include "sbuf/StringConvert.h"
33 #include "SquidConfig.h"
37 #include "auth/UserRequest.h"
40 #include "adaptation/icap/icap_log.h"
43 HttpRequest::HttpRequest(const MasterXaction::Pointer
&mx
) :
44 Http::Message(hoRequest
),
51 HttpRequest::HttpRequest(const HttpRequestMethod
& aMethod
, AnyP::ProtocolType aProtocol
, const char *aSchemeImg
, const char *aUrlpath
, const MasterXaction::Pointer
&mx
) :
52 Http::Message(hoRequest
),
56 static unsigned int id
= 1;
57 debugs(93,7, "constructed, this=" << this << " id=" << ++id
);
59 initHTTP(aMethod
, aProtocol
, aSchemeImg
, aUrlpath
);
62 HttpRequest::~HttpRequest()
65 debugs(93,7, "destructed, this=" << this);
69 HttpRequest::initHTTP(const HttpRequestMethod
& aMethod
, AnyP::ProtocolType aProtocol
, const char *aSchemeImg
, const char *aUrlpath
)
72 url
.setScheme(aProtocol
, aSchemeImg
);
79 method
= Http::METHOD_NONE
;
82 auth_user_request
= NULL
;
84 flags
= RequestFlags();
89 client_addr
.setEmpty();
95 peer_login
= NULL
; // not allocated/deallocated by this class
96 peer_domain
= NULL
; // not allocated/deallocated by this class
98 vary_headers
= SBuf();
99 myportname
= null_string
;
102 extacl_user
= null_string
;
103 extacl_passwd
= null_string
;
105 extacl_log
= null_string
;
106 extacl_message
= null_string
;
107 pstate
= Http::Message::psReadyToParseStartLine
;
108 #if FOLLOW_X_FORWARDED_FOR
109 indirect_client_addr
.setEmpty();
110 #endif /* FOLLOW_X_FORWARDED_FOR */
112 adaptHistory_
= NULL
;
117 rangeOffsetLimit
= -2; //a value of -2 means not checked yet
118 forcedBodyContinuation
= false;
124 // we used to assert that the pipe is NULL, but now the request only
125 // points to a pipe that is owned and initiated by another object.
128 auth_user_request
= NULL
;
130 vary_headers
.clear();
136 delete cache_control
;
137 cache_control
= NULL
;
152 extacl_passwd
.clean();
156 extacl_message
.clean();
161 adaptHistory_
= NULL
;
176 HttpRequest::clone() const
178 HttpRequest
*copy
= new HttpRequest(masterXaction
);
179 copy
->method
= method
;
180 // TODO: move common cloning clone to Msg::copyTo() or copy ctor
181 copy
->header
.append(&header
);
182 copy
->hdrCacheInit();
183 copy
->hdr_sz
= hdr_sz
;
184 copy
->http_ver
= http_ver
;
185 copy
->pstate
= pstate
; // TODO: should we assert a specific state here?
186 copy
->body_pipe
= body_pipe
;
190 // range handled in hdrCacheInit()
192 copy
->imslen
= imslen
;
193 copy
->hier
= hier
; // Is it safe to copy? Should we?
197 // XXX: what to do with copy->peer_login?
199 copy
->lastmod
= lastmod
;
201 copy
->vary_headers
= vary_headers
;
202 // XXX: what to do with copy->peer_domain?
205 copy
->extacl_log
= extacl_log
;
206 copy
->extacl_message
= extacl_message
;
208 const bool inheritWorked
= copy
->inheritProperties(this);
209 assert(inheritWorked
);
215 HttpRequest::inheritProperties(const Http::Message
*aMsg
)
217 const HttpRequest
* aReq
= dynamic_cast<const HttpRequest
*>(aMsg
);
221 client_addr
= aReq
->client_addr
;
222 #if FOLLOW_X_FORWARDED_FOR
223 indirect_client_addr
= aReq
->indirect_client_addr
;
225 my_addr
= aReq
->my_addr
;
227 dnsWait
= aReq
->dnsWait
;
230 adaptHistory_
= aReq
->adaptHistory();
233 icapHistory_
= aReq
->icapHistory();
236 // This may be too conservative for the 204 No Content case
237 // may eventually need cloneNullAdaptationImmune() for that.
238 flags
= aReq
->flags
.cloneAdaptationImmune();
242 auth_user_request
= aReq
->auth_user_request
;
243 extacl_user
= aReq
->extacl_user
;
244 extacl_passwd
= aReq
->extacl_passwd
;
247 myportname
= aReq
->myportname
;
249 forcedBodyContinuation
= aReq
->forcedBodyContinuation
;
251 // main property is which connection the request was received on (if any)
252 clientConnectionManager
= aReq
->clientConnectionManager
;
254 downloader
= aReq
->downloader
;
256 theNotes
= aReq
->theNotes
;
258 sources
= aReq
->sources
;
263 * Checks the first line of an HTTP request is valid
264 * currently just checks the request method is present.
266 * NP: Other errors are left for detection later in the parse.
269 HttpRequest::sanityCheckStartLine(const char *buf
, const size_t hdr_len
, Http::StatusCode
*scode
)
271 // content is long enough to possibly hold a reply
272 // 2 being magic size of a 1-byte request method plus space delimiter
274 // this is only a real error if the headers apparently complete.
276 debugs(58, 3, "Too large request header (" << hdr_len
<< " bytes)");
277 *scode
= Http::scInvalidHeader
;
282 /* See if the request buffer starts with a non-whitespace HTTP request 'method'. */
284 m
.HttpRequestMethodXXX(buf
);
285 if (m
== Http::METHOD_NONE
) {
286 debugs(73, 3, "HttpRequest::sanityCheckStartLine: did not find HTTP request method");
287 *scode
= Http::scInvalidHeader
;
295 HttpRequest::parseFirstLine(const char *start
, const char *end
)
297 method
.HttpRequestMethodXXX(start
);
299 if (method
== Http::METHOD_NONE
)
302 // XXX: performance regression, strcspn() over the method bytes a second time.
303 // cheaper than allocate+copy+deallocate cycle to SBuf convert a piece of start.
304 const char *t
= start
+ strcspn(start
, w_space
);
306 start
= t
+ strspn(t
, w_space
); // skip w_space after method
308 const char *ver
= findTrailingHTTPVersion(start
, end
);
313 while (xisspace(*end
)) // find prev non-space
316 ++end
; // back to space
318 if (2 != sscanf(ver
+ 5, "%d.%d", &http_ver
.major
, &http_ver
.minor
)) {
319 debugs(73, DBG_IMPORTANT
, "ERROR: parseRequestLine: Invalid HTTP identifier.");
327 if (end
< start
) // missing URI
330 return url
.parse(method
, SBuf(start
, size_t(end
-start
)));
333 /* swaps out request using httpRequestPack */
335 HttpRequest::swapOut(StoreEntry
* e
)
343 /* packs request-line and headers, appends <crlf> terminator */
345 HttpRequest::pack(Packable
* p
) const
348 /* pack request-line */
349 p
->appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" HTTP/%d.%d\r\n",
350 SQUIDSBUFPRINT(method
.image()), SQUIDSBUFPRINT(url
.path()),
351 http_ver
.major
, http_ver
.minor
);
355 p
->append("\r\n", 2);
359 * A wrapper for debugObj()
362 httpRequestPack(void *obj
, Packable
*p
)
364 HttpRequest
*request
= static_cast<HttpRequest
*>(obj
);
368 /* returns the length of request line + headers + crlf */
370 HttpRequest::prefixLen() const
372 return method
.image().length() + 1 +
373 url
.path().length() + 1 +
378 /* sync this routine when you update HttpRequest struct */
380 HttpRequest::hdrCacheInit()
382 Http::Message::hdrCacheInit();
385 range
= header
.getRange();
389 Adaptation::Icap::History::Pointer
390 HttpRequest::icapHistory() const
393 if (Log::TheConfig
.hasIcapToken
|| IcapLogfileStatus
== LOG_ENABLE
) {
394 icapHistory_
= new Adaptation::Icap::History();
395 debugs(93,4, "made " << icapHistory_
<< " for " << this);
404 Adaptation::History::Pointer
405 HttpRequest::adaptHistory(bool createIfNone
) const
407 if (!adaptHistory_
&& createIfNone
) {
408 adaptHistory_
= new Adaptation::History();
409 debugs(93,4, "made " << adaptHistory_
<< " for " << this);
412 return adaptHistory_
;
415 Adaptation::History::Pointer
416 HttpRequest::adaptLogHistory() const
418 return HttpRequest::adaptHistory(Log::TheConfig
.hasAdaptToken
);
422 HttpRequest::adaptHistoryImport(const HttpRequest
&them
)
424 if (!adaptHistory_
) {
425 adaptHistory_
= them
.adaptHistory_
; // may be nil
427 // check that histories did not diverge
428 Must(!them
.adaptHistory_
|| them
.adaptHistory_
== adaptHistory_
);
435 HttpRequest::multipartRangeRequest() const
437 return (range
&& range
->specs
.size() > 1);
441 HttpRequest::bodyNibbled() const
443 return body_pipe
!= NULL
&& body_pipe
->consumedSize() > 0;
447 HttpRequest::prepForPeering(const CachePeer
&peer
)
449 // XXX: Saving two pointers to memory controlled by an independent object.
450 peer_login
= peer
.login
;
451 peer_domain
= peer
.domain
;
452 flags
.auth_no_keytab
= peer
.options
.auth_no_keytab
;
453 debugs(11, 4, this << " to " << peer
.host
<< (!peer
.options
.originserver
? " proxy" : " origin"));
457 HttpRequest::prepForDirect()
459 peer_login
= nullptr;
460 peer_domain
= nullptr;
461 flags
.auth_no_keytab
= false;
466 HttpRequest::clearError()
468 debugs(11, 7, "old: " << error
);
473 HttpRequest::packFirstLineInto(Packable
* p
, bool full_uri
) const
475 const SBuf
tmp(full_uri
? effectiveRequestUri() : url
.path());
477 // form HTTP request-line
478 p
->appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" HTTP/%d.%d\r\n",
479 SQUIDSBUFPRINT(method
.image()),
481 http_ver
.major
, http_ver
.minor
);
485 * Indicate whether or not we would expect an entity-body
486 * along with this request
489 HttpRequest::expectingBody(const HttpRequestMethod
&, int64_t &theSize
) const
491 bool expectBody
= false;
494 * Note: Checks for message validity is in clientIsContentLengthValid().
495 * this just checks if a entity-body is expected based on HTTP message syntax
497 if (header
.chunked()) {
500 } else if (content_length
>= 0) {
502 theSize
= content_length
;
512 * Create a Request from a URL and METHOD.
514 * If the METHOD is CONNECT, then a host:port pair is looked for instead of a URL.
515 * If the request cannot be created cleanly, NULL is returned
518 HttpRequest::FromUrl(const SBuf
&url
, const MasterXaction::Pointer
&mx
, const HttpRequestMethod
& method
)
520 std::unique_ptr
<HttpRequest
> req(new HttpRequest(mx
));
521 if (req
->url
.parse(method
, url
)) {
522 req
->method
= method
;
523 return req
.release();
529 HttpRequest::FromUrlXXX(const char * url
, const MasterXaction::Pointer
&mx
, const HttpRequestMethod
& method
)
531 return FromUrl(SBuf(url
), mx
, method
);
535 * Are responses to this request possible cacheable ?
536 * If false then no matter what the response must not be cached.
539 HttpRequest::maybeCacheable()
541 // Intercepted request with Host: header which cannot be trusted.
542 // Because it failed verification, or someone bypassed the security tests
543 // we cannot cache the response for sharing between clients.
544 // TODO: update cache to store for particular clients only (going to same Host: and destination IP)
545 if (!flags
.hostVerified
&& (flags
.intercepted
|| flags
.interceptTproxy
))
548 switch (url
.getScheme()) {
549 case AnyP::PROTO_HTTP
:
550 case AnyP::PROTO_HTTPS
:
551 if (!method
.respMaybeCacheable())
554 // RFC 7234 section 5.2.1.5:
555 // "cache MUST NOT store any part of either this request or any response to it"
557 // NP: refresh_pattern ignore-no-store only applies to response messages
558 // this test is handling request message CC header.
559 if (!flags
.ignoreCc
&& cache_control
&& cache_control
->hasNoStore())
563 case AnyP::PROTO_GOPHER
:
564 if (!gopherCachable(this))
568 case AnyP::PROTO_CACHE_OBJECT
:
571 //case AnyP::PROTO_FTP:
580 HttpRequest::conditional() const
583 header
.has(Http::HdrType::IF_MATCH
) ||
584 header
.has(Http::HdrType::IF_NONE_MATCH
);
588 HttpRequest::recordLookup(const Dns::LookupDetails
&dns
)
590 if (dns
.wait
>= 0) { // known delay
591 if (dnsWait
>= 0) { // have recorded DNS wait before
592 debugs(78, 7, this << " " << dnsWait
<< " += " << dns
);
595 debugs(78, 7, this << " " << dns
);
602 HttpRequest::getRangeOffsetLimit()
604 /* -2 is the starting value of rangeOffsetLimit.
605 * If it is -2, that means we haven't checked it yet.
606 * Otherwise, return the current value */
607 if (rangeOffsetLimit
!= -2)
608 return rangeOffsetLimit
;
610 rangeOffsetLimit
= 0; // default value for rangeOffsetLimit
612 ACLFilledChecklist
ch(NULL
, this, NULL
);
613 ch
.src_addr
= client_addr
;
614 ch
.my_addr
= my_addr
;
616 for (AclSizeLimit
*l
= Config
.rangeOffsetLimit
; l
; l
= l
-> next
) {
617 /* if there is no ACL list or if the ACLs listed match use this limit value */
618 if (!l
->aclList
|| ch
.fastCheck(l
->aclList
).allowed()) {
619 rangeOffsetLimit
= l
->size
; // may be -1
620 debugs(58, 4, rangeOffsetLimit
);
625 return rangeOffsetLimit
;
629 HttpRequest::ignoreRange(const char *reason
)
632 debugs(73, 3, static_cast<void*>(range
) << " for " << reason
);
636 // Some callers also reset isRanged but it may not be safe for all callers:
637 // isRanged is used to determine whether a weak ETag comparison is allowed,
638 // and that check should not ignore the Range header if it was present.
639 // TODO: Some callers also delete HDR_RANGE, HDR_REQUEST_RANGE. Should we?
643 HttpRequest::canHandle1xx() const
645 // old clients do not support 1xx unless they sent Expect: 100-continue
646 // (we reject all other Http::HdrType::EXPECT values so just check for Http::HdrType::EXPECT)
647 if (http_ver
<= Http::ProtocolVersion(1,0) && !header
.has(Http::HdrType::EXPECT
))
650 // others must support 1xx control messages
655 HttpRequest::checkEntityFraming() const
657 // RFC 7230 section 3.3.1:
659 // A server that receives a request message with a transfer coding it
660 // does not understand SHOULD respond with 501 (Not Implemented).
662 if (header
.unsupportedTe())
663 return Http::scNotImplemented
;
665 // RFC 7230 section 3.3.3 #3 paragraph 3:
666 // Transfer-Encoding overrides Content-Length
667 if (header
.chunked())
670 // RFC 7230 Section 3.3.3 #4:
671 // conflicting Content-Length(s) mean a message framing error
672 if (header
.conflictingContentLength())
673 return Http::scBadRequest
;
675 // HTTP/1.0 requirements differ from HTTP/1.1
676 if (http_ver
<= Http::ProtocolVersion(1,0)) {
677 const auto m
= method
.id();
679 // RFC 1945 section 8.3:
681 // A valid Content-Length is required on all HTTP/1.0 POST requests.
683 // RFC 1945 Appendix D.1.1:
685 // The fundamental difference between the POST and PUT requests is
686 // reflected in the different meaning of the Request-URI.
688 if (m
== Http::METHOD_POST
|| m
== Http::METHOD_PUT
)
689 return (content_length
>= 0 ? Http::scNone
: Http::scLengthRequired
);
691 // RFC 1945 section 7.2:
693 // An entity body is included with a request message only when the
694 // request method calls for one.
696 // section 8.1-2: GET and HEAD do not define ('call for') an entity
697 if (m
== Http::METHOD_GET
|| m
== Http::METHOD_HEAD
)
698 return (content_length
< 0 ? Http::scNone
: Http::scBadRequest
);
699 // appendix D1.1.2-4: DELETE, LINK, UNLINK do not define ('call for') an entity
700 if (m
== Http::METHOD_DELETE
|| m
== Http::METHOD_LINK
|| m
== Http::METHOD_UNLINK
)
701 return (content_length
< 0 ? Http::scNone
: Http::scBadRequest
);
703 // other methods are not defined in RFC 1945
704 // assume they support an (optional) entity
708 // RFC 7230 section 3.3
710 // The presence of a message body in a request is signaled by a
711 // Content-Length or Transfer-Encoding header field. Request message
712 // framing is independent of method semantics, even if the method does
713 // not define any use for a message body.
719 HttpRequest::parseHeader(Http1::Parser
&hp
)
721 Http::ContentLengthInterpreter clen
;
722 return Message::parseHeader(hp
, clen
);
726 HttpRequest::parseHeader(const char *buffer
, const size_t size
)
728 Http::ContentLengthInterpreter clen
;
729 return header
.parse(buffer
, size
, clen
);
733 HttpRequest::pinnedConnection()
735 if (clientConnectionManager
.valid() && clientConnectionManager
->pinning
.pinned
)
736 return clientConnectionManager
.get();
741 HttpRequest::storeId()
743 if (store_id
.size() != 0) {
744 debugs(73, 3, "sent back store_id: " << store_id
);
745 return StringToSBuf(store_id
);
747 debugs(73, 3, "sent back effectiveRequestUrl: " << effectiveRequestUri());
748 return effectiveRequestUri();
752 HttpRequest::effectiveRequestUri() const
754 if (method
.id() == Http::METHOD_CONNECT
|| url
.getScheme() == AnyP::PROTO_AUTHORITY_FORM
)
755 return url
.authority(true); // host:port
756 return url
.absolute();
763 theNotes
= new NotePairs
;
768 UpdateRequestNotes(ConnStateData
*csd
, HttpRequest
&request
, NotePairs
const &helperNotes
)
770 // Tag client connection if the helper responded with clt_conn_tag=tag.
771 const char *cltTag
= "clt_conn_tag";
772 if (const char *connTag
= helperNotes
.findFirst(cltTag
)) {
774 csd
->notes()->remove(cltTag
);
775 csd
->notes()->add(cltTag
, connTag
);
778 request
.notes()->replaceOrAdd(&helperNotes
);
782 HttpRequest::manager(const CbcPointer
<ConnStateData
> &aMgr
, const AccessLogEntryPointer
&al
)
784 clientConnectionManager
= aMgr
;
786 if (!clientConnectionManager
.valid())
789 AnyP::PortCfgPointer port
= clientConnectionManager
->port
;
791 myportname
= port
->name
;
792 flags
.ignoreCc
= port
->ignore_cc
;
795 if (auto clientConnection
= clientConnectionManager
->clientConnection
) {
796 client_addr
= clientConnection
->remote
; // XXX: remove request->client_addr member.
797 #if FOLLOW_X_FORWARDED_FOR
798 // indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:)
799 // not details about the TCP connection itself
800 indirect_client_addr
= clientConnection
->remote
;
801 #endif /* FOLLOW_X_FORWARDED_FOR */
802 my_addr
= clientConnection
->local
;
804 flags
.intercepted
= ((clientConnection
->flags
& COMM_INTERCEPTION
) != 0);
805 flags
.interceptTproxy
= ((clientConnection
->flags
& COMM_TRANSPARENT
) != 0 ) ;
806 const bool proxyProtocolPort
= port
? port
->flags
.proxySurrogate
: false;
807 if (flags
.interceptTproxy
&& !proxyProtocolPort
) {
808 if (Config
.accessList
.spoof_client_ip
) {
809 ACLFilledChecklist
*checklist
= new ACLFilledChecklist(Config
.accessList
.spoof_client_ip
, this, clientConnection
->rfc931
);
811 checklist
->syncAle(this, nullptr);
812 flags
.spoofClientIp
= checklist
->fastCheck().allowed();
815 flags
.spoofClientIp
= true;
817 flags
.spoofClientIp
= false;
822 HttpRequest::canonicalCleanUrl() const
824 return urlCanonicalCleanWithoutRequest(effectiveRequestUri(), method
, url
.getScheme());
827 /// a helper for validating FindListeningPortAddress()-found address candidates
828 static const Ip::Address
*
829 FindListeningPortAddressInAddress(const Ip::Address
*ip
)
831 // FindListeningPortAddress() callers do not want INADDR_ANY addresses
832 return (ip
&& !ip
->isAnyAddr()) ? ip
: nullptr;
835 /// a helper for handling PortCfg cases of FindListeningPortAddress()
836 static const Ip::Address
*
837 FindListeningPortAddressInPort(const AnyP::PortCfgPointer
&port
)
839 return port
? FindListeningPortAddressInAddress(&port
->s
) : nullptr;
842 /// a helper for handling Connection cases of FindListeningPortAddress()
843 static const Ip::Address
*
844 FindListeningPortAddressInConn(const Comm::ConnectionPointer
&conn
)
846 return conn
? FindListeningPortAddressInAddress(&conn
->local
) : nullptr;
850 FindListeningPortAddress(const HttpRequest
*callerRequest
, const AccessLogEntry
*ale
)
852 // Check all sources of usable listening port information, giving
853 // HttpRequest and masterXaction a preference over ALE.
855 const HttpRequest
*request
= callerRequest
;
857 request
= ale
->request
;
859 return nullptr; // not enough information
861 const Ip::Address
*ip
= FindListeningPortAddressInPort(request
->masterXaction
->squidPort
);
863 ip
= FindListeningPortAddressInPort(ale
->cache
.port
);
865 // XXX: also handle PROXY protocol here when we have a flag to identify such request
866 if (ip
|| request
->flags
.interceptTproxy
|| request
->flags
.intercepted
)
869 /* handle non-intercepted cases that were not handled above */
870 ip
= FindListeningPortAddressInConn(request
->masterXaction
->tcpClient
);
872 ip
= FindListeningPortAddressInConn(ale
->tcpClient
);
873 return ip
; // may still be nil