/*! Implements a Cookie jar for storing multiple cookies */
class CookieJar {
public:
- std::map<std::string, Cookie> cookies; //<! cookie container
+ std::map<std::string, Cookie, ASCIICINullSafeComparator> cookies; //<! cookie container
CookieJar() {}; //<! constructs empty cookie jar
CookieJar(const CookieJar & rhs) {
this->cookies = rhs.cookies;
} //<! copy cookies from another cookie jar
+ void clear() {
+ this->cookies.clear();
+ }
+
void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value) {
size_t pos;
pos = keyvalue.find("=");
buffer.append(somedata);
while(state < 2) {
int cr=0;
+ pos = buffer.find_first_of("\n");
// need to find CRLF in buffer
- if ((pos = buffer.find_first_of("\n")) == std::string::npos) return false;
- if (buffer[pos-1]=='\r')
+ if (pos == std::string::npos) return false;
+ if (pos>0 && buffer[pos-1]=='\r')
cr=1;
std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF
buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF
std::string tmpurl;
std::istringstream iss(line);
iss >> target->method >> tmpurl >> ver;
- if (ver.find("HTTP/1.") != 0)
- throw ParseError("Not a HTTP 1.x request");
+ if (ver.size() == 0)
+ target->version = 9;
+ else if (ver.find("HTTP/0.9") == 0)
+ target->version = 9;
+ else if (ver.find("HTTP/1.0") == 0)
+ target->version = 10;
+ else if (ver.find("HTTP/1.1") == 0)
+ target->version = 11;
+ else
+ throw ParseError("HTTP version not supported");
// uppercase the target method
std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper);
target->url.parse(tmpurl);
} else if(target->kind == YAHTTP_TYPE_RESPONSE) {
std::string ver;
std::istringstream iss(line);
- iss >> ver >> target->status >> target->statusText;
- if (ver.find("HTTP/1.") != 0)
- throw ParseError("Not a HTTP 1.x response");
+ std::string::size_type pos1;
+ iss >> ver >> target->status;
+ std::getline(iss, target->statusText);
+ pos1=0;
+ while(pos1 < target->statusText.size() && ::isspace(target->statusText.at(pos1))) pos1++;
+ target->statusText = target->statusText.substr(pos1);
+ if ((pos1 = target->statusText.find("\r")) != std::string::npos) {
+ target->statusText = target->statusText.substr(0, pos1-1);
+ }
+ if (ver.size() == 0) {
+ target->version = 9;
+ } else if (ver.find("HTTP/0.9") == 0)
+ target->version = 9;
+ else if (ver.find("HTTP/1.0") == 0)
+ target->version = 10;
+ else if (ver.find("HTTP/1.1") == 0)
+ target->version = 11;
+ else
+ throw ParseError("HTTP version not supported");
state = 1;
}
} else if (state == 1) {
break;
}
// split headers
- if ((pos = line.find_first_of(": ")) == std::string::npos)
+ if ((pos = line.find(": ")) == std::string::npos)
throw ParseError("Malformed header line");
key = line.substr(0, pos);
value = line.substr(pos+2);
- Utility::trimRight(value);
+ for(std::string::iterator it=key.begin(); it != key.end(); it++)
+ if (std::isspace(*it))
+ throw ParseError("Header key contains whitespace which is not allowed by RFC");
+
+ Utility::trim(value);
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
// is it already defined
target->jar.parseCookieHeader(value);
} else {
if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) {
- // maybe it contains port?
+ // maybe it contains port?
if ((pos = value.find(":")) == std::string::npos) {
target->url.host = value;
} else {
minbody = 0;
// check for expected body size
if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = target->max_request_size;
- else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size;
+ else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size;
else maxbody = 0;
-
+
if (!chunked) {
if (target->headers.find("content-length") != target->headers.end()) {
std::istringstream maxbodyS(target->headers["content-length"]);
if (buffer.at(chunk_size) == '\r') {
if (buffer.size() < static_cast<size_t>(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return
crlf=2;
- } else if (buffer.at(chunk_size) != '\n') return false;
+ } else if (buffer.at(chunk_size) != '\n') return false;
std::string tmp = buffer.substr(0, chunk_size);
buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf);
bodybuf << tmp;
if (buffer.size() == 0) break; // just in case
}
} else {
- if (bodybuf.str().length() + buffer.length() > maxbody)
+ if (bodybuf.str().length() + buffer.length() > maxbody)
bodybuf << buffer.substr(0, maxbody - bodybuf.str().length());
- else
+ else
bodybuf << buffer;
buffer = "";
}
return ready();
};
-
+
void HTTPBase::write(std::ostream& os) const {
if (kind == YAHTTP_TYPE_REQUEST) {
std::ostringstream getparmbuf;
std::string getparms;
- // prepare URL
+ // prepare URL
for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) {
getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&";
}
- if (getparmbuf.str().length() > 0)
+ if (getparmbuf.str().length() > 0)
getparms = "?" + std::string(getparmbuf.str().begin(), getparmbuf.str().end() - 1);
else
getparms = "";
- os << method << " " << url.path << getparms << " HTTP/1.1";
+ os << method << " " << url.path << getparms << " HTTP/" << versionStr(this->version);
} else if (kind == YAHTTP_TYPE_RESPONSE) {
- os << "HTTP/1.1 " << status << " ";
+ os << "HTTP/" << versionStr(this->version) << " " << status << " ";
if (statusText.empty())
os << Utility::status2text(status);
else
os << statusText;
}
os << "\r\n";
-
+
+ bool cookieSent = false;
+ bool sendChunked = false;
+
+ if (this->version > 10) { // 1.1 or better
+ if (headers.find("content-length") == headers.end()) {
+ // must use chunked on response
+ sendChunked = (kind == YAHTTP_TYPE_RESPONSE);
+ if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) {
+ throw YaHTTP::Error("Transfer-encoding must be chunked, or Content-Length defined");
+ }
+ if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) {
+ sendChunked = true;
+ // write the header now
+ os << "Transfer-Encoding: chunked" << "\r\n";
+ }
+ } else {
+ if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) {
+ sendChunked = true;
+ // write the header now
+ os << "Transfer-Encoding: chunked" << "\r\n";
+ } else if (headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second == "chunked") {
+ sendChunked = true;
+ }
+ }
+ }
+
// write headers
strstr_map_t::const_iterator iter = headers.begin();
while(iter != headers.end()) {
if (iter->first == "host" && kind != YAHTTP_TYPE_REQUEST) { iter++; continue; }
+ std::string header = Utility::camelizeHeader(iter->first);
+ if (header == "Cookie" || header == "Set-Cookie") cookieSent = true;
os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n";
iter++;
}
- if (jar.cookies.size() > 0) { // write cookies
+ if (!cookieSent && jar.cookies.size() > 0) { // write cookies
for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
if (kind == YAHTTP_TYPE_REQUEST) {
os << "Cookie: ";
}
os << "\r\n";
#ifdef HAVE_CPP_FUNC_PTR
- this->renderer(this, os);
+ this->renderer(this, os, sendChunked);
#else
- os << body;
+ SendbodyRenderer r;
+ r(this, os, chunked)
#endif
};
-
+
std::ostream& operator<<(std::ostream& os, const Response &resp) {
resp.write(os);
return os;
};
-
+
std::istream& operator>>(std::istream& is, Response &resp) {
YaHTTP::AsyncResponseLoader arl;
arl.initialize(&resp);
while(is.good()) {
char buf[1024];
is.read(buf, 1024);
- if (is.gcount()) { // did we actually read anything
+ if (is.gcount()>0) { // did we actually read anything
is.clear();
if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
}
}
// throw unless ready
- if (arl.ready() == false)
+ if (arl.ready() == false)
throw ParseError("Was not able to extract a valid Response from stream");
arl.finalize();
return is;
};
-
+
std::ostream& operator<<(std::ostream& os, const Request &req) {
req.write(os);
return os;
};
-
+
std::istream& operator>>(std::istream& is, Request &req) {
YaHTTP::AsyncRequestLoader arl;
arl.initialize(&req);
#include <unistd.h>
#endif
+#include <algorithm>
+
#ifndef YAHTTP_MAX_REQUEST_SIZE
#define YAHTTP_MAX_REQUEST_SIZE 2097152
#endif
#define YAHTTP_TYPE_RESPONSE 2
namespace YaHTTP {
- typedef std::map<std::string,std::string> strstr_map_t; //<! String to String map
- typedef std::map<std::string,Cookie> strcookie_map_t; //<! String to Cookie map
+ typedef std::map<std::string,Cookie,ASCIICINullSafeComparator> strcookie_map_t; //<! String to Cookie map
typedef enum {
urlencoded,
/*! Base class for request and response */
class HTTPBase {
public:
-#ifdef HAVE_CPP_FUNC_PTR
/*! Default renderer for request/response, simply copies body to response */
class SendBodyRender {
public:
SendBodyRender() {};
- size_t operator()(const HTTPBase *doc, std::ostream& os) const {
- os << doc->body;
+ size_t operator()(const HTTPBase *doc, std::ostream& os, bool chunked) const {
+ if (chunked) {
+ std::string::size_type i,cl;
+ for(i=0;i<doc->body.length();i+=1024) {
+ cl = std::min(static_cast<std::string::size_type>(1024), doc->body.length()-i); // for less than 1k blocks
+ os << std::hex << cl << std::dec << "\r\n";
+ os << doc->body.substr(i, cl) << "\r\n";
+ }
+ os << 0 << "\r\n\r\n"; // last chunk
+ } else {
+ os << doc->body;
+ }
return doc->body.length();
}; //<! writes body to ostream and returns length
};
this->path = path;
};
- size_t operator()(const HTTPBase *doc __attribute__((unused)), std::ostream& os) const {
+ size_t operator()(const HTTPBase *doc __attribute__((unused)), std::ostream& os, bool chunked) const {
char buf[4096];
size_t n,k;
#ifdef HAVE_CXX11
std::ifstream ifs(path.c_str(), std::ifstream::binary);
#endif
n = 0;
+
while(ifs && ifs.good()) {
ifs.read(buf, sizeof buf);
n += (k = ifs.gcount());
- if (k)
+ if (k) {
+ if (chunked) os << std::hex << k << std::dec << "\r\n";
os.write(buf, k);
+ if (chunked) os << "\r\n";
+ }
}
-
+ if (chunked) os << 0 << "\r\n\r\n";
return n;
}; //<! writes file to ostream and returns length
std::string path; //<! File to send
};
-#endif
+
HTTPBase() {
+ initialize();
+ };
+
+ virtual void initialize() {
kind = 0;
status = 0;
#ifdef HAVE_CPP_FUNC_PTR
#endif
max_request_size = YAHTTP_MAX_REQUEST_SIZE;
max_response_size = YAHTTP_MAX_RESPONSE_SIZE;
- };
+ url = "";
+ method = "";
+ statusText = "";
+ jar.clear();
+ headers.clear();
+ parameters.clear();
+ getvars.clear();
+ postvars.clear();
+ body = "";
+ routeName = "";
+ version = 11; // default to version 1.1
+ }
protected:
HTTPBase(const HTTPBase& rhs) {
this->url = rhs.url; this->kind = rhs.kind;
this->jar = rhs.jar; this->postvars = rhs.postvars;
this->parameters = rhs.parameters; this->getvars = rhs.getvars;
this->body = rhs.body; this->max_request_size = rhs.max_request_size;
- this->max_response_size = rhs.max_response_size;
+ this->max_response_size = rhs.max_response_size; this->version = rhs.version;
#ifdef HAVE_CPP_FUNC_PTR
this->renderer = rhs.renderer;
#endif
this->jar = rhs.jar; this->postvars = rhs.postvars;
this->parameters = rhs.parameters; this->getvars = rhs.getvars;
this->body = rhs.body; this->max_request_size = rhs.max_request_size;
- this->max_response_size = rhs.max_response_size;
+ this->max_response_size = rhs.max_response_size; this->version = rhs.version;
#ifdef HAVE_CPP_FUNC_PTR
this->renderer = rhs.renderer;
#endif
URL url; //<! URL of this request/response
int kind; //<! Type of object (1 = request, 2 = response)
int status; //<! status code
+ int version; //<! http version 9 = 0.9, 10 = 1.0, 11 = 1.1
std::string statusText; //<! textual representation of status code
std::string method; //<! http verb
strstr_map_t headers; //<! map of header(s)
ssize_t max_response_size; //<! maximum size of response
#ifdef HAVE_CPP_FUNC_PTR
- funcptr::function<size_t(const HTTPBase*,std::ostream&)> renderer; //<! rendering function
+ funcptr::function<size_t(const HTTPBase*,std::ostream&,bool)> renderer; //<! rendering function
#endif
void write(std::ostream& os) const; //<! writes request to the given output stream
strstr_map_t& POST() { return postvars; }; //<! accessor for postvars
strcookie_map_t& COOKIES() { return jar.cookies; }; //<! accessor for cookies
+ std::string versionStr(int version) const {
+ switch(version) {
+ case 9: return "0.9";
+ case 10: return "1.0";
+ case 11: return "1.1";
+ default: throw YaHTTP::Error("Unsupported version");
+ }
+ };
+
std::string str() const {
std::ostringstream oss;
write(oss);
/*! Response class, represents a HTTP Response document */
class Response: public HTTPBase {
public:
- Response() { this->kind = YAHTTP_TYPE_RESPONSE; };
+ Response() { initialize(); };
Response(const HTTPBase& rhs): HTTPBase(rhs) {
this->kind = YAHTTP_TYPE_RESPONSE;
};
this->kind = YAHTTP_TYPE_RESPONSE;
return *this;
};
+ void initialize() {
+ HTTPBase::initialize();
+ this->kind = YAHTTP_TYPE_RESPONSE;
+ }
friend std::ostream& operator<<(std::ostream& os, const Response &resp);
friend std::istream& operator>>(std::istream& is, Response &resp);
};
/* Request class, represents a HTTP Request document */
class Request: public HTTPBase {
public:
- Request() { this->kind = YAHTTP_TYPE_REQUEST; };
+ Request() { initialize(); };
Request(const HTTPBase& rhs): HTTPBase(rhs) {
this->kind = YAHTTP_TYPE_REQUEST;
};
this->kind = YAHTTP_TYPE_REQUEST;
return *this;
};
-
+ void initialize() {
+ HTTPBase::initialize();
+ this->kind = YAHTTP_TYPE_REQUEST;
+ }
void setup(const std::string& method, const std::string& url) {
this->url.parse(url);
this->headers["host"] = this->url.host;
bodybuf.str(""); maxbody = 0;
pos = 0; state = 0; this->target = target;
hasBody = false;
+ buffer = "";
+ this->target->initialize();
}; //<! Initialize the parser for target and clear state
int feed(const std::string& somedata); //<! Feed data to the parser
bool ready() {
continue;
}
}
- std::cout << mask.substr(k3) << std::endl;
path << mask.substr(k3);
result = path.str();
return std::make_pair(method, result);
static const char *MONTHS[] = {0,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",0}; //<! List of months
static const char *DAYS[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat",0}; //<! List of days
+ /*! Case-Insensitive NULL safe comparator for string maps */
+ struct ASCIICINullSafeComparator {
+ bool operator() (const std::string& lhs, const std::string& rhs) const {
+ char v;
+ std::string::const_iterator lhi = lhs.begin();
+ std::string::const_iterator rhi = rhs.begin();
+ for(;lhi != lhs.end() && rhi != rhs.end(); lhi++, rhi++)
+ if ((v = ::tolower(*lhi) - ::tolower(*rhi)) != 0) return v<0;
+ if (lhi == lhs.end() && rhi != rhs.end()) return true;
+ if (lhi != lhs.end() && rhi == rhs.end()) return false;
+ return false; // they are equal
+ }
+ };
+
+ typedef std::map<std::string,std::string,ASCIICINullSafeComparator> strstr_map_t; //<! String to String map
+
/*! Represents a date/time with utc offset */
class DateTime {
public:
const char *ptr;
if ( (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T", &tm)) != NULL) {
while(*ptr && ( ::isspace(*ptr) || ::isalnum(*ptr) )) ptr++;
- std::cerr << ptr << std::endl;
if (*ptr) throw "Unparseable date (non-final)"; // must be final.
fromTm(&tm);
this->utc_offset = 0;
}
}; //<! static HTTP codes to text mappings
- static std::map<std::string,std::string> parseUrlParameters(std::string parameters) {
+ static strstr_map_t parseUrlParameters(std::string parameters) {
std::string::size_type pos = 0;
- std::map<std::string,std::string> parameter_map;
+ strstr_map_t parameter_map;
while (pos != std::string::npos) {
// find next parameter start
std::string::size_type nextpos = parameters.find("&", pos);
return iequals(a,b,a.size());
}; //<! case-insensitive comparison
+ static void trimLeft(std::string &str) {
+ const std::locale &loc = std::locale::classic();
+ std::string::iterator iter = str.begin();
+ while(iter != str.end() && std::isspace(*iter, loc)) iter++;
+ str.erase(str.begin(), iter);
+ }; //<! removes whitespace from left
+
static void trimRight(std::string &str) {
const std::locale &loc = std::locale::classic();
std::string::reverse_iterator iter = str.rbegin();
str.erase(iter.base(), str.end());
}; //<! removes whitespace from right
+ static void trim(std::string &str) {
+ trimLeft(str);
+ trimRight(str);
+ }; //<! removes whitespace from left and right
+
static std::string camelizeHeader(const std::string &str) {
std::string::const_iterator iter = str.begin();
std::string result;