XRootD
XrdClHttp::HeaderParser Class Reference

#include <XrdClHttpUtil.hh>

+ Collaboration diagram for XrdClHttp::HeaderParser:

Public Member Functions

 HeaderParser ()
 
VerbsCache::HttpVerbs GetAllowedVerbs () const
 
const std::string & GetCacheControl () const
 
const XrdClHttp::ChecksumInfo & GetChecksums () const
 
int64_t GetContentLength () const
 
const std::string & GetETag () const
 
const std::string & GetLocation () const
 
uint64_t GetOffset () const
 
int GetStatusCode () const
 
std::string GetStatusMessage () const
 
bool HeadersDone () const
 
bool IsMultipartByterange () const
 
ResponseInfo::HeaderMap && MoveHeaders ()
 
const std::string & MultipartSeparator () const
 
bool Parse (const std::string &headers)
 
void SetMultipartSeparator (const std::string_view &sep)
 
void SetStatusCode (int sc)
 

Static Public Member Functions

static bool Base64Decode (std::string_view input, std::array< unsigned char, g_max_checksum_length > &output)
 
static bool Canonicalize (std::string &headerName)
 
static std::string ChecksumTypeToDigestName (XrdClHttp::ChecksumType type)
 
static void ParseDigest (const std::string &digest, XrdClHttp::ChecksumInfo &info)
 

Detailed Description

Definition at line 93 of file XrdClHttpUtil.hh.

Constructor & Destructor Documentation

◆ HeaderParser()

XrdClHttp::HeaderParser::HeaderParser ( )
inline

Definition at line 95 of file XrdClHttpUtil.hh.

95 {}

Member Function Documentation

◆ Base64Decode()

bool HeaderParser::Base64Decode ( std::string_view  input,
std::array< unsigned char, g_max_checksum_length > &  output 
)
static

Definition at line 343 of file XrdClHttpUtil.cc.

345  {
346  if (input.size() > 4 * ((output.size() + 2) / 3)
347  || input.size() % 4 != 0)
348  return false;
349  if (input.size() == 0) return true;
350 
351  std::unique_ptr<BIO, decltype(&BIO_free_all)> b64(BIO_new(BIO_f_base64()), &BIO_free_all);
352  BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
353  std::unique_ptr<BIO, decltype(&BIO_free_all)> bmem(
354  BIO_new_mem_buf(const_cast<char *>(input.data()), input.size()), &BIO_free_all);
355  bmem.reset(BIO_push(b64.release(), bmem.release()));
356 
357  // Compute expected length of output; used to verify BIO_read consumes all input
358  size_t expectedLen = static_cast<size_t>(input.size() * 0.75);
359  if (input[input.size() - 1] == '=') {
360  expectedLen -= 1;
361  if (input[input.size() - 2] == '=') {
362  expectedLen -= 1;
363  }
364  }
365 
366  auto len = BIO_read(bmem.get(), &output[0], output.size());
367 
368  if (len == -1 || static_cast<size_t>(len) != expectedLen) return false;
369 
370  return true;
371 }

Referenced by ParseDigest().

+ Here is the caller graph for this function:

◆ Canonicalize()

bool HeaderParser::Canonicalize ( std::string &  headerName)
static

Definition at line 664 of file XrdClHttpUtil.cc.

665 {
666  auto upper = true;
667  const static int toLower = 'a' - 'A';
668  for (size_t idx=0; idx<headerName.size(); idx++) {
669  char c = headerName[idx];
670  if (!validHeaderByte(c)) {
671  return false;
672  }
673  if (upper && 'a' <= c && c <= 'z') {
674  c -= toLower;
675  } else if (!upper && 'A' <= c && c <= 'Z') {
676  c += toLower;
677  }
678  headerName[idx] = c;
679  upper = c == '-';
680  }
681  return true;
682 }

Referenced by Parse().

+ Here is the caller graph for this function:

◆ ChecksumTypeToDigestName()

std::string HeaderParser::ChecksumTypeToDigestName ( XrdClHttp::ChecksumType  type)
static

Definition at line 604 of file XrdClHttpUtil.cc.

604  {
605  switch (type) {
606  case XrdClHttp::ChecksumType::kADLER32:
607  return "adler32";
608  case XrdClHttp::ChecksumType::kCRC32:
609  return "crc32";
610  case XrdClHttp::ChecksumType::kMD5:
611  return "MD5";
612  case XrdClHttp::ChecksumType::kCRC32C:
613  return "CRC32c";
614  case XrdClHttp::ChecksumType::kSHA1:
615  return "SHA";
616  case XrdClHttp::ChecksumType::kSHA256:
617  return "SHA-256";
618  case XrdClHttp::ChecksumType::kAll: {
619  std::string result;
620  for (int value = 0;
621  value < static_cast<int>(XrdClHttp::ChecksumType::kAll);
622  ++value) {
623  if (!result.empty()) result += ',';
624  result += ChecksumTypeToDigestName(
625  static_cast<XrdClHttp::ChecksumType>(value));
626  }
627  return result;
628  }
629  default:
630  return "";
631  }
632 }
static std::string ChecksumTypeToDigestName(XrdClHttp::ChecksumType type)

Referenced by XrdClHttp::CurlChecksumOp::Setup().

+ Here is the caller graph for this function:

◆ GetAllowedVerbs()

VerbsCache::HttpVerbs XrdClHttp::HeaderParser::GetAllowedVerbs ( ) const
inline

Definition at line 132 of file XrdClHttpUtil.hh.

133  {
134  return VerbsCache::HttpVerbs(m_allow_verbs);
135  }

Referenced by XrdClHttp::CurlOptionsOp::Success().

+ Here is the caller graph for this function:

◆ GetCacheControl()

const std::string& XrdClHttp::HeaderParser::GetCacheControl ( ) const
inline

Definition at line 141 of file XrdClHttpUtil.hh.

141 {return m_cache_control;}

Referenced by XrdClHttp::CurlPrefetchOpenOp::Pause().

+ Here is the caller graph for this function:

◆ GetChecksums()

const XrdClHttp::ChecksumInfo& XrdClHttp::HeaderParser::GetChecksums ( ) const
inline

Definition at line 144 of file XrdClHttpUtil.hh.

144 {return m_checksums;}

Referenced by XrdClHttp::CurlChecksumOp::Success().

+ Here is the caller graph for this function:

◆ GetContentLength()

int64_t XrdClHttp::HeaderParser::GetContentLength ( ) const
inline

Definition at line 99 of file XrdClHttpUtil.hh.

99 {return m_content_length;}

Referenced by XrdClHttp::CurlStatOp::GetStatInfo(), and XrdClHttp::CurlPrefetchOpenOp::Pause().

+ Here is the caller graph for this function:

◆ GetETag()

const std::string& XrdClHttp::HeaderParser::GetETag ( ) const
inline

Definition at line 140 of file XrdClHttpUtil.hh.

140 {return m_etag;}

Referenced by XrdClHttp::CurlPrefetchOpenOp::Pause(), and XrdClHttp::CurlQueryOp::Success().

+ Here is the caller graph for this function:

◆ GetLocation()

const std::string& XrdClHttp::HeaderParser::GetLocation ( ) const
inline

Definition at line 139 of file XrdClHttpUtil.hh.

139 {return m_location;}

Referenced by XrdClHttp::CurlStatOp::OptionsDone().

+ Here is the caller graph for this function:

◆ GetOffset()

uint64_t XrdClHttp::HeaderParser::GetOffset ( ) const
inline

Definition at line 101 of file XrdClHttpUtil.hh.

101 {return m_response_offset;}

Referenced by XrdClHttp::CurlVectorReadOp::Write().

+ Here is the caller graph for this function:

◆ GetStatusCode()

int XrdClHttp::HeaderParser::GetStatusCode ( ) const
inline

Definition at line 112 of file XrdClHttpUtil.hh.

112 {return m_status_code;}

Referenced by XrdClHttp::CurlOperation::GetStatusCode(), and XrdClHttp::CurlOperation::IsRedirect().

+ Here is the caller graph for this function:

◆ GetStatusMessage()

std::string XrdClHttp::HeaderParser::GetStatusMessage ( ) const
inline

Definition at line 137 of file XrdClHttpUtil.hh.

137 {return m_resp_message;}

Referenced by XrdClHttp::CurlOperation::GetStatusMessage().

+ Here is the caller graph for this function:

◆ HeadersDone()

bool XrdClHttp::HeaderParser::HeadersDone ( ) const
inline

Definition at line 105 of file XrdClHttpUtil.hh.

105 {return m_recv_all_headers;}

◆ IsMultipartByterange()

bool XrdClHttp::HeaderParser::IsMultipartByterange ( ) const
inline

Definition at line 119 of file XrdClHttpUtil.hh.

119 {return m_multipart_byteranges;}

Referenced by XrdClHttp::CurlVectorReadOp::Write().

+ Here is the caller graph for this function:

◆ MoveHeaders()

ResponseInfo::HeaderMap&& XrdClHttp::HeaderParser::MoveHeaders ( )
inline

Definition at line 110 of file XrdClHttpUtil.hh.

110 {return std::move(m_headers);}

◆ MultipartSeparator()

const std::string& XrdClHttp::HeaderParser::MultipartSeparator ( ) const
inline

Definition at line 123 of file XrdClHttpUtil.hh.

123 {return m_multipart_sep;}

Referenced by XrdClHttp::CurlVectorReadOp::Write().

+ Here is the caller graph for this function:

◆ Parse()

bool HeaderParser::Parse ( const std::string &  headers)

Definition at line 378 of file XrdClHttpUtil.cc.

379 {
380  if (m_recv_all_headers) {
381  m_recv_all_headers = false;
382  m_recv_status_line = false;
383  }
384 
385  if (!m_recv_status_line) {
386  m_recv_status_line = true;
387 
388  std::stringstream ss(header_line);
389  std::string item;
390  if (!std::getline(ss, item, ' ')) return false;
391  m_resp_protocol = item;
392  if (!std::getline(ss, item, ' ')) return false;
393  try {
394  m_status_code = std::stol(item);
395  } catch (...) {
396  return false;
397  }
398  if (m_status_code < 100 || m_status_code >= 600) {
399  return false;
400  }
401  if (!std::getline(ss, item, '\n')) return false;
402  auto cr_loc = item.find('\r');
403  if (cr_loc != std::string::npos) {
404  m_resp_message = item.substr(0, cr_loc);
405  } else {
406  m_resp_message = item;
407  }
408  return true;
409  }
410 
411  if (header_line.empty() || header_line == "\n" || header_line == "\r\n") {
412  m_recv_all_headers = true;
413  return true;
414  }
415 
416  auto found = header_line.find(":");
417  if (found == std::string::npos) {
418  return false;
419  }
420 
421  std::string header_name = header_line.substr(0, found);
422  if (!Canonicalize(header_name)) {
423  return false;
424  }
425 
426  found += 1;
427  while (found < header_line.size()) {
428  if (header_line[found] != ' ') {break;}
429  found += 1;
430  }
431  std::string header_value = header_line.substr(found);
432  // Note: ignoring the fact headers are only supposed to contain ASCII.
433  // We should trim out UTF-8.
434  header_value.erase(header_value.find_last_not_of(" \r\n\t") + 1);
435 
436  // Record the line in our header structure. Will be returned as part
437  // of the response info object.
438  auto iter = m_headers.find(header_name);
439  if (iter == m_headers.end()) {
440  m_headers.insert(iter, {header_name, {header_value}});
441  } else {
442  iter->second.push_back(header_value);
443  }
444 
445  if (header_name == "Allow") {
446  std::string_view val(header_value);
447  while (!val.empty()) {
448  auto found = val.find(',');
449  auto method = val.substr(0, found);
450  if (method == "PROPFIND") {
451  auto new_verbs = static_cast<unsigned>(m_allow_verbs) | static_cast<unsigned>(VerbsCache::HttpVerb::kPROPFIND);
452  m_allow_verbs = static_cast<VerbsCache::HttpVerb>(new_verbs);
453  }
454  if (found == std::string_view::npos) break;
455  val = val.substr(found + 1);
456  }
457  if (static_cast<unsigned>(m_allow_verbs) & ~static_cast<unsigned>(VerbsCache::HttpVerb::kUnknown)) {
458  m_allow_verbs = static_cast<VerbsCache::HttpVerb>(static_cast<unsigned>(m_allow_verbs) & ~static_cast<unsigned>(VerbsCache::HttpVerb::kUnknown));
459  }
460  } else if (header_name == "Content-Length") {
461  try {
462  m_content_length = std::stoll(header_value);
463  } catch (...) {
464  return false;
465  }
466  }
467  else if (header_name == "Content-Type") {
468  std::string_view val(header_value);
469  auto found = val.find(";");
470  auto first_type = val.substr(0, found);
471  m_multipart_byteranges = first_type == "multipart/byteranges";
472  if (m_multipart_byteranges) {
473  auto remainder = val.substr(found + 1);
474  found = remainder.find("boundary=");
475  if (found != std::string_view::npos) {
476  SetMultipartSeparator(remainder.substr(found + 9));
477  }
478  }
479  }
480  else if (header_name == "Content-Range") {
481  auto found = header_value.find(" ");
482  if (found == std::string::npos) {
483  return false;
484  }
485  std::string range_unit = header_value.substr(0, found);
486  if (range_unit != "bytes") {
487  return false;
488  }
489  auto range_resp = header_value.substr(found + 1);
490  found = range_resp.find("/");
491  if (found == std::string::npos) {
492  return false;
493  }
494  auto incl_range = range_resp.substr(0, found);
495  found = incl_range.find("-");
496  if (found == std::string::npos) {
497  return false;
498  }
499  auto first_pos = incl_range.substr(0, found);
500  try {
501  m_response_offset = std::stoll(first_pos);
502  } catch (...) {
503  return false;
504  }
505  auto last_pos = incl_range.substr(found + 1);
506  size_t last_byte;
507  try {
508  last_byte = std::stoll(last_pos);
509  } catch (...) {
510  return false;
511  }
512  m_content_length = last_byte - m_response_offset + 1;
513  }
514  else if (header_name == "Location") {
515  m_location = header_value;
516  } else if (header_name == "Digest") {
517  ParseDigest(header_value, m_checksums);
518  }
519  else if (header_name == "Etag")
520  {
521  // Note, the original hader name is ETag, renamed to Etag in parsing
522  // remove additional quotes
523  m_etag = header_value;
524  m_etag.erase(remove(m_etag.begin(), m_etag.end(), '\"'), m_etag.end());
525  }
526  else if (header_name == "Cache-Control")
527  {
528  m_cache_control = header_value;
529  }
530 
531  return true;
532 }
void getline(uchar *buff, int blen)
void SetMultipartSeparator(const std::string_view &sep)
static void ParseDigest(const std::string &digest, XrdClHttp::ChecksumInfo &info)
static bool Canonicalize(std::string &headerName)

References Canonicalize(), getline(), XrdClHttp::VerbsCache::kPROPFIND, XrdClHttp::VerbsCache::kUnknown, ParseDigest(), and SetMultipartSeparator().

+ Here is the call graph for this function:

◆ ParseDigest()

void HeaderParser::ParseDigest ( const std::string &  digest,
XrdClHttp::ChecksumInfo &  info 
)
static

Definition at line 537 of file XrdClHttpUtil.cc.

537  {
538  std::string_view view(digest);
539  std::array<unsigned char, g_max_checksum_length> checksum_value{};
540  std::string digest_lower;
541  while (!view.empty()) {
542  auto nextsep = view.find(',');
543  auto entry = view.substr(0, nextsep);
544  if (nextsep == std::string_view::npos) {
545  view = "";
546  } else {
547  view = view.substr(nextsep + 1);
548  }
549  nextsep = entry.find('=');
550  if (nextsep == std::string_view::npos) continue;
551  auto name = trim_view(entry.substr(0, nextsep));
552  auto value = trim_view(entry.substr(nextsep + 1));
553  digest_lower.clear();
554  digest_lower.resize(name.size());
555  std::transform(name.begin(), name.end(), digest_lower.begin(), [](unsigned char c) {
556  return std::tolower(c);
557  });
558  const auto setHex32 = [&](ChecksumType type) {
559  if (value.empty() || value.size() > 8) return;
560  std::string padded(8 - value.size(), '0');
561  padded.append(value.data(), value.size());
562  XrdCksData checksum;
563  if (!checksum.Set(padded.c_str(), padded.size())) return;
564  std::copy_n(
565  reinterpret_cast<const unsigned char *>(checksum.Value),
566  GetChecksumLength(type), checksum_value.begin());
567  info.Set(type, checksum_value);
568  };
569  const auto setBase64 = [&](ChecksumType type) {
570  const size_t decodedSize = GetChecksumLength(type);
571  const size_t encodedSize = 4 * ((decodedSize + 2) / 3);
572  if (value.size() == encodedSize
573  && Base64Decode(value, checksum_value))
574  {
575  info.Set(type, checksum_value);
576  }
577  };
578  if (digest_lower == "adler" || digest_lower == "adler32") {
579  setHex32(ChecksumType::kADLER32);
580  } else if (digest_lower == "crc32") {
581  setHex32(ChecksumType::kCRC32);
582  } else if (digest_lower == "md5") {
583  setBase64(ChecksumType::kMD5);
584  } else if (digest_lower == "sha" || digest_lower == "sha1") {
585  setBase64(ChecksumType::kSHA1);
586  } else if (digest_lower == "sha-256" || digest_lower == "sha256") {
587  setBase64(ChecksumType::kSHA256);
588  } else if (digest_lower == "crc32c") {
589  // XRootD currently incorrectly base64-encodes crc32c checksums; see
590  // https://github.com/xrootd/xrootd/issues/2456
591  // For backward comaptibility, if this looks like base64 encoded (8
592  // bytes long and last two bytes are padding), then we base64 decode.
593  if (value.size() == 8 && value[6] == '=' && value[7] == '=') {
594  setBase64(ChecksumType::kCRC32C);
595  continue;
596  }
597  setHex32(ChecksumType::kCRC32C);
598  }
599  }
600 }
char Value[ValuSize]
Definition: XrdCksData.hh:53
int Set(const char *csName)
Definition: XrdCksData.hh:81
bool Set(ChecksumType ctype, const std::array< unsigned char, g_max_checksum_length > &value)
static bool Base64Decode(std::string_view input, std::array< unsigned char, g_max_checksum_length > &output)
size_t GetChecksumLength(ChecksumType ctype)
std::string_view trim_view(const std::string_view &input_view)

References Base64Decode(), XrdClHttp::GetChecksumLength(), XrdClHttp::ChecksumInfo::Set(), XrdCksData::Set(), XrdClHttp::trim_view(), and XrdCksData::Value.

Referenced by Parse().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ SetMultipartSeparator()

void XrdClHttp::HeaderParser::SetMultipartSeparator ( const std::string_view &  sep)
inline

Definition at line 127 of file XrdClHttpUtil.hh.

127  {
128  m_multipart_sep = "--" + std::string(sep);
129  m_multipart_byteranges = true;
130  }

Referenced by Parse(), and XrdClHttp::CurlVectorReadOp::SetSeparator().

+ Here is the caller graph for this function:

◆ SetStatusCode()

void XrdClHttp::HeaderParser::SetStatusCode ( int  sc)
inline

Definition at line 116 of file XrdClHttpUtil.hh.

116 {m_status_code = sc;}

Referenced by XrdClHttp::CurlVectorReadOp::SetStatusCode().

+ Here is the caller graph for this function:

The documentation for this class was generated from the following files: