XRootD
Loading...
Searching...
No Matches
XrdHttpTpcState.cc
Go to the documentation of this file.
1
2#include <algorithm>
3#include <sstream>
4#include <stdexcept>
5
6#include "XrdVersion.hh"
10
11#include <curl/curl.h>
12
13#include "XrdHttpTpcState.hh"
14#include "XrdHttpTpcStream.hh"
15
17
18using namespace TPC;
19
20
22 if (m_headers) {
23 curl_slist_free_all(m_headers);
24 m_headers = NULL;
25 if (m_curl) {curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headers);}
26 }
27}
28
29
30void State::Move(State &other)
31{
32 m_push = other.m_push;
33 m_recv_status_line = other.m_recv_status_line;
34 m_recv_all_headers = other.m_recv_all_headers;
35 m_offset = other.m_offset;
36 m_start_offset = other.m_start_offset;
37 m_status_code = other.m_status_code;
38 m_content_length = other.m_content_length;
39 m_push_length = other.m_push_length;
40 m_stream = other.m_stream;
41 m_curl = other.m_curl;
42 m_headers = other.m_headers;
43 m_headers_copy = other.m_headers_copy;
44 m_resp_protocol = other.m_resp_protocol;
45 m_is_transfer_state = other.m_is_transfer_state;
46 curl_easy_setopt(m_curl, CURLOPT_HEADERDATA, this);
47 if (m_is_transfer_state) {
48 if (m_push) {
49 curl_easy_setopt(m_curl, CURLOPT_READDATA, this);
50 } else {
51 curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this);
52 }
53 }
54 tpcForwardCreds = other.tpcForwardCreds;
55 other.m_headers_copy.clear();
56 other.m_curl = NULL;
57 other.m_headers = NULL;
58 other.m_stream = NULL;
59 other.m_repr_digests = m_repr_digests;
60}
61
62
63bool State::InstallHandlers(CURL *curl) {
64 curl_easy_setopt(curl, CURLOPT_USERAGENT, "xrootd-tpc/" XrdVERSION);
65 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &State::HeaderCB);
66 curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
67 if(m_is_transfer_state) {
68 if (m_push) {
69 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
70 curl_easy_setopt(curl, CURLOPT_READFUNCTION, &State::ReadCB);
71 curl_easy_setopt(curl, CURLOPT_READDATA, this);
72 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &State::PushRespCB);
73 curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
74 struct stat buf;
75 if (SFS_OK == m_stream->Stat(&buf)) {
76 m_push_length = buf.st_size;
77 curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, buf.st_size);
78 }
79 } else {
80 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &State::WriteCB);
81 curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
82 }
83 }
84 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
85 if(tpcForwardCreds) {
86 curl_easy_setopt(curl,CURLOPT_UNRESTRICTED_AUTH,1L);
87 }
88
89 return true;
90}
91
100 struct curl_slist *list = NULL;
101 for (const auto & [header,value]: req.headers) {
102 if (!strncasecmp(header.c_str(),"copy-header", 11)) {
103 list = curl_slist_append(list, value.c_str());
104 m_headers_copy.emplace_back(value);
105 }
106 // Note: len("TransferHeader") == 14
107 if (!strncasecmp(header.c_str(),"transferheader",14)) {
108 std::stringstream ss;
109 ss << header.substr(14) << ": " << value;
110 list = curl_slist_append(list, ss.str().c_str());
111 m_headers_copy.emplace_back(ss.str());
112 }
113 }
114
115 if(m_is_transfer_state && !m_push && !req.mReprDigest.empty()) {
116 size_t reprDigestSize = req.mReprDigest.size();
117 std::stringstream ss;
118 ss << "Want-Repr-Digest: ";
119 size_t cpt = 1;
120 for (const auto &kv: req.mReprDigest) {
121 // We put the same weight for the digest names as we do not have any way, according to the specs,
122 // to give priority to a digest name in particular
123 ss << kv.first << '=' << 5;
124 if(cpt < reprDigestSize) {
125 ss << ',';
126 }
127 cpt++;
128 }
129 list = curl_slist_append(list, ss.str().c_str());
130 m_headers_copy.emplace_back(ss.str());
131 }
132
133 if (m_is_transfer_state && m_push && m_push_length > 0) {
134 // On libcurl 8.5.0 - 8.9.1, we've observed bugs causing failures whenever
135 // `Expect: 100-continue` is not used. Older versions of libcurl unconditionally
136 // set `Expect` whenever PUT is used (likely an older bug). To workaround the issue,
137 // we force `Expect` to be set, triggering the older libcurl behavior.
138 // See: https://github.com/xrootd/xrootd/issues/2470
139 // See: https://github.com/curl/curl/issues/17004
140 list = curl_slist_append(list, "Expect: 100-continue");
141 // Add Repr-Digest header to PUT request (PUSH)
142 auto reprDigest = XrdOucTUtils::caseInsensitiveFind(req.headers,"repr-digest");
143 if(reprDigest != req.headers.end()) {
144 std::string reprDigestHeader {"Repr-Digest: " + reprDigest->second};
145 curl_slist_append(list,reprDigestHeader.c_str());
146 }
147 }
148
149 if (list != nullptr) {
150 curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, list);
151 m_headers = list;
152 }
153}
154
156 struct curl_slist *list = NULL;
157 for (const auto & [header,value]: req.headers) {
158 if (!strncasecmp(header.c_str(),"copy-header", 11)) {
159 list = curl_slist_append(list, value.c_str());
160 }
161 // Note: len("TransferHeader") == 14
162 if (!strncasecmp(header.c_str(),"transferheader",14)) {
163 std::stringstream ss;
164 ss << header.substr(14) << ": " << value;
165 list = curl_slist_append(list, ss.str().c_str());
166 }
167 }
168 if(!req.mReprDigest.empty()) {
169 size_t reprDigestSize = req.mReprDigest.size();
170 std::stringstream ss;
171 ss << "Want-Repr-Digest: ";
172 size_t cpt = 1;
173 for (const auto &kv: req.mReprDigest) {
174 // We put the same weight for the digest names as we do not have any way, according to the specs,
175 // to give priority to a digest name in particular
176 ss << kv.first << '=' << 5;
177 if(cpt < reprDigestSize) {
178 ss << ',';
179 }
180 cpt++;
181 }
182 list = curl_slist_append(list, ss.str().c_str());
183 }
184
185 if (list != nullptr) {
186 curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, list);
187 }
188}
189
191 m_offset = 0;
192 m_status_code = -1;
193 m_content_length = -1;
194 m_push_length = -1;
195 m_recv_all_headers = false;
196 m_recv_status_line = false;
197 m_repr_digests.clear();
198}
199
200size_t State::HeaderCB(char *buffer, size_t size, size_t nitems, void *userdata)
201{
202 State *obj = static_cast<State*>(userdata);
203 std::string header(buffer, size*nitems);
204 return obj->Header(header);
205}
206
207int State::Header(const std::string &header) {
208 //printf("Received remote header (%d, %d): %s", m_recv_all_headers, m_recv_status_line, header.c_str());
209 if (m_recv_all_headers) { // This is the second request -- maybe processed a redirect?
210 m_recv_all_headers = false;
211 m_recv_status_line = false;
212 }
213 if (!m_recv_status_line) {
214 std::stringstream ss(header);
215 std::string item;
216 if (!std::getline(ss, item, ' ')) return 0;
217 m_resp_protocol = item;
218 //printf("\n\nResponse protocol: %s\n", m_resp_protocol.c_str());
219 if (!std::getline(ss, item, ' ')) return 0;
220 try {
221 m_status_code = std::stol(item);
222 } catch (...) {
223 return 0;
224 }
225 m_recv_status_line = true;
226 } else if (header.size() == 0 || header == "\n" || header == "\r\n") {
227 m_recv_all_headers = true;
228 }
229 else if (header != "\r\n") {
230 // Parse the header
231 std::size_t found = header.find(":");
232 if (found != std::string::npos) {
233 std::string header_name = header.substr(0, found);
234 std::transform(header_name.begin(), header_name.end(), header_name.begin(), ::tolower);
235 std::string header_value = header.substr(found+1);
236 if (header_name == "content-length")
237 {
238 try {
239 m_content_length = std::stoll(header_value);
240 } catch (...) {
241 // Header unparseable -- not a great sign, fail request.
242 //printf("Content-length header unparseable\n");
243 return 0;
244 }
245 }
246 if(header_name == "repr-digest") {
247 XrdHttpHeaderUtils::parseReprDigest(header_value,m_repr_digests);
248 }
249 } else {
250 // Non-empty header that isn't the status line, but no ':' present --
251 // malformed request?
252 //printf("Malformed header: %s\n", header.c_str());
253 return 0;
254 }
255 }
256 return header.size();
257}
258
259size_t State::WriteCB(void *buffer, size_t size, size_t nitems, void *userdata) {
260 State *obj = static_cast<State*>(userdata);
261 if (obj->GetStatusCode() < 0) {
262 return 0;
263 } // malformed request - got body before headers.
264 if (obj->GetStatusCode() >= 400) {
265 obj->m_error_buf += std::string(static_cast<char*>(buffer),
266 std::min(static_cast<size_t>(1024), size*nitems));
267 // Record error messages until we hit a KB; at that point, fail out.
268 if (obj->m_error_buf.size() >= 1024)
269 return 0;
270 else
271 return size*nitems;
272 } // Status indicates failure.
273 return obj->Write(static_cast<char*>(buffer), size*nitems);
274}
275
281size_t State::PushRespCB(void *buffer, size_t size, size_t nitems, void *userdata) {
282 State *obj = static_cast<State*>(userdata);
283 // Note: The obj's status code is set by the HeaderCB once there's a reply from the passive server
284 if (obj->GetStatusCode() < 0) {
285 return 0;
286 } // malformed request - got body before headers.
287 if (obj->GetStatusCode() >= 400) {
288 obj->m_error_buf += std::string(static_cast<char*>(buffer),
289 std::min(static_cast<size_t>(1024), size*nitems));
290 // Record error messages until we hit a KB; at that point, fail out.
291 if (obj->m_error_buf.size() >= 1024)
292 return 0;
293 else
294 return size*nitems;
295 }
296 return size*nitems;
297}
298
299ssize_t State::Write(char *buffer, size_t size) {
300 ssize_t retval = m_stream->Write(m_start_offset + m_offset, buffer, size, false);
301 if (retval == SFS_ERROR) {
302 m_error_buf = m_stream->GetErrorMessage();
303 m_error_code = errWrite;
304 return -1;
305 }
306 m_offset += retval;
307 return retval;
308}
309
310void State::RecordFinalizeError(int error_code, const std::string &error_msg) {
311 if (m_finalize_error_code) {
312 return;
313 }
314 m_finalize_error_code = error_code;
315 m_finalize_error_buf = error_msg;
316}
317
319 if (m_push) {
320 return 0;
321 }
322
323 if (m_stream->Flush() == SFS_ERROR) {
324 RecordFinalizeError(errFlush, m_stream->GetErrorMessage());
325 return -1;
326 }
327 return 0;
328}
329
330size_t State::ReadCB(void *buffer, size_t size, size_t nitems, void *userdata) {
331 State *obj = static_cast<State*>(userdata);
332 if (obj->GetStatusCode() < 0) {return 0;} // malformed request - got body before headers.
333 if (obj->GetStatusCode() >= 400) {return 0;} // Status indicates failure.
334 return obj->Read(static_cast<char*>(buffer), size*nitems);
335}
336
337int State::Read(char *buffer, size_t size) {
338 int retval = m_stream->Read(m_start_offset + m_offset, buffer, size);
339 if (retval == SFS_ERROR) {
340 return -1;
341 }
342 m_offset += retval;
343 //printf("Read a total of %ld bytes.\n", m_offset);
344 return retval;
345}
346
348 CURL *curl = curl_easy_duphandle(m_curl);
349 if (!curl) {
350 throw std::runtime_error("Failed to duplicate existing curl handle.");
351 }
352
353 State *state = new State(0, *m_stream, curl, m_push, tpcForwardCreds);
354
355 if (m_headers) {
356 state->m_headers_copy.reserve(m_headers_copy.size());
357 for (std::vector<std::string>::const_iterator header_iter = m_headers_copy.begin();
358 header_iter != m_headers_copy.end();
359 header_iter++) {
360 state->m_headers = curl_slist_append(state->m_headers, header_iter->c_str());
361 state->m_headers_copy.push_back(*header_iter);
362 }
363 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
364 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->m_headers);
365 }
366
367 return state;
368}
369
370void State::SetTransferParameters(off_t offset, size_t size) {
371 m_start_offset = offset;
372 m_offset = 0;
373 m_content_length = size;
374 std::stringstream ss;
375 ss << offset << "-" << (offset+size-1);
376 curl_easy_setopt(m_curl, CURLOPT_RANGE, ss.str().c_str());
377}
378
380{
381 return m_stream->AvailableBuffers();
382}
383
385{
386 m_stream->DumpBuffers();
387}
388
390{
391 if (!m_stream->Finalize()) {
392 RecordFinalizeError(errClose, m_stream->GetErrorMessage());
393 return false;
394 }
395 return true;
396}
397
399{
400 // CURLINFO_PRIMARY_PORT is only defined for 7.21.0 or later; on older
401 // library versions, simply omit this information.
402#if LIBCURL_VERSION_NUM >= 0x071500
403 char *curl_ip = NULL;
404 CURLcode rc = curl_easy_getinfo(m_curl, CURLINFO_PRIMARY_IP, &curl_ip);
405 if ((rc != CURLE_OK) || !curl_ip) {
406 return "";
407 }
408 long curl_port = 0;
409 rc = curl_easy_getinfo(m_curl, CURLINFO_PRIMARY_PORT, &curl_port);
410 if ((rc != CURLE_OK) || !curl_port) {
411 return "";
412 }
413 std::stringstream ss;
414 // libcurl returns IPv6 addresses of the form:
415 // 2600:900:6:1301:5054:ff:fe0b:9cba:8000
416 // However the HTTP-TPC spec says to use the form
417 // [2600:900:6:1301:5054:ff:fe0b:9cba]:8000
418 // Hence, we add '[' and ']' whenever a ':' is seen.
419 if (NULL == strchr(curl_ip, ':'))
420 ss << "tcp:" << curl_ip << ":" << curl_port;
421 else
422 ss << "tcp:[" << curl_ip << "]:" << curl_port;
423 return ss.str();
424#else
425 return "";
426#endif
427}
void CURL
#define stat(a, b)
Definition XrdPosix.hh:105
#define SFS_ERROR
#define SFS_OK
State * Duplicate()
void Move(State &other)
int GetStatusCode() const
void DumpBuffers() const
void ResetAfterRequest()
void SetTransferParameters(off_t offset, size_t size)
std::string GetConnectionDescription()
void SetupHeaders(XrdHttpExtReq &req)
void SetupHeadersForHEAD(XrdHttpExtReq &req)
int AvailableBuffers() const
int Stat(struct stat *)
std::map< std::string, std::string > & headers
std::map< std::string, std::string > mReprDigest
Repr-Digest map where the key is the digest name and the value is the base64 encoded digest value.
static void parseReprDigest(const std::string &value, std::map< std::string, std::string > &output)
static std::map< std::string, T >::const_iterator caseInsensitiveFind(const std::map< std::string, T > &m, const std::string &lowerCaseSearchKey)