diff --git a/src/HttpUtil.cpp b/src/HttpUtil.cpp new file mode 100644 index 0000000000..435dba01bc --- /dev/null +++ b/src/HttpUtil.cpp @@ -0,0 +1,100 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. 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 "HttpUtil.h" + +#include + +#include "support/raw_istream.h" +#include "llvm/StringExtras.h" + +namespace cs { + +llvm::StringRef ReadLine(wpi::raw_istream& is, llvm::SmallVectorImpl& buf, + int maxLen, bool* error) { + buf.clear(); + for (int i = 0; i < maxLen; ++i) { + char c; + is.read(c); + if (is.has_error()) { + *error = true; + return llvm::StringRef{buf.data(), buf.size()}; + } + if (c == '\r') continue; + buf.push_back(c); + if (c == '\n') break; + } + *error = false; + return llvm::StringRef{buf.data(), buf.size()}; +} + +llvm::StringRef UnescapeURI(llvm::StringRef str, + llvm::SmallVectorImpl& buf, bool* error) { + buf.clear(); + for (auto i = str.begin(), end = str.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 llvm::StringRef{}; + } + + // replace %xx with the corresponding character + unsigned val1 = llvm::hexDigitValue(*++i); + if (val1 == -1U) { + *error = true; + return llvm::StringRef{}; + } + unsigned val2 = llvm::hexDigitValue(*++i); + if (val2 == -1U) { + *error = true; + return llvm::StringRef{}; + } + buf.push_back((val1 << 4) | val2); + } + + *error = false; + return llvm::StringRef{buf.data(), buf.size()}; +} + +llvm::StringRef EscapeURI(llvm::StringRef str, llvm::SmallVectorImpl& buf, + bool spacePlus) { + static const char *const hexLut = "0123456789ABCDEF"; + + buf.clear(); + for (auto i = str.begin(), end = str.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 llvm::StringRef{buf.data(), buf.size()}; +} + +} // namespace cs diff --git a/src/HttpUtil.h b/src/HttpUtil.h new file mode 100644 index 0000000000..cd3faf0c19 --- /dev/null +++ b/src/HttpUtil.h @@ -0,0 +1,45 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016. 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. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CS_HTTPUTIL_H_ +#define CS_HTTPUTIL_H_ + +#include "llvm/SmallVector.h" +#include "llvm/StringRef.h" + +namespace wpi { +class raw_istream; +} + +namespace cs { + +// Read a line from an input stream (up to a maximum length). +// The returned buffer will contain the trailing \n (unless the maximum length +// was reached). \r's are stripped from the buffer. +// @param buf Buffer for output +// @param error Set to true if an error occurred +// @return Line +llvm::StringRef ReadLine(wpi::raw_istream& is, llvm::SmallVectorImpl& buf, + int maxLen, bool* error); + +// Unescape a %xx-encoded URI. +// @param buf Buffer for output +// @param error Set to true if an error occurred +// @return Escaped string +llvm::StringRef UnescapeURI(llvm::StringRef str, + llvm::SmallVectorImpl& buf, bool* error); + +// Escape a string with %xx-encoding. +// @param buf Buffer for output +// @param spacePlus If true, encodes spaces to '+' rather than "%20" +// @return Escaped string +llvm::StringRef EscapeURI(llvm::StringRef str, llvm::SmallVectorImpl& buf, + bool spacePlus = true); + +} // namespace cs + +#endif // CS_HTTPUTIL_H_ diff --git a/src/MjpegServerImpl.cpp b/src/MjpegServerImpl.cpp index 2ce5f4403d..c753edc8f3 100644 --- a/src/MjpegServerImpl.cpp +++ b/src/MjpegServerImpl.cpp @@ -10,7 +10,6 @@ #include #include "llvm/SmallString.h" -#include "llvm/StringExtras.h" #include "support/raw_socket_istream.h" #include "support/raw_socket_ostream.h" #include "tcpsockets/TCPAcceptor.h" @@ -18,6 +17,7 @@ #include "c_util.h" #include "cscore_cpp.h" #include "Handle.h" +#include "HttpUtil.h" #include "Log.h" #include "Notifier.h" #include "SourceImpl.h" @@ -166,50 +166,6 @@ static void SendError(llvm::raw_ostream& os, int code, os << baseMessage << "\r\n" << message; } -// Read a line from an input stream (up to a maximum length). -// The returned buffer will contain the trailing \n (unless the maximum length -// was reached). -static bool ReadLine(wpi::raw_istream& is, llvm::SmallVectorImpl& buffer, - int maxLen) { - buffer.clear(); - for (int i = 0; i < maxLen; ++i) { - char c; - is.read(c); - if (is.has_error()) return false; - if (c == '\r') continue; - buffer.push_back(c); - if (c == '\n') break; - } - return true; -} - -// Unescape a %xx-encoded URI. Returns false on error. -static bool UnescapeURI(llvm::StringRef str, llvm::SmallVectorImpl& out) { - for (auto i = str.begin(), end = str.end(); i != end; ++i) { - // pass non-escaped characters to output - if (*i != '%') { - // decode + to space - if (*i == '+') - out.push_back(' '); - else - out.push_back(*i); - continue; - } - - // are there enough characters left? - if (i + 2 >= end) return false; - - // replace %xx with the corresponding character - unsigned val1 = llvm::hexDigitValue(*++i); - if (val1 == -1U) return false; - unsigned val2 = llvm::hexDigitValue(*++i); - if (val2 == -1U) return false; - out.push_back((val1 << 4) | val2); - } - - return true; -} - // Perform a command specified by HTTP GET parameters. bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os, SourceImpl& source, @@ -229,8 +185,10 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os, << "\""); // unescape param - llvm::SmallString<64> param; - if (!UnescapeURI(rawParam, param)) { + bool error = false; + llvm::SmallString<64> paramBuf; + llvm::StringRef param = UnescapeURI(rawParam, paramBuf, &error); + if (error) { llvm::SmallString<128> error; llvm::raw_svector_ostream oss{error}; oss << "could not unescape parameter \"" << rawParam << "\""; @@ -240,8 +198,9 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os, } // unescape value - llvm::SmallString<64> value; - if (!UnescapeURI(rawValue, value)) { + llvm::SmallString<64> valueBuf; + llvm::StringRef value = UnescapeURI(rawValue, valueBuf, &error); + if (error) { llvm::SmallString<128> error; llvm::raw_svector_ostream oss{error}; oss << "could not unescape value \"" << rawValue << "\""; @@ -254,7 +213,7 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os, // rather than as properties if (param == "resolution") { llvm::StringRef widthStr, heightStr; - std::tie(widthStr, heightStr) = value.str().split('x'); + std::tie(widthStr, heightStr) = value.split('x'); int width, height; if (widthStr.getAsInteger(10, width)) { response << param << ": \"width is not integer\"\r\n"; @@ -280,7 +239,7 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os, if (param == "fps") { int fps; - if (value.str().getAsInteger(10, fps)) { + if (value.getAsInteger(10, fps)) { response << param << ": \"invalid integer\"\r\n"; WARNING("HTTP parameter \"" << param << "\" value \"" << value << "\" is not an integer"); @@ -315,7 +274,7 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(llvm::raw_ostream& os, case CS_PROP_INTEGER: case CS_PROP_ENUM: { int val; - if (value.str().getAsInteger(10, val)) { + if (value.getAsInteger(10, val)) { response << param << ": \"invalid integer\"\r\n"; WARNING("HTTP parameter \"" << param << "\" value \"" << value << "\" is not an integer"); @@ -551,8 +510,10 @@ void MjpegServerImpl::ConnThread::ProcessRequest() { wpi::raw_socket_ostream os{*m_stream, true}; // Read the request string from the stream - llvm::SmallString<128> buf; - if (!ReadLine(is, buf, 4096)) { + bool error = false; + llvm::SmallString<128> reqBuf; + llvm::StringRef req = ReadLine(is, reqBuf, 4096, &error); + if (error) { DEBUG("HTTP error getting request string"); return; } @@ -561,33 +522,33 @@ void MjpegServerImpl::ConnThread::ProcessRequest() { llvm::StringRef parameters; size_t pos; - DEBUG("HTTP request: '" << buf << "'\n"); + DEBUG("HTTP request: '" << req << "'\n"); // Determine request kind. Most of these are for mjpgstreamer // compatibility, others are for Axis camera compatibility. - if ((pos = buf.find("POST /stream")) != llvm::StringRef::npos) { + if ((pos = req.find("POST /stream")) != llvm::StringRef::npos) { kind = kStream; - parameters = buf.substr(buf.find('?', pos + 12)).substr(1); - } else if ((pos = buf.find("GET /?action=stream")) != llvm::StringRef::npos) { + parameters = req.substr(req.find('?', pos + 12)).substr(1); + } else if ((pos = req.find("GET /?action=stream")) != llvm::StringRef::npos) { kind = kStream; - parameters = buf.substr(buf.find('&', pos + 19)).substr(1); - } else if ((pos = buf.find("GET /stream.mjpg")) != llvm::StringRef::npos) { + parameters = req.substr(req.find('&', pos + 19)).substr(1); + } else if ((pos = req.find("GET /stream.mjpg")) != llvm::StringRef::npos) { kind = kStream; - parameters = buf.substr(buf.find('?', pos + 16)).substr(1); - } else if (buf.find("GET /settings") != llvm::StringRef::npos && - buf.find(".json") != llvm::StringRef::npos) { + parameters = req.substr(req.find('?', pos + 16)).substr(1); + } else if (req.find("GET /settings") != llvm::StringRef::npos && + req.find(".json") != llvm::StringRef::npos) { kind = kGetSettings; - } else if (buf.find("GET /input") != llvm::StringRef::npos && - buf.find(".json") != llvm::StringRef::npos) { + } else if (req.find("GET /input") != llvm::StringRef::npos && + req.find(".json") != llvm::StringRef::npos) { kind = kGetSettings; - } else if (buf.find("GET /output") != llvm::StringRef::npos && - buf.find(".json") != llvm::StringRef::npos) { + } else if (req.find("GET /output") != llvm::StringRef::npos && + req.find(".json") != llvm::StringRef::npos) { kind = kGetSettings; - } else if ((pos = buf.find("GET /?action=command")) != + } else if ((pos = req.find("GET /?action=command")) != llvm::StringRef::npos) { kind = kCommand; - parameters = buf.substr(buf.find('&', pos + 20)).substr(1); - } else if (buf.find("GET / ") != llvm::StringRef::npos || buf == "GET /\n") { + parameters = req.substr(req.find('&', pos + 20)).substr(1); + } else if (req.find("GET / ") != llvm::StringRef::npos || req == "GET /\n") { kind = kRootPage; } else { DEBUG("HTTP request resource not found"); @@ -604,10 +565,11 @@ void MjpegServerImpl::ConnThread::ProcessRequest() { // Read the rest of the HTTP request. // The end of the request is marked by a single, empty line - llvm::SmallString<128> buf2; - do { - if (!ReadLine(is, buf2, 4096)) return; - } while (!buf2.startswith("\n")); + llvm::SmallString<128> lineBuf; + for (;;) { + if (ReadLine(is, lineBuf, 4096, &error).startswith("\n")) break; + if (error) return; + } // Send response switch (kind) {