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 && ifs.good()) { 83 ifs.read(buf, sizeof buf); 84 n += (k = ifs.gcount()); 85 if (k) { 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 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 } 122 protected: HTTPBase(const HTTPBase & rhs)123 HTTPBase(const HTTPBase& rhs) { 124 this->url = rhs.url; this->kind = rhs.kind; 125 this->status = rhs.status; this->statusText = rhs.statusText; 126 this->method = rhs.method; this->headers = rhs.headers; 127 this->jar = rhs.jar; this->postvars = rhs.postvars; 128 this->parameters = rhs.parameters; this->getvars = rhs.getvars; 129 this->body = rhs.body; this->max_request_size = rhs.max_request_size; 130 this->max_response_size = rhs.max_response_size; this->version = rhs.version; 131 #ifdef HAVE_CPP_FUNC_PTR 132 this->renderer = rhs.renderer; 133 #endif 134 }; operator =(const HTTPBase & rhs)135 HTTPBase& operator=(const HTTPBase& rhs) { 136 this->url = rhs.url; this->kind = rhs.kind; 137 this->status = rhs.status; this->statusText = rhs.statusText; 138 this->method = rhs.method; this->headers = rhs.headers; 139 this->jar = rhs.jar; this->postvars = rhs.postvars; 140 this->parameters = rhs.parameters; this->getvars = rhs.getvars; 141 this->body = rhs.body; this->max_request_size = rhs.max_request_size; 142 this->max_response_size = rhs.max_response_size; this->version = rhs.version; 143 #ifdef HAVE_CPP_FUNC_PTR 144 this->renderer = rhs.renderer; 145 #endif 146 return *this; 147 }; 148 public: 149 URL url; //<! URL of this request/response 150 int kind; //<! Type of object (1 = request, 2 = response) 151 int status; //<! status code 152 int version; //<! http version 9 = 0.9, 10 = 1.0, 11 = 1.1 153 std::string statusText; //<! textual representation of status code 154 std::string method; //<! http verb 155 strstr_map_t headers; //<! map of header(s) 156 CookieJar jar; //<! cookies 157 strstr_map_t postvars; //<! map of POST variables (from POST body) 158 strstr_map_t getvars; //<! map of GET variables (from URL) 159 // these two are for Router 160 strstr_map_t parameters; //<! map of route parameters (only if you use YaHTTP::Router) 161 std::string routeName; //<! name of the current route (only if you use YaHTTP::Router) 162 163 std::string body; //<! the actual content 164 165 ssize_t max_request_size; //<! maximum size of request 166 ssize_t max_response_size; //<! maximum size of response 167 168 #ifdef HAVE_CPP_FUNC_PTR 169 funcptr::function<size_t(const HTTPBase*,std::ostream&,bool)> renderer; //<! rendering function 170 #endif 171 void write(std::ostream& os) const; //<! writes request to the given output stream 172 GET()173 strstr_map_t& GET() { return getvars; }; //<! acccessor for getvars POST()174 strstr_map_t& POST() { return postvars; }; //<! accessor for postvars COOKIES()175 strcookie_map_t& COOKIES() { return jar.cookies; }; //<! accessor for cookies 176 versionStr(int version) const177 std::string versionStr(int version) const { 178 switch(version) { 179 case 9: return "0.9"; 180 case 10: return "1.0"; 181 case 11: return "1.1"; 182 default: throw YaHTTP::Error("Unsupported version"); 183 } 184 }; 185 str() const186 std::string str() const { 187 std::ostringstream oss; 188 write(oss); 189 return oss.str(); 190 }; //<! return string representation of this object 191 }; 192 193 /*! Response class, represents a HTTP Response document */ 194 class Response: public HTTPBase { 195 public: Response()196 Response() { initialize(); }; Response(const HTTPBase & rhs)197 Response(const HTTPBase& rhs): HTTPBase(rhs) { 198 this->kind = YAHTTP_TYPE_RESPONSE; 199 }; operator =(const HTTPBase & rhs)200 Response& operator=(const HTTPBase& rhs) { 201 HTTPBase::operator=(rhs); 202 this->kind = YAHTTP_TYPE_RESPONSE; 203 return *this; 204 }; initialize()205 void initialize() { 206 HTTPBase::initialize(); 207 this->kind = YAHTTP_TYPE_RESPONSE; 208 } initialize(const HTTPBase & rhs)209 void initialize(const HTTPBase& rhs) { 210 HTTPBase::initialize(); 211 this->kind = YAHTTP_TYPE_RESPONSE; 212 // copy SOME attributes 213 this->url = rhs.url; 214 this->method = rhs.method; 215 this->jar = rhs.jar; 216 this->version = rhs.version; 217 } 218 friend std::ostream& operator<<(std::ostream& os, const Response &resp); 219 friend std::istream& operator>>(std::istream& is, Response &resp); 220 }; 221 222 /* Request class, represents a HTTP Request document */ 223 class Request: public HTTPBase { 224 public: Request()225 Request() { initialize(); }; Request(const HTTPBase & rhs)226 Request(const HTTPBase& rhs): HTTPBase(rhs) { 227 this->kind = YAHTTP_TYPE_REQUEST; 228 }; operator =(const HTTPBase & rhs)229 Request& operator=(const HTTPBase& rhs) { 230 HTTPBase::operator=(rhs); 231 this->kind = YAHTTP_TYPE_REQUEST; 232 return *this; 233 }; initialize()234 void initialize() { 235 HTTPBase::initialize(); 236 this->kind = YAHTTP_TYPE_REQUEST; 237 } initialize(const HTTPBase & rhs)238 void initialize(const HTTPBase& rhs) { 239 HTTPBase::initialize(); 240 this->kind = YAHTTP_TYPE_REQUEST; 241 // copy SOME attributes 242 this->url = rhs.url; 243 this->method = rhs.method; 244 this->jar = rhs.jar; 245 this->version = rhs.version; 246 } setup(const std::string & method,const std::string & url)247 void setup(const std::string& method, const std::string& url) { 248 this->url.parse(url); 249 this->headers["host"] = this->url.host; 250 this->method = method; 251 std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper); 252 this->headers["user-agent"] = "YaHTTP v1.0"; 253 }; //<! Set some initial things for a request 254 preparePost(postformat_t format=urlencoded)255 void preparePost(postformat_t format = urlencoded) { 256 std::ostringstream postbuf; 257 if (format == urlencoded) { 258 for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { 259 postbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&"; 260 } 261 // remove last bit 262 if (postbuf.str().length()>0) 263 body = postbuf.str().substr(0, postbuf.str().length()-1); 264 else 265 body = ""; 266 headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8"; 267 } else if (format == multipart) { 268 headers["content-type"] = "multipart/form-data; boundary=YaHTTP-12ca543"; 269 for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { 270 postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first, false) << "; charset=UTF-8\r\n\r\n" 271 << Utility::encodeURL(i->second, false) << "\r\n"; 272 } 273 } 274 275 postbuf.str(""); 276 postbuf << body.length(); 277 // set method and change headers 278 method = "POST"; 279 headers["content-length"] = postbuf.str(); 280 }; //<! convert all postvars into string and stuff it into body 281 282 friend std::ostream& operator<<(std::ostream& os, const Request &resp); 283 friend std::istream& operator>>(std::istream& is, Request &resp); 284 }; 285 286 /*! Asynchronous HTTP document loader */ 287 template <class T> 288 class AsyncLoader { 289 public: 290 T* target; //<! target to populate 291 int state; //<! reader state 292 size_t pos; //<! reader position 293 294 std::string buffer; //<! read buffer 295 bool chunked; //<! whether we are parsing chunked data 296 int chunk_size; //<! expected size of next chunk 297 std::ostringstream bodybuf; //<! buffer for body 298 size_t maxbody; //<! maximum size of body 299 size_t minbody; //<! minimum size of body 300 bool hasBody; //<! are we expecting body 301 302 void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value); //<! key value pair parser helper 303 initialize(T * target)304 void initialize(T* target) { 305 chunked = false; chunk_size = 0; 306 bodybuf.str(""); minbody = 0; maxbody = 0; 307 pos = 0; state = 0; this->target = target; 308 hasBody = false; 309 buffer = ""; 310 this->target->initialize(); 311 }; //<! Initialize the parser for target and clear state 312 int feed(const std::string& somedata); //<! Feed data to the parser ready()313 bool ready() { 314 return (chunked == true && state == 3) || // if it's chunked we get end of data indication 315 (chunked == false && state > 1 && 316 (!hasBody || 317 (bodybuf.str().size() <= maxbody && 318 bodybuf.str().size() >= minbody) 319 ) 320 ); 321 }; //<! whether we have received enough data finalize()322 void finalize() { 323 bodybuf.flush(); 324 if (ready()) { 325 strstr_map_t::iterator pos = target->headers.find("content-type"); 326 if (pos != target->headers.end() && Utility::iequals(pos->second, "application/x-www-form-urlencoded", 32)) { 327 target->postvars = Utility::parseUrlParameters(bodybuf.str()); 328 } 329 target->body = bodybuf.str(); 330 } 331 bodybuf.str(""); 332 this->target = NULL; 333 }; //<! finalize and release target 334 }; 335 336 /*! Asynchronous HTTP response loader */ 337 class AsyncResponseLoader: public AsyncLoader<Response> { 338 }; 339 340 /*! Asynchronous HTTP request loader */ 341 class AsyncRequestLoader: public AsyncLoader<Request> { 342 }; 343 344 }; 345