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