install true;
};
+ headers "msp/http"
+ {
+ source "source/http";
+ install true;
+ };
+
library "mspnet"
{
source "source/net";
+ source "source/http";
install true;
};
--- /dev/null
+#include <msp/core/except.h>
+#include <msp/core/refptr.h>
+#include <msp/net/resolve.h>
+#include <msp/time/units.h>
+#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<Net::SockAddr> 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
--- /dev/null
+#ifndef MSP_HTTP_CLIENT_H_
+#define MSP_HTTP_CLIENT_H_
+
+#include <string>
+#include <sigc++/signal.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/net/streamsocket.h>
+
+namespace Msp {
+namespace Http {
+
+class Request;
+class Response;
+
+class Client
+{
+public:
+ sigc::signal<void, const Response &> signal_response_complete;
+ sigc::signal<void, const std::exception *> 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
--- /dev/null
+#include <cstdlib>
+#include <msp/core/maputils.h>
+#include <msp/strings/formatter.h>
+#include <msp/strings/utils.h>
+#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<unsigned>(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<d.size())
+ {
+ if(chunk_length==0)
+ {
+ unsigned lf = d.find('\n', pos);
+ if(lf==string::npos)
+ return pos;
+ chunk_length = lexical_cast<unsigned>(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
--- /dev/null
+#ifndef MSP_HTTP_MESSAGE_H_
+#define MSP_HTTP_MESSAGE_H_
+
+#include <map>
+#include <string>
+#include <msp/core/variant.h>
+#include "version.h"
+
+namespace Msp {
+namespace Http {
+
+class Message
+{
+protected:
+ typedef std::map<std::string, std::string> 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
--- /dev/null
+#include <msp/strings/formatter.h>
+#include <msp/strings/regex.h>
+#include <msp/strings/utils.h>
+#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<string> 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
--- /dev/null
+#ifndef MSP_HTTP_REQUEST_H_
+#define MSP_HTTP_REQUEST_H_
+
+#include <string>
+#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
--- /dev/null
+#include <msp/strings/formatter.h>
+#include <msp/strings/utils.h>
+#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<int>(status), status);
+ result += str_common();
+
+ return result;
+}
+
+Response Response::parse(const string &str)
+{
+ Response result;
+
+ unsigned lf = str.find('\n');
+ vector<string> 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<Status>(lexical_cast<unsigned>(parts[1]));
+
+ lf += result.parse_headers(str.substr(lf+1));
+
+ result.parse_content(str.substr(lf+1));
+
+ return result;
+}
+
+} // namespace Http
+} // namespace Msp
--- /dev/null
+#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
--- /dev/null
+#include <exception>
+#include <msp/core/refptr.h>
+#include <msp/net/inet.h>
+#include <msp/net/resolve.h>
+#include <msp/net/streamsocket.h>
+#include <msp/strings/format.h>
+#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<Net::SockAddr> 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<const Net::InetAddr &>(addr).get_port();
+ return 0;
+}
+
+void Server::use_event_dispatcher(IO::EventDispatcher *ed)
+{
+ if(event_disp)
+ {
+ event_disp->remove(sock);
+ for(list<Client>::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<Client>::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<Client>::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> 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<Client>::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<Net::StreamSocket> s):
+ sock(s),
+ request(0),
+ response(0),
+ async(false),
+ stale(false)
+{ }
+
+Server::Client::~Client()
+{
+ delete request;
+ delete response;
+}
+
+} // namespace Http
+} // namespace Msp
--- /dev/null
+#ifndef MSP_HTTP_SERVER_H_
+#define MSP_HTTP_SERVER_H_
+
+#include <msp/core/refptr.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/net/streamserversocket.h>
+
+namespace Msp {
+namespace Http {
+
+class Request;
+class Response;
+
+class Server
+{
+public:
+ sigc::signal<void, const Request &, Response &> signal_request;
+
+private:
+ struct Client
+ {
+ RefPtr<Net::StreamSocket> sock;
+ std::string in_buf;
+ Request *request;
+ Response *response;
+ bool async;
+ bool stale;
+
+ Client(RefPtr<Net::StreamSocket>);
+ ~Client();
+ };
+
+ Net::StreamServerSocket sock;
+ std::list<Client> 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
--- /dev/null
+#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
--- /dev/null
+#ifndef MSP_HTTPSERVER_STATUS_H_
+#define MSP_HTTPSERVER_STATUS_H_
+
+#include <ostream>
+
+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
--- /dev/null
+#include <algorithm>
+#include <msp/strings/formatter.h>
+#include <msp/strings/regex.h>
+#include <msp/strings/utils.h>
+#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; i<str.size(); ++i)
+ {
+ char c = str[i];
+ if(c=='%')
+ {
+ if(i+3>str.size())
+ throw invalid_argument("urldecode");
+ result += lexical_cast<unsigned char>(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<string> parts = split(str, '&');
+ Query query;
+ for(vector<string>::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
--- /dev/null
+#ifndef MSP_HTTP_UTILS_H_
+#define MSP_HTTP_UTILS_H_
+
+#include <map>
+#include <string>
+
+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<std::string, std::string> 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
--- /dev/null
+#include <msp/strings/formatter.h>
+#include <msp/strings/lexicalcast.h>
+#include <msp/strings/regex.h>
+#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<unsigned>(match[1].str)<<4 | lexical_cast<unsigned>(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
--- /dev/null
+#ifndef MSP_HTTP_MISC_H_
+#define MSP_HTTP_MISC_H_
+
+#include <string>
+
+namespace Msp {
+namespace Http {
+
+typedef unsigned Version;
+
+Version parse_version(const std::string &);
+std::string version_str(Version);
+
+} // namespace Http
+} // namespace Msp
+
+#endif