/xbmc/utils/HttpHeader.cpp

http://github.com/xbmc/xbmc · C++ · 239 lines · 175 code · 45 blank · 19 comment · 49 complexity · 3ae257b0e416b4ae2302d8eb596ee486 MD5 · raw file

  1. /*
  2. * Copyright (C) 2005-2018 Team Kodi
  3. * This file is part of Kodi - https://kodi.tv
  4. *
  5. * SPDX-License-Identifier: GPL-2.0-or-later
  6. * See LICENSES/README.md for more information.
  7. */
  8. #include "HttpHeader.h"
  9. #include "utils/StringUtils.h"
  10. // header white space characters according to RFC 2616
  11. const char* const CHttpHeader::m_whitespaceChars = " \t";
  12. CHttpHeader::CHttpHeader()
  13. {
  14. m_headerdone = false;
  15. }
  16. CHttpHeader::~CHttpHeader() = default;
  17. void CHttpHeader::Parse(const std::string& strData)
  18. {
  19. size_t pos = 0;
  20. const size_t len = strData.length();
  21. const char* const strDataC = strData.c_str();
  22. // According to RFC 2616 any header line can have continuation on next line, if next line is started from whitespace char
  23. // This code at first checks for whitespace char at the begging of the line, and if found, then current line is appended to m_lastHeaderLine
  24. // If current line is NOT started from whitespace char, then previously stored (and completed) m_lastHeaderLine is parsed and current line is assigned to m_lastHeaderLine (to be parsed later)
  25. while (pos < len)
  26. {
  27. size_t lineEnd = strData.find('\x0a', pos); // use '\x0a' instead of '\n' to be platform independent
  28. if (lineEnd == std::string::npos)
  29. return; // error: expected only complete lines
  30. const size_t nextLine = lineEnd + 1;
  31. if (lineEnd > pos && strDataC[lineEnd - 1] == '\x0d') // use '\x0d' instead of '\r' to be platform independent
  32. lineEnd--;
  33. if (m_headerdone)
  34. Clear(); // clear previous header and process new one
  35. if (strDataC[pos] == ' ' || strDataC[pos] == '\t') // same chars as in CHttpHeader::m_whitespaceChars
  36. { // line is started from whitespace char: this is continuation of previous line
  37. pos = strData.find_first_not_of(m_whitespaceChars, pos);
  38. m_lastHeaderLine.push_back(' '); // replace all whitespace chars at start of the line with single space
  39. m_lastHeaderLine.append(strData, pos, lineEnd - pos); // append current line
  40. }
  41. else
  42. { // this line is NOT continuation, this line is new header line
  43. if (!m_lastHeaderLine.empty())
  44. ParseLine(m_lastHeaderLine); // process previously stored completed line (if any)
  45. m_lastHeaderLine.assign(strData, pos, lineEnd - pos); // store current line to (possibly) complete later. Will be parsed on next turns.
  46. if (pos == lineEnd)
  47. m_headerdone = true; // current line is bare "\r\n" (or "\n"), means end of header; no need to process current m_lastHeaderLine
  48. }
  49. pos = nextLine; // go to next line (if any)
  50. }
  51. }
  52. bool CHttpHeader::ParseLine(const std::string& headerLine)
  53. {
  54. const size_t valueStart = headerLine.find(':');
  55. if (valueStart != std::string::npos)
  56. {
  57. std::string strParam(headerLine, 0, valueStart);
  58. std::string strValue(headerLine, valueStart + 1);
  59. StringUtils::Trim(strParam, m_whitespaceChars);
  60. StringUtils::ToLower(strParam);
  61. StringUtils::Trim(strValue, m_whitespaceChars);
  62. if (!strParam.empty() && !strValue.empty())
  63. m_params.push_back(HeaderParams::value_type(strParam, strValue));
  64. else
  65. return false;
  66. }
  67. else if (m_protoLine.empty())
  68. m_protoLine = headerLine;
  69. return true;
  70. }
  71. void CHttpHeader::AddParam(const std::string& param, const std::string& value, const bool overwrite /*= false*/)
  72. {
  73. std::string paramLower(param);
  74. StringUtils::ToLower(paramLower);
  75. StringUtils::Trim(paramLower, m_whitespaceChars);
  76. if (paramLower.empty())
  77. return;
  78. if (overwrite)
  79. { // delete ALL parameters with the same name
  80. // note: 'GetValue' always returns last added parameter,
  81. // so you probably don't need to overwrite
  82. for (size_t i = 0; i < m_params.size();)
  83. {
  84. if (m_params[i].first == paramLower)
  85. m_params.erase(m_params.begin() + i);
  86. else
  87. ++i;
  88. }
  89. }
  90. std::string valueTrim(value);
  91. StringUtils::Trim(valueTrim, m_whitespaceChars);
  92. if (valueTrim.empty())
  93. return;
  94. m_params.push_back(HeaderParams::value_type(paramLower, valueTrim));
  95. }
  96. std::string CHttpHeader::GetValue(const std::string& strParam) const
  97. {
  98. std::string paramLower(strParam);
  99. StringUtils::ToLower(paramLower);
  100. return GetValueRaw(paramLower);
  101. }
  102. std::string CHttpHeader::GetValueRaw(const std::string& strParam) const
  103. {
  104. // look in reverse to find last parameter (probably most important)
  105. for (HeaderParams::const_reverse_iterator iter = m_params.rbegin(); iter != m_params.rend(); ++iter)
  106. {
  107. if (iter->first == strParam)
  108. return iter->second;
  109. }
  110. return "";
  111. }
  112. std::vector<std::string> CHttpHeader::GetValues(std::string strParam) const
  113. {
  114. StringUtils::ToLower(strParam);
  115. std::vector<std::string> values;
  116. for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter)
  117. {
  118. if (iter->first == strParam)
  119. values.push_back(iter->second);
  120. }
  121. return values;
  122. }
  123. std::string CHttpHeader::GetHeader(void) const
  124. {
  125. if (m_protoLine.empty() && m_params.empty())
  126. return "";
  127. std::string strHeader(m_protoLine + "\r\n");
  128. for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter)
  129. strHeader += ((*iter).first + ": " + (*iter).second + "\r\n");
  130. strHeader += "\r\n";
  131. return strHeader;
  132. }
  133. std::string CHttpHeader::GetMimeType(void) const
  134. {
  135. std::string strValue(GetValueRaw("content-type"));
  136. std::string mimeType(strValue, 0, strValue.find(';'));
  137. StringUtils::TrimRight(mimeType, m_whitespaceChars);
  138. return mimeType;
  139. }
  140. std::string CHttpHeader::GetCharset(void) const
  141. {
  142. std::string strValue(GetValueRaw("content-type"));
  143. if (strValue.empty())
  144. return strValue;
  145. StringUtils::ToUpper(strValue);
  146. const size_t len = strValue.length();
  147. // extract charset value from 'contenttype/contentsubtype;pram1=param1Val ; charset=XXXX\t;param2=param2Val'
  148. // most common form: 'text/html; charset=XXXX'
  149. // charset value can be in double quotes: 'text/xml; charset="XXX XX"'
  150. size_t pos = strValue.find(';');
  151. while (pos < len)
  152. {
  153. // move to the next non-whitespace character
  154. pos = strValue.find_first_not_of(m_whitespaceChars, pos + 1);
  155. if (pos != std::string::npos)
  156. {
  157. if (strValue.compare(pos, 8, "CHARSET=", 8) == 0)
  158. {
  159. pos += 8; // move position to char after 'CHARSET='
  160. size_t len = strValue.find(';', pos);
  161. if (len != std::string::npos)
  162. len -= pos;
  163. std::string charset(strValue, pos, len); // intentionally ignoring possible ';' inside quoted string
  164. // as we don't support any charset with ';' in name
  165. StringUtils::Trim(charset, m_whitespaceChars);
  166. if (!charset.empty())
  167. {
  168. if (charset[0] != '"')
  169. return charset;
  170. else
  171. { // charset contains quoted string (allowed according to RFC 2616)
  172. StringUtils::Replace(charset, "\\", ""); // unescape chars, ignoring possible '\"' and '\\'
  173. const size_t closingQ = charset.find('"', 1);
  174. if (closingQ == std::string::npos)
  175. return ""; // no closing quote
  176. return charset.substr(1, closingQ - 1);
  177. }
  178. }
  179. }
  180. pos = strValue.find(';', pos); // find next parameter
  181. }
  182. }
  183. return ""; // no charset is detected
  184. }
  185. void CHttpHeader::Clear()
  186. {
  187. m_params.clear();
  188. m_protoLine.clear();
  189. m_headerdone = false;
  190. m_lastHeaderLine.clear();
  191. }