From: Mikko Rasa Date: Wed, 10 Aug 2011 18:07:48 +0000 (+0300) Subject: Prepare for assimilation into mspnet X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=refs%2Fheads%2Fhttp-master;p=libs%2Fnet.git Prepare for assimilation into mspnet --- diff --git a/source/client.cpp b/source/client.cpp deleted file mode 100644 index 0729544..0000000 --- a/source/client.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include -#include -#include -#include -#include "client.h" -#include "request.h" -#include "response.h" - -using namespace std; - -namespace Msp { -namespace Http { - -Client::Client(): - sock(0), - event_disp(0), - user_agent("libmsphttp/0.1"), - request(0), - response(0) -{ } - -Client::~Client() -{ - delete sock; - delete request; - delete response; -} - -void Client::use_event_dispatcher(IO::EventDispatcher *ed) -{ - if(event_disp && sock) - event_disp->remove(*sock); - event_disp = ed; - if(event_disp && sock) - event_disp->add(*sock); -} - -void Client::start_request(const Request &r) -{ - if(request) - throw InvalidState("Already processing a request"); - - string host = r.get_header("Host"); - if(host.find(':')==string::npos) - host += ":80"; - RefPtr addr = Net::resolve(host); - - delete sock; - sock = new Net::StreamSocket(addr->get_family()); - sock->set_block(false); - - sock->signal_data_available.connect(sigc::mem_fun(this, &Client::data_available)); - sock->signal_connect_finished.connect(sigc::mem_fun(this, &Client::connect_finished)); - if(event_disp) - event_disp->add(*sock); - - sock->connect(*addr); - - request = new Request(r); - if(!user_agent.empty()) - request->set_header("User-Agent", user_agent); - - delete response; - response = 0; - in_buf.clear(); -} - -const Response *Client::get_url(const std::string &url) -{ - start_request(Request::from_url(url)); - wait_response(); - return response; -} - -void Client::tick() -{ - if(!request) - return; - - while(IO::PollEvent ev = IO::poll(*sock, sock->get_events(), Time::zero)) - sock->event(ev); - - if(response && response->is_complete()) - { - signal_response_complete.emit(*response); - - delete sock; - sock = 0; - delete request; - request = 0; - } -} - -void Client::wait_response() -{ - while(request && (!response || !response->is_complete())) - tick(); -} - -void Client::abort() -{ - delete sock; - sock = 0; - delete request; - request = 0; -} - -void Client::connect_finished(const exception *err) -{ - if(err) - { - signal_socket_error.emit(err); - - delete request; - request = 0; - } - else - sock->write(request->str()); -} - -void Client::data_available() -{ - char rbuf[4096]; - unsigned len; - try - { - len = sock->read(rbuf, sizeof(rbuf)); - } - catch(const exception &e) - { - signal_socket_error.emit(&e); - return; - } - - if(!len) - return; - in_buf.append(rbuf, len); - - if(!response) - { - if(in_buf.find("\r\n\r\n")!=string::npos || in_buf.find("\n\n")!=string::npos) - { - response = new Response(Response::parse(in_buf)); - response->set_user_data(request->get_user_data()); - in_buf = string(); - } - } - else - { - len = response->parse_content(in_buf); - in_buf.erase(0, len); - } - - if(response && response->is_complete()) - { - signal_response_complete.emit(*response); - - delete request; - request = 0; - } -} - -} // namespace Http -} // namespace Msp diff --git a/source/client.h b/source/client.h deleted file mode 100644 index ee10f8c..0000000 --- a/source/client.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef MSP_HTTP_CLIENT_H_ -#define MSP_HTTP_CLIENT_H_ - -#include -#include -#include -#include - -namespace Msp { -namespace Http { - -class Request; -class Response; - -class Client -{ -public: - sigc::signal signal_response_complete; - sigc::signal signal_socket_error; - -private: - Net::StreamSocket *sock; - IO::EventDispatcher *event_disp; - std::string user_agent; - Request *request; - Response *response; - std::string in_buf; - - Client(const Client &); - Client &operator=(const Client &); -public: - Client(); - ~Client(); - - void use_event_dispatcher(IO::EventDispatcher *); - void set_user_agent(const std::string &); - void start_request(const Request &); - const Response *get_url(const std::string &); - void tick(); - void wait_response(); - void abort(); - const Request *get_request() const { return request; } - const Response *get_response() const { return response; } -private: - void connect_finished(const std::exception *); - void data_available(); -}; - -} // namespace Http -} // namespace Msp - -#endif diff --git a/source/http/client.cpp b/source/http/client.cpp new file mode 100644 index 0000000..0729544 --- /dev/null +++ b/source/http/client.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include "client.h" +#include "request.h" +#include "response.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Client::Client(): + sock(0), + event_disp(0), + user_agent("libmsphttp/0.1"), + request(0), + response(0) +{ } + +Client::~Client() +{ + delete sock; + delete request; + delete response; +} + +void Client::use_event_dispatcher(IO::EventDispatcher *ed) +{ + if(event_disp && sock) + event_disp->remove(*sock); + event_disp = ed; + if(event_disp && sock) + event_disp->add(*sock); +} + +void Client::start_request(const Request &r) +{ + if(request) + throw InvalidState("Already processing a request"); + + string host = r.get_header("Host"); + if(host.find(':')==string::npos) + host += ":80"; + RefPtr addr = Net::resolve(host); + + delete sock; + sock = new Net::StreamSocket(addr->get_family()); + sock->set_block(false); + + sock->signal_data_available.connect(sigc::mem_fun(this, &Client::data_available)); + sock->signal_connect_finished.connect(sigc::mem_fun(this, &Client::connect_finished)); + if(event_disp) + event_disp->add(*sock); + + sock->connect(*addr); + + request = new Request(r); + if(!user_agent.empty()) + request->set_header("User-Agent", user_agent); + + delete response; + response = 0; + in_buf.clear(); +} + +const Response *Client::get_url(const std::string &url) +{ + start_request(Request::from_url(url)); + wait_response(); + return response; +} + +void Client::tick() +{ + if(!request) + return; + + while(IO::PollEvent ev = IO::poll(*sock, sock->get_events(), Time::zero)) + sock->event(ev); + + if(response && response->is_complete()) + { + signal_response_complete.emit(*response); + + delete sock; + sock = 0; + delete request; + request = 0; + } +} + +void Client::wait_response() +{ + while(request && (!response || !response->is_complete())) + tick(); +} + +void Client::abort() +{ + delete sock; + sock = 0; + delete request; + request = 0; +} + +void Client::connect_finished(const exception *err) +{ + if(err) + { + signal_socket_error.emit(err); + + delete request; + request = 0; + } + else + sock->write(request->str()); +} + +void Client::data_available() +{ + char rbuf[4096]; + unsigned len; + try + { + len = sock->read(rbuf, sizeof(rbuf)); + } + catch(const exception &e) + { + signal_socket_error.emit(&e); + return; + } + + if(!len) + return; + in_buf.append(rbuf, len); + + if(!response) + { + if(in_buf.find("\r\n\r\n")!=string::npos || in_buf.find("\n\n")!=string::npos) + { + response = new Response(Response::parse(in_buf)); + response->set_user_data(request->get_user_data()); + in_buf = string(); + } + } + else + { + len = response->parse_content(in_buf); + in_buf.erase(0, len); + } + + if(response && response->is_complete()) + { + signal_response_complete.emit(*response); + + delete request; + request = 0; + } +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/client.h b/source/http/client.h new file mode 100644 index 0000000..ee10f8c --- /dev/null +++ b/source/http/client.h @@ -0,0 +1,52 @@ +#ifndef MSP_HTTP_CLIENT_H_ +#define MSP_HTTP_CLIENT_H_ + +#include +#include +#include +#include + +namespace Msp { +namespace Http { + +class Request; +class Response; + +class Client +{ +public: + sigc::signal signal_response_complete; + sigc::signal signal_socket_error; + +private: + Net::StreamSocket *sock; + IO::EventDispatcher *event_disp; + std::string user_agent; + Request *request; + Response *response; + std::string in_buf; + + Client(const Client &); + Client &operator=(const Client &); +public: + Client(); + ~Client(); + + void use_event_dispatcher(IO::EventDispatcher *); + void set_user_agent(const std::string &); + void start_request(const Request &); + const Response *get_url(const std::string &); + void tick(); + void wait_response(); + void abort(); + const Request *get_request() const { return request; } + const Response *get_response() const { return response; } +private: + void connect_finished(const std::exception *); + void data_available(); +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/http/message.cpp b/source/http/message.cpp new file mode 100644 index 0000000..2b3e40b --- /dev/null +++ b/source/http/message.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include "message.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Message::Message(): + http_version(0x11), + chunk_length(0), + complete(false) +{ } + +void Message::set_header(const string &hdr, const string &val) +{ + headers[normalize_header_name(hdr)] = val; +} + +bool Message::has_header(const string &hdr) const +{ + return headers.count(normalize_header_name(hdr)); +} + +const string &Message::get_header(const string &hdr) const +{ + return get_item(headers, normalize_header_name(hdr)); +} + +void Message::add_content(const string &d) +{ + content += d; + if(headers.count("Content-Type")==0) + set_header("Content-Type", "text/plain"); + set_header("Content-Length", lexical_cast(content.size())); +} + +void Message::set_user_data(const Variant &d) +{ + user_data = d; +} + +unsigned Message::parse_content(const string &d) +{ + if(complete) + return 0; + + HeaderMap::const_iterator i = headers.find("Content-Length"); + if(i!=headers.end()) + { + unsigned needed = lexical_cast(i->second)-content.size(); + unsigned len = min(needed, d.size()); + + content.append(d, 0, len); + + if(len==needed) + complete = true; + + return len; + } + + i = headers.find("Transfer-Encoding"); + if(i!=headers.end() && strcasecmp(i->second, "chunked")==0) + { + unsigned pos = 0; + while(!complete && pos(strip(d.substr(pos, lf-pos)), "x"); + if(chunk_length==0) + complete = true; + pos = lf+1; + } + else + { + unsigned len = min(chunk_length, d.size()-pos); + content.append(d, pos, len); + chunk_length -= len; + if((pos = d.find('\n', pos+len))!=string::npos) + ++pos; + } + } + + return pos; + } + + complete = true; + return 0; +} + +unsigned Message::parse_headers(const string &d) +{ + unsigned start = 0; + while(1) + { + unsigned lf = d.find('\n', start); + if(lf==string::npos) + throw invalid_argument("Message::parse_headers"); + if(lf==start || (d[start]=='\r' && lf==start+1)) + return lf+1; + + unsigned colon = d.find(':', start); + if(colon>lf) + throw invalid_argument("Message::parse_headers"); + + set_header(d.substr(start, colon-start), strip(d.substr(colon+1, lf-colon-1))); + + start = lf+1; + } +} + +string Message::str_common() const +{ + string result; + + for(HeaderMap::const_iterator i=headers.begin(); i!=headers.end(); ++i) + if(i->first[0]!='-') + result += format("%s: %s\r\n", i->first, i->second); + result += "\r\n"; + result += content; + + return result; +} + +string Message::normalize_header_name(const string &hdr) const +{ + string result = hdr; + bool upper = true; + for(string::iterator i=result.begin(); i!=result.end(); ++i) + { + if(upper) + { + *i = toupper(*i); + upper = false; + } + else if(*i=='-') + upper = true; + else + *i = tolower(*i); + } + return result; +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/message.h b/source/http/message.h new file mode 100644 index 0000000..4d23102 --- /dev/null +++ b/source/http/message.h @@ -0,0 +1,47 @@ +#ifndef MSP_HTTP_MESSAGE_H_ +#define MSP_HTTP_MESSAGE_H_ + +#include +#include +#include +#include "version.h" + +namespace Msp { +namespace Http { + +class Message +{ +protected: + typedef std::map HeaderMap; + + Version http_version; + HeaderMap headers; + std::string content; + unsigned chunk_length; + bool complete; + Variant user_data; + + Message(); +public: + virtual ~Message() { } + + void set_header(const std::string &, const std::string &); + bool has_header(const std::string &) const; + const std::string &get_header(const std::string &) const; + void add_content(const std::string &); + const std::string &get_content() const { return content; } + void set_user_data(const Variant &); + const Variant &get_user_data() const { return user_data; } + bool is_complete() const { return complete; } + unsigned parse_content(const std::string &); + virtual std::string str() const = 0; +protected: + unsigned parse_headers(const std::string &); + std::string str_common() const; + std::string normalize_header_name(const std::string &) const; +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/http/request.cpp b/source/http/request.cpp new file mode 100644 index 0000000..c948755 --- /dev/null +++ b/source/http/request.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include "request.h" +#include "utils.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Request::Request(const string &m, const string &p): + method(m), + path(p) +{ } + +string Request::str() const +{ + string result = format("%s %s %s\r\n", method, path, version_str(http_version)); + result += str_common(); + + return result; +} + +Request Request::parse(const string &str) +{ + unsigned lf = str.find('\n'); + vector parts = split(str.substr(0, lf-(str[lf-1]=='\r')), ' ', 2); + if(parts.size()<3) + throw invalid_argument("Request::parse"); + + Request result(parts[0], parts[1]); + result.http_version = parse_version(parts[2]); + + lf += result.parse_headers(str.substr(lf+1)); + + result.parse_content(str.substr(lf+1)); + + return result; +} + +Request Request::from_url(const string &str) +{ + Url url = parse_url(str); + if(url.scheme!="http") + throw invalid_argument("Request::from_url"); + + string path = url.path; + if(path.empty()) + path = "/"; + if(!url.query.empty()) + { + path += '?'; + path += url.query; + } + + Request result("GET", path); + result.set_header("Host", url.host); + result.set_header("Connection", "close"); + + return result; +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/request.h b/source/http/request.h new file mode 100644 index 0000000..ec5030e --- /dev/null +++ b/source/http/request.h @@ -0,0 +1,29 @@ +#ifndef MSP_HTTP_REQUEST_H_ +#define MSP_HTTP_REQUEST_H_ + +#include +#include "message.h" + +namespace Msp { +namespace Http { + +class Request: public Message +{ +private: + std::string method; + std::string path; + +public: + Request(const std::string &, const std::string &); + const std::string &get_method() const { return method; } + const std::string &get_path() const { return path; } + virtual std::string str() const; + + static Request parse(const std::string &); + static Request from_url(const std::string &); +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/http/response.cpp b/source/http/response.cpp new file mode 100644 index 0000000..ede249b --- /dev/null +++ b/source/http/response.cpp @@ -0,0 +1,42 @@ +#include +#include +#include "response.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Response::Response(Status s): + status(s) +{ } + +string Response::str() const +{ + string result = format("%s %d %s\r\n", version_str(http_version), static_cast(status), status); + result += str_common(); + + return result; +} + +Response Response::parse(const string &str) +{ + Response result; + + unsigned lf = str.find('\n'); + vector parts = split(str.substr(0, lf), ' ', 2); + if(parts.size()<2) + throw invalid_argument("Response::parse"); + + result.http_version = parse_version(parts[0]); + result.status = static_cast(lexical_cast(parts[1])); + + lf += result.parse_headers(str.substr(lf+1)); + + result.parse_content(str.substr(lf+1)); + + return result; +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/response.h b/source/http/response.h new file mode 100644 index 0000000..d721ad8 --- /dev/null +++ b/source/http/response.h @@ -0,0 +1,27 @@ +#ifndef MSP_HTTP_RESPONSE_H_ +#define MSP_HTTO_RESPONSE_H_ + +#include "message.h" +#include "status.h" + +namespace Msp { +namespace Http { + +class Response: public Message +{ +private: + Status status; + + Response() { } +public: + Response(Status); + Status get_status() const { return status; } + virtual std::string str() const; + + static Response parse(const std::string &); +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/http/server.cpp b/source/http/server.cpp new file mode 100644 index 0000000..9205cc1 --- /dev/null +++ b/source/http/server.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include +#include +#include "request.h" +#include "response.h" +#include "server.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Server::Server(unsigned port): + sock(Net::INET), + event_disp(0) +{ + sock.signal_data_available.connect(sigc::mem_fun(this, &Server::data_available)); + RefPtr addr = Net::resolve("*", format("%d", port)); + sock.listen(*addr, 8); +} + +unsigned Server::get_port() const +{ + const Net::SockAddr &addr = sock.get_local_address(); + if(addr.get_family()==Net::INET) + return static_cast(addr).get_port(); + return 0; +} + +void Server::use_event_dispatcher(IO::EventDispatcher *ed) +{ + if(event_disp) + { + event_disp->remove(sock); + for(list::iterator i=clients.begin(); i!=clients.end(); ++i) + event_disp->remove(*i->sock); + } + event_disp = ed; + if(event_disp) + { + event_disp->add(sock); + for(list::iterator i=clients.begin(); i!=clients.end(); ++i) + event_disp->add(*i->sock); + } +} + +void Server::delay_response(Response &resp) +{ + get_client_by_response(resp).async = true; +} + +void Server::submit_response(Response &resp) +{ + Client &cl = get_client_by_response(resp); + if(cl.async) + { + cl.sock->write(resp.str()); + cl.stale = true; + } +} + +void Server::data_available() +{ + Net::StreamSocket *csock = sock.accept(); + clients.push_back(Client(csock)); + csock->signal_data_available.connect(sigc::bind(sigc::mem_fun(this, &Server::client_data_available), sigc::ref(clients.back()))); + csock->signal_end_of_file.connect(sigc::bind(sigc::mem_fun(this, &Server::client_end_of_file), sigc::ref(clients.back()))); + if(event_disp) + event_disp->add(*csock); +} + +void Server::client_data_available(Client &cl) +{ + for(list::iterator i=clients.begin(); i!=clients.end(); ++i) + if(i->stale && &*i!=&cl) + { + clients.erase(i); + break; + } + + char rbuf[4096]; + unsigned len = cl.sock->read(rbuf, sizeof(rbuf)); + cl.in_buf.append(rbuf, len); + + RefPtr response; + if(!cl.request) + { + if(cl.in_buf.find("\r\n\r\n")!=string::npos || cl.in_buf.find("\n\n")!=string::npos) + { + try + { + cl.request = new Request(Request::parse(cl.in_buf)); + + string addr_str = cl.sock->get_peer_address().str(); + unsigned colon = addr_str.find(':'); + cl.request->set_header("-Client-Host", addr_str.substr(0, colon)); + + if(cl.request->get_method()!="GET" && cl.request->get_method()!="POST") + { + response = new Response(NOT_IMPLEMENTED); + response->add_content("Method not implemented\n"); + } + } + catch(const exception &e) + { + response = new Response(BAD_REQUEST); + response->add_content(e.what()); + } + cl.in_buf = string(); + } + } + else + { + len = cl.request->parse_content(cl.in_buf); + cl.in_buf.erase(0, len); + } + + if(cl.request && cl.request->is_complete() && !response) + { + response = new Response(NONE); + try + { + cl.response = response.get(); + signal_request.emit(*cl.request, *response); + if(cl.async) + response.release(); + else + { + cl.response = 0; + if(response->get_status()==NONE) + { + response = new Response(NOT_FOUND); + response->add_content("The requested resource was not found\n"); + } + } + } + catch(const exception &e) + { + cl.response = 0; + response = new Response(INTERNAL_ERROR); + response->add_content(e.what()); + } + } + + if(response) + { + cl.sock->write(response->str()); + cl.stale = true; + } +} + +void Server::client_end_of_file(Client &cl) +{ + cl.stale = true; +} + +Server::Client &Server::get_client_by_response(Response &resp) +{ + for(list::iterator i=clients.begin(); i!=clients.end(); ++i) + if(i->response==&resp) + return *i; + + // XXX Do this differently + throw invalid_argument("Response does not belong to any client"); +} + + +Server::Client::Client(RefPtr s): + sock(s), + request(0), + response(0), + async(false), + stale(false) +{ } + +Server::Client::~Client() +{ + delete request; + delete response; +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/server.h b/source/http/server.h new file mode 100644 index 0000000..41894db --- /dev/null +++ b/source/http/server.h @@ -0,0 +1,53 @@ +#ifndef MSP_HTTP_SERVER_H_ +#define MSP_HTTP_SERVER_H_ + +#include +#include +#include + +namespace Msp { +namespace Http { + +class Request; +class Response; + +class Server +{ +public: + sigc::signal signal_request; + +private: + struct Client + { + RefPtr sock; + std::string in_buf; + Request *request; + Response *response; + bool async; + bool stale; + + Client(RefPtr); + ~Client(); + }; + + Net::StreamServerSocket sock; + std::list clients; + IO::EventDispatcher *event_disp; + +public: + Server(unsigned); + unsigned get_port() const; + void use_event_dispatcher(IO::EventDispatcher *); + void delay_response(Response &); + void submit_response(Response &); +private: + void data_available(); + void client_data_available(Client &); + void client_end_of_file(Client &); + Client &get_client_by_response(Response &); +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/http/status.cpp b/source/http/status.cpp new file mode 100644 index 0000000..6b95822 --- /dev/null +++ b/source/http/status.cpp @@ -0,0 +1,26 @@ +#include "status.h" + +using namespace std; + +namespace Msp { +namespace Http { + +ostream &operator<<(ostream &out, Status status) +{ + switch(status) + { + case NONE: out<<"None"; break; + case OK: out<<"OK"; break; + case BAD_REQUEST: out<<"Bad Request"; break; + case FORBIDDEN: out<<"Forbidden"; break; + case NOT_FOUND: out<<"Not Found"; break; + case INTERNAL_ERROR: out<<"Internal Error"; break; + case NOT_IMPLEMENTED: out<<"Not Implemented"; break; + default: out<<"Unknown Status"; break; + } + + return out; +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/status.h b/source/http/status.h new file mode 100644 index 0000000..075016d --- /dev/null +++ b/source/http/status.h @@ -0,0 +1,25 @@ +#ifndef MSP_HTTPSERVER_STATUS_H_ +#define MSP_HTTPSERVER_STATUS_H_ + +#include + +namespace Msp { +namespace Http { + +enum Status +{ + NONE = 0, + OK = 200, + BAD_REQUEST = 400, + FORBIDDEN = 403, + NOT_FOUND = 404, + INTERNAL_ERROR = 500, + NOT_IMPLEMENTED = 501 +}; + +extern std::ostream &operator<<(std::ostream &, Status); + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/http/utils.cpp b/source/http/utils.cpp new file mode 100644 index 0000000..5a5dc04 --- /dev/null +++ b/source/http/utils.cpp @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include "utils.h" + +using namespace std; + +namespace { + +const char *reserved[]= +{ + " #%&+=?", + " #%&*+:;=?@[]", + " !#$%&'()*+,/:;=?@[]", +}; + +bool is_reserved(char c, unsigned level) +{ + for(const char *r=reserved[level]; *r; ++r) + if(c==*r) + return true; + return false; +} + +} + +namespace Msp { +namespace Http { + +string urlencode(const string &str, EncodeLevel level) +{ + string result; + for(string::const_iterator i=str.begin(); i!=str.end(); ++i) + { + if(is_reserved(*i, level)) + result += format("%%%02X", *i); + else + result += *i; + } + return result; +} + +string urlencode_plus(const string &str, EncodeLevel level) +{ + string result; + for(string::const_iterator i=str.begin(); i!=str.end(); ++i) + { + if(*i==' ') + result += '+'; + else if(is_reserved(*i, level)) + result += format("%%%02X", *i); + else + result += *i; + } + return result; +} + +string urldecode(const string &str) +{ + string result; + for(unsigned i=0; istr.size()) + throw invalid_argument("urldecode"); + result += lexical_cast(str.substr(i+1, 2), "x"); + i += 2; + } + else if(c=='+') + result += ' '; + else + result += c; + } + return result; +} + +Url parse_url(const string &str) +{ + static Regex r_url("(([a-z]+)://)?([a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*(:[0-9])?)?(/[^?#]*)?(\\?([^#]+))?(#(.*))?"); + if(RegMatch m = r_url.match(str)) + { + Url url; + url.scheme = m[2].str; + url.host = m[3].str; + url.path = urldecode(m[6].str); + url.query = m[8].str; + url.fragment = m[10].str; + return url; + } + else + throw invalid_argument("parse_url"); +} + +string build_url(const Url &url) +{ + if(!url.path.empty() && url.path[0]!='/') + throw invalid_argument("build_url"); + + string str; + if(!url.scheme.empty()) + str += url.scheme+"://"; + str += url.host; + str += urlencode(url.path); + if(!url.query.empty()) + { + str += '?'; + str += url.query; + } + if(!url.fragment.empty()) + { + str += '#'; + str += url.fragment; + } + return str; +} + +Query parse_query(const std::string &str) +{ + vector parts = split(str, '&'); + Query query; + for(vector::const_iterator i=parts.begin(); i!=parts.end(); ++i) + { + unsigned equals = i->find('='); + string &value = query[urldecode(i->substr(0, equals))]; + if(equals!=string::npos) + value = urldecode(i->substr(equals+1)); + } + return query; +} + +string build_query(const Query &query) +{ + string str; + for(Query::const_iterator i=query.begin(); i!=query.end(); ++i) + { + if(i!=query.begin()) + str += '&'; + str += urlencode_plus(i->first); + str += '='; + str += urlencode_plus(i->second); + } + return str; +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/utils.h b/source/http/utils.h new file mode 100644 index 0000000..a25a748 --- /dev/null +++ b/source/http/utils.h @@ -0,0 +1,39 @@ +#ifndef MSP_HTTP_UTILS_H_ +#define MSP_HTTP_UTILS_H_ + +#include +#include + +namespace Msp { +namespace Http { + +enum EncodeLevel +{ + MINIMAL, + SAFE, + PARANOID +}; + +struct Url +{ + std::string scheme; + std::string host; + std::string path; + std::string query; + std::string fragment; +}; + +typedef std::map Query; + +std::string urlencode(const std::string &, EncodeLevel =SAFE); +std::string urlencode_plus(const std::string &, EncodeLevel =SAFE); +std::string urldecode(const std::string &); +Url parse_url(const std::string &); +std::string build_url(const Url &); +Query parse_query(const std::string &); +std::string build_query(const Query &); + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/http/version.cpp b/source/http/version.cpp new file mode 100644 index 0000000..f22b46e --- /dev/null +++ b/source/http/version.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include "version.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Version parse_version(const std::string &ver) +{ + if(RegMatch match = Regex("^HTTP/([0-9]+).([0-9]+)$").match(ver)) + return lexical_cast(match[1].str)<<4 | lexical_cast(match[2].str); + else + throw invalid_argument("parse_version"); +} + +string version_str(Version ver) +{ + return format("HTTP/%u.%u", (ver>>4)&0xF, ver&0xF); +} + +} // namespace Http +} // namespace Msp diff --git a/source/http/version.h b/source/http/version.h new file mode 100644 index 0000000..19a50b9 --- /dev/null +++ b/source/http/version.h @@ -0,0 +1,17 @@ +#ifndef MSP_HTTP_MISC_H_ +#define MSP_HTTP_MISC_H_ + +#include + +namespace Msp { +namespace Http { + +typedef unsigned Version; + +Version parse_version(const std::string &); +std::string version_str(Version); + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/message.cpp b/source/message.cpp deleted file mode 100644 index 2b3e40b..0000000 --- a/source/message.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include -#include -#include -#include -#include "message.h" - -using namespace std; - -namespace Msp { -namespace Http { - -Message::Message(): - http_version(0x11), - chunk_length(0), - complete(false) -{ } - -void Message::set_header(const string &hdr, const string &val) -{ - headers[normalize_header_name(hdr)] = val; -} - -bool Message::has_header(const string &hdr) const -{ - return headers.count(normalize_header_name(hdr)); -} - -const string &Message::get_header(const string &hdr) const -{ - return get_item(headers, normalize_header_name(hdr)); -} - -void Message::add_content(const string &d) -{ - content += d; - if(headers.count("Content-Type")==0) - set_header("Content-Type", "text/plain"); - set_header("Content-Length", lexical_cast(content.size())); -} - -void Message::set_user_data(const Variant &d) -{ - user_data = d; -} - -unsigned Message::parse_content(const string &d) -{ - if(complete) - return 0; - - HeaderMap::const_iterator i = headers.find("Content-Length"); - if(i!=headers.end()) - { - unsigned needed = lexical_cast(i->second)-content.size(); - unsigned len = min(needed, d.size()); - - content.append(d, 0, len); - - if(len==needed) - complete = true; - - return len; - } - - i = headers.find("Transfer-Encoding"); - if(i!=headers.end() && strcasecmp(i->second, "chunked")==0) - { - unsigned pos = 0; - while(!complete && pos(strip(d.substr(pos, lf-pos)), "x"); - if(chunk_length==0) - complete = true; - pos = lf+1; - } - else - { - unsigned len = min(chunk_length, d.size()-pos); - content.append(d, pos, len); - chunk_length -= len; - if((pos = d.find('\n', pos+len))!=string::npos) - ++pos; - } - } - - return pos; - } - - complete = true; - return 0; -} - -unsigned Message::parse_headers(const string &d) -{ - unsigned start = 0; - while(1) - { - unsigned lf = d.find('\n', start); - if(lf==string::npos) - throw invalid_argument("Message::parse_headers"); - if(lf==start || (d[start]=='\r' && lf==start+1)) - return lf+1; - - unsigned colon = d.find(':', start); - if(colon>lf) - throw invalid_argument("Message::parse_headers"); - - set_header(d.substr(start, colon-start), strip(d.substr(colon+1, lf-colon-1))); - - start = lf+1; - } -} - -string Message::str_common() const -{ - string result; - - for(HeaderMap::const_iterator i=headers.begin(); i!=headers.end(); ++i) - if(i->first[0]!='-') - result += format("%s: %s\r\n", i->first, i->second); - result += "\r\n"; - result += content; - - return result; -} - -string Message::normalize_header_name(const string &hdr) const -{ - string result = hdr; - bool upper = true; - for(string::iterator i=result.begin(); i!=result.end(); ++i) - { - if(upper) - { - *i = toupper(*i); - upper = false; - } - else if(*i=='-') - upper = true; - else - *i = tolower(*i); - } - return result; -} - -} // namespace Http -} // namespace Msp diff --git a/source/message.h b/source/message.h deleted file mode 100644 index 4d23102..0000000 --- a/source/message.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef MSP_HTTP_MESSAGE_H_ -#define MSP_HTTP_MESSAGE_H_ - -#include -#include -#include -#include "version.h" - -namespace Msp { -namespace Http { - -class Message -{ -protected: - typedef std::map HeaderMap; - - Version http_version; - HeaderMap headers; - std::string content; - unsigned chunk_length; - bool complete; - Variant user_data; - - Message(); -public: - virtual ~Message() { } - - void set_header(const std::string &, const std::string &); - bool has_header(const std::string &) const; - const std::string &get_header(const std::string &) const; - void add_content(const std::string &); - const std::string &get_content() const { return content; } - void set_user_data(const Variant &); - const Variant &get_user_data() const { return user_data; } - bool is_complete() const { return complete; } - unsigned parse_content(const std::string &); - virtual std::string str() const = 0; -protected: - unsigned parse_headers(const std::string &); - std::string str_common() const; - std::string normalize_header_name(const std::string &) const; -}; - -} // namespace Http -} // namespace Msp - -#endif diff --git a/source/request.cpp b/source/request.cpp deleted file mode 100644 index c948755..0000000 --- a/source/request.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include -#include "request.h" -#include "utils.h" - -using namespace std; - -namespace Msp { -namespace Http { - -Request::Request(const string &m, const string &p): - method(m), - path(p) -{ } - -string Request::str() const -{ - string result = format("%s %s %s\r\n", method, path, version_str(http_version)); - result += str_common(); - - return result; -} - -Request Request::parse(const string &str) -{ - unsigned lf = str.find('\n'); - vector parts = split(str.substr(0, lf-(str[lf-1]=='\r')), ' ', 2); - if(parts.size()<3) - throw invalid_argument("Request::parse"); - - Request result(parts[0], parts[1]); - result.http_version = parse_version(parts[2]); - - lf += result.parse_headers(str.substr(lf+1)); - - result.parse_content(str.substr(lf+1)); - - return result; -} - -Request Request::from_url(const string &str) -{ - Url url = parse_url(str); - if(url.scheme!="http") - throw invalid_argument("Request::from_url"); - - string path = url.path; - if(path.empty()) - path = "/"; - if(!url.query.empty()) - { - path += '?'; - path += url.query; - } - - Request result("GET", path); - result.set_header("Host", url.host); - result.set_header("Connection", "close"); - - return result; -} - -} // namespace Http -} // namespace Msp diff --git a/source/request.h b/source/request.h deleted file mode 100644 index ec5030e..0000000 --- a/source/request.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef MSP_HTTP_REQUEST_H_ -#define MSP_HTTP_REQUEST_H_ - -#include -#include "message.h" - -namespace Msp { -namespace Http { - -class Request: public Message -{ -private: - std::string method; - std::string path; - -public: - Request(const std::string &, const std::string &); - const std::string &get_method() const { return method; } - const std::string &get_path() const { return path; } - virtual std::string str() const; - - static Request parse(const std::string &); - static Request from_url(const std::string &); -}; - -} // namespace Http -} // namespace Msp - -#endif diff --git a/source/response.cpp b/source/response.cpp deleted file mode 100644 index ede249b..0000000 --- a/source/response.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include -#include "response.h" - -using namespace std; - -namespace Msp { -namespace Http { - -Response::Response(Status s): - status(s) -{ } - -string Response::str() const -{ - string result = format("%s %d %s\r\n", version_str(http_version), static_cast(status), status); - result += str_common(); - - return result; -} - -Response Response::parse(const string &str) -{ - Response result; - - unsigned lf = str.find('\n'); - vector parts = split(str.substr(0, lf), ' ', 2); - if(parts.size()<2) - throw invalid_argument("Response::parse"); - - result.http_version = parse_version(parts[0]); - result.status = static_cast(lexical_cast(parts[1])); - - lf += result.parse_headers(str.substr(lf+1)); - - result.parse_content(str.substr(lf+1)); - - return result; -} - -} // namespace Http -} // namespace Msp diff --git a/source/response.h b/source/response.h deleted file mode 100644 index d721ad8..0000000 --- a/source/response.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef MSP_HTTP_RESPONSE_H_ -#define MSP_HTTO_RESPONSE_H_ - -#include "message.h" -#include "status.h" - -namespace Msp { -namespace Http { - -class Response: public Message -{ -private: - Status status; - - Response() { } -public: - Response(Status); - Status get_status() const { return status; } - virtual std::string str() const; - - static Response parse(const std::string &); -}; - -} // namespace Http -} // namespace Msp - -#endif diff --git a/source/server.cpp b/source/server.cpp deleted file mode 100644 index 9205cc1..0000000 --- a/source/server.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "request.h" -#include "response.h" -#include "server.h" - -using namespace std; - -namespace Msp { -namespace Http { - -Server::Server(unsigned port): - sock(Net::INET), - event_disp(0) -{ - sock.signal_data_available.connect(sigc::mem_fun(this, &Server::data_available)); - RefPtr addr = Net::resolve("*", format("%d", port)); - sock.listen(*addr, 8); -} - -unsigned Server::get_port() const -{ - const Net::SockAddr &addr = sock.get_local_address(); - if(addr.get_family()==Net::INET) - return static_cast(addr).get_port(); - return 0; -} - -void Server::use_event_dispatcher(IO::EventDispatcher *ed) -{ - if(event_disp) - { - event_disp->remove(sock); - for(list::iterator i=clients.begin(); i!=clients.end(); ++i) - event_disp->remove(*i->sock); - } - event_disp = ed; - if(event_disp) - { - event_disp->add(sock); - for(list::iterator i=clients.begin(); i!=clients.end(); ++i) - event_disp->add(*i->sock); - } -} - -void Server::delay_response(Response &resp) -{ - get_client_by_response(resp).async = true; -} - -void Server::submit_response(Response &resp) -{ - Client &cl = get_client_by_response(resp); - if(cl.async) - { - cl.sock->write(resp.str()); - cl.stale = true; - } -} - -void Server::data_available() -{ - Net::StreamSocket *csock = sock.accept(); - clients.push_back(Client(csock)); - csock->signal_data_available.connect(sigc::bind(sigc::mem_fun(this, &Server::client_data_available), sigc::ref(clients.back()))); - csock->signal_end_of_file.connect(sigc::bind(sigc::mem_fun(this, &Server::client_end_of_file), sigc::ref(clients.back()))); - if(event_disp) - event_disp->add(*csock); -} - -void Server::client_data_available(Client &cl) -{ - for(list::iterator i=clients.begin(); i!=clients.end(); ++i) - if(i->stale && &*i!=&cl) - { - clients.erase(i); - break; - } - - char rbuf[4096]; - unsigned len = cl.sock->read(rbuf, sizeof(rbuf)); - cl.in_buf.append(rbuf, len); - - RefPtr response; - if(!cl.request) - { - if(cl.in_buf.find("\r\n\r\n")!=string::npos || cl.in_buf.find("\n\n")!=string::npos) - { - try - { - cl.request = new Request(Request::parse(cl.in_buf)); - - string addr_str = cl.sock->get_peer_address().str(); - unsigned colon = addr_str.find(':'); - cl.request->set_header("-Client-Host", addr_str.substr(0, colon)); - - if(cl.request->get_method()!="GET" && cl.request->get_method()!="POST") - { - response = new Response(NOT_IMPLEMENTED); - response->add_content("Method not implemented\n"); - } - } - catch(const exception &e) - { - response = new Response(BAD_REQUEST); - response->add_content(e.what()); - } - cl.in_buf = string(); - } - } - else - { - len = cl.request->parse_content(cl.in_buf); - cl.in_buf.erase(0, len); - } - - if(cl.request && cl.request->is_complete() && !response) - { - response = new Response(NONE); - try - { - cl.response = response.get(); - signal_request.emit(*cl.request, *response); - if(cl.async) - response.release(); - else - { - cl.response = 0; - if(response->get_status()==NONE) - { - response = new Response(NOT_FOUND); - response->add_content("The requested resource was not found\n"); - } - } - } - catch(const exception &e) - { - cl.response = 0; - response = new Response(INTERNAL_ERROR); - response->add_content(e.what()); - } - } - - if(response) - { - cl.sock->write(response->str()); - cl.stale = true; - } -} - -void Server::client_end_of_file(Client &cl) -{ - cl.stale = true; -} - -Server::Client &Server::get_client_by_response(Response &resp) -{ - for(list::iterator i=clients.begin(); i!=clients.end(); ++i) - if(i->response==&resp) - return *i; - - // XXX Do this differently - throw invalid_argument("Response does not belong to any client"); -} - - -Server::Client::Client(RefPtr s): - sock(s), - request(0), - response(0), - async(false), - stale(false) -{ } - -Server::Client::~Client() -{ - delete request; - delete response; -} - -} // namespace Http -} // namespace Msp diff --git a/source/server.h b/source/server.h deleted file mode 100644 index 41894db..0000000 --- a/source/server.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef MSP_HTTP_SERVER_H_ -#define MSP_HTTP_SERVER_H_ - -#include -#include -#include - -namespace Msp { -namespace Http { - -class Request; -class Response; - -class Server -{ -public: - sigc::signal signal_request; - -private: - struct Client - { - RefPtr sock; - std::string in_buf; - Request *request; - Response *response; - bool async; - bool stale; - - Client(RefPtr); - ~Client(); - }; - - Net::StreamServerSocket sock; - std::list clients; - IO::EventDispatcher *event_disp; - -public: - Server(unsigned); - unsigned get_port() const; - void use_event_dispatcher(IO::EventDispatcher *); - void delay_response(Response &); - void submit_response(Response &); -private: - void data_available(); - void client_data_available(Client &); - void client_end_of_file(Client &); - Client &get_client_by_response(Response &); -}; - -} // namespace Http -} // namespace Msp - -#endif diff --git a/source/status.cpp b/source/status.cpp deleted file mode 100644 index 6b95822..0000000 --- a/source/status.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "status.h" - -using namespace std; - -namespace Msp { -namespace Http { - -ostream &operator<<(ostream &out, Status status) -{ - switch(status) - { - case NONE: out<<"None"; break; - case OK: out<<"OK"; break; - case BAD_REQUEST: out<<"Bad Request"; break; - case FORBIDDEN: out<<"Forbidden"; break; - case NOT_FOUND: out<<"Not Found"; break; - case INTERNAL_ERROR: out<<"Internal Error"; break; - case NOT_IMPLEMENTED: out<<"Not Implemented"; break; - default: out<<"Unknown Status"; break; - } - - return out; -} - -} // namespace Http -} // namespace Msp diff --git a/source/status.h b/source/status.h deleted file mode 100644 index 075016d..0000000 --- a/source/status.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef MSP_HTTPSERVER_STATUS_H_ -#define MSP_HTTPSERVER_STATUS_H_ - -#include - -namespace Msp { -namespace Http { - -enum Status -{ - NONE = 0, - OK = 200, - BAD_REQUEST = 400, - FORBIDDEN = 403, - NOT_FOUND = 404, - INTERNAL_ERROR = 500, - NOT_IMPLEMENTED = 501 -}; - -extern std::ostream &operator<<(std::ostream &, Status); - -} // namespace Http -} // namespace Msp - -#endif diff --git a/source/utils.cpp b/source/utils.cpp deleted file mode 100644 index 5a5dc04..0000000 --- a/source/utils.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include -#include -#include -#include -#include "utils.h" - -using namespace std; - -namespace { - -const char *reserved[]= -{ - " #%&+=?", - " #%&*+:;=?@[]", - " !#$%&'()*+,/:;=?@[]", -}; - -bool is_reserved(char c, unsigned level) -{ - for(const char *r=reserved[level]; *r; ++r) - if(c==*r) - return true; - return false; -} - -} - -namespace Msp { -namespace Http { - -string urlencode(const string &str, EncodeLevel level) -{ - string result; - for(string::const_iterator i=str.begin(); i!=str.end(); ++i) - { - if(is_reserved(*i, level)) - result += format("%%%02X", *i); - else - result += *i; - } - return result; -} - -string urlencode_plus(const string &str, EncodeLevel level) -{ - string result; - for(string::const_iterator i=str.begin(); i!=str.end(); ++i) - { - if(*i==' ') - result += '+'; - else if(is_reserved(*i, level)) - result += format("%%%02X", *i); - else - result += *i; - } - return result; -} - -string urldecode(const string &str) -{ - string result; - for(unsigned i=0; istr.size()) - throw invalid_argument("urldecode"); - result += lexical_cast(str.substr(i+1, 2), "x"); - i += 2; - } - else if(c=='+') - result += ' '; - else - result += c; - } - return result; -} - -Url parse_url(const string &str) -{ - static Regex r_url("(([a-z]+)://)?([a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*(:[0-9])?)?(/[^?#]*)?(\\?([^#]+))?(#(.*))?"); - if(RegMatch m = r_url.match(str)) - { - Url url; - url.scheme = m[2].str; - url.host = m[3].str; - url.path = urldecode(m[6].str); - url.query = m[8].str; - url.fragment = m[10].str; - return url; - } - else - throw invalid_argument("parse_url"); -} - -string build_url(const Url &url) -{ - if(!url.path.empty() && url.path[0]!='/') - throw invalid_argument("build_url"); - - string str; - if(!url.scheme.empty()) - str += url.scheme+"://"; - str += url.host; - str += urlencode(url.path); - if(!url.query.empty()) - { - str += '?'; - str += url.query; - } - if(!url.fragment.empty()) - { - str += '#'; - str += url.fragment; - } - return str; -} - -Query parse_query(const std::string &str) -{ - vector parts = split(str, '&'); - Query query; - for(vector::const_iterator i=parts.begin(); i!=parts.end(); ++i) - { - unsigned equals = i->find('='); - string &value = query[urldecode(i->substr(0, equals))]; - if(equals!=string::npos) - value = urldecode(i->substr(equals+1)); - } - return query; -} - -string build_query(const Query &query) -{ - string str; - for(Query::const_iterator i=query.begin(); i!=query.end(); ++i) - { - if(i!=query.begin()) - str += '&'; - str += urlencode_plus(i->first); - str += '='; - str += urlencode_plus(i->second); - } - return str; -} - -} // namespace Http -} // namespace Msp diff --git a/source/utils.h b/source/utils.h deleted file mode 100644 index a25a748..0000000 --- a/source/utils.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef MSP_HTTP_UTILS_H_ -#define MSP_HTTP_UTILS_H_ - -#include -#include - -namespace Msp { -namespace Http { - -enum EncodeLevel -{ - MINIMAL, - SAFE, - PARANOID -}; - -struct Url -{ - std::string scheme; - std::string host; - std::string path; - std::string query; - std::string fragment; -}; - -typedef std::map Query; - -std::string urlencode(const std::string &, EncodeLevel =SAFE); -std::string urlencode_plus(const std::string &, EncodeLevel =SAFE); -std::string urldecode(const std::string &); -Url parse_url(const std::string &); -std::string build_url(const Url &); -Query parse_query(const std::string &); -std::string build_query(const Query &); - -} // namespace Http -} // namespace Msp - -#endif diff --git a/source/version.cpp b/source/version.cpp deleted file mode 100644 index f22b46e..0000000 --- a/source/version.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include -#include "version.h" - -using namespace std; - -namespace Msp { -namespace Http { - -Version parse_version(const std::string &ver) -{ - if(RegMatch match = Regex("^HTTP/([0-9]+).([0-9]+)$").match(ver)) - return lexical_cast(match[1].str)<<4 | lexical_cast(match[2].str); - else - throw invalid_argument("parse_version"); -} - -string version_str(Version ver) -{ - return format("HTTP/%u.%u", (ver>>4)&0xF, ver&0xF); -} - -} // namespace Http -} // namespace Msp diff --git a/source/version.h b/source/version.h deleted file mode 100644 index 19a50b9..0000000 --- a/source/version.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MSP_HTTP_MISC_H_ -#define MSP_HTTP_MISC_H_ - -#include - -namespace Msp { -namespace Http { - -typedef unsigned Version; - -Version parse_version(const std::string &); -std::string version_str(Version); - -} // namespace Http -} // namespace Msp - -#endif