mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-02-12 05:55:19 +00:00
328 lines
8.5 KiB
C
328 lines
8.5 KiB
C
|
/*
|
||
|
* httpclientlite.hpp
|
||
|
* ===========================================================================================
|
||
|
*
|
||
|
* The MIT License
|
||
|
*
|
||
|
* Copyright (c) 2016 Christian C. Sachs
|
||
|
* Copyright (c) 2021 Maxim G.
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to deal
|
||
|
* in the Software without restriction, including without limitation the rights
|
||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
* copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in all
|
||
|
* copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
* SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#if defined (__linux__)
|
||
|
# define PLATFORM_LINUX
|
||
|
#elif defined (_WIN32) || defined (_WIN64)
|
||
|
# define PLATFORM_WINDOWS
|
||
|
#else
|
||
|
/* TODO:
|
||
|
* - Added Apple OS */
|
||
|
|
||
|
/* warning: Unknown OS */
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#include <iostream>
|
||
|
#include <string>
|
||
|
#include <map>
|
||
|
#include <vector>
|
||
|
#include <cstring>
|
||
|
#include <sstream>
|
||
|
|
||
|
#include <sys/types.h>
|
||
|
|
||
|
#if defined (PLATFORM_WINDOWS)
|
||
|
# include <WinSock2.h>
|
||
|
# include <WS2tcpip.h>
|
||
|
|
||
|
typedef SOCKET socktype_t;
|
||
|
typedef int socklen_t;
|
||
|
|
||
|
# pragma comment(lib, "ws2_32.lib")
|
||
|
|
||
|
#elif defined (PLATFORM_LINUX)
|
||
|
# include <unistd.h>
|
||
|
# include <sys/socket.h>
|
||
|
# include <netdb.h>
|
||
|
|
||
|
# define INVALID_SOCKET -1
|
||
|
# define closesocket(__sock) close(__sock)
|
||
|
|
||
|
typedef int socktype_t;
|
||
|
|
||
|
#endif /* PLATFORM_WINDOWS or PLATFORM_LINUX */
|
||
|
|
||
|
|
||
|
|
||
|
const std::string content_type = "Content-Type: text/plain; version=0.0.4; charset=utf-8";
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
namespace jdl {
|
||
|
|
||
|
void init_socket() {
|
||
|
#if defined (PLATFORM_WINDOWS)
|
||
|
WSADATA wsa_data;
|
||
|
WSAStartup(MAKEWORD(2, 2), &wsa_data);
|
||
|
#endif /* PLATFORM_WINDOWS */
|
||
|
}
|
||
|
|
||
|
void deinit_socket() {
|
||
|
#if defined (PLATFORM_WINDOWS)
|
||
|
WSACleanup();
|
||
|
#endif /* PLATFORM_WINDOWS */
|
||
|
}
|
||
|
|
||
|
|
||
|
class tokenizer {
|
||
|
public:
|
||
|
inline tokenizer(std::string &str) : str(str), position(0){}
|
||
|
|
||
|
inline std::string next(std::string search, bool returnTail = false) {
|
||
|
size_t hit = str.find(search, position);
|
||
|
if (hit == std::string::npos) {
|
||
|
if (returnTail) {
|
||
|
return tail();
|
||
|
} else {
|
||
|
return "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
size_t oldPosition = position;
|
||
|
position = hit + search.length();
|
||
|
|
||
|
return str.substr(oldPosition, hit - oldPosition);
|
||
|
}
|
||
|
|
||
|
inline std::string tail() {
|
||
|
size_t oldPosition = position;
|
||
|
position = str.length();
|
||
|
return str.substr(oldPosition);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
std::string str;
|
||
|
std::size_t position;
|
||
|
};
|
||
|
|
||
|
typedef std::map<std::string, std::string> stringMap;
|
||
|
|
||
|
struct URI {
|
||
|
inline void parseParameters() {
|
||
|
tokenizer qt(querystring);
|
||
|
do {
|
||
|
std::string key = qt.next("=");
|
||
|
if (key == "")
|
||
|
break;
|
||
|
parameters[key] = qt.next("&", true);
|
||
|
} while (true);
|
||
|
}
|
||
|
|
||
|
inline URI(std::string input, bool shouldParseParameters = false) {
|
||
|
tokenizer t = tokenizer(input);
|
||
|
protocol = t.next("://");
|
||
|
std::string hostPortString = t.next("/");
|
||
|
|
||
|
tokenizer hostPort(hostPortString);
|
||
|
|
||
|
host = hostPort.next(hostPortString[0] == '[' ? "]:" : ":", true);
|
||
|
|
||
|
if (host[0] == '[')
|
||
|
host = host.substr(1, host.size() - 1);
|
||
|
|
||
|
port = hostPort.tail();
|
||
|
if (port.empty())
|
||
|
port = "80";
|
||
|
|
||
|
address = t.next("?", true);
|
||
|
querystring = t.next("#", true);
|
||
|
|
||
|
hash = t.tail();
|
||
|
|
||
|
if (shouldParseParameters) {
|
||
|
parseParameters();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::string protocol, host, port, address, querystring, hash;
|
||
|
stringMap parameters;
|
||
|
};
|
||
|
|
||
|
struct HTTPResponse {
|
||
|
bool success;
|
||
|
std::string protocol;
|
||
|
std::string response;
|
||
|
std::string responseString;
|
||
|
|
||
|
stringMap header;
|
||
|
|
||
|
std::string body;
|
||
|
|
||
|
inline HTTPResponse() : success(true){}
|
||
|
inline static HTTPResponse fail() {
|
||
|
HTTPResponse result;
|
||
|
result.success = false;
|
||
|
return result;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct HTTPClient {
|
||
|
typedef enum {
|
||
|
m_options = 0,
|
||
|
m_get,
|
||
|
m_head,
|
||
|
m_post,
|
||
|
m_put,
|
||
|
m_delete,
|
||
|
m_trace,
|
||
|
m_connect
|
||
|
} HTTPMethod;
|
||
|
|
||
|
inline static const char *method2string(HTTPMethod method) {
|
||
|
const char *methods[] = {"OPTIONS", "GET", "HEAD", "POST", "PUT",
|
||
|
"DELETE", "TRACE", "CONNECT", nullptr};
|
||
|
return methods[method];
|
||
|
}
|
||
|
|
||
|
inline static socktype_t connectToURI(const URI& uri) {
|
||
|
struct addrinfo hints, *result, *rp;
|
||
|
|
||
|
memset(&hints, 0, sizeof(addrinfo));
|
||
|
|
||
|
hints.ai_family = AF_UNSPEC;
|
||
|
hints.ai_socktype = SOCK_STREAM;
|
||
|
|
||
|
int getaddrinfo_result =
|
||
|
getaddrinfo(uri.host.c_str(), uri.port.c_str(), &hints, &result);
|
||
|
|
||
|
if (getaddrinfo_result != 0)
|
||
|
return -1;
|
||
|
|
||
|
socktype_t fd = INVALID_SOCKET;
|
||
|
|
||
|
for (rp = result; rp != nullptr; rp = rp->ai_next) {
|
||
|
|
||
|
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||
|
|
||
|
if (fd == INVALID_SOCKET) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
int connect_result = connect(fd, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen));
|
||
|
|
||
|
if (connect_result == -1) {
|
||
|
// successfully created a socket, but connection failed. close it!
|
||
|
closesocket(fd);
|
||
|
fd = INVALID_SOCKET;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
freeaddrinfo(result);
|
||
|
|
||
|
return fd;
|
||
|
}
|
||
|
|
||
|
inline static std::string bufferedRead(socktype_t fd) {
|
||
|
size_t initial_factor = 4, buffer_increment_size = 8192, buffer_size = 0,
|
||
|
bytes_read = 0;
|
||
|
std::string buffer;
|
||
|
|
||
|
buffer.resize(initial_factor * buffer_increment_size);
|
||
|
|
||
|
// do {
|
||
|
bytes_read = recv(fd, ((char*)buffer.c_str()) + buffer_size,
|
||
|
static_cast<socklen_t>(buffer.size() - buffer_size), 0);
|
||
|
|
||
|
buffer_size += bytes_read;
|
||
|
|
||
|
// if (bytes_read > 0 &&
|
||
|
// (buffer.size() - buffer_size) < buffer_increment_size) {
|
||
|
// buffer.resize(buffer.size() + buffer_increment_size);
|
||
|
// }
|
||
|
// } while (bytes_read > 0);
|
||
|
|
||
|
buffer.resize(buffer_size);
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
#define HTTP_NEWLINE "\r\n"
|
||
|
#define HTTP_SPACE " "
|
||
|
#define HTTP_HEADER_SEPARATOR ": "
|
||
|
|
||
|
inline static HTTPResponse request(HTTPMethod method, const URI& uri, const std::string& body = "") {
|
||
|
|
||
|
socktype_t fd = connectToURI(uri);
|
||
|
if (fd < 0)
|
||
|
return HTTPResponse::fail();
|
||
|
|
||
|
// string request = string(method2string(method)) + string(" /") +
|
||
|
// uri.address + ((uri.querystring == "") ? "" : "?") +
|
||
|
// uri.querystring + " HTTP/1.1" HTTP_NEWLINE "Host: " +
|
||
|
// uri.host + HTTP_NEWLINE
|
||
|
// "Accept: */*" HTTP_NEWLINE
|
||
|
// "Connection: close" HTTP_NEWLINE HTTP_NEWLINE;
|
||
|
|
||
|
std::string request = std::string(method2string(method)) + std::string(" /") +
|
||
|
uri.address + ((uri.querystring == "") ? "" : "?") + uri.querystring + " HTTP/1.1" + HTTP_NEWLINE +
|
||
|
"Host: " + uri.host + ":" + uri.port + HTTP_NEWLINE +
|
||
|
"Accept: */*" + HTTP_NEWLINE +
|
||
|
content_type + HTTP_NEWLINE +
|
||
|
"Content-Length: " + std::to_string(body.size()) + HTTP_NEWLINE + HTTP_NEWLINE +
|
||
|
body;
|
||
|
|
||
|
/*int bytes_written = */send(fd, request.c_str(), static_cast<socklen_t>(request.size()), 0);
|
||
|
|
||
|
std::string buffer = bufferedRead(fd);
|
||
|
|
||
|
closesocket(fd);
|
||
|
|
||
|
HTTPResponse result;
|
||
|
|
||
|
tokenizer bt(buffer);
|
||
|
|
||
|
result.protocol = bt.next(HTTP_SPACE);
|
||
|
result.response = bt.next(HTTP_SPACE);
|
||
|
result.responseString = bt.next(HTTP_NEWLINE);
|
||
|
|
||
|
std::string header = bt.next(HTTP_NEWLINE HTTP_NEWLINE);
|
||
|
|
||
|
result.body = bt.tail();
|
||
|
|
||
|
tokenizer ht(header);
|
||
|
|
||
|
do {
|
||
|
std::string key = ht.next(HTTP_HEADER_SEPARATOR);
|
||
|
if (key == "")
|
||
|
break;
|
||
|
result.header[key] = ht.next(HTTP_NEWLINE, true);
|
||
|
} while (true);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} /* jdl:: */
|