2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2017-08-06 23:28:21 -07:00
|
|
|
|
2018-04-29 23:33:19 -07:00
|
|
|
#include "wpi/HttpUtil.h"
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
#include <cctype>
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
#include "fmt/format.h"
|
2018-04-29 23:33:19 -07:00
|
|
|
#include "wpi/Base64.h"
|
|
|
|
|
#include "wpi/StringExtras.h"
|
|
|
|
|
#include "wpi/TCPConnector.h"
|
|
|
|
|
#include "wpi/raw_ostream.h"
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
namespace wpi {
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view UnescapeURI(std::string_view str, SmallVectorImpl<char>& buf,
|
|
|
|
|
bool* error) {
|
2017-08-06 23:28:21 -07:00
|
|
|
buf.clear();
|
2021-06-06 16:13:58 -07:00
|
|
|
for (auto i = str.begin(), end = str.end(); i != end; ++i) {
|
2017-08-06 23:28:21 -07:00
|
|
|
// pass non-escaped characters to output
|
|
|
|
|
if (*i != '%') {
|
|
|
|
|
// decode + to space
|
2020-12-28 12:58:06 -08:00
|
|
|
if (*i == '+') {
|
2017-08-06 23:28:21 -07:00
|
|
|
buf.push_back(' ');
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2017-08-06 23:28:21 -07:00
|
|
|
buf.push_back(*i);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// are there enough characters left?
|
|
|
|
|
if (i + 2 >= end) {
|
|
|
|
|
*error = true;
|
2021-06-06 16:13:58 -07:00
|
|
|
return {};
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// replace %xx with the corresponding character
|
2018-04-29 23:33:19 -07:00
|
|
|
unsigned val1 = hexDigitValue(*++i);
|
2017-08-06 23:28:21 -07:00
|
|
|
if (val1 == -1U) {
|
|
|
|
|
*error = true;
|
2021-06-06 16:13:58 -07:00
|
|
|
return {};
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
2018-04-29 23:33:19 -07:00
|
|
|
unsigned val2 = hexDigitValue(*++i);
|
2017-08-06 23:28:21 -07:00
|
|
|
if (val2 == -1U) {
|
|
|
|
|
*error = true;
|
2021-06-06 16:13:58 -07:00
|
|
|
return {};
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
buf.push_back((val1 << 4) | val2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*error = false;
|
2021-06-06 16:13:58 -07:00
|
|
|
return {buf.data(), buf.size()};
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view EscapeURI(std::string_view str, SmallVectorImpl<char>& buf,
|
|
|
|
|
bool spacePlus) {
|
2017-10-21 20:31:20 -07:00
|
|
|
static const char* const hexLut = "0123456789ABCDEF";
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
buf.clear();
|
2021-06-06 16:13:58 -07:00
|
|
|
for (auto i = str.begin(), end = str.end(); i != end; ++i) {
|
2017-08-06 23:28:21 -07:00
|
|
|
// pass unreserved characters to output
|
|
|
|
|
if (std::isalnum(*i) || *i == '-' || *i == '_' || *i == '.' || *i == '~') {
|
|
|
|
|
buf.push_back(*i);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// encode space to +
|
|
|
|
|
if (spacePlus && *i == ' ') {
|
|
|
|
|
buf.push_back('+');
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convert others to %xx
|
|
|
|
|
buf.push_back('%');
|
|
|
|
|
buf.push_back(hexLut[((*i) >> 4) & 0x0f]);
|
|
|
|
|
buf.push_back(hexLut[(*i) & 0x0f]);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
return {buf.data(), buf.size()};
|
2020-06-27 10:47:41 -07:00
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
HttpQueryMap::HttpQueryMap(std::string_view query) {
|
|
|
|
|
SmallVector<std::string_view, 16> queryElems;
|
|
|
|
|
split(query, queryElems, '&', 100, false);
|
2020-06-27 10:47:41 -07:00
|
|
|
for (auto elem : queryElems) {
|
2021-06-06 16:13:58 -07:00
|
|
|
auto [nameEsc, valueEsc] = split(elem, '=');
|
|
|
|
|
SmallString<64> nameBuf;
|
2020-06-27 10:47:41 -07:00
|
|
|
bool err = false;
|
|
|
|
|
auto name = wpi::UnescapeURI(nameEsc, nameBuf, &err);
|
|
|
|
|
// note: ignores duplicates
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!err) {
|
|
|
|
|
m_elems.try_emplace(name, valueEsc);
|
|
|
|
|
}
|
2020-06-27 10:47:41 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::optional<std::string_view> HttpQueryMap::Get(
|
|
|
|
|
std::string_view name, wpi::SmallVectorImpl<char>& buf) const {
|
2020-06-27 10:47:41 -07:00
|
|
|
auto it = m_elems.find(name);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (it == m_elems.end()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
2020-06-27 10:47:41 -07:00
|
|
|
bool err = false;
|
|
|
|
|
auto val = wpi::UnescapeURI(it->second, buf, &err);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (err) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
2020-06-27 10:47:41 -07:00
|
|
|
return val;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
HttpPath::HttpPath(std::string_view path) {
|
2020-06-27 10:47:41 -07:00
|
|
|
// special-case root path to be a single empty element
|
|
|
|
|
if (path == "/") {
|
|
|
|
|
m_pathEnds.emplace_back(0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
wpi::SmallVector<std::string_view, 16> pathElems;
|
|
|
|
|
split(path, pathElems, '/', 100, false);
|
2020-06-27 10:47:41 -07:00
|
|
|
for (auto elem : pathElems) {
|
2021-06-06 16:13:58 -07:00
|
|
|
SmallString<64> buf;
|
2020-06-27 10:47:41 -07:00
|
|
|
bool err = false;
|
|
|
|
|
auto val = wpi::UnescapeURI(elem, buf, &err);
|
|
|
|
|
if (err) {
|
|
|
|
|
m_pathEnds.clear();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_pathBuf += val;
|
|
|
|
|
m_pathEnds.emplace_back(m_pathBuf.size());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
bool HttpPath::startswith(size_t start,
|
2021-06-06 19:51:14 -07:00
|
|
|
span<const std::string_view> match) const {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (m_pathEnds.size() < (start + match.size())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-06-27 10:47:41 -07:00
|
|
|
bool first = start == 0;
|
|
|
|
|
auto p = m_pathEnds.begin() + start;
|
|
|
|
|
for (auto m : match) {
|
2021-06-06 16:13:58 -07:00
|
|
|
auto val = slice(m_pathBuf, first ? 0 : *(p - 1), *p);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (val != m) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-06-27 10:47:41 -07:00
|
|
|
first = false;
|
|
|
|
|
++p;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view HttpPath::operator[](size_t n) const {
|
|
|
|
|
return slice(m_pathBuf, n == 0 ? 0 : m_pathEnds[n - 1], m_pathEnds[n]);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 23:33:19 -07:00
|
|
|
bool ParseHttpHeaders(raw_istream& is, SmallVectorImpl<char>* contentType,
|
|
|
|
|
SmallVectorImpl<char>* contentLength) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (contentType) {
|
|
|
|
|
contentType->clear();
|
|
|
|
|
}
|
|
|
|
|
if (contentLength) {
|
|
|
|
|
contentLength->clear();
|
|
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
bool inContentType = false;
|
|
|
|
|
bool inContentLength = false;
|
2018-04-29 23:33:19 -07:00
|
|
|
SmallString<64> lineBuf;
|
2017-08-06 23:28:21 -07:00
|
|
|
for (;;) {
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view line = rtrim(is.getline(lineBuf, 1024));
|
2020-12-28 12:58:06 -08:00
|
|
|
if (is.has_error()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (line.empty()) {
|
|
|
|
|
return true; // empty line signals end of headers
|
|
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// header fields start at the beginning of the line
|
|
|
|
|
if (!std::isspace(line[0])) {
|
|
|
|
|
inContentType = false;
|
|
|
|
|
inContentLength = false;
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view field;
|
|
|
|
|
std::tie(field, line) = split(line, ':');
|
|
|
|
|
field = rtrim(field);
|
|
|
|
|
if (equals_lower(field, "content-type")) {
|
2017-08-06 23:28:21 -07:00
|
|
|
inContentType = true;
|
2021-06-06 16:13:58 -07:00
|
|
|
} else if (equals_lower(field, "content-length")) {
|
2017-08-06 23:28:21 -07:00
|
|
|
inContentLength = true;
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2017-08-06 23:28:21 -07:00
|
|
|
continue; // ignore other fields
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// collapse whitespace
|
2021-06-06 16:13:58 -07:00
|
|
|
line = ltrim(line);
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// save field data
|
2020-12-28 12:58:06 -08:00
|
|
|
if (inContentType && contentType) {
|
2017-08-06 23:28:21 -07:00
|
|
|
contentType->append(line.begin(), line.end());
|
2020-12-28 12:58:06 -08:00
|
|
|
} else if (inContentLength && contentLength) {
|
2017-08-06 23:28:21 -07:00
|
|
|
contentLength->append(line.begin(), line.end());
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
bool FindMultipartBoundary(raw_istream& is, std::string_view boundary,
|
2017-08-06 23:28:21 -07:00
|
|
|
std::string* saveBuf) {
|
2018-04-29 23:33:19 -07:00
|
|
|
SmallString<64> searchBuf;
|
2017-08-06 23:28:21 -07:00
|
|
|
searchBuf.resize(boundary.size() + 2);
|
|
|
|
|
size_t searchPos = 0;
|
|
|
|
|
|
|
|
|
|
// Per the spec, the --boundary should be preceded by \r\n, so do a first
|
|
|
|
|
// pass of 1-byte reads to throw those away (common case) and keep the
|
|
|
|
|
// last non-\r\n character in searchBuf.
|
|
|
|
|
if (!saveBuf) {
|
|
|
|
|
do {
|
|
|
|
|
is.read(searchBuf.data(), 1);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (is.has_error()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
} while (searchBuf[0] == '\r' || searchBuf[0] == '\n');
|
|
|
|
|
searchPos = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Look for --boundary. Read boundarysize+2 bytes at a time
|
|
|
|
|
// during the search to speed up the reads, then fast-scan for -,
|
|
|
|
|
// and only then match the entire boundary. This will be slow if
|
|
|
|
|
// there's a bunch of continuous -'s in the output, but that's unlikely.
|
|
|
|
|
for (;;) {
|
|
|
|
|
is.read(searchBuf.data() + searchPos, searchBuf.size() - searchPos);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (is.has_error()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// Did we find the boundary?
|
|
|
|
|
if (searchBuf[0] == '-' && searchBuf[1] == '-' &&
|
2020-12-28 12:58:06 -08:00
|
|
|
searchBuf.substr(2) == boundary) {
|
2017-08-06 23:28:21 -07:00
|
|
|
return true;
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// Fast-scan for '-'
|
|
|
|
|
size_t pos = searchBuf.find('-', searchBuf[0] == '-' ? 1 : 0);
|
2021-06-06 16:13:58 -07:00
|
|
|
if (pos == std::string_view::npos) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (saveBuf) {
|
|
|
|
|
saveBuf->append(searchBuf.data(), searchBuf.size());
|
|
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
} else {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (saveBuf) {
|
|
|
|
|
saveBuf->append(searchBuf.data(), pos);
|
|
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// move '-' and following to start of buffer (next read will fill)
|
|
|
|
|
std::memmove(searchBuf.data(), searchBuf.data() + pos,
|
|
|
|
|
searchBuf.size() - pos);
|
|
|
|
|
searchPos = searchBuf.size() - pos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
HttpLocation::HttpLocation(std::string_view url_, bool* error,
|
2017-08-06 23:28:21 -07:00
|
|
|
std::string* errorMsg)
|
2021-06-06 16:13:58 -07:00
|
|
|
: url{url_} {
|
2017-08-06 23:28:21 -07:00
|
|
|
// Split apart into components
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view query{url};
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// scheme:
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view scheme;
|
|
|
|
|
std::tie(scheme, query) = split(query, ':');
|
|
|
|
|
if (!equals_lower(scheme, "http")) {
|
2017-08-06 23:28:21 -07:00
|
|
|
*errorMsg = "only supports http URLs";
|
|
|
|
|
*error = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "//"
|
2021-06-06 16:13:58 -07:00
|
|
|
if (!starts_with(query, "//")) {
|
2017-08-06 23:28:21 -07:00
|
|
|
*errorMsg = "expected http://...";
|
|
|
|
|
*error = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
query.remove_prefix(2);
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// user:password@host:port/
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view authority;
|
|
|
|
|
std::tie(authority, query) = split(query, '/');
|
2017-08-06 23:28:21 -07:00
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
auto [userpass, hostport] = split(authority, '@');
|
2017-08-06 23:28:21 -07:00
|
|
|
// split leaves the RHS empty if the split char isn't present...
|
|
|
|
|
if (hostport.empty()) {
|
|
|
|
|
hostport = userpass;
|
2021-06-06 16:13:58 -07:00
|
|
|
userpass = {};
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!userpass.empty()) {
|
2021-06-06 16:13:58 -07:00
|
|
|
auto [rawUser, rawPassword] = split(userpass, ':');
|
2018-04-29 23:33:19 -07:00
|
|
|
SmallString<64> userBuf, passBuf;
|
2017-08-06 23:28:21 -07:00
|
|
|
user = UnescapeURI(rawUser, userBuf, error);
|
|
|
|
|
if (*error) {
|
2021-06-06 16:13:58 -07:00
|
|
|
*errorMsg = fmt::format("could not unescape user \"{}\"", rawUser);
|
2017-08-06 23:28:21 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
password = UnescapeURI(rawPassword, passBuf, error);
|
|
|
|
|
if (*error) {
|
2021-06-06 16:13:58 -07:00
|
|
|
*errorMsg =
|
|
|
|
|
fmt::format("could not unescape password \"{}\"", rawPassword);
|
2017-08-06 23:28:21 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view portStr;
|
|
|
|
|
std::tie(host, portStr) = rsplit(hostport, ':');
|
2017-08-06 23:28:21 -07:00
|
|
|
if (host.empty()) {
|
|
|
|
|
*errorMsg = "host is empty";
|
|
|
|
|
*error = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (portStr.empty()) {
|
|
|
|
|
port = 80;
|
2021-06-06 16:13:58 -07:00
|
|
|
} else if (auto p = parse_integer<int>(portStr, 10)) {
|
|
|
|
|
port = p.value();
|
|
|
|
|
} else {
|
|
|
|
|
*errorMsg = fmt::format("port \"{}\" is not an integer", portStr);
|
2017-08-06 23:28:21 -07:00
|
|
|
*error = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// path?query#fragment
|
2021-06-06 16:13:58 -07:00
|
|
|
std::tie(query, fragment) = split(query, '#');
|
|
|
|
|
std::tie(path, query) = split(query, '?');
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// Split query string into parameters
|
|
|
|
|
while (!query.empty()) {
|
|
|
|
|
// split out next param and value
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view rawParam, rawValue;
|
|
|
|
|
std::tie(rawParam, query) = split(query, '&');
|
2020-12-28 12:58:06 -08:00
|
|
|
if (rawParam.empty()) {
|
|
|
|
|
continue; // ignore "&&"
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
std::tie(rawParam, rawValue) = split(rawParam, '=');
|
2017-08-06 23:28:21 -07:00
|
|
|
|
|
|
|
|
// unescape param
|
|
|
|
|
*error = false;
|
2018-04-29 23:33:19 -07:00
|
|
|
SmallString<64> paramBuf;
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view param = UnescapeURI(rawParam, paramBuf, error);
|
2017-08-06 23:28:21 -07:00
|
|
|
if (*error) {
|
2021-06-06 16:13:58 -07:00
|
|
|
*errorMsg = fmt::format("could not unescape parameter \"{}\"", rawParam);
|
2017-08-06 23:28:21 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unescape value
|
2018-04-29 23:33:19 -07:00
|
|
|
SmallString<64> valueBuf;
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view value = UnescapeURI(rawValue, valueBuf, error);
|
2017-08-06 23:28:21 -07:00
|
|
|
if (*error) {
|
2021-06-06 16:13:58 -07:00
|
|
|
*errorMsg = fmt::format("could not unescape value \"{}\"", rawValue);
|
2017-08-06 23:28:21 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params.emplace_back(std::make_pair(param, value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*error = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpRequest::SetAuth(const HttpLocation& loc) {
|
|
|
|
|
if (!loc.user.empty()) {
|
2018-04-29 23:33:19 -07:00
|
|
|
SmallString<64> userpass;
|
2017-08-06 23:28:21 -07:00
|
|
|
userpass += loc.user;
|
|
|
|
|
userpass += ':';
|
|
|
|
|
userpass += loc.password;
|
2021-06-06 16:13:58 -07:00
|
|
|
Base64Encode(userpass.str(), &auth);
|
2017-08-06 23:28:21 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HttpConnection::Handshake(const HttpRequest& request,
|
|
|
|
|
std::string* warnMsg) {
|
|
|
|
|
// send GET request
|
|
|
|
|
os << "GET /" << request.path << " HTTP/1.1\r\n";
|
|
|
|
|
os << "Host: " << request.host << "\r\n";
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!request.auth.empty()) {
|
2017-08-06 23:28:21 -07:00
|
|
|
os << "Authorization: Basic " << request.auth << "\r\n";
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2017-08-06 23:28:21 -07:00
|
|
|
os << "\r\n";
|
|
|
|
|
os.flush();
|
|
|
|
|
|
|
|
|
|
// read first line of response
|
2018-04-29 23:33:19 -07:00
|
|
|
SmallString<64> lineBuf;
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view line = rtrim(is.getline(lineBuf, 1024));
|
2017-08-13 00:56:35 -07:00
|
|
|
if (is.has_error()) {
|
2017-08-06 23:28:21 -07:00
|
|
|
*warnMsg = "disconnected before response";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// see if we got a HTTP 200 response
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view httpver, code, codeText;
|
|
|
|
|
std::tie(httpver, line) = split(line, ' ');
|
|
|
|
|
std::tie(code, codeText) = split(line, ' ');
|
|
|
|
|
if (!starts_with(httpver, "HTTP")) {
|
2017-08-06 23:28:21 -07:00
|
|
|
*warnMsg = "did not receive HTTP response";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (code != "200") {
|
2021-06-06 16:13:58 -07:00
|
|
|
*warnMsg = fmt::format("received {} {} response", code, codeText);
|
2017-08-06 23:28:21 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse headers
|
|
|
|
|
if (!ParseHttpHeaders(is, &contentType, &contentLength)) {
|
|
|
|
|
*warnMsg = "disconnected during headers";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
void HttpMultipartScanner::SetBoundary(std::string_view boundary) {
|
2018-08-20 22:00:54 -07:00
|
|
|
m_boundaryWith = "\n--";
|
|
|
|
|
m_boundaryWith += boundary;
|
|
|
|
|
m_boundaryWithout = "\n";
|
|
|
|
|
m_boundaryWithout += boundary;
|
|
|
|
|
m_dashes = kUnknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpMultipartScanner::Reset(bool saveSkipped) {
|
|
|
|
|
m_saveSkipped = saveSkipped;
|
|
|
|
|
m_state = kBoundary;
|
|
|
|
|
m_posWith = 0;
|
|
|
|
|
m_posWithout = 0;
|
|
|
|
|
m_buf.resize(0);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::string_view HttpMultipartScanner::Execute(std::string_view in) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (m_state == kDone) {
|
|
|
|
|
Reset(m_saveSkipped);
|
|
|
|
|
}
|
|
|
|
|
if (m_saveSkipped) {
|
|
|
|
|
m_buf += in;
|
|
|
|
|
}
|
2018-08-20 22:00:54 -07:00
|
|
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
if (m_state == kBoundary) {
|
|
|
|
|
for (char ch : in) {
|
|
|
|
|
++pos;
|
|
|
|
|
if (m_dashes != kWithout) {
|
|
|
|
|
if (ch == m_boundaryWith[m_posWith]) {
|
|
|
|
|
++m_posWith;
|
|
|
|
|
if (m_posWith == m_boundaryWith.size()) {
|
|
|
|
|
// Found the boundary; transition to padding
|
|
|
|
|
m_state = kPadding;
|
|
|
|
|
m_dashes = kWith; // no longer accept plain 'boundary'
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (ch == m_boundaryWith[0]) {
|
|
|
|
|
m_posWith = 1;
|
|
|
|
|
} else {
|
|
|
|
|
m_posWith = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_dashes != kWith) {
|
|
|
|
|
if (ch == m_boundaryWithout[m_posWithout]) {
|
|
|
|
|
++m_posWithout;
|
|
|
|
|
if (m_posWithout == m_boundaryWithout.size()) {
|
|
|
|
|
// Found the boundary; transition to padding
|
|
|
|
|
m_state = kPadding;
|
|
|
|
|
m_dashes = kWithout; // no longer accept '--boundary'
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (ch == m_boundaryWithout[0]) {
|
|
|
|
|
m_posWithout = 1;
|
|
|
|
|
} else {
|
|
|
|
|
m_posWithout = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_state == kPadding) {
|
2021-06-06 16:13:58 -07:00
|
|
|
for (char ch : drop_front(in, pos)) {
|
2018-08-20 22:00:54 -07:00
|
|
|
++pos;
|
|
|
|
|
if (ch == '\n') {
|
|
|
|
|
// Found the LF; return remaining input buffer (following it)
|
|
|
|
|
m_state = kDone;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (m_saveSkipped) {
|
|
|
|
|
m_buf.resize(m_buf.size() - in.size() + pos);
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
return drop_front(in, pos);
|
2018-08-20 22:00:54 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We consumed the entire input
|
2021-06-06 16:13:58 -07:00
|
|
|
return {};
|
2018-08-20 22:00:54 -07:00
|
|
|
}
|
|
|
|
|
|
2017-08-06 23:28:21 -07:00
|
|
|
} // namespace wpi
|