XRootD
Loading...
Searching...
No Matches
XrdHttpUtils.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: Apr 2013
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
38
39
40#include "XrdHttpUtils.hh"
41
42#include <cstring>
43#include <openssl/hmac.h>
44#include <openssl/bio.h>
45#include <openssl/buffer.h>
46#include <openssl/err.h>
47#include <openssl/ssl.h>
48# include "sys/param.h"
49
50#include <pthread.h>
51#include <memory>
52#include <vector>
53#include <algorithm>
54
57
58// Encode an array of bytes to base64
59
60void Tobase64(const unsigned char *input, int length, char *out) {
61 BIO *bmem, *b64;
62 BUF_MEM *bptr;
63
64 if (!out) return;
65
66 out[0] = '\0';
67
68 b64 = BIO_new(BIO_f_base64());
69 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
70 bmem = BIO_new(BIO_s_mem());
71 BIO_push(b64, bmem);
72 BIO_write(b64, input, length);
73
74 if (BIO_flush(b64) <= 0) {
75 BIO_free_all(b64);
76 return;
77 }
78
79 BIO_get_mem_ptr(b64, &bptr);
80
81
82 memcpy(out, bptr->data, bptr->length);
83 out[bptr->length] = '\0';
84
85 BIO_free_all(b64);
86
87 return;
88}
89
90void Tobase64(const std::vector<uint8_t> & input, std::string & base64Output) {
91 BIO *bmem, *b64;
92 BUF_MEM *bptr;
93
94 base64Output.clear();
95
96 if(input.empty()) {
97 return;
98 }
99
100 b64 = BIO_new(BIO_f_base64());
101 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
102 bmem = BIO_new(BIO_s_mem());
103 BIO_push(b64, bmem);
104 BIO_write(b64, input.data(), input.size());
105
106 if (BIO_flush(b64) <= 0) {
107 BIO_free_all(b64);
108 return;
109 }
110
111 BIO_get_mem_ptr(b64, &bptr);
112
113 base64Output.assign(bptr->data,bptr->length);
114
115 BIO_free_all(b64);
116}
117
118void base64ToBytes(const std::string & base64digest, std::vector<uint8_t> & outputBytes) {
119 outputBytes.clear();
120
121 if (base64digest.empty()) {
122 return;
123 }
124
125 BIO *b64 = BIO_new(BIO_f_base64());
126 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); // match your encoder
127
128 BIO *bmem = BIO_new_mem_buf(base64digest.data(), static_cast<int>(base64digest.size()));
129 bmem = BIO_push(b64, bmem);
130
131 // Estimate maximum size (base64 expands data by ~33%)
132 std::vector<uint8_t> buffer(base64digest.size());
133
134 int decodedLen = BIO_read(bmem, buffer.data(), static_cast<int>(buffer.size()));
135 if (decodedLen > 0) {
136 buffer.resize(decodedLen);
137 outputBytes.swap(buffer);
138 } else {
139 outputBytes.clear(); // decoding failed
140 }
141
142 BIO_free_all(bmem);
143}
144
145void bytesToHex(const std::vector<uint8_t> & bytes, std::string & output) {
146 static const char* lut = "0123456789abcdef";
147 output.clear();
148 output.reserve(bytes.size() * 2);
149 for (uint8_t b : bytes) {
150 output.push_back(lut[b >> 4]);
151 output.push_back(lut[b & 0x0F]);
152 }
153}
154
155void base64DecodeHex(const std::string & base64, std::string & hexOutput) {
156 std::vector<uint8_t> bytes;
157 base64ToBytes(base64,bytes);
158 bytesToHex(bytes, hexOutput);
159}
160
161
162static int
164{
165 unsigned char c = static_cast<unsigned char>(ch);
166 if (std::isdigit(c)) {
167 return c - '0';
168 } else {
169 c = ::tolower(c);
170 if (c >= 'a' && c <= 'f') {
171 return c - 'a' + 10;
172 }
173 return -1;
174 }
175}
176
177bool Fromhexdigest(const std::string & hex, std::vector<uint8_t> & outputBytes) {
178 if(hex.size() % 2 != 0) {
179 return false;
180 }
181
182 outputBytes.reserve(hex.size() / 2);
183
184 for(size_t i = 0; i < hex.size(); i += 2) {
185 int upper = char_to_int(hex[i]);
186 int lower = char_to_int(hex[i + 1]);
187 if (upper < 0 || lower < 0) return false;
188 outputBytes.push_back(static_cast<uint8_t>((upper << 4) + lower));
189 }
190 return true;
191}
192
193
194// Simple itoa function
195std::string itos(long i) {
196 char buf[128];
197 sprintf(buf, "%ld", i);
198
199 return buf;
200}
201
202
203
204// Home made implementation of strchrnul
205char *mystrchrnul(const char *s, int c) {
206 char *ptr = strchr((char *)s, c);
207
208 if (!ptr)
209 return strchr((char *)s, '\0');
210
211 return ptr;
212}
213
214
215
216// Calculates the opaque arguments hash, needed for a secure redirection
217//
218// - hash is a string that will be filled with the hash
219//
220// - fn: the original filename that was requested
221// - dhost: target redirection hostname
222// - client: address:port of the client
223// - tim: creation time of the url
224// - tim_grace: validity time before and after creation time
225//
226// Input for the key (simple shared secret)
227// - key
228// - key length
229//
230
232 char *hash,
233
234 const char *fn,
235
236 kXR_int16 request,
237
238 XrdSecEntity *secent,
239
240 time_t tim,
241
242 const char *key) {
243
244
245#if OPENSSL_VERSION_NUMBER >= 0x30000000L
246 EVP_MAC *mac;
247 EVP_MAC_CTX *ctx;
248 size_t len;
249#else
250 HMAC_CTX *ctx;
251 unsigned int len;
252#endif
253 unsigned char mdbuf[EVP_MAX_MD_SIZE];
254 char buf[64];
255 struct tm tms;
256
257
258 if (!hash) {
259 return;
260 }
261 hash[0] = '\0';
262
263 if (!key) {
264 return;
265 }
266
267 if (!fn || !secent) {
268 return;
269 }
270
271#if OPENSSL_VERSION_NUMBER >= 0x30000000L
272
273 if (!(mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr))) {
274 return;
275 }
276
277 if (!(ctx = EVP_MAC_CTX_new(mac))) {
278 EVP_MAC_free(mac);
279 return;
280 }
281
282 OSSL_PARAM params[2] = {
283 OSSL_PARAM_construct_utf8_string("digest", (char*)"SHA256", 0),
284 OSSL_PARAM_construct_end()
285 };
286
287 if (!EVP_MAC_init(ctx, (const unsigned char *) key, strlen(key), params)) {
288 EVP_MAC_CTX_free(ctx);
289 EVP_MAC_free(mac);
290 return;
291 }
292
293 if (fn)
294 EVP_MAC_update(ctx, (const unsigned char *) fn,
295 strlen(fn) + 1);
296
297 EVP_MAC_update(ctx, (const unsigned char *) &request,
298 sizeof (request));
299
300 if (secent->name)
301 EVP_MAC_update(ctx, (const unsigned char *) secent->name,
302 strlen(secent->name) + 1);
303
304 if (secent->vorg)
305 EVP_MAC_update(ctx, (const unsigned char *) secent->vorg,
306 strlen(secent->vorg) + 1);
307
308 if (secent->host)
309 EVP_MAC_update(ctx, (const unsigned char *) secent->host,
310 strlen(secent->host) + 1);
311
312 if (secent->moninfo)
313 EVP_MAC_update(ctx, (const unsigned char *) secent->moninfo,
314 strlen(secent->moninfo) + 1);
315
316 localtime_r(&tim, &tms);
317 strftime(buf, sizeof (buf), "%s", &tms);
318 EVP_MAC_update(ctx, (const unsigned char *) buf,
319 strlen(buf) + 1);
320
321 EVP_MAC_final(ctx, mdbuf, &len, EVP_MAX_MD_SIZE);
322
323 EVP_MAC_CTX_free(ctx);
324 EVP_MAC_free(mac);
325
326#else
327
328 ctx = HMAC_CTX_new();
329
330 if (!ctx) {
331 return;
332 }
333
334
335
336 HMAC_Init_ex(ctx, (const void *) key, strlen(key), EVP_sha256(), 0);
337
338
339 if (fn)
340 HMAC_Update(ctx, (const unsigned char *) fn,
341 strlen(fn) + 1);
342
343 HMAC_Update(ctx, (const unsigned char *) &request,
344 sizeof (request));
345
346 if (secent->name)
347 HMAC_Update(ctx, (const unsigned char *) secent->name,
348 strlen(secent->name) + 1);
349
350 if (secent->vorg)
351 HMAC_Update(ctx, (const unsigned char *) secent->vorg,
352 strlen(secent->vorg) + 1);
353
354 if (secent->host)
355 HMAC_Update(ctx, (const unsigned char *) secent->host,
356 strlen(secent->host) + 1);
357
358 if (secent->moninfo)
359 HMAC_Update(ctx, (const unsigned char *) secent->moninfo,
360 strlen(secent->moninfo) + 1);
361
362 localtime_r(&tim, &tms);
363 strftime(buf, sizeof (buf), "%s", &tms);
364 HMAC_Update(ctx, (const unsigned char *) buf,
365 strlen(buf) + 1);
366
367 HMAC_Final(ctx, mdbuf, &len);
368
369 HMAC_CTX_free(ctx);
370
371#endif
372
373 Tobase64(mdbuf, len / 2, hash);
374}
375
377 const char *h1,
378 const char *h2) {
379
380 if (h1 == h2) return 0;
381
382 if (!h1 || !h2)
383 return 1;
384
385 return strcmp(h1, h2);
386
387}
388
389// unquote a string and return a new one
390
391char *unquote(char *str) {
392 int l = strlen(str);
393 char *r = (char *) malloc(l + 1);
394 r[0] = '\0';
395 int i, j = 0;
396
397 for (i = 0; i < l; i++) {
398 if (str[i] == '%') {
399 if (i + 3 > l) {
400 r[j] = '\0';
401 return r;
402 }
403 char savec = str[i + 3];
404 str[i + 3] = '\0';
405
406 r[j] = strtol(str + i + 1, 0, 16);
407 str[i + 3] = savec;
408
409 i += 2;
410 } else r[j] = str[i];
411
412 j++;
413 }
414
415 r[j] = '\0';
416
417 return r;
418
419}
420
421// Quote a string and return a new one
422
423char *quote(const char *str) {
424 int l = strlen(str);
425 char *r = (char *) malloc(l*3 + 1);
426 r[0] = '\0';
427 int i, j = 0;
428
429 for (i = 0; i < l; i++) {
430 char c = str[i];
431
432 switch (c) {
433 case ' ':
434 strcpy(r + j, "%20");
435 j += 3;
436 break;
437 case '[':
438 strcpy(r + j, "%5B");
439 j += 3;
440 break;
441 case ']':
442 strcpy(r + j, "%5D");
443 j += 3;
444 break;
445 case ':':
446 strcpy(r + j, "%3A");
447 j += 3;
448 break;
449 // case '/':
450 // strcpy(r + j, "%2F");
451 // j += 3;
452 // break;
453 case '#':
454 strcpy(r + j, "%23");
455 j += 3;
456 break;
457 case '\n':
458 strcpy(r + j, "%0A");
459 j += 3;
460 break;
461 case '\r':
462 strcpy(r + j, "%0D");
463 j += 3;
464 break;
465 case '=':
466 strcpy(r + j, "%3D");
467 j += 3;
468 break;
469 default:
470 r[j++] = c;
471 }
472 }
473
474 r[j] = '\0';
475
476 return r;
477}
478
479
480// Escape a string and return a new one
481
482char *escapeXML(const char *str) {
483 int l = strlen(str);
484 char *r = (char *) malloc(l*6 + 1);
485 r[0] = '\0';
486 int i, j = 0;
487
488 for (i = 0; i < l; i++) {
489 char c = str[i];
490
491 switch (c) {
492 case '"':
493 strcpy(r + j, "&quot;");
494 j += 6;
495 break;
496 case '&':
497 strcpy(r + j, "&amp;");
498 j += 5;
499 break;
500 case '<':
501 strcpy(r + j, "&lt;");
502 j += 4;
503 break;
504 case '>':
505 strcpy(r + j, "&gt;");
506 j += 4;
507 break;
508 case '\'':
509 strcpy(r + j, "&apos;");
510 j += 6;
511 break;
512
513 default:
514 r[j++] = c;
515 }
516 }
517
518 r[j] = '\0';
519
520 return r;
521}
522
524
525 int errNo = XProtocol::toErrno(xrdError);
526 return mapErrNoToHttp(errNo);
527
528}
529
530int mapErrNoToHttp(int errNo) {
531
532 switch (errNo) {
533
534 case EACCES:
535 case EROFS:
536 case EPERM:
537 return HTTP_FORBIDDEN;
538
539 case EAUTH:
540 return HTTP_UNAUTHORIZED;
541
542 case ENOENT:
543 return HTTP_NOT_FOUND;
544
545 case EEXIST:
546 case EISDIR:
547 case ENOTDIR:
548 case ENOTEMPTY:
549 return HTTP_CONFLICT;
550
551 case EXDEV:
553
554 case ENAMETOOLONG:
555 return HTTP_URI_TOO_LONG;
556
557 case ELOOP:
558 return HTTP_LOOP_DETECTED;
559
560 case ENOSPC:
561 case EDQUOT:
563
564 case EFBIG:
566
567 case EINVAL:
568 case EBADF:
569 case EFAULT:
570 case ENXIO:
571 case ESPIPE:
572 case EOVERFLOW:
573 return HTTP_BAD_REQUEST;
574
575 case ENOTSUP: // EOPNOTSUPP
577
578 case EBUSY:
579 case EAGAIN:
580 case EINTR:
581 case ENOMEM:
582 case EMFILE:
583 case ENFILE:
584 case ETXTBSY:
586
587 case ETIMEDOUT:
589
590 case ECONNREFUSED:
591 case ECONNRESET:
592 case ENETDOWN:
593 case ENETUNREACH:
594 case EHOSTUNREACH:
595 case EPIPE:
596 return HTTP_BAD_GATEWAY;
597
598 default:
600 }
601}
602
603std::string httpStatusToString(int status) {
604 switch (status) {
605 // 1xx Informational
606 case 100: return "Continue";
607 case 101: return "Switching Protocols";
608 case 102: return "Processing";
609 case 103: return "Early Hints";
610
611 // 2xx Success
612 case 200: return "OK";
613 case 201: return "Created";
614 case 202: return "Accepted";
615 case 203: return "Non-Authoritative Information";
616 case 204: return "No Content";
617 case 205: return "Reset Content";
618 case 206: return "Partial Content";
619 case 207: return "Multi-Status";
620 case 208: return "Already Reported";
621 case 226: return "IM Used";
622
623 // 3xx Redirection
624 case 300: return "Multiple Choices";
625 case 301: return "Moved Permanently";
626 case 302: return "Found";
627 case 303: return "See Other";
628 case 304: return "Not Modified";
629 case 305: return "Use Proxy";
630 case 307: return "Temporary Redirect";
631 case 308: return "Permanent Redirect";
632
633 // 4xx Client Errors
634 case 400: return "Bad Request";
635 case 401: return "Unauthorized";
636 case 402: return "Payment Required";
637 case 403: return "Forbidden";
638 case 404: return "Not Found";
639 case 405: return "Method Not Allowed";
640 case 406: return "Not Acceptable";
641 case 407: return "Proxy Authentication Required";
642 case 408: return "Request Timeout";
643 case 409: return "Conflict";
644 case 410: return "Gone";
645 case 411: return "Length Required";
646 case 412: return "Precondition Failed";
647 case 413: return "Payload Too Large";
648 case 414: return "URI Too Long";
649 case 415: return "Unsupported Media Type";
650 case 416: return "Range Not Satisfiable";
651 case 417: return "Expectation Failed";
652 case 418: return "I'm a teapot";
653 case 421: return "Misdirected Request";
654 case 422: return "Unprocessable Entity";
655 case 423: return "Locked";
656 case 424: return "Failed Dependency";
657 case 425: return "Too Early";
658 case 426: return "Upgrade Required";
659 case 428: return "Precondition Required";
660 case 429: return "Too Many Requests";
661 case 431: return "Request Header Fields Too Large";
662 case 451: return "Unavailable For Legal Reasons";
663
664 // 5xx Server Errors
665 case 500: return "Internal Server Error";
666 case 501: return "Not Implemented";
667 case 502: return "Bad Gateway";
668 case 503: return "Service Unavailable";
669 case 504: return "Gateway Timeout";
670 case 505: return "HTTP Version Not Supported";
671 case 506: return "Variant Also Negotiates";
672 case 507: return "Insufficient Storage";
673 case 508: return "Loop Detected";
674 case 510: return "Not Extended";
675 case 511: return "Network Authentication Required";
676
677 default:
678 switch (status) {
679 case 100 ... 199: return "Informational";
680 case 200 ... 299: return "Success";
681 case 300 ... 399: return "Redirection";
682 case 400 ... 499: return "Client Error";
683 case 500 ... 599: return "Server Error";
684 default: return "Unknown";
685 }
686 }
687}
XErrorCode
#define EAUTH
short kXR_int16
Definition XPtypes.hh:66
std::string itos(long i)
void Tobase64(const unsigned char *input, int length, char *out)
int compareHash(const char *h1, const char *h2)
static int char_to_int(int ch)
void base64ToBytes(const std::string &base64digest, std::vector< uint8_t > &outputBytes)
char * unquote(char *str)
int mapXrdErrToHttp(XErrorCode xrdError)
char * quote(const char *str)
bool Fromhexdigest(const std::string &hex, std::vector< uint8_t > &outputBytes)
char * escapeXML(const char *str)
int mapErrNoToHttp(int errNo)
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)
std::string httpStatusToString(int status)
void base64DecodeHex(const std::string &base64, std::string &hexOutput)
void bytesToHex(const std::vector< uint8_t > &bytes, std::string &output)
Utility functions for XrdHTTP.
@ HTTP_INSUFFICIENT_STORAGE
@ HTTP_BAD_REQUEST
@ HTTP_LOOP_DETECTED
@ HTTP_SERVICE_UNAVAILABLE
@ HTTP_URI_TOO_LONG
@ HTTP_UNAUTHORIZED
@ HTTP_NOT_FOUND
@ HTTP_FORBIDDEN
@ HTTP_BAD_GATEWAY
@ HTTP_GATEWAY_TIMEOUT
@ HTTP_INTERNAL_SERVER_ERROR
@ HTTP_PAYLOAD_TOO_LARGE
@ HTTP_NOT_IMPLEMENTED
@ HTTP_UNPROCESSABLE_ENTITY
@ HTTP_CONFLICT
static int toErrno(int xerr)
char * vorg
Entity's virtual organization(s).
char * name
Entity's name.
char * moninfo
Information for monitoring.
char * host
Entity's host name dnr dependent.