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 
10 #include <curl/curl.h>
11 
12 #include "XrdHttpTpcState.hh"
13 #include "XrdHttpTpcStream.hh"
14 
15 using namespace TPC;
16 
17 
19  if (m_headers) {
20  curl_slist_free_all(m_headers);
21  m_headers = NULL;
22  if (m_curl) {curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headers);}
23  }
24 }
25 
26 
27 void State::Move(State &other)
28 {
29  m_push = other.m_push;
30  m_recv_status_line = other.m_recv_status_line;
31  m_recv_all_headers = other.m_recv_all_headers;
32  m_offset = other.m_offset;
33  m_start_offset = other.m_start_offset;
34  m_status_code = other.m_status_code;
35  m_content_length = other.m_content_length;
36  m_push_length = other.m_push_length;
37  m_stream = other.m_stream;
38  m_curl = other.m_curl;
39  m_headers = other.m_headers;
40  m_headers_copy = other.m_headers_copy;
41  m_resp_protocol = other.m_resp_protocol;
42  m_is_transfer_state = other.m_is_transfer_state;
43  curl_easy_setopt(m_curl, CURLOPT_HEADERDATA, this);
44  if (m_is_transfer_state) {
45  if (m_push) {
46  curl_easy_setopt(m_curl, CURLOPT_READDATA, this);
47  } else {
48  curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this);
49  }
50  }
51  tpcForwardCreds = other.tpcForwardCreds;
52  other.m_headers_copy.clear();
53  other.m_curl = NULL;
54  other.m_headers = NULL;
55  other.m_stream = NULL;
56 }
57 
58 
59 bool State::InstallHandlers(CURL *curl) {
60  curl_easy_setopt(curl, CURLOPT_USERAGENT, "xrootd-tpc/" XrdVERSION);
61  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &State::HeaderCB);
62  curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
63  if(m_is_transfer_state) {
64  if (m_push) {
65  curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
66  curl_easy_setopt(curl, CURLOPT_READFUNCTION, &State::ReadCB);
67  curl_easy_setopt(curl, CURLOPT_READDATA, this);
68  struct stat buf;
69  if (SFS_OK == m_stream->Stat(&buf)) {
70  m_push_length = buf.st_size;
71  curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, buf.st_size);
72  }
73  } else {
74  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &State::WriteCB);
75  curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
76  }
77  }
78  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
79  if(tpcForwardCreds) {
80  curl_easy_setopt(curl,CURLOPT_UNRESTRICTED_AUTH,1L);
81  }
82 
83  return true;
84 }
85 
94  struct curl_slist *list = NULL;
95  for (std::map<std::string, std::string>::const_iterator hdr_iter = req.headers.begin();
96  hdr_iter != req.headers.end();
97  hdr_iter++) {
98  if (!strcasecmp(hdr_iter->first.c_str(),"copy-header")) {
99  list = curl_slist_append(list, hdr_iter->second.c_str());
100  m_headers_copy.emplace_back(hdr_iter->second);
101  }
102  // Note: len("TransferHeader") == 14
103  if (!strncasecmp(hdr_iter->first.c_str(),"transferheader",14)) {
104  std::stringstream ss;
105  ss << hdr_iter->first.substr(14) << ": " << hdr_iter->second;
106  list = curl_slist_append(list, ss.str().c_str());
107  m_headers_copy.emplace_back(ss.str());
108  }
109  }
110 
111  if (m_is_transfer_state && m_push && m_push_length > 0) {
112  // On libcurl 8.5.0 - 8.9.1, we've observed bugs causing failures whenever
113  // `Expect: 100-continue` is not used. Older versions of libcurl unconditionally
114  // set `Expect` whenever PUT is used (likely an older bug). To workaround the issue,
115  // we force `Expect` to be set, triggering the older libcurl behavior.
116  // See: https://github.com/xrootd/xrootd/issues/2470
117  // See: https://github.com/curl/curl/issues/17004
118  list = curl_slist_append(list, "Expect: 100-continue");
119  }
120 
121  if (list != NULL) {
122  curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, list);
123  m_headers = list;
124  }
125 }
126 
128  m_offset = 0;
129  m_status_code = -1;
130  m_content_length = -1;
131  m_push_length = -1;
132  m_recv_all_headers = false;
133  m_recv_status_line = false;
134 }
135 
136 size_t State::HeaderCB(char *buffer, size_t size, size_t nitems, void *userdata)
137 {
138  State *obj = static_cast<State*>(userdata);
139  std::string header(buffer, size*nitems);
140  return obj->Header(header);
141 }
142 
143 int State::Header(const std::string &header) {
144  //printf("Received remote header (%d, %d): %s", m_recv_all_headers, m_recv_status_line, header.c_str());
145  if (m_recv_all_headers) { // This is the second request -- maybe processed a redirect?
146  m_recv_all_headers = false;
147  m_recv_status_line = false;
148  }
149  if (!m_recv_status_line) {
150  std::stringstream ss(header);
151  std::string item;
152  if (!std::getline(ss, item, ' ')) return 0;
153  m_resp_protocol = item;
154  //printf("\n\nResponse protocol: %s\n", m_resp_protocol.c_str());
155  if (!std::getline(ss, item, ' ')) return 0;
156  try {
157  m_status_code = std::stol(item);
158  } catch (...) {
159  return 0;
160  }
161  m_recv_status_line = true;
162  } else if (header.size() == 0 || header == "\n" || header == "\r\n") {
163  m_recv_all_headers = true;
164  }
165  else if (header != "\r\n") {
166  // Parse the header
167  std::size_t found = header.find(":");
168  if (found != std::string::npos) {
169  std::string header_name = header.substr(0, found);
170  std::transform(header_name.begin(), header_name.end(), header_name.begin(), ::tolower);
171  std::string header_value = header.substr(found+1);
172  if (header_name == "content-length")
173  {
174  try {
175  m_content_length = std::stoll(header_value);
176  } catch (...) {
177  // Header unparseable -- not a great sign, fail request.
178  //printf("Content-length header unparseable\n");
179  return 0;
180  }
181  }
182  } else {
183  // Non-empty header that isn't the status line, but no ':' present --
184  // malformed request?
185  //printf("Malformed header: %s\n", header.c_str());
186  return 0;
187  }
188  }
189  return header.size();
190 }
191 
192 size_t State::WriteCB(void *buffer, size_t size, size_t nitems, void *userdata) {
193  State *obj = static_cast<State*>(userdata);
194  if (obj->GetStatusCode() < 0) {
195  return 0;
196  } // malformed request - got body before headers.
197  if (obj->GetStatusCode() >= 400) {
198  obj->m_error_buf += std::string(static_cast<char*>(buffer),
199  std::min(static_cast<size_t>(1024), size*nitems));
200  // Record error messages until we hit a KB; at that point, fail out.
201  if (obj->m_error_buf.size() >= 1024)
202  return 0;
203  else
204  return size*nitems;
205  } // Status indicates failure.
206  return obj->Write(static_cast<char*>(buffer), size*nitems);
207 }
208 
209 ssize_t State::Write(char *buffer, size_t size) {
210  ssize_t retval = m_stream->Write(m_start_offset + m_offset, buffer, size, false);
211  if (retval == SFS_ERROR) {
212  m_error_buf = m_stream->GetErrorMessage();
213  m_error_code = errWrite;
214  return -1;
215  }
216  m_offset += retval;
217  return retval;
218 }
219 
220 void State::RecordFinalizeError(int error_code, const std::string &error_msg) {
221  if (m_finalize_error_code) {
222  return;
223  }
224  m_finalize_error_code = error_code;
225  m_finalize_error_buf = error_msg;
226 }
227 
229  if (m_push) {
230  return 0;
231  }
232 
233  ssize_t retval = m_stream->Write(m_start_offset + m_offset, 0, 0, true);
234  if (retval == SFS_ERROR) {
235  RecordFinalizeError(errFlush, m_stream->GetErrorMessage());
236  return -1;
237  }
238  m_offset += retval;
239  return retval;
240 }
241 
242 size_t State::ReadCB(void *buffer, size_t size, size_t nitems, void *userdata) {
243  State *obj = static_cast<State*>(userdata);
244  if (obj->GetStatusCode() < 0) {return 0;} // malformed request - got body before headers.
245  if (obj->GetStatusCode() >= 400) {return 0;} // Status indicates failure.
246  return obj->Read(static_cast<char*>(buffer), size*nitems);
247 }
248 
249 int State::Read(char *buffer, size_t size) {
250  int retval = m_stream->Read(m_start_offset + m_offset, buffer, size);
251  if (retval == SFS_ERROR) {
252  return -1;
253  }
254  m_offset += retval;
255  //printf("Read a total of %ld bytes.\n", m_offset);
256  return retval;
257 }
258 
260  CURL *curl = curl_easy_duphandle(m_curl);
261  if (!curl) {
262  throw std::runtime_error("Failed to duplicate existing curl handle.");
263  }
264 
265  State *state = new State(0, *m_stream, curl, m_push, tpcForwardCreds);
266 
267  if (m_headers) {
268  state->m_headers_copy.reserve(m_headers_copy.size());
269  for (std::vector<std::string>::const_iterator header_iter = m_headers_copy.begin();
270  header_iter != m_headers_copy.end();
271  header_iter++) {
272  state->m_headers = curl_slist_append(state->m_headers, header_iter->c_str());
273  state->m_headers_copy.push_back(*header_iter);
274  }
275  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
276  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->m_headers);
277  }
278 
279  return state;
280 }
281 
282 void State::SetTransferParameters(off_t offset, size_t size) {
283  m_start_offset = offset;
284  m_offset = 0;
285  m_content_length = size;
286  std::stringstream ss;
287  ss << offset << "-" << (offset+size-1);
288  curl_easy_setopt(m_curl, CURLOPT_RANGE, ss.str().c_str());
289 }
290 
292 {
293  return m_stream->AvailableBuffers();
294 }
295 
296 void State::DumpBuffers() const
297 {
298  m_stream->DumpBuffers();
299 }
300 
302 {
303  if (!m_stream->Finalize()) {
304  RecordFinalizeError(errClose, m_stream->GetErrorMessage());
305  return false;
306  }
307  return true;
308 }
309 
311 {
312  // CURLINFO_PRIMARY_PORT is only defined for 7.21.0 or later; on older
313  // library versions, simply omit this information.
314 #if LIBCURL_VERSION_NUM >= 0x071500
315  char *curl_ip = NULL;
316  CURLcode rc = curl_easy_getinfo(m_curl, CURLINFO_PRIMARY_IP, &curl_ip);
317  if ((rc != CURLE_OK) || !curl_ip) {
318  return "";
319  }
320  long curl_port = 0;
321  rc = curl_easy_getinfo(m_curl, CURLINFO_PRIMARY_PORT, &curl_port);
322  if ((rc != CURLE_OK) || !curl_port) {
323  return "";
324  }
325  std::stringstream ss;
326  // libcurl returns IPv6 addresses of the form:
327  // 2600:900:6:1301:5054:ff:fe0b:9cba:8000
328  // However the HTTP-TPC spec says to use the form
329  // [2600:900:6:1301:5054:ff:fe0b:9cba]:8000
330  // Hence, we add '[' and ']' whenever a ':' is seen.
331  if (NULL == strchr(curl_ip, ':'))
332  ss << "tcp:" << curl_ip << ":" << curl_port;
333  else
334  ss << "tcp:[" << curl_ip << "]:" << curl_port;
335  return ss.str();
336 #else
337  return "";
338 #endif
339 }
void CURL
#define stat(a, b)
Definition: XrdPosix.hh:101
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)
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 *)
std::map< std::string, std::string > & headers