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.
178 readRangeHandler.ParseContentRange(val);
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
477 xrdreq.header.requestid = htons(kXR_readv);
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) {
541 readRangeHandler.NotifyError();
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 buf[512];
607 char hash[512];
608 hash[0] = '\0';
609
610 bool invalid_host = false;
611
612 if (!hname) {
613 invalid_host = true;
614 } else {
615 for (const char *c = hname; *c; ++c)
616 if (*c == '\r' || *c == '\n')
617 invalid_host = true;
618 }
619
620 if (invalid_host) {
621 prot->SendSimpleResp(502, nullptr, nullptr, "Invalid redirect host", 0, false);
622 return keepalive;
623 }
624
625 if (prot->isdesthttps)
626 redirdest = "Location: https://";
627 else
628 redirdest = "Location: http://";
629
630 // port < 0 signals switch to full URL
631 if (port < 0)
632 {
633 if (strncmp(hname, "file://", 7) == 0)
634 {
635 TRACE(REQ, " XrdHttpReq::Redir Switching to file:// ");
636 redirdest = "Location: "; // "file://" already contained in hname
637 }
638 }
639 // Beware, certain Ofs implementations (e.g. EOS) add opaque data directly to the host name
640 // This must be correctly treated here and appended to the opaque info
641 // that we may already have
642 char *pp = strchr((char *)hname, '?');
643 char *vardata = 0;
644 if (pp) {
645 *pp = '\0';
646 redirdest += hname;
647 vardata = pp+1;
648 int varlen = strlen(vardata);
649
650 //Now extract the remaining, vardata points to it
651 while(*vardata == '&' && varlen) {vardata++; varlen--;}
652
653 // Put the question mark back where it was
654 *pp = '?';
655 }
656 else
657 redirdest += hname;
658
659 if (port > 0) {
660 sprintf(buf, ":%d", port);
661 redirdest += buf;
662 }
663
664 redirdest += encode_str(resource.c_str()).c_str();
665
666 // Here we put back the opaque info, if any
667 if (vardata) {
668 redirdest += "?&";
669 redirdest += encode_opaque(vardata).c_str();
670 }
671
672 // Shall we put also the opaque data of the request? Maybe not
673 //int l;
674 //if (opaque && opaque->Env(l))
675 // redirdest += opaque->Env(l);
676
677
678 time_t timenow = 0;
679 if (!prot->isdesthttps && prot->ishttps) {
680 // If the destination is not https, then we suppose that it
681 // will need this token to fill its authorization info
682 timenow = time(0);
683 calcHashes(hash, this->resource.c_str(), (kXR_int16) request,
684 &prot->SecEntity,
685 timenow,
686 prot->secretkey);
687 }
688
689 if (hash[0]) {
690 appendOpaque(redirdest, &prot->SecEntity, hash, timenow);
691 } else
692 appendOpaque(redirdest, 0, 0, 0);
693
694 if (!prot->strp_cgi_params.empty()) {
695 stripCgi(redirdest, prot->strp_cgi_params); /* appendOpaque() may have added credentials */
696 }
697
698 TRACE(REQ, " XrdHttpReq::Redir Redirecting to " << redirdest.c_str());
699
700 if (request != rtGET)
701 prot->SendSimpleResp(307, NULL, (char *) redirdest.c_str(), 0, 0, keepalive);
702 else
703 prot->SendSimpleResp(302, NULL, (char *) redirdest.c_str(), 0, 0, keepalive);
704
705 bool ret_keepalive = keepalive; // reset() clears keepalive
706 reset();
707 return ret_keepalive;
708};
709
710
711void XrdHttpReq::appendOpaque(XrdOucString &s, XrdSecEntity *secent, char *hash, time_t tnow) {
712
713 int l = 0;
714 char * p = 0;
715 if (opaque)
716 p = opaque->Env(l);
717
718 if (hdr2cgistr.empty() && (l < 2) && !hash) return;
719
720 // this works in most cases, except if the url already contains the xrdhttp tokens
721 s = s + "?";
722 if (!hdr2cgistr.empty()) {
723 s += encode_opaque(hdr2cgistr).c_str();
724 }
725 if (p && (l > 1)) {
726 if (!hdr2cgistr.empty()) {
727 s = s + "&";
728 }
729 s = s + encode_opaque(p + 1).c_str();
730 }
731
732 if (hash) {
733 if (l > 1) s += "&";
734 s += "xrdhttptk=";
735 s += hash;
736
737 s += "&xrdhttptime=";
738 char buf[256];
739 sprintf(buf, "%lld", (long long) tnow);
740 s += buf;
741
742 if (secent) {
743 if (secent->name) {
744 s += "&xrdhttpname=";
745 s += encode_str(secent->name).c_str();
746 }
747 }
748
749 if (secent->vorg) {
750 s += "&xrdhttpvorg=";
751 s += encode_str(secent->vorg).c_str();
752 }
753
754 if (secent->host) {
755 s += "&xrdhttphost=";
756 s += encode_str(secent->host).c_str();
757 }
758
759 if (secent->moninfo) {
760 s += "&xrdhttpdn=";
761 s += encode_str(secent->moninfo).c_str();
762 }
763
764 if (secent->role) {
765 s += "&xrdhttprole=";
766 s += encode_str(secent->role).c_str();
767 }
768
769 if (secent->grps) {
770 s += "&xrdhttpgrps=";
771 s += encode_str(secent->grps).c_str();
772 }
773
774 if (secent->endorsements) {
775 s += "&xrdhttpendorsements=";
776 s += encode_str(secent->endorsements).c_str();
777 }
778
779 if (secent->credslen) {
780 s += "&xrdhttpcredslen=";
781 char buf[16];
782 sprintf(buf, "%d", secent->credslen);
783 s += encode_str(buf).c_str();
784 }
785
786 if (secent->credslen) {
787 if (secent->creds) {
788 s += "&xrdhttpcreds=";
789 // Apparently this string might be not 0-terminated (!)
790 char *zerocreds = strndup(secent->creds, secent->credslen);
791 if (zerocreds) {
792 s += encode_str(zerocreds).c_str();
793 free(zerocreds);
794 }
795 }
796 }
797 }
798 }
799
800// Sanitize the resource from the http[s]://[host]/ questionable prefix
801// https://github.com/xrootd/xrootd/issues/1675
802void XrdHttpReq::sanitizeResourcePfx() {
803
804 if (resource.beginswith("https://")) {
805 // Find the slash that follows the hostname, and keep it
806 int p = resource.find('/', 8);
808 return;
809 }
810
811 if (resource.beginswith("http://")) {
812 // Find the slash that follows the hostname, and keep it
813 int p = resource.find('/', 7);
814 resource.erasefromstart(p);
815 return;
816 }
817}
818
819void XrdHttpReq::addCgi(const std::string &key, const std::string &value) {
820 if (hdr2cgistr.length() > 0) {
821 hdr2cgistr.append("&");
822 }
823 hdr2cgistr.append(key);
824 hdr2cgistr.append("=");
825 hdr2cgistr.append(value);
826}
827
828
829// Parse a resource line:
830// - sanitize
831// - extracts the opaque info from the given url
832// - sanitize the resource from http[s]://[host]/ questionable prefix
833void XrdHttpReq::parseResource(char *res) {
834
835
836
837
838 // Look for the first '?'
839 char *p = strchr(res, '?');
840
841 // Not found, then it's just a filename
842 if (!p) {
843 resource.assign(res, 0);
844
845 // Some poor client implementations may inject a http[s]://[host]/ prefix
846 // to the resource string. Here we choose to ignore it as a protection measure
847 sanitizeResourcePfx();
848
849 std::string resourceDecoded = decode_str(resource.c_str());
850 resource = resourceDecoded.c_str();
851 resourceplusopaque = resourceDecoded.c_str();
852
853
854 // Sanitize the resource string, removing double slashes
855 int pos = 0;
856 do {
857 pos = resource.find("//", pos);
858 if (pos != STR_NPOS)
859 resource.erase(pos, 1);
860 } while (pos != STR_NPOS);
861
862 return;
863 }
864
865 // Whatever comes before '?' is a filename
866
867 int cnt = p - res; // Number of chars to copy
868 resource.assign(res, 0, cnt - 1);
869
870 // Some poor client implementations may inject a http[s]://[host]/ prefix
871 // to the resource string. Here we choose to ignore it as a protection measure
872 sanitizeResourcePfx();
873
874 resource = decode_str(resource.c_str()).c_str();
875
876 // Sanitize the resource string, removing double slashes
877 int pos = 0;
878 do {
879 pos = resource.find("//", pos);
880 if (pos != STR_NPOS)
881 resource.erase(pos, 1);
882 } while (pos != STR_NPOS);
883
885 // Whatever comes after is opaque data to be parsed
886 if (strlen(p) > 1) {
887 std::string decoded = decode_str(p + 1);
888 opaque = new XrdOucEnv(decoded.c_str());
889 resourceplusopaque.append('?');
890 resourceplusopaque.append(p + 1);
891 }
892}
893
894void XrdHttpReq::generateWebdavErrMsg() {
895
896 // This block is only used when sending an "X-Transfer-Status" trailer response.
897 // We set the body to "OK" so that the trailer becomes "X-Transfer-Status: 200 OK",
898 // indicating a successful transfer.
899 if (xrdresp == kXR_ok) {
900 httpStatusCode = 200;
901 httpErrorBody = "OK";
902 return;
903 }
904
905 // default error
906 httpStatusCode = mapXrdErrToHttp(xrderrcode);
907 httpErrorBody = etext + "\n";
908
909}
910
911int XrdHttpReq::prepareChecksumQuery(XrdHttpChecksumHandler::XrdHttpChecksumRawPtr &outCksum,
912 XrdOucString &outResourceDigestOpaque) {
913 const char *opaque = strchr(resourceplusopaque.c_str(), '?');
914
915 outResourceDigestOpaque = resourceplusopaque;
916
917 if(m_want_digest.size()) {
918 // According to rfc9530 "Integrity preference fields are only a hint. The receiver of the
919 // field can ignore it and send an Integrity field using any algorithm
920 // or omit the field entirely.
921 // However, in the case a client requests both Want-Digest AND Want-Repr-Digest,
922 // we will return a 'Digest' header in response to the Want-Digest request in order to keep backward compatibility.
923 outCksum = prot->cksumHandler.getChecksumToRunWantDigest(m_want_digest);
924 } else {
925 // Want-Repr-Digest has been passed alone, deduce the checksum to run from that header
926 outCksum = prot->cksumHandler.getChecksumToRunWantReprDigest(m_want_repr_digest);
927 }
928 if(!outCksum) {
929 // No HTTP IANA checksums have been configured by the server admin, return a "METHOD_NOT_ALLOWED" error
930 prot->SendSimpleResp(405, NULL, NULL, (char *) "No HTTP-IANA compatible checksums have been configured.", 0, false);
931 return -1;
932 }
933 outResourceDigestOpaque += !opaque ? "?" : "&";
934 outResourceDigestOpaque += "cks.type=";
935 outResourceDigestOpaque += outCksum->getXRootDConfigDigestName().c_str();
936
937 return 0;
938}
939
941
942 kXR_int32 l;
943 if (startTime == std::chrono::steady_clock::time_point::min()) startTime = std::chrono::steady_clock::now();
944
945 // State variable for tracking the query parameter search
946 // - 0: Indicates we've not yet searched the URL for '?'
947 // - 1: Indicates we have a '?' and hence query parameters
948 // - 2: Indicates we do *not* have '?' present -- no query parameters
949 int query_param_status = 0;
950 if (!m_appended_asize) {
951 m_appended_asize = true;
952 if (request == rtPUT && length) {
953 if (query_param_status == 0) {
954 query_param_status = strchr(resourceplusopaque.c_str(), '?') ? 1 : 2;
955 }
956 resourceplusopaque.append((query_param_status == 1) ? '&' : '?');
957 query_param_status = 1;
958 auto length_str = std::to_string(length);
959 resourceplusopaque.append("oss.asize=");
960 resourceplusopaque.append(length_str.c_str());
961 if (!opaque) {
962 opaque = new XrdOucEnv();
963 }
964 opaque->Put("oss.asize", length_str.c_str());
965 }
966 }
967
969 if (!m_appended_hdr2cgistr && !hdr2cgistr.empty()) {
970 if (query_param_status == 0) {
971 query_param_status = strchr(resourceplusopaque.c_str(), '?') ? 1 : 2;
972 }
973 resourceplusopaque.append((query_param_status == 1) ? '&' : '?');
974
975 std::string hdr2cgistrEncoded = encode_opaque(hdr2cgistr);
976 resourceplusopaque.append(hdr2cgistrEncoded.c_str());
977 if (TRACING(TRACE_DEBUG)) {
978 // The obfuscation of "authz" will only be done if the server http.header2cgi config contains something that maps a header to this "authz" cgi.
979 // Unfortunately the obfuscation code will be called no matter what is configured in http.header2cgi.
980 std::string header2cgistrObf = obfuscateAuth(hdr2cgistr);
981
982 TRACEI(DEBUG, "Appended header fields to opaque info: '"
983 << header2cgistrObf.c_str() << "'");
984
985 }
986
988 }
989
990 // Verify if we have an external handler for this request
991 if (reqstate == 0) {
992 XrdHttpExtHandler *exthandler = prot->FindMatchingExtHandler(*this);
993 if (exthandler) {
994 XrdHttpExtReq xreq(this, prot);
995 int r = exthandler->ProcessReq(xreq);
996 reset();
997 if (!r) return 1; // All went fine, response sent
998 if (r < 0) return -1; // There was a hard error... close the connection
999
1000 return 1; // There was an error and a response was sent
1001 }
1002 }
1003
1004 //
1005 // Here we process the request locally
1006 //
1007
1008 switch (request) {
1010 return -1;
1013 generateWebdavErrMsg();
1014 prot->SendSimpleResp(400, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), false);
1015 reset();
1016 return -1;
1017 }
1018 case XrdHttpReq::rtHEAD:
1019 {
1020 if (reqstate == 0) {
1021 // Always start with Stat; in the case of a checksum request, we'll have a follow-up query
1022 if (prot->doStat((char *) resourceplusopaque.c_str())) {
1023 prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run request.", 0, false);
1024 return -1;
1025 }
1026 return 0;
1027 } else {
1028 // Note that doChksum requires that the memory stays alive until the callback is invoked.
1029 int prepareCksum = prepareChecksumQuery(m_req_cksum, m_resource_with_digest);
1030 if(prepareCksum < 0) {
1031 return -1;
1032 }
1033 if (prot->doChksum(m_resource_with_digest) < 0) {
1034 // In this case, the Want-Digest header was set and PostProcess gave the go-ahead to do a checksum.
1035 prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to create initial checksum request.", 0, false);
1036 return -1;
1037 }
1038 return 1;
1039 }
1040 }
1041 case XrdHttpReq::rtGET:
1042 {
1043 int retval = keepalive ? 1 : -1; // reset() clears keepalive
1044
1045 if (resource.beginswith("/static/")) {
1046
1047 // This is a request for a /static resource
1048 // If we have to use the embedded ones then we return the ones in memory as constants
1049
1050 // The sysadmin can always redirect the request to another host that
1051 // contains his static resources
1052
1053 // We also allow xrootd to preread from the local disk all the files
1054 // that have to be served as static resources.
1055
1056 if (prot->embeddedstatic) {
1057
1058 // Default case: the icon and the css of the HTML rendering of XrdHttp
1059 if (resource == "/static/css/xrdhttp.css") {
1060 prot->SendSimpleResp(200, NULL, NULL, (char *) static_css_xrdhttp_css, static_css_xrdhttp_css_len, keepalive);
1061 reset();
1062 return retval;
1063 }
1064 if (resource == "/static/icons/xrdhttp.ico") {
1065 prot->SendSimpleResp(200, NULL, NULL, (char *) favicon_ico, favicon_ico_len, keepalive);
1066 reset();
1067 return retval;
1068 }
1069
1070 }
1071
1072 // If we are here then none of the embedded resources match (or they are disabled)
1073 // We may have to redirect to a host that is supposed to serve the static resources
1074 if (prot->staticredir) {
1075
1076 XrdOucString s = "Location: ";
1077 s.append(prot->staticredir);
1078
1079 if (s.endswith('/'))
1080 s.erasefromend(1);
1081
1082 s.append(encode_str(std::string(resource.c_str())).c_str());
1083 appendOpaque(s, 0, 0, 0);
1084
1085 prot->SendSimpleResp(302, NULL, (char *) s.c_str(), 0, 0, false);
1086 return -1;
1087
1088
1089 } else {
1090
1091 // We lookup the requested path in a hash containing the preread files
1092 if (prot->staticpreload) {
1093 XrdHttpProtocol::StaticPreloadInfo *mydata = prot->staticpreload->Find(resource.c_str());
1094 if (mydata) {
1095 prot->SendSimpleResp(200, NULL, NULL, (char *) mydata->data, mydata->len, keepalive);
1096 reset();
1097 return retval;
1098 }
1099 }
1100
1101 }
1102
1103
1104 }
1105
1106 // The reqstate parameter basically moves us through a simple state machine.
1107 // To optimize things, we start off by opening the file; if it turns out to be a directory, then
1108 // we close the file handle and switch to doing a HTML-based rendering of the directory. This
1109 // avoids needing to always to do "stat" first to determine the next step (since the file-open also
1110 // does a "stat").
1111 // - 0: Perform an open on the resource
1112 // - 1: Perform a checksum request on the resource (only if requested in header; otherwise skipped)
1113 // - 2: Perform a close (for dirlist only)
1114 // - 3: Perform a dirlist.
1115 // - 4+: Reads from file; if at end, perform a close.
1116 switch (reqstate) {
1117 case 0: // Open the path for reading.
1118 {
1119 memset(&xrdreq, 0, sizeof (ClientRequest));
1120 xrdreq.open.requestid = htons(kXR_open);
1121 l = resourceplusopaque.length() + 1;
1122 xrdreq.open.dlen = htonl(l);
1123 xrdreq.open.mode = 0;
1124 xrdreq.open.options = htons(kXR_retstat | kXR_open_read | ((readRangeHandler.getMaxRanges() <= 1) ? kXR_seqio : 0));
1125
1126 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1127 prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run request.", 0, false);
1128 return -1;
1129 }
1130
1131 // Prepare to chunk up the request
1132 writtenbytes = 0;
1133
1134 // We want to be invoked again after this request is finished
1135 return 0;
1136 }
1137 case 1: // Checksum request
1138 if (!(fileflags & kXR_isDir) && (!m_want_digest.empty() || !m_want_repr_digest.empty())) {
1139 // In this case, the Want-Digest or then Want-Repr-Digest header was set.
1140 int prepareCksum = prepareChecksumQuery(m_req_cksum, m_resource_with_digest);
1141 if(prepareCksum < 0) {
1142 return -1;
1143 }
1144 if (prot->doChksum(m_resource_with_digest) < 0) {
1145 prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to start internal checksum request to satisfy Want-Digest or Want-Repr-Digest header.", 0, false);
1146 return -1;
1147 }
1148 return 0;
1149 } else {
1150 TRACEI(DEBUG, "No checksum requested; skipping to request state 2");
1151 reqstate += 1;
1152 }
1153 // fallthrough
1154 case 2: // Close file handle for directory
1155 if ((fileflags & kXR_isDir) && fopened) {
1156 memset(&xrdreq, 0, sizeof (ClientRequest));
1157 xrdreq.close.requestid = htons(kXR_close);
1158 memcpy(xrdreq.close.fhandle, fhandle, 4);
1159
1160 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1161 generateWebdavErrMsg();
1162 return sendFooterError("Could not run close request on the bridge");
1163 }
1164 return 0;
1165 } else {
1166 reqstate += 1;
1167 }
1168 // fallthrough
1169 case 3: // List directory
1170 if (fileflags & kXR_isDir) {
1171 if (prot->listdeny) {
1172 // Return 403 as the administrator forbid the directory listing
1173 prot->SendSimpleResp(403, NULL, NULL, (char *) "Listings are disabled.", 0, false);
1174 return -1;
1175 }
1176
1177 if (prot->listredir) {
1178 XrdOucString s = "Location: ";
1179 s.append(prot->listredir);
1180
1181 if (s.endswith('/'))
1182 s.erasefromend(1);
1183
1184 s.append(encode_str(std::string(resource.c_str())).c_str());
1185 appendOpaque(s, 0, 0, 0);
1186
1187 prot->SendSimpleResp(302, NULL, (char *) s.c_str(), 0, 0, false);
1188 return -1;
1189 }
1190
1191 std::string res;
1192 res = resourceplusopaque.c_str();
1193
1194 // --------- DIRLIST
1195 memset(&xrdreq, 0, sizeof (ClientRequest));
1196 xrdreq.dirlist.requestid = htons(kXR_dirlist);
1197 xrdreq.dirlist.options[0] = kXR_dstat;
1198 l = res.length() + 1;
1199 xrdreq.dirlist.dlen = htonl(l);
1200
1201 if (!prot->Bridge->Run((char *) &xrdreq, (char *) res.c_str(), l)) {
1202 generateWebdavErrMsg();
1203 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), false);
1204 sendFooterError("Could not run listing request on the bridge");
1205 return -1;
1206 }
1207
1208 // We don't want to be invoked again after this request is finished
1209 return 1;
1210 }
1211 else {
1212 reqstate += 1;
1213 }
1214 // fallthrough
1215 case 4:
1216 {
1217 auto retval = ReturnGetHeaders();
1218 if (retval) {
1219 return retval;
1220 }
1221 }
1222 // fallthrough
1223 default: // Read() or Close(); reqstate is 4+
1224 {
1225 const XrdHttpIOList &readChunkList = readRangeHandler.NextReadList();
1226
1227 // Close() if we have finished, otherwise read the next chunk
1228
1229 // --------- CLOSE
1230 if ( closeAfterError || readChunkList.empty() )
1231 {
1232
1233 memset(&xrdreq, 0, sizeof (ClientRequest));
1234 xrdreq.close.requestid = htons(kXR_close);
1235 memcpy(xrdreq.close.fhandle, fhandle, 4);
1236
1237 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1238 TRACEI(REQ, " Failed to run close request on the bridge.");
1239 // Note: we have already completed the request and sent the data to the client.
1240 // Hence, there's no need to send an error. However, since the bridge is potentially
1241 // in a bad state, we close the TCP socket to force the client to reconnect.
1242 return -1;
1243 }
1244
1245 // We have finished
1246 readClosing = true;
1247 return 1;
1248
1249 }
1250 // --------- READ or READV
1251
1252 if ( readChunkList.size() == 1 ) {
1253 // Use a read request for single range
1254
1255 long l;
1256 long long offs;
1257
1258 // --------- READ
1259 memset(&xrdreq, 0, sizeof (xrdreq));
1260 xrdreq.read.requestid = htons(kXR_read);
1261 memcpy(xrdreq.read.fhandle, fhandle, 4);
1262 xrdreq.read.dlen = 0;
1263
1264 offs = readChunkList[0].offset;
1265 l = readChunkList[0].size;
1266
1267 xrdreq.read.offset = htonll(offs);
1268 xrdreq.read.rlen = htonl(l);
1269
1270 // If we are using HTTPS or if the client requested trailers, or if the
1271 // read concerns a multirange reponse, disable sendfile
1272 // (in the latter two cases, the extra framing is only done in PostProcessHTTPReq)
1273 if (prot->ishttps || (m_transfer_encoding_chunked && m_trailer_headers) ||
1274 !readRangeHandler.isSingleRange()) {
1275 if (!prot->Bridge->setSF((kXR_char *) fhandle, false)) {
1276 TRACE(REQ, " XrdBridge::SetSF(false) failed.");
1277
1278 }
1279 }
1280
1281
1282
1283 if (l <= 0) {
1284 if (l < 0) {
1285 TRACE(ALL, " Data sizes mismatch.");
1286 return -1;
1287 }
1288 else {
1289 TRACE(ALL, " No more bytes to send.");
1290 reset();
1291 return 1;
1292 }
1293 }
1294
1295 if ((offs >= filesize) || (offs+l > filesize)) {
1296 httpStatusCode = 416;
1297 httpErrorBody = "Range Not Satisfiable";
1298 std::stringstream ss;
1299 ss << "Requested range " << l << "@" << offs << " is past the end of file (" << filesize << ")";
1300 return sendFooterError(ss.str());
1301 }
1302
1303 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1304 generateWebdavErrMsg();
1305 return sendFooterError("Could not run read request on the bridge");
1306 }
1307 } else {
1308 // --------- READV
1309
1310 length = ReqReadV(readChunkList);
1311
1312 if (!prot->Bridge->Run((char *) &xrdreq, (char *) &ralist[0], length)) {
1313 generateWebdavErrMsg();
1314 return sendFooterError("Could not run ReadV request on the bridge");
1315 }
1316
1317 }
1318
1319 // We want to be invoked again after this request is finished
1320 return 0;
1321 } // case 3+
1322
1323 } // switch (reqstate)
1324
1325
1326 } // case XrdHttpReq::rtGET
1327
1328 case XrdHttpReq::rtPUT:
1329 {
1330 //if (prot->ishttps) {
1331 //prot->SendSimpleResp(501, NULL, NULL, (char *) "HTTPS not supported yet for direct writing. Sorry.", 0);
1332 //return -1;
1333 //}
1334
1335 if (!fopened) {
1336
1337 // --------- OPEN for write!
1338 memset(&xrdreq, 0, sizeof (ClientRequest));
1339 xrdreq.open.requestid = htons(kXR_open);
1340 l = resourceplusopaque.length() + 1;
1341 xrdreq.open.dlen = htonl(l);
1342 xrdreq.open.mode = htons(kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);
1343 if (! XrdHttpProtocol::usingEC)
1344 xrdreq.open.options = htons(kXR_mkpath | kXR_open_wrto | kXR_delete);
1345 else
1346 xrdreq.open.options = htons(kXR_mkpath | kXR_open_wrto | kXR_new);
1347
1348 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1349 prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run request.", 0, keepalive);
1350 return -1;
1351 }
1352
1353
1354 // We want to be invoked again after this request is finished
1355 // Only if there is data to fetch from the socket or there will
1356 // never be more data
1357 if (prot->BuffUsed() > 0 || (length == 0 && !sendcontinue))
1358 return 0;
1359
1360 return 1;
1361
1362 } else {
1363
1364 if (m_transfer_encoding_chunked) {
1365 if (m_current_chunk_size == m_current_chunk_offset) {
1366 // Chunk has been consumed; we now must process the CRLF.
1367 // Note that we don't support trailer headers.
1368 if (prot->BuffUsed() < 2) return 1;
1369 if (prot->myBuffStart[0] != '\r' || prot->myBuffStart[1] != '\n') {
1370 prot->SendSimpleResp(400, NULL, NULL, (char *) "Invalid trailing chunk encoding.", 0, keepalive);
1371 return -1;
1372 }
1373 prot->BuffConsume(2);
1374 if (m_current_chunk_size == 0) {
1375 // All data has been sent. Turn off chunk processing and
1376 // set the bytes written and length appropriately; on next callback,
1377 // we will hit the close() block below.
1378 m_transfer_encoding_chunked = false;
1380 return ProcessHTTPReq();
1381 }
1382 m_current_chunk_size = -1;
1383 m_current_chunk_offset = 0;
1384 // If there is more data, we try to process the next chunk; otherwise, return
1385 if (!prot->BuffUsed()) return 1;
1386 }
1387 if (-1 == m_current_chunk_size) {
1388
1389 // Parse out the next chunk size.
1390 long long idx = 0;
1391 bool found_newline = false;
1392 // Set a maximum size of chunk we will allow
1393 // Nginx sets this to "NGX_MAX_OFF_T_VALUE", which is 9223372036854775807 (a some crazy number)
1394 // We set it to 1TB, which is 1099511627776
1395 // This is to prevent a malicious client from sending a very large chunk size
1396 // or a malformed chunk request.
1397 // 1TB in base-16 is 0x40000000000, so only allow 11 characters, plus the CRLF
1398 long long max_chunk_size_chars = std::min(static_cast<long long>(prot->BuffUsed()), static_cast<long long>(13));
1399 for (; idx < max_chunk_size_chars; idx++) {
1400 if (prot->myBuffStart[idx] == '\n') {
1401 found_newline = true;
1402 break;
1403 }
1404 }
1405 // If we found a new line, but it is the first character in the buffer (no chunk length)
1406 // or if the previous character is not a CR.
1407 if (found_newline && ((idx == 0) || prot->myBuffStart[idx-1] != '\r')) {
1408 prot->SendSimpleResp(400, NULL, NULL, (char *)"Invalid chunked encoding", 0, false);
1409 TRACE(REQ, "XrdHTTP PUT: Sending invalid chunk encoding. Start of chunk should have had a length, followed by a CRLF.");
1410 return -1;
1411 }
1412 if (found_newline) {
1413 char *endptr = NULL;
1414 std::string line_contents(prot->myBuffStart, idx);
1415 long long chunk_contents = strtoll(line_contents.c_str(), &endptr, 16);
1416 // Chunk sizes can be followed by trailer information or CRLF
1417 if (*endptr != ';' && *endptr != '\r') {
1418 prot->SendSimpleResp(400, NULL, NULL, (char *)"Invalid chunked encoding", 0, false);
1419 TRACE(REQ, "XrdHTTP PUT: Sending invalid chunk encoding. Chunk size was not followed by a ';' or CR." << __LINE__);
1420 return -1;
1421 }
1422 m_current_chunk_size = chunk_contents;
1423 m_current_chunk_offset = 0;
1424 prot->BuffConsume(idx + 1);
1425 TRACE(REQ, "XrdHTTP PUT: next chunk from client will be " << m_current_chunk_size << " bytes");
1426 } else {
1427 // Need more data!
1428 return 1;
1429 }
1430 }
1431
1432 if (m_current_chunk_size == 0) {
1433 // All data has been sent. Invoke this routine again immediately to process CRLF
1434 return ProcessHTTPReq();
1435 } else {
1436 // At this point, we have a chunk size defined and should consume payload data
1437 memset(&xrdreq, 0, sizeof (xrdreq));
1438 xrdreq.write.requestid = htons(kXR_write);
1439 memcpy(xrdreq.write.fhandle, fhandle, 4);
1440
1441 long long chunk_bytes_remaining = m_current_chunk_size - m_current_chunk_offset;
1442 long long bytes_to_write = std::min(static_cast<long long>(prot->BuffUsed()),
1443 chunk_bytes_remaining);
1444
1445 xrdreq.write.offset = htonll(writtenbytes);
1446 xrdreq.write.dlen = htonl(bytes_to_write);
1447
1448 TRACEI(REQ, "XrdHTTP PUT: Writing chunk of size " << bytes_to_write << " starting with '" << *(prot->myBuffStart) << "'" << " with " << chunk_bytes_remaining << " bytes remaining in the chunk");
1449 if (!prot->Bridge->Run((char *) &xrdreq, prot->myBuffStart, bytes_to_write)) {
1450 generateWebdavErrMsg();
1451 return sendFooterError("Could not run write request on the bridge");
1452 }
1453 // If there are more bytes in the buffer, then immediately call us after the
1454 // write is finished; otherwise, wait for data.
1455 return (prot->BuffUsed() > chunk_bytes_remaining) ? 0 : 1;
1456 }
1457 } else if (writtenbytes < length) {
1458
1459
1460 // --------- WRITE
1461 memset(&xrdreq, 0, sizeof (xrdreq));
1462 xrdreq.write.requestid = htons(kXR_write);
1463 memcpy(xrdreq.write.fhandle, fhandle, 4);
1464
1465 long long bytes_to_read = std::min(static_cast<long long>(prot->BuffUsed()),
1467
1468 xrdreq.write.offset = htonll(writtenbytes);
1469 xrdreq.write.dlen = htonl(bytes_to_read);
1470
1471 TRACEI(REQ, "Writing " << bytes_to_read);
1472 if (!prot->Bridge->Run((char *) &xrdreq, prot->myBuffStart, bytes_to_read)) {
1473 generateWebdavErrMsg();
1474 return sendFooterError("Could not run write request on the bridge");
1475 }
1476
1477 if (writtenbytes + prot->BuffUsed() >= length)
1478 // Trigger an immediate recall after this request has finished
1479 return 0;
1480 else
1481 // We want to be invoked again after this request is finished
1482 // only if there is pending data
1483 return 1;
1484
1485
1486
1487 } else {
1488
1489 // --------- CLOSE
1490 memset(&xrdreq, 0, sizeof (ClientRequest));
1491 xrdreq.close.requestid = htons(kXR_close);
1492 memcpy(xrdreq.close.fhandle, fhandle, 4);
1493
1494
1495 if (!prot->Bridge->Run((char *) &xrdreq, 0, 0)) {
1496 generateWebdavErrMsg();
1497 return sendFooterError("Could not run close request on the bridge");
1498 }
1499
1500 // We have finished
1501 return 1;
1502
1503 }
1504
1505 }
1506
1507 break;
1508
1509 }
1511 {
1512 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);
1513 bool ret_keepalive = keepalive; // reset() clears keepalive
1514 reset();
1515 return ret_keepalive ? 1 : -1;
1516 }
1518 {
1519
1520
1521 switch (reqstate) {
1522
1523 case 0: // Stat()
1524 {
1525
1526
1527 // --------- STAT is always the first step
1528 memset(&xrdreq, 0, sizeof (ClientRequest));
1529 xrdreq.stat.requestid = htons(kXR_stat);
1530 std::string s = resourceplusopaque.c_str();
1531
1532
1533 l = resourceplusopaque.length() + 1;
1534 xrdreq.stat.dlen = htonl(l);
1535
1536 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1537 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1538 return -1;
1539 }
1540
1541 // We need to be invoked again to complete the request
1542 return 0;
1543 }
1544 default:
1545
1546 if (fileflags & kXR_isDir) {
1547 // --------- RMDIR
1548 memset(&xrdreq, 0, sizeof (ClientRequest));
1549 xrdreq.rmdir.requestid = htons(kXR_rmdir);
1550
1551 std::string s = resourceplusopaque.c_str();
1552
1553 l = s.length() + 1;
1554 xrdreq.rmdir.dlen = htonl(l);
1555
1556 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1557 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run rmdir request.", 0, false);
1558 return -1;
1559 }
1560 } else {
1561 // --------- DELETE
1562 memset(&xrdreq, 0, sizeof (ClientRequest));
1563 xrdreq.rm.requestid = htons(kXR_rm);
1564
1565 std::string s = resourceplusopaque.c_str();
1566
1567 l = s.length() + 1;
1568 xrdreq.rm.dlen = htonl(l);
1569
1570 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1571 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run rm request.", 0, false);
1572 return -1;
1573 }
1574 }
1575
1576
1577 // We don't want to be invoked again after this request is finished
1578 return 1;
1579
1580 }
1581
1582
1583
1584 }
1586 {
1587 prot->SendSimpleResp(501, NULL, NULL, (char *) "Request not supported yet.", 0, false);
1588
1589 return -1;
1590 }
1592 {
1593
1594
1595
1596 switch (reqstate) {
1597
1598 case 0: // Stat() and add the current item to the list of the things to send
1599 {
1600
1601 if (length > 0) {
1602 TRACE(REQ, "Reading request body " << length << " bytes.");
1603 char *p = 0;
1604 // We have to specifically read all the request body
1605
1606 if (prot->BuffgetData(length, &p, true) < length) {
1607 prot->SendSimpleResp(501, NULL, NULL, (char *) "Error in getting the PROPFIND request body.", 0, false);
1608 return -1;
1609 }
1610
1611 if ((depth > 1) || (depth < 0)) {
1612 prot->SendSimpleResp(501, NULL, NULL, (char *) "Invalid depth value.", 0, false);
1613 return -1;
1614 }
1615
1616
1617 parseBody(p, length);
1618 }
1619
1620
1621 // --------- STAT is always the first step
1622 memset(&xrdreq, 0, sizeof (ClientRequest));
1623 xrdreq.stat.requestid = htons(kXR_stat);
1624 std::string s = resourceplusopaque.c_str();
1625
1626
1627 l = resourceplusopaque.length() + 1;
1628 xrdreq.stat.dlen = htonl(l);
1629
1630 if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) {
1631 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1632 return -1;
1633 }
1634
1635
1636 if (depth == 0) {
1637 // We don't need to be invoked again
1638 return 1;
1639 } else
1640 // We need to be invoked again to complete the request
1641 return 0;
1642
1643
1644
1645 break;
1646 }
1647
1648 default: // Dirlist()
1649 {
1650
1651 // --------- DIRLIST
1652 memset(&xrdreq, 0, sizeof (ClientRequest));
1653 xrdreq.dirlist.requestid = htons(kXR_dirlist);
1654
1655 std::string s = resourceplusopaque.c_str();
1656 xrdreq.dirlist.options[0] = kXR_dstat;
1657 //s += "?xrd.dirstat=1";
1658
1659 l = s.length() + 1;
1660 xrdreq.dirlist.dlen = htonl(l);
1661
1662 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1663 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1664 return -1;
1665 }
1666
1667 // We don't want to be invoked again after this request is finished
1668 return 1;
1669 }
1670 }
1671
1672
1673 break;
1674 }
1676 {
1677
1678 // --------- MKDIR
1679 memset(&xrdreq, 0, sizeof (ClientRequest));
1680 xrdreq.mkdir.requestid = htons(kXR_mkdir);
1681
1682 std::string s = resourceplusopaque.c_str();
1683 xrdreq.mkdir.options[0] = (kXR_char) kXR_mkdirpath;
1684
1685 l = s.length() + 1;
1686 xrdreq.mkdir.dlen = htonl(l);
1687
1688 if (!prot->Bridge->Run((char *) &xrdreq, (char *) s.c_str(), l)) {
1689 prot->SendSimpleResp(501, NULL, NULL, (char *) "Could not run request.", 0, false);
1690 return -1;
1691 }
1692
1693 // We don't want to be invoked again after this request is finished
1694 return 1;
1695 }
1696 case XrdHttpReq::rtMOVE:
1697 {
1698 // Skip the protocol part of destination URL
1699 size_t skip = destination.find("://");
1700 skip = (skip == std::string::npos) ? 0 : skip + 3;
1701
1702 // If we have a manager role, enforce source and destination are on the same host
1703 if (prot->myRole == kXR_isManager && destination.compare(skip, host.size(), host) != 0) {
1704 prot->SendSimpleResp(501, NULL, NULL, (char *) "Only in-place renaming is supported for MOVE.", 0, false);
1705 return -1;
1706 }
1707
1708 // If needed, append opaque info from source onto destination
1709 int pos = resourceplusopaque.find("?");
1710 if (pos != STR_NPOS) {
1711 destination.append((destination.find("?") == std::string::npos) ? "?" : "&");
1712 destination.append(resourceplusopaque.c_str() + pos + 1);
1713 }
1714
1715 size_t path_pos = destination.find('/', skip + 1);
1716
1717 if (path_pos == std::string::npos) {
1718 prot->SendSimpleResp(400, NULL, NULL, (char *) "Cannot determine destination path", 0, false);
1719 return -1;
1720 }
1721
1722 // Construct args to kXR_mv request (i.e. <src> + " " + <dst>)
1723 std::string mv_args = std::string(resourceplusopaque.c_str()) + " " + destination.substr(path_pos);
1724
1725 l = mv_args.length() + 1;
1726
1727 // Prepare and run kXR_mv request
1728 memset(&xrdreq, 0, sizeof (ClientRequest));
1729 xrdreq.mv.requestid = htons(kXR_mv);
1730 xrdreq.mv.arg1len = htons(resourceplusopaque.length());
1731 xrdreq.mv.dlen = htonl(l);
1732
1733 if (!prot->Bridge->Run((char *) &xrdreq, (char *) mv_args.c_str(), l)) {
1734 prot->SendSimpleResp(500, NULL, NULL, (char *) "Could not run request.", 0, false);
1735 return -1;
1736 }
1737
1738 // We don't want to be invoked again after this request is finished
1739 return 1;
1740 }
1741 default:
1742 {
1743 prot->SendSimpleResp(501, NULL, NULL, (char *) "Request not supported.", 0, false);
1744 return -1;
1745 }
1746
1747 }
1748
1749 return 1;
1750}
1751
1752
1753int
1754XrdHttpReq::PostProcessChecksum(std::string &digest_header) {
1755 if (iovN > 0) {
1756 if (xrdresp == kXR_error) {
1757 prot->SendSimpleResp(httpStatusCode, NULL, NULL, "Failed to determine checksum", 0, false);
1758 return -1;
1759 }
1760
1761 TRACEI(REQ, "Checksum for HEAD " << resource.c_str() << " "
1762 << reinterpret_cast<char *>(iovP[0].iov_base) << "="
1763 << reinterpret_cast<char *>(iovP[iovN-1].iov_base));
1764
1765 std::string cksumType {reinterpret_cast<char *>(iovP[0].iov_base),iovP[0].iov_len};
1766 // Remove '\0' from the actual size of the cksumValue which is at the end of iovP[iovN-1].iov_base
1767 size_t cksumValueLen = iovP[iovN-1].iov_len - 1;
1768 std::string cksumValue {reinterpret_cast<char *>(iovP[iovN-1].iov_base), cksumValueLen};
1769 std::string digest_value = cksumValue;
1770
1771 // We convert the byte representation of the checksum to base64 if the checksum needs to be base64 encoded (md5 for example)
1772 // or if the Want-Repr-Digest header was used
1773 bool convert_to_base64 = m_req_cksum->needsBase64Padding() || !m_want_repr_digest.empty();
1774 if (convert_to_base64) {
1775 std::vector<uint8_t> digest_binary_value;
1776 if (!Fromhexdigest(cksumValue,digest_binary_value)) {
1777 prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to convert checksum hexdigest to base64.", 0, false);
1778 return -1;
1779 }
1780 Tobase64(digest_binary_value,digest_value);
1781 }
1782
1783 if(m_want_repr_digest.empty()) {
1784 digest_header = "Digest: ";
1785 digest_header += m_req_cksum->getHttpName();
1786 digest_header += "=";
1787 digest_header += digest_value;
1788 } else {
1789 digest_header = "Repr-Digest: ";
1790 digest_header += m_req_cksum->getHttpName();
1791 digest_header += "=:";
1792 digest_header += digest_value;
1793 digest_header += ":";
1794 }
1795
1796 return 0;
1797 } else {
1798 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), false);
1799 return -1;
1800 }
1801}
1802
1803int
1804XrdHttpReq::PostProcessListing(bool final_) {
1805
1806 if (xrdresp == kXR_error) {
1807 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
1808 httpErrorBody.c_str(), httpErrorBody.length(), false);
1809 return -1;
1810 }
1811
1812 if (stringresp.empty()) {
1813 // Start building the HTML response
1814 stringresp = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
1815 "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
1816 "<head>\n"
1817 "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>\n"
1818 "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/css/xrdhttp.css\"/>\n"
1819 "<link rel=\"icon\" type=\"image/png\" href=\"/static/icons/xrdhttp.ico\"/>\n";
1820
1821 stringresp += "<title>";
1822 stringresp += resource.c_str();
1823 stringresp += "</title>\n";
1824
1825 stringresp += "</head>\n"
1826 "<body>\n";
1827
1828 char *estr = escapeXML(resource.c_str());
1829
1830 stringresp += "<h1>Listing of: ";
1831 stringresp += estr;
1832 stringresp += "</h1>\n";
1833
1834 free(estr);
1835
1836 stringresp += "<div id=\"header\">";
1837
1838 stringresp += "<table id=\"ft\">\n"
1839 "<thead><tr>\n"
1840 "<th class=\"mode\">Mode</th>"
1841 "<th class=\"flags\">Flags</th>"
1842 "<th class=\"size\">Size</th>"
1843 "<th class=\"datetime\">Modified</th>"
1844 "<th class=\"name\">Name</th>"
1845 "</tr></thead>\n";
1846 }
1847
1848 // Now parse the answer building the entries vector
1849 if (iovN > 0) {
1850 char *startp = (char *) iovP[0].iov_base, *endp = 0;
1851 char entry[1024];
1852 DirListInfo e;
1853 while ( (size_t)(startp - (char *) iovP[0].iov_base) < (size_t)( iovP[0].iov_len - 1) ) {
1854 // Find the filename, it comes before the \n
1855 if ((endp = (char *) strchr((const char*) startp, '\n'))) {
1856 strncpy(entry, (char *) startp, endp - startp);
1857 entry[endp - startp] = 0;
1858 e.path = entry;
1859
1860 endp++;
1861
1862 // Now parse the stat info
1863 TRACEI(REQ, "Dirlist " << resource.c_str() << " entry=" << entry
1864 << " stat=" << endp);
1865
1866 long dummyl;
1867 sscanf(endp, "%ld %lld %ld %ld",
1868 &dummyl,
1869 &e.size,
1870 &e.flags,
1871 &e.modtime);
1872 } else
1873 strcpy(entry, (char *) startp);
1874
1875 if (e.path.length() && (e.path != ".") && (e.path != "..")) {
1876 // The entry is filled. <td class="ft-file"><a href="file1.txt">file1.txt</a></td>
1877 std::string p = "<tr>"
1878 "<td class=\"mode\">";
1879
1880 if (e.flags & kXR_isDir) p += "d";
1881 else p += "-";
1882
1883 if (e.flags & kXR_other) p += "o";
1884 else p += "-";
1885
1886 if (e.flags & kXR_offline) p += "O";
1887 else p += "-";
1888
1889 if (e.flags & kXR_readable) p += "r";
1890 else p += "-";
1891
1892 if (e.flags & kXR_writable) p += "w";
1893 else p += "-";
1894
1895 if (e.flags & kXR_xset) p += "x";
1896 else p += "-";
1897
1898 p += "</td>";
1899 p += "<td class=\"mode\">" + itos(e.flags) + "</td>"
1900 "<td class=\"size\">" + itos(e.size) + "</td>"
1901 "<td class=\"datetime\">" + ISOdatetime(e.modtime) + "</td>"
1902 "<td class=\"name\">"
1903 "<a href=\"";
1904
1905 if (resource != "/") {
1906
1907 char *estr = escapeXML(resource.c_str());
1908
1909 p += estr;
1910 if (!p.empty() && p[p.size() - 1] != '/')
1911 p += "/";
1912
1913 free(estr);
1914 }
1915 std::unique_ptr<char, decltype(&free)> estr(escapeXML(e.path.c_str()), &free);
1916 p += estr.get();
1917 if (e.flags & kXR_isDir) p += "/";
1918 p += "\">";
1919 p += estr.get();
1920 if (e.flags & kXR_isDir) p += "/";
1921 p += "</a></td></tr>";
1922
1923 stringresp += p;
1924 }
1925
1926 if (endp) {
1927 char *pp = (char *)strchr((const char *)endp, '\n');
1928 if (pp) startp = pp+1;
1929 else break;
1930 } else break;
1931
1932 }
1933 }
1934
1935 // If this was the last bunch of entries, send the buffer and empty it immediately
1936 if (final_) {
1937 stringresp += "</table></div><br><br><hr size=1>"
1938 "<p><span id=\"requestby\">Request by ";
1939
1940 if (prot->SecEntity.name)
1941 stringresp += prot->SecEntity.name;
1942 else
1943 stringresp += prot->Link->ID;
1944
1945 if (prot->SecEntity.vorg ||
1946 prot->SecEntity.name ||
1947 prot->SecEntity.moninfo ||
1948 prot->SecEntity.role)
1949 stringresp += " (";
1950
1951 if (prot->SecEntity.vorg) {
1952 stringresp += " VO: ";
1953 stringresp += prot->SecEntity.vorg;
1954 }
1955
1956 if (prot->SecEntity.moninfo) {
1957 stringresp += " DN: ";
1958 stringresp += prot->SecEntity.moninfo;
1959 } else
1960 if (prot->SecEntity.name) {
1961 stringresp += " DN: ";
1962 stringresp += prot->SecEntity.name;
1963 }
1964
1965 if (prot->SecEntity.role) {
1966 stringresp += " Role: ";
1967 stringresp += prot->SecEntity.role;
1968 if (prot->SecEntity.endorsements) {
1969 stringresp += " (";
1970 stringresp += prot->SecEntity.endorsements;
1971 stringresp += ") ";
1972 }
1973 }
1974
1975 if (prot->SecEntity.vorg ||
1976 prot->SecEntity.moninfo ||
1977 prot->SecEntity.role)
1978 stringresp += " )";
1979
1980 if (prot->SecEntity.host) {
1981 stringresp += " ( ";
1982 stringresp += prot->SecEntity.host;
1983 stringresp += " )";
1984 }
1985
1986 stringresp += "</span></p>\n";
1987 stringresp += "<p>Powered by XrdHTTP ";
1988 stringresp += XrdVSTRING;
1989 stringresp += " (CERN IT-SDC)</p>\n";
1990
1991 prot->SendSimpleResp(200, NULL, NULL, (char *) stringresp.c_str(), 0, keepalive);
1992 stringresp.clear();
1993 return keepalive ? 1 : -1;
1994 }
1995
1996 return 0;
1997}
1998
1999int
2000XrdHttpReq::ReturnGetHeaders() {
2001 std::string responseHeader;
2002 if (!m_digest_header.empty()) {
2003 responseHeader = m_digest_header;
2004 }
2005 if (fileflags & kXR_cachersp) {
2006 if (!responseHeader.empty()) {
2007 responseHeader += "\r\n";
2008 }
2009 addAgeHeader(responseHeader);
2010 }
2011
2012 const XrdHttpReadRangeHandler::UserRangeList &uranges = readRangeHandler.ListResolvedRanges();
2013 if (uranges.empty() && readRangeHandler.getError()) {
2014 prot->SendSimpleResp(readRangeHandler.getError().httpRetCode, NULL, NULL, readRangeHandler.getError().errMsg.c_str(),0,false);
2015 return -1;
2016 }
2017
2018 if (readRangeHandler.isFullFile()) {
2019 // Full file.
2020 TRACEI(REQ, "Sending full file: " << filesize);
2021 if (m_transfer_encoding_chunked && m_trailer_headers) {
2022 setTransferStatusHeader(responseHeader);
2023 prot->StartChunkedResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), -1, keepalive);
2024 } else {
2025 prot->SendSimpleResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), NULL, filesize, keepalive);
2026 }
2027 return 0;
2028 }
2029
2030 if (readRangeHandler.isSingleRange()) {
2031 // Possibly with zero sized file but should have been included
2032 // in the FullFile case above
2033 if (uranges.size() != 1)
2034 return -1;
2035
2036 // Only one range to return to the user
2037 char buf[64];
2038 const off_t cnt = uranges[0].end - uranges[0].start + 1;
2039
2040 std::string header = "Content-Range: bytes ";
2041 sprintf(buf, "%lld-%lld/%lld", (long long int)uranges[0].start, (long long int)uranges[0].end, filesize);
2042 header += buf;
2043 if (!responseHeader.empty()) {
2044 header += "\r\n";
2045 header += responseHeader.c_str();
2046 }
2047
2048 if (m_transfer_encoding_chunked && m_trailer_headers) {
2050 prot->StartChunkedResp(206, NULL, header.empty() ? nullptr : header.c_str(), -1, keepalive);
2051 } else {
2052 prot->SendSimpleResp(206, NULL, header.empty() ? nullptr : header.c_str(), NULL, cnt, keepalive);
2053 }
2054 return 0;
2055 }
2056
2057 // Multiple reads to perform, compose and send the header
2058 off_t cnt = 0;
2059 for (auto &ur : uranges) {
2060 cnt += ur.end - ur.start + 1;
2061
2062 cnt += buildPartialHdr(ur.start,
2063 ur.end,
2064 filesize,
2065 (char *) "123456").size();
2066
2067 }
2068 cnt += buildPartialHdrEnd((char *) "123456").size();
2069 std::string header = "Content-Type: multipart/byteranges; boundary=123456";
2070 if (!m_digest_header.empty()) {
2071 header += "\n";
2072 header += m_digest_header;
2073 }
2074 if (fileflags & kXR_cachersp) {
2075 if (!header.empty()) {
2076 header += "\r\n";
2077 }
2078 addAgeHeader(header);
2079 }
2080
2081 if (m_transfer_encoding_chunked && m_trailer_headers) {
2083 prot->StartChunkedResp(206, NULL, header.c_str(), -1, keepalive);
2084 } else {
2085 prot->SendSimpleResp(206, NULL, header.c_str(), NULL, cnt, keepalive);
2086 }
2087 return 0;
2088}
2089
2090void XrdHttpReq::setTransferStatusHeader(std::string &header) {
2091 if (m_status_trailer) {
2092 if (header.empty()) {
2093 header += "Trailer: X-Transfer-Status";
2094 } else {
2095 header += "\r\nTrailer: X-Transfer-Status";
2096 }
2097 }
2098}
2099
2100// This is invoked by the callbacks, after something has happened in the bridge
2101
2102int XrdHttpReq::PostProcessHTTPReq(bool final_) {
2103
2104 TRACEI(REQ, "PostProcessHTTPReq req: " << request << " reqstate: " << reqstate << " final_:" << final_);
2105 generateWebdavErrMsg();
2106
2107 if(xrdreq.set.requestid == htons(kXR_set)) {
2108 // We have set the user agent, if it fails we return a 500 error, otherwise the callback is successful --> we continue
2109 if(xrdresp != kXR_ok) {
2110 prot->SendSimpleResp(500, nullptr, nullptr, "Could not set user agent.", 0, false);
2111 return -1;
2112 }
2113 return 0;
2114 }
2115
2116 switch (request) {
2118 {
2119 prot->SendSimpleResp(400, NULL, NULL, (char *) "Request malformed 1", 0, false);
2120 return -1;
2121 }
2123 {
2124 prot->SendSimpleResp(400, NULL, NULL, (char *) "Request malformed 2", 0, false);
2125 return -1;
2126 }
2127 case XrdHttpReq::rtHEAD:
2128 {
2129 if (xrdresp != kXR_ok) {
2130 // NOTE that HEAD MUST NOT return a body, even in the case of failure.
2131 prot->SendSimpleResp(httpStatusCode, NULL, NULL, NULL, 0, false);
2132 return -1;
2133 } else if (reqstate == 0) {
2134 if (iovN > 0) {
2135 std::string response_headers;
2136
2137 // Now parse the stat info
2138 TRACEI(REQ, "Stat for HEAD " << resource.c_str()
2139 << " stat=" << (char *) iovP[0].iov_base);
2140
2141 sscanf((const char *) iovP[0].iov_base, "%lld %lld %ld %ld",
2142 &etagval,
2143 &filesize,
2144 &fileflags,
2145 &filemodtime);
2146
2147 if (m_want_digest.size() || m_want_repr_digest.size()) {
2148 return 0;
2149 } else {
2150 if (fileflags & kXR_cachersp) {
2151 addAgeHeader(response_headers);
2152 response_headers += "\r\n";
2153 }
2154
2155 addETagHeader(response_headers);
2156 response_headers += "\r\n";
2157
2158 response_headers += "Accept-Ranges: bytes";
2159 prot->SendSimpleResp(200, NULL, response_headers.c_str(), NULL, filesize, keepalive);
2160 return keepalive ? 1 : -1;
2161 }
2162 }
2163
2164 prot->SendSimpleResp(httpStatusCode, NULL, NULL, NULL, 0, keepalive);
2165 bool ret_keepalive = keepalive; // reset() clears keepalive
2166 reset();
2167 return ret_keepalive ? 1 : -1;
2168 } else { // We requested a checksum and now have its response.
2169 if (iovN > 0) {
2170 std::string response_headers;
2171 int response = PostProcessChecksum(response_headers);
2172 if (-1 == response) {
2173 return -1;
2174 }
2175 if (!response_headers.empty()) {response_headers += "\r\n";}
2176 if (fileflags & kXR_cachersp) {
2177 addAgeHeader(response_headers);
2178 response_headers += "\r\n";
2179 }
2180 response_headers += "Accept-Ranges: bytes";
2181 prot->SendSimpleResp(200, NULL, response_headers.c_str(), NULL, filesize, keepalive);
2182 return keepalive ? 1 : -1;
2183 } else {
2184 prot->SendSimpleResp(500, NULL, NULL, "Underlying filesystem failed to calculate checksum.", 0, false);
2185 return -1;
2186 }
2187 }
2188 }
2189 case XrdHttpReq::rtGET:
2190 {
2191 // To duplicate the state diagram from the rtGET request state
2192 // - 0: Perform an open request
2193 // - 1: Perform a checksum request on the resource (only if requested in header; otherwise skipped)
2194 // - 2: Perform a close (for directory listings only)
2195 // - 3: Perform a dirlist
2196 // - 4+: Reads from file; if at end, perform a close.
2197 switch (reqstate) {
2198 case 0: // open
2199 {
2200 if (xrdresp == kXR_ok) {
2201 fopened = true;
2202 getfhandle();
2203
2204 // Always try to parse response. In the case of a caching proxy, the open
2205 // will have created the file in cache
2206 if (iovP[1].iov_len > 1) {
2207 TRACEI(REQ, "Stat for GET " << resource.c_str()
2208 << " stat=" << (char *) iovP[1].iov_base);
2209
2210 long dummyl;
2211 sscanf((const char *) iovP[1].iov_base, "%ld %lld %ld %ld",
2212 &dummyl,
2213 &filesize,
2214 &fileflags,
2215 &filemodtime);
2216
2217 // If this is a directory, bail out early; we will close the file handle
2218 // and then issue a directory listing.
2219 if (fileflags & kXR_isDir) {
2220 return 0;
2221 }
2222
2223 readRangeHandler.SetFilesize(filesize);
2224
2225 // As above: if the client specified a response size, we use that.
2226 // Otherwise, utilize the filesize
2227 if (!length) {
2228 length = filesize;
2229 }
2230 }
2231 else {
2232 TRACEI(ALL, "GET returned no STAT information. Internal error?");
2233 prot->SendSimpleResp(500, NULL, NULL, "Storage system did not return stat info.", 0, false);
2234 return -1;
2235 }
2236 return 0;
2237 } else if (xrderrcode == kXR_isDirectory) { // This is a directory; trigger directory-handling topic.
2239 return 0;
2240 } else { // xrdresp indicates an error occurred
2241
2242 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2243 httpErrorBody.c_str(), httpErrorBody.length(), false);
2244 return -1;
2245 }
2246 // Case should not be reachable
2247 return -1;
2248 } // end open
2249 case 1: // checksum was requested and now we have its response.
2250 {
2251 return PostProcessChecksum(m_digest_header);
2252 }
2253 case 2: // close file handle in case of the directory
2254 {
2255 if (xrdresp != kXR_ok) {
2256 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2257 httpErrorBody.c_str(), httpErrorBody.length(), false);
2258 return -1;
2259 }
2260 return 0;
2261 }
2262 case 3: // handle the directory listing response
2263 {
2264 return PostProcessListing(final_);
2265 }
2266 default: //read or readv, followed by a close.
2267 {
2268 // If we are postprocessing a close, potentially send out informational trailers
2269 if ((ntohs(xrdreq.header.requestid) == kXR_close) || readClosing)
2270 {
2271 // If we already sent out an error, then we cannot send any further
2272 // messages
2273 if (closeAfterError) {
2274 TRACEI(REQ, "Close was completed after an error: " << xrdresp);
2275 return xrdresp != kXR_ok ? -1 : 1;
2276 }
2277
2278 const XrdHttpReadRangeHandler::Error &rrerror = readRangeHandler.getError();
2279 if (rrerror) {
2280 httpStatusCode = rrerror.httpRetCode;
2281 httpErrorBody = rrerror.errMsg;
2282 }
2283
2284 if (m_transfer_encoding_chunked && m_trailer_headers) {
2285 std::string trailer = "X-Transfer-Status: " + std::to_string(httpStatusCode) + ": " + httpErrorBody + "\r\n";
2286
2287 if (prot->ChunkResp(trailer.c_str(), -1)) return -1;
2288 }
2289
2290 if (rrerror) return -1;
2291 return keepalive ? 1 : -1;
2292 }
2293
2294 // On error, we can only send out a message if trailers are enabled and the
2295 // status response in trailer behavior is requested.
2296 if (xrdresp == kXR_error) {
2297 auto rc = sendFooterError("");
2298 if (rc == 1) {
2299 closeAfterError = true;
2300 return 0;
2301 }
2302 return -1;
2303 }
2304
2305
2306 TRACEI(REQ, "Got data vectors to send:" << iovN);
2307
2308 XrdHttpIOList received;
2309 getReadResponse(received);
2310
2311 int rc;
2312 if (readRangeHandler.isSingleRange()) {
2313 rc = sendReadResponseSingleRange(received);
2314 } else {
2315 rc = sendReadResponsesMultiRanges(received);
2316 }
2317 if (rc) {
2318 // make sure readRangeHandler will trigger close
2319 // of file after next NextReadList().
2320 readRangeHandler.NotifyError();
2321 }
2322
2323 return 0;
2324 } // end read or readv
2325
2326 } // switch reqstate
2327 break;
2328 } // case GET
2329
2330 case XrdHttpReq::rtPUT:
2331 {
2332 if (!fopened) {
2333 if (xrdresp != kXR_ok) {
2334 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2335 return -1;
2336 }
2337
2338 getfhandle();
2339 fopened = true;
2340
2341 // We try to completely fill up our buffer before flushing
2342 prot->ResumeBytes = std::min(length - writtenbytes, (long long) prot->BuffAvailable());
2343
2344 if (sendcontinue) {
2345 prot->SendSimpleResp(100, NULL, NULL, 0, 0, keepalive);
2346 return 0;
2347 }
2348
2349 break;
2350 } else {
2351
2352 // If we are here it's too late to send a proper error message...
2353 // However, we decide to send a response anyway before we close the connection
2354 // We are not sure if sending a final response before reading the entire request
2355 if (xrdresp == kXR_error) {
2356 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2357 return -1;
2358 }
2359
2360 if (ntohs(xrdreq.header.requestid) == kXR_write) {
2361 int l = ntohl(xrdreq.write.dlen);
2362
2363 // Consume the written bytes
2364 prot->BuffConsume(ntohl(xrdreq.write.dlen));
2365 writtenbytes += l;
2366
2367 // Update the chunk offset
2368 if (m_transfer_encoding_chunked) {
2369 m_current_chunk_offset += l;
2370 }
2371
2372 // We try to completely fill up our buffer before flushing
2373 prot->ResumeBytes = std::min(length - writtenbytes, (long long) prot->BuffAvailable());
2374
2375 return 0;
2376 }
2377
2378 if (ntohs(xrdreq.header.requestid) == kXR_close) {
2379 if (xrdresp == kXR_ok) {
2380 prot->SendSimpleResp(201, NULL, NULL, (char *)":-)", 0, keepalive);
2381 return keepalive ? 1 : -1;
2382 } else {
2383 prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2384 return -1;
2385 }
2386 }
2387 }
2388
2389
2390
2391
2392
2393 break;
2394 }
2395
2396
2397
2399 {
2400
2401 if (xrdresp != kXR_ok) {
2402 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2403 httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2404 return -1;
2405 }
2406
2407
2408
2409
2410 switch (reqstate) {
2411
2412 case 0: // response to stat()
2413 {
2414 if (iovN > 0) {
2415
2416 // Now parse the stat info
2417 TRACEI(REQ, "Stat for removal " << resource.c_str()
2418 << " stat=" << (char *) iovP[0].iov_base);
2419
2420 long dummyl;
2421 sscanf((const char *) iovP[0].iov_base, "%ld %lld %ld %ld",
2422 &dummyl,
2423 &filesize,
2424 &fileflags,
2425 &filemodtime);
2426 }
2427
2428 return 0;
2429 }
2430 default: // response to rm
2431 {
2432 if (xrdresp == kXR_ok) {
2433 prot->SendSimpleResp(200, NULL, NULL, (char *) ":-)", 0, keepalive);
2434 return keepalive ? 1 : -1;
2435 }
2436 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2437 httpErrorBody.c_str(), httpErrorBody.length(), keepalive);
2438 return -1;
2439 }
2440 }
2441
2442
2443 }
2444
2446 {
2447
2448 if (xrdresp == kXR_error) {
2449 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2450 httpErrorBody.c_str(), httpErrorBody.length(), false);
2451 return -1;
2452 }
2453
2454 switch (reqstate) {
2455
2456 case 0: // response to stat()
2457 {
2458 DirListInfo e;
2459 e.size = 0;
2460 e.flags = 0;
2461
2462 // Now parse the answer building the entries vector
2463 if (iovN > 0) {
2464 e.path = resource.c_str();
2465
2466 // Now parse the stat info
2467 TRACEI(REQ, "Collection " << resource.c_str()
2468 << " stat=" << (char *) iovP[0].iov_base);
2469
2470 long dummyl;
2471 sscanf((const char *) iovP[0].iov_base, "%ld %lld %ld %ld",
2472 &dummyl,
2473 &e.size,
2474 &e.flags,
2475 &e.modtime);
2476
2477 if (e.path.length() && (e.path != ".") && (e.path != "..")) {
2478 /* The entry is filled. */
2479
2480
2481 std::string p;
2482 stringresp += "<D:response xmlns:lp1=\"DAV:\" xmlns:lp2=\"http://apache.org/dav/props/\" xmlns:lp3=\"LCGDM:\">\n";
2483
2484 char *estr = escapeXML(e.path.c_str());
2485
2486 stringresp += "<D:href>";
2487 stringresp += estr;
2488 stringresp += "</D:href>\n";
2489
2490 free(estr);
2491
2492 stringresp += "<D:propstat>\n<D:prop>\n";
2493
2494 // Now add the properties that we have to add
2495
2496 // File size
2497 stringresp += "<lp1:getcontentlength>";
2498 stringresp += itos(e.size);
2499 stringresp += "</lp1:getcontentlength>\n";
2500
2501
2502
2503 stringresp += "<lp1:getlastmodified>";
2505 stringresp += "</lp1:getlastmodified>\n";
2506
2507
2508
2509 if (e.flags & kXR_isDir) {
2510 stringresp += "<lp1:resourcetype><D:collection/></lp1:resourcetype>\n";
2511 stringresp += "<lp1:iscollection>1</lp1:iscollection>\n";
2512 } else {
2513 stringresp += "<lp1:resourcetype/>\n";
2514 stringresp += "<lp1:iscollection>0</lp1:iscollection>\n";
2515 }
2516
2517 if (e.flags & kXR_xset) {
2518 stringresp += "<lp1:executable>T</lp1:executable>\n";
2519 } else {
2520 stringresp += "<lp1:executable>F</lp1:executable>\n";
2521 }
2522
2523
2524
2525 stringresp += "</D:prop>\n<D:status>HTTP/1.1 200 OK</D:status>\n</D:propstat>\n</D:response>\n";
2526
2527
2528 }
2529
2530
2531 }
2532
2533 // If this was the last bunch of entries, send the buffer and empty it immediately
2534 if ((depth == 0) || !(e.flags & kXR_isDir)) {
2535 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";
2536 stringresp.insert(0, s);
2537 stringresp += "</D:multistatus>\n";
2538 prot->SendSimpleResp(207, (char *) "Multi-Status", (char *) "Content-Type: text/xml; charset=\"utf-8\"",
2539 (char *) stringresp.c_str(), stringresp.length(), keepalive);
2540 stringresp.clear();
2541 return keepalive ? 1 : -1;
2542 }
2543
2544 break;
2545 }
2546 default: // response to dirlist()
2547 {
2548
2549
2550 // Now parse the answer building the entries vector
2551 if (iovN > 0) {
2552 char *startp = (char *) iovP[0].iov_base, *endp = 0;
2553 char entry[1024];
2554 DirListInfo e;
2555
2556 while ( (size_t)(startp - (char *) iovP[0].iov_base) < (size_t)(iovP[0].iov_len - 1) ) {
2557 // Find the filename, it comes before the \n
2558 if ((endp = (char *) mystrchrnul((const char*) startp, '\n'))) {
2559 strncpy(entry, (char *) startp, endp - startp);
2560 entry[endp - startp] = 0;
2561 e.path = entry;
2562
2563 endp++;
2564
2565 // Now parse the stat info
2566 TRACEI(REQ, "Dirlist " <<resource.c_str() <<" entry=" <<entry
2567 << " stat=" << endp);
2568
2569 long dummyl;
2570 sscanf(endp, "%ld %lld %ld %ld",
2571 &dummyl,
2572 &e.size,
2573 &e.flags,
2574 &e.modtime);
2575 }
2576
2577
2578 if (e.path.length() && (e.path != ".") && (e.path != "..")) {
2579 /* The entry is filled.
2580
2581 <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/" xmlns:lp3="LCGDM:">
2582 <D:href>/dpm/cern.ch/home/testers2.eu-emi.eu/</D:href>
2583 <D:propstat>
2584 <D:prop>
2585 <lp1:getcontentlength>1</lp1:getcontentlength>
2586 <lp1:getlastmodified>Tue, 01 May 2012 02:42:13 GMT</lp1:getlastmodified>
2587 <lp1:resourcetype>
2588 <D:collection/>
2589 </lp1:resourcetype>
2590 </D:prop>
2591 <D:status>HTTP/1.1 200 OK</D:status>
2592 </D:propstat>
2593 </D:response>
2594 */
2595
2596
2597 std::string p = resource.c_str();
2598 if (*p.rbegin() != '/') p += "/";
2599
2600 p += e.path;
2601
2602 stringresp += "<D:response xmlns:lp1=\"DAV:\" xmlns:lp2=\"http://apache.org/dav/props/\" xmlns:lp3=\"LCGDM:\">\n";
2603
2604 char *estr = escapeXML(p.c_str());
2605 stringresp += "<D:href>";
2606 stringresp += estr;
2607 stringresp += "</D:href>\n";
2608 free(estr);
2609
2610 stringresp += "<D:propstat>\n<D:prop>\n";
2611
2612
2613
2614 // Now add the properties that we have to add
2615
2616 // File size
2617 stringresp += "<lp1:getcontentlength>";
2618 stringresp += itos(e.size);
2619 stringresp += "</lp1:getcontentlength>\n";
2620
2621 stringresp += "<lp1:getlastmodified>";
2623 stringresp += "</lp1:getlastmodified>\n";
2624
2625 if (e.flags & kXR_isDir) {
2626 stringresp += "<lp1:resourcetype><D:collection/></lp1:resourcetype>\n";
2627 stringresp += "<lp1:iscollection>1</lp1:iscollection>\n";
2628 } else {
2629 stringresp += "<lp1:resourcetype/>\n";
2630 stringresp += "<lp1:iscollection>0</lp1:iscollection>\n";
2631 }
2632
2633 if (e.flags & kXR_xset) {
2634 stringresp += "<lp1:executable>T</lp1:executable>\n";
2635 stringresp += "<lp1:iscollection>1</lp1:iscollection>\n";
2636 } else {
2637 stringresp += "<lp1:executable>F</lp1:executable>\n";
2638 }
2639
2640 stringresp += "</D:prop>\n<D:status>HTTP/1.1 200 OK</D:status>\n</D:propstat>\n</D:response>\n";
2641
2642
2643 }
2644
2645
2646
2647 if (endp) {
2648 char *pp = (char *)strchr((const char *)endp, '\n');
2649 if (pp) startp = pp+1;
2650 else break;
2651 } else break;
2652
2653 }
2654 }
2655
2656
2657
2658 // If this was the last bunch of entries, send the buffer and empty it immediately
2659 if (final_) {
2660 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";
2661 stringresp.insert(0, s);
2662 stringresp += "</D:multistatus>\n";
2663 prot->SendSimpleResp(207, (char *) "Multi-Status", (char *) "Content-Type: text/xml; charset=\"utf-8\"",
2664 (char *) stringresp.c_str(), stringresp.length(), keepalive);
2665 stringresp.clear();
2666 return keepalive ? 1 : -1;
2667 }
2668
2669 break;
2670 } // default reqstate
2671 } // switch reqstate
2672
2673
2674 break;
2675
2676 } // case propfind
2677
2679 {
2680
2681 if (xrdresp != kXR_ok) {
2682 if (xrderrcode == kXR_ItExists) {
2683 prot->SendSimpleResp(405, NULL, NULL, (char *) "Method is not allowed; resource already exists.", 0, false);
2684 } else {
2685 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2686 httpErrorBody.c_str(), httpErrorBody.length(), false);
2687 }
2688 return -1;
2689 }
2690
2691 prot->SendSimpleResp(201, NULL, NULL, (char *) ":-)", 0, keepalive);
2692 return keepalive ? 1 : -1;
2693
2694 }
2695 case XrdHttpReq::rtMOVE:
2696 {
2697
2698 if (xrdresp != kXR_ok) {
2699 prot->SendSimpleResp(httpStatusCode, NULL, NULL, (char *) etext.c_str(), 0, false);
2700 return -1;
2701 }
2702
2703 prot->SendSimpleResp(201, NULL, NULL, (char *) ":-)", 0, keepalive);
2704 return keepalive ? 1 : -1;
2705
2706 }
2707
2708 default:
2709 break;
2710
2711 }
2712
2713
2714 switch (xrdresp) {
2715 case kXR_error:
2716 prot->SendSimpleResp(httpStatusCode, NULL, NULL,
2717 httpErrorBody.c_str(), httpErrorBody.length(), false);
2718 return -1;
2719 break;
2720
2721 default:
2722
2723 break;
2724 }
2725
2726
2727 return 0;
2728}
2729
2730int XrdHttpReq::sendFooterError(const std::string &extra_text) {
2731 if (m_transfer_encoding_chunked && m_trailer_headers && m_status_trailer) {
2732 std::stringstream ss;
2733
2734 ss << httpStatusCode;
2735 if (!httpErrorBody.empty()) {
2736 std::string_view statusView(httpErrorBody);
2737 // Remove trailing newline; this is not valid in a trailer value
2738 // and causes incorrect framing of the response, confusing clients.
2739 if (!statusView.empty() && statusView.back() == '\n') {
2740 ss << ": " << statusView.substr(0, statusView.size() - 1);
2741 } else {
2742 ss << ": " << httpErrorBody;
2743 }
2744 }
2745
2746 if (!extra_text.empty()) ss << ": " << extra_text;
2747 TRACEI(REQ, ss.str());
2748 ss << "\r\n";
2749
2750 const std::string trailer = "X-Transfer-Status: " + ss.str();
2751
2752 // delegate everything to ChunkResp (bodylen==-1 means trailers)
2753 if (prot->ChunkResp(trailer.c_str(), -1)) return -1;
2754
2755 return keepalive ? 1 : -1;
2756 } else {
2757 TRACEI(REQ, "Failure during response: " << httpStatusCode << ": " << httpErrorBody << (extra_text.empty() ? "" : (": " + extra_text)));
2758 return -1;
2759 }
2760}
2761
2762void XrdHttpReq::addAgeHeader(std::string &headers) {
2763 long object_age = time(NULL) - filemodtime;
2764 headers += std::string("Age: ") + std::to_string(object_age < 0 ? 0 : object_age);
2765}
2766
2767void XrdHttpReq::addETagHeader(std::string &headers) {
2768 headers += std::string("Etag: \"") + std::to_string(etagval) + "\"";
2769}
2770
2772
2773 TRACE(REQ, " XrdHttpReq request ended.");
2774
2775 //if (xmlbody) xmlFreeDoc(xmlbody);
2776 readRangeHandler.reset();
2777 readClosing = false;
2778 closeAfterError = false;
2779 writtenbytes = 0;
2780 etext.clear();
2781 redirdest = "";
2782
2783 // // Here we should deallocate this
2784 // const struct iovec *iovP //!< pointer to data array
2785 // int iovN, //!< array count
2786 // int iovL, //!< byte count
2787 // bool final //!< true -> final result
2788
2789
2790 //xmlbody = 0;
2791 depth = 0;
2794 ralist.clear();
2795 ralist.shrink_to_fit();
2796
2797 request = rtUnset;
2798 resource = "";
2799 allheaders.clear();
2800
2801 // Reset the state of the request's digest request.
2802 m_want_digest.clear();
2803 m_digest_header.clear();
2804 m_req_cksum = nullptr;
2805
2806 m_user_agent = "";
2807 m_origin = "";
2808
2809 httpStatusCode = -1;
2810 initialStatusCode= -1;
2811 httpErrorCode = "";
2812 httpErrorBody = "";
2813
2814 headerok = false;
2815 keepalive = true;
2816 length = 0;
2817 length_seen = false;
2818 filesize = 0;
2819 depth = 0;
2820 sendcontinue = false;
2821
2822 m_transfer_encoding_chunked = false;
2823 m_current_chunk_size = -1;
2824 m_current_chunk_offset = 0;
2825
2826 m_trailer_headers = false;
2827 m_status_trailer = false;
2828
2830 reqstate = 0;
2831
2832 memset(&xrdreq, 0, sizeof (xrdreq));
2833 memset(&xrdresp, 0, sizeof (xrdresp));
2835
2836 etext.clear();
2837 redirdest = "";
2838
2839 stringresp = "";
2840
2841 host = "";
2842 destination = "";
2843 hdr2cgistr = "";
2844 m_appended_hdr2cgistr = false;
2845 m_appended_asize = false;
2846
2847 iovP = 0;
2848 iovN = 0;
2849 iovL = 0;
2850
2851 if (opaque) delete(opaque);
2852 opaque = 0;
2853
2854 fopened = false;
2855 final = false;
2856 mScitag = -1;
2857
2858 m_repr_digest.clear();
2859 m_want_repr_digest.clear();
2860
2862 startTime = std::chrono::steady_clock::time_point::min();
2863}
2864
2865void XrdHttpReq::getfhandle() {
2866
2867 memcpy(fhandle, iovP[0].iov_base, 4);
2868 TRACEI(REQ, "fhandle:" <<
2869 (int) fhandle[0] << ":" << (int) fhandle[1] << ":" << (int) fhandle[2] << ":" << (int) fhandle[3]);
2870
2871}
2872
2873void XrdHttpReq::getReadResponse(XrdHttpIOList &received) {
2874 received.clear();
2875
2876 if (ntohs(xrdreq.header.requestid) == kXR_readv) {
2877 readahead_list *l;
2878 char *p;
2879 kXR_int32 len;
2880
2881 // Cycle on all the data that is coming from the server
2882 for (int i = 0; i < iovN; i++) {
2883
2884 for (p = (char *) iovP[i].iov_base; p < (char *) iovP[i].iov_base + iovP[i].iov_len;) {
2885 l = (readahead_list *) p;
2886 memcpy(&len, &l->rlen, sizeof(kXR_int32));
2887 len = ntohl(len);
2888
2889 received.emplace_back(p+sizeof(readahead_list), -1, len);
2890
2891 p += sizeof (readahead_list);
2892 p += len;
2893
2894 }
2895 }
2896 return;
2897 }
2898
2899 // kXR_read result
2900 for (int i = 0; i < iovN; i++) {
2901 received.emplace_back((char*)iovP[i].iov_base, -1, iovP[i].iov_len);
2902 }
2903
2904}
2905
2906int XrdHttpReq::sendReadResponsesMultiRanges(const XrdHttpIOList &received) {
2907
2908 if (received.size() == 0) {
2909 bool start, finish;
2910 if (readRangeHandler.NotifyReadResult(0, nullptr, start, finish) < 0) {
2911 return -1;
2912 }
2913 return 0;
2914 }
2915
2916 // user is expecting multiple ranges, we must be prepared to send an
2917 // individual header for each and format it according to the http rules
2918
2919 struct rinfo {
2920 bool start;
2921 bool finish;
2922 const XrdOucIOVec2 *ci;
2923 const XrdHttpReadRangeHandler::UserRange *ur;
2924 std::string st_header;
2925 std::string fin_header;
2926 };
2927
2928 // report each received byte chunk to the range handler and record the details
2929 // of original user range it related to and if starts a range or finishes all.
2930 // also sum the total of the headers and data which need to be sent to the user,
2931 // in case we need it for chunked transfer encoding
2932 std::vector<rinfo> rvec;
2933 off_t sum_len = 0;
2934
2935 rvec.reserve(received.size());
2936
2937 for(const auto &rcv: received) {
2938 rinfo rentry;
2939 bool start, finish;
2940 const XrdHttpReadRangeHandler::UserRange *ur;
2941
2942 if (readRangeHandler.NotifyReadResult(rcv.size, &ur, start, finish) < 0) {
2943 return -1;
2944 }
2945 rentry.ur = ur;
2946 rentry.start = start;
2947 rentry.finish = finish;
2948 rentry.ci = &rcv;
2949
2950 if (start) {
2951 std::string s = buildPartialHdr(ur->start,
2952 ur->end,
2953 filesize,
2954 (char *) "123456");
2955
2956 rentry.st_header = s;
2957 sum_len += s.size();
2958 }
2959
2960 sum_len += rcv.size;
2961
2962 if (finish) {
2963 std::string s = buildPartialHdrEnd((char *) "123456");
2964 rentry.fin_header = s;
2965 sum_len += s.size();
2966 }
2967
2968 rvec.push_back(rentry);
2969 }
2970
2971
2972 // Send chunked encoding header
2973 if (m_transfer_encoding_chunked && m_trailer_headers) {
2974 prot->ChunkRespHeader(sum_len);
2975 }
2976
2977 // send the user the headers / data
2978 for(const auto &rentry: rvec) {
2979
2980 if (rentry.start) {
2981 TRACEI(REQ, "Sending multipart: " << rentry.ur->start << "-" << rentry.ur->end);
2982 if (prot->SendData((char *) rentry.st_header.c_str(), rentry.st_header.size())) {
2983 return -1;
2984 }
2985 }
2986
2987 // Send all the data we have
2988 if (prot->SendData((char *) rentry.ci->data, rentry.ci->size)) {
2989 return -1;
2990 }
2991
2992 if (rentry.finish) {
2993 if (prot->SendData((char *) rentry.fin_header.c_str(), rentry.fin_header.size())) {
2994 return -1;
2995 }
2996 }
2997 }
2998
2999 // Send chunked encoding footer
3000 if (m_transfer_encoding_chunked && m_trailer_headers) {
3001 prot->ChunkRespFooter();
3002 }
3003
3004 return 0;
3005}
3006
3007int XrdHttpReq::sendReadResponseSingleRange(const XrdHttpIOList &received) {
3008 // single range http transfer
3009
3010 if (received.size() == 0) {
3011 bool start, finish;
3012 if (readRangeHandler.NotifyReadResult(0, nullptr, start, finish) < 0) {
3013 return -1;
3014 }
3015 return 0;
3016 }
3017
3018 off_t sum = 0;
3019 // notify the range handler and return if error
3020 for(const auto &rcv: received) {
3021 bool start, finish;
3022 if (readRangeHandler.NotifyReadResult(rcv.size, nullptr, start, finish) < 0) {
3023 return -1;
3024 }
3025 sum += rcv.size;
3026 }
3027
3028 // Send chunked encoding header
3029 if (m_transfer_encoding_chunked && m_trailer_headers) {
3030 prot->ChunkRespHeader(sum);
3031 }
3032 for(const auto &rcv: received) {
3033 if (prot->SendData((char *) rcv.data, rcv.size)) return -1;
3034 }
3035 if (m_transfer_encoding_chunked && m_trailer_headers) {
3036 prot->ChunkRespFooter();
3037 }
3038 return 0;
3039}
XErrorCode
@ kXR_ItExists
@ kXR_noErrorYet
@ kXR_isDirectory
#define kXR_isManager
struct ClientSetRequest set
Definition XProtocol.hh:913
@ 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
@ kXR_noResponsesYet
Definition XProtocol.hh:950
@ kXR_ok
Definition XProtocol.hh:941
@ kXR_error
Definition XProtocol.hh:945
@ kXR_dstat
Definition XProtocol.hh:269
@ 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_unt16 requestid
Definition XProtocol.hh:755
kXR_int32 rlen
Definition XProtocol.hh:696
@ kXR_mkdirpath
Definition XProtocol.hh:440
@ 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
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)
void calcHashes(char *hash, const char *fn, kXR_int16 request, XrdSecEntity *secent, time_t tim, const char *key)
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
std::string getXRootDConfigDigestName() 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)
std::vector< UserRange > UserRangeList
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
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 erase(int start=0, int size=0)
int find(const char c, int start=0, bool forward=1)
void append(const int i)
const char * c_str() const
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)