XRootD
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.
Definition: XrdOucTUtils.hh:51
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};
146  XrdOucUtils::trim(sv);
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
84  XrdOucUtils::trim(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: