1 #include "api/internal/io/HttpHeader_p.h"
2 using namespace BamTools;
3 using namespace BamTools::Internal;
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';
25 static const string FIELD_NEWLINE = "\r\n";
26 static const string FIELD_SEPARATOR = ": ";
27 static const string HTTP_STRING = "HTTP/";
29 } // namespace Constants
31 // ------------------------
32 // static utility methods
33 // ------------------------
38 bool IsSpace(const char c) {
39 const int n = static_cast<int>(c);
40 return ( n== 0 || (n <= 13 && n >= 9) );
43 // split on hitting single char delim
44 static vector<string> Split(const string& source, const char delim) {
45 stringstream ss(source);
47 vector<string> fields;
48 while ( getline(ss, field, delim) )
49 fields.push_back(field);
53 static string Trim(const string& source) {
55 // skip if empty string
60 const char* s = source.data(); // ignoring null-term on purpose
61 const size_t size = source.size();
65 // skip if no spaces at start or end
66 if ( !IsSpace(s[start]) && !IsSpace( s[end] ) )
69 // remove leading whitespace
70 while ( (start != end) && IsSpace(s[start]) )
73 // remove trailing whitespace
75 while ( end && IsSpace(s[end]) )
80 return string(s + start, (end-start) + 1);
83 } // namespace Internal
84 } // namespace BamTools
86 // ---------------------------
87 // HttpHeader implementation
88 // ---------------------------
90 HttpHeader::HttpHeader(void)
96 HttpHeader::HttpHeader(const string& s)
104 HttpHeader::~HttpHeader(void) { }
106 bool HttpHeader::ContainsKey(const string& key) const {
107 return ( m_fields.find(key) != m_fields.end() );
110 int HttpHeader::GetMajorVersion(void) const {
111 return m_majorVersion;
114 int HttpHeader::GetMinorVersion(void) const {
115 return m_minorVersion;
118 string HttpHeader::GetValue(const string& key) const {
119 if ( ContainsKey(key) )
120 return m_fields.at(key);
121 else return string();
124 bool HttpHeader::IsValid(void) const {
128 void HttpHeader::Parse(const string& s) {
130 // trim whitespace from input string
131 const string trimmed = Trim(s);
133 // split into list of header lines
134 vector<string> rawFields = Split(trimmed, Constants::NEWLINE_CHAR);
136 // prep our 'cleaned' fields container
137 vector<string> cleanFields;
138 cleanFields.reserve(rawFields.size());
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);
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);
155 // store cleaned field
156 cleanFields.push_back(field);
159 // skip add'l processing if nothing here
160 if ( cleanFields.empty() )
163 // parse header lines
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 ) ) {
175 bool HttpHeader::ParseLine(const string& line, int) {
177 // find colon position, return failure if not found
178 const size_t colonFound = line.find(Constants::COLON_CHAR);
179 if ( colonFound == string::npos )
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;
189 void HttpHeader::RemoveField(const string& key) {
193 void HttpHeader::SetField(const string& key, const string& value) {
194 m_fields[key] = value;
197 void HttpHeader::SetValid(bool ok) {
201 void HttpHeader::SetVersion(int major, int minor) {
202 m_majorVersion = major;
203 m_minorVersion = minor;
206 string HttpHeader::ToString(void) const {
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;
222 // ----------------------------------
223 // HttpRequestHeader implementation
224 // ----------------------------------
226 HttpRequestHeader::HttpRequestHeader(const string& method,
227 const string& resource,
232 , m_resource(resource)
234 SetVersion(majorVersion, minorVersion);
237 HttpRequestHeader::~HttpRequestHeader(void) { }
239 string HttpRequestHeader::GetMethod(void) const {
243 string HttpRequestHeader::GetResource(void) const {
247 bool HttpRequestHeader::ParseLine(const string& line, int lineNumber) {
249 // if not 'request line', just let base class parse
250 if ( lineNumber != 0 )
251 return HttpHeader::ParseLine(line, lineNumber);
253 // fail if empty line
257 // walk through request line, storing positions
258 // GET /path/to/resource HTTP/1.1
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;
271 // parse out method & resource
272 m_method = line.substr(foundMethod, foundFirstSpace - foundMethod);
273 m_resource = line.substr(foundResource, foundSecondSpace - foundResource);
275 // parse out version numbers
276 const string temp = line.substr(foundVersion);
277 if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
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);
283 // if we get here, return success
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();
298 // -----------------------------------
299 // HttpResponseHeader implementation
300 // -----------------------------------
302 HttpResponseHeader::HttpResponseHeader(const int statusCode,
303 const string& reason,
308 , m_statusCode(statusCode)
311 SetVersion(majorVersion, minorVersion);
314 HttpResponseHeader::HttpResponseHeader(const string& s)
321 HttpResponseHeader::~HttpResponseHeader(void) { }
323 string HttpResponseHeader::GetReason(void) const {
327 int HttpResponseHeader::GetStatusCode(void) const {
331 bool HttpResponseHeader::ParseLine(const string& line, int lineNumber) {
333 // if not 'status line', just let base class
334 if ( lineNumber != 0 )
335 return HttpHeader::ParseLine(line, lineNumber);
337 // fail if empty line
341 // walk through status line, storing positions
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;
356 // parse version numbers
357 string temp = line.substr(foundVersion, foundFirstSpace - foundVersion);
358 if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
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);
365 temp = line.substr(foundStatusCode, foundSecondSpace - foundStatusCode);
366 if ( temp.size() != 3 ) return false;
367 m_statusCode = atoi( temp.c_str() );
369 // reason phrase should be everything else left
370 m_reason = line.substr(foundReason);
372 // if we get here, return success
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();