Refactor HTTP utilities.

This commit is contained in:
Peter Johnson
2016-12-02 19:15:40 -08:00
parent 9a8f66e3e5
commit c32fc57ce1
3 changed files with 181 additions and 74 deletions

100
src/HttpUtil.cpp Normal file
View File

@@ -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 <cctype>
#include "support/raw_istream.h"
#include "llvm/StringExtras.h"
namespace cs {
llvm::StringRef ReadLine(wpi::raw_istream& is, llvm::SmallVectorImpl<char>& 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<char>& 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<char>& 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

45
src/HttpUtil.h Normal file
View File

@@ -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<char>& 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<char>& 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<char>& buf,
bool spacePlus = true);
} // namespace cs
#endif // CS_HTTPUTIL_H_

View File

@@ -10,7 +10,6 @@
#include <chrono>
#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<char>& 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<char>& 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) {