secure = false;
httponly = false;
name = value = "";
+ expires = DateTime();
}; //!< Set the cookie to empty value
Cookie(const Cookie &rhs) {
+ name = rhs.name;
+ value = rhs.value;
domain = rhs.domain;
path = rhs.path;
secure = rhs.secure;
httponly = rhs.httponly;
+ expires = rhs.expires;
+ }; //<! Copy cookie values
+
+ Cookie& operator=(const Cookie &rhs) {
name = rhs.name;
value = rhs.value;
- }; //<! Copy cookie values
+ domain = rhs.domain;
+ path = rhs.path;
+ secure = rhs.secure;
+ httponly = rhs.httponly;
+ expires = rhs.expires;
+ return *this;
+ }
DateTime expires; /*!< Expiration date */
std::string domain; /*!< Domain where cookie is valid */
std::string str() const {
std::ostringstream oss;
oss << YaHTTP::Utility::encodeURL(name) << "=" << YaHTTP::Utility::encodeURL(value);
+
if (expires.isSet)
oss << "; expires=" << expires.cookie_str();
if (domain.size()>0)
} //<! key value pair parser
void parseCookieHeader(const std::string &cookiestr) {
+ size_t pos, npos;
std::list<Cookie> lcookies;
- int cstate = 0; //cookiestate
- size_t pos,npos;
+ Cookie c;
pos = 0;
- cstate = 0;
while(pos < cookiestr.size()) {
- if (cookiestr.compare(pos, 7, "expires") ==0 ||
- cookiestr.compare(pos, 6, "domain") ==0 ||
- cookiestr.compare(pos, 4, "path") ==0) {
- cstate = 1;
- // get the date
- std::string key, value, s;
- npos = cookiestr.find("; ", pos);
- if (npos == std::string::npos) {
- // last value
- s = std::string(cookiestr.begin() + pos + 1, cookiestr.end());
- pos = cookiestr.size();
- } else {
- s = std::string(cookiestr.begin() + pos + 1, cookiestr.begin() + npos - 1);
- pos = npos+2;
- }
- keyValuePair(s, key, value);
- if (s == "expires") {
- DateTime dt;
- dt.parseCookie(value);
- for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++)
- i->expires = dt;
- } else if (s == "domain") {
- for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++)
- i->domain = value;
- } else if (s == "path") {
- for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++)
- i->path = value;
- }
- } else if (cookiestr.compare(pos, 8, "httpOnly")==0) {
- cstate = 1;
- for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++)
- i->httponly = true;
- } else if (cookiestr.compare(pos, 6, "secure") ==0) {
- cstate = 1;
- for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++)
- i->secure = true;
- } else if (cstate == 0) { // expect cookie
- Cookie c;
- std::string s;
- npos = cookiestr.find("; ", pos);
- if (npos == std::string::npos) {
- // last value
- s = std::string(cookiestr.begin() + pos, cookiestr.end());
- pos = cookiestr.size();
- } else {
- s = std::string(cookiestr.begin() + pos, cookiestr.begin() + npos);
- pos = npos+2;
- }
- keyValuePair(s, c.name, c.value);
- c.name = YaHTTP::Utility::decodeURL(c.name);
- c.value = YaHTTP::Utility::decodeURL(c.value);
- lcookies.push_back(c);
- } else if (cstate == 1) {
+ if ((npos = cookiestr.find("; ", pos)) == std::string::npos)
+ npos = cookiestr.size();
+ keyValuePair(cookiestr.substr(pos, npos-pos), c.name, c.value);
+ c.name = YaHTTP::Utility::decodeURL(c.name);
+ c.value = YaHTTP::Utility::decodeURL(c.value);
+ lcookies.push_back(c);
+ pos = npos+2;
+ }
+ for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++) {
+ this->cookies[i->name] = *i;
+ }
+ }
+
+ void parseSetCookieHeader(const std::string &cookiestr) {
+ Cookie c;
+ size_t pos,npos;
+ std::string k, v;
+
+ if ((pos = cookiestr.find("; ", 0)) == std::string::npos)
+ pos = cookiestr.size();
+ keyValuePair(cookiestr.substr(0, pos), c.name, c.value);
+ c.name = YaHTTP::Utility::decodeURL(c.name);
+ c.value = YaHTTP::Utility::decodeURL(c.value);
+ if (pos < cookiestr.size()) pos+=2;
+
+ while(pos < cookiestr.size()) {
+ if ((npos = cookiestr.find("; ", pos)) == std::string::npos)
+ npos = cookiestr.size();
+ std::string s = cookiestr.substr(pos, npos-pos);
+ if (s.find("=") != std::string::npos)
+ keyValuePair(s, k, v);
+ else
+ k = s;
+ if (k == "expires") {
+ DateTime dt;
+ dt.parseCookie(v);
+ c.expires = dt;
+ } else if (k == "domain") {
+ c.domain = v;
+ } else if (k == "path") {
+ c.path = v;
+ } else if (k == "httpOnly") {
+ c.httponly = true;
+ } else if (k == "secure") {
+ c.secure = true;
+ } else {
// ignore crap
break;
}
+ pos = npos+2;
}
- // store cookies
- for(std::list<Cookie>::iterator i = lcookies.begin(); i != lcookies.end(); i++) {
- this->cookies[i->name] = *i;
- }
+ this->cookies[c.name] = c;
}; //<! Parse multiple cookies from header
};
};
#include "yahttp.hpp"
namespace YaHTTP {
+
+ bool isspace(char c) {
+ return std::isspace(c) != 0;
+ }
+
+ bool isspace(char c, const std::locale& loc) {
+ return std::isspace(c, loc);
+ }
+
+ bool isxdigit(char c) {
+ return std::isxdigit(c) != 0;
+ }
+
+ bool isxdigit(char c, const std::locale& loc) {
+ return std::isxdigit(c, loc);
+ }
+
+ bool isdigit(char c) {
+ return std::isdigit(c) != 0;
+ }
+
+ bool isdigit(char c, const std::locale& loc) {
+ return std::isdigit(c, loc);
+ }
+
+ bool isalnum(char c) {
+ return std::isalnum(c) != 0;
+ }
+
+ bool isalnum(char c, const std::locale& loc) {
+ return std::isalnum(c, loc);
+ }
+
template <class T>
- int AsyncLoader<T>::feed(const std::string& somedata) {
+ bool AsyncLoader<T>::feed(const std::string& somedata) {
buffer.append(somedata);
while(state < 2) {
int cr=0;
iss >> ver >> target->status;
std::getline(iss, target->statusText);
pos1=0;
- while(pos1 < target->statusText.size() && ::isspace(target->statusText.at(pos1))) pos1++;
+ while(pos1 < target->statusText.size() && YaHTTP::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);
key = line.substr(0, pos1);
value = line.substr(pos1+2);
for(std::string::iterator it=key.begin(); it != key.end(); it++)
- if (std::isspace(*it))
+ if (YaHTTP::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
- if ((key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) ||
- (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST)) {
+ if (key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) {
+ target->jar.parseSetCookieHeader(value);
+ } else if (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST) {
target->jar.parseCookieHeader(value);
} else {
if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) {
buf[pos]=0; // just in case...
buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer
sscanf(buf, "%x", &chunk_size);
- if (!chunk_size) { state = 3; break; } // last chunk
+ if (chunk_size == 0) { state = 3; break; } // last chunk
} else {
int crlf=1;
if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline
bool sendChunked = false;
if (this->version > 10) { // 1.1 or better
- if (headers.find("content-length") == headers.end()) {
+ if (headers.find("content-length") == headers.end() && !this->is_multipart) {
// must use chunked on response
sendChunked = (kind == YAHTTP_TYPE_RESPONSE);
if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) {
// write headers
strstr_map_t::const_iterator iter = headers.begin();
while(iter != headers.end()) {
- if (iter->first == "host" && kind != YAHTTP_TYPE_REQUEST) { iter++; continue; }
+ if (iter->first == "host" && (kind != YAHTTP_TYPE_REQUEST || version < 10)) { iter++; continue; }
if (iter->first == "transfer-encoding" && sendChunked) { 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 (!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: ";
- } else {
+ if (version > 9 && !cookieSent && jar.cookies.size() > 0) { // write cookies
+ if (kind == YAHTTP_TYPE_REQUEST) {
+ bool first = true;
+ os << "Cookie: ";
+ for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
+ if (first)
+ first = false;
+ else
+ os << "; ";
+ os << Utility::encodeURL(i->second.name) << "=" << Utility::encodeURL(i->second.value);
+ }
+ } else if (kind == YAHTTP_TYPE_REQUEST) {
+ for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
os << "Set-Cookie: ";
+ os << i->second.str() << "\r\n";
}
- os << i->second.str() << "\r\n";
}
}
os << "\r\n";
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
}
#endif
n = 0;
- while(ifs && ifs.good()) {
+ while(ifs.good()) {
ifs.read(buf, sizeof buf);
n += (k = ifs.gcount());
- if (k) {
+ if (k > 0) {
if (chunked) os << std::hex << k << std::dec << "\r\n";
os.write(buf, k);
if (chunked) os << "\r\n";
body = "";
routeName = "";
version = 11; // default to version 1.1
+ is_multipart = false;
}
protected:
HTTPBase(const HTTPBase& rhs) {
ssize_t max_request_size; //<! maximum size of request
ssize_t max_response_size; //<! maximum size of response
-
+ bool is_multipart; //<! if the request is multipart, prevents Content-Length header
#ifdef HAVE_CPP_FUNC_PTR
funcptr::function<size_t(const HTTPBase*,std::ostream&,bool)> renderer; //<! rendering function
#endif
headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
} else if (format == multipart) {
headers["content-type"] = "multipart/form-data; boundary=YaHTTP-12ca543";
+ this->is_multipart = true;
for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) {
- postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first, false) << "; charset=UTF-8\r\n\r\n"
+ 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"
<< Utility::encodeURL(i->second, false) << "\r\n";
}
+ postbuf << "--";
+ body = postbuf.str();
}
postbuf.str("");
postbuf << body.length();
// set method and change headers
method = "POST";
- headers["content-length"] = postbuf.str();
+ if (!this->is_multipart)
+ headers["content-length"] = postbuf.str();
}; //<! convert all postvars into string and stuff it into body
friend std::ostream& operator<<(std::ostream& os, const Request &resp);
buffer = "";
this->target->initialize();
}; //<! Initialize the parser for target and clear state
- int feed(const std::string& somedata); //<! Feed data to the parser
+ bool feed(const std::string& somedata); //<! Feed data to the parser
bool ready() {
return (chunked == true && state == 3) || // if it's chunked we get end of data indication
(chunked == false && state > 1 &&
password = url.substr(pos2+1, pos1 - pos2 - 1);
password = Utility::decodeURL(password);
} else {
- username = url.substr(pos+1, pos1 - pos);
+ username = url.substr(pos, pos1 - pos);
}
pos = pos1+1;
username = Utility::decodeURL(username);
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
+ bool isspace(char c);
+ bool isspace(char c, const std::locale& loc);
+ bool isxdigit(char c);
+ bool isxdigit(char c, const std::locale& loc);
+ bool isdigit(char c);
+ bool isdigit(char c, const std::locale& loc);
+ bool isalnum(char c);
+ bool isalnum(char c, const std::locale& loc);
+
/*! Case-Insensitive NULL safe comparator for string maps */
struct ASCIICINullSafeComparator {
bool operator() (const std::string& lhs, const std::string& rhs) const {
}; //<! parses date from struct tm
void validate() const {
- if (wday < 0 || wday > 6) throw "Invalid date";
- if (month < 1 || month > 12) throw "Invalid date";
- if (year < 0) throw "Invalid date";
+ if (wday < 0 || wday > 6) throw std::range_error("Invalid date");
+ if (month < 1 || month > 12) throw std::range_error("Invalid date");
+ if (year < 0) throw std::range_error("Invalid date");
if (hours < 0 || hours > 23 ||
minutes < 0 || minutes > 59 ||
- seconds < 0 || seconds > 60) throw "Invalid date";
+ seconds < 0 || seconds > 60) throw std::range_error("Invalid date");
}; //<! make sure we are within ranges (not a *REAL* validation, just range check)
std::string rfc_str() const {
if ( (ptr = strptime(rfc822_date.c_str(), "%a, %d %b %Y %T", &tm)) != NULL) {
int sign;
// parse the timezone parameter
- while(*ptr && ::isspace(*ptr)) ptr++;
+ while(*ptr && YaHTTP::isspace(*ptr)) ptr++;
if (*ptr == '+') sign = 0;
else if (*ptr == '-') sign = -1;
- else throw "Unparseable date";
+ else throw YaHTTP::ParseError("Unparseable date");
ptr++;
utc_offset = ::atoi(ptr) * sign;
- while(*ptr && ::isdigit(*ptr)) ptr++;
+ while(*ptr != '\0' && YaHTTP::isdigit(*ptr)) ptr++;
#endif
- while(*ptr && ::isspace(*ptr)) ptr++;
- if (*ptr) throw "Unparseable date"; // must be final.
+ while(*ptr != '\0' && YaHTTP::isspace(*ptr)) ptr++;
+ if (*ptr != '\0') throw YaHTTP::ParseError("Unparseable date"); // must be final.
fromTm(&tm);
} else {
- throw "Unparseable date";
+ throw YaHTTP::ParseError("Unparseable date");
}
}; //<! parses RFC-822 date
void parseCookie(const std::string &cookie_date) {
struct tm tm;
const char *ptr;
- if ( (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T", &tm)) != NULL) {
- while(*ptr && ( ::isspace(*ptr) || ::isalnum(*ptr) )) ptr++;
- if (*ptr) throw "Unparseable date (non-final)"; // must be final.
+ if ( (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T", &tm)) != NULL
+#ifdef HAVE_TM_GMTOFF
+ || (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T %z", &tm)) != NULL
+ || (ptr = strptime(cookie_date.c_str(), "%a, %d-%b-%Y %T %Z", &tm)) != NULL
+#endif
+ ) {
+ while(*ptr != '\0' && ( YaHTTP::isspace(*ptr) || YaHTTP::isalnum(*ptr) )) ptr++;
+ if (*ptr != '\0') throw YaHTTP::ParseError("Unparseable date (non-final)"); // must be final.
fromTm(&tm);
this->utc_offset = 0;
} else {
- throw "Unparseable date (did not match pattern cookie)";
+ std::cout << cookie_date << std::endl;
+ throw YaHTTP::ParseError("Unparseable date (did not match pattern cookie)");
}
}; //<! parses HTTP Cookie date
char repl[3];
size_t pos;
for(std::string::iterator iter = result.begin(); iter != result.end(); iter++) {
- if (!std::isalnum(*iter) && (!asUrl || skip.find(*iter) == std::string::npos)) {
+ if (!YaHTTP::isalnum(*iter) && (!asUrl || skip.find(*iter) == std::string::npos)) {
// replace with different thing
pos = std::distance(result.begin(), iter);
::snprintf(repl,3,"%02x", static_cast<unsigned char>(*iter));
std::ostringstream result;
std::string skip = "+-.,&;_#%[]?/@(){}=";
for(std::vector<unsigned char>::iterator iter = vec.begin(); iter != vec.end(); iter++) {
- if (!std::isalnum((char)*iter) && (!asUrl || skip.find((char)*iter) == std::string::npos)) {
+ if (!YaHTTP::isalnum((char)*iter) && (!asUrl || skip.find((char)*iter) == std::string::npos)) {
// bit more complex replace
result << "%" << std::hex << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*iter);
} else result << (char)*iter;
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++;
+ while(iter != str.end() && YaHTTP::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();
- while(iter != str.rend() && std::isspace(*iter, loc)) iter++;
+ while(iter != str.rend() && YaHTTP::isspace(*iter, loc)) iter++;
str.erase(iter.base(), str.end());
}; //<! removes whitespace from right
#include <algorithm>
#include <string>
#include <cstdio>
+#include <stdexcept>
#include <sys/time.h>
#include <iomanip>
#include <list>
#include <vector>
#include "yahttp-config.h"
+#include "exception.hpp"
#include "url.hpp"
#include "utility.hpp"
-#include "exception.hpp"
#include "url.hpp"
#include "cookie.hpp"
#include "reqresp.hpp"