XRootD
Loading...
Searching...
No Matches
XrdHttpHeaderUtils Class Reference

#include <XrdHttpHeaderUtils.hh>

Collaboration diagram for XrdHttpHeaderUtils:

Static Public Member Functions

static ssize_t parseContentLength (const std::string &value)
static void parseReprDigest (const std::string &value, std::map< std::string, std::string > &output)
static int parseTransferEncoding (const std::string &value)
static void parseWantReprDigest (const std::string &value, std::map< std::string, uint8_t > &output)

Detailed Description

Definition at line 31 of file XrdHttpHeaderUtils.hh.

Member Function Documentation

◆ parseContentLength()

ssize_t XrdHttpHeaderUtils::parseContentLength ( const std::string & value)
static

Parses the value of a 'Content-Length' HTTP header.

The HTTP spec says Content-Length must be one or more decimal digits with no sign character and no whitespace inside the number. Anything else is malformed and must be rejected with HTTP 400, because if we trust a garbage Content-Length we may disagree with a proxy in front of us on where the request body ends — which is the basis of HTTP request smuggling attacks.

Trailing whitespace and CRLF in the value are tolerated (parseLine forwards the raw header line including the closing \r
).

Reference: RFC 9112 §6.2 (Content-Length syntax) and RFC 7230 §3.3.3 rule 4 (rejection of invalid values).

Parameters
valuethe raw value of the Content-Length header
Returns
the parsed non-negative integer on success, or a distinct negative code on error: -1 the value is empty (after trimming trailing CRLF / OWS), -2 the value contains a non-digit character (sign, garbage, embedded whitespace), -3 the value is numerically too large for an ssize_t.

Definition at line 110 of file XrdHttpHeaderUtils.cc.

110 {
111 std::string_view sv{value};
112 // parseLine forwards the trailing CRLF (and any preceding spaces/tabs).
113 while (!sv.empty() && (sv.back() == '\r' || sv.back() == '\n' ||
114 sv.back() == ' ' || sv.back() == '\t')) {
115 sv.remove_suffix(1);
116 }
117 if (sv.empty()) {
118 return -1;
119 }
120 // RFC 9112 §6.2: 1*DIGIT only — no sign, no embedded whitespace.
121 for (char c : sv) {
122 if (c < '0' || c > '9') {
123 return -2;
124 }
125 }
126 std::string nullTerm{sv};
127 errno = 0;
128 long long parsed = std::strtoll(nullTerm.c_str(), nullptr, 10);
129 if (errno == ERANGE ||
130 parsed > static_cast<long long>(std::numeric_limits<ssize_t>::max())) {
131 return -3;
132 }
133 return static_cast<ssize_t>(parsed);
134}

Referenced by XrdHttpReq::parseLine().

Here is the caller graph for this function:

◆ parseReprDigest()

void XrdHttpHeaderUtils::parseReprDigest ( const std::string & value,
std::map< std::string, std::string > & output )
static

Parses the 'Repr-Digest' header value received from the client Syntax: "Repr-Digest: adler=:base64EncodedValue:, crc32=:base64EncodedValue:

Parameters
valuecontains the value of the header Repr-Digest
outputthe map containing the digests and their associated base64 encoded values

Definition at line 35 of file XrdHttpHeaderUtils.cc.

35 {
36 // Expected format per entry: <cksumType>=:<digestValue>:
37 std::vector<std::string> digestNameValuePairs;
38 XrdOucTUtils::splitString(digestNameValuePairs, value, ",");
39
40 for (const auto &digestNameValue : digestNameValuePairs) {
41 std::string_view digestNameValueSV {digestNameValue};
42 auto equalPos = digestNameValueSV.find('=');
43 if (equalPos == std::string_view::npos || equalPos >= digestNameValueSV.size() - 1)
44 continue;
45
46 std::string_view cksumTypeSV = digestNameValueSV.substr(0, equalPos);
47 XrdOucUtils::trim(cksumTypeSV);
48 if (cksumTypeSV.empty())
49 continue;
50
51 std::string_view cksumValueInSV = digestNameValueSV.substr(equalPos + 1);
52 size_t beginCksumPos = cksumValueInSV.find(':');
53 size_t endCksumPos = cksumValueInSV.rfind(':');
54
55 // Check that the string starts with ':' and contains two distinct colons
56 if (beginCksumPos == 0 && endCksumPos > beginCksumPos + 1 && endCksumPos < cksumValueInSV.size()) {
57 std::string_view cksumValue = cksumValueInSV.substr(beginCksumPos + 1, endCksumPos - beginCksumPos - 1);
58 XrdOucUtils::trim(cksumValue);
59 if (!cksumValue.empty()) {
60 //What we get as checksum value is a base64-encoded hexadecimal bytes
61 //Let's decode that.
62 std::string chksumDecoded;
63 base64DecodeHex(std::string(cksumValue), chksumDecoded);
64 std::string cksumTypeLC {cksumTypeSV};
65 std::transform(cksumTypeLC.begin(), cksumTypeLC.end(), cksumTypeLC.begin(), ::tolower);
66 output[cksumTypeLC] = chksumDecoded;
67 }
68 }
69 // Malformed entries are silently ignored
70 }
71}
void base64DecodeHex(const std::string &base64, std::string &hexOutput)
static void splitString(Container &result, const std::string &input, const std::string &delimiter)
Split a string.
static void trim(std::string &str)

References base64DecodeHex(), XrdOucTUtils::splitString(), and XrdOucUtils::trim().

Referenced by XrdHttpReq::parseLine().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parseTransferEncoding()

int XrdHttpHeaderUtils::parseTransferEncoding ( const std::string & value)
static

Parses the value of a 'Transfer-Encoding' HTTP header.

Transfer-Encoding carries a comma-separated list of body encodings, for example "chunked" or "gzip, chunked". The list is case-insensitive and items can have whitespace around them.

The only encoding this server speaks is "chunked". HTTP requires that whenever "chunked" appears it must be the LAST item in the list (because that is the one that determines how the body is framed on the wire).

This function checks two things together: that "chunked" is actually present (matched case-insensitively as a whole token, not as a substring like the old code did), and that it is the last item.

Trailing CRLF on the last item is tolerated.

Reference: RFC 9112 §6.1 (Transfer-Encoding syntax and the "chunked-must-be-last" rule) and RFC 7230 §3.3.1 (same rule).

Parameters
valuethe raw value of the Transfer-Encoding header
Returns
0 on success (the list ends in "chunked"), or a distinct negative code on error: -1 the value is empty (no tokens after splitting/trimming), -2 there is no "chunked" token in the list at all, -3 "chunked" appears but is not the final token.

Definition at line 136 of file XrdHttpHeaderUtils.cc.

136 {
137 std::vector<std::string> tokens;
138 XrdOucTUtils::splitString(tokens, value, ",");
139
140 bool chunked_seen = false;
141 bool last_was_chunked = false;
142 bool any_token = false;
143
144 for (auto & tok : tokens) {
145 std::string_view sv{tok};
147 if (sv.empty()) continue;
148
149 // RFC 9112 §6.1: transfer-coding names are case-insensitive. Whole-token
150 // match (not strstr) so "chunkedX" or "x-chunked" no longer falsely hit.
151 std::string tlow{sv};
152 std::transform(tlow.begin(), tlow.end(), tlow.begin(), ::tolower);
153 any_token = true;
154 if (tlow == "chunked") {
155 chunked_seen = true;
156 last_was_chunked = true;
157 } else {
158 last_was_chunked = false;
159 }
160 }
161
162 if (!any_token) return -1;
163 if (!chunked_seen) return -2;
164 if (!last_was_chunked) return -3;
165 return 0;
166}

References XrdOucTUtils::splitString(), and XrdOucUtils::trim().

Referenced by XrdHttpReq::parseLine().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parseWantReprDigest()

void XrdHttpHeaderUtils::parseWantReprDigest ( const std::string & value,
std::map< std::string, uint8_t > & output )
static

Parses 'Want-Repr-Digest' header value received from the client Syntax: "Want-Repr-Digest: adler=1, crc32=2, sha-256=9 The values are integers representing the preference, comprised between 0 and 9.

Parameters
valuecontains the value of the header Want-Repr-Digest
outputthe map containing the lower-cased digest name and the associated preference

Definition at line 73 of file XrdHttpHeaderUtils.cc.

73 {
74 size_t pos = 0;
75 std::string_view value_sv {value};
76 while(pos <= value_sv.size()) {
77 // find comma
78 size_t comma = value.find(',',pos);
79 // extract item, no comma means the item is the full string
80 std::string_view item = (comma == std::string_view::npos) ? value_sv.substr(pos) : value_sv.substr(pos, comma - pos);
81 // move current cursor to 'comma + 1' or after the string end
82 pos = (comma == std::string_view::npos) ? value.size() + 1 : comma + 1;
83 // trim the item
85 if(item.empty()) continue;
86
87 size_t eq = item.find('=');
88 // If no '=' sign, we discard this entry as it is malformed
89 if(eq == std::string_view::npos) continue;
90 // We found the equal sign on the item
91 std::string_view digestName {item.substr(0, eq)};
92 XrdOucUtils::trim(digestName);
93 std::string_view preference {item.substr(eq+1)};
94 XrdOucUtils::trim(preference);
95
96 std::string key_lower {digestName};
97 std::transform(key_lower.begin(),key_lower.end(),key_lower.begin(),::tolower);
98
99 try {
100 uint8_t preference_us = XrdOucUtils::touint8_t(preference);
101 // Max allowed value for Repr-Digest is 10
102 preference_us = std::min(preference_us,(uint8_t)10);
103 output[key_lower] = preference_us;
104 } catch (...) {
105 // discard invalid values
106 }
107 }
108}
static uint8_t touint8_t(const std::string_view sv)

References XrdOucUtils::touint8_t(), and XrdOucUtils::trim().

Referenced by XrdHttpReq::parseLine().

Here is the call graph for this function:
Here is the caller graph for this function:

The documentation for this class was generated from the following files: