+++ /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 <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
+++ /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