XRootD
XrdHttpHeaderUtils.cc
Go to the documentation of this file.
1 //------------------------------------------------------------------------------
2 // This file is part of XrdHTTP: A pragmatic implementation of the
3 // HTTP/WebDAV protocol for the Xrootd framework
4 //
5 // Copyright (c) 2025 by European Organization for Nuclear Research (CERN)
6 // Author: Cedric Caffy <ccaffy@cern.ch>
7 // File Date: Jun 2025
8 //------------------------------------------------------------------------------
9 // XRootD is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU Lesser General Public License as published by
11 // the Free Software Foundation, either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // XRootD is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public License
20 // along with XRootD. If not, see <http://www.gnu.org/licenses/>.
21 //------------------------------------------------------------------------------
22 #include "XrdHttpHeaderUtils.hh"
23 #include "XrdOuc/XrdOucTUtils.hh"
24 #include "XrdOuc/XrdOucUtils.hh"
25 #include "XrdHttpUtils.hh"
26 
27 #include <vector>
28 #include <algorithm>
29 #include <cerrno>
30 #include <cstdlib>
31 #include <limits>
32 #include <string_view>
33 
34 
35 void XrdHttpHeaderUtils::parseReprDigest(const std::string &value, std::map<std::string, std::string> &output) {
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 }
72 
73 void XrdHttpHeaderUtils::parseWantReprDigest(const std::string & value, std::map<std::string, uint8_t> &output) {
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 }
109 
110 ssize_t XrdHttpHeaderUtils::parseContentLength(const std::string & value) {
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 }
135 
136 int XrdHttpHeaderUtils::parseTransferEncoding(const std::string & value) {
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 }
void base64DecodeHex(const std::string &base64, std::string &hexOutput)
Utility functions for XrdHTTP.
static int parseTransferEncoding(const std::string &value)
static void parseWantReprDigest(const std::string &value, std::map< std::string, uint8_t > &output)
static void parseReprDigest(const std::string &value, std::map< std::string, std::string > &output)
static ssize_t parseContentLength(const std::string &value)
static void splitString(Container &result, const std::string &input, const std::string &delimiter)
Split a string.
Definition: XrdOucTUtils.hh:51
static uint8_t touint8_t(const std::string_view sv)
static void trim(std::string &str)