]> git.donarmstrong.com Git - bamtools.git/blob - src/api/internal/io/HttpHeader_p.cpp
1398d4cfeb041feb33375e3d593f307d7d37e479
[bamtools.git] / src / api / internal / io / HttpHeader_p.cpp
1 #include "api/internal/io/HttpHeader_p.h"
2 using namespace BamTools;
3 using namespace BamTools::Internal;
4
5 #include <cstdlib>
6 #include <sstream>
7 #include <vector>
8 using namespace std;
9
10 namespace BamTools {
11
12 // -----------
13 // constants
14 // -----------
15
16 namespace Constants {
17
18 static const char CAR_RET_CHAR = '\r';
19 static const char COLON_CHAR   = ':';
20 static const char DOT_CHAR     = '.';
21 static const char NEWLINE_CHAR = '\n';
22 static const char SPACE_CHAR   = ' ';
23 static const char TAB_CHAR     = '\t';
24
25 static const string FIELD_NEWLINE   = "\r\n";
26 static const string FIELD_SEPARATOR = ": ";
27 static const string HTTP_STRING     = "HTTP/";
28
29 } // namespace Constants
30
31 // ------------------------
32 // static utility methods
33 // ------------------------
34
35 namespace Internal {
36
37 static inline
38 bool IsSpace(const char c) {
39     const int n = static_cast<int>(c);
40     return ( n== 0 || (n <= 13 && n >= 9) );
41 }
42
43 // split on hitting single char delim
44 static vector<string> Split(const string& source, const char delim) {
45     stringstream ss(source);
46     string field;
47     vector<string> fields;
48     while ( getline(ss, field, delim) )
49         fields.push_back(field);
50     return fields;
51 }
52
53 static string Trim(const string& source) {
54
55     // skip if empty string
56     if ( source.empty() )
57         return source;
58
59     // fetch string data
60     const char*  s    = source.data(); // ignoring null-term on purpose
61     const size_t size = source.size();
62     size_t start = 0;
63     size_t end   = size-1;
64
65     // skip if no spaces at start or end
66     if ( !IsSpace(s[start]) && !IsSpace( s[end] ) )
67         return source;
68
69     // remove leading whitespace
70     while ( (start != end) && IsSpace(s[start]) )
71         ++start;
72
73     // remove trailing whitespace
74     if ( start <= end ) {
75         while ( end && IsSpace(s[end]) )
76             --end;
77     }
78
79     // return result
80     return string(s + start, (end-start) + 1);
81 }
82
83 } // namespace Internal
84 } // namespace BamTools
85
86 // ---------------------------
87 // HttpHeader implementation
88 // ---------------------------
89
90 HttpHeader::HttpHeader(void)
91     : m_isValid(true)
92     , m_majorVersion(1)
93     , m_minorVersion(1)
94 { }
95
96 HttpHeader::HttpHeader(const string& s)
97     : m_isValid(true)
98     , m_majorVersion(1)
99     , m_minorVersion(1)
100 {
101     Parse(s);
102 }
103
104 HttpHeader::~HttpHeader(void) { }
105
106 bool HttpHeader::ContainsKey(const string& key) const {
107     return ( m_fields.find(key) != m_fields.end() );
108 }
109
110 int HttpHeader::GetMajorVersion(void) const {
111     return m_majorVersion;
112 }
113
114 int HttpHeader::GetMinorVersion(void) const {
115     return m_minorVersion;
116 }
117
118 string HttpHeader::GetValue(const string& key) const {
119     if ( ContainsKey(key) )
120         return m_fields.at(key);
121     else return string();
122 }
123
124 bool HttpHeader::IsValid(void) const {
125     return m_isValid;
126 }
127
128 void HttpHeader::Parse(const string& s) {
129
130     // trim whitespace from input string
131     const string trimmed = Trim(s);
132
133     // split into list of header lines
134     vector<string> rawFields = Split(trimmed, Constants::NEWLINE_CHAR);
135
136     // prep our 'cleaned' fields container
137     vector<string> cleanFields;
138     cleanFields.reserve(rawFields.size());
139
140     // remove any empty fields and clean any trailing windows-style carriage returns ('\r')
141     vector<string>::iterator rawFieldIter = rawFields.begin();
142     vector<string>::iterator rawFieldEnd  = rawFields.end();
143     for ( ; rawFieldIter != rawFieldEnd; ++rawFieldIter ) {
144         string& field = (*rawFieldIter);
145
146         // skip empty fields
147         if ( field.empty() )
148             continue;
149
150         // remove carriage returns
151         const size_t fieldSize = field.size();
152         if ( field[fieldSize-1] == Constants::CAR_RET_CHAR )
153             field.resize(fieldSize-1);
154
155         // store cleaned field
156         cleanFields.push_back(field);
157     }
158
159     // skip add'l processing if nothing here
160     if ( cleanFields.empty() )
161         return;
162
163     // parse header lines
164     int lineNumber = 0;
165     vector<string>::const_iterator fieldIter = cleanFields.begin();
166     vector<string>::const_iterator fieldEnd  = cleanFields.end();
167     for ( ; fieldIter != fieldEnd; ++fieldIter, ++lineNumber ) {
168         if ( !ParseLine( (*fieldIter), lineNumber ) ) {
169             m_isValid = false;
170             return;
171         }
172     }
173 }
174
175 bool HttpHeader::ParseLine(const string& line, int) {
176
177     // find colon position, return failure if not found
178     const size_t colonFound = line.find(Constants::COLON_CHAR);
179     if ( colonFound == string::npos )
180         return false;
181
182     // store key/value (without leading/trailing whitespace) & return success
183     const string key   = Trim(line.substr(0, colonFound));
184     const string value = Trim(line.substr(colonFound+1));
185     m_fields[key] = value;
186     return true;
187 }
188
189 void HttpHeader::RemoveField(const string& key) {
190     m_fields.erase(key);
191 }
192
193 void HttpHeader::SetField(const string& key, const string& value) {
194     m_fields[key] = value;
195 }
196
197 void HttpHeader::SetValid(bool ok) {
198     m_isValid = ok;
199 }
200
201 void HttpHeader::SetVersion(int major, int minor) {
202     m_majorVersion = major;
203     m_minorVersion = minor;
204 }
205
206 string HttpHeader::ToString(void) const {
207     string result("");
208     if ( m_isValid ) {
209         map<string, string>::const_iterator fieldIter = m_fields.begin();
210         map<string, string>::const_iterator fieldEnd  = m_fields.end();
211         for ( ; fieldIter != fieldEnd; ++fieldIter ) {
212             const string& key   = (*fieldIter).first;
213             const string& value = (*fieldIter).second;
214             const string& line  = key   + Constants::FIELD_SEPARATOR +
215                                   value + Constants::FIELD_NEWLINE;
216             result += line;
217         }
218     }
219     return result;
220 }
221
222 // ----------------------------------
223 // HttpRequestHeader implementation
224 // ----------------------------------
225
226 HttpRequestHeader::HttpRequestHeader(const string& method,
227                                      const string& resource,
228                                      int majorVersion,
229                                      int minorVersion)
230     : HttpHeader()
231     , m_method(method)
232     , m_resource(resource)
233 {
234     SetVersion(majorVersion, minorVersion);
235 }
236
237 HttpRequestHeader::~HttpRequestHeader(void) { }
238
239 string HttpRequestHeader::GetMethod(void) const {
240     return m_method;
241 }
242
243 string HttpRequestHeader::GetResource(void) const {
244     return m_resource;
245 }
246
247 bool HttpRequestHeader::ParseLine(const string& line, int lineNumber) {
248
249     // if not 'request line', just let base class parse
250     if ( lineNumber != 0 )
251         return HttpHeader::ParseLine(line, lineNumber);
252
253     // fail if empty line
254     if ( line.empty() )
255         return false;
256
257     // walk through request line, storing positions
258     //    GET /path/to/resource HTTP/1.1
259     //    ^  ^^                ^^
260     const size_t foundMethod = line.find_first_not_of(Constants::SPACE_CHAR); // skip any leading whitespace
261     if ( foundMethod == string::npos ) return false;
262     const size_t foundFirstSpace = line.find(Constants::SPACE_CHAR, foundMethod+1);
263     if ( foundFirstSpace == string::npos ) return false;
264     const size_t foundResource = line.find_first_not_of(Constants::SPACE_CHAR, foundFirstSpace+1);
265     if ( foundResource == string::npos ) return false;
266     const size_t foundSecondSpace = line.find(Constants::SPACE_CHAR, foundResource+1);
267     if ( foundSecondSpace == string::npos ) return false;
268     const size_t foundVersion= line.find_first_not_of(Constants::SPACE_CHAR, foundSecondSpace+1);
269     if ( foundVersion == string::npos ) return false;
270
271     // parse out method & resource
272     m_method   = line.substr(foundMethod,   foundFirstSpace  - foundMethod);
273     m_resource = line.substr(foundResource, foundSecondSpace - foundResource);
274
275     // parse out version numbers
276     const string temp = line.substr(foundVersion);
277     if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
278         return false;
279     const int major = static_cast<int>(temp.at(5) - '0');
280     const int minor = static_cast<int>(temp.at(7) - '0');
281     SetVersion(major, minor);
282
283     // if we get here, return success
284     return true;
285 }
286
287 string HttpRequestHeader::ToString(void) const {
288     stringstream request("");
289     request << m_method   << Constants::SPACE_CHAR
290             << m_resource << Constants::SPACE_CHAR
291             << Constants::HTTP_STRING << GetMajorVersion() << Constants::DOT_CHAR << GetMinorVersion()
292             << Constants::FIELD_NEWLINE
293             << HttpHeader::ToString()
294             << Constants::FIELD_NEWLINE;
295     return request.str();
296 }
297
298 // -----------------------------------
299 // HttpResponseHeader implementation
300 // -----------------------------------
301
302 HttpResponseHeader::HttpResponseHeader(const int statusCode,
303                                        const string& reason,
304                                        int majorVersion,
305                                        int minorVersion)
306
307     : HttpHeader()
308     , m_statusCode(statusCode)
309     , m_reason(reason)
310 {
311     SetVersion(majorVersion, minorVersion);
312 }
313
314 HttpResponseHeader::HttpResponseHeader(const string& s)
315     : HttpHeader()
316     , m_statusCode(0)
317 {
318     Parse(s);
319 }
320
321 HttpResponseHeader::~HttpResponseHeader(void) { }
322
323 string HttpResponseHeader::GetReason(void) const  {
324     return m_reason;
325 }
326
327 int HttpResponseHeader::GetStatusCode(void) const {
328     return m_statusCode;
329 }
330
331 bool HttpResponseHeader::ParseLine(const string& line, int lineNumber) {
332
333     // if not 'status line', just let base class
334     if ( lineNumber != 0 )
335         return HttpHeader::ParseLine(line, lineNumber);
336
337     // fail if empty line
338     if ( line.empty() )
339         return false;
340
341     // walk through status line, storing positions
342     //    HTTP/1.1 200 OK
343     //    ^       ^^  ^^
344
345     const size_t foundVersion = line.find_first_not_of(Constants::SPACE_CHAR); // skip any leading whitespace
346     if ( foundVersion == string::npos ) return false;
347     const size_t foundFirstSpace = line.find(Constants::SPACE_CHAR, foundVersion+1);
348     if ( foundFirstSpace == string::npos ) return false;
349     const size_t foundStatusCode = line.find_first_not_of(Constants::SPACE_CHAR, foundFirstSpace+1);
350     if ( foundStatusCode == string::npos ) return false;
351     const size_t foundSecondSpace = line.find(Constants::SPACE_CHAR, foundStatusCode+1);
352     if ( foundSecondSpace == string::npos ) return false;
353     const size_t foundReason= line.find_first_not_of(Constants::SPACE_CHAR, foundSecondSpace+1);
354     if ( foundReason == string::npos ) return false;
355
356     // parse version numbers
357     string temp = line.substr(foundVersion, foundFirstSpace - foundVersion);
358     if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
359         return false;
360     const int major = static_cast<int>(temp.at(5) - '0');
361     const int minor = static_cast<int>(temp.at(7) - '0');
362     SetVersion(major, minor);
363
364     // parse status code
365     temp = line.substr(foundStatusCode, foundSecondSpace - foundStatusCode);
366     if ( temp.size() != 3 ) return false;
367     m_statusCode = atoi( temp.c_str() );
368
369     // reason phrase should be everything else left
370     m_reason = line.substr(foundReason);
371
372     // if we get here, return success
373     return true;
374 }
375
376 string HttpResponseHeader::ToString(void) const {
377     stringstream response("");
378     response << Constants::HTTP_STRING << GetMajorVersion() << Constants::DOT_CHAR << GetMinorVersion()
379              << Constants::SPACE_CHAR  << m_statusCode
380              << Constants::SPACE_CHAR  << m_reason
381              << Constants::FIELD_NEWLINE
382              << HttpHeader::ToString()
383              << Constants::FIELD_NEWLINE;
384     return response.str();
385 }