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