XRootD
Loading...
Searching...
No Matches
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"
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
35void 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
73void 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
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
110ssize_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
136int 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};
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.
static uint8_t touint8_t(const std::string_view sv)
static void trim(std::string &str)