mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-04-24 00:00:05 +08:00
Merge commit '86cc97e55fe346502462284d2e636a2b3708163e' as 'Sources/OpenVPN3'
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Denote the data in an HTTP header
|
||||
|
||||
#ifndef OPENVPN_HTTP_HEADER_H
|
||||
#define OPENVPN_HTTP_HEADER_H
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <openvpn/common/size.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/string.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
|
||||
struct Header {
|
||||
Header() {}
|
||||
Header(std::string name_arg, std::string value_arg)
|
||||
: name(std::move(name_arg)), value(std::move(value_arg)) {}
|
||||
|
||||
bool name_match(const std::string& n) const
|
||||
{
|
||||
return string::strcasecmp(n, name) == 0;
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << name << '=' << value;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct HeaderList : public std::vector<Header>
|
||||
{
|
||||
const Header* get(const std::string& key) const
|
||||
{
|
||||
for (auto &h : *this)
|
||||
{
|
||||
if (h.name_match(key))
|
||||
return &h;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Header* get(const std::string& key)
|
||||
{
|
||||
for (auto &h : *this)
|
||||
{
|
||||
if (h.name_match(key))
|
||||
return &h;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string get_value(const std::string& key) const
|
||||
{
|
||||
const Header* h = get(key);
|
||||
if (h)
|
||||
return h->value;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string get_value_trim(const std::string& key) const
|
||||
{
|
||||
return string::trim_copy(get_value(key));
|
||||
}
|
||||
|
||||
std::string get_value_trim_lower(const std::string& key) const
|
||||
{
|
||||
return string::to_lower_copy(get_value_trim(key));
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
for (size_t i = 0; i < size(); ++i)
|
||||
out << '[' << i << "] " << (*this)[i].to_string() << std::endl;
|
||||
return out.str();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,283 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// A state machine to skip extraneous HTML in an
|
||||
// HTTP CONNECT proxy response.
|
||||
|
||||
// Matches on typical HTML blocks:
|
||||
//
|
||||
// <!doctype html> ... </html>
|
||||
// <html> ... </html>
|
||||
//
|
||||
// Notes:
|
||||
// 1. Case insensitive
|
||||
// 2. </html> can be followed by CR/LF chars
|
||||
|
||||
#ifndef OPENVPN_HTTP_HTMLSKIP_H
|
||||
#define OPENVPN_HTTP_HTMLSKIP_H
|
||||
|
||||
#include <openvpn/buffer/buffer.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
|
||||
class HTMLSkip
|
||||
{
|
||||
public:
|
||||
enum Status {
|
||||
PENDING,
|
||||
MATCH,
|
||||
NOMATCH,
|
||||
};
|
||||
|
||||
HTMLSkip()
|
||||
: state(INITIAL),
|
||||
residual(64, 0),
|
||||
bytes(0)
|
||||
{}
|
||||
|
||||
Status add(unsigned char c)
|
||||
{
|
||||
bool retain = false;
|
||||
|
||||
++bytes;
|
||||
switch (state)
|
||||
{
|
||||
case INITIAL:
|
||||
retain = true;
|
||||
if (c == '<')
|
||||
state = O_OPEN;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_OPEN:
|
||||
retain = true;
|
||||
if (c == '!')
|
||||
state = O_BANG;
|
||||
else if (c == 'h' || c == 'H')
|
||||
state = O_HTML_H;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_BANG:
|
||||
retain = true;
|
||||
if (c == 'd' || c == 'D')
|
||||
state = O_DOCTYPE_D;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_DOCTYPE_D:
|
||||
retain = true;
|
||||
if (c == 'o' || c == 'O')
|
||||
state = O_DOCTYPE_O;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_DOCTYPE_O:
|
||||
retain = true;
|
||||
if (c == 'c' || c == 'C')
|
||||
state = O_DOCTYPE_C;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_DOCTYPE_C:
|
||||
retain = true;
|
||||
if (c == 't' || c == 'T')
|
||||
state = O_DOCTYPE_T;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_DOCTYPE_T:
|
||||
retain = true;
|
||||
if (c == 'y' || c == 'Y')
|
||||
state = O_DOCTYPE_Y;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_DOCTYPE_Y:
|
||||
retain = true;
|
||||
if (c == 'p' || c == 'P')
|
||||
state = O_DOCTYPE_P;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_DOCTYPE_P:
|
||||
retain = true;
|
||||
if (c == 'e' || c == 'E')
|
||||
state = O_DOCTYPE_SPACE;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_DOCTYPE_SPACE:
|
||||
retain = true;
|
||||
if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
|
||||
break;
|
||||
else if (c == 'h' || c == 'H')
|
||||
state = O_HTML_H;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_HTML_H:
|
||||
retain = true;
|
||||
if (c == 't' || c == 'T')
|
||||
state = O_HTML_T;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_HTML_T:
|
||||
retain = true;
|
||||
if (c == 'm' || c == 'M')
|
||||
state = O_HTML_M;
|
||||
else
|
||||
state = FAIL;
|
||||
break;
|
||||
case O_HTML_M:
|
||||
if (c == 'l' || c == 'L')
|
||||
{
|
||||
state = CONTENT;
|
||||
residual.reset_content();
|
||||
}
|
||||
else
|
||||
{
|
||||
state = FAIL;
|
||||
retain = true;
|
||||
}
|
||||
break;
|
||||
case CONTENT:
|
||||
if (c == '<')
|
||||
state = C_OPEN;
|
||||
break;
|
||||
case C_OPEN:
|
||||
if (c == '/')
|
||||
state = C_SLASH;
|
||||
else
|
||||
state = CONTENT;
|
||||
break;
|
||||
case C_SLASH:
|
||||
if (c == 'h' || c == 'H')
|
||||
state = C_HTML_H;
|
||||
else
|
||||
state = CONTENT;
|
||||
break;
|
||||
case C_HTML_H:
|
||||
if (c == 't' || c == 'T')
|
||||
state = C_HTML_T;
|
||||
else
|
||||
state = CONTENT;
|
||||
break;
|
||||
case C_HTML_T:
|
||||
if (c == 'm' || c == 'M')
|
||||
state = C_HTML_M;
|
||||
else
|
||||
state = CONTENT;
|
||||
break;
|
||||
case C_HTML_M:
|
||||
if (c == 'l' || c == 'L')
|
||||
state = C_HTML_L;
|
||||
else
|
||||
state = CONTENT;
|
||||
break;
|
||||
case C_HTML_L:
|
||||
if (c == '>')
|
||||
state = C_CRLF;
|
||||
else
|
||||
state = CONTENT;
|
||||
break;
|
||||
case C_CRLF:
|
||||
if (!(c == '\n' || c == '\r'))
|
||||
{
|
||||
residual.reset_content();
|
||||
residual.push_back(c);
|
||||
state = DONE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
retain = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (retain)
|
||||
residual.push_back(c);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case DONE:
|
||||
return MATCH;
|
||||
case FAIL:
|
||||
return NOMATCH;
|
||||
default:
|
||||
return PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
void get_residual(BufferAllocated& buf) const
|
||||
{
|
||||
if (residual.size() <= buf.offset())
|
||||
{
|
||||
buf.prepend(residual.c_data(), residual.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferAllocated newbuf(residual.size() + buf.size(), 0);
|
||||
newbuf.write(residual.c_data(), residual.size());
|
||||
newbuf.write(buf.c_data(), buf.size());
|
||||
buf.move(newbuf);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long n_bytes() const { return bytes; }
|
||||
|
||||
private:
|
||||
enum State {
|
||||
DONE,
|
||||
FAIL,
|
||||
INITIAL,
|
||||
O_OPEN,
|
||||
O_BANG,
|
||||
O_DOCTYPE_D,
|
||||
O_DOCTYPE_O,
|
||||
O_DOCTYPE_C,
|
||||
O_DOCTYPE_T,
|
||||
O_DOCTYPE_Y,
|
||||
O_DOCTYPE_P,
|
||||
O_DOCTYPE_SPACE,
|
||||
O_HTML_H,
|
||||
O_HTML_T,
|
||||
O_HTML_M,
|
||||
CONTENT,
|
||||
C_OPEN,
|
||||
C_SLASH,
|
||||
C_HTML_H,
|
||||
C_HTML_T,
|
||||
C_HTML_M,
|
||||
C_HTML_L,
|
||||
C_CRLF,
|
||||
};
|
||||
|
||||
State state;
|
||||
BufferAllocated residual;
|
||||
unsigned long bytes;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef OPENVPN_HTTP_METHOD_H
|
||||
#define OPENVPN_HTTP_METHOD_H
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
namespace Method {
|
||||
enum Type {
|
||||
OTHER,
|
||||
GET,
|
||||
POST,
|
||||
};
|
||||
|
||||
Type parse(const std::string& methstr)
|
||||
{
|
||||
if (methstr == "GET")
|
||||
return GET;
|
||||
else if (methstr == "POST")
|
||||
return POST;
|
||||
else
|
||||
return OTHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Adapted from code Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// Common utility methods for HTTP classes
|
||||
|
||||
#ifndef OPENVPN_HTTP_PARSEUTIL_H
|
||||
#define OPENVPN_HTTP_PARSEUTIL_H
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
namespace Util {
|
||||
|
||||
// Check if a byte is an HTTP character.
|
||||
inline bool is_char(const unsigned char c)
|
||||
{
|
||||
return c <= 127;
|
||||
}
|
||||
|
||||
// Check if a byte is an HTTP control character.
|
||||
inline bool is_ctl(const unsigned char c)
|
||||
{
|
||||
return (c <= 31)|| (c == 127);
|
||||
}
|
||||
|
||||
// Check if a byte is defined as an HTTP tspecial character.
|
||||
inline bool is_tspecial(const unsigned char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '(': case ')': case '<': case '>': case '@':
|
||||
case ',': case ';': case ':': case '\\': case '"':
|
||||
case '/': case '[': case ']': case '?': case '=':
|
||||
case '{': case '}': case ' ': case '\t':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a byte is a digit.
|
||||
inline bool is_digit(const unsigned char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
// Check if char should be URL-escaped
|
||||
inline bool is_escaped(const unsigned char c)
|
||||
{
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return false;
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return false;
|
||||
if (c >= '0' && c <= '9')
|
||||
return false;
|
||||
if (c == '.' || c == '-' || c == '_')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,398 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Adapted from code Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// Parse an HTTP reply
|
||||
|
||||
#ifndef OPENVPN_HTTP_REPLY_H
|
||||
#define OPENVPN_HTTP_REPLY_H
|
||||
|
||||
#include <openvpn/http/header.hpp>
|
||||
#include <openvpn/http/parseutil.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
|
||||
struct Reply {
|
||||
Reply() : http_version_major(0), http_version_minor(0), status_code(0) {}
|
||||
|
||||
void reset()
|
||||
{
|
||||
http_version_major = 0;
|
||||
http_version_minor = 0;
|
||||
status_code = 0;
|
||||
status_text = "";
|
||||
headers.clear();
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "HTTP Reply" << std::endl;
|
||||
out << "version=" << http_version_major << '/' << http_version_minor << std::endl;
|
||||
out << "status_code=" << status_code << std::endl;
|
||||
out << "status_text=" << status_text << std::endl;
|
||||
out << headers.to_string();
|
||||
return out.str();
|
||||
}
|
||||
|
||||
int http_version_major;
|
||||
int http_version_minor;
|
||||
int status_code;
|
||||
std::string status_text;
|
||||
HeaderList headers;
|
||||
};
|
||||
|
||||
class ReplyParser {
|
||||
enum state
|
||||
{
|
||||
http_version_h,
|
||||
http_version_t_1,
|
||||
http_version_t_2,
|
||||
http_version_p,
|
||||
http_version_slash,
|
||||
http_version_major_start,
|
||||
http_version_major,
|
||||
http_version_minor_start,
|
||||
http_version_minor,
|
||||
status_code_start,
|
||||
status_code,
|
||||
status_text_start,
|
||||
status_text,
|
||||
expecting_newline_1,
|
||||
header_line_start,
|
||||
header_lws,
|
||||
header_name,
|
||||
space_before_header_value,
|
||||
header_value,
|
||||
expecting_newline_2,
|
||||
expecting_newline_3
|
||||
};
|
||||
|
||||
public:
|
||||
enum status {
|
||||
pending,
|
||||
fail,
|
||||
success,
|
||||
};
|
||||
|
||||
ReplyParser()
|
||||
: state_(http_version_h)
|
||||
{
|
||||
}
|
||||
|
||||
// Reset to initial parser state.
|
||||
void reset()
|
||||
{
|
||||
state_ = http_version_h;
|
||||
}
|
||||
|
||||
// Parse some HTTP reply data.
|
||||
status consume(Reply& req, const unsigned char input)
|
||||
{
|
||||
switch (state_)
|
||||
{
|
||||
case http_version_h:
|
||||
if (input == 'H')
|
||||
{
|
||||
state_ = http_version_t_1;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_t_1:
|
||||
if (input == 'T')
|
||||
{
|
||||
state_ = http_version_t_2;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_t_2:
|
||||
if (input == 'T')
|
||||
{
|
||||
state_ = http_version_p;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_p:
|
||||
if (input == 'P')
|
||||
{
|
||||
state_ = http_version_slash;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_slash:
|
||||
if (input == '/')
|
||||
{
|
||||
req.http_version_major = 0;
|
||||
req.http_version_minor = 0;
|
||||
state_ = http_version_major_start;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_major_start:
|
||||
if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_major = req.http_version_major * 10 + input - '0';
|
||||
state_ = http_version_major;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_major:
|
||||
if (input == '.')
|
||||
{
|
||||
state_ = http_version_minor_start;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_major = req.http_version_major * 10 + input - '0';
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_minor_start:
|
||||
if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_minor = req.http_version_minor * 10 + input - '0';
|
||||
state_ = http_version_minor;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_minor:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = status_code_start;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_minor = req.http_version_minor * 10 + input - '0';
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case status_code_start:
|
||||
if (Util::is_digit(input))
|
||||
{
|
||||
req.status_code = req.status_code * 10 + input - '0';
|
||||
state_ = status_code;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case status_code:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = status_text_start;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_digit(input))
|
||||
{
|
||||
req.status_code = req.status_code * 10 + input - '0';
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case status_text_start:
|
||||
if (!Util::is_char(input) || Util::is_ctl(input) || Util::is_tspecial(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = status_text;
|
||||
req.status_text.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case status_text:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_1;
|
||||
return pending;
|
||||
}
|
||||
else if (!Util::is_char(input) || Util::is_ctl(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.status_text.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case expecting_newline_1:
|
||||
if (input == '\n')
|
||||
{
|
||||
state_ = header_line_start;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case header_line_start:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_3;
|
||||
return pending;
|
||||
}
|
||||
else if (!req.headers.empty() && (input == ' ' || input == '\t'))
|
||||
{
|
||||
state_ = header_lws;
|
||||
return pending;
|
||||
}
|
||||
else if (!Util::is_char(input) || Util::is_ctl(input) || Util::is_tspecial(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.push_back(Header());
|
||||
req.headers.back().name.push_back(input);
|
||||
state_ = header_name;
|
||||
return pending;
|
||||
}
|
||||
case header_lws:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_2;
|
||||
return pending;
|
||||
}
|
||||
else if (input == ' ' || input == '\t')
|
||||
{
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_ctl(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = header_value;
|
||||
req.headers.back().value.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case header_name:
|
||||
if (input == ':')
|
||||
{
|
||||
state_ = space_before_header_value;
|
||||
return pending;
|
||||
}
|
||||
else if (!Util::is_char(input) || Util::is_ctl(input) || Util::is_tspecial(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.back().name.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case space_before_header_value:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = header_value;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case header_value:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_2;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_ctl(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.back().value.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case expecting_newline_2:
|
||||
if (input == '\n')
|
||||
{
|
||||
state_ = header_line_start;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case expecting_newline_3:
|
||||
if (input == '\n')
|
||||
return success;
|
||||
else
|
||||
return fail;
|
||||
default:
|
||||
return fail;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// The current state of the parser.
|
||||
state state_;
|
||||
};
|
||||
|
||||
struct ReplyType
|
||||
{
|
||||
typedef Reply State;
|
||||
typedef ReplyParser Parser;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,398 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Adapted from code Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// Parse an HTTP request
|
||||
|
||||
#ifndef OPENVPN_HTTP_REQUEST_H
|
||||
#define OPENVPN_HTTP_REQUEST_H
|
||||
|
||||
#include <openvpn/http/header.hpp>
|
||||
#include <openvpn/http/parseutil.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
|
||||
struct Request {
|
||||
Request() : http_version_major(0), http_version_minor(0) {}
|
||||
|
||||
void reset()
|
||||
{
|
||||
method = "";
|
||||
uri = "";
|
||||
http_version_major = 0;
|
||||
http_version_minor = 0;
|
||||
headers.clear();
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "HTTP Request" << std::endl;
|
||||
out << "method=" << method << std::endl;
|
||||
out << "uri=" << uri << std::endl;
|
||||
out << "version=" << http_version_major << '/' << http_version_minor << std::endl;
|
||||
out << headers.to_string();
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string to_string_compact() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << method << ' ' << uri << " HTTP/" << http_version_major << '.' << http_version_minor;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
bool at_least_http_1_1() const
|
||||
{
|
||||
return http_version_major > 1 || (http_version_major == 1 && http_version_minor >= 1);
|
||||
}
|
||||
|
||||
std::string method;
|
||||
std::string uri;
|
||||
int http_version_major;
|
||||
int http_version_minor;
|
||||
HeaderList headers;
|
||||
};
|
||||
|
||||
class RequestParser {
|
||||
enum state
|
||||
{
|
||||
method_start,
|
||||
method,
|
||||
uri,
|
||||
http_version_h,
|
||||
http_version_t_1,
|
||||
http_version_t_2,
|
||||
http_version_p,
|
||||
http_version_slash,
|
||||
http_version_major_start,
|
||||
http_version_major,
|
||||
http_version_minor_start,
|
||||
http_version_minor,
|
||||
expecting_newline_1,
|
||||
header_line_start,
|
||||
header_lws,
|
||||
header_name,
|
||||
space_before_header_value,
|
||||
header_value,
|
||||
expecting_newline_2,
|
||||
expecting_newline_3
|
||||
};
|
||||
|
||||
public:
|
||||
enum status {
|
||||
pending,
|
||||
fail,
|
||||
success,
|
||||
};
|
||||
|
||||
RequestParser()
|
||||
: state_(method_start)
|
||||
{
|
||||
}
|
||||
|
||||
// Reset to initial parser state.
|
||||
void reset()
|
||||
{
|
||||
state_ = method_start;
|
||||
}
|
||||
|
||||
// Parse some HTTP request data.
|
||||
status consume(Request& req, const unsigned char input)
|
||||
{
|
||||
switch (state_)
|
||||
{
|
||||
case method_start:
|
||||
if (!Util::is_char(input) || Util::is_ctl(input) || Util::is_tspecial(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = method;
|
||||
req.method.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case method:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = uri;
|
||||
return pending;
|
||||
}
|
||||
else if (!Util::is_char(input) || Util::is_ctl(input) || Util::is_tspecial(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.method.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case uri:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = http_version_h;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_ctl(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.uri.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case http_version_h:
|
||||
if (input == 'H')
|
||||
{
|
||||
state_ = http_version_t_1;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_t_1:
|
||||
if (input == 'T')
|
||||
{
|
||||
state_ = http_version_t_2;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_t_2:
|
||||
if (input == 'T')
|
||||
{
|
||||
state_ = http_version_p;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_p:
|
||||
if (input == 'P')
|
||||
{
|
||||
state_ = http_version_slash;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_slash:
|
||||
if (input == '/')
|
||||
{
|
||||
req.http_version_major = 0;
|
||||
req.http_version_minor = 0;
|
||||
state_ = http_version_major_start;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_major_start:
|
||||
if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_major = req.http_version_major * 10 + input - '0';
|
||||
state_ = http_version_major;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_major:
|
||||
if (input == '.')
|
||||
{
|
||||
state_ = http_version_minor_start;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_major = req.http_version_major * 10 + input - '0';
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_minor_start:
|
||||
if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_minor = req.http_version_minor * 10 + input - '0';
|
||||
state_ = http_version_minor;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case http_version_minor:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_1;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_digit(input))
|
||||
{
|
||||
req.http_version_minor = req.http_version_minor * 10 + input - '0';
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case expecting_newline_1:
|
||||
if (input == '\n')
|
||||
{
|
||||
state_ = header_line_start;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case header_line_start:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_3;
|
||||
return pending;
|
||||
}
|
||||
else if (!req.headers.empty() && (input == ' ' || input == '\t'))
|
||||
{
|
||||
state_ = header_lws;
|
||||
return pending;
|
||||
}
|
||||
else if (!Util::is_char(input) || Util::is_ctl(input) || Util::is_tspecial(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.push_back(Header());
|
||||
req.headers.back().name.push_back(input);
|
||||
state_ = header_name;
|
||||
return pending;
|
||||
}
|
||||
case header_lws:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_2;
|
||||
return pending;
|
||||
}
|
||||
else if (input == ' ' || input == '\t')
|
||||
{
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_ctl(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = header_value;
|
||||
req.headers.back().value.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case header_name:
|
||||
if (input == ':')
|
||||
{
|
||||
state_ = space_before_header_value;
|
||||
return pending;
|
||||
}
|
||||
else if (!Util::is_char(input) || Util::is_ctl(input) || Util::is_tspecial(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.back().name.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case space_before_header_value:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = header_value;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case header_value:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_2;
|
||||
return pending;
|
||||
}
|
||||
else if (Util::is_ctl(input))
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.back().value.push_back(input);
|
||||
return pending;
|
||||
}
|
||||
case expecting_newline_2:
|
||||
if (input == '\n')
|
||||
{
|
||||
state_ = header_line_start;
|
||||
return pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail;
|
||||
}
|
||||
case expecting_newline_3:
|
||||
if (input == '\n')
|
||||
return success;
|
||||
else
|
||||
return fail;
|
||||
default:
|
||||
return fail;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// The current state of the parser.
|
||||
state state_;
|
||||
};
|
||||
|
||||
struct RequestType
|
||||
{
|
||||
typedef Request State;
|
||||
typedef RequestParser Parser;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef OPENVPN_HTTP_STATUS_H
|
||||
#define OPENVPN_HTTP_STATUS_H
|
||||
|
||||
// HTTP status codes
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
namespace Status {
|
||||
enum {
|
||||
OK=200,
|
||||
Connected=200,
|
||||
SwitchingProtocols=101,
|
||||
BadRequest=400,
|
||||
Unauthorized=401,
|
||||
Forbidden=403,
|
||||
NotFound=404,
|
||||
ProxyAuthenticationRequired=407,
|
||||
PreconditionFailed=412,
|
||||
InternalServerError=500,
|
||||
ProxyError=502,
|
||||
ServiceUnavailable=503,
|
||||
};
|
||||
|
||||
inline const char *to_string(const int status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case OK:
|
||||
return "OK";
|
||||
case SwitchingProtocols:
|
||||
return "Switching Protocols";
|
||||
case BadRequest:
|
||||
return "Bad Request";
|
||||
case Unauthorized:
|
||||
return "Unauthorized";
|
||||
case Forbidden:
|
||||
return "Forbidden";
|
||||
case NotFound:
|
||||
return "Not Found";
|
||||
case ProxyAuthenticationRequired:
|
||||
return "Proxy Authentication Required";
|
||||
case PreconditionFailed:
|
||||
return "Precondition Failed";
|
||||
case InternalServerError:
|
||||
return "Internal Server Error";
|
||||
case ProxyError:
|
||||
return "Proxy Error";
|
||||
case ServiceUnavailable:
|
||||
return "Service Unavailable";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,120 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef OPENVPN_HTTP_URLENCODE_H
|
||||
#define OPENVPN_HTTP_URLENCODE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/split.hpp>
|
||||
#include <openvpn/common/string.hpp>
|
||||
#include <openvpn/common/hexstr.hpp>
|
||||
#include <openvpn/common/unicode.hpp>
|
||||
#include <openvpn/http/parseutil.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace URL {
|
||||
OPENVPN_EXCEPTION(url_error);
|
||||
|
||||
inline std::string encode(const std::string& str)
|
||||
{
|
||||
std::string ret;
|
||||
ret.reserve(str.length() * 2); // just a guess
|
||||
for (auto &c : str)
|
||||
{
|
||||
if (HTTP::Util::is_escaped(c))
|
||||
{
|
||||
ret += '%';
|
||||
ret += render_hex_number((unsigned char)c, true);
|
||||
}
|
||||
else
|
||||
ret += c;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string decode(const std::string& encoded)
|
||||
{
|
||||
enum State {
|
||||
TEXT,
|
||||
PERCENT,
|
||||
DIGIT
|
||||
};
|
||||
|
||||
int value = 0;
|
||||
State state = TEXT;
|
||||
std::string ret;
|
||||
ret.reserve(encoded.size()); // just a guess
|
||||
|
||||
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<unsigned char>((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<std::string> decode_path(std::string path)
|
||||
{
|
||||
std::vector<std::string> list;
|
||||
if (!path.empty() && path[0] == '/')
|
||||
path = path.substr(1);
|
||||
Split::by_char_void<decltype(list), NullLex, Split::NullLimit>(list, path, '/');
|
||||
for (auto &i : list)
|
||||
i = decode(i);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,211 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef OPENVPN_HTTP_URLPARM_H
|
||||
#define OPENVPN_HTTP_URLPARM_H
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <openvpn/common/number.hpp>
|
||||
#include <openvpn/http/urlencode.hpp>
|
||||
#include <openvpn/http/webexcept.hpp>
|
||||
#include <openvpn/common/string.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace URL {
|
||||
OPENVPN_EXCEPTION(url_parameter_error);
|
||||
|
||||
struct Parm
|
||||
{
|
||||
Parm() {}
|
||||
|
||||
Parm(const std::string& name_arg, const std::string& value_arg)
|
||||
: name(name_arg), value(value_arg)
|
||||
{
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << name << '=' << value;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
class ParmList : public std::vector<Parm>
|
||||
{
|
||||
public:
|
||||
ParmList(const std::string& uri)
|
||||
{
|
||||
try {
|
||||
const std::vector<std::string> req_parms = string::split(uri, '?', 1);
|
||||
request_ = req_parms[0];
|
||||
if (req_parms.size() == 2)
|
||||
{
|
||||
const std::vector<std::string> kv_list = string::split(req_parms[1], '&');
|
||||
for (auto &kvstr : kv_list)
|
||||
{
|
||||
const std::vector<std::string> kv = string::split(kvstr, '=', 1);
|
||||
Parm p;
|
||||
p.name = decode(kv[0]);
|
||||
if (kv.size() == 2)
|
||||
p.value = decode(kv[1]);
|
||||
push_back(std::move(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw HTTP::WebException(HTTP::Status::BadRequest, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
const Parm* get(const std::string& key) const
|
||||
{
|
||||
for (auto &p : *this)
|
||||
{
|
||||
if (key == p.name)
|
||||
return &p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string get_value(const std::string& key) const
|
||||
{
|
||||
const Parm* p = get(key);
|
||||
if (p)
|
||||
return p->value;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::string& get_value_required(const std::string& key) const
|
||||
{
|
||||
const Parm* p = get(key);
|
||||
if (p)
|
||||
return p->value;
|
||||
else
|
||||
throw url_parameter_error(key + " : not found");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get_num(const std::string& name, const std::string& short_name, const T default_value) const
|
||||
{
|
||||
const Parm* p = get(name);
|
||||
if (!p && !short_name.empty())
|
||||
p = get(short_name);
|
||||
if (p)
|
||||
return parse_number_throw<T>(p->value, name);
|
||||
else
|
||||
return default_value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get_num_required(const std::string& name, const std::string& short_name) const
|
||||
{
|
||||
const Parm* p = get(name);
|
||||
if (!p && !short_name.empty())
|
||||
p = get(short_name);
|
||||
if (!p)
|
||||
throw url_parameter_error(name + " : not found");
|
||||
return parse_number_throw<T>(p->value, name);
|
||||
}
|
||||
|
||||
bool get_bool(const std::string& name, const std::string& short_name, const bool default_value) const
|
||||
{
|
||||
const Parm* p = get(name);
|
||||
if (!p && !short_name.empty())
|
||||
p = get(short_name);
|
||||
if (p)
|
||||
{
|
||||
if (p->value == "0")
|
||||
return false;
|
||||
else if (p->value == "1")
|
||||
return true;
|
||||
else
|
||||
throw url_parameter_error(name + ": parameter must be 0 or 1");
|
||||
}
|
||||
else
|
||||
return default_value;
|
||||
}
|
||||
|
||||
std::string get_string(const std::string& name, const std::string& short_name) const
|
||||
{
|
||||
const Parm* p = get(name);
|
||||
if (!p && !short_name.empty())
|
||||
p = get(short_name);
|
||||
if (p)
|
||||
return p->value;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string get_string_required(const std::string& name, const std::string& short_name) const
|
||||
{
|
||||
const Parm* p = get(name);
|
||||
if (!p && !short_name.empty())
|
||||
p = get(short_name);
|
||||
if (!p)
|
||||
throw url_parameter_error(name + " : not found");
|
||||
return p->value;
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
for (size_t i = 0; i < size(); ++i)
|
||||
out << '[' << i << "] " << (*this)[i].to_string() << std::endl;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string request(const bool remove_leading_slash) const
|
||||
{
|
||||
std::string ret = request_;
|
||||
if (remove_leading_slash)
|
||||
{
|
||||
if (ret.length() > 0 && ret[0] == '/')
|
||||
ret = ret.substr(1);
|
||||
else
|
||||
throw HTTP::WebException(HTTP::Status::BadRequest, "URI missing leading slash");
|
||||
}
|
||||
if (ret.empty())
|
||||
throw HTTP::WebException(HTTP::Status::BadRequest, "URI resource is empty");
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::string& request() const
|
||||
{
|
||||
return request_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string request_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,262 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef OPENVPN_HTTP_URLPARSE_H
|
||||
#define OPENVPN_HTTP_URLPARSE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <openvpn/common/platform.hpp>
|
||||
#include <openvpn/common/exception.hpp>
|
||||
#include <openvpn/common/string.hpp>
|
||||
#include <openvpn/common/hostport.hpp>
|
||||
#include <openvpn/common/format.hpp>
|
||||
#include <openvpn/http/validate_uri.hpp>
|
||||
#include <openvpn/http/parseutil.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace URL {
|
||||
OPENVPN_EXCEPTION(url_parse_error);
|
||||
|
||||
class Parse
|
||||
{
|
||||
public:
|
||||
Parse() {}
|
||||
|
||||
Parse(const std::string& url,
|
||||
const bool set_default_port=false,
|
||||
const bool loose_validation=false)
|
||||
{
|
||||
enum State {
|
||||
Scheme,
|
||||
PostSchemeSlash1,
|
||||
PostSchemeSlash2,
|
||||
StartHost,
|
||||
Host,
|
||||
BracketedHost,
|
||||
PostBracketedHost,
|
||||
Port,
|
||||
URI,
|
||||
};
|
||||
|
||||
State state = Scheme;
|
||||
for (auto &c : url)
|
||||
{
|
||||
reprocess:
|
||||
switch (state)
|
||||
{
|
||||
case Scheme:
|
||||
if (c == ':')
|
||||
state = PostSchemeSlash1;
|
||||
else if (is_valid_scheme_char(c))
|
||||
scheme += c;
|
||||
else
|
||||
throw url_parse_error("bad scheme char");
|
||||
break;
|
||||
case PostSchemeSlash1:
|
||||
if (c == '/')
|
||||
state = PostSchemeSlash2;
|
||||
else
|
||||
throw url_parse_error("expected '://' after scheme");
|
||||
break;
|
||||
case PostSchemeSlash2:
|
||||
if (c == '/')
|
||||
state = StartHost;
|
||||
else
|
||||
throw url_parse_error("expected '://' after scheme");
|
||||
break;
|
||||
case StartHost:
|
||||
if (c == '[')
|
||||
state = BracketedHost;
|
||||
else
|
||||
{
|
||||
state = Host;
|
||||
goto reprocess;
|
||||
}
|
||||
break;
|
||||
case Host:
|
||||
if (c == ':')
|
||||
state = Port;
|
||||
else if (c == '/')
|
||||
{
|
||||
state = URI;
|
||||
goto reprocess;
|
||||
}
|
||||
else
|
||||
host += c;
|
||||
break;
|
||||
case BracketedHost:
|
||||
if (c == ']')
|
||||
state = PostBracketedHost;
|
||||
else
|
||||
host += c;
|
||||
break;
|
||||
case PostBracketedHost:
|
||||
if (c == ':')
|
||||
{
|
||||
state = Port;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = URI;
|
||||
goto reprocess;
|
||||
}
|
||||
break;
|
||||
case Port:
|
||||
if (c == '/')
|
||||
{
|
||||
state = URI;
|
||||
goto reprocess;
|
||||
}
|
||||
else
|
||||
port += c;
|
||||
break;
|
||||
case URI:
|
||||
if (!HTTP::is_valid_uri_char(c) && !loose_validation)
|
||||
throw url_parse_error("bad URI char");
|
||||
uri += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (set_default_port)
|
||||
default_port();
|
||||
if (uri.empty())
|
||||
uri = "/";
|
||||
validate();
|
||||
}
|
||||
|
||||
// Note that special address types such as unix domain
|
||||
// sockets or windows named pipes store a tag such as
|
||||
// "unix" or "np" as the port component of an address/port
|
||||
// tuple. Here, we move such tags into the scheme.
|
||||
static Parse from_components(const bool https,
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& uri)
|
||||
{
|
||||
Parse p;
|
||||
p.scheme = https ? "https" : "http";
|
||||
p.host = host;
|
||||
if (port.size() >= 1 && !string::is_digit(port[0])) // non-INET address
|
||||
p.scheme = port;
|
||||
else
|
||||
p.port = port;
|
||||
p.uri = uri;
|
||||
return p;
|
||||
}
|
||||
|
||||
void validate() const
|
||||
{
|
||||
if (scheme.empty())
|
||||
throw url_parse_error("undefined scheme");
|
||||
if (host.empty())
|
||||
throw url_parse_error("undefined host");
|
||||
if (uri.empty())
|
||||
throw url_parse_error("undefined uri");
|
||||
|
||||
if (!port.empty() && !HostPort::is_valid_port(port))
|
||||
throw url_parse_error("bad port");
|
||||
if ((scheme == "http" || scheme == "https") && !HostPort::is_valid_host(host))
|
||||
throw url_parse_error("bad host");
|
||||
}
|
||||
|
||||
void default_port()
|
||||
{
|
||||
if (port.empty())
|
||||
{
|
||||
if (scheme == "http")
|
||||
port = "80";
|
||||
else if (scheme == "https")
|
||||
port = "443";
|
||||
}
|
||||
}
|
||||
|
||||
bool port_implied() const
|
||||
{
|
||||
return (scheme == "http" && port == "80") || (scheme == "https" && port == "443");
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
const bool bracket_host = (host.find_first_of(":/\\") != std::string::npos);
|
||||
|
||||
std::string ret;
|
||||
ret.reserve(256);
|
||||
ret += scheme;
|
||||
ret += "://";
|
||||
if (bracket_host)
|
||||
ret += '[';
|
||||
ret += host;
|
||||
if (bracket_host)
|
||||
ret += ']';
|
||||
if (!port.empty() && !port_implied())
|
||||
{
|
||||
ret += ':';
|
||||
ret += port;
|
||||
}
|
||||
ret += uri;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string format_components() const
|
||||
{
|
||||
return printfmt("[scheme=%r host=%r port=%r uri=%r]", scheme, host, port, uri);
|
||||
}
|
||||
|
||||
// Note that special address types such as unix domain
|
||||
// sockets or windows named pipes store a tag such as
|
||||
// "unix" or "np" as the port component of an address/port
|
||||
// tuple. This method returns the port number for INET
|
||||
// addresses or a special tag for non-INET addresses.
|
||||
// Internally, we store the tag as an alternative
|
||||
// scheme such as "unix" or "np".
|
||||
std::string port_for_scheme() const
|
||||
{
|
||||
#ifdef OPENVPN_PLATFORM_WIN
|
||||
if (scheme == "np") // named pipe
|
||||
return scheme;
|
||||
#else
|
||||
if (scheme == "unix") // unix domain socket
|
||||
return scheme;
|
||||
#endif
|
||||
if (scheme == "http" || scheme == "https")
|
||||
return port;
|
||||
|
||||
throw url_parse_error("unknown scheme");
|
||||
}
|
||||
|
||||
std::string scheme;
|
||||
std::string host;
|
||||
std::string port;
|
||||
std::string uri;
|
||||
|
||||
private:
|
||||
bool is_valid_scheme_char(const char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || c == '_';
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <openvpn/common/exception.hpp>
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
inline bool is_valid_uri_char(const unsigned char c)
|
||||
{
|
||||
return c >= 0x21 && c <= 0x7E;
|
||||
}
|
||||
|
||||
inline bool is_valid_uri_char(const char c)
|
||||
{
|
||||
return is_valid_uri_char((unsigned char)c);
|
||||
}
|
||||
|
||||
inline void validate_uri(const std::string& uri, const std::string& title)
|
||||
{
|
||||
if (uri.empty())
|
||||
throw Exception(title + " : URI is empty");
|
||||
if (uri[0] != '/')
|
||||
throw Exception(title + " : URI must begin with '/'");
|
||||
for (auto &c : uri)
|
||||
{
|
||||
if (!is_valid_uri_char(c))
|
||||
throw Exception(title + " : URI contains illegal character");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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-2017 OpenVPN 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef OPENVPN_HTTP_EXCEPT_H
|
||||
#define OPENVPN_HTTP_EXCEPT_H
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
|
||||
#include <openvpn/http/status.hpp>
|
||||
|
||||
# define OPENVPN_THROW_WEB(exc, status, stuff) \
|
||||
do { \
|
||||
std::ostringstream _ovpn_exc; \
|
||||
_ovpn_exc << stuff; \
|
||||
throw exc(status, _ovpn_exc.str()); \
|
||||
} while (0)
|
||||
|
||||
namespace openvpn {
|
||||
namespace HTTP {
|
||||
class WebException : public std::exception
|
||||
{
|
||||
public:
|
||||
WebException(const int status, const std::string& error)
|
||||
: status_(status),
|
||||
error_(error),
|
||||
formatted(std::string(Status::to_string(status_)) + " : " + error_)
|
||||
{
|
||||
}
|
||||
|
||||
WebException(const int status)
|
||||
: status_(status),
|
||||
error_(Status::to_string(status_)),
|
||||
formatted(error_)
|
||||
{
|
||||
}
|
||||
|
||||
int status() const { return status_; }
|
||||
const std::string& error() const { return error_; }
|
||||
|
||||
virtual const char* what() const throw() { return formatted.c_str(); }
|
||||
|
||||
private:
|
||||
const int status_;
|
||||
const std::string error_;
|
||||
const std::string formatted;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user