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