1 #ifdef HAVE_CXX11 2 #include <functional> 3 #define HAVE_CPP_FUNC_PTR 4 namespace funcptr = std; 5 #else 6 #ifdef HAVE_BOOST 7 #include <boost/function.hpp> 8 namespace funcptr = boost; 9 #define HAVE_CPP_FUNC_PTR 10 #endif 11 #endif 12 13 #include <fstream> 14 #include <cctype> 15 16 #ifndef WIN32 17 #include <cstdio> 18 #include <unistd.h> 19 #endif 20 21 #include <algorithm> 22 23 #ifndef YAHTTP_MAX_REQUEST_SIZE 24 #define YAHTTP_MAX_REQUEST_SIZE 2097152 25 #endif 26 27 #ifndef YAHTTP_MAX_RESPONSE_SIZE 28 #define YAHTTP_MAX_RESPONSE_SIZE 2097152 29 #endif 30 31 #define YAHTTP_TYPE_REQUEST 1 32 #define YAHTTP_TYPE_RESPONSE 2 33 34 namespace YaHTTP { 35 typedef std::map<std::string,Cookie,ASCIICINullSafeComparator> strcookie_map_t; //<! String to Cookie map 36 37 typedef enum { 38 urlencoded, 39 multipart 40 } postformat_t; //<! Enumeration of possible post encodings, url encoding or multipart 41 42 /*! Base class for request and response */ 43 class HTTPBase { 44 public: 45 /*! Default renderer for request/response, simply copies body to response */ 46 class SendBodyRender { 47 public: SendBodyRender()48 SendBodyRender() {}; 49 operator ()(const HTTPBase * doc,std::ostream & os,bool chunked) const50 size_t operator()(const HTTPBase *doc, std::ostream& os, bool chunked) const { 51 if (chunked) { 52 std::string::size_type i,cl; 53 for(i=0;i<doc->body.length();i+=1024) { 54 cl = std::min(static_cast<std::string::size_type>(1024), doc->body.length()-i); // for less than 1k blocks 55 os << std::hex << cl << std::dec << "\r\n"; 56 os << doc->body.substr(i, cl) << "\r\n"; 57 } 58 os << 0 << "\r\n\r\n"; // last chunk 59 } else { 60 os << doc->body; 61 } 62 return doc->body.length(); 63 }; //<! writes body to ostream and returns length 64 }; 65 /* Simple sendfile renderer which streams file to ostream */ 66 class SendFileRender { 67 public: SendFileRender(const std::string & path_)68 SendFileRender(const std::string& path_) { 69 this->path = path_; 70 }; 71 operator ()(const HTTPBase * doc,std::ostream & os,bool chunked) const72 size_t operator()(const HTTPBase *doc __attribute__((unused)), std::ostream& os, bool chunked) const { 73 char buf[4096]; 74 size_t n,k; 75 #ifdef HAVE_CXX11 76 std::ifstream ifs(path, std::ifstream::binary); 77 #else 78 std::ifstream ifs(path.c_str(), std::ifstream::binary); 79 #endif 80 n = 0; 81 82 while(ifs.good()) { 83 ifs.read(buf, sizeof buf); 84 n += (k = ifs.gcount()); 85 if (k > 0) { 86 if (chunked) os << std::hex << k << std::dec << "\r\n"; 87 os.write(buf, k); 88 if (chunked) os << "\r\n"; 89 } 90 } 91 if (chunked) os << 0 << "\r\n\r\n"; 92 return n; 93 }; //<! writes file to ostream and returns length 94 95 std::string path; //<! File to send 96 }; 97 HTTPBase()98 HTTPBase() { 99 HTTPBase::initialize(); 100 }; 101 initialize()102 virtual void initialize() { 103 kind = 0; 104 status = 0; 105 #ifdef HAVE_CPP_FUNC_PTR 106 renderer = SendBodyRender(); 107 #endif 108 max_request_size = YAHTTP_MAX_REQUEST_SIZE; 109 max_response_size = YAHTTP_MAX_RESPONSE_SIZE; 110 url = ""; 111 method = ""; 112 statusText = ""; 113 jar.clear(); 114 headers.clear(); 115 parameters.clear(); 116 getvars.clear(); 117 postvars.clear(); 118 body = ""; 119 routeName = ""; 120 version = 11; // default to version 1.1 121 is_multipart = false; 122 } 123 protected: HTTPBase(const HTTPBase & rhs)124 HTTPBase(const HTTPBase& rhs) { 125 this->url = rhs.url; this->kind = rhs.kind; 126 this->status = rhs.status; this->statusText = rhs.statusText; 127 this->method = rhs.method; this->headers = rhs.headers; 128 this->jar = rhs.jar; this->postvars = rhs.postvars; 129 this->parameters = rhs.parameters; this->getvars = rhs.getvars; 130 this->body = rhs.body; this->max_request_size = rhs.max_request_size; 131 this->max_response_size = rhs.max_response_size; this->version = rhs.version; 132 #ifdef HAVE_CPP_FUNC_PTR 133 this->renderer = rhs.renderer; 134 #endif 135 this->is_multipart = rhs.is_multipart; 136 }; operator =(const HTTPBase & rhs)137 virtual HTTPBase& operator=(const HTTPBase& rhs) { 138 this->url = rhs.url; this->kind = rhs.kind; 139 this->status = rhs.status; this->statusText = rhs.statusText; 140 this->method = rhs.method; this->headers = rhs.headers; 141 this->jar = rhs.jar; this->postvars = rhs.postvars; 142 this->parameters = rhs.parameters; this->getvars = rhs.getvars; 143 this->body = rhs.body; this->max_request_size = rhs.max_request_size; 144 this->max_response_size = rhs.max_response_size; this->version = rhs.version; 145 #ifdef HAVE_CPP_FUNC_PTR 146 this->renderer = rhs.renderer; 147 #endif 148 this->is_multipart = rhs.is_multipart; 149 return *this; 150 }; 151 public: 152 URL url; //<! URL of this request/response 153 int kind; //<! Type of object (1 = request, 2 = response) 154 int status; //<! status code 155 int version; //<! http version 9 = 0.9, 10 = 1.0, 11 = 1.1 156 std::string statusText; //<! textual representation of status code 157 std::string method; //<! http verb 158 strstr_map_t headers; //<! map of header(s) 159 CookieJar jar; //<! cookies 160 strstr_map_t postvars; //<! map of POST variables (from POST body) 161 strstr_map_t getvars; //<! map of GET variables (from URL) 162 // these two are for Router 163 strstr_map_t parameters; //<! map of route parameters (only if you use YaHTTP::Router) 164 std::string routeName; //<! name of the current route (only if you use YaHTTP::Router) 165 166 std::string body; //<! the actual content 167 168 ssize_t max_request_size; //<! maximum size of request 169 ssize_t max_response_size; //<! maximum size of response 170 bool is_multipart; //<! if the request is multipart, prevents Content-Length header 171 #ifdef HAVE_CPP_FUNC_PTR 172 funcptr::function<size_t(const HTTPBase*,std::ostream&,bool)> renderer; //<! rendering function 173 #endif 174 void write(std::ostream& os) const; //<! writes request to the given output stream 175 GET()176 strstr_map_t& GET() { return getvars; }; //<! acccessor for getvars POST()177 strstr_map_t& POST() { return postvars; }; //<! accessor for postvars COOKIES()178 strcookie_map_t& COOKIES() { return jar.cookies; }; //<! accessor for cookies 179 versionStr(int version_) const180 std::string versionStr(int version_) const { 181 switch(version_) { 182 case 9: return "0.9"; 183 case 10: return "1.0"; 184 case 11: return "1.1"; 185 default: throw YaHTTP::Error("Unsupported version"); 186 } 187 }; 188 str() const189 std::string str() const { 190 std::ostringstream oss; 191 write(oss); 192 return oss.str(); 193 }; //<! return string representation of this object 194 }; 195 196 /*! Response class, represents a HTTP Response document */ 197 class Response: public HTTPBase { 198 public: Response()199 Response() { Response::initialize(); }; Response(const HTTPBase & rhs)200 Response(const HTTPBase& rhs): HTTPBase(rhs) { 201 this->kind = YAHTTP_TYPE_RESPONSE; 202 }; operator =(const HTTPBase & rhs)203 Response& operator=(const HTTPBase& rhs) override { 204 HTTPBase::operator=(rhs); 205 this->kind = YAHTTP_TYPE_RESPONSE; 206 return *this; 207 }; initialize()208 void initialize() override { 209 HTTPBase::initialize(); 210 this->kind = YAHTTP_TYPE_RESPONSE; 211 } initialize(const HTTPBase & rhs)212 void initialize(const HTTPBase& rhs) { 213 HTTPBase::initialize(); 214 this->kind = YAHTTP_TYPE_RESPONSE; 215 // copy SOME attributes 216 this->url = rhs.url; 217 this->method = rhs.method; 218 this->jar = rhs.jar; 219 this->version = rhs.version; 220 } 221 friend std::ostream& operator<<(std::ostream& os, const Response &resp); 222 friend std::istream& operator>>(std::istream& is, Response &resp); 223 }; 224 225 /* Request class, represents a HTTP Request document */ 226 class Request: public HTTPBase { 227 public: Request()228 Request() { Request::initialize(); }; Request(const HTTPBase & rhs)229 Request(const HTTPBase& rhs): HTTPBase(rhs) { 230 this->kind = YAHTTP_TYPE_REQUEST; 231 }; operator =(const HTTPBase & rhs)232 Request& operator=(const HTTPBase& rhs) override { 233 HTTPBase::operator=(rhs); 234 this->kind = YAHTTP_TYPE_REQUEST; 235 return *this; 236 }; initialize()237 void initialize() override { 238 HTTPBase::initialize(); 239 this->kind = YAHTTP_TYPE_REQUEST; 240 } initialize(const HTTPBase & rhs)241 void initialize(const HTTPBase& rhs) { 242 HTTPBase::initialize(); 243 this->kind = YAHTTP_TYPE_REQUEST; 244 // copy SOME attributes 245 this->url = rhs.url; 246 this->method = rhs.method; 247 this->jar = rhs.jar; 248 this->version = rhs.version; 249 } setup(const std::string & method_,const std::string & url_)250 void setup(const std::string& method_, const std::string& url_) { 251 this->url.parse(url_); 252 this->headers["host"] = this->url.host.find(":") == std::string::npos ? this->url.host : "[" + this->url.host + "]"; 253 this->method = method_; 254 std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper); 255 this->headers["user-agent"] = "YaHTTP v1.0"; 256 }; //<! Set some initial things for a request 257 preparePost(postformat_t format=urlencoded)258 void preparePost(postformat_t format = urlencoded) { 259 std::ostringstream postbuf; 260 if (format == urlencoded) { 261 for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { 262 postbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&"; 263 } 264 // remove last bit 265 if (postbuf.str().length()>0) 266 body = postbuf.str().substr(0, postbuf.str().length()-1); 267 else 268 body = ""; 269 headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8"; 270 } else if (format == multipart) { 271 headers["content-type"] = "multipart/form-data; boundary=YaHTTP-12ca543"; 272 this->is_multipart = true; 273 for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { 274 postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first, false) << "\"; charset=UTF-8\r\nContent-Length: " << i->second.size() << "\r\n\r\n" 275 << Utility::encodeURL(i->second, false) << "\r\n"; 276 } 277 postbuf << "--"; 278 body = postbuf.str(); 279 } 280 281 postbuf.str(""); 282 postbuf << body.length(); 283 // set method and change headers 284 method = "POST"; 285 if (!this->is_multipart) 286 headers["content-length"] = postbuf.str(); 287 }; //<! convert all postvars into string and stuff it into body 288 289 friend std::ostream& operator<<(std::ostream& os, const Request &resp); 290 friend std::istream& operator>>(std::istream& is, Request &resp); 291 }; 292 293 /*! Asynchronous HTTP document loader */ 294 template <class T> 295 class AsyncLoader { 296 public: 297 T* target; //<! target to populate 298 int state; //<! reader state 299 size_t pos; //<! reader position 300 301 std::string buffer; //<! read buffer 302 bool chunked; //<! whether we are parsing chunked data 303 int chunk_size; //<! expected size of next chunk 304 std::ostringstream bodybuf; //<! buffer for body 305 size_t maxbody; //<! maximum size of body 306 size_t minbody; //<! minimum size of body 307 bool hasBody; //<! are we expecting body 308 309 void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value); //<! key value pair parser helper 310 initialize(T * target_)311 void initialize(T* target_) { 312 chunked = false; chunk_size = 0; 313 bodybuf.str(""); minbody = 0; maxbody = 0; 314 pos = 0; state = 0; this->target = target_; 315 hasBody = false; 316 buffer = ""; 317 this->target->initialize(); 318 }; //<! Initialize the parser for target and clear state 319 bool feed(const std::string& somedata); //<! Feed data to the parser ready()320 bool ready() { 321 return (chunked == true && state == 3) || // if it's chunked we get end of data indication 322 (chunked == false && state > 1 && 323 (!hasBody || 324 (bodybuf.str().size() <= maxbody && 325 bodybuf.str().size() >= minbody) 326 ) 327 ); 328 }; //<! whether we have received enough data finalize()329 void finalize() { 330 bodybuf.flush(); 331 if (ready()) { 332 strstr_map_t::iterator cpos = target->headers.find("content-type"); 333 if (cpos != target->headers.end() && Utility::iequals(cpos->second, "application/x-www-form-urlencoded", 32)) { 334 target->postvars = Utility::parseUrlParameters(bodybuf.str()); 335 } 336 target->body = bodybuf.str(); 337 } 338 bodybuf.str(""); 339 this->target = NULL; 340 }; //<! finalize and release target 341 }; 342 343 /*! Asynchronous HTTP response loader */ 344 class AsyncResponseLoader: public AsyncLoader<Response> { 345 }; 346 347 /*! Asynchronous HTTP request loader */ 348 class AsyncRequestLoader: public AsyncLoader<Request> { 349 }; 350 351 }; 352