diff --git a/openvpn/http/urlencode.hpp b/openvpn/http/urlencode.hpp
new file mode 100644
index 00000000..63cdb51c
--- /dev/null
+++ b/openvpn/http/urlencode.hpp
@@ -0,0 +1,98 @@
+// OpenVPN -- An application to securely tunnel IP networks
+// over a single port, with support for SSL/TLS-based
+// session authentication and key exchange,
+// packet encryption, packet authentication, and
+// packet compression.
+//
+// Copyright (C) 2012-2015 OpenVPN Technologies, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License Version 3
+// as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program in the COPYING file.
+// If not, see .
+
+#ifndef OPENVPN_HTTP_URLENCODE_H
+#define OPENVPN_HTTP_URLENCODE_H
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace openvpn {
+ namespace URL {
+ OPENVPN_EXCEPTION(url_error);
+
+ inline std::string decode(const std::string& encoded)
+ {
+ enum State {
+ TEXT,
+ PERCENT,
+ DIGIT
+ };
+
+ int value = 0;
+ State state = TEXT;
+ std::string ret;
+
+ for (auto &c : encoded)
+ {
+ switch (state)
+ {
+ case TEXT:
+ {
+ if (c == '%')
+ state = PERCENT;
+ else
+ ret += c;
+ break;
+ }
+ case PERCENT:
+ {
+ const int v = parse_hex_char(c);
+ if (v < 0)
+ throw url_error(std::string("decode error after %: ") + encoded);
+ value = v;
+ state = DIGIT;
+ break;
+ }
+ case DIGIT:
+ {
+ const int v = parse_hex_char(c);
+ if (v < 0)
+ throw url_error(std::string("decode error after %: ") + encoded);
+ ret += static_cast((value * 16) + v);
+ state = TEXT;
+ }
+ }
+ }
+ if (state != TEXT)
+ throw url_error(std::string("decode error: %-encoding item not closed out: ") + encoded);
+ if (!Unicode::is_valid_utf8(ret))
+ throw url_error(std::string("not UTF-8: ") + encoded);
+ return ret;
+ }
+
+ inline std::vector decode_path(const std::string& path)
+ {
+ std::vector list;
+ Split::by_char_void(list, path, '/');
+ for (auto &i : list)
+ i = decode(i);
+ return list;
+ }
+ }
+}
+
+#endif