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