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