Squashed 'third_party/allwpilib_2019/' content from commit bd05dfa1c
Change-Id: I2b1c2250cdb9b055133780c33593292098c375b7
git-subtree-dir: third_party/allwpilib_2019
git-subtree-split: bd05dfa1c7cca74c4fac451e7b9d6a37e7b53447
diff --git a/wpiutil/src/main/native/cpp/HttpUtil.cpp b/wpiutil/src/main/native/cpp/HttpUtil.cpp
new file mode 100644
index 0000000..37fea60
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/HttpUtil.cpp
@@ -0,0 +1,410 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "wpi/HttpUtil.h"
+
+#include <cctype>
+
+#include "wpi/Base64.h"
+#include "wpi/STLExtras.h"
+#include "wpi/StringExtras.h"
+#include "wpi/TCPConnector.h"
+#include "wpi/raw_ostream.h"
+
+namespace wpi {
+
+StringRef UnescapeURI(const Twine& str, SmallVectorImpl<char>& buf,
+ bool* error) {
+ SmallString<128> strBuf;
+ StringRef strStr = str.toStringRef(strBuf);
+ buf.clear();
+ for (auto i = strStr.begin(), end = strStr.end(); i != end; ++i) {
+ // pass non-escaped characters to output
+ if (*i != '%') {
+ // decode + to space
+ if (*i == '+')
+ buf.push_back(' ');
+ else
+ buf.push_back(*i);
+ continue;
+ }
+
+ // are there enough characters left?
+ if (i + 2 >= end) {
+ *error = true;
+ return StringRef{};
+ }
+
+ // replace %xx with the corresponding character
+ unsigned val1 = hexDigitValue(*++i);
+ if (val1 == -1U) {
+ *error = true;
+ return StringRef{};
+ }
+ unsigned val2 = hexDigitValue(*++i);
+ if (val2 == -1U) {
+ *error = true;
+ return StringRef{};
+ }
+ buf.push_back((val1 << 4) | val2);
+ }
+
+ *error = false;
+ return StringRef{buf.data(), buf.size()};
+}
+
+StringRef EscapeURI(const Twine& str, SmallVectorImpl<char>& buf,
+ bool spacePlus) {
+ static const char* const hexLut = "0123456789ABCDEF";
+
+ SmallString<128> strBuf;
+ StringRef strStr = str.toStringRef(strBuf);
+ buf.clear();
+ for (auto i = strStr.begin(), end = strStr.end(); i != end; ++i) {
+ // 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]);
+ }
+
+ return StringRef{buf.data(), buf.size()};
+}
+
+bool ParseHttpHeaders(raw_istream& is, SmallVectorImpl<char>* contentType,
+ SmallVectorImpl<char>* contentLength) {
+ if (contentType) contentType->clear();
+ if (contentLength) contentLength->clear();
+
+ bool inContentType = false;
+ bool inContentLength = false;
+ SmallString<64> lineBuf;
+ for (;;) {
+ StringRef line = is.getline(lineBuf, 1024).rtrim();
+ if (is.has_error()) return false;
+ if (line.empty()) return true; // empty line signals end of headers
+
+ // header fields start at the beginning of the line
+ if (!std::isspace(line[0])) {
+ inContentType = false;
+ inContentLength = false;
+ StringRef field;
+ std::tie(field, line) = line.split(':');
+ field = field.rtrim();
+ if (field.equals_lower("content-type"))
+ inContentType = true;
+ else if (field.equals_lower("content-length"))
+ inContentLength = true;
+ else
+ continue; // ignore other fields
+ }
+
+ // collapse whitespace
+ line = line.ltrim();
+
+ // save field data
+ if (inContentType && contentType)
+ contentType->append(line.begin(), line.end());
+ else if (inContentLength && contentLength)
+ contentLength->append(line.begin(), line.end());
+ }
+}
+
+bool FindMultipartBoundary(raw_istream& is, StringRef boundary,
+ std::string* saveBuf) {
+ SmallString<64> searchBuf;
+ 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);
+ if (is.has_error()) return false;
+ } 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);
+ if (is.has_error()) return false;
+
+ // Did we find the boundary?
+ if (searchBuf[0] == '-' && searchBuf[1] == '-' &&
+ searchBuf.substr(2) == boundary)
+ return true;
+
+ // Fast-scan for '-'
+ size_t pos = searchBuf.find('-', searchBuf[0] == '-' ? 1 : 0);
+ if (pos == StringRef::npos) {
+ if (saveBuf) saveBuf->append(searchBuf.data(), searchBuf.size());
+ } else {
+ if (saveBuf) saveBuf->append(searchBuf.data(), pos);
+
+ // 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;
+ }
+ }
+}
+
+HttpLocation::HttpLocation(const Twine& url_, bool* error,
+ std::string* errorMsg)
+ : url{url_.str()} {
+ // Split apart into components
+ StringRef query{url};
+
+ // scheme:
+ StringRef scheme;
+ std::tie(scheme, query) = query.split(':');
+ if (!scheme.equals_lower("http")) {
+ *errorMsg = "only supports http URLs";
+ *error = true;
+ return;
+ }
+
+ // "//"
+ if (!query.startswith("//")) {
+ *errorMsg = "expected http://...";
+ *error = true;
+ return;
+ }
+ query = query.drop_front(2);
+
+ // user:password@host:port/
+ StringRef authority;
+ std::tie(authority, query) = query.split('/');
+
+ StringRef userpass, hostport;
+ std::tie(userpass, hostport) = authority.split('@');
+ // split leaves the RHS empty if the split char isn't present...
+ if (hostport.empty()) {
+ hostport = userpass;
+ userpass = StringRef{};
+ }
+
+ if (!userpass.empty()) {
+ StringRef rawUser, rawPassword;
+ std::tie(rawUser, rawPassword) = userpass.split(':');
+ SmallString<64> userBuf, passBuf;
+ user = UnescapeURI(rawUser, userBuf, error);
+ if (*error) {
+ raw_string_ostream oss(*errorMsg);
+ oss << "could not unescape user \"" << rawUser << "\"";
+ oss.flush();
+ return;
+ }
+ password = UnescapeURI(rawPassword, passBuf, error);
+ if (*error) {
+ raw_string_ostream oss(*errorMsg);
+ oss << "could not unescape password \"" << rawPassword << "\"";
+ oss.flush();
+ return;
+ }
+ }
+
+ StringRef portStr;
+ std::tie(host, portStr) = hostport.rsplit(':');
+ if (host.empty()) {
+ *errorMsg = "host is empty";
+ *error = true;
+ return;
+ }
+ if (portStr.empty()) {
+ port = 80;
+ } else if (portStr.getAsInteger(10, port)) {
+ raw_string_ostream oss(*errorMsg);
+ oss << "port \"" << portStr << "\" is not an integer";
+ oss.flush();
+ *error = true;
+ return;
+ }
+
+ // path?query#fragment
+ std::tie(query, fragment) = query.split('#');
+ std::tie(path, query) = query.split('?');
+
+ // Split query string into parameters
+ while (!query.empty()) {
+ // split out next param and value
+ StringRef rawParam, rawValue;
+ std::tie(rawParam, query) = query.split('&');
+ if (rawParam.empty()) continue; // ignore "&&"
+ std::tie(rawParam, rawValue) = rawParam.split('=');
+
+ // unescape param
+ *error = false;
+ SmallString<64> paramBuf;
+ StringRef param = UnescapeURI(rawParam, paramBuf, error);
+ if (*error) {
+ raw_string_ostream oss(*errorMsg);
+ oss << "could not unescape parameter \"" << rawParam << "\"";
+ oss.flush();
+ return;
+ }
+
+ // unescape value
+ SmallString<64> valueBuf;
+ StringRef value = UnescapeURI(rawValue, valueBuf, error);
+ if (*error) {
+ raw_string_ostream oss(*errorMsg);
+ oss << "could not unescape value \"" << rawValue << "\"";
+ oss.flush();
+ return;
+ }
+
+ params.emplace_back(std::make_pair(param, value));
+ }
+
+ *error = false;
+}
+
+void HttpRequest::SetAuth(const HttpLocation& loc) {
+ if (!loc.user.empty()) {
+ SmallString<64> userpass;
+ userpass += loc.user;
+ userpass += ':';
+ userpass += loc.password;
+ Base64Encode(userpass, &auth);
+ }
+}
+
+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";
+ if (!request.auth.empty())
+ os << "Authorization: Basic " << request.auth << "\r\n";
+ os << "\r\n";
+ os.flush();
+
+ // read first line of response
+ SmallString<64> lineBuf;
+ StringRef line = is.getline(lineBuf, 1024).rtrim();
+ if (is.has_error()) {
+ *warnMsg = "disconnected before response";
+ return false;
+ }
+
+ // see if we got a HTTP 200 response
+ StringRef httpver, code, codeText;
+ std::tie(httpver, line) = line.split(' ');
+ std::tie(code, codeText) = line.split(' ');
+ if (!httpver.startswith("HTTP")) {
+ *warnMsg = "did not receive HTTP response";
+ return false;
+ }
+ if (code != "200") {
+ raw_string_ostream oss(*warnMsg);
+ oss << "received " << code << " " << codeText << " response";
+ oss.flush();
+ return false;
+ }
+
+ // Parse headers
+ if (!ParseHttpHeaders(is, &contentType, &contentLength)) {
+ *warnMsg = "disconnected during headers";
+ return false;
+ }
+
+ return true;
+}
+
+void HttpMultipartScanner::SetBoundary(StringRef boundary) {
+ 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);
+}
+
+StringRef HttpMultipartScanner::Execute(StringRef in) {
+ if (m_state == kDone) Reset(m_saveSkipped);
+ if (m_saveSkipped) m_buf += in;
+
+ 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) {
+ for (char ch : in.drop_front(pos)) {
+ ++pos;
+ if (ch == '\n') {
+ // Found the LF; return remaining input buffer (following it)
+ m_state = kDone;
+ if (m_saveSkipped) m_buf.resize(m_buf.size() - in.size() + pos);
+ return in.drop_front(pos);
+ }
+ }
+ }
+
+ // We consumed the entire input
+ return StringRef{};
+}
+
+} // namespace wpi