]> git.donarmstrong.com Git - bamtools.git/blobdiff - src/api/internal/io/HttpHeader_p.cpp
Added FTP support (text-tested, not BAM)
[bamtools.git] / src / api / internal / io / HttpHeader_p.cpp
diff --git a/src/api/internal/io/HttpHeader_p.cpp b/src/api/internal/io/HttpHeader_p.cpp
new file mode 100644 (file)
index 0000000..1398d4c
--- /dev/null
@@ -0,0 +1,385 @@
+#include "api/internal/io/HttpHeader_p.h"
+using namespace BamTools;
+using namespace BamTools::Internal;
+
+#include <cstdlib>
+#include <sstream>
+#include <vector>
+using namespace std;
+
+namespace BamTools {
+
+// -----------
+// constants
+// -----------
+
+namespace Constants {
+
+static const char CAR_RET_CHAR = '\r';
+static const char COLON_CHAR   = ':';
+static const char DOT_CHAR     = '.';
+static const char NEWLINE_CHAR = '\n';
+static const char SPACE_CHAR   = ' ';
+static const char TAB_CHAR     = '\t';
+
+static const string FIELD_NEWLINE   = "\r\n";
+static const string FIELD_SEPARATOR = ": ";
+static const string HTTP_STRING     = "HTTP/";
+
+} // namespace Constants
+
+// ------------------------
+// static utility methods
+// ------------------------
+
+namespace Internal {
+
+static inline
+bool IsSpace(const char c) {
+    const int n = static_cast<int>(c);
+    return ( n== 0 || (n <= 13 && n >= 9) );
+}
+
+// split on hitting single char delim
+static vector<string> Split(const string& source, const char delim) {
+    stringstream ss(source);
+    string field;
+    vector<string> fields;
+    while ( getline(ss, field, delim) )
+        fields.push_back(field);
+    return fields;
+}
+
+static string Trim(const string& source) {
+
+    // skip if empty string
+    if ( source.empty() )
+        return source;
+
+    // fetch string data
+    const char*  s    = source.data(); // ignoring null-term on purpose
+    const size_t size = source.size();
+    size_t start = 0;
+    size_t end   = size-1;
+
+    // skip if no spaces at start or end
+    if ( !IsSpace(s[start]) && !IsSpace( s[end] ) )
+        return source;
+
+    // remove leading whitespace
+    while ( (start != end) && IsSpace(s[start]) )
+        ++start;
+
+    // remove trailing whitespace
+    if ( start <= end ) {
+        while ( end && IsSpace(s[end]) )
+            --end;
+    }
+
+    // return result
+    return string(s + start, (end-start) + 1);
+}
+
+} // namespace Internal
+} // namespace BamTools
+
+// ---------------------------
+// HttpHeader implementation
+// ---------------------------
+
+HttpHeader::HttpHeader(void)
+    : m_isValid(true)
+    , m_majorVersion(1)
+    , m_minorVersion(1)
+{ }
+
+HttpHeader::HttpHeader(const string& s)
+    : m_isValid(true)
+    , m_majorVersion(1)
+    , m_minorVersion(1)
+{
+    Parse(s);
+}
+
+HttpHeader::~HttpHeader(void) { }
+
+bool HttpHeader::ContainsKey(const string& key) const {
+    return ( m_fields.find(key) != m_fields.end() );
+}
+
+int HttpHeader::GetMajorVersion(void) const {
+    return m_majorVersion;
+}
+
+int HttpHeader::GetMinorVersion(void) const {
+    return m_minorVersion;
+}
+
+string HttpHeader::GetValue(const string& key) const {
+    if ( ContainsKey(key) )
+        return m_fields.at(key);
+    else return string();
+}
+
+bool HttpHeader::IsValid(void) const {
+    return m_isValid;
+}
+
+void HttpHeader::Parse(const string& s) {
+
+    // trim whitespace from input string
+    const string trimmed = Trim(s);
+
+    // split into list of header lines
+    vector<string> rawFields = Split(trimmed, Constants::NEWLINE_CHAR);
+
+    // prep our 'cleaned' fields container
+    vector<string> cleanFields;
+    cleanFields.reserve(rawFields.size());
+
+    // remove any empty fields and clean any trailing windows-style carriage returns ('\r')
+    vector<string>::iterator rawFieldIter = rawFields.begin();
+    vector<string>::iterator rawFieldEnd  = rawFields.end();
+    for ( ; rawFieldIter != rawFieldEnd; ++rawFieldIter ) {
+        string& field = (*rawFieldIter);
+
+        // skip empty fields
+        if ( field.empty() )
+            continue;
+
+        // remove carriage returns
+        const size_t fieldSize = field.size();
+        if ( field[fieldSize-1] == Constants::CAR_RET_CHAR )
+            field.resize(fieldSize-1);
+
+        // store cleaned field
+        cleanFields.push_back(field);
+    }
+
+    // skip add'l processing if nothing here
+    if ( cleanFields.empty() )
+        return;
+
+    // parse header lines
+    int lineNumber = 0;
+    vector<string>::const_iterator fieldIter = cleanFields.begin();
+    vector<string>::const_iterator fieldEnd  = cleanFields.end();
+    for ( ; fieldIter != fieldEnd; ++fieldIter, ++lineNumber ) {
+        if ( !ParseLine( (*fieldIter), lineNumber ) ) {
+            m_isValid = false;
+            return;
+        }
+    }
+}
+
+bool HttpHeader::ParseLine(const string& line, int) {
+
+    // find colon position, return failure if not found
+    const size_t colonFound = line.find(Constants::COLON_CHAR);
+    if ( colonFound == string::npos )
+        return false;
+
+    // store key/value (without leading/trailing whitespace) & return success
+    const string key   = Trim(line.substr(0, colonFound));
+    const string value = Trim(line.substr(colonFound+1));
+    m_fields[key] = value;
+    return true;
+}
+
+void HttpHeader::RemoveField(const string& key) {
+    m_fields.erase(key);
+}
+
+void HttpHeader::SetField(const string& key, const string& value) {
+    m_fields[key] = value;
+}
+
+void HttpHeader::SetValid(bool ok) {
+    m_isValid = ok;
+}
+
+void HttpHeader::SetVersion(int major, int minor) {
+    m_majorVersion = major;
+    m_minorVersion = minor;
+}
+
+string HttpHeader::ToString(void) const {
+    string result("");
+    if ( m_isValid ) {
+        map<string, string>::const_iterator fieldIter = m_fields.begin();
+        map<string, string>::const_iterator fieldEnd  = m_fields.end();
+        for ( ; fieldIter != fieldEnd; ++fieldIter ) {
+            const string& key   = (*fieldIter).first;
+            const string& value = (*fieldIter).second;
+            const string& line  = key   + Constants::FIELD_SEPARATOR +
+                                  value + Constants::FIELD_NEWLINE;
+            result += line;
+        }
+    }
+    return result;
+}
+
+// ----------------------------------
+// HttpRequestHeader implementation
+// ----------------------------------
+
+HttpRequestHeader::HttpRequestHeader(const string& method,
+                                     const string& resource,
+                                     int majorVersion,
+                                     int minorVersion)
+    : HttpHeader()
+    , m_method(method)
+    , m_resource(resource)
+{
+    SetVersion(majorVersion, minorVersion);
+}
+
+HttpRequestHeader::~HttpRequestHeader(void) { }
+
+string HttpRequestHeader::GetMethod(void) const {
+    return m_method;
+}
+
+string HttpRequestHeader::GetResource(void) const {
+    return m_resource;
+}
+
+bool HttpRequestHeader::ParseLine(const string& line, int lineNumber) {
+
+    // if not 'request line', just let base class parse
+    if ( lineNumber != 0 )
+        return HttpHeader::ParseLine(line, lineNumber);
+
+    // fail if empty line
+    if ( line.empty() )
+        return false;
+
+    // walk through request line, storing positions
+    //    GET /path/to/resource HTTP/1.1
+    //    ^  ^^                ^^
+    const size_t foundMethod = line.find_first_not_of(Constants::SPACE_CHAR); // skip any leading whitespace
+    if ( foundMethod == string::npos ) return false;
+    const size_t foundFirstSpace = line.find(Constants::SPACE_CHAR, foundMethod+1);
+    if ( foundFirstSpace == string::npos ) return false;
+    const size_t foundResource = line.find_first_not_of(Constants::SPACE_CHAR, foundFirstSpace+1);
+    if ( foundResource == string::npos ) return false;
+    const size_t foundSecondSpace = line.find(Constants::SPACE_CHAR, foundResource+1);
+    if ( foundSecondSpace == string::npos ) return false;
+    const size_t foundVersion= line.find_first_not_of(Constants::SPACE_CHAR, foundSecondSpace+1);
+    if ( foundVersion == string::npos ) return false;
+
+    // parse out method & resource
+    m_method   = line.substr(foundMethod,   foundFirstSpace  - foundMethod);
+    m_resource = line.substr(foundResource, foundSecondSpace - foundResource);
+
+    // parse out version numbers
+    const string temp = line.substr(foundVersion);
+    if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
+        return false;
+    const int major = static_cast<int>(temp.at(5) - '0');
+    const int minor = static_cast<int>(temp.at(7) - '0');
+    SetVersion(major, minor);
+
+    // if we get here, return success
+    return true;
+}
+
+string HttpRequestHeader::ToString(void) const {
+    stringstream request("");
+    request << m_method   << Constants::SPACE_CHAR
+            << m_resource << Constants::SPACE_CHAR
+            << Constants::HTTP_STRING << GetMajorVersion() << Constants::DOT_CHAR << GetMinorVersion()
+            << Constants::FIELD_NEWLINE
+            << HttpHeader::ToString()
+            << Constants::FIELD_NEWLINE;
+    return request.str();
+}
+
+// -----------------------------------
+// HttpResponseHeader implementation
+// -----------------------------------
+
+HttpResponseHeader::HttpResponseHeader(const int statusCode,
+                                       const string& reason,
+                                       int majorVersion,
+                                       int minorVersion)
+
+    : HttpHeader()
+    , m_statusCode(statusCode)
+    , m_reason(reason)
+{
+    SetVersion(majorVersion, minorVersion);
+}
+
+HttpResponseHeader::HttpResponseHeader(const string& s)
+    : HttpHeader()
+    , m_statusCode(0)
+{
+    Parse(s);
+}
+
+HttpResponseHeader::~HttpResponseHeader(void) { }
+
+string HttpResponseHeader::GetReason(void) const  {
+    return m_reason;
+}
+
+int HttpResponseHeader::GetStatusCode(void) const {
+    return m_statusCode;
+}
+
+bool HttpResponseHeader::ParseLine(const string& line, int lineNumber) {
+
+    // if not 'status line', just let base class
+    if ( lineNumber != 0 )
+        return HttpHeader::ParseLine(line, lineNumber);
+
+    // fail if empty line
+    if ( line.empty() )
+        return false;
+
+    // walk through status line, storing positions
+    //    HTTP/1.1 200 OK
+    //    ^       ^^  ^^
+
+    const size_t foundVersion = line.find_first_not_of(Constants::SPACE_CHAR); // skip any leading whitespace
+    if ( foundVersion == string::npos ) return false;
+    const size_t foundFirstSpace = line.find(Constants::SPACE_CHAR, foundVersion+1);
+    if ( foundFirstSpace == string::npos ) return false;
+    const size_t foundStatusCode = line.find_first_not_of(Constants::SPACE_CHAR, foundFirstSpace+1);
+    if ( foundStatusCode == string::npos ) return false;
+    const size_t foundSecondSpace = line.find(Constants::SPACE_CHAR, foundStatusCode+1);
+    if ( foundSecondSpace == string::npos ) return false;
+    const size_t foundReason= line.find_first_not_of(Constants::SPACE_CHAR, foundSecondSpace+1);
+    if ( foundReason == string::npos ) return false;
+
+    // parse version numbers
+    string temp = line.substr(foundVersion, foundFirstSpace - foundVersion);
+    if ( (temp.find(Constants::HTTP_STRING) != 0) || (temp.size() != 8) )
+        return false;
+    const int major = static_cast<int>(temp.at(5) - '0');
+    const int minor = static_cast<int>(temp.at(7) - '0');
+    SetVersion(major, minor);
+
+    // parse status code
+    temp = line.substr(foundStatusCode, foundSecondSpace - foundStatusCode);
+    if ( temp.size() != 3 ) return false;
+    m_statusCode = atoi( temp.c_str() );
+
+    // reason phrase should be everything else left
+    m_reason = line.substr(foundReason);
+
+    // if we get here, return success
+    return true;
+}
+
+string HttpResponseHeader::ToString(void) const {
+    stringstream response("");
+    response << Constants::HTTP_STRING << GetMajorVersion() << Constants::DOT_CHAR << GetMinorVersion()
+             << Constants::SPACE_CHAR  << m_statusCode
+             << Constants::SPACE_CHAR  << m_reason
+             << Constants::FIELD_NEWLINE
+             << HttpHeader::ToString()
+             << Constants::FIELD_NEWLINE;
+    return response.str();
+}