2 * Copyright (C) 1996-2023 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"
23 #include "http/ContentLengthInterpreter.h"
24 #include "http/one/RequestParser.h"
25 #include "http/Stream.h"
26 #include "HttpHdrCc.h"
27 #include "HttpHeaderRange.h"
28 #include "HttpRequest.h"
29 #include "log/Config.h"
31 #include "sbuf/StringConvert.h"
32 #include "SquidConfig.h"
36 #include "auth/UserRequest.h"
39 #include "adaptation/icap/icap_log.h"
42 HttpRequest::HttpRequest(const MasterXaction::Pointer
&mx
) :
43 Http::Message(hoRequest
),
50 HttpRequest::HttpRequest(const HttpRequestMethod
& aMethod
, AnyP::ProtocolType aProtocol
, const char *aSchemeImg
, const char *aUrlpath
, const MasterXaction::Pointer
&mx
) :
51 Http::Message(hoRequest
),
55 static unsigned int id
= 1;
56 debugs(93,7, "constructed, this=" << this << " id=" << ++id
);
58 initHTTP(aMethod
, aProtocol
, aSchemeImg
, aUrlpath
);
61 HttpRequest::~HttpRequest()
64 debugs(93,7, "destructed, this=" << this);
68 HttpRequest::initHTTP(const HttpRequestMethod
& aMethod
, AnyP::ProtocolType aProtocol
, const char *aSchemeImg
, const char *aUrlpath
)
71 url
.setScheme(aProtocol
, aSchemeImg
);
78 method
= Http::METHOD_NONE
;
81 auth_user_request
= nullptr;
83 flags
= RequestFlags();
88 client_addr
.setEmpty();
94 peer_login
= nullptr; // not allocated/deallocated by this class
95 peer_domain
= nullptr; // not allocated/deallocated by this class
97 vary_headers
= SBuf();
98 myportname
= null_string
;
101 extacl_user
= null_string
;
102 extacl_passwd
= null_string
;
104 extacl_log
= null_string
;
105 extacl_message
= null_string
;
106 pstate
= Http::Message::psReadyToParseStartLine
;
107 #if FOLLOW_X_FORWARDED_FOR
108 indirect_client_addr
.setEmpty();
109 #endif /* FOLLOW_X_FORWARDED_FOR */
111 adaptHistory_
= nullptr;
114 icapHistory_
= nullptr;
116 rangeOffsetLimit
= -2; //a value of -2 means not checked yet
117 forcedBodyContinuation
= false;
123 // we used to assert that the pipe is NULL, but now the request only
124 // points to a pipe that is owned and initiated by another object.
127 auth_user_request
= nullptr;
129 vary_headers
.clear();
135 delete cache_control
;
136 cache_control
= nullptr;
151 extacl_passwd
.clean();
155 extacl_message
.clean();
160 adaptHistory_
= nullptr;
163 icapHistory_
= nullptr;
175 HttpRequest::clone() const
177 HttpRequest
*copy
= new HttpRequest(masterXaction
);
178 copy
->method
= method
;
179 // TODO: move common cloning clone to Msg::copyTo() or copy ctor
180 copy
->header
.append(&header
);
181 copy
->hdrCacheInit();
182 copy
->hdr_sz
= hdr_sz
;
183 copy
->http_ver
= http_ver
;
184 copy
->pstate
= pstate
; // TODO: should we assert a specific state here?
185 copy
->body_pipe
= body_pipe
;
189 // range handled in hdrCacheInit()
191 copy
->imslen
= imslen
;
192 copy
->hier
= hier
; // Is it safe to copy? Should we?
196 // XXX: what to do with copy->peer_login?
198 copy
->lastmod
= lastmod
;
200 copy
->vary_headers
= vary_headers
;
201 // XXX: what to do with copy->peer_domain?
204 copy
->extacl_log
= extacl_log
;
205 copy
->extacl_message
= extacl_message
;
207 const bool inheritWorked
= copy
->inheritProperties(this);
208 assert(inheritWorked
);
214 HttpRequest::inheritProperties(const Http::Message
*aMsg
)
216 const HttpRequest
* aReq
= dynamic_cast<const HttpRequest
*>(aMsg
);
220 client_addr
= aReq
->client_addr
;
221 #if FOLLOW_X_FORWARDED_FOR
222 indirect_client_addr
= aReq
->indirect_client_addr
;
224 my_addr
= aReq
->my_addr
;
226 dnsWait
= aReq
->dnsWait
;
229 adaptHistory_
= aReq
->adaptHistory();
232 icapHistory_
= aReq
->icapHistory();
235 // This may be too conservative for the 204 No Content case
236 // may eventually need cloneNullAdaptationImmune() for that.
237 flags
= aReq
->flags
.cloneAdaptationImmune();
241 auth_user_request
= aReq
->auth_user_request
;
242 extacl_user
= aReq
->extacl_user
;
243 extacl_passwd
= aReq
->extacl_passwd
;
246 myportname
= aReq
->myportname
;
248 forcedBodyContinuation
= aReq
->forcedBodyContinuation
;
250 // main property is which connection the request was received on (if any)
251 clientConnectionManager
= aReq
->clientConnectionManager
;
253 downloader
= aReq
->downloader
;
255 theNotes
= aReq
->theNotes
;
257 sources
= aReq
->sources
;
262 * Checks the first line of an HTTP request is valid
263 * currently just checks the request method is present.
265 * NP: Other errors are left for detection later in the parse.
268 HttpRequest::sanityCheckStartLine(const char *buf
, const size_t hdr_len
, Http::StatusCode
*scode
)
270 // content is long enough to possibly hold a reply
271 // 2 being magic size of a 1-byte request method plus space delimiter
273 // this is only a real error if the headers apparently complete.
275 debugs(58, 3, "Too large request header (" << hdr_len
<< " bytes)");
276 *scode
= Http::scInvalidHeader
;
281 /* See if the request buffer starts with a non-whitespace HTTP request 'method'. */
283 m
.HttpRequestMethodXXX(buf
);
284 if (m
== Http::METHOD_NONE
) {
285 debugs(73, 3, "HttpRequest::sanityCheckStartLine: did not find HTTP request method");
286 *scode
= Http::scInvalidHeader
;
294 HttpRequest::parseFirstLine(const char *start
, const char *end
)
296 method
.HttpRequestMethodXXX(start
);
298 if (method
== Http::METHOD_NONE
)
301 // XXX: performance regression, strcspn() over the method bytes a second time.
302 // cheaper than allocate+copy+deallocate cycle to SBuf convert a piece of start.
303 const char *t
= start
+ strcspn(start
, w_space
);
305 start
= t
+ strspn(t
, w_space
); // skip w_space after method
307 const char *ver
= findTrailingHTTPVersion(start
, end
);
312 while (xisspace(*end
)) // find prev non-space
315 ++end
; // back to space
317 if (2 != sscanf(ver
+ 5, "%d.%d", &http_ver
.major
, &http_ver
.minor
)) {
318 debugs(73, DBG_IMPORTANT
, "ERROR: parseRequestLine: Invalid HTTP identifier.");
326 if (end
< start
) // missing URI
329 return url
.parse(method
, SBuf(start
, size_t(end
-start
)));
332 /* swaps out request using httpRequestPack */
334 HttpRequest::swapOut(StoreEntry
* e
)
342 /* packs request-line and headers, appends <crlf> terminator */
344 HttpRequest::pack(Packable
* p
) const
347 /* pack request-line */
348 p
->appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" HTTP/%d.%d\r\n",
349 SQUIDSBUFPRINT(method
.image()), SQUIDSBUFPRINT(url
.path()),
350 http_ver
.major
, http_ver
.minor
);
354 p
->append("\r\n", 2);
358 * A wrapper for debugObj()
361 httpRequestPack(void *obj
, Packable
*p
)
363 HttpRequest
*request
= static_cast<HttpRequest
*>(obj
);
367 /* returns the length of request line + headers + crlf */
369 HttpRequest::prefixLen() const
371 return method
.image().length() + 1 +
372 url
.path().length() + 1 +
377 /* sync this routine when you update HttpRequest struct */
379 HttpRequest::hdrCacheInit()
381 Http::Message::hdrCacheInit();
384 range
= header
.getRange();
388 Adaptation::Icap::History::Pointer
389 HttpRequest::icapHistory() const
392 if (Log::TheConfig
.hasIcapToken
|| IcapLogfileStatus
== LOG_ENABLE
) {
393 icapHistory_
= new Adaptation::Icap::History();
394 debugs(93,4, "made " << icapHistory_
<< " for " << this);
403 Adaptation::History::Pointer
404 HttpRequest::adaptHistory(bool createIfNone
) const
406 if (!adaptHistory_
&& createIfNone
) {
407 adaptHistory_
= new Adaptation::History();
408 debugs(93,4, "made " << adaptHistory_
<< " for " << this);
411 return adaptHistory_
;
414 Adaptation::History::Pointer
415 HttpRequest::adaptLogHistory() const
417 return HttpRequest::adaptHistory(Log::TheConfig
.hasAdaptToken
);
421 HttpRequest::adaptHistoryImport(const HttpRequest
&them
)
423 if (!adaptHistory_
) {
424 adaptHistory_
= them
.adaptHistory_
; // may be nil
426 // check that histories did not diverge
427 Must(!them
.adaptHistory_
|| them
.adaptHistory_
== adaptHistory_
);
434 HttpRequest::multipartRangeRequest() const
436 return (range
&& range
->specs
.size() > 1);
440 HttpRequest::bodyNibbled() const
442 return body_pipe
!= nullptr && body_pipe
->consumedSize() > 0;
446 HttpRequest::prepForPeering(const CachePeer
&peer
)
448 // XXX: Saving two pointers to memory controlled by an independent object.
449 peer_login
= peer
.login
;
450 peer_domain
= peer
.domain
;
451 flags
.auth_no_keytab
= peer
.options
.auth_no_keytab
;
452 debugs(11, 4, this << " to " << peer
);
456 HttpRequest::prepForDirect()
458 peer_login
= nullptr;
459 peer_domain
= nullptr;
460 flags
.auth_no_keytab
= false;
465 HttpRequest::clearError()
467 debugs(11, 7, "old: " << error
);
472 HttpRequest::packFirstLineInto(Packable
* p
, bool full_uri
) const
474 const SBuf
tmp(full_uri
? effectiveRequestUri() : url
.path());
476 // form HTTP request-line
477 p
->appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" HTTP/%d.%d\r\n",
478 SQUIDSBUFPRINT(method
.image()),
480 http_ver
.major
, http_ver
.minor
);
484 * Indicate whether or not we would expect an entity-body
485 * along with this request
488 HttpRequest::expectingBody(const HttpRequestMethod
&, int64_t &theSize
) const
490 bool expectBody
= false;
493 * Note: Checks for message validity is in clientIsContentLengthValid().
494 * this just checks if a entity-body is expected based on HTTP message syntax
496 if (header
.chunked()) {
499 } else if (content_length
>= 0) {
501 theSize
= content_length
;
511 * Create a Request from a URL and METHOD.
513 * If the METHOD is CONNECT, then a host:port pair is looked for instead of a URL.
514 * If the request cannot be created cleanly, NULL is returned
517 HttpRequest::FromUrl(const SBuf
&url
, const MasterXaction::Pointer
&mx
, const HttpRequestMethod
& method
)
519 std::unique_ptr
<HttpRequest
> req(new HttpRequest(mx
));
520 if (req
->url
.parse(method
, url
)) {
521 req
->method
= method
;
522 return req
.release();
528 HttpRequest::FromUrlXXX(const char * url
, const MasterXaction::Pointer
&mx
, const HttpRequestMethod
& method
)
530 return FromUrl(SBuf(url
), mx
, method
);
534 * Are responses to this request possible cacheable ?
535 * If false then no matter what the response must not be cached.
538 HttpRequest::maybeCacheable()
540 // Intercepted request with Host: header which cannot be trusted.
541 // Because it failed verification, or someone bypassed the security tests
542 // we cannot cache the response for sharing between clients.
543 // TODO: update cache to store for particular clients only (going to same Host: and destination IP)
544 if (!flags
.hostVerified
&& (flags
.intercepted
|| flags
.interceptTproxy
))
547 switch (url
.getScheme()) {
548 case AnyP::PROTO_HTTP
:
549 case AnyP::PROTO_HTTPS
:
550 if (!method
.respMaybeCacheable())
553 // RFC 9111 section 5.2.1.5:
554 // "The no-store request directive indicates that a cache MUST NOT
555 // 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_FTP:
572 HttpRequest::conditional() const
575 header
.has(Http::HdrType::IF_MATCH
) ||
576 header
.has(Http::HdrType::IF_NONE_MATCH
);
580 HttpRequest::recordLookup(const Dns::LookupDetails
&dns
)
582 if (dns
.wait
>= 0) { // known delay
583 if (dnsWait
>= 0) { // have recorded DNS wait before
584 debugs(78, 7, this << " " << dnsWait
<< " += " << dns
);
587 debugs(78, 7, this << " " << dns
);
594 HttpRequest::getRangeOffsetLimit()
596 /* -2 is the starting value of rangeOffsetLimit.
597 * If it is -2, that means we haven't checked it yet.
598 * Otherwise, return the current value */
599 if (rangeOffsetLimit
!= -2)
600 return rangeOffsetLimit
;
602 rangeOffsetLimit
= 0; // default value for rangeOffsetLimit
604 ACLFilledChecklist
ch(nullptr, this, nullptr);
605 ch
.src_addr
= client_addr
;
606 ch
.my_addr
= my_addr
;
608 for (AclSizeLimit
*l
= Config
.rangeOffsetLimit
; l
; l
= l
-> next
) {
609 /* if there is no ACL list or if the ACLs listed match use this limit value */
610 if (!l
->aclList
|| ch
.fastCheck(l
->aclList
).allowed()) {
611 rangeOffsetLimit
= l
->size
; // may be -1
612 debugs(58, 4, rangeOffsetLimit
);
617 return rangeOffsetLimit
;
621 HttpRequest::ignoreRange(const char *reason
)
624 debugs(73, 3, static_cast<void*>(range
) << " for " << reason
);
628 // Some callers also reset isRanged but it may not be safe for all callers:
629 // isRanged is used to determine whether a weak ETag comparison is allowed,
630 // and that check should not ignore the Range header if it was present.
631 // TODO: Some callers also delete HDR_RANGE, HDR_REQUEST_RANGE. Should we?
635 HttpRequest::canHandle1xx() const
637 // old clients do not support 1xx unless they sent Expect: 100-continue
638 // (we reject all other Http::HdrType::EXPECT values so just check for Http::HdrType::EXPECT)
639 if (http_ver
<= Http::ProtocolVersion(1,0) && !header
.has(Http::HdrType::EXPECT
))
642 // others must support 1xx control messages
647 HttpRequest::checkEntityFraming() const
649 // RFC 7230 section 3.3.1:
651 // A server that receives a request message with a transfer coding it
652 // does not understand SHOULD respond with 501 (Not Implemented).
654 if (header
.unsupportedTe())
655 return Http::scNotImplemented
;
657 // RFC 7230 section 3.3.3 #3 paragraph 3:
658 // Transfer-Encoding overrides Content-Length
659 if (header
.chunked())
662 // RFC 7230 Section 3.3.3 #4:
663 // conflicting Content-Length(s) mean a message framing error
664 if (header
.conflictingContentLength())
665 return Http::scBadRequest
;
667 // HTTP/1.0 requirements differ from HTTP/1.1
668 if (http_ver
<= Http::ProtocolVersion(1,0)) {
669 const auto m
= method
.id();
671 // RFC 1945 section 8.3:
673 // A valid Content-Length is required on all HTTP/1.0 POST requests.
675 // RFC 1945 Appendix D.1.1:
677 // The fundamental difference between the POST and PUT requests is
678 // reflected in the different meaning of the Request-URI.
680 if (m
== Http::METHOD_POST
|| m
== Http::METHOD_PUT
)
681 return (content_length
>= 0 ? Http::scNone
: Http::scLengthRequired
);
683 // RFC 1945 section 7.2:
685 // An entity body is included with a request message only when the
686 // request method calls for one.
688 // section 8.1-2: GET and HEAD do not define ('call for') an entity
689 if (m
== Http::METHOD_GET
|| m
== Http::METHOD_HEAD
)
690 return (content_length
< 0 ? Http::scNone
: Http::scBadRequest
);
691 // appendix D1.1.2-4: DELETE, LINK, UNLINK do not define ('call for') an entity
692 if (m
== Http::METHOD_DELETE
|| m
== Http::METHOD_LINK
|| m
== Http::METHOD_UNLINK
)
693 return (content_length
< 0 ? Http::scNone
: Http::scBadRequest
);
695 // other methods are not defined in RFC 1945
696 // assume they support an (optional) entity
700 // RFC 7230 section 3.3
702 // The presence of a message body in a request is signaled by a
703 // Content-Length or Transfer-Encoding header field. Request message
704 // framing is independent of method semantics, even if the method does
705 // not define any use for a message body.
711 HttpRequest::parseHeader(Http1::Parser
&hp
)
713 Http::ContentLengthInterpreter clen
;
714 return Message::parseHeader(hp
, clen
);
718 HttpRequest::parseHeader(const char *buffer
, const size_t size
)
720 Http::ContentLengthInterpreter clen
;
721 return header
.parse(buffer
, size
, clen
);
725 HttpRequest::pinnedConnection()
727 if (clientConnectionManager
.valid() && clientConnectionManager
->pinning
.pinned
)
728 return clientConnectionManager
.get();
733 HttpRequest::storeId()
735 if (store_id
.size() != 0) {
736 debugs(73, 3, "sent back store_id: " << store_id
);
737 return StringToSBuf(store_id
);
739 debugs(73, 3, "sent back effectiveRequestUrl: " << effectiveRequestUri());
740 return effectiveRequestUri();
744 HttpRequest::effectiveRequestUri() const
746 if (method
.id() == Http::METHOD_CONNECT
|| url
.getScheme() == AnyP::PROTO_AUTHORITY_FORM
)
747 return url
.authority(true); // host:port
748 return url
.absolute();
755 theNotes
= new NotePairs
;
760 UpdateRequestNotes(ConnStateData
*csd
, HttpRequest
&request
, NotePairs
const &helperNotes
)
762 // Tag client connection if the helper responded with clt_conn_tag=tag.
763 const char *cltTag
= "clt_conn_tag";
764 if (const char *connTag
= helperNotes
.findFirst(cltTag
)) {
766 csd
->notes()->remove(cltTag
);
767 csd
->notes()->add(cltTag
, connTag
);
770 request
.notes()->replaceOrAdd(&helperNotes
);
774 HttpRequest::manager(const CbcPointer
<ConnStateData
> &aMgr
, const AccessLogEntryPointer
&al
)
776 clientConnectionManager
= aMgr
;
778 if (!clientConnectionManager
.valid())
781 AnyP::PortCfgPointer port
= clientConnectionManager
->port
;
783 myportname
= port
->name
;
784 flags
.ignoreCc
= port
->ignore_cc
;
787 if (auto clientConnection
= clientConnectionManager
->clientConnection
) {
788 client_addr
= clientConnection
->remote
; // XXX: remove request->client_addr member.
789 #if FOLLOW_X_FORWARDED_FOR
790 // indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:)
791 // not details about the TCP connection itself
792 indirect_client_addr
= clientConnection
->remote
;
793 #endif /* FOLLOW_X_FORWARDED_FOR */
794 my_addr
= clientConnection
->local
;
796 flags
.intercepted
= ((clientConnection
->flags
& COMM_INTERCEPTION
) != 0);
797 flags
.interceptTproxy
= ((clientConnection
->flags
& COMM_TRANSPARENT
) != 0 ) ;
798 const bool proxyProtocolPort
= port
? port
->flags
.proxySurrogate
: false;
799 if (flags
.interceptTproxy
&& !proxyProtocolPort
) {
800 if (Config
.accessList
.spoof_client_ip
) {
801 ACLFilledChecklist
*checklist
= new ACLFilledChecklist(Config
.accessList
.spoof_client_ip
, this, clientConnection
->rfc931
);
803 checklist
->syncAle(this, nullptr);
804 flags
.spoofClientIp
= checklist
->fastCheck().allowed();
807 flags
.spoofClientIp
= true;
809 flags
.spoofClientIp
= false;
814 HttpRequest::canonicalCleanUrl() const
816 return urlCanonicalCleanWithoutRequest(effectiveRequestUri(), method
, url
.getScheme());
819 /// a helper for handling PortCfg cases of FindListeningPortAddress()
820 template <typename Filter
>
821 static const Ip::Address
*
822 FindGoodListeningPortAddressInPort(const AnyP::PortCfgPointer
&port
, const Filter isGood
)
824 return (port
&& isGood(port
->s
)) ? &port
->s
: nullptr;
827 /// a helper for handling Connection cases of FindListeningPortAddress()
828 template <typename Filter
>
829 static const Ip::Address
*
830 FindGoodListeningPortAddressInConn(const Comm::ConnectionPointer
&conn
, const Filter isGood
)
832 return (conn
&& isGood(conn
->local
)) ? &conn
->local
: nullptr;
835 template <typename Filter
>
837 FindGoodListeningPortAddress(const HttpRequest
*callerRequest
, const AccessLogEntry
*ale
, const Filter filter
)
839 // Check all sources of usable listening port information, giving
840 // HttpRequest and masterXaction a preference over ALE.
842 const HttpRequest
*request
= callerRequest
;
844 request
= ale
->request
;
846 return nullptr; // not enough information
848 auto ip
= FindGoodListeningPortAddressInPort(request
->masterXaction
->squidPort
, filter
);
850 ip
= FindGoodListeningPortAddressInPort(ale
->cache
.port
, filter
);
852 // XXX: also handle PROXY protocol here when we have a flag to identify such request
853 if (ip
|| request
->flags
.interceptTproxy
|| request
->flags
.intercepted
)
856 /* handle non-intercepted cases that were not handled above */
857 ip
= FindGoodListeningPortAddressInConn(request
->masterXaction
->tcpClient
, filter
);
859 ip
= FindGoodListeningPortAddressInConn(ale
->tcpClient
, filter
);
860 return ip
; // may still be nil
864 FindListeningPortAddress(const HttpRequest
*callerRequest
, const AccessLogEntry
*ale
)
866 return FindGoodListeningPortAddress(callerRequest
, ale
, [](const Ip::Address
&address
) {
867 // FindListeningPortAddress() callers do not want INADDR_ANY addresses
868 return !address
.isAnyAddr();
873 FindListeningPortNumber(const HttpRequest
*callerRequest
, const AccessLogEntry
*ale
)
875 const auto ip
= FindGoodListeningPortAddress(callerRequest
, ale
, [](const Ip::Address
&address
) {
876 return address
.port() > 0;
882 Assure(ip
->port() > 0);