]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Update YaHTTP from upstream (fixes several bugs) 1490/head
authorAki Tuomi <cmouse@desteem.org>
Wed, 25 Jun 2014 20:31:40 +0000 (23:31 +0300)
committerAki Tuomi <cmouse@desteem.org>
Wed, 25 Jun 2014 22:35:04 +0000 (01:35 +0300)
pdns/ext/yahttp/yahttp/cookie.hpp
pdns/ext/yahttp/yahttp/exception.hpp
pdns/ext/yahttp/yahttp/reqresp.cpp
pdns/ext/yahttp/yahttp/reqresp.hpp
pdns/ext/yahttp/yahttp/url.hpp
pdns/ext/yahttp/yahttp/utility.hpp

index 10070d1d5da3f2d084e373b1dd5c6fa22ae5aef6..4b285aa760c4459fe2edbebe5d50d0ef28f274c8 100644 (file)
@@ -43,14 +43,15 @@ namespace YaHTTP {
      }; //!< Stringify the cookie
   };
 
+  /*! Implements a Cookie jar for storing multiple cookies */
   class CookieJar {
     public:
-    std::map<std::string, Cookie> cookies; 
+    std::map<std::string, Cookie> cookies;  //<! cookie container
   
-    CookieJar() {};
+    CookieJar() {}; //<! constructs empty cookie jar
     CookieJar(const CookieJar & rhs) {
       this->cookies = rhs.cookies;
-    }
+    } //<! copy cookies from another cookie jar
   
     void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value) {
       size_t pos;
@@ -58,7 +59,7 @@ namespace YaHTTP {
       if (pos == std::string::npos) throw "Not a Key-Value pair (cookie)";
       key = std::string(keyvalue.begin(), keyvalue.begin()+pos);
       value = std::string(keyvalue.begin()+pos+1, keyvalue.end());
-    }
+    } //<! key value pair parser
   
     void parseCookieHeader(const std::string &cookiestr) {
       std::list<Cookie> cookies;
@@ -129,6 +130,6 @@ namespace YaHTTP {
       for(std::list<Cookie>::iterator i = cookies.begin(); i != cookies.end(); i++) {
         this->cookies[i->name] = *i;
       }
-    };
+    }; //<! Parse multiple cookies from header 
   };
 };
index 51ff3faf18314183ee296df16bddd5a1d3fe3f49..2d7afbf5dcc11fa76be0ef80153b316c53fb4414 100644 (file)
@@ -4,6 +4,7 @@
 #include <exception>
 
 namespace YaHTTP {
+  /*! Generic error class */
   class Error: public std::exception {
   public:
     Error() {};
@@ -14,8 +15,9 @@ namespace YaHTTP {
     {
       return reason.c_str();
     }
-    const std::string reason;
+    const std::string reason; //<! Cause of the error
   };
+  /*! Parse error class */
   class ParseError: public YaHTTP::Error {
   public:
     ParseError() {};
index fe51d4523d92cbf76a21135248a706be86a5d60d..d9d15ef096e746017265abf99755b56004c73610 100644 (file)
@@ -138,7 +138,7 @@ namespace YaHTTP {
       std::string getparms;
       // prepare URL 
       for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) {
-        getparmbuf << Utility::encodeURL(i->first) << "=" << Utility::encodeURL(i->second) << "&";
+        getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&";
       }
       if (getparmbuf.str().length() > 0)  
         getparms = "?" + std::string(getparmbuf.str().begin(), getparmbuf.str().end() - 1);
index 91a83cb2521494f1272295a781200f080deafe16..3c31e63138027f1e39cd32a4e8356d0f2d61ca4a 100644 (file)
@@ -30,17 +30,19 @@ namespace funcptr = boost;
 #define YAHTTP_TYPE_RESPONSE 2
 
 namespace YaHTTP {
-  typedef std::map<std::string,std::string> strstr_map_t;
-  typedef std::map<std::string,Cookie> strcookie_map_t;
+  typedef std::map<std::string,std::string> strstr_map_t; //<! String to String map 
+  typedef std::map<std::string,Cookie> strcookie_map_t; //<! String to Cookie map
 
   typedef enum {
     urlencoded,
     multipart
-  } postformat_t;
+  } postformat_t; //<! Enumeration of possible post encodings, url encoding or multipart
 
+  /*! Base class for request and response */
   class HTTPBase {
   public:
 #ifdef HAVE_CPP_FUNC_PTR
+    /*! Default renderer for request/response, simply copies body to response */
     class SendBodyRender {
     public:
       SendBodyRender() {};
@@ -48,15 +50,16 @@ namespace YaHTTP {
       size_t operator()(const HTTPBase *doc, std::ostream& os) const {
         os << doc->body;
         return doc->body.length();
-      };
+      }; //<! writes body to ostream and returns length 
     };
+    /* Simple sendfile renderer which streams file to ostream */
     class SendFileRender {
     public:
       SendFileRender(const std::string& path) {
         this->path = path;
       };
   
-      size_t operator()(const HTTPBase *doc, std::ostream& os) const {
+      size_t operator()(const HTTPBase *doc __attribute__((unused)), std::ostream& os) const {
         char buf[4096];
         size_t n,k;
 #ifdef HAVE_CXX11
@@ -73,12 +76,14 @@ namespace YaHTTP {
         }
 
         return n;
-      };
+      }; //<! writes file to ostream and returns length
 
-      std::string path;
+      std::string path; //<! File to send
     };
 #endif
     HTTPBase() {
+      kind = 0;
+      status = 0;
 #ifdef HAVE_CPP_FUNC_PTR
       renderer = SendBodyRender();
 #endif
@@ -108,31 +113,38 @@ protected:
       return *this;
     };
 public:
-    URL url;
-    int kind;
-    int status;
-    std::string statusText;
-    std::string method;
-    strstr_map_t headers;
-    CookieJar jar;
-    strstr_map_t postvars;
-    strstr_map_t getvars;
+    URL url; //<! URL of this request/response
+    int kind; //<! Type of object (1 = request, 2 = response)
+    int status; //<! status code 
+    std::string statusText; //<! textual representation of status code
+    std::string method; //<! http verb
+    strstr_map_t headers; //<! map of header(s)
+    CookieJar jar; //<! cookies 
+    strstr_map_t postvars; //<! map of POST variables (from POST body)
+    strstr_map_t getvars; //<! map of GET variables (from URL)
 // these two are for Router
-    strstr_map_t parameters;
-    std::string routeName;
+    strstr_map_t parameters; //<! map of route parameters (only if you use YaHTTP::Router)
+    std::string routeName; //<! name of the current route (only if you use YaHTTP::Router)
 
-    std::string body;
+    std::string body; //<! the actual content
  
 #ifdef HAVE_CPP_FUNC_PTR
-    funcptr::function<size_t(const HTTPBase*,std::ostream&)> renderer;
+    funcptr::function<size_t(const HTTPBase*,std::ostream&)> renderer; //<! rendering function
 #endif
-    void write(std::ostream& os) const;
+    void write(std::ostream& os) const; //<! writes request to the given output stream
 
-    strstr_map_t& GET() { return getvars; };
-    strstr_map_t& POST() { return postvars; };
-    strcookie_map_t& COOKIES() { return jar.cookies; };
+    strstr_map_t& GET() { return getvars; }; //<! acccessor for getvars
+    strstr_map_t& POST() { return postvars; }; //<! accessor for postvars
+    strcookie_map_t& COOKIES() { return jar.cookies; }; //<! accessor for cookies
+
+    std::string str() const {
+       std::ostringstream oss;
+       write(oss);
+       return oss.str();
+    }; //<! return string representation of this object
   };
 
+  /*! Response class, represents a HTTP Response document */
   class Response: public HTTPBase { 
   public:
     Response() { this->kind = YAHTTP_TYPE_RESPONSE; };
@@ -143,11 +155,12 @@ public:
       HTTPBase::operator=(rhs);
       this->kind = YAHTTP_TYPE_RESPONSE;
       return *this;
-    }
+    };
     friend std::ostream& operator<<(std::ostream& os, const Response &resp);
     friend std::istream& operator>>(std::istream& is, Response &resp);
   };
 
+  /* Request class, represents a HTTP Request document */
   class Request: public HTTPBase {
   public:
     Request() { this->kind = YAHTTP_TYPE_REQUEST; };
@@ -158,7 +171,7 @@ public:
       HTTPBase::operator=(rhs);
       this->kind = YAHTTP_TYPE_REQUEST;
       return *this;
-    }
+    };
 
     void setup(const std::string& method, const std::string& url) {
       this->url.parse(url);
@@ -166,67 +179,70 @@ public:
       this->method = method;
       std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper);
       this->headers["user-agent"] = "YaHTTP v1.0";
-    }
+    }; //<! Set some initial things for a request
 
     void preparePost(postformat_t format = urlencoded) {
       std::ostringstream postbuf;
       if (format == urlencoded) {
         for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) {
-          postbuf << Utility::encodeURL(i->first) << "=" << Utility::encodeURL(i->second) << "&";
+          postbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&";
         }
         // remove last bit
         if (postbuf.str().length()>0) 
-          body = std::string(postbuf.str().begin(), postbuf.str().end()-1);
+          body = postbuf.str().substr(0, postbuf.str().length()-1);
         else
           body = "";
         headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
       } else if (format == multipart) {
         headers["content-type"] = "multipart/form-data; boundary=YaHTTP-12ca543";
         for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) {
-          postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first) << "; charset=UTF-8\r\n\r\n"
-            << Utility::encodeURL(i->second) << "\r\n";
+          postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first, false) << "; charset=UTF-8\r\n\r\n"
+            << Utility::encodeURL(i->second, false) << "\r\n";
         }
       }
 
+      postbuf.str("");
+      postbuf << body.length();
       // set method and change headers
       method = "POST";
-      headers["content-length"] = body.length();
-    };
+      headers["content-length"] = postbuf.str();
+    }; //<! convert all postvars into string and stuff it into body
 
     friend std::ostream& operator<<(std::ostream& os, const Request &resp);
     friend std::istream& operator>>(std::istream& is, Request &resp);
   };
 
+  /*! Asynchronous HTTP document loader */
   template <class T>
   class AsyncLoader {
   public:
-    T* target;
-    int state;
-    size_t pos;
+    T* target; //<! target to populate
+    int state; //<! reader state
+    size_t pos; //<! reader position
     
-    std::string buffer;
-    bool chunked;
-    int chunk_size;
-    std::ostringstream bodybuf;
-    size_t maxbody;
-    size_t minbody;
-    bool hasBody;
+    std::string buffer; //<! read buffer 
+    bool chunked; //<! whether we are parsing chunked data
+    int chunk_size; //<! expected size of next chunk
+    std::ostringstream bodybuf; //<! buffer for body
+    size_t maxbody; //<! maximum size of body
+    size_t minbody; //<! minimum size of body
+    bool hasBody; //<! are we expecting body
 
-    void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value);
+    void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value); //<! key value pair parser helper
 
     void initialize(T* target) {
       chunked = false; chunk_size = 0;
       bodybuf.str(""); maxbody = 0;
       pos = 0; state = 0; this->target = target; 
       hasBody = false;
-    };
-    int feed(const std::string& somedata);
+    }; //<! Initialize the parser for target and clear state
+    int feed(const std::string& somedata); //<! Feed data to the parser
     bool ready() {  return state > 1 && 
                       (!hasBody || 
                          (bodybuf.str().size() <= maxbody && 
                           bodybuf.str().size() >= minbody)
                       ); 
-                 };
+                 }; //<! whether we have received enough data
     void finalize() {
       bodybuf.flush();
       if (ready()) {
@@ -238,12 +254,14 @@ public:
       }
       bodybuf.str("");
       this->target = NULL;
-    };
+    }; //<! finalize and release target
   };
 
+  /*! Asynchronous HTTP response loader */
   class AsyncResponseLoader: public AsyncLoader<Response> {
   };
 
+  /*! Asynchronous HTTP request loader */
   class AsyncRequestLoader: public AsyncLoader<Request> {
   };
 
index e649c59fb6406fa07444edc3baf9a17823fff6fc..73b8d2342cb18943f944f493ff73acc5f69816c5 100644 (file)
@@ -10,6 +10,7 @@
 #endif 
 
 namespace YaHTTP {
+  /*! URL parser and container */
   class URL {
    private: 
       bool parseSchema(const std::string& url, size_t &pos) {
@@ -25,7 +26,7 @@ namespace YaHTTP {
              pos += 2;
           }
           return true;
-      }
+      }; //<! parse schema/protocol part 
 
       bool parseHost(const std::string& url, size_t &pos) {
           size_t pos1;
@@ -38,8 +39,13 @@ namespace YaHTTP {
              host = url.substr(pos, pos1-pos);
              pos = pos1;
           }
+          if ( (pos1 = host.find_first_of(":")) != std::string::npos ) {
+             std::istringstream tmp(host.substr(pos1+1));
+             tmp >> port;
+             host = host.substr(0, pos1);
+          }
           return true;
-      }
+      }; //<! parse host and port
 
       bool parseUserPass(const std::string& url, size_t &pos) {
           size_t pos1,pos2;
@@ -58,7 +64,7 @@ namespace YaHTTP {
           pos = pos1+1;
           username = Utility::decodeURL(username);
           return true;
-      };
+      }; //<! parse possible username and password
 
       bool parsePath(const std::string& url, size_t &pos) {
           size_t pos1;
@@ -72,7 +78,7 @@ namespace YaHTTP {
              pos = pos1;
           }
           return true;
-      }
+      }; //<! parse path component
 
       bool parseParameters(const std::string& url, size_t &pos) {
           size_t pos1;
@@ -88,18 +94,18 @@ namespace YaHTTP {
           }
           if (parameters.size()>0 && *(parameters.end()-1) == '&') parameters.resize(parameters.size()-1);
           return true;
-      }
+      }; //<! parse url parameters
 
       bool parseAnchor(const std::string& url, size_t &pos) {
           if (pos >= url.size()) return true; // no data
           if (url[pos] != '#') return false; // not anchor
           anchor = url.substr(pos+1);
           return true;
-      }
+      }; //<! parse anchor
 
       void initialize() {
         protocol = ""; host = ""; port = 0; username = ""; password = ""; path = ""; parameters = ""; anchor =""; pathless = true;
-      }
+      }; //<! initialize to empty URL
 
   public:
       std::string to_string() const {
@@ -135,26 +141,26 @@ namespace YaHTTP {
           if (anchor.empty() == false)
              oss << "#" << anchor;
           return oss.str();
-      }
-
-      std::string protocol;
-      std::string host;
-      int port;
-      std::string username;
-      std::string password;
-      std::string path;
-      std::string parameters;
-      std::string anchor;
-      bool pathless;
-
-      URL() { initialize(); }; 
+      }; //<! convert this URL to string
+
+      std::string protocol; //<! schema/protocol 
+      std::string host; //<! host
+      int port; //<! port
+      std::string username; //<! username
+      std::string password; //<! password
+      std::string path; //<! path 
+      std::string parameters; //<! url parameters
+      std::string anchor; //<! anchor
+      bool pathless; //<! whether this url has no path
+
+      URL() { initialize(); }; //<! construct empty url
       URL(const std::string& url) {
           parse(url);
-      };
+      }; //<! calls parse with url 
 
       URL(const char *url) {
           parse(std::string(url));
-      };
+      }; //<! calls parse with url
 
       bool parse(const std::string& url) {
           // setup
@@ -174,12 +180,12 @@ namespace YaHTTP {
           if (parsePath(url, pos) == false) return false;
           if (parseParameters(url, pos) == false) return false;
           return parseAnchor(url, pos);
-      };
+      }; /*! parse various formats of urls ranging from http://example.com/foo?bar=baz into data:base64:d089swt64wt... */
 
       friend std::ostream & operator<<(std::ostream& os, const URL& url) {
          os<<url.to_string();
          return os;
-      }
+      };
   };
 };
 #endif
index b6cfd30331fa1911eafee07827cc2e91cddcaae0..08b2c6f45313294154998932e9d2a968902c5f81 100644 (file)
@@ -2,42 +2,43 @@
 #define _YAHTTP_UTILITY_HPP 1
 
 namespace YaHTTP {
-  static const char *MONTHS[] = {0,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",0};
-  static const char *DAYS[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat",0};
+  static const char *MONTHS[] = {0,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",0}; //<! List of months 
+  static const char *DAYS[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat",0}; //<! List of days
 
+  /*! Represents a date/time with utc offset */
   class DateTime {
   public:
-     bool isSet;
+     bool isSet; //<! if this is initialized yet
 
-     int year;
+     int year; //<! year, 0 is year 0, not 1900
 
-     int month;
-     int day;
-     int wday;
+     int month; //<! month, range 1-12
+     int day; //<! day, range 1-31
+     int wday; //<! week day, range 1-7
 
-     int hours;
-     int minutes;
-     int seconds;
+     int hours; //<! hours, range 0-23
+     int minutes; //<! minutes, range 0-59
+     int seconds; //<! seconds, range 0-60
 
-     int utc_offset;
+     int utc_offset; //<! UTC offset with minutes (hhmm)
 
      DateTime() { 
        initialize();
-     };
+     }; //<! Construct and initialize
 
      void initialize() {
        isSet = false; 
        year = month = day = wday = hours = minutes = seconds = utc_offset = 0;
        month = 1; // it's invalid otherwise
-     };
+     }; //<! Creates year 0 date
 
      void setLocal() {
        fromLocaltime(time((time_t*)NULL)); 
-     };
+     }; //<! sets current local time
 
      void setGm() {
        fromGmtime(time((time_t*)NULL));
-     }
+     }; //<! sets current gmtime (almost UTC)
 
      void fromLocaltime(time_t t) {
 #ifdef HAVE_LOCALTIME_R
@@ -49,7 +50,7 @@ namespace YaHTTP {
        tm = localtime(&t);
        fromTm(tm);
 #endif
-     };
+     }; //<! uses localtime for time
 
      void fromGmtime(time_t t) {
 #ifdef HAVE_GMTIME_R
@@ -61,7 +62,7 @@ namespace YaHTTP {
        tm = gmtime(&t);
        fromTm(tm);
 #endif
-     };
+     }; //<! uses gmtime for time
 
      void fromTm(const struct tm *tm) {
        year = tm->tm_year + 1900;
@@ -73,7 +74,7 @@ namespace YaHTTP {
        wday = tm->tm_wday;
        utc_offset = tm->tm_gmtoff;
        isSet = true;
-     };
+     }; //<! parses date from struct tm 
 
      void validate() const {
        if (wday < 0 || wday > 6) throw "Invalid date";
@@ -82,7 +83,7 @@ namespace YaHTTP {
        if (hours < 0 || hours > 23 ||
            minutes < 0 || minutes > 59 ||
            seconds < 0 || seconds > 60) throw "Invalid date";
-     }
+     }; //<! make sure we are within ranges (not a *REAL* validation, just range check)
 
      std::string rfc_str() const {
        std::ostringstream oss;
@@ -99,7 +100,7 @@ namespace YaHTTP {
        oss << std::setfill('0') << std::setw(2) << (tmp_off%3600)/60;
 
        return oss.str(); 
-     };
+     }; //<! converts this date into a RFC-822 format
  
      std::string cookie_str() const {
        std::ostringstream oss;
@@ -109,27 +110,25 @@ namespace YaHTTP {
          std::setfill('0') << std::setw(2) << minutes << ":" << 
          std::setfill('0') << std::setw(2) << seconds << " GMT";
        return oss.str();
-     }
+     }; //<! converts this date into a HTTP Cookie date
  
      void parse822(const std::string &rfc822_date) {
-       char *pos;
        struct tm tm;
-       if ( (pos = strptime(rfc822_date.c_str(), "%a, %d %b %Y %T %z", &tm)) != NULL) {
+       if ( (strptime(rfc822_date.c_str(), "%a, %d %b %Y %T %z", &tm)) != NULL) {
           fromTm(&tm);
        } else {
           throw "Unparseable date";
        }
-     };
+     }; //<! parses RFC-822 date
 
      void parseCookie(const std::string &cookie_date) {
-       char *pos;
        struct tm tm;
-       if ( (pos = strptime(cookie_date.c_str(), "%d-%b-%Y %T %Z", &tm)) != NULL) {
+       if ( (strptime(cookie_date.c_str(), "%d-%b-%Y %T %Z", &tm)) != NULL) {
           fromTm(&tm);
        } else {
           throw "Unparseable date";
        }
-     };
+     }; //<! parses HTTP Cookie date
 
      int unixtime() const {
        struct tm tm;
@@ -141,10 +140,10 @@ namespace YaHTTP {
        tm.tm_sec = seconds;
        tm.tm_gmtoff = utc_offset;
        return mktime(&tm);
-     }
-     
+     }; //<! returns this datetime as unixtime. will not work for dates before 1970/1/1 00:00:00 GMT
   };
+
+  /*! Various helpers needed in the code */ 
   class Utility {
   public:
     static std::string decodeURL(const std::string& component) {
@@ -174,7 +173,7 @@ namespace YaHTTP {
            pos2=pos1;
         }
         return result;
-    };
+    }; //<! Decodes %xx from string into bytes
     
     static std::string encodeURL(const std::string& component, bool asUrl = true) {
       std::string result = component;
@@ -191,7 +190,7 @@ namespace YaHTTP {
         }
       }
       return result;
-    };
+    }; //<! Escapes any characters into %xx representation when necessary, set asUrl to false to fully encode the url
 
     static std::string encodeURL(const std::wstring& component, bool asUrl = true) {
       unsigned char const *p = reinterpret_cast<unsigned char const*>(&component[0]);
@@ -207,9 +206,7 @@ namespace YaHTTP {
         } else result << (char)*iter;
       }
       return result.str();
-    };
-
-
+    }; //<! Escapes any characters into %xx representation when necessary, set asUrl to false to fully encode the url, for wide strings, returns ordinary string
 
     static std::string status2text(int status) {
        switch(status) {
@@ -292,7 +289,7 @@ namespace YaHTTP {
        default:
            return "Unknown Status";
        }
-    };
+    }; //<! static HTTP codes to text mappings
 
     static std::map<std::string,std::string> parseUrlParameters(std::string parameters) {
       std::string::size_type pos = 0;
@@ -331,7 +328,7 @@ namespace YaHTTP {
         pos = nextpos+1;
       }
       return parameter_map;
-    };
+    }; //<! parses URL parameters into string map 
 
     static bool iequals(const std::string& a, const std::string& b, size_t length) {
       std::string::const_iterator ai, bi;
@@ -345,19 +342,19 @@ namespace YaHTTP {
           (ai != a.end() && bi == b.end())) return false;
       
       return ::toupper(*ai) == ::toupper(*bi);
-    }
+    }; //<! case-insensitive comparison with length
 
     static bool iequals(const std::string& a, const std::string& b) {
       if (a.size() != b.size()) return false;
       return iequals(a,b,a.size());
-    }
+    }; //<! case-insensitive comparison
 
     static void trimRight(std::string &str) {
        const std::locale &loc = std::locale::classic();
        std::string::reverse_iterator iter = str.rbegin();
        while(iter != str.rend() && std::isspace(*iter, loc)) iter++;
        str.erase(iter.base(), str.end());
-    };
+    }; //<! removes whitespace from right
 
     static std::string camelizeHeader(const std::string &str) {
        std::string::const_iterator iter = str.begin();
@@ -375,7 +372,7 @@ namespace YaHTTP {
        }
 
        return result;
-     };
-   };
+   }; //<! camelizes headers, such as, content-type => Content-Type
+  };
 };
 #endif