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 // ***************************************************************************
11 #include "api/internal/io/HttpHeader_p.h"
12 using namespace BamTools;
13 using namespace BamTools::Internal;
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';
35 static const string FIELD_NEWLINE = "\r\n";
36 static const string FIELD_SEPARATOR = ": ";
37 static const string HTTP_STRING = "HTTP/";
39 } // namespace Constants
41 // ------------------------
42 // static utility methods
43 // ------------------------
48 bool IsSpace(const char c) {
49 const int n = static_cast<int>(c);
50 return ( n== 0 || (n <= 13 && n >= 9) );
53 // split on hitting single char delim
54 static vector<string> Split(const string& source, const char delim) {
55 stringstream ss(source);
57 vector<string> fields;
58 while ( getline(ss, field, delim) )
59 fields.push_back(field);
63 static string Trim(const string& source) {
65 // skip if empty string
70 const char* s = source.data(); // ignoring null-term on purpose
71 const size_t size = source.size();
75 // skip if no spaces at start or end
76 if ( !IsSpace(s[start]) && !IsSpace( s[end] ) )
79 // remove leading whitespace
80 while ( (start != end) && IsSpace(s[start]) )
83 // remove trailing whitespace
85 while ( end && IsSpace(s[end]) )
90 return string(s + start, (end-start) + 1);
93 } // namespace Internal
94 } // namespace BamTools
96 // ---------------------------
97 // HttpHeader implementation
98 // ---------------------------
100 HttpHeader::HttpHeader(void)
106 HttpHeader::HttpHeader(const string& s)
114 HttpHeader::~HttpHeader(void) { }
116 bool HttpHeader::ContainsKey(const string& key) const {
117 return ( m_fields.find(key) != m_fields.end() );
120 int HttpHeader::GetMajorVersion(void) const {
121 return m_majorVersion;
124 int HttpHeader::GetMinorVersion(void) const {
125 return m_minorVersion;
128 string HttpHeader::GetValue(const string& key) const {
129 if ( ContainsKey(key) )
130 return m_fields[key];
131 else return string();
134 bool HttpHeader::IsValid(void) const {
138 void HttpHeader::Parse(const string& s) {
140 // trim whitespace from input string
141 const string trimmed = Trim(s);
143 // split into list of header lines
144 vector<string> rawFields = Split(trimmed, Constants::NEWLINE_CHAR);
146 // prep our 'cleaned' fields container
147 vector<string> cleanFields;
148 cleanFields.reserve(rawFields.size());
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);
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);
165 // store cleaned field
166 cleanFields.push_back(field);
169 // skip add'l processing if nothing here
170 if ( cleanFields.empty() )
173 // parse header lines
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 ) ) {
185 bool HttpHeader::ParseLine(const string& line, int) {
187 // find colon position, return failure if not found
188 const size_t colonFound = line.find(Constants::COLON_CHAR);
189 if ( colonFound == string::npos )
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;
199 void HttpHeader::RemoveField(const string& key) {
203 void HttpHeader::SetField(const string& key, const string& value) {
204 m_fields[key] = value;
207 void HttpHeader::SetValid(bool ok) {
211 void HttpHeader::SetVersion(int major, int minor) {
212 m_majorVersion = major;
213 m_minorVersion = minor;
216 string HttpHeader::ToString(void) const {
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;
232 // ----------------------------------
233 // HttpRequestHeader implementation
234 // ----------------------------------
236 HttpRequestHeader::HttpRequestHeader(const string& method,
237 const string& resource,
242 , m_resource(resource)
244 SetVersion(majorVersion, minorVersion);
247 HttpRequestHeader::~HttpRequestHeader(void) { }
249 string HttpRequestHeader::GetMethod(void) const {
253 string HttpRequestHeader::GetResource(void) const {
257 bool HttpRequestHeader::ParseLine(const string& line, int lineNumber) {
259 // if not 'request line', just let base class parse
260 if ( lineNumber != 0 )
261 return HttpHeader::ParseLine(line, lineNumber);
263 // fail if empty line
267 // walk through request line, storing positions
268 // GET /path/to/resource HTTP/1.1
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;
281 // parse out method & resource
282 m_method = line.substr(foundMethod, foundFirstSpace - foundMethod);
283 m_resource = line.substr(foundResource, foundSecondSpace - foundResource);
285 // parse out version numbers
286 const string temp = line.substr(foundVersion);
287 if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
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);
293 // if we get here, return success
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();
308 // -----------------------------------
309 // HttpResponseHeader implementation
310 // -----------------------------------
312 HttpResponseHeader::HttpResponseHeader(const int statusCode,
313 const string& reason,
318 , m_statusCode(statusCode)
321 SetVersion(majorVersion, minorVersion);
324 HttpResponseHeader::HttpResponseHeader(const string& s)
331 HttpResponseHeader::~HttpResponseHeader(void) { }
333 string HttpResponseHeader::GetReason(void) const {
337 int HttpResponseHeader::GetStatusCode(void) const {
341 bool HttpResponseHeader::ParseLine(const string& line, int lineNumber) {
343 // if not 'status line', just let base class
344 if ( lineNumber != 0 )
345 return HttpHeader::ParseLine(line, lineNumber);
347 // fail if empty line
351 // walk through status line, storing positions
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;
366 // parse version numbers
367 string temp = line.substr(foundVersion, foundFirstSpace - foundVersion);
368 if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
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);
375 temp = line.substr(foundStatusCode, foundSecondSpace - foundStatusCode);
376 if ( temp.size() != 3 ) return false;
377 m_statusCode = atoi( temp.c_str() );
379 // reason phrase should be everything else left
380 m_reason = line.substr(foundReason);
382 // if we get here, return success
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();