diff options
Diffstat (limited to 'xbmc/utils/HttpHeader.cpp')
| -rw-r--r-- | xbmc/utils/HttpHeader.cpp | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/xbmc/utils/HttpHeader.cpp b/xbmc/utils/HttpHeader.cpp new file mode 100644 index 0000000..ad73bb2 --- /dev/null +++ b/xbmc/utils/HttpHeader.cpp | |||
| @@ -0,0 +1,239 @@ | |||
| 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 | |||
| 9 | #include "HttpHeader.h" | ||
| 10 | |||
| 11 | #include "utils/StringUtils.h" | ||
| 12 | |||
| 13 | // header white space characters according to RFC 2616 | ||
| 14 | const char* const CHttpHeader::m_whitespaceChars = " \t"; | ||
| 15 | |||
| 16 | |||
| 17 | CHttpHeader::CHttpHeader() | ||
| 18 | { | ||
| 19 | m_headerdone = false; | ||
| 20 | } | ||
| 21 | |||
| 22 | CHttpHeader::~CHttpHeader() = default; | ||
| 23 | |||
| 24 | void CHttpHeader::Parse(const std::string& strData) | ||
| 25 | { | ||
| 26 | size_t pos = 0; | ||
| 27 | const size_t len = strData.length(); | ||
| 28 | const char* const strDataC = strData.c_str(); | ||
| 29 | |||
| 30 | // According to RFC 2616 any header line can have continuation on next line, if next line is started from whitespace char | ||
| 31 | // 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 | ||
| 32 | // 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) | ||
| 33 | while (pos < len) | ||
| 34 | { | ||
| 35 | size_t lineEnd = strData.find('\x0a', pos); // use '\x0a' instead of '\n' to be platform independent | ||
| 36 | |||
| 37 | if (lineEnd == std::string::npos) | ||
| 38 | return; // error: expected only complete lines | ||
| 39 | |||
| 40 | const size_t nextLine = lineEnd + 1; | ||
| 41 | if (lineEnd > pos && strDataC[lineEnd - 1] == '\x0d') // use '\x0d' instead of '\r' to be platform independent | ||
| 42 | lineEnd--; | ||
| 43 | |||
| 44 | if (m_headerdone) | ||
| 45 | Clear(); // clear previous header and process new one | ||
| 46 | |||
| 47 | if (strDataC[pos] == ' ' || strDataC[pos] == '\t') // same chars as in CHttpHeader::m_whitespaceChars | ||
| 48 | { // line is started from whitespace char: this is continuation of previous line | ||
| 49 | pos = strData.find_first_not_of(m_whitespaceChars, pos); | ||
| 50 | |||
| 51 | m_lastHeaderLine.push_back(' '); // replace all whitespace chars at start of the line with single space | ||
| 52 | m_lastHeaderLine.append(strData, pos, lineEnd - pos); // append current line | ||
| 53 | } | ||
| 54 | else | ||
| 55 | { // this line is NOT continuation, this line is new header line | ||
| 56 | if (!m_lastHeaderLine.empty()) | ||
| 57 | ParseLine(m_lastHeaderLine); // process previously stored completed line (if any) | ||
| 58 | |||
| 59 | m_lastHeaderLine.assign(strData, pos, lineEnd - pos); // store current line to (possibly) complete later. Will be parsed on next turns. | ||
| 60 | |||
| 61 | if (pos == lineEnd) | ||
| 62 | m_headerdone = true; // current line is bare "\r\n" (or "\n"), means end of header; no need to process current m_lastHeaderLine | ||
| 63 | } | ||
| 64 | |||
| 65 | pos = nextLine; // go to next line (if any) | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | bool CHttpHeader::ParseLine(const std::string& headerLine) | ||
| 70 | { | ||
| 71 | const size_t valueStart = headerLine.find(':'); | ||
| 72 | |||
| 73 | if (valueStart != std::string::npos) | ||
| 74 | { | ||
| 75 | std::string strParam(headerLine, 0, valueStart); | ||
| 76 | std::string strValue(headerLine, valueStart + 1); | ||
| 77 | |||
| 78 | StringUtils::Trim(strParam, m_whitespaceChars); | ||
| 79 | StringUtils::ToLower(strParam); | ||
| 80 | |||
| 81 | StringUtils::Trim(strValue, m_whitespaceChars); | ||
| 82 | |||
| 83 | if (!strParam.empty() && !strValue.empty()) | ||
| 84 | m_params.push_back(HeaderParams::value_type(strParam, strValue)); | ||
| 85 | else | ||
| 86 | return false; | ||
| 87 | } | ||
| 88 | else if (m_protoLine.empty()) | ||
| 89 | m_protoLine = headerLine; | ||
| 90 | |||
| 91 | return true; | ||
| 92 | } | ||
| 93 | |||
| 94 | void CHttpHeader::AddParam(const std::string& param, const std::string& value, const bool overwrite /*= false*/) | ||
| 95 | { | ||
| 96 | std::string paramLower(param); | ||
| 97 | StringUtils::ToLower(paramLower); | ||
| 98 | StringUtils::Trim(paramLower, m_whitespaceChars); | ||
| 99 | if (paramLower.empty()) | ||
| 100 | return; | ||
| 101 | |||
| 102 | if (overwrite) | ||
| 103 | { // delete ALL parameters with the same name | ||
| 104 | // note: 'GetValue' always returns last added parameter, | ||
| 105 | // so you probably don't need to overwrite | ||
| 106 | for (size_t i = 0; i < m_params.size();) | ||
| 107 | { | ||
| 108 | if (m_params[i].first == paramLower) | ||
| 109 | m_params.erase(m_params.begin() + i); | ||
| 110 | else | ||
| 111 | ++i; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | std::string valueTrim(value); | ||
| 116 | StringUtils::Trim(valueTrim, m_whitespaceChars); | ||
| 117 | if (valueTrim.empty()) | ||
| 118 | return; | ||
| 119 | |||
| 120 | m_params.push_back(HeaderParams::value_type(paramLower, valueTrim)); | ||
| 121 | } | ||
| 122 | |||
| 123 | std::string CHttpHeader::GetValue(const std::string& strParam) const | ||
| 124 | { | ||
| 125 | std::string paramLower(strParam); | ||
| 126 | StringUtils::ToLower(paramLower); | ||
| 127 | |||
| 128 | return GetValueRaw(paramLower); | ||
| 129 | } | ||
| 130 | |||
| 131 | std::string CHttpHeader::GetValueRaw(const std::string& strParam) const | ||
| 132 | { | ||
| 133 | // look in reverse to find last parameter (probably most important) | ||
| 134 | for (HeaderParams::const_reverse_iterator iter = m_params.rbegin(); iter != m_params.rend(); ++iter) | ||
| 135 | { | ||
| 136 | if (iter->first == strParam) | ||
| 137 | return iter->second; | ||
| 138 | } | ||
| 139 | |||
| 140 | return ""; | ||
| 141 | } | ||
| 142 | |||
| 143 | std::vector<std::string> CHttpHeader::GetValues(std::string strParam) const | ||
| 144 | { | ||
| 145 | StringUtils::ToLower(strParam); | ||
| 146 | std::vector<std::string> values; | ||
| 147 | |||
| 148 | for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter) | ||
| 149 | { | ||
| 150 | if (iter->first == strParam) | ||
| 151 | values.push_back(iter->second); | ||
| 152 | } | ||
| 153 | |||
| 154 | return values; | ||
| 155 | } | ||
| 156 | |||
| 157 | std::string CHttpHeader::GetHeader(void) const | ||
| 158 | { | ||
| 159 | if (m_protoLine.empty() && m_params.empty()) | ||
| 160 | return ""; | ||
| 161 | |||
| 162 | std::string strHeader(m_protoLine + "\r\n"); | ||
| 163 | |||
| 164 | for (HeaderParams::const_iterator iter = m_params.begin(); iter != m_params.end(); ++iter) | ||
| 165 | strHeader += ((*iter).first + ": " + (*iter).second + "\r\n"); | ||
| 166 | |||
| 167 | strHeader += "\r\n"; | ||
| 168 | return strHeader; | ||
| 169 | } | ||
| 170 | |||
| 171 | std::string CHttpHeader::GetMimeType(void) const | ||
| 172 | { | ||
| 173 | std::string strValue(GetValueRaw("content-type")); | ||
| 174 | |||
| 175 | std::string mimeType(strValue, 0, strValue.find(';')); | ||
| 176 | StringUtils::TrimRight(mimeType, m_whitespaceChars); | ||
| 177 | |||
| 178 | return mimeType; | ||
| 179 | } | ||
| 180 | |||
| 181 | std::string CHttpHeader::GetCharset(void) const | ||
| 182 | { | ||
| 183 | std::string strValue(GetValueRaw("content-type")); | ||
| 184 | if (strValue.empty()) | ||
| 185 | return strValue; | ||
| 186 | |||
| 187 | StringUtils::ToUpper(strValue); | ||
| 188 | const size_t len = strValue.length(); | ||
| 189 | |||
| 190 | // extract charset value from 'contenttype/contentsubtype;pram1=param1Val ; charset=XXXX\t;param2=param2Val' | ||
| 191 | // most common form: 'text/html; charset=XXXX' | ||
| 192 | // charset value can be in double quotes: 'text/xml; charset="XXX XX"' | ||
| 193 | |||
| 194 | size_t pos = strValue.find(';'); | ||
| 195 | while (pos < len) | ||
| 196 | { | ||
| 197 | // move to the next non-whitespace character | ||
| 198 | pos = strValue.find_first_not_of(m_whitespaceChars, pos + 1); | ||
| 199 | |||
| 200 | if (pos != std::string::npos) | ||
| 201 | { | ||
| 202 | if (strValue.compare(pos, 8, "CHARSET=", 8) == 0) | ||
| 203 | { | ||
| 204 | pos += 8; // move position to char after 'CHARSET=' | ||
| 205 | size_t len = strValue.find(';', pos); | ||
| 206 | if (len != std::string::npos) | ||
| 207 | len -= pos; | ||
| 208 | std::string charset(strValue, pos, len); // intentionally ignoring possible ';' inside quoted string | ||
| 209 | // as we don't support any charset with ';' in name | ||
| 210 | StringUtils::Trim(charset, m_whitespaceChars); | ||
| 211 | if (!charset.empty()) | ||
| 212 | { | ||
| 213 | if (charset[0] != '"') | ||
| 214 | return charset; | ||
| 215 | else | ||
| 216 | { // charset contains quoted string (allowed according to RFC 2616) | ||
| 217 | StringUtils::Replace(charset, "\\", ""); // unescape chars, ignoring possible '\"' and '\\' | ||
| 218 | const size_t closingQ = charset.find('"', 1); | ||
| 219 | if (closingQ == std::string::npos) | ||
| 220 | return ""; // no closing quote | ||
| 221 | |||
| 222 | return charset.substr(1, closingQ - 1); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | pos = strValue.find(';', pos); // find next parameter | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | return ""; // no charset is detected | ||
| 231 | } | ||
| 232 | |||
| 233 | void CHttpHeader::Clear() | ||
| 234 | { | ||
| 235 | m_params.clear(); | ||
| 236 | m_protoLine.clear(); | ||
| 237 | m_headerdone = false; | ||
| 238 | m_lastHeaderLine.clear(); | ||
| 239 | } | ||
