XRootD
XrdHttpTpcState.cc
Go to the documentation of this file.
1 
2 #include <algorithm>
3 #include <sstream>
4 #include <stdexcept>
5 
6 #include "XrdVersion.hh"
9 #include "XrdOuc/XrdOucTUtils.hh"
10 
11 #include <curl/curl.h>
12 
13 #include "XrdHttpTpcState.hh"
14 #include "XrdHttpTpcStream.hh"
15 
17 
18 using 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 
30 void 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 
63 bool 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 
200 size_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 
207 int 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 
259 size_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 
281 size_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 
299 ssize_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 
310 void 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 
330 size_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 
337 int 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 
370 void 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 
384 void State::DumpBuffers() const
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
void getline(uchar *buff, int blen)
#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 Read(off_t offset, char *buffer, size_t size)
ssize_t Write(off_t offset, const char *buffer, size_t size, bool force)
void DumpBuffers() const
std::string GetErrorMessage() const
size_t AvailableBuffers() const
int Stat(struct stat *)
ssize_t Flush()
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)
Definition: XrdOucTUtils.hh:79