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"
9
10#include <curl/curl.h>
11
12#include "XrdHttpTpcState.hh"
13#include "XrdHttpTpcStream.hh"
14
15using 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
27void 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
59bool 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
136size_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
143int 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
192size_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
209ssize_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
220void 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
242size_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
249int 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
282void 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
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
#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 Stat(struct stat *)
std::map< std::string, std::string > & headers