2019-07-27 17:36:24 +03:00
|
|
|
//
|
2020-06-12 18:52:38 +03:00
|
|
|
// CRLib - Simple library for STL replacement in private projects.
|
|
|
|
|
// Copyright © 2020 YaPB Development Team <team@yapb.ru>.
|
2019-07-27 17:36:24 +03:00
|
|
|
//
|
2020-06-12 18:52:38 +03:00
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
2019-07-27 17:36:24 +03:00
|
|
|
//
|
2020-06-12 18:52:38 +03:00
|
|
|
// 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 General Public License for more details.
|
|
|
|
|
//
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
#include <crlib/cr-string.h>
|
|
|
|
|
#include <crlib/cr-files.h>
|
|
|
|
|
#include <crlib/cr-logger.h>
|
|
|
|
|
#include <crlib/cr-platform.h>
|
|
|
|
|
|
|
|
|
|
#if defined (CR_LINUX) || defined (CR_OSX)
|
|
|
|
|
# include <netinet/in.h>
|
|
|
|
|
# include <sys/socket.h>
|
|
|
|
|
# include <sys/types.h>
|
2019-07-31 14:05:36 +03:00
|
|
|
# include <sys/uio.h>
|
2019-07-27 17:36:24 +03:00
|
|
|
# include <arpa/inet.h>
|
|
|
|
|
# include <unistd.h>
|
|
|
|
|
# include <errno.h>
|
|
|
|
|
# include <netdb.h>
|
|
|
|
|
# include <fcntl.h>
|
|
|
|
|
#elif defined (CR_WINDOWS)
|
|
|
|
|
# include <winsock2.h>
|
2019-09-21 23:20:33 +03:00
|
|
|
# include <ws2tcpip.h>
|
2019-07-27 17:36:24 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// status codes for http client
|
|
|
|
|
CR_DECLARE_SCOPED_ENUM (HttpClientResult,
|
|
|
|
|
OK = 0,
|
|
|
|
|
NotFound,
|
|
|
|
|
Forbidden,
|
|
|
|
|
SocketError,
|
|
|
|
|
ConnectError,
|
|
|
|
|
HttpOnly,
|
|
|
|
|
Undefined,
|
|
|
|
|
NoLocalFile = -1,
|
|
|
|
|
LocalFileExists = -2
|
2019-08-01 23:02:44 +03:00
|
|
|
)
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
CR_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
|
|
class Socket final : public DenyCopying {
|
|
|
|
|
private:
|
2020-06-12 18:52:38 +03:00
|
|
|
int32 socket_;
|
|
|
|
|
uint32 timeout_;
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
public:
|
2020-06-12 18:52:38 +03:00
|
|
|
Socket () : socket_ (-1), timeout_ (2) {
|
2019-07-27 17:36:24 +03:00
|
|
|
#if defined(CR_WINDOWS)
|
|
|
|
|
WSADATA wsa;
|
|
|
|
|
|
2019-07-28 21:30:33 +03:00
|
|
|
if (WSAStartup (MAKEWORD (2, 2), &wsa) != 0) {
|
2019-07-27 17:36:24 +03:00
|
|
|
logger.error ("Unable to inialize sockets.");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~Socket () {
|
|
|
|
|
disconnect ();
|
|
|
|
|
#if defined (CR_WINDOWS)
|
|
|
|
|
WSACleanup ();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
2020-06-12 18:52:38 +03:00
|
|
|
bool connect (StringRef hostname) {
|
2019-09-21 23:20:33 +03:00
|
|
|
addrinfo hints, *result = nullptr;
|
|
|
|
|
plat.bzero (&hints, sizeof (hints));
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
constexpr auto NumericServ = 0x00000008;
|
|
|
|
|
|
|
|
|
|
hints.ai_flags = NumericServ;
|
2019-09-21 23:20:33 +03:00
|
|
|
hints.ai_family = AF_INET;
|
|
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
|
|
|
|
|
|
if (getaddrinfo (hostname.chars (), "80", &hints, &result) != 0) {
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
2020-06-12 18:52:38 +03:00
|
|
|
socket_ = static_cast <int> (socket (result->ai_family, result->ai_socktype, 0));
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
if (socket_ < 0) {
|
2019-09-22 01:01:24 +03:00
|
|
|
freeaddrinfo (result);
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
auto getTimeouts = [&] () -> Twin <char *, int32> {
|
2019-07-27 17:36:24 +03:00
|
|
|
#if defined (CR_WINDOWS)
|
2020-06-12 18:52:38 +03:00
|
|
|
DWORD tv = timeout_ * 1000;
|
2019-07-27 17:36:24 +03:00
|
|
|
#else
|
2020-06-12 18:52:38 +03:00
|
|
|
timeval tv { static_cast <time_t> (timeout_), 0 };
|
2019-07-27 17:36:24 +03:00
|
|
|
#endif
|
2020-06-12 18:52:38 +03:00
|
|
|
return { reinterpret_cast <char *> (&tv), static_cast <int32> (sizeof (tv)) };
|
2019-07-27 17:36:24 +03:00
|
|
|
};
|
|
|
|
|
auto timeouts = getTimeouts ();
|
|
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
if (setsockopt (socket_, SOL_SOCKET, SO_RCVTIMEO, timeouts.first, timeouts.second) == -1) {
|
2019-09-21 23:20:33 +03:00
|
|
|
logger.message ("Unable to set SO_RCVTIMEO.");
|
2019-07-28 15:47:46 +03:00
|
|
|
}
|
|
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
if (setsockopt (socket_, SOL_SOCKET, SO_SNDTIMEO, timeouts.first, timeouts.second) == -1) {
|
2019-09-21 23:20:33 +03:00
|
|
|
logger.message ("Unable to set SO_SNDTIMEO.");
|
2019-07-28 15:47:46 +03:00
|
|
|
}
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
if (::connect (socket_, result->ai_addr, static_cast <int32> (result->ai_addrlen)) == -1) {
|
2019-07-27 17:36:24 +03:00
|
|
|
disconnect ();
|
2019-09-21 23:20:33 +03:00
|
|
|
freeaddrinfo (result);
|
|
|
|
|
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
2019-09-21 23:20:33 +03:00
|
|
|
freeaddrinfo (result);
|
|
|
|
|
|
2019-07-27 17:36:24 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setTimeout (uint32 timeout) {
|
2020-06-12 18:52:38 +03:00
|
|
|
timeout_ = timeout;
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void disconnect () {
|
|
|
|
|
#if defined(CR_WINDOWS)
|
2020-06-12 18:52:38 +03:00
|
|
|
if (socket_ != -1) {
|
|
|
|
|
closesocket (socket_);
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
#else
|
2020-06-12 18:52:38 +03:00
|
|
|
if (socket_ != -1)
|
|
|
|
|
close (socket_);
|
2019-07-27 17:36:24 +03:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
template <typename U> int32 send (const U *buffer, int32 length) const {
|
2020-06-12 18:52:38 +03:00
|
|
|
return ::send (socket_, reinterpret_cast <const char *> (buffer), length, 0);
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename U> int32 recv (U *buffer, int32 length) {
|
2020-06-12 18:52:38 +03:00
|
|
|
return ::recv (socket_, reinterpret_cast <char *> (buffer), length, 0);
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
2019-07-31 14:05:36 +03:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
static int32 CR_STDCALL sendto (int socket, const void *message, size_t length, int flags, const struct sockaddr *dest, int32 destLength) {
|
|
|
|
|
#if defined (CR_WINDOWS)
|
2020-06-12 18:52:38 +03:00
|
|
|
WSABUF buffer = { static_cast <ULONG> (length), const_cast <char *> (reinterpret_cast <const char *> (message)) };
|
2019-07-31 14:05:36 +03:00
|
|
|
DWORD sendLength = 0;
|
|
|
|
|
|
|
|
|
|
if (WSASendTo (socket, &buffer, 1, &sendLength, flags, dest, destLength, NULL, NULL) == SOCKET_ERROR) {
|
|
|
|
|
errno = WSAGetLastError ();
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return static_cast <int32> (sendLength);
|
|
|
|
|
#else
|
|
|
|
|
iovec iov = { const_cast <void *> (message), length };
|
2019-07-31 14:17:05 +03:00
|
|
|
msghdr msg {};
|
2019-07-31 14:05:36 +03:00
|
|
|
|
|
|
|
|
msg.msg_name = reinterpret_cast <void *> (const_cast <struct sockaddr *> (dest));
|
|
|
|
|
msg.msg_namelen = destLength;
|
|
|
|
|
msg.msg_iov = &iov;
|
|
|
|
|
msg.msg_iovlen = 1;
|
|
|
|
|
|
|
|
|
|
return sendmsg (socket, &msg, flags);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2019-07-27 17:36:24 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace detail {
|
|
|
|
|
|
|
|
|
|
// simple http uri omitting query-string and port
|
|
|
|
|
struct HttpUri {
|
|
|
|
|
String path, protocol, host;
|
|
|
|
|
|
|
|
|
|
public:
|
2020-06-12 18:52:38 +03:00
|
|
|
static HttpUri parse (StringRef uri) {
|
2019-07-27 17:36:24 +03:00
|
|
|
HttpUri result;
|
|
|
|
|
|
|
|
|
|
if (uri.empty ()) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
size_t protocol = uri.find ("://");
|
|
|
|
|
|
2019-08-18 21:00:00 +03:00
|
|
|
if (protocol != String::InvalidIndex) {
|
2019-07-27 17:36:24 +03:00
|
|
|
result.protocol = uri.substr (0, protocol);
|
|
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
size_t hostIndex = uri.find ("/", protocol + 3);
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
if (hostIndex != String::InvalidIndex) {
|
|
|
|
|
result.path = uri.substr (hostIndex + 1);
|
|
|
|
|
result.host = uri.substr (protocol + 3, hostIndex - protocol - 3);
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
};
|
2019-08-01 23:02:44 +03:00
|
|
|
}
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
// simple http client for downloading/uploading files only
|
|
|
|
|
class HttpClient final : public Singleton <HttpClient> {
|
|
|
|
|
private:
|
2019-09-05 10:44:08 +03:00
|
|
|
enum : int32 {
|
|
|
|
|
MaxReceiveErrors = 12
|
|
|
|
|
};
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
private:
|
2020-06-12 18:52:38 +03:00
|
|
|
Socket socket_;
|
|
|
|
|
String userAgent_ = "crlib";
|
|
|
|
|
HttpClientResult statusCode_ = HttpClientResult::Undefined;
|
|
|
|
|
int32 chunkSize_ = 4096;
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
HttpClient () = default;
|
|
|
|
|
~HttpClient () = default;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
HttpClientResult parseResponseHeader (uint8 *buffer) {
|
|
|
|
|
bool isFinished = false;
|
|
|
|
|
int32 pos = 0, symbols = 0, errors = 0;
|
|
|
|
|
|
|
|
|
|
// prase response header
|
2020-06-12 18:52:38 +03:00
|
|
|
while (!isFinished && pos < chunkSize_) {
|
|
|
|
|
if (socket_.recv (&buffer[pos], 1) < 1) {
|
2019-09-05 10:44:08 +03:00
|
|
|
if (++errors > MaxReceiveErrors) {
|
2019-07-27 17:36:24 +03:00
|
|
|
isFinished = true;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (buffer[pos]) {
|
|
|
|
|
case '\r':
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '\n':
|
|
|
|
|
isFinished = (symbols == 0);
|
|
|
|
|
symbols = 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2019-08-04 18:22:01 +03:00
|
|
|
++symbols;
|
2019-07-27 17:36:24 +03:00
|
|
|
break;
|
|
|
|
|
}
|
2019-08-04 18:22:01 +03:00
|
|
|
++pos;
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
2020-06-12 18:52:38 +03:00
|
|
|
String response { reinterpret_cast <const char *> (buffer) };
|
2019-07-27 17:36:24 +03:00
|
|
|
size_t responseCodeStart = response.find ("HTTP/1.1");
|
|
|
|
|
|
2019-08-18 21:00:00 +03:00
|
|
|
if (responseCodeStart != String::InvalidIndex) {
|
2020-06-12 18:52:38 +03:00
|
|
|
String respCode = response.substr (responseCodeStart + 9, 3);
|
|
|
|
|
respCode.trim ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
if (respCode == "200") {
|
|
|
|
|
return HttpClientResult::OK;
|
|
|
|
|
}
|
|
|
|
|
else if (respCode == "403") {
|
|
|
|
|
return HttpClientResult::Forbidden;
|
|
|
|
|
}
|
|
|
|
|
else if (respCode == "404") {
|
|
|
|
|
return HttpClientResult::NotFound;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return HttpClientResult::NotFound;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
// simple blocked download
|
2020-06-12 18:52:38 +03:00
|
|
|
bool downloadFile (StringRef url, StringRef localPath) {
|
2019-07-27 17:36:24 +03:00
|
|
|
if (File::exists (localPath.chars ())) {
|
2020-06-12 18:52:38 +03:00
|
|
|
statusCode_ = HttpClientResult::LocalFileExists;
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
auto uri = detail::HttpUri::parse (url);
|
|
|
|
|
|
|
|
|
|
// no https...
|
|
|
|
|
if (uri.protocol == "https") {
|
2020-06-12 18:52:38 +03:00
|
|
|
statusCode_ = HttpClientResult::HttpOnly;
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unable to connect...
|
2020-06-12 18:52:38 +03:00
|
|
|
if (!socket_.connect (uri.host)) {
|
|
|
|
|
statusCode_ = HttpClientResult::ConnectError;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String request;
|
2020-06-12 18:52:38 +03:00
|
|
|
request.appendf ("GET /%s HTTP/1.1\r\n", uri.path);
|
2019-07-27 17:36:24 +03:00
|
|
|
request.append ("Accept: */*\r\n");
|
|
|
|
|
request.append ("Connection: close\r\n");
|
|
|
|
|
request.append ("Keep-Alive: 115\r\n");
|
2020-06-12 18:52:38 +03:00
|
|
|
request.appendf ("User-Agent: %s\r\n", userAgent_);
|
|
|
|
|
request.appendf ("Host: %s\r\n\r\n", uri.host);
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
if (socket_.send (request.chars (), static_cast <int32> (request.length ())) < 1) {
|
|
|
|
|
statusCode_ = HttpClientResult::SocketError;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-06-12 18:52:38 +03:00
|
|
|
SmallArray <uint8> buffer (chunkSize_);
|
|
|
|
|
statusCode_ = parseResponseHeader (buffer.data ());
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
if (statusCode_ != HttpClientResult::OK) {
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// receive the file
|
|
|
|
|
File file (localPath, "wb");
|
|
|
|
|
|
|
|
|
|
if (!file) {
|
2020-06-12 18:52:38 +03:00
|
|
|
statusCode_ = HttpClientResult::Undefined;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
int32 length = 0;
|
|
|
|
|
int32 errors = 0;
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
2020-06-12 18:52:38 +03:00
|
|
|
length = socket_.recv (buffer.data (), chunkSize_);
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
if (length > 0) {
|
|
|
|
|
file.write (buffer.data (), length);
|
|
|
|
|
}
|
|
|
|
|
else if (++errors > 12) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
file.close ();
|
|
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
socket_.disconnect ();
|
|
|
|
|
statusCode_ = HttpClientResult::OK;
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
bool uploadFile (StringRef url, StringRef localPath) {
|
2019-07-27 17:36:24 +03:00
|
|
|
if (!File::exists (localPath.chars ())) {
|
2020-06-12 18:52:38 +03:00
|
|
|
statusCode_ = HttpClientResult::NoLocalFile;
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
auto uri = detail::HttpUri::parse (url);
|
|
|
|
|
|
|
|
|
|
// no https...
|
|
|
|
|
if (uri.protocol == "https") {
|
2020-06-12 18:52:38 +03:00
|
|
|
statusCode_ = HttpClientResult::HttpOnly;
|
2019-07-27 17:36:24 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unable to connect...
|
2020-06-12 18:52:38 +03:00
|
|
|
if (!socket_.connect (uri.host)) {
|
|
|
|
|
statusCode_ = HttpClientResult::ConnectError;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// receive the file
|
|
|
|
|
File file (localPath, "rb");
|
|
|
|
|
|
|
|
|
|
if (!file) {
|
2020-06-12 18:52:38 +03:00
|
|
|
statusCode_ = HttpClientResult::Undefined;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
String boundaryName = localPath;
|
|
|
|
|
size_t boundarySlash = localPath.findLastOf ("\\/");
|
|
|
|
|
|
2019-08-18 21:00:00 +03:00
|
|
|
if (boundarySlash != String::InvalidIndex) {
|
2019-07-27 17:36:24 +03:00
|
|
|
boundaryName = localPath.substr (boundarySlash + 1);
|
|
|
|
|
}
|
2020-06-12 18:52:38 +03:00
|
|
|
StringRef boundaryLine = "---crlib_upload_boundary_1337";
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
String request, start, end;
|
2020-06-12 18:52:38 +03:00
|
|
|
start.appendf ("--%s\r\n", boundaryLine);
|
|
|
|
|
start.appendf ("Content-Disposition: form-data; name='file'; filename='%s'\r\n", boundaryName);
|
2019-07-27 17:36:24 +03:00
|
|
|
start.append ("Content-Type: application/octet-stream\r\n\r\n");
|
|
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
end.appendf ("\r\n--%s--\r\n\r\n", boundaryLine);
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
request.appendf ("POST /%s HTTP/1.1\r\n", uri.path);
|
|
|
|
|
request.appendf ("Host: %s\r\n", uri.host);
|
|
|
|
|
request.appendf ("User-Agent: %s\r\n", userAgent_);
|
|
|
|
|
request.appendf ("Content-Type: multipart/form-data; boundary=%s\r\n", boundaryLine);
|
2019-07-27 17:36:24 +03:00
|
|
|
request.appendf ("Content-Length: %d\r\n\r\n", file.length () + start.length () + end.length ());
|
|
|
|
|
|
|
|
|
|
// send the main request
|
2020-06-12 18:52:38 +03:00
|
|
|
if (socket_.send (request.chars (), static_cast <int32> (request.length ())) < 1) {
|
|
|
|
|
statusCode_ = HttpClientResult::SocketError;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// send boundary start
|
2020-06-12 18:52:38 +03:00
|
|
|
if (socket_.send (start.chars (), static_cast <int32> (start.length ())) < 1) {
|
|
|
|
|
statusCode_ = HttpClientResult::SocketError;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-06-12 18:52:38 +03:00
|
|
|
SmallArray <uint8> buffer (chunkSize_);
|
2019-07-27 17:36:24 +03:00
|
|
|
int32 length = 0;
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
2020-06-12 18:52:38 +03:00
|
|
|
length = static_cast <int32> (file.read (buffer.data (), 1, chunkSize_));
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
if (length > 0) {
|
2020-06-12 18:52:38 +03:00
|
|
|
socket_.send (buffer.data (), length);
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// send boundary end
|
2020-06-12 18:52:38 +03:00
|
|
|
if (socket_.send (end.chars (), static_cast <int32> (end.length ())) < 1) {
|
|
|
|
|
statusCode_ = HttpClientResult::SocketError;
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-06-12 18:52:38 +03:00
|
|
|
statusCode_ = parseResponseHeader (buffer.data ());
|
|
|
|
|
socket_.disconnect ();
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
return statusCode_ == HttpClientResult::OK;
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
2020-06-12 18:52:38 +03:00
|
|
|
void setUserAgent (StringRef ua) {
|
|
|
|
|
userAgent_ = ua;
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HttpClientResult getLastStatusCode () {
|
2020-06-12 18:52:38 +03:00
|
|
|
return statusCode_;
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setChunkSize (int32 chunkSize) {
|
2020-06-12 18:52:38 +03:00
|
|
|
chunkSize_ = chunkSize;
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setTimeout (uint32 timeout) {
|
2020-06-12 18:52:38 +03:00
|
|
|
socket_.setTimeout (timeout);
|
2019-07-27 17:36:24 +03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// expose global http client
|
2020-02-08 00:03:52 +03:00
|
|
|
CR_EXPOSE_GLOBAL_SINGLETON (HttpClient, http);
|
2019-07-27 17:36:24 +03:00
|
|
|
|
2020-06-12 18:52:38 +03:00
|
|
|
CR_NAMESPACE_END
|