1 #include "yahttp.hpp"
2 
3 namespace YaHTTP {
4   template <class T>
feed(const std::string & somedata)5   int AsyncLoader<T>::feed(const std::string& somedata) {
6     buffer.append(somedata);
7     while(state < 2) {
8       int cr=0;
9       pos = buffer.find_first_of("\n");
10       // need to find CRLF in buffer
11       if (pos == std::string::npos) return false;
12       if (pos>0 && buffer[pos-1]=='\r')
13         cr=1;
14       std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF
15       buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF
16 
17       if (state == 0) { // startup line
18         if (target->kind == YAHTTP_TYPE_REQUEST) {
19           std::string ver;
20           std::string tmpurl;
21           std::istringstream iss(line);
22           iss >> target->method >> tmpurl >> ver;
23           if (ver.size() == 0)
24             target->version = 9;
25           else if (ver.find("HTTP/0.9") == 0)
26             target->version = 9;
27           else if (ver.find("HTTP/1.0") == 0)
28             target->version = 10;
29           else if (ver.find("HTTP/1.1") == 0)
30             target->version = 11;
31           else
32             throw ParseError("HTTP version not supported");
33           // uppercase the target method
34           std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper);
35           target->url.parse(tmpurl);
36           target->getvars = Utility::parseUrlParameters(target->url.parameters);
37           state = 1;
38         } else if(target->kind == YAHTTP_TYPE_RESPONSE) {
39           std::string ver;
40           std::istringstream iss(line);
41           std::string::size_type pos1;
42           iss >> ver >> target->status;
43           std::getline(iss, target->statusText);
44           pos1=0;
45           while(pos1 < target->statusText.size() && ::isspace(target->statusText.at(pos1))) pos1++;
46           target->statusText = target->statusText.substr(pos1);
47           if ((pos1 = target->statusText.find("\r")) != std::string::npos) {
48             target->statusText = target->statusText.substr(0, pos1-1);
49           }
50           if (ver.size() == 0) {
51             target->version = 9;
52           } else if (ver.find("HTTP/0.9") == 0)
53             target->version = 9;
54           else if (ver.find("HTTP/1.0") == 0)
55             target->version = 10;
56           else if (ver.find("HTTP/1.1") == 0)
57             target->version = 11;
58           else
59             throw ParseError("HTTP version not supported");
60           state = 1;
61         }
62       } else if (state == 1) {
63         std::string key,value;
64         size_t pos;
65         if (line.empty()) {
66           chunked = (target->headers.find("transfer-encoding") != target->headers.end() && target->headers["transfer-encoding"] == "chunked");
67           state = 2;
68           break;
69         }
70         // split headers
71         if ((pos = line.find(": ")) == std::string::npos)
72           throw ParseError("Malformed header line");
73         key = line.substr(0, pos);
74         value = line.substr(pos+2);
75         for(std::string::iterator it=key.begin(); it != key.end(); it++)
76           if (std::isspace(*it))
77             throw ParseError("Header key contains whitespace which is not allowed by RFC");
78 
79         Utility::trim(value);
80         std::transform(key.begin(), key.end(), key.begin(), ::tolower);
81         // is it already defined
82 
83         if ((key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) ||
84             (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST)) {
85           target->jar.parseCookieHeader(value);
86         } else {
87           if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) {
88             // maybe it contains port?
89             if ((pos = value.find(":")) == std::string::npos) {
90               target->url.host = value;
91             } else {
92               target->url.host = value.substr(0, pos);
93               target->url.port = ::atoi(value.substr(pos).c_str());
94             }
95           }
96           if (target->headers.find(key) != target->headers.end()) {
97             target->headers[key] = target->headers[key] + ";" + value;
98           } else {
99             target->headers[key] = value;
100           }
101         }
102       }
103     }
104 
105     minbody = 0;
106     // check for expected body size
107     if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = target->max_request_size;
108     else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size;
109     else maxbody = 0;
110 
111     if (!chunked) {
112       if (target->headers.find("content-length") != target->headers.end()) {
113         std::istringstream maxbodyS(target->headers["content-length"]);
114         maxbodyS >> minbody;
115         maxbody = minbody;
116       }
117       if (minbody < 1) return true; // guess there isn't anything left.
118       if (target->kind == YAHTTP_TYPE_REQUEST && static_cast<ssize_t>(minbody) > target->max_request_size) throw ParseError("Max request body size exceeded");
119       else if (target->kind == YAHTTP_TYPE_RESPONSE && static_cast<ssize_t>(minbody) > target->max_response_size) throw ParseError("Max response body size exceeded");
120     }
121 
122     if (maxbody == 0) hasBody = false;
123     else hasBody = true;
124 
125     if (buffer.size() == 0) return ready();
126 
127     while(buffer.size() > 0) {
128       if (chunked) {
129         if (chunk_size == 0) {
130           char buf[100];
131           // read chunk length
132           if ((pos = buffer.find('\n')) == std::string::npos) return false;
133           if (pos > 99)
134             throw ParseError("Impossible chunk_size");
135           buffer.copy(buf, pos);
136           buf[pos]=0; // just in case...
137           buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer
138           sscanf(buf, "%x", &chunk_size);
139           if (!chunk_size) { state = 3; break; } // last chunk
140         } else {
141           int crlf=1;
142           if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline
143           if (buffer.at(chunk_size) == '\r') {
144             if (buffer.size() < static_cast<size_t>(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return
145             crlf=2;
146           } else if (buffer.at(chunk_size) != '\n') return false;
147           std::string tmp = buffer.substr(0, chunk_size);
148           buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf);
149           bodybuf << tmp;
150           chunk_size = 0;
151           if (buffer.size() == 0) break; // just in case
152         }
153       } else {
154         if (bodybuf.str().length() + buffer.length() > maxbody)
155           bodybuf << buffer.substr(0, maxbody - bodybuf.str().length());
156         else
157           bodybuf << buffer;
158         buffer = "";
159       }
160     }
161 
162     if (chunk_size!=0) return false; // need more data
163 
164     return ready();
165   };
166 
write(std::ostream & os) const167   void HTTPBase::write(std::ostream& os) const {
168     if (kind == YAHTTP_TYPE_REQUEST) {
169       std::ostringstream getparmbuf;
170       std::string getparms;
171       // prepare URL
172       for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) {
173         getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&";
174       }
175       if (getparmbuf.str().length() > 0) {
176         std::string buf = getparmbuf.str();
177         getparms = "?" + std::string(buf.begin(), buf.end() - 1);
178       }
179       else
180         getparms = "";
181       os << method << " " << url.path << getparms << " HTTP/" << versionStr(this->version);
182     } else if (kind == YAHTTP_TYPE_RESPONSE) {
183       os << "HTTP/" << versionStr(this->version) << " " << status << " ";
184       if (statusText.empty())
185         os << Utility::status2text(status);
186       else
187         os << statusText;
188     }
189     os << "\r\n";
190 
191     bool cookieSent = false;
192     bool sendChunked = false;
193 
194     if (this->version > 10) { // 1.1 or better
195       if (headers.find("content-length") == headers.end()) {
196         // must use chunked on response
197         sendChunked = (kind == YAHTTP_TYPE_RESPONSE);
198         if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) {
199           throw YaHTTP::Error("Transfer-encoding must be chunked, or Content-Length defined");
200         }
201         if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) {
202           sendChunked = true;
203           os << "Transfer-Encoding: chunked\r\n";
204         }
205       } else {
206 	sendChunked = false;
207       }
208     }
209 
210     // write headers
211     strstr_map_t::const_iterator iter = headers.begin();
212     while(iter != headers.end()) {
213       if (iter->first == "host" && kind != YAHTTP_TYPE_REQUEST) { iter++; continue; }
214       if (iter->first == "transfer-encoding" && sendChunked) { iter++; continue; }
215       std::string header = Utility::camelizeHeader(iter->first);
216       if (header == "Cookie" || header == "Set-Cookie") cookieSent = true;
217       os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n";
218       iter++;
219     }
220     if (!cookieSent && jar.cookies.size() > 0) { // write cookies
221       for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
222         if (kind == YAHTTP_TYPE_REQUEST) {
223           os << "Cookie: ";
224         } else {
225           os << "Set-Cookie: ";
226         }
227         os << i->second.str() << "\r\n";
228       }
229     }
230     os << "\r\n";
231 #ifdef HAVE_CPP_FUNC_PTR
232     this->renderer(this, os, sendChunked);
233 #else
234     SendbodyRenderer r;
235     r(this, os, chunked)
236 #endif
237   };
238 
operator <<(std::ostream & os,const Response & resp)239   std::ostream& operator<<(std::ostream& os, const Response &resp) {
240     resp.write(os);
241     return os;
242   };
243 
operator >>(std::istream & is,Response & resp)244   std::istream& operator>>(std::istream& is, Response &resp) {
245     YaHTTP::AsyncResponseLoader arl;
246     arl.initialize(&resp);
247     while(is.good()) {
248       char buf[1024];
249       is.read(buf, 1024);
250       if (is.gcount()>0) { // did we actually read anything
251         is.clear();
252         if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
253       }
254     }
255     // throw unless ready
256     if (arl.ready() == false)
257       throw ParseError("Was not able to extract a valid Response from stream");
258     arl.finalize();
259     return is;
260   };
261 
operator <<(std::ostream & os,const Request & req)262   std::ostream& operator<<(std::ostream& os, const Request &req) {
263     req.write(os);
264     return os;
265   };
266 
operator >>(std::istream & is,Request & req)267   std::istream& operator>>(std::istream& is, Request &req) {
268     YaHTTP::AsyncRequestLoader arl;
269     arl.initialize(&req);
270     while(is.good()) {
271       char buf[1024];
272       is.read(buf, 1024);
273       if (is.gcount()) { // did we actually read anything
274         is.clear();
275         if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
276       }
277     }
278     if (arl.ready() == false)
279       throw ParseError("Was not able to extract a valid Request from stream");
280     arl.finalize();
281     return is;
282   };
283 };
284