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