XRootD
Loading...
Searching...
No Matches
XrdHttpReq.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) 2013 by European Organization for Nuclear Research (CERN)
6// Author: Fabrizio Furano <furano@cern.ch>
7// File Date: Nov 2012
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
23
24
25
26
27
28
29
30
39#include "XrdVersion.hh"
40#include "XrdHttpReq.hh"
41#include "XrdHttpTrace.hh"
42#include "XrdHttpExtHandler.hh"
43#include "XrdHttpHeaderUtils.hh"
44#include <cstring>
45#include <arpa/inet.h>
46#include <sstream>
48#include "XrdOuc/XrdOucEnv.hh"
49#include "XrdHttpProtocol.hh"
50#include "Xrd/XrdLink.hh"
52#include "Xrd/XrdBuffer.hh"
53#include <algorithm>
54#include <functional>
55#include <cctype>
56#include <locale>
57#include <string>
59#include "XrdOuc/XrdOucUtils.hh"
62
63#include "XrdHttpUtils.hh"
64
65#include "XrdHttpStatic.hh"
66
67#define MAX_TK_LEN 256
68#define MAX_RESOURCE_LEN 16384
69
70// This is to fix the trace macros
71#define TRACELINK prot->Link
72
73namespace
74{
75const char *TraceID = "Req";
76}
77
78void trim(std::string &str)
79{
81}
82
83
84std::string ISOdatetime(time_t t) {
85 char datebuf[128];
86 struct tm t1;
87
88 memset(&t1, 0, sizeof (t1));
89 gmtime_r(&t, &t1);
90
91 strftime(datebuf, 127, "%a, %d %b %Y %H:%M:%S GMT", &t1);
92 return (std::string) datebuf;
93
94}
95
96int XrdHttpReq::parseBody(char *body, long long len) {
97 /*
98 * The document being in memory, it has no base per RFC 2396,
99 * and the "noname.xml" argument will serve as its base.
100 */
101 //xmlbody = xmlReadMemory(body, len, "noname.xml", NULL, 0);
102 //if (xmlbody == NULL) {
103 // fprintf(stderr, "Failed to parse document\n");
104 // return 1;
105 //}
106
107
108
109 return 1;
110}
111
113 //if (xmlbody) xmlFreeDoc(xmlbody);
114
115 reset();
116}
117
118int XrdHttpReq::parseLine(char *line, int len) {
119
120 char *key = line;
121 int pos;
122
123 // Do the parsing
124 if (!line) return -1;
125
126
127 char *p = strchr((char *) line, (int) ':');
128 if (!p) {
129
131 return -1;
132 }
133
134 pos = (p - line);
135 if (pos > (MAX_TK_LEN - 1)) {
136
138 return -2;
139 }
140
141 if (pos > 0) {
142 line[pos] = 0;
143 char *val = line + pos + 1;
144
145 // Trim left
146 while ( (!isgraph(*val) || (!*val)) && (val < line+len)) val++;
147
148 // We memorize the headers also as a string
149 // because external plugins may need to process it differently
150 std::string ss = val;
151 if(ss.length() >= 2 && ss.substr(ss.length() - 2, 2) != "\r\n") {
153 return -3;
154 }
155 trim(ss);
156 allheaders[key] = ss;
157
158 // Here we are supposed to initialize whatever flag or variable that is needed
159 // by looking at the first token of the line
160 // The token is key
161 // The value is val
162
163 // Screen out the needed header lines
164 if (!strcasecmp(key, "connection")) {
165
166 if (!strcasecmp(val, "Keep-Alive\r\n")) {
167 keepalive = true;
168 } else if (!strcasecmp(val, "close\r\n")) {
169 keepalive = false;
170 }
171
172 } else if (!strcasecmp(key, "host")) {
173 parseHost(val);
174 } else if (!strcasecmp(key, "range")) {
175 // (rfc2616 14.35.1) says if Range header contains any range
176 // which is syntactically invalid the Range header should be ignored.
177 // Therefore no need for the range handler to report an error.
179 } else if (!strcasecmp(key, "content-length")) {
180 // Parse and validate the Content-Length value (one-or-more digits,
181 // no sign, no embedded garbage, no overflow). Anything malformed
182 // gives the server an ambiguous body length and is an HTTP request
183 // smuggling primitive — reject with HTTP 400.
184 // Reference: RFC 9112 §6.2, RFC 7230 §3.3.3 rule 4.
185 ssize_t parsed = XrdHttpHeaderUtils::parseContentLength(val);
186 if (parsed < 0) {
188 return -6;
189 }
190 if (m_transfer_encoding_chunked) {
191 // A request that already declared Transfer-Encoding: chunked and
192 // now also sends Content-Length is the classic smuggling vector
193 // (the frontend and backend may disagree on which header wins).
194 // Reference: RFC 9112 §6.1.
196 return -8;
197 }
198 if (length_seen && parsed != length) {
199 // Two Content-Length headers with different values. The body
200 // length is ambiguous; reject.
201 // Reference: RFC 7230 §3.3.3 rule 4.
203 return -7;
204 }
205 length = parsed;
206 length_seen = true;
207
208 } else if (!strcasecmp(key, "destination")) {
209 destination.assign(val, line+len-val);
211 } else if (!strcasecmp(key, "want-digest")) {
212 // Discard Want-Repr-Digest in favor of Want-Digest
213 m_want_repr_digest.clear();
214 m_want_digest.assign(val, line + len - val);
216 //Transform the user requests' want-digest to lowercase
217 std::transform(m_want_digest.begin(), m_want_digest.end(), m_want_digest.begin(), ::tolower);
218 } else if (!strcasecmp(key, "depth")) {
219 depth = -1;
220 if (strcmp(val, "infinity"))
221 depth = atoll(val);
222
223 } else if (!strcasecmp(key, "expect") && strstr(val, "100-continue")) {
224 sendcontinue = true;
225 } else if (!strcasecmp(key, "te") && strstr(val, "trailers")) {
226 m_trailer_headers = true;
227 } else if (!strcasecmp(key, "transfer-encoding")) {
228 // Tokenize the Transfer-Encoding list and verify that "chunked"
229 // is present AND is the final encoding. Anything else (substring
230 // matches like "chunkedX", a non-final "chunked", or only unknown
231 // codings) is rejected so a frontend proxy cannot disagree with us
232 // about how the body is framed.
233 // Reference: RFC 9112 §6.1, RFC 7230 §3.3.1.
236 return -4;
237 }
238 if (length_seen) {
239 // Content-Length was already accepted and now Transfer-Encoding:
240 // chunked arrives. Reject (see the matching check in the
241 // Content-Length branch above).
242 // Reference: RFC 9112 §6.1.
244 return -8;
245 }
246 m_transfer_encoding_chunked = true;
247 } else if (!strcasecmp(key, "x-transfer-status") && strstr(val, "true")) {
248 m_transfer_encoding_chunked = true;
249 m_status_trailer = true;
250 } else if (!strcasecmp(key, "scitag")) {
251 if(prot->pmarkHandle != nullptr) {
252 parseScitag(val);
253 }
254 } else if (!strcasecmp(key, "user-agent")) {
255 m_user_agent = val;
256 trim(m_user_agent);
257 } else if (!strcasecmp(key,"origin")) {
258 m_origin = val;
259 trim(m_origin);
260 } else if (!strcasecmp(key,"repr-digest")) {
262 } else if (!strcasecmp(key,"want-repr-digest")) {
263 if(m_want_digest.empty()) {
264 // If Want-Digest was set, don't parse want-repr-digest
266 }
267 } else {
268 // Some headers need to be translated into "local" cgi info.
269 auto it = std::find_if(prot->hdr2cgimap.begin(), prot->hdr2cgimap.end(),[key](const auto & item) {
270 return !strcasecmp(key,item.first.c_str());
271 });
272 if (it != prot->hdr2cgimap.end() && (opaque ? (0 == opaque->Get(it->second.c_str())) : true)) {
273 std::string s;
274 s.assign(val, line+len-val);
275 trim(s);
276 addCgi(it->second,s);
277 }
278 }
279
280
281 line[pos] = ':';
282 }
283
284 return 0;
285}
286
287int XrdHttpReq::parseHost(char *line) {
288 host = line;
289 trim(host);
290 return 0;
291}
292
293void XrdHttpReq::parseScitag(const std::string & val) {
294 // The scitag header has been populated and the packet marking was configured, the scitag will either be equal to 0
295 // or to the value passed by the client
296 mScitag = 0;
297 std::string scitagS = val;
298 trim(scitagS);
299 if(scitagS.size()) {
300 if(scitagS[0] != '-') {
301 try {
302 mScitag = std::stoi(scitagS.c_str(), nullptr, 10);
304 mScitag = 0;
305 }
306 } catch (...) {
307 //Nothing to do, scitag = 0 by default
308 }
309 }
310 }
311 addCgi("scitag.flow", std::to_string(mScitag));
313 // We specify to the packet marking handle the type of transfer this request is
314 // so the source and destination in the firefly are properly set
315 addCgi("pmark.appname",this->request == ReqType::rtGET ? "http-get" : "http-put");
316 }
317}
318
319int XrdHttpReq::parseFirstLine(char *line, int len) {
320
321 char *key = line;
322
323 int pos;
324
325 // Do the naive parsing
326 if (!line) return -1;
327
328 // Look for the first space-delimited token
329 char *p = strchr((char *) line, (int) ' ');
330 if (!p) {
332 return -1;
333 }
334
335
336 pos = p - line;
337 // The first token cannot be too long
338 if (pos > MAX_TK_LEN - 1) {
340 return -2;
341 }
342
343 // The first space-delimited char cannot be the first one
344 // this allows to deal with the case when a client sends a first line that starts with a space " GET / HTTP/1.1"
345 if(pos == 0) {
347 return -4;
348 }
349
350 // the first token must be non empty
351 if (pos > 0) {
352 line[pos] = 0;
353 char *val = line + pos + 1;
354
355 // Here we are supposed to initialize whatever flag or variable that is needed
356 // by looking at the first token of the line
357
358 // The token is key
359 // The remainder is val, look for the resource
360 p = strchr((char *) val, (int) ' ');
361
362 if (!p) {
364 line[pos] = ' ';
365 return -3;
366 }
367
368 *p = '\0';
369 parseResource(val);
370
371 *p = ' ';
372
373 // Xlate the known header lines
374 if (!strcmp(key, "GET")) {
375 request = rtGET;
376 } else if (!strcmp(key, "HEAD")) {
377 request = rtHEAD;
378 } else if (!strcmp(key, "PUT")) {
379 request = rtPUT;
380 } else if (!strcmp(key, "POST")) {
381 request = rtPOST;
382 } else if (!strcmp(key, "PATCH")) {
384 } else if (!strcmp(key, "OPTIONS")) {
386 } else if (!strcmp(key, "DELETE")) {
388 } else if (!strcmp(key, "PROPFIND")) {
390 } else if (!strcmp(key, "MKCOL")) {
392 } else if (!strcmp(key, "MOVE")) {
393 request = rtMOVE;
394 } else if (!strcmp(key, "COPY")) {
395 request = rtCOPY;
396 } else {
398 }
399
400 requestverb = key;
401
402 // The last token should be the protocol. If it is HTTP/1.0, then
403 // keepalive is disabled by default.
404 if (!strcmp(p+1, "HTTP/1.0\r\n")) {
405 keepalive = false;
406 }
407 line[pos] = ' ';
408 }
409
410 return 0;
411}
412
413
414
415
416//___________________________________________________________________________
417
418void XrdHttpReq::clientMarshallReadAheadList(int nitems) {
419 // This function applies the network byte order on the
420 // vector of read-ahead information
421 kXR_int64 tmpl;
422
423
424
425 for (int i = 0; i < nitems; i++) {
426 memcpy(&tmpl, &(ralist[i].offset), sizeof (kXR_int64));
427 tmpl = htonll(tmpl);
428 memcpy(&(ralist[i].offset), &tmpl, sizeof (kXR_int64));
429 ralist[i].rlen = htonl(ralist[i].rlen);
430 }
431}
432
433
434//___________________________________________________________________________
435
436void XrdHttpReq::clientUnMarshallReadAheadList(int nitems) {
437 // This function applies the network byte order on the
438 // vector of read-ahead information
439 kXR_int64 tmpl;
440
441
442
443 for (int i = 0; i < nitems; i++) {
444 memcpy(&tmpl, &(ralist[i].offset), sizeof (kXR_int64));
445 tmpl = ntohll(tmpl);
446 memcpy(&(ralist[i].offset), &tmpl, sizeof (kXR_int64));
447 ralist[i].rlen = ntohl(ralist[i].rlen);
448 }
449}
450
452
453
454 // Now we build the protocol-ready read ahead list
455 // and also put the correct placeholders inside the cache
456 int n = cl.size();
457 ralist.clear();
458 ralist.reserve(n);
459
460 int j = 0;
461 for (const auto &c: cl) {
462 ralist.emplace_back();
463 auto &ra = ralist.back();
464 memcpy(&ra.fhandle, this->fhandle, 4);
465
466 ra.offset = c.offset;
467 ra.rlen = c.size;
468 j++;
469 }
470
471 if (j > 0) {
472
473 // Prepare a request header
474
475 memset(&xrdreq, 0, sizeof (xrdreq));
476
478 xrdreq.readv.dlen = htonl(j * sizeof (struct readahead_list));
479
480 clientMarshallReadAheadList(j);
481
482
483 }
484
485 return (j * sizeof (struct readahead_list));
486}
487
488std::string XrdHttpReq::buildPartialHdr(long long bytestart, long long byteend, long long fsz, char *token) {
489 std::ostringstream s;
490
491 s << "\r\n--" << token << "\r\n";
492 s << "Content-type: text/plain; charset=UTF-8\r\n";
493 s << "Content-range: bytes " << bytestart << "-" << byteend << "/" << fsz << "\r\n\r\n";
494
495 return s.str();
496}
497
498std::string XrdHttpReq::buildPartialHdrEnd(char *token) {
499 std::ostringstream s;
500
501 s << "\r\n--" << token << "--\r\n";
502
503 return s.str();
504}
505
507 const
508 struct iovec *iovP_,
509 int iovN_,
510 int iovL_,
511 bool final_
512 ) {
513
514 TRACE(REQ, " XrdHttpReq::Data! final=" << final);
515
516 this->xrdresp = kXR_ok;
517 this->iovP = iovP_;
518 this->iovN = iovN_;
519 this->iovL = iovL_;
520 this->final = final_;
521
522 if (PostProcessHTTPReq(final_)) reset();
523
524 return true;
525
526};
527
529 int dlen
530 ) {
531
532 // sendfile about to be sent by bridge for fetching data for GET:
533 // no https, no chunked+trailer, no multirange
534
535 //prot->SendSimpleResp(200, NULL, NULL, NULL, dlen);
536 int rc = info.Send(0, 0, 0, 0);
537 TRACE(REQ, " XrdHttpReq::File dlen:" << dlen << " send rc:" << rc);
538 bool start, finish;
539 // short read will be classed as error
540 if (rc) {
542 return false;
543 }
544
545 if (readRangeHandler.NotifyReadResult(dlen, nullptr, start, finish) < 0)
546 return false;
547
548
549 return true;
550};
551
553
554 TRACE(REQ, " XrdHttpReq::Done");
555
556 xrdresp = kXR_ok;
557
558 this->iovN = 0;
559
560 int r = PostProcessHTTPReq(true);
561 // Beware, we don't have to reset() if the result is 0
562 if (r) reset();
563 if (r < 0) return false;
564
565
566 return true;
567};
568
570 int ecode,
571 const char *etext_
572 ) {
573
574 TRACE(REQ, " XrdHttpReq::Error");
575
577 xrderrcode = (XErrorCode) ecode;
578
579 if (etext_) {
580 char *s = escapeXML(etext_);
581 this->etext = s;
582 free(s);
583 }
584
585 auto rc = PostProcessHTTPReq();
586 if (rc) {
587 reset();
588 }
589
590 // If we are servicing a GET on a directory, it'll generate an error for the default
591 // OSS (we don't assume this is always true). Catch and suppress the error so we can instead
592 // generate a directory listing (if configured).
593 if ((request == rtGET) && (xrdreq.header.requestid == ntohs(kXR_open)) && (xrderrcode == kXR_isDirectory))
594 return true;
595
596 return rc == 0;
597};
598
600 int port,
601 const char *hname
602 ) {
603
604
605
606 char hash[512];
607 hash[0] = '\0';
608
609 bool invalid_host = false;
610
611 if (!hname) {
612 invalid_host = true;
613 } else {
614 for (const char *c = hname; *c; ++c)
615 if (*c == '\r' || *c == '\n')
616 invalid_host = true;
617 }
618
619 if (invalid_host) {
620 prot->SendSimpleResp(502, nullptr, nullptr, "Invalid redirect host", 0, false);
621 return keepalive;
622 }
623
624 // port < 0 means XRootD URL-form redirect: hname is a full scheme://… URL
625 // (host+port+path).
626 // Do not prefix http(s):// and do not append the original resource
627 // otherwise, that would produce https://https://…/pfn/lfn.
628 const bool fullUrl = (port < 0);
629 if (fullUrl) {
630 TRACE(REQ, " XrdHttpReq::Redir using absolute URL: " << hname);
631 }
632
633 // Beware, certain Ofs implementations (e.g. EOS) add opaque data directly to the host name
634 // This must be correctly treated here and appended to the opaque info
635 // that we may already have
636 char *pp = strchr((char *)hname, '?');
637 char *vardata = 0;
638 if (pp) {
639 *pp = '\0';
640 vardata = pp+1;
641 int varlen = strlen(vardata);
642
643 //Now extract the remaining, vardata points to it
644 while(*vardata == '&' && varlen) {vardata++; varlen--;}
645 }
646
647 // If the destination speaks cleartext HTTP, it needs the
648 // secret hash to reconstruct authorization info.
649 const bool destHttpCleartext =
650 fullUrl ? (strncmp(hname, "http://", 7) == 0) : (!prot->isdesthttps);
651
652 std::string urlHashPath;
653 {
654 const std::string encodedResource =
655 fullUrl ? std::string() : encode_str(resource.c_str());
656 redirdest = httpBuildRedirectLocation(prot->isdesthttps, port, hname,
657 encodedResource.c_str()).c_str();
658
659 // Derive the token path while hname is still truncated at '?', so the CGI
660 // stays out of the hash and both ends digest the same string.
661 if (fullUrl && destHttpCleartext)
663 }
664
665 // Restore '?' so later uses of hname see the original string.
666 if (pp)
667 *pp = '?';
668
669 // Here we put back the opaque info, if any
670 if (vardata) {
671 redirdest += "?&";
672 redirdest += encode_opaque(vardata).c_str();
673 }
674
675 // Shall we put also the opaque data of the request? Maybe not
676 //int l;
677 //if (opaque && opaque->Env(l))
678 // redirdest += opaque->Env(l);
679
680
681 time_t timenow = 0;
682 if (destHttpCleartext && prot->ishttps) {
683 const char *hashPath = urlHashPath.empty() ? resource.c_str() : urlHashPath.c_str();
684 // If the destination is not https, then we suppose that it
685 // will need this token to fill its authorization info
686 timenow = time(0);
687 calcHashes(hash, hashPath, (kXR_int16) request,
688 &prot->SecEntity,
689 timenow,
690 prot->secretkey);
691 }
692
693 if (hash[0]) {
694 appendOpaque(redirdest, &prot->SecEntity, hash, timenow);
695 } else
696 appendOpaque(redirdest, 0, 0, 0);
697
698 if (!prot->strp_cgi_params.empty()) {
699 stripCgi(redirdest, prot->strp_cgi_params); /* appendOpaque() may have added credentials */
700 }
701
702 TRACE(REQ, " XrdHttpReq::Redir Redirecting to " << redirdest.c_str());
703
704 if (request != rtGET)
705 prot->SendSimpleResp(307, NULL, (char *) redirdest.c_str(), 0, 0, keepalive);
706 else
707 prot->SendSimpleResp(302, NULL, (char *) redirdest.c_str(), 0, 0, keepalive);
708
709 bool ret_keepalive = keepalive; // reset() clears keepalive
710 reset();
711 return ret_keepalive;
712};
713
714
715void XrdHttpReq::appendOpaque(XrdOucString &s, XrdSecEntity *secent, char *hash, time_t tnow) {
716
717 int l = 0;
718 char * p = 0;
719 if (opaque)
720 p = opaque->Env(l);
721
722 if (hdr2cgistr.empty() && (l < 2) && !hash) return;
723
724 // this works in most cases, except if the url already contains the xrdhttp tokens
725 s = s + "?";
726 if (!hdr2cgistr.empty()) {
727 s += encode_opaque(hdr2cgistr).c_str();
728 }
729 if (p && (l > 1)) {
730 if (!hdr2cgistr.empty()) {
731 s = s + "&";
732 }
733 s = s + encode_opaque(p + 1).c_str();
734 }
735
736 if (hash) {
737 if (l > 1) s += "&";
738 s += "xrdhttptk=";
739 s += hash;
740
741 s += "&xrdhttptime=";
742 char buf[256];
743 sprintf(buf, "%lld", (long long) tnow);
744 s += buf;
745
746 if (secent) {
747 if (secent->name) {
748 s += "&xrdhttpname=";
749 s += encode_str(secent->name).c_str();
750 }
751 }
752
753 if (secent->vorg) {
754 s += "&xrdhttpvorg=";
755 s += encode_str(secent->vorg).c_str();
756 }
757
758 if (secent->host) {
759 s += "&xrdhttphost=";
760 s += encode_str(secent->host).c_str();
761 }
762
763 if (secent->moninfo) {
764 s += "&xrdhttpdn=";
765 s += encode_str(secent->moninfo).c_str();
766 }
767
768 if (secent->role) {
769 s += "&xrdhttprole=";
770 s += encode_str(secent->role).c_str();
771 }
772
773 if (secent->grps) {
774 s += "&xrdhttpgrps=";
775 s += encode_str(secent->grps).c_str();
776 }
777
778 if (secent->endorsements) {
779 s += "&xrdhttpendorsements=";
780 s += encode_str(secent->endorsements).c_str();
781 }
782
783 if (secent->credslen) {
784 s += "&xrdhttpcredslen=";
785 char buf[16];
786 sprintf(buf, "%d", secent->credslen);
787 s += encode_str(buf).c_str();
788 }
789
790 if (secent->credslen) {
791 if (secent->creds) {
792 s += "&xrdhttpcreds=";
793 // Apparently this string might be not 0-terminated (!)
794 char *zerocreds = strndup(secent->creds, secent->credslen);
795 if (zerocreds) {
796 s += encode_str(zerocreds).c_str();
797 free(zerocreds);
798 }
799 }
800 }
801 }
802 }
803
804// Sanitize the resource from the http[s]://[host]/ questionable prefix
805// https://github.com/xrootd/xrootd/issues/1675
806void XrdHttpReq::sanitizeResourcePfx() {
807
808 if (resource.beginswith("https://")) {
809 // Find the slash that follows the hostname, and keep it
810 int p = resource.find('/', 8);
812 return;
813 }
814
815 if (resource.beginswith("http://")) {
816 // Find the slash that follows the hostname, and keep it
817 int p = resource.find('/', 7);
819 return;
820 }
821}
822
823void XrdHttpReq::addCgi(const std::string &key, const std::string &value) {
824 if (hdr2cgistr.length() > 0) {
825 hdr2cgistr.append("&");
826 }
827 hdr2cgistr.append(key);
828 hdr2cgistr.append("=");
829 hdr2cgistr.append(value);
830}
831
832
833// Parse a resource line:
834// - sanitize
835// - extracts the opaque info from the given url
836// - sanitize the resource from http[s]://[host]/ questionable prefix
837void XrdHttpReq::parseResource(char *res) {
838
839
840
841
842 // Look for the first '?'
843 char *p = strchr(res, '?');
844
845 // Not found, then it's just a filename
846 if (!p) {
847 resource.assign(res, 0);
848
849 // Some poor client implementations may inject a http[s]://[host]/ prefix
850 // to the resource string. Here we choose to ignore it as a protection measure
851 sanitizeResourcePfx();
852
853 const std::string resourceDecoded = decode_str(resource.c_str());
854 resourceplusopaque = resourceDecoded.c_str();
855 resource = httpCollapseSlashes(resourceDecoded).c_str();
856
857 return;
858 }
859
860 // Whatever comes before '?' is a filename
861
862 int cnt = p - res; // Number of chars to copy
863 resource.assign(res, 0, cnt - 1);
864
865 // Some poor client implementations may inject a http[s]://[host]/ prefix
866 // to the resource string. Here we choose to ignore it as a protection measure
867 sanitizeResourcePfx();
868
870
872 // Whatever comes after is opaque data to be parsed
873 if (strlen(p) > 1) {
874 std::string decoded = decode_str(p + 1);
875 opaque = new XrdOucEnv(decoded.c_str());
878 }
879}
880
881void XrdHttpReq::generateWebdavErrMsg() {
882
883 // This block is only used when sending an "X-Transfer-Status" trailer response.
884 // We set the body to "OK" so that the trailer becomes "X-Transfer-Status: 200 OK",
885 // indicating a successful transfer.
886 if (xrdresp == kXR_ok) {
887 httpStatusCode = 200;
888 httpErrorBody = "OK";
889 return;
890 }
891
892 // default error
893 httpStatusCode = mapXrdErrToHttp(xrderrcode);
894 httpErrorBody = etext + "\n";
895
896}
897
898int XrdHttpReq::prepareChecksumQuery(XrdHttpChecksumHandler::XrdHttpChecksumRawPtr &outCksum,
899 XrdOucString &outResourceDigestOpaque) {
900 const char *opaque = strchr(resourceplusopaque.c_str(), '?');
901
902 outResourceDigestOpaque = resourceplusopaque;
903
904 if(m_want_digest.size()) {
905 // According to rfc9530 "Integrity preference fields are only a hint. The receiver of the
906 // field can ignore it and send an Integrity field using any algorithm
907 // or omit the field entirely.
908 // However, in the case a client requests both Want-Digest AND Want-Repr-Digest,
909 // we will return a 'Digest' header in response to the Want-Digest request in order to keep backward compatibility.
910 outCksum = prot->cksumHandler.getChecksumToRunWantDigest(m_want_digest);
911 } else {
912 // Want-Repr-Digest has been passed alone, deduce the checksum to run from that header
913 outCksum = prot->cksumHandler.getChecksumToRunWantReprDigest(m_want_repr_digest);
914 }
915 if(!outCksum) {
916 // No HTTP IANA checksums have been configured by the server admin, return a "METHOD_NOT_ALLOWED" error
917 prot->SendSimpleResp(405, NULL, NULL, (char *) "No HTTP-IANA compatible checksums have been configured.", 0, false);
918 return -1;
919 }
920 outResourceDigestOpaque += !opaque ? "?" : "&";
921 outResourceDigestOpaque += "cks.type=";
922 outResourceDigestOpaque += outCksum->getXRootDConfigDigestName().c_str();
923
924 return 0;
925}
926
928
929 kXR_int32 l;
930 if (startTime == std::chrono::steady_clock::time_point::min()) startTime = std::chrono::steady_clock::now();
931
932 // State variable for tracking the query parameter search
933 // - 0: Indicates we've not yet searched the URL for '?'
934 // - 1: Indicates we have a '?' and hence query parameters
935 // - 2: Indicates we do *not* have '?' present -- no query parameters
936 int query_param_status = 0;
937 if (!m_appended_asize) {
938 m_appended_asize = true;
939 if (request == rtPUT && length) {
940 if (query_param_status == 0) {
941 query_param_status = strchr(resourceplusopaque.c_str(), '?') ? 1 : 2;
942 }
943 resourceplusopaque.append((query_param_status == 1) ? '&' : '?');
944 query_param_status = 1;
945 auto length_str = std::to_string(length);
946 resourceplusopaque.append("oss.asize=");
947 resourceplusopaque.append(length_str.c_str());
948 if (!opaque) {
949 opaque = new XrdOucEnv();
950 }
951 opaque->Put("oss.asize", length_str.c_str());
952 }
953 }
954
956 if (!m_appended_hdr2cgistr && !hdr2cgistr.empty()) {
957 if (query_param_status == 0) {
958 query_param_status = strchr(resourceplusopaque.c_str(), '?') ? 1 : 2;
959 }
960 resourceplusopaque.append((query_param_status == 1) ? '&' : '?');
961
962 std::string hdr2cgistrEncoded = encode_opaque(hdr2cgistr);
963 resourceplusopaque.append(hdr2cgistrEncoded.c_str());
964 if (TRACING(TRACE_DEBUG)) {
965 // The obfuscation of "authz" will only be done if the server http.header2cgi config contains something that maps a header to this "authz" cgi.
966 // Unfortunately the obfuscation code will be called no matter what is configured in http.header2cgi.
967 std::string header2cgistrObf = obfuscateAuth(hdr2cgistr);
968
969 TRACEI(DEBUG, "Appended header fields to opaque info: '"
970 << header2cgistrObf.c_str() << "'");
971
972 }
973
975 }
976
977 // Verify if we have an external handler for this request
978 if (reqstate == 0) {
979 XrdHttpExtHandler *exthandler = prot->FindMatchingExtHandler(*this);
980 if (exthandler) {
981 XrdHttpExtReq xreq(this, prot);
982 int r = exthandler->ProcessReq(xreq);
983 reset();
984 if (!r) return 1; // All went fine, response sent
985 if (r < 0) return -1; // There was a hard error... close the connection
986
987 return 1; // There was an error and a response was sent
988 }
989 }
990
991 //
992 // Here we process the request locally
993 //
994
995 switch (request) {
997 return -1;
1000 generateWebdavErrMsg();
1001 prot->SendSimpleResp(400, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), false);
1002 reset();
1003 return -1;
1004 }
1005 case XrdHttpReq::rtHEAD:
1006 {
1007 if (reqstate == 0) {
1008 // Always start with Stat; in the case of a checksum request, we'll have a follow-up query
1009 if (prot->doStat((char *) resourceplusopaque.c_str())) {
1010 prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run request.", 0, false);
1011 return -1;
1012 }
1013 return 0;
1014 } else {
1015 // Note that doChksum requires that the memory stays alive until the callback is invoked.
1016 int prepareCksum = prepareChecksumQuery(m_req_cksum, m_resource_with_digest);
1017 if(prepareCksum < 0) {
1018 return -1;
1019 }
1020 if (prot->doChksum(m_resource_with_digest) < 0) {
1021 // In this case, the Want-Digest header was set and PostProcess gave the go-ahead to do a checksum.
1022 prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to create initial checksum request.", 0, false);
1023 return -1;
1024 }
1025 return 1;
1026 }
1027 }
1028 case XrdHttpReq::rtGET:
1029 {
1030 int retval = keepalive ? 1 : -1; // reset() clears keepalive
1031
1032 if (resource.beginswith("/static/")) {
1033
1034 // This is a request for a /static resource
1035 // If we have to use the embedded ones then we return the ones in memory as constants
1036
1037 // The sysadmin can always redirect the request to another host that
1038 // contains his static resources
1039
1040 // We also allow xrootd to preread from the local disk all the files
1041 // that have to be served as static resources.
1042
1043 if (prot->embeddedstatic) {
1044
1045 // Default case: the icon and the css of the HTML rendering of XrdHttp
1046 if (resource == "/static/css/xrdhttp.css") {
1047 prot->SendSimpleResp(200, NULL, NULL, (char *) static_css_xrdhttp_css, static_css_xrdhttp_css_len, keepalive);
1048 reset();
1049 return retval;
1050 }
1051 if (resource == "/static/icons/xrdhttp.ico") {
1052 prot->SendSimpleResp(200, NULL, NULL, (char *) favicon_ico, favicon_ico_len, keepalive);
1053 reset();
1054 return retval;
1055 }
1056
1057 }
1058
1059 // If we are here then none of the embedded resources match (or they are disabled)
1060 // We may have to redirect to a host that is supposed to serve the static resources
1061 if (prot->staticredir) {
1062
1063 XrdOucString s = "Location: ";
1064 s.append(prot->staticredir);
1065
1066 if (s.endswith('/'))
1067 s.erasefromend(1);
1068
1069 s.append(encode_str(std::string(resource.c_str())).c_str());
1070 appendOpaque(s, 0, 0, 0);
1071
1072 prot->SendSimpleResp(302, NULL, (char *) s.c_str(), 0, 0, false);
1073 return -1;
1074
1075
1076 } else {
1077
1078 // We lookup the requested path in a hash containing the preread files
1079 if (prot->staticpreload) {
1080 XrdHttpProtocol::StaticPreloadInfo *mydata = prot->staticpreload->Find(resource.c_str());
1081 if (mydata) {
1082 prot->SendSimpleResp(200, NULL, NULL, (char *) mydata->data, mydata->len, keepalive);
1083 reset();
1084 return retval;
1085 }
1086 }
1087
1088 }
1089
1090
1091 }
1092
1093 // The reqstate parameter basically moves us through a simple state machine.
1094 // To optimize things, we start off by opening the file; if it turns out to be a directory, then
1095 // we close the file handle and switch to doing a HTML-based rendering of the directory. This
1096 // avoids needing to always to do "stat" first to determine the next step (since the file-open also
1097 // does a "stat").
1098 // - 0: Perform an open on the resource
1099 // - 1: Perform a checksum request on the resource (only if requested in header; otherwise skipped)
1100 // - 2: Perform a close (for dirlist only)
1101 // - 3: Perform a dirlist.
1102 // - 4+: Reads from file; if at end, perform a close.
1103 switch (reqstate) {
1104 case 0: // Open the path for reading.
1105 {
1106 memset(&xrdreq, 0, sizeof (ClientRequest));
1107 xrdreq.open.requestid = htons(kXR_open);
1108 l = resourceplusopaque.length() + 1;
1109 xrdreq.open.dlen = htonl(l);
1110 xrdreq.open.mode = 0;
1112
1113 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1114 prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run request.", 0, false);
1115 return -1;
1116 }
1117
1118 // Prepare to chunk up the request
1119 writtenbytes = 0;
1120
1121 // We want to be invoked again after this request is finished
1122 return 0;
1123 }
1124 case 1: // Checksum request
1125 if (!(fileflags & kXR_isDir) && (!m_want_digest.empty() || !m_want_repr_digest.empty())) {
1126 // In this case, the Want-Digest or then Want-Repr-Digest header was set.
1127 int prepareCksum = prepareChecksumQuery(m_req_cksum, m_resource_with_digest);
1128 if(prepareCksum < 0) {
1129 return -1;
1130 }
1131 if (prot->doChksum(m_resource_with_digest) < 0) {
1132 prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to start internal checksum request to satisfy Want-Digest or Want-Repr-Digest header.", 0, false);
1133 return -1;
1134 }
1135 return 0;
1136 } else {
1137 TRACEI(DEBUG, "No checksum requested; skipping to request state 2");
1138 reqstate += 1;
1139 }
1140 // fallthrough
1141 case 2: // Close file handle for directory
1142 if ((fileflags & kXR_isDir) && fopened) {
1143 memset(&xrdreq, 0, sizeof (ClientRequest));
1145 memcpy(xrdreq.close.fhandle, fhandle, 4);
1146
1147 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1148 generateWebdavErrMsg();
1149 return sendFooterError("Could not run close request on the bridge");
1150 }
1151 return 0;
1152 } else {
1153 reqstate += 1;
1154 }
1155 // fallthrough
1156 case 3: // List directory
1157 if (fileflags & kXR_isDir) {
1158 if (prot->listdeny) {
1159 // Return 403 as the administrator forbid the directory listing
1160 prot->SendSimpleResp(403, NULL, NULL, (char *) "Listings are disabled.", 0, false);
1161 return -1;
1162 }
1163
1164 if (prot->listredir) {
1165 XrdOucString s = "Location: ";
1166 s.append(prot->listredir);
1167
1168 if (s.endswith('/'))
1169 s.erasefromend(1);
1170
1171 s.append(encode_str(std::string(resource.c_str())).c_str());
1172 appendOpaque(s, 0, 0, 0);
1173
1174 prot->SendSimpleResp(302, NULL, (char *) s.c_str(), 0, 0, false);
1175 return -1;
1176 }
1177
1178 std::string res;
1179 res = resourceplusopaque.c_str();
1180
1181 // --------- DIRLIST
1182 memset(&xrdreq, 0, sizeof (ClientRequest));
1185 l = res.length() + 1;
1186 xrdreq.dirlist.dlen = htonl(l);
1187
1188 if (!prot->Bridge->Run((char *) &xrdreq, (char *) res.c_str(), l)) {
1189 generateWebdavErrMsg();
1190 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), false);
1191 sendFooterError("Could not run listing request on the bridge");
1192 return -1;
1193 }
1194
1195 // We don't want to be invoked again after this request is finished
1196 return 1;
1197 }
1198 else {
1199 reqstate += 1;
1200 }
1201 // fallthrough
1202 case 4:
1203 {
1204 auto retval = ReturnGetHeaders();
1205 if (retval) {
1206 return retval;
1207 }
1208 }
1209 // fallthrough
1210 default: // Read() or Close(); reqstate is 4+
1211 {
1212 const XrdHttpIOList &readChunkList = readRangeHandler.NextReadList();
1213
1214 // Close() if we have finished, otherwise read the next chunk
1215
1216 // --------- CLOSE
1217 if ( closeAfterError || readChunkList.empty() )
1218 {
1219
1220 memset(&xrdreq, 0, sizeof (ClientRequest));
1222 memcpy(xrdreq.close.fhandle, fhandle, 4);
1223
1224 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1225 TRACEI(REQ, " Failed to run close request on the bridge.");
1226 // Note: we have already completed the request and sent the data to the client.
1227 // Hence, there's no need to send an error. However, since the bridge is potentially
1228 // in a bad state, we close the TCP socket to force the client to reconnect.
1229 return -1;
1230 }
1231
1232 // We have finished
1233 readClosing = true;
1234 return 1;
1235
1236 }
1237 // --------- READ or READV
1238
1239 if ( readChunkList.size() == 1 ) {
1240 // Use a read request for single range
1241
1242 long l;
1243 long long offs;
1244
1245 // --------- READ
1246 memset(&xrdreq, 0, sizeof (xrdreq));
1247 xrdreq.read.requestid = htons(kXR_read);
1248 memcpy(xrdreq.read.fhandle, fhandle, 4);
1249 xrdreq.read.dlen = 0;
1250
1251 offs = readChunkList[0].offset;
1252 l = readChunkList[0].size;
1253
1254 xrdreq.read.offset = htonll(offs);
1255 xrdreq.read.rlen = htonl(l);
1256
1257 // If we are using HTTPS or if the client requested trailers, or if the
1258 // read concerns a multirange reponse, disable sendfile
1259 // (in the latter two cases, the extra framing is only done in PostProcessHTTPReq)
1260 if (prot->ishttps || (m_transfer_encoding_chunked && m_trailer_headers) ||
1262 if (!prot->Bridge->setSF((kXR_char *) fhandle, false)) {
1263 TRACE(REQ, " XrdBridge::SetSF(false) failed.");
1264
1265 }
1266 }
1267
1268
1269
1270 if (l <= 0) {
1271 if (l < 0) {
1272 TRACE(ALL, " Data sizes mismatch.");
1273 return -1;
1274 }
1275 else {
1276 TRACE(ALL, " No more bytes to send.");
1277 reset();
1278 return 1;
1279 }
1280 }
1281
1282 if ((offs >= filesize) || (offs+l > filesize)) {
1283 httpStatusCode = 416;
1284 httpErrorBody = "Range Not Satisfiable";
1285 std::stringstream ss;
1286 ss << "Requested range " << l << "@" << offs << " is past the end of file (" << filesize << ")";
1287 return sendFooterError(ss.str());
1288 }
1289
1290 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1291 generateWebdavErrMsg();
1292 return sendFooterError("Could not run read request on the bridge");
1293 }
1294 } else {
1295 // --------- READV
1296
1297 length = ReqReadV(readChunkList);
1298
1299 if (!prot->Bridge->Run((char *) &xrdreq, (char *) &ralist[0], length)) {
1300 generateWebdavErrMsg();
1301 return sendFooterError("Could not run ReadV request on the bridge");
1302 }
1303
1304 }
1305
1306 // We want to be invoked again after this request is finished
1307 return 0;
1308 } // case 3+
1309
1310 } // switch (reqstate)
1311
1312
1313 } // case XrdHttpReq::rtGET
1314
1315 case XrdHttpReq::rtPUT:
1316 {
1317 //if (prot->ishttps) {
1318 //prot->SendSimpleResp(501, NULL, NULL, (char *) "HTTPS not supported yet for direct writing. Sorry.", 0);
1319 //return -1;
1320 //}
1321
1322 if (!fopened) {
1323
1324 // --------- OPEN for write!
1325 memset(&xrdreq, 0, sizeof (ClientRequest));
1326 xrdreq.open.requestid = htons(kXR_open);
1327 l = resourceplusopaque.length() + 1;
1328 xrdreq.open.dlen = htonl(l);
1329 xrdreq.open.mode = htons(kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);
1330 // 'Overwrite: F' (e.g. forwarded by a TPC push) forbids replacing
1331 // an existing file; the open then fails with kXR_ItExists (409).
1332 auto overwrite = XrdOucTUtils::caseInsensitiveFind(allheaders, "overwrite");
1333 bool noOverwrite = overwrite != allheaders.end() && overwrite->second == "F";
1334 if (! XrdHttpProtocol::usingEC && ! noOverwrite)
1336 else
1338
1339 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1340 prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run request.", 0, keepalive);
1341 return -1;
1342 }
1343
1344
1345 // We want to be invoked again after this request is finished
1346 // Only if there is data to fetch from the socket or there will
1347 // never be more data
1348 if (prot->BuffUsed() > 0 || (length == 0 && !sendcontinue))
1349 return 0;
1350
1351 return 1;
1352
1353 } else {
1354
1355 if (m_transfer_encoding_chunked) {
1356 if (m_current_chunk_size == m_current_chunk_offset) {
1357 // Chunk has been consumed; we now must process the CRLF.
1358 // Note that we don't support trailer headers.
1359 if (prot->BuffUsed() < 2) return 1;
1360 if (prot->myBuffStart[0] != '\r' || prot->myBuffStart[1] != '\n') {
1361 prot->SendSimpleResp(400, NULL, NULL, (char *) "Invalid trailing chunk encoding.", 0, keepalive);
1362 return -1;
1363 }
1364 prot->BuffConsume(2);
1365 if (m_current_chunk_size == 0) {
1366 // All data has been sent. Turn off chunk processing and
1367 // set the bytes written and length appropriately; on next callback,
1368 // we will hit the close() block below.
1369 m_transfer_encoding_chunked = false;
1371 return ProcessHTTPReq();
1372 }
1373 m_current_chunk_size = -1;
1374 m_current_chunk_offset = 0;
1375 // If there is more data, we try to process the next chunk; otherwise, return
1376 if (!prot->BuffUsed()) return 1;
1377 }
1378 if (-1 == m_current_chunk_size) {
1379
1380 // Parse out the next chunk size.
1381 long long idx = 0;
1382 bool found_newline = false;
1383 // Set a maximum size of chunk we will allow
1384 // Nginx sets this to "NGX_MAX_OFF_T_VALUE", which is 9223372036854775807 (a some crazy number)
1385 // We set it to 1TB, which is 1099511627776
1386 // This is to prevent a malicious client from sending a very large chunk size
1387 // or a malformed chunk request.
1388 // 1TB in base-16 is 0x40000000000, so only allow 11 characters, plus the CRLF
1389 long long max_chunk_size_chars = std::min(static_cast<long long>(prot->BuffUsed()), static_cast<long long>(13));
1390 for (; idx < max_chunk_size_chars; idx++) {
1391 if (prot->myBuffStart[idx] == '\n') {
1392 found_newline = true;
1393 break;
1394 }
1395 }
1396 // If we found a new line, but it is the first character in the buffer (no chunk length)
1397 // or if the previous character is not a CR.
1398 if (found_newline && ((idx == 0) || prot->myBuffStart[idx-1] != '\r')) {
1399 prot->SendSimpleResp(400, NULL, NULL, (char *)"Invalid chunked encoding", 0, false);
1400 TRACE(REQ, "XrdHTTP PUT: Sending invalid chunk encoding. Start of chunk should have had a length, followed by a CRLF.");
1401 return -1;
1402 }
1403 if (found_newline) {
1404 char *endptr = NULL;
1405 std::string line_contents(prot->myBuffStart, idx);
1406 long long chunk_contents = strtoll(line_contents.c_str(), &endptr, 16);
1407 // Chunk sizes can be followed by trailer information or CRLF
1408 if (*endptr != ';' && *endptr != '\r') {
1409 prot->SendSimpleResp(400, NULL, NULL, (char *)"Invalid chunked encoding", 0, false);
1410 TRACE(REQ, "XrdHTTP PUT: Sending invalid chunk encoding. Chunk size was not followed by a ';' or CR." << __LINE__);
1411 return -1;
1412 }
1413 m_current_chunk_size = chunk_contents;
1414 m_current_chunk_offset = 0;
1415 prot->BuffConsume(idx + 1);
1416 TRACE(REQ, "XrdHTTP PUT: next chunk from client will be " << m_current_chunk_size << " bytes");
1417 } else {
1418 // Need more data!
1419 return 1;
1420 }
1421 }
1422
1423 if (m_current_chunk_size == 0) {
1424 // All data has been sent. Invoke this routine again immediately to process CRLF
1425 return ProcessHTTPReq();
1426 } else {
1427 // At this point, we have a chunk size defined and should consume payload data
1428 memset(&xrdreq, 0, sizeof (xrdreq));
1430 memcpy(xrdreq.write.fhandle, fhandle, 4);
1431
1432 long long chunk_bytes_remaining = m_current_chunk_size - m_current_chunk_offset;
1433 long long bytes_to_write = std::min(static_cast<long long>(prot->BuffUsed()),
1434 chunk_bytes_remaining);
1435
1436 xrdreq.write.offset = htonll(writtenbytes);
1437 xrdreq.write.dlen = htonl(bytes_to_write);
1438
1439 TRACEI(REQ, "XrdHTTP PUT: Writing chunk of size " << bytes_to_write << " starting with '" << *(prot->myBuffStart) << "'" << " with " << chunk_bytes_remaining << " bytes remaining in the chunk");
1440 if (!prot->Bridge->Run((char *) &xrdreq, prot->myBuffStart, bytes_to_write)) {
1441 generateWebdavErrMsg();
1442 return sendFooterError("Could not run write request on the bridge");
1443 }
1444 // If there are more bytes in the buffer, then immediately call us after the
1445 // write is finished; otherwise, wait for data.
1446 return (prot->BuffUsed() > chunk_bytes_remaining) ? 0 : 1;
1447 }
1448 } else if (writtenbytes < length) {
1449
1450
1451 // --------- WRITE
1452 memset(&xrdreq, 0, sizeof (xrdreq));
1454 memcpy(xrdreq.write.fhandle, fhandle, 4);
1455
1456 long long bytes_to_read = std::min(static_cast<long long>(prot->BuffUsed()),
1458
1459 xrdreq.write.offset = htonll(writtenbytes);
1460 xrdreq.write.dlen = htonl(bytes_to_read);
1461
1462 TRACEI(REQ, "Writing " << bytes_to_read);
1463 if (!prot->Bridge->Run((char *) &xrdreq, prot->myBuffStart, bytes_to_read)) {
1464 generateWebdavErrMsg();
1465 return sendFooterError("Could not run write request on the bridge");
1466 }
1467
1468 if (writtenbytes + prot->BuffUsed() >= length)
1469 // Trigger an immediate recall after this request has finished
1470 return 0;
1471 else
1472 // We want to be invoked again after this request is finished
1473 // only if there is pending data
1474 return 1;
1475
1476
1477
1478 } else {
1479
1480 // --------- CLOSE
1481 memset(&xrdreq, 0, sizeof (ClientRequest));
1483 memcpy(xrdreq.close.fhandle, fhandle, 4);
1484
1485
1486 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1487 generateWebdavErrMsg();
1488 return sendFooterError("Could not run close request on the bridge");
1489 }
1490
1491 // We have finished
1492 return 1;
1493
1494 }
1495
1496 }
1497
1498 break;
1499
1500 }
1502 {
1503 prot->SendSimpleResp(200, NULL, (char *) "DAV: 1\r\nDAV: <http://apache.org/dav/propset/fs/1>\r\nAllow: HEAD,GET,PUT,PROPFIND,DELETE,OPTIONS", NULL, 0, keepalive);
1504 bool ret_keepalive = keepalive; // reset() clears keepalive
1505 reset();
1506 return ret_keepalive ? 1 : -1;
1507 }
1509 {
1510
1511
1512 switch (reqstate) {
1513
1514 case 0: // Stat()
1515 {
1516
1517
1518 // --------- STAT is always the first step
1519 memset(&xrdreq, 0, sizeof (ClientRequest));
1520 xrdreq.stat.requestid = htons(kXR_stat);
1521 std::string s = resourceplusopaque.c_str();
1522
1523
1524 l = resourceplusopaque.length() + 1;
1525 xrdreq.stat.dlen = htonl(l);
1526
1527 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1528 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1529 return -1;
1530 }
1531
1532 // We need to be invoked again to complete the request
1533 return 0;
1534 }
1535 default:
1536
1537 if (fileflags & kXR_isDir) {
1538 // --------- RMDIR
1539 memset(&xrdreq, 0, sizeof (ClientRequest));
1541
1542 std::string s = resourceplusopaque.c_str();
1543
1544 l = s.length() + 1;
1545 xrdreq.rmdir.dlen = htonl(l);
1546
1547 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1548 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run rmdir request.", 0, false);
1549 return -1;
1550 }
1551 } else {
1552 // --------- DELETE
1553 memset(&xrdreq, 0, sizeof (ClientRequest));
1554 xrdreq.rm.requestid = htons(kXR_rm);
1555
1556 std::string s = resourceplusopaque.c_str();
1557
1558 l = s.length() + 1;
1559 xrdreq.rm.dlen = htonl(l);
1560
1561 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1562 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run rm request.", 0, false);
1563 return -1;
1564 }
1565 }
1566
1567
1568 // We don't want to be invoked again after this request is finished
1569 return 1;
1570
1571 }
1572
1573
1574
1575 }
1577 {
1578 prot->SendSimpleResp(501, NULL, NULL, (char *) "Request not supported yet.", 0, false);
1579
1580 return -1;
1581 }
1583 {
1584
1585
1586
1587 switch (reqstate) {
1588
1589 case 0: // Stat() and add the current item to the list of the things to send
1590 {
1591
1592 if (length > 0) {
1593 TRACE(REQ, "Reading request body " << length << " bytes.");
1594 char *p = 0;
1595 // We have to specifically read all the request body
1596
1597 if (prot->BuffgetData(length, &p, true) < length) {
1598 prot->SendSimpleResp(501, NULL, NULL, (char *) "Error in getting the PROPFIND request body.", 0, false);
1599 return -1;
1600 }
1601
1602 if ((depth > 1) || (depth < 0)) {
1603 prot->SendSimpleResp(501, NULL, NULL, (char *) "Invalid depth value.", 0, false);
1604 return -1;
1605 }
1606
1607
1608 parseBody(p, length);
1609 }
1610
1611
1612 // --------- STAT is always the first step
1613 memset(&xrdreq, 0, sizeof (ClientRequest));
1614 xrdreq.stat.requestid = htons(kXR_stat);
1615 std::string s = resourceplusopaque.c_str();
1616
1617
1618 l = resourceplusopaque.length() + 1;
1619 xrdreq.stat.dlen = htonl(l);
1620
1621 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1622 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1623 return -1;
1624 }
1625
1626
1627 if (depth == 0) {
1628 // We don't need to be invoked again
1629 return 1;
1630 } else
1631 // We need to be invoked again to complete the request
1632 return 0;
1633
1634
1635
1636 break;
1637 }
1638
1639 default: // Dirlist()
1640 {
1641
1642 // --------- DIRLIST
1643 memset(&xrdreq, 0, sizeof (ClientRequest));
1645
1646 std::string s = resourceplusopaque.c_str();
1648 //s += "?xrd.dirstat=1";
1649
1650 l = s.length() + 1;
1651 xrdreq.dirlist.dlen = htonl(l);
1652
1653 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1654 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1655 return -1;
1656 }
1657
1658 // We don't want to be invoked again after this request is finished
1659 return 1;
1660 }
1661 }
1662
1663
1664 break;
1665 }
1667 {
1668
1669 // --------- MKDIR
1670 memset(&xrdreq, 0, sizeof (ClientRequest));
1672
1673 std::string s = resourceplusopaque.c_str();
1675
1676 l = s.length() + 1;
1677 xrdreq.mkdir.dlen = htonl(l);
1678
1679 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1680 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1681 return -1;
1682 }
1683
1684 // We don't want to be invoked again after this request is finished
1685 return 1;
1686 }
1687 case XrdHttpReq::rtMOVE:
1688 {
1689 // Skip the protocol part of destination URL
1690 size_t skip = destination.find("://");
1691 skip = (skip == std::string::npos) ? 0 : skip + 3;
1692
1693 // If we have a manager role, enforce source and destination are on the same host
1694 if (prot->myRole == kXR_isManager && destination.compare(skip, host.size(), host) != 0) {
1695 prot->SendSimpleResp(501, NULL, NULL, (char *) "Only in-place renaming is supported for MOVE.", 0, false);
1696 return -1;
1697 }
1698
1699 // If needed, append opaque info from source onto destination
1700 int pos = resourceplusopaque.find("?");
1701 if (pos != STR_NPOS) {
1702 destination.append((destination.find("?") == std::string::npos) ? "?" : "&");
1703 destination.append(resourceplusopaque.c_str() + pos + 1);
1704 }
1705
1706 size_t path_pos = destination.find('/', skip + 1);
1707
1708 if (path_pos == std::string::npos) {
1709 prot->SendSimpleResp(400, NULL, NULL, (char *) "Cannot determine destination path", 0, false);
1710 return -1;
1711 }
1712
1713 // Construct args to kXR_mv request (i.e. <src> + " " + <dst>)
1714 std::string mv_args = std::string(resourceplusopaque.c_str()) + " " + destination.substr(path_pos);
1715
1716 l = mv_args.length() + 1;
1717
1718 // Prepare and run kXR_mv request
1719 memset(&xrdreq, 0, sizeof (ClientRequest));
1720 xrdreq.mv.requestid = htons(kXR_mv);
1722 xrdreq.mv.dlen = htonl(l);
1723
1724 if (!prot->Bridge->Run((char *) &xrdreq, (char *) mv_args.c_str(), l)) {
1725 prot->SendSimpleResp(500, NULL, NULL, (char *) "Could not run request.", 0, false);
1726 return -1;
1727 }
1728
1729 // We don't want to be invoked again after this request is finished
1730 return 1;
1731 }
1732 default:
1733 {
1734 prot->SendSimpleResp(501, NULL, NULL, (char *) "Request not supported.", 0, false);
1735 return -1;
1736 }
1737
1738 }
1739
1740 return 1;
1741}
1742
1743
1744int
1745XrdHttpReq::PostProcessChecksum(std::string &digest_header) {
1746 if (iovN > 0) {
1747 if (xrdresp == kXR_error) {
1748 prot->SendSimpleResp(httpStatusCode, NULL, NULL, "Failed to determine checksum", 0, false);
1749 return -1;
1750 }
1751
1752 TRACEI(REQ, "Checksum for HEAD " << resource.c_str() << " "
1753 << reinterpret_cast<char *>(iovP[0].iov_base) << "="
1754 << reinterpret_cast<char *>(iovP[iovN-1].iov_base));
1755
1756 std::string cksumType {reinterpret_cast<char *>(iovP[0].iov_base),iovP[0].iov_len};
1757 // Remove '\0' from the actual size of the cksumValue which is at the end of iovP[iovN-1].iov_base
1758 size_t cksumValueLen = iovP[iovN-1].iov_len - 1;
1759 std::string cksumValue {reinterpret_cast<char *>(iovP[iovN-1].iov_base), cksumValueLen};
1760 std::string digest_value = cksumValue;
1761
1762 // We convert the byte representation of the checksum to base64 if the checksum needs to be base64 encoded (md5 for example)
1763 // or if the Want-Repr-Digest header was used
1764 bool convert_to_base64 = m_req_cksum->needsBase64Padding() || !m_want_repr_digest.empty();
1765 if (convert_to_base64) {
1766 std::vector<uint8_t> digest_binary_value;
1767 if (!Fromhexdigest(cksumValue,digest_binary_value)) {
1768 prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to convert checksum hexdigest to base64.", 0, false);
1769 return -1;
1770 }
1771 Tobase64(digest_binary_value,digest_value);
1772 }
1773
1774 if(m_want_repr_digest.empty()) {
1775 digest_header = "Digest: ";
1776 digest_header += m_req_cksum->getHttpName();
1777 digest_header += "=";
1778 digest_header += digest_value;
1779 } else {
1780 digest_header = "Repr-Digest: ";
1781 digest_header += m_req_cksum->getHttpName();
1782 digest_header += "=:";
1783 digest_header += digest_value;
1784 digest_header += ":";
1785 }
1786
1787 return 0;
1788 } else {
1789 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), false);
1790 return -1;
1791 }
1792}
1793
1794int
1795XrdHttpReq::PostProcessListing(bool final_) {
1796
1797 if (xrdresp == kXR_error) {
1798 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
1799 httpErrorBody.c_str(), httpErrorBody.length(), false);
1800 return -1;
1801 }
1802
1803 if (stringresp.empty()) {
1804 // Start building the HTML response
1805 stringresp = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
1806 "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
1807 "<head>\n"
1808 "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>\n"
1809 "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/css/xrdhttp.css\"/>\n"
1810 "<link rel=\"icon\" type=\"image/png\" href=\"/static/icons/xrdhttp.ico\"/>\n";
1811
1812 stringresp += "<title>";
1814 stringresp += "</title>\n";
1815
1816 stringresp += "</head>\n"
1817 "<body>\n";
1818
1819 char *estr = escapeXML(resource.c_str());
1820
1821 stringresp += "<h1>Listing of: ";
1822 stringresp += estr;
1823 stringresp += "</h1>\n";
1824
1825 free(estr);
1826
1827 stringresp += "<div id=\"header\">";
1828
1829 stringresp += "<table id=\"ft\">\n"
1830 "<thead><tr>\n"
1831 "<th class=\"mode\">Mode</th>"
1832 "<th class=\"flags\">Flags</th>"
1833 "<th class=\"size\">Size</th>"
1834 "<th class=\"datetime\">Modified</th>"
1835 "<th class=\"name\">Name</th>"
1836 "</tr></thead>\n";
1837 }
1838
1839 // Now parse the answer building the entries vector
1840 if (iovN > 0) {
1841 char *startp = (char *) iovP[0].iov_base, *endp = 0;
1842 char entry[1024];
1843 DirListInfo e;
1844 while ( (size_t)(startp - (char *) iovP[0].iov_base) < (size_t)( iovP[0].iov_len - 1) ) {
1845 // Find the filename, it comes before the \n
1846 if ((endp = (char *) strchr((const char*) startp, '\n'))) {
1847 strncpy(entry, (char *) startp, endp - startp);
1848 entry[endp - startp] = 0;
1849 e.path = entry;
1850
1851 endp++;
1852
1853 // Now parse the stat info
1854 TRACEI(REQ, "Dirlist " << resource.c_str() << " entry=" << entry
1855 << " stat=" << endp);
1856
1857 long dummyl;
1858 sscanf(endp, "%ld %lld %ld %ld",
1859 &dummyl,
1860 &e.size,
1861 &e.flags,
1862 &e.modtime);
1863 } else
1864 strcpy(entry, (char *) startp);
1865
1866 if (e.path.length() && (e.path != ".") && (e.path != "..")) {
1867 // The entry is filled. <td class="ft-file"><a href="file1.txt">file1.txt</a></td>
1868 std::string p = "<tr>"
1869 "<td class=\"mode\">";
1870
1871 if (e.flags & kXR_isDir) p += "d";
1872 else p += "-";
1873
1874 if (e.flags & kXR_other) p += "o";
1875 else p += "-";
1876
1877 if (e.flags & kXR_offline) p += "O";
1878 else p += "-";
1879
1880 if (e.flags & kXR_readable) p += "r";
1881 else p += "-";
1882
1883 if (e.flags & kXR_writable) p += "w";
1884 else p += "-";
1885
1886 if (e.flags & kXR_xset) p += "x";
1887 else p += "-";
1888
1889 p += "</td>";
1890 p += "<td class=\"mode\">" + itos(e.flags) + "</td>"
1891 "<td class=\"size\">" + itos(e.size) + "</td>"
1892 "<td class=\"datetime\">" + ISOdatetime(e.modtime) + "</td>"
1893 "<td class=\"name\">"
1894 "<a href=\"";
1895
1896 if (resource != "/") {
1897
1898 char *estr = escapeXML(resource.c_str());
1899
1900 p += estr;
1901 if (!p.empty() && p[p.size() - 1] != '/')
1902 p += "/";
1903
1904 free(estr);
1905 }
1906 std::unique_ptr<char, decltype(&free)> estr(escapeXML(e.path.c_str()), &free);
1907 p += estr.get();
1908 if (e.flags & kXR_isDir) p += "/";
1909 p += "\">";
1910 p += estr.get();
1911 if (e.flags & kXR_isDir) p += "/";
1912 p += "</a></td></tr>";
1913
1914 stringresp += p;
1915 }
1916
1917 if (endp) {
1918 char *pp = (char *)strchr((const char *)endp, '\n');
1919 if (pp) startp = pp+1;
1920 else break;
1921 } else break;
1922
1923 }
1924 }
1925
1926 // If this was the last bunch of entries, send the buffer and empty it immediately
1927 if (final_) {
1928 stringresp += "</table></div><br><br><hr size=1>"
1929 "<p><span id=\"requestby\">Request by ";
1930
1931 if (prot->SecEntity.name)
1932 stringresp += prot->SecEntity.name;
1933 else
1934 stringresp += prot->Link->ID;
1935
1936 if (prot->SecEntity.vorg ||
1937 prot->SecEntity.name ||
1938 prot->SecEntity.moninfo ||
1939 prot->SecEntity.role)
1940 stringresp += " (";
1941
1942 if (prot->SecEntity.vorg) {
1943 stringresp += " VO: ";
1944 stringresp += prot->SecEntity.vorg;
1945 }
1946
1947 if (prot->SecEntity.moninfo) {
1948 stringresp += " DN: ";
1949 stringresp += prot->SecEntity.moninfo;
1950 } else
1951 if (prot->SecEntity.name) {
1952 stringresp += " DN: ";
1953 stringresp += prot->SecEntity.name;
1954 }
1955
1956 if (prot->SecEntity.role) {
1957 stringresp += " Role: ";
1958 stringresp += prot->SecEntity.role;
1959 if (prot->SecEntity.endorsements) {
1960 stringresp += " (";
1961 stringresp += prot->SecEntity.endorsements;
1962 stringresp += ") ";
1963 }
1964 }
1965
1966 if (prot->SecEntity.vorg ||
1967 prot->SecEntity.moninfo ||
1968 prot->SecEntity.role)
1969 stringresp += " )";
1970
1971 if (prot->SecEntity.host) {
1972 stringresp += " ( ";
1973 stringresp += prot->SecEntity.host;
1974 stringresp += " )";
1975 }
1976
1977 stringresp += "</span></p>\n";
1978 stringresp += "<p>Powered by XrdHTTP ";
1979 stringresp += XrdVSTRING;
1980 stringresp += " (CERN IT-SDC)</p>\n";
1981
1982 prot->SendSimpleResp(200, NULL, NULL, (char *) stringresp.c_str(), 0, keepalive);
1983 stringresp.clear();
1984 return keepalive ? 1 : -1;
1985 }
1986
1987 return 0;
1988}
1989
1990int
1991XrdHttpReq::ReturnGetHeaders() {
1992 std::string responseHeader;
1993 if (!m_digest_header.empty()) {
1994 responseHeader = m_digest_header;
1995 }
1996 if (fileflags & kXR_cachersp) {
1997 if (!responseHeader.empty()) {
1998 responseHeader += "\r\n";
1999 }
2000 addAgeHeader(responseHeader);
2001 }
2002
2004 if (uranges.empty() && readRangeHandler.getError()) {
2005 prot->SendSimpleResp(readRangeHandler.getError().httpRetCode, NULL, NULL, readRangeHandler.getError().errMsg.c_str(),0,false);
2006 return -1;
2007 }
2008
2010 // Full file.
2011 TRACEI(REQ, "Sending full file: " << filesize);
2012 if (m_transfer_encoding_chunked && m_trailer_headers) {
2013 setTransferStatusHeader(responseHeader);
2014 prot->StartChunkedResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), -1, keepalive);
2015 } else {
2016 prot->SendSimpleResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), NULL, filesize, keepalive);
2017 }
2018 return 0;
2019 }
2020
2022 // Possibly with zero sized file but should have been included
2023 // in the FullFile case above
2024 if (uranges.size() != 1)
2025 return -1;
2026
2027 // Only one range to return to the user
2028 char buf[64];
2029 const off_t cnt = uranges[0].end - uranges[0].start + 1;
2030
2031 std::string header = "Content-Range: bytes ";
2032 sprintf(buf, "%lld-%lld/%lld", (long long int)uranges[0].start, (long long int)uranges[0].end, filesize);
2033 header += buf;
2034 if (!responseHeader.empty()) {
2035 header += "\r\n";
2036 header += responseHeader.c_str();
2037 }
2038
2039 if (m_transfer_encoding_chunked && m_trailer_headers) {
2041 prot->StartChunkedResp(206, NULL, header.empty() ? nullptr : header.c_str(), -1, keepalive);
2042 } else {
2043 prot->SendSimpleResp(206, NULL, header.empty() ? nullptr : header.c_str(), NULL, cnt, keepalive);
2044 }
2045 return 0;
2046 }
2047
2048 // Multiple reads to perform, compose and send the header
2049 off_t cnt = 0;
2050 for (auto &ur : uranges) {
2051 cnt += ur.end - ur.start + 1;
2052
2053 cnt += buildPartialHdr(ur.start,
2054 ur.end,
2055 filesize,
2056 (char *) "123456").size();
2057
2058 }
2059 cnt += buildPartialHdrEnd((char *) "123456").size();
2060 std::string header = "Content-Type: multipart/byteranges; boundary=123456";
2061 if (!m_digest_header.empty()) {
2062 header += "\n";
2063 header += m_digest_header;
2064 }
2065 if (fileflags & kXR_cachersp) {
2066 if (!header.empty()) {
2067 header += "\r\n";
2068 }
2069 addAgeHeader(header);
2070 }
2071
2072 if (m_transfer_encoding_chunked && m_trailer_headers) {
2074 prot->StartChunkedResp(206, NULL, header.c_str(), -1, keepalive);
2075 } else {
2076 prot->SendSimpleResp(206, NULL, header.c_str(), NULL, cnt, keepalive);
2077 }
2078 return 0;
2079}
2080
2081void XrdHttpReq::setTransferStatusHeader(std::string &header) {
2082 if (m_status_trailer) {
2083 if (header.empty()) {
2084 header += "Trailer: X-Transfer-Status";
2085 } else {
2086 header += "\r\nTrailer: X-Transfer-Status";
2087 }
2088 }
2089}
2090
2091// This is invoked by the callbacks, after something has happened in the bridge
2092
2093int XrdHttpReq::PostProcessHTTPReq(bool final_) {
2094
2095 TRACEI(REQ, "PostProcessHTTPReq req: " << request << " reqstate: " << reqstate << " final_:" << final_);
2096 generateWebdavErrMsg();
2097
2098 if(xrdreq.set.requestid == htons(kXR_set)) {
2099 // We have set the user agent, if it fails we return a 500 error, otherwise the callback is successful --> we continue
2100 if(xrdresp != kXR_ok) {
2101 prot->SendSimpleResp(500, nullptr, nullptr, "Could not set user agent.", 0, false);
2102 return -1;
2103 }
2104 return 0;
2105 }
2106
2107 switch (request) {
2109 {
2110 prot->SendSimpleResp(400, NULL, NULL, (char *) "Request malformed 1", 0, false);
2111 return -1;
2112 }
2114 {
2115 prot->SendSimpleResp(400, NULL, NULL, (char *) "Request malformed 2", 0, false);
2116 return -1;
2117 }
2118 case XrdHttpReq::rtHEAD:
2119 {
2120 if (xrdresp != kXR_ok) {
2121 // NOTE that HEAD MUST NOT return a body, even in the case of failure.
2122 prot->SendSimpleResp(httpStatusCode, NULL, NULL, NULL, 0, false);
2123 return -1;
2124 } else if (reqstate == 0) {
2125 if (iovN > 0) {
2126 std::string response_headers;
2127
2128 // Now parse the stat info
2129 TRACEI(REQ, "Stat for HEAD " << resource.c_str()
2130 << " stat=" << (char *) iovP[0].iov_base);
2131
2132 sscanf((const char *) iovP[0].iov_base, "%lld %lld %ld %ld",
2133 &etagval,
2134 &filesize,
2135 &fileflags,
2136 &filemodtime);
2137
2138 if (m_want_digest.size() || m_want_repr_digest.size()) {
2139 return 0;
2140 } else {
2141 if (fileflags & kXR_cachersp) {
2142 addAgeHeader(response_headers);
2143 response_headers += "\r\n";
2144 }
2145
2146 addETagHeader(response_headers);
2147 response_headers += "\r\n";
2148
2149 response_headers += "Accept-Ranges: bytes";
2150 prot->SendSimpleResp(200, NULL, response_headers.c_str(), NULL, filesize, keepalive);
2151 return keepalive ? 1 : -1;
2152 }
2153 }
2154
2155 prot->SendSimpleResp(httpStatusCode, NULL, NULL, NULL, 0, keepalive);
2156 bool ret_keepalive = keepalive; // reset() clears keepalive
2157 reset();
2158 return ret_keepalive ? 1 : -1;
2159 } else { // We requested a checksum and now have its response.
2160 if (iovN > 0) {
2161 std::string response_headers;
2162 int response = PostProcessChecksum(response_headers);
2163 if (-1 == response) {
2164 return -1;
2165 }
2166 if (!response_headers.empty()) {response_headers += "\r\n";}
2167 if (fileflags & kXR_cachersp) {
2168 addAgeHeader(response_headers);
2169 response_headers += "\r\n";
2170 }
2171 response_headers += "Accept-Ranges: bytes";
2172 prot->SendSimpleResp(200, NULL, response_headers.c_str(), NULL, filesize, keepalive);
2173 return keepalive ? 1 : -1;
2174 } else {
2175 prot->SendSimpleResp(500, NULL, NULL, "Underlying filesystem failed to calculate checksum.", 0, false);
2176 return -1;
2177 }
2178 }
2179 }
2180 case XrdHttpReq::rtGET:
2181 {
2182 // To duplicate the state diagram from the rtGET request state
2183 // - 0: Perform an open request
2184 // - 1: Perform a checksum request on the resource (only if requested in header; otherwise skipped)
2185 // - 2: Perform a close (for directory listings only)
2186 // - 3: Perform a dirlist
2187 // - 4+: Reads from file; if at end, perform a close.
2188 switch (reqstate) {
2189 case 0: // open
2190 {
2191 if (xrdresp == kXR_ok) {
2192 fopened = true;
2193 getfhandle();
2194
2195 // Always try to parse response. In the case of a caching proxy, the open
2196 // will have created the file in cache
2197 if (iovP[1].iov_len > 1) {
2198 TRACEI(REQ, "Stat for GET " << resource.c_str()
2199 << " stat=" << (char *) iovP[1].iov_base);
2200
2201 long dummyl;
2202 sscanf((const char *) iovP[1].iov_base, "%ld %lld %ld %ld",
2203 &dummyl,
2204 &filesize,
2205 &fileflags,
2206 &filemodtime);
2207
2208 // If this is a directory, bail out early; we will close the file handle
2209 // and then issue a directory listing.
2210 if (fileflags & kXR_isDir) {
2211 return 0;
2212 }
2213
2215
2216 // As above: if the client specified a response size, we use that.
2217 // Otherwise, utilize the filesize
2218 if (!length) {
2219 length = filesize;
2220 }
2221 }
2222 else {
2223 TRACEI(ALL, "GET returned no STAT information. Internal error?");
2224 prot->SendSimpleResp(500, NULL, NULL, "Storage system did not return stat info.", 0, false);
2225 return -1;
2226 }
2227 return 0;
2228 } else if (xrderrcode == kXR_isDirectory) { // This is a directory; trigger directory-handling topic.
2230 return 0;
2231 } else { // xrdresp indicates an error occurred
2232
2233 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2234 httpErrorBody.c_str(), httpErrorBody.length(), false);
2235 return -1;
2236 }
2237 // Case should not be reachable
2238 return -1;
2239 } // end open
2240 case 1: // checksum was requested and now we have its response.
2241 {
2242 return PostProcessChecksum(m_digest_header);
2243 }
2244 case 2: // close file handle in case of the directory
2245 {
2246 if (xrdresp != kXR_ok) {
2247 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2248 httpErrorBody.c_str(), httpErrorBody.length(), false);
2249 return -1;
2250 }
2251 return 0;
2252 }
2253 case 3: // handle the directory listing response
2254 {
2255 return PostProcessListing(final_);
2256 }
2257 default: //read or readv, followed by a close.
2258 {
2259 // If we are postprocessing a close, potentially send out informational trailers
2260 if ((ntohs(xrdreq.header.requestid) == kXR_close) || readClosing)
2261 {
2262 // If we already sent out an error, then we cannot send any further
2263 // messages
2264 if (closeAfterError) {
2265 TRACEI(REQ, "Close was completed after an error: " << xrdresp);
2266 return xrdresp != kXR_ok ? -1 : 1;
2267 }
2268
2269 const XrdHttpReadRangeHandler::Error &rrerror = readRangeHandler.getError();
2270 if (rrerror) {
2271 httpStatusCode = rrerror.httpRetCode;
2272 httpErrorBody = rrerror.errMsg;
2273 }
2274
2275 if (m_transfer_encoding_chunked && m_trailer_headers) {
2276 std::string trailer = "X-Transfer-Status: " + std::to_string(httpStatusCode) + ": " + httpErrorBody + "\r\n";
2277
2278 if (prot->ChunkResp(trailer.c_str(), -1)) return -1;
2279 }
2280
2281 if (rrerror) return -1;
2282 return keepalive ? 1 : -1;
2283 }
2284
2285 // On error, we can only send out a message if trailers are enabled and the
2286 // status response in trailer behavior is requested.
2287 if (xrdresp == kXR_error) {
2288 auto rc = sendFooterError("");
2289 if (rc == 1) {
2290 closeAfterError = true;
2291 return 0;
2292 }
2293 return -1;
2294 }
2295
2296
2297 TRACEI(REQ, "Got data vectors to send:" << iovN);
2298
2299 XrdHttpIOList received;
2300 getReadResponse(received);
2301
2302 int rc;
2304 rc = sendReadResponseSingleRange(received);
2305 } else {
2306 rc = sendReadResponsesMultiRanges(received);
2307 }
2308 if (rc) {
2309 // make sure readRangeHandler will trigger close
2310 // of file after next NextReadList().
2312 }
2313
2314 return 0;
2315 } // end read or readv
2316
2317 } // switch reqstate
2318 break;
2319 } // case GET
2320
2321 case XrdHttpReq::rtPUT:
2322 {
2323 if (!fopened) {
2324 if (xrdresp != kXR_ok) {
2325 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2326 return -1;
2327 }
2328
2329 getfhandle();
2330 fopened = true;
2331
2332 // We try to completely fill up our buffer before flushing
2333 prot->ResumeBytes = std::min(length - writtenbytes, (long long) prot->BuffAvailable());
2334
2335 if (sendcontinue) {
2336 prot->SendSimpleResp(100, NULL, NULL, 0, 0, keepalive);
2337 return 0;
2338 }
2339
2340 break;
2341 } else {
2342
2343 // If we are here it's too late to send a proper error message...
2344 // However, we decide to send a response anyway before we close the connection
2345 // We are not sure if sending a final response before reading the entire request
2346 if (xrdresp == kXR_error) {
2347 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2348 return -1;
2349 }
2350
2351 if (ntohs(xrdreq.header.requestid) == kXR_write) {
2352 int l = ntohl(xrdreq.write.dlen);
2353
2354 // Consume the written bytes
2355 prot->BuffConsume(ntohl(xrdreq.write.dlen));
2356 writtenbytes += l;
2357
2358 // Update the chunk offset
2359 if (m_transfer_encoding_chunked) {
2360 m_current_chunk_offset += l;
2361 }
2362
2363 // We try to completely fill up our buffer before flushing
2364 prot->ResumeBytes = std::min(length - writtenbytes, (long long) prot->BuffAvailable());
2365
2366 return 0;
2367 }
2368
2369 if (ntohs(xrdreq.header.requestid) == kXR_close) {
2370 if (xrdresp == kXR_ok) {
2371 prot->SendSimpleResp(201, NULL, NULL, (char *)":-)", 0, keepalive);
2372 return keepalive ? 1 : -1;
2373 } else {
2374 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2375 return -1;
2376 }
2377 }
2378 }
2379
2380
2381
2382
2383
2384 break;
2385 }
2386
2387
2388
2390 {
2391
2392 if (xrdresp != kXR_ok) {
2393 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2394 httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2395 return -1;
2396 }
2397
2398
2399
2400
2401 switch (reqstate) {
2402
2403 case 0: // response to stat()
2404 {
2405 if (iovN > 0) {
2406
2407 // Now parse the stat info
2408 TRACEI(REQ, "Stat for removal " << resource.c_str()
2409 << " stat=" << (char *) iovP[0].iov_base);
2410
2411 long dummyl;
2412 sscanf((const char *) iovP[0].iov_base, "%ld %lld %ld %ld",
2413 &dummyl,
2414 &filesize,
2415 &fileflags,
2416 &filemodtime);
2417 }
2418
2419 return 0;
2420 }
2421 default: // response to rm
2422 {
2423 if (xrdresp == kXR_ok) {
2424 prot->SendSimpleResp(200, NULL, NULL, (char *) ":-)", 0, keepalive);
2425 return keepalive ? 1 : -1;
2426 }
2427 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2428 httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2429 return -1;
2430 }
2431 }
2432
2433
2434 }
2435
2437 {
2438
2439 if (xrdresp == kXR_error) {
2440 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2441 httpErrorBody.c_str(), httpErrorBody.length(), false);
2442 return -1;
2443 }
2444
2445 switch (reqstate) {
2446
2447 case 0: // response to stat()
2448 {
2449 DirListInfo e;
2450 e.size = 0;
2451 e.flags = 0;
2452
2453 // Now parse the answer building the entries vector
2454 if (iovN > 0) {
2455 e.path = resource.c_str();
2456
2457 // Now parse the stat info
2458 TRACEI(REQ, "Collection " << resource.c_str()
2459 << " stat=" << (char *) iovP[0].iov_base);
2460
2461 long dummyl;
2462 sscanf((const char *) iovP[0].iov_base, "%ld %lld %ld %ld",
2463 &dummyl,
2464 &e.size,
2465 &e.flags,
2466 &e.modtime);
2467
2468 if (e.path.length() && (e.path != ".") && (e.path != "..")) {
2469 /* The entry is filled. */
2470
2471
2472 std::string p;
2473 stringresp += "<D:response xmlns:lp1=\"DAV:\" xmlns:lp2=\"http://apache.org/dav/props/\" xmlns:lp3=\"LCGDM:\">\n";
2474
2475 char *estr = escapeXML(e.path.c_str());
2476
2477 stringresp += "<D:href>";
2478 stringresp += estr;
2479 stringresp += "</D:href>\n";
2480
2481 free(estr);
2482
2483 stringresp += "<D:propstat>\n<D:prop>\n";
2484
2485 // Now add the properties that we have to add
2486
2487 // File size
2488 stringresp += "<lp1:getcontentlength>";
2489 stringresp += itos(e.size);
2490 stringresp += "</lp1:getcontentlength>\n";
2491
2492
2493
2494 stringresp += "<lp1:getlastmodified>";
2496 stringresp += "</lp1:getlastmodified>\n";
2497
2498
2499
2500 if (e.flags & kXR_isDir) {
2501 stringresp += "<lp1:resourcetype><D:collection/></lp1:resourcetype>\n";
2502 stringresp += "<lp1:iscollection>1</lp1:iscollection>\n";
2503 } else {
2504 stringresp += "<lp1:resourcetype/>\n";
2505 stringresp += "<lp1:iscollection>0</lp1:iscollection>\n";
2506 }
2507
2508 if (e.flags & kXR_xset) {
2509 stringresp += "<lp1:executable>T</lp1:executable>\n";
2510 } else {
2511 stringresp += "<lp1:executable>F</lp1:executable>\n";
2512 }
2513
2514
2515
2516 stringresp += "</D:prop>\n<D:status>HTTP/1.1 200 OK</D:status>\n</D:propstat>\n</D:response>\n";
2517
2518
2519 }
2520
2521
2522 }
2523
2524 // If this was the last bunch of entries, send the buffer and empty it immediately
2525 if ((depth == 0) || !(e.flags & kXR_isDir)) {
2526 std::string s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<D:multistatus xmlns:D=\"DAV:\" xmlns:ns1=\"http://apache.org/dav/props/\" xmlns:ns0=\"DAV:\">\n";
2527 stringresp.insert(0, s);
2528 stringresp += "</D:multistatus>\n";
2529 prot->SendSimpleResp(207, (char *) "Multi-Status", (char *) "Content-Type: text/xml; charset=\"utf-8\"",
2530 (char *) stringresp.c_str(), stringresp.length(), keepalive);
2531 stringresp.clear();
2532 return keepalive ? 1 : -1;
2533 }
2534
2535 break;
2536 }
2537 default: // response to dirlist()
2538 {
2539
2540
2541 // Now parse the answer building the entries vector
2542 if (iovN > 0) {
2543 char *startp = (char *) iovP[0].iov_base, *endp = 0;
2544 char entry[1024];
2545 DirListInfo e;
2546
2547 while ( (size_t)(startp - (char *) iovP[0].iov_base) < (size_t)(iovP[0].iov_len - 1) ) {
2548 // Find the filename, it comes before the \n
2549 if ((endp = (char *) mystrchrnul((const char*) startp, '\n'))) {
2550 strncpy(entry, (char *) startp, endp - startp);
2551 entry[endp - startp] = 0;
2552 e.path = entry;
2553
2554 endp++;
2555
2556 // Now parse the stat info
2557 TRACEI(REQ, "Dirlist " <<resource.c_str() <<" entry=" <<entry
2558 << " stat=" << endp);
2559
2560 long dummyl;
2561 sscanf(endp, "%ld %lld %ld %ld",
2562 &dummyl,
2563 &e.size,
2564 &e.flags,
2565 &e.modtime);
2566 }
2567
2568
2569 if (e.path.length() && (e.path != ".") && (e.path != "..")) {
2570 /* The entry is filled.
2571
2572 <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/" xmlns:lp3="LCGDM:">
2573 <D:href>/dpm/cern.ch/home/testers2.eu-emi.eu/</D:href>
2574 <D:propstat>
2575 <D:prop>
2576 <lp1:getcontentlength>1</lp1:getcontentlength>
2577 <lp1:getlastmodified>Tue, 01 May 2012 02:42:13 GMT</lp1:getlastmodified>
2578 <lp1:resourcetype>
2579 <D:collection/>
2580 </lp1:resourcetype>
2581 </D:prop>
2582 <D:status>HTTP/1.1 200 OK</D:status>
2583 </D:propstat>
2584 </D:response>
2585 */
2586
2587
2588 std::string p = resource.c_str();
2589 if (*p.rbegin() != '/') p += "/";
2590
2591 p += e.path;
2592
2593 stringresp += "<D:response xmlns:lp1=\"DAV:\" xmlns:lp2=\"http://apache.org/dav/props/\" xmlns:lp3=\"LCGDM:\">\n";
2594
2595 char *estr = escapeXML(p.c_str());
2596 stringresp += "<D:href>";
2597 stringresp += estr;
2598 stringresp += "</D:href>\n";
2599 free(estr);
2600
2601 stringresp += "<D:propstat>\n<D:prop>\n";
2602
2603
2604
2605 // Now add the properties that we have to add
2606
2607 // File size
2608 stringresp += "<lp1:getcontentlength>";
2609 stringresp += itos(e.size);
2610 stringresp += "</lp1:getcontentlength>\n";
2611
2612 stringresp += "<lp1:getlastmodified>";
2614 stringresp += "</lp1:getlastmodified>\n";
2615
2616 if (e.flags & kXR_isDir) {
2617 stringresp += "<lp1:resourcetype><D:collection/></lp1:resourcetype>\n";
2618 stringresp += "<lp1:iscollection>1</lp1:iscollection>\n";
2619 } else {
2620 stringresp += "<lp1:resourcetype/>\n";
2621 stringresp += "<lp1:iscollection>0</lp1:iscollection>\n";
2622 }
2623
2624 if (e.flags & kXR_xset) {
2625 stringresp += "<lp1:executable>T</lp1:executable>\n";
2626 stringresp += "<lp1:iscollection>1</lp1:iscollection>\n";
2627 } else {
2628 stringresp += "<lp1:executable>F</lp1:executable>\n";
2629 }
2630
2631 stringresp += "</D:prop>\n<D:status>HTTP/1.1 200 OK</D:status>\n</D:propstat>\n</D:response>\n";
2632
2633
2634 }
2635
2636
2637
2638 if (endp) {
2639 char *pp = (char *)strchr((const char *)endp, '\n');
2640 if (pp) startp = pp+1;
2641 else break;
2642 } else break;
2643
2644 }
2645 }
2646
2647
2648
2649 // If this was the last bunch of entries, send the buffer and empty it immediately
2650 if (final_) {
2651 std::string s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<D:multistatus xmlns:D=\"DAV:\" xmlns:ns1=\"http://apache.org/dav/props/\" xmlns:ns0=\"DAV:\">\n";
2652 stringresp.insert(0, s);
2653 stringresp += "</D:multistatus>\n";
2654 prot->SendSimpleResp(207, (char *) "Multi-Status", (char *) "Content-Type: text/xml; charset=\"utf-8\"",
2655 (char *) stringresp.c_str(), stringresp.length(), keepalive);
2656 stringresp.clear();
2657 return keepalive ? 1 : -1;
2658 }
2659
2660 break;
2661 } // default reqstate
2662 } // switch reqstate
2663
2664
2665 break;
2666
2667 } // case propfind
2668
2670 {
2671
2672 if (xrdresp != kXR_ok) {
2673 if (xrderrcode == kXR_ItExists) {
2674 prot->SendSimpleResp(405, NULL, NULL, (char *) "Method is not allowed; resource already exists.", 0, false);
2675 } else {
2676 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2677 httpErrorBody.c_str(), httpErrorBody.length(), false);
2678 }
2679 return -1;
2680 }
2681
2682 prot->SendSimpleResp(201, NULL, NULL, (char *) ":-)", 0, keepalive);
2683 return keepalive ? 1 : -1;
2684
2685 }
2686 case XrdHttpReq::rtMOVE:
2687 {
2688
2689 if (xrdresp != kXR_ok) {
2690 prot->SendSimpleResp(httpStatusCode, NULL, NULL, (char *) etext.c_str(), 0, false);
2691 return -1;
2692 }
2693
2694 prot->SendSimpleResp(201, NULL, NULL, (char *) ":-)", 0, keepalive);
2695 return keepalive ? 1 : -1;
2696
2697 }
2698
2699 default:
2700 break;
2701
2702 }
2703
2704
2705 switch (xrdresp) {
2706 case kXR_error:
2707 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2708 httpErrorBody.c_str(), httpErrorBody.length(), false);
2709 return -1;
2710 break;
2711
2712 default:
2713
2714 break;
2715 }
2716
2717
2718 return 0;
2719}
2720
2721int XrdHttpReq::sendFooterError(const std::string &extra_text) {
2722 if (m_transfer_encoding_chunked && m_trailer_headers && m_status_trailer) {
2723 std::stringstream ss;
2724
2725 ss << httpStatusCode;
2726 if (!httpErrorBody.empty()) {
2727 std::string_view statusView(httpErrorBody);
2728 // Remove trailing newline; this is not valid in a trailer value
2729 // and causes incorrect framing of the response, confusing clients.
2730 if (!statusView.empty() && statusView.back() == '\n') {
2731 ss << ": " << statusView.substr(0, statusView.size() - 1);
2732 } else {
2733 ss << ": " << httpErrorBody;
2734 }
2735 }
2736
2737 if (!extra_text.empty()) ss << ": " << extra_text;
2738 TRACEI(REQ, ss.str());
2739 ss << "\r\n";
2740
2741 const std::string trailer = "X-Transfer-Status: " + ss.str();
2742
2743 // delegate everything to ChunkResp (bodylen==-1 means trailers)
2744 if (prot->ChunkResp(trailer.c_str(), -1)) return -1;
2745
2746 return keepalive ? 1 : -1;
2747 } else {
2748 TRACEI(REQ, "Failure during response: " << httpStatusCode << ": " << httpErrorBody << (extra_text.empty() ? "" : (": " + extra_text)));
2749 return -1;
2750 }
2751}
2752
2753void XrdHttpReq::addAgeHeader(std::string &headers) {
2754 long object_age = time(NULL) - filemodtime;
2755 headers += std::string("Age: ") + std::to_string(object_age < 0 ? 0 : object_age);
2756}
2757
2758void XrdHttpReq::addETagHeader(std::string &headers) {
2759 headers += std::string("Etag: \"") + std::to_string(etagval) + "\"";
2760}
2761
2763
2764 TRACE(REQ, " XrdHttpReq request ended.");
2765
2766 //if (xmlbody) xmlFreeDoc(xmlbody);
2768 readClosing = false;
2769 closeAfterError = false;
2770 writtenbytes = 0;
2771 etext.clear();
2772 redirdest = "";
2773
2774 // // Here we should deallocate this
2775 // const struct iovec *iovP //!< pointer to data array
2776 // int iovN, //!< array count
2777 // int iovL, //!< byte count
2778 // bool final //!< true -> final result
2779
2780
2781 //xmlbody = 0;
2782 depth = 0;
2785 ralist.clear();
2786 ralist.shrink_to_fit();
2787
2788 request = rtUnset;
2789 resource = "";
2790 allheaders.clear();
2791
2792 // Reset the state of the request's digest request.
2793 m_want_digest.clear();
2794 m_digest_header.clear();
2795 m_req_cksum = nullptr;
2796
2797 m_user_agent = "";
2798 m_origin = "";
2799
2800 httpStatusCode = -1;
2801 initialStatusCode= -1;
2802 httpErrorCode = "";
2803 httpErrorBody = "";
2804
2805 headerok = false;
2806 keepalive = true;
2807 length = 0;
2808 length_seen = false;
2809 filesize = 0;
2810 depth = 0;
2811 sendcontinue = false;
2812
2813 m_transfer_encoding_chunked = false;
2814 m_current_chunk_size = -1;
2815 m_current_chunk_offset = 0;
2816
2817 m_trailer_headers = false;
2818 m_status_trailer = false;
2819
2821 reqstate = 0;
2822
2823 memset(&xrdreq, 0, sizeof (xrdreq));
2824 memset(&xrdresp, 0, sizeof (xrdresp));
2826
2827 etext.clear();
2828 redirdest = "";
2829
2830 stringresp = "";
2831
2832 host = "";
2833 destination = "";
2834 hdr2cgistr = "";
2835 m_appended_hdr2cgistr = false;
2836 m_appended_asize = false;
2837
2838 iovP = 0;
2839 iovN = 0;
2840 iovL = 0;
2841
2842 if (opaque) delete(opaque);
2843 opaque = 0;
2844
2845 fopened = false;
2846 final = false;
2847 mScitag = -1;
2848
2849 m_repr_digest.clear();
2850 m_want_repr_digest.clear();
2851
2853 startTime = std::chrono::steady_clock::time_point::min();
2854}
2855
2856void XrdHttpReq::getfhandle() {
2857
2858 memcpy(fhandle, iovP[0].iov_base, 4);
2859 TRACEI(REQ, "fhandle:" <<
2860 (int) fhandle[0] << ":" << (int) fhandle[1] << ":" << (int) fhandle[2] << ":" << (int) fhandle[3]);
2861
2862}
2863
2864void XrdHttpReq::getReadResponse(XrdHttpIOList &received) {
2865 received.clear();
2866
2867 if (ntohs(xrdreq.header.requestid) == kXR_readv) {
2868 readahead_list *l;
2869 char *p;
2870 kXR_int32 len;
2871
2872 // Cycle on all the data that is coming from the server
2873 for (int i = 0; i < iovN; i++) {
2874
2875 for (p = (char *) iovP[i].iov_base; p < (char *) iovP[i].iov_base + iovP[i].iov_len;) {
2876 l = (readahead_list *) p;
2877 memcpy(&len, &l->rlen, sizeof(kXR_int32));
2878 len = ntohl(len);
2879
2880 received.emplace_back(p+sizeof(readahead_list), -1, len);
2881
2882 p += sizeof (readahead_list);
2883 p += len;
2884
2885 }
2886 }
2887 return;
2888 }
2889
2890 // kXR_read result
2891 for (int i = 0; i < iovN; i++) {
2892 received.emplace_back((char*)iovP[i].iov_base, -1, iovP[i].iov_len);
2893 }
2894
2895}
2896
2897int XrdHttpReq::sendReadResponsesMultiRanges(const XrdHttpIOList &received) {
2898
2899 if (received.size() == 0) {
2900 bool start, finish;
2901 if (readRangeHandler.NotifyReadResult(0, nullptr, start, finish) < 0) {
2902 return -1;
2903 }
2904 return 0;
2905 }
2906
2907 // user is expecting multiple ranges, we must be prepared to send an
2908 // individual header for each and format it according to the http rules
2909
2910 struct rinfo {
2911 bool start;
2912 bool finish;
2913 const XrdOucIOVec2 *ci;
2914 const XrdHttpReadRangeHandler::UserRange *ur;
2915 std::string st_header;
2916 std::string fin_header;
2917 };
2918
2919 // report each received byte chunk to the range handler and record the details
2920 // of original user range it related to and if starts a range or finishes all.
2921 // also sum the total of the headers and data which need to be sent to the user,
2922 // in case we need it for chunked transfer encoding
2923 std::vector<rinfo> rvec;
2924 off_t sum_len = 0;
2925
2926 rvec.reserve(received.size());
2927
2928 for(const auto &rcv: received) {
2929 rinfo rentry;
2930 bool start, finish;
2931 const XrdHttpReadRangeHandler::UserRange *ur;
2932
2933 if (readRangeHandler.NotifyReadResult(rcv.size, &ur, start, finish) < 0) {
2934 return -1;
2935 }
2936 rentry.ur = ur;
2937 rentry.start = start;
2938 rentry.finish = finish;
2939 rentry.ci = &rcv;
2940
2941 if (start) {
2942 std::string s = buildPartialHdr(ur->start,
2943 ur->end,
2944 filesize,
2945 (char *) "123456");
2946
2947 rentry.st_header = s;
2948 sum_len += s.size();
2949 }
2950
2951 sum_len += rcv.size;
2952
2953 if (finish) {
2954 std::string s = buildPartialHdrEnd((char *) "123456");
2955 rentry.fin_header = s;
2956 sum_len += s.size();
2957 }
2958
2959 rvec.push_back(rentry);
2960 }
2961
2962
2963 // Send chunked encoding header
2964 if (m_transfer_encoding_chunked && m_trailer_headers) {
2965 prot->ChunkRespHeader(sum_len);
2966 }
2967
2968 // send the user the headers / data
2969 for(const auto &rentry: rvec) {
2970
2971 if (rentry.start) {
2972 TRACEI(REQ, "Sending multipart: " << rentry.ur->start << "-" << rentry.ur->end);
2973 if (prot->SendData((char *) rentry.st_header.c_str(), rentry.st_header.size())) {
2974 return -1;
2975 }
2976 }
2977
2978 // Send all the data we have
2979 if (prot->SendData((char *) rentry.ci->data, rentry.ci->size)) {
2980 return -1;
2981 }
2982
2983 if (rentry.finish) {
2984 if (prot->SendData((char *) rentry.fin_header.c_str(), rentry.fin_header.size())) {
2985 return -1;
2986 }
2987 }
2988 }
2989
2990 // Send chunked encoding footer
2991 if (m_transfer_encoding_chunked && m_trailer_headers) {
2992 prot->ChunkRespFooter();
2993 }
2994
2995 return 0;
2996}
2997
2998int XrdHttpReq::sendReadResponseSingleRange(const XrdHttpIOList &received) {
2999 // single range http transfer
3000
3001 if (received.size() == 0) {
3002 bool start, finish;
3003 if (readRangeHandler.NotifyReadResult(0, nullptr, start, finish) < 0) {
3004 return -1;
3005 }
3006 return 0;
3007 }
3008
3009 off_t sum = 0;
3010 // notify the range handler and return if error
3011 for(const auto &rcv: received) {
3012 bool start, finish;
3013 if (readRangeHandler.NotifyReadResult(rcv.size, nullptr, start, finish) < 0) {
3014 return -1;
3015 }
3016 sum += rcv.size;
3017 }
3018
3019 // Send chunked encoding header
3020 if (m_transfer_encoding_chunked && m_trailer_headers) {
3021 prot->ChunkRespHeader(sum);
3022 }
3023 for(const auto &rcv: received) {
3024 if (prot->SendData((char *) rcv.data, rcv.size)) return -1;
3025 }
3026 if (m_transfer_encoding_chunked && m_trailer_headers) {
3027 prot->ChunkRespFooter();
3028 }
3029 return 0;
3030}
kXR_unt16 requestid
Definition XProtocol.hh:511
kXR_char options[1]
Definition XProtocol.hh:278
XErrorCode
@ kXR_ItExists
@ kXR_noErrorYet
@ kXR_isDirectory
kXR_int16 arg1len
Definition XProtocol.hh:460
#define kXR_isManager
kXR_unt16 requestid
Definition XProtocol.hh:847
struct ClientCloseRequest close
Definition XProtocol.hh:893
kXR_char fhandle[4]
Definition XProtocol.hh:848
struct ClientSetRequest set
Definition XProtocol.hh:913
struct ClientMkdirRequest mkdir
Definition XProtocol.hh:900
kXR_int32 dlen
Definition XProtocol.hh:461
kXR_unt16 requestid
Definition XProtocol.hh:680
kXR_unt16 options
Definition XProtocol.hh:513
struct ClientDirlistRequest dirlist
Definition XProtocol.hh:894
kXR_unt16 requestid
Definition XProtocol.hh:257
struct ClientReadVRequest readv
Definition XProtocol.hh:910
@ kXR_open_wrto
Definition XProtocol.hh:499
@ kXR_delete
Definition XProtocol.hh:483
@ kXR_open_read
Definition XProtocol.hh:486
@ kXR_mkpath
Definition XProtocol.hh:490
@ kXR_seqio
Definition XProtocol.hh:498
@ kXR_new
Definition XProtocol.hh:485
@ kXR_retstat
Definition XProtocol.hh:493
struct ClientOpenRequest open
Definition XProtocol.hh:902
@ kXR_noResponsesYet
Definition XProtocol.hh:950
@ kXR_ok
Definition XProtocol.hh:941
@ kXR_error
Definition XProtocol.hh:945
@ kXR_dstat
Definition XProtocol.hh:269
struct ClientRequestHdr header
Definition XProtocol.hh:887
kXR_unt16 requestid
Definition XProtocol.hh:458
kXR_char fhandle[4]
Definition XProtocol.hh:681
kXR_char fhandle[4]
Definition XProtocol.hh:258
kXR_unt16 requestid
Definition XProtocol.hh:159
@ kXR_read
Definition XProtocol.hh:126
@ kXR_open
Definition XProtocol.hh:123
@ kXR_readv
Definition XProtocol.hh:138
@ kXR_mkdir
Definition XProtocol.hh:121
@ kXR_dirlist
Definition XProtocol.hh:117
@ kXR_rm
Definition XProtocol.hh:127
@ kXR_write
Definition XProtocol.hh:132
@ kXR_set
Definition XProtocol.hh:131
@ kXR_rmdir
Definition XProtocol.hh:128
@ kXR_mv
Definition XProtocol.hh:122
@ kXR_stat
Definition XProtocol.hh:130
@ kXR_close
Definition XProtocol.hh:116
kXR_int32 dlen
Definition XProtocol.hh:735
struct ClientRmRequest rm
Definition XProtocol.hh:911
kXR_unt16 requestid
Definition XProtocol.hh:755
struct ClientReadRequest read
Definition XProtocol.hh:909
struct ClientMvRequest mv
Definition XProtocol.hh:901
kXR_int32 rlen
Definition XProtocol.hh:696
kXR_unt16 requestid
Definition XProtocol.hh:808
struct ClientRmdirRequest rmdir
Definition XProtocol.hh:912
kXR_unt16 requestid
Definition XProtocol.hh:445
kXR_char options[1]
Definition XProtocol.hh:446
kXR_unt16 requestid
Definition XProtocol.hh:733
@ kXR_mkdirpath
Definition XProtocol.hh:440
struct ClientStatRequest stat
Definition XProtocol.hh:915
struct ClientWriteRequest write
Definition XProtocol.hh:918
@ kXR_gw
Definition XProtocol.hh:474
@ kXR_ur
Definition XProtocol.hh:470
@ kXR_uw
Definition XProtocol.hh:471
@ kXR_gr
Definition XProtocol.hh:473
@ kXR_or
Definition XProtocol.hh:476
@ kXR_readable
@ kXR_isDir
@ kXR_offline
@ kXR_other
@ kXR_writable
@ kXR_cachersp
@ kXR_xset
kXR_unt16 requestid
Definition XProtocol.hh:744
long long kXR_int64
Definition XPtypes.hh:98
int kXR_int32
Definition XPtypes.hh:89
short kXR_int16
Definition XPtypes.hh:66
unsigned char kXR_char
Definition XPtypes.hh:65
#define DEBUG(x)
A pragmatic implementation of the HTTP/DAV protocol for the Xrd framework.
std::string ISOdatetime(time_t t)
Definition XrdHttpReq.cc:84
#define MAX_TK_LEN
Definition XrdHttpReq.cc:67
void trim(std::string &str)
Definition XrdHttpReq.cc:78
Main request/response class, handling the logical status of the communication.
long long size
Definition XrdHttpReq.hh:56
void trim(std::string &str)
Definition XrdHttpReq.cc:78
std::string path
Definition XrdHttpReq.hh:55
Static resources, here for performance and ease of setup.
Trace definitions.
std::string itos(long i)
void Tobase64(const unsigned char *input, int length, char *out)
int mapXrdErrToHttp(XErrorCode xrdError)
bool Fromhexdigest(const std::string &hex, std::vector< uint8_t > &outputBytes)
char * escapeXML(const char *str)
char * mystrchrnul(const char *s, int c)
std::string httpCollapseSlashes(std::string path)
void calcHashes(char *hash, const char *fn, kXR_int16 request, XrdSecEntity *secent, time_t tim, const char *key)
std::string httpPathFromAbsoluteUrl(const char *hname)
Path starting at the first / after the authority. An absent path is /.
std::string httpBuildRedirectLocation(bool destHttps, int port, const char *hname, const char *encodedResource)
Utility functions for XrdHTTP.
std::string encode_opaque(const std::string &opaque)
std::string encode_str(const std::string &str)
std::vector< XrdOucIOVec2 > XrdHttpIOList
std::string decode_str(const std::string &str)
void stripCgi(std::string &url, const std::unordered_set< std::string > &cgiKeys)
std::string obfuscateAuth(const std::string &input)
#define STR_NPOS
#define TRACE_DEBUG
Definition XrdTrace.hh:36
#define TRACE(act, x)
Definition XrdTrace.hh:63
#define TRACING(x)
Definition XrdTrace.hh:70
#define TRACEI(act, x)
Definition XrdTrace.hh:66
XrdHttpChecksumHandlerImpl::XrdHttpChecksumRawPtr XrdHttpChecksumRawPtr
bool needsBase64Padding() const
std::string getXRootDConfigDigestName() const
std::string getHttpName() const
virtual int ProcessReq(XrdHttpExtReq &)=0
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)
void reset()
resets this handler
const XrdHttpIOList & NextReadList()
return XrdHttpIOList for sending to read or readv
void ParseContentRange(const char *const line)
parse the line after a "Range: " http request header
int SetFilesize(const off_t sz)
sets the filesize, used during resolving and issuing range requests
void NotifyError()
Force handler to enter error state.
bool isFullFile()
indicates when there were no valid Range head ranges supplied
std::vector< UserRange > UserRangeList
int NotifyReadResult(const ssize_t ret, const UserRange **const urp, bool &start, bool &allend)
Advance internal counters concerning received bytes.
size_t getMaxRanges() const
return the maximum number of ranges that may be requested
const Error & getError() const
return the Error object
bool isSingleRange()
indicates a single range (implied whole file, or single range) or empty file
const UserRangeList & ListResolvedRanges()
return resolved (i.e. obsolute start and end) byte ranges desired
int reqstate
State machine to talk to the bridge.
char fhandle[4]
int ReqReadV(const XrdHttpIOList &cl)
Prepare the buffers for sending a readv request.
int parseBody(char *body, long long len)
Parse the body of a request, assuming that it's XML and that it's entirely in memory.
Definition XrdHttpReq.cc:96
std::vector< readahead_list > ralist
std::string destination
The destination field specified in the req.
XrdOucString resource
The resource specified by the request, stripped of opaque data.
bool headerok
Tells if we have finished reading the header.
std::map< std::string, uint8_t > m_want_repr_digest
ssize_t length
std::string m_digest_header
The computed digest for the HTTP response header.
std::string etext
std::string stringresp
If we want to give a string as a response, we compose it here.
XResponseType xrdresp
The last response data we got.
std::map< std::string, std::string > m_repr_digest
Repr-Digest map where the key is the digest name and the value is the base64 encoded digest value.
std::string requestverb
ReqType request
The request we got.
bool length_seen
int ProcessHTTPReq()
bool closeAfterError
long long writtenbytes
In a long write, we track where we have arrived.
XrdOucEnv * opaque
The opaque data, after parsing.
int iovL
byte count
const struct iovec * iovP
The latest data chunks got from the xrd layer. These are valid only inside the callbacks!
virtual ~XrdHttpReq()
XrdOucString resourceplusopaque
The resource specified by the request, including all the opaque data.
virtual bool Data(XrdXrootd::Bridge::Context &info, const struct iovec *iovP, int iovN, int iovL, bool final)
std::string hdr2cgistr
Additional opaque info that may come from the hdr2cgi directive.
virtual bool Done(XrdXrootd::Bridge::Context &info)
the result context
std::string host
The host field specified in the req.
long filemodtime
int parseFirstLine(char *line, int len)
Parse the first line of the header.
XrdOucString redirdest
std::string m_origin
int parseLine(char *line, int len)
Parse the header.
std::string buildPartialHdrEnd(char *token)
Build the closing part for a multipart response.
XrdHttpChecksumHandler::XrdHttpChecksumRawPtr m_req_cksum
The checksum that was ran for this request.
std::string m_want_digest
The requested digest type.
void setTransferStatusHeader(std::string &header)
bool m_appended_hdr2cgistr
void appendOpaque(XrdOucString &s, XrdSecEntity *secent, char *hash, time_t tnow)
int iovN
array count
bool m_appended_asize
Track whether we already appended the oss.asize argument for PUTs.
XrdOucString m_resource_with_digest
long long filesize
bool readClosing
std::chrono::steady_clock::time_point startTime
long long etagval
virtual bool Redir(XrdXrootd::Bridge::Context &info, int port, const char *hname)
XErrorCode xrderrcode
virtual int File(XrdXrootd::Bridge::Context &info, int dlen)
std::map< std::string, std::string > allheaders
void addCgi(const std::string &key, const std::string &value)
bool sendcontinue
ClientRequest xrdreq
The last issued xrd request, often pending.
XrdHttpMonState monState
std::string buildPartialHdr(long long bytestart, long long byteend, long long filesize, char *token)
Build a partial header for a multipart response.
XrdHttpReadRangeHandler readRangeHandler
Tracking the next ranges of data to read during GET.
virtual void reset()
virtual bool Error(XrdXrootd::Bridge::Context &info, int ecode, const char *etext)
static const int minTotID
static const int maxTotID
char * Env(int &envlen)
Definition XrdOucEnv.hh:48
char * Get(const char *varname)
Definition XrdOucEnv.hh:69
XrdOucEnv(const char *vardata=0, int vardlen=0, const XrdSecEntity *secent=0)
Definition XrdOucEnv.cc:42
void assign(const char *s, int j, int k=-1)
int erasefromstart(int sz=0)
int erasefromend(int sz=0)
bool endswith(char c)
bool beginswith(char c)
int find(const char c, int start=0, bool forward=1)
int length() const
void append(const int i)
const char * c_str() const
static std::map< std::string, T >::const_iterator caseInsensitiveFind(const std::map< std::string, T > &m, const std::string &lowerCaseSearchKey)
static void trim(std::string &str)
char * vorg
Entity's virtual organization(s).
int credslen
Length of the 'creds' data.
char * creds
Raw entity credentials or cert.
char * grps
Entity's group name(s).
char * name
Entity's name.
char * role
Entity's role(s).
char * endorsements
Protocol specific endorsements.
char * moninfo
Information for monitoring.
char * host
Entity's host name dnr dependent.
virtual int Send(const struct iovec *headP, int headN, const struct iovec *tailP, int tailN)