mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 21:17:18 +03:00
move to new cspot
This commit is contained in:
232
components/spotify/cspot/bell/main/io/BellHTTPServer.cpp
Normal file
232
components/spotify/cspot/bell/main/io/BellHTTPServer.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
#include "BellHTTPServer.h"
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include "CivetServer.h"
|
||||
#include "civetweb.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
class WebSocketHandler : public CivetWebSocketHandler {
|
||||
public:
|
||||
BellHTTPServer::WSDataHandler dataHandler;
|
||||
BellHTTPServer::WSStateHandler stateHandler;
|
||||
|
||||
WebSocketHandler(BellHTTPServer::WSDataHandler dataHandler,
|
||||
BellHTTPServer::WSStateHandler stateHandler) {
|
||||
this->dataHandler = dataHandler;
|
||||
this->stateHandler = stateHandler;
|
||||
}
|
||||
|
||||
virtual bool handleConnection(CivetServer* server,
|
||||
struct mg_connection* conn) {
|
||||
this->stateHandler(conn, BellHTTPServer::WSState::CONNECTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void handleReadyState(CivetServer* server,
|
||||
struct mg_connection* conn) {
|
||||
this->stateHandler(conn, BellHTTPServer::WSState::READY);
|
||||
}
|
||||
|
||||
virtual bool handleData(CivetServer* server, struct mg_connection* conn,
|
||||
int bits, char* data, size_t data_len) {
|
||||
this->dataHandler(conn, data, data_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void handleClose(CivetServer* server, struct mg_connection* conn) {
|
||||
stateHandler(conn, BellHTTPServer::WSState::CLOSED);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::string> BellHTTPServer::Router::split(
|
||||
const std::string str, const std::string regex_str) {
|
||||
std::regex regexz(regex_str);
|
||||
return {std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
|
||||
std::sregex_token_iterator()};
|
||||
}
|
||||
|
||||
void BellHTTPServer::Router::insert(const std::string& route,
|
||||
HTTPHandler& value) {
|
||||
auto parts = split(route, "/");
|
||||
auto currentNode = &root;
|
||||
|
||||
for (int index = 0; index < parts.size(); index++) {
|
||||
auto part = parts[index];
|
||||
if (part[0] == ':') {
|
||||
currentNode->isParam = true;
|
||||
currentNode->paramName = part.substr(1);
|
||||
part = "";
|
||||
} else if (part[0] == '*') {
|
||||
currentNode->isCatchAll = true;
|
||||
currentNode->value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentNode->children.count(part)) {
|
||||
currentNode->children[part] = std::make_unique<RouterNode>();
|
||||
}
|
||||
currentNode = currentNode->children[part].get();
|
||||
}
|
||||
currentNode->value = value;
|
||||
}
|
||||
|
||||
BellHTTPServer::Router::HandlerAndParams BellHTTPServer::Router::find(
|
||||
const std::string& route) {
|
||||
auto parts = split(route, "/");
|
||||
auto currentNode = &root;
|
||||
std::unordered_map<std::string, std::string> params;
|
||||
|
||||
for (int index = 0; index < parts.size(); index++) {
|
||||
auto part = parts[index];
|
||||
|
||||
if (currentNode->children.count(part)) {
|
||||
currentNode = currentNode->children[part].get();
|
||||
} else if (currentNode->isParam) {
|
||||
params[currentNode->paramName] = part;
|
||||
if (currentNode->children.count("")) {
|
||||
currentNode = currentNode->children[""].get();
|
||||
} else {
|
||||
return {nullptr, Params()};
|
||||
}
|
||||
} else if (currentNode->isCatchAll) {
|
||||
params["**"] = '*';
|
||||
return {currentNode->value, params};
|
||||
} else {
|
||||
return {nullptr, Params()};
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNode->value != nullptr) {
|
||||
return {currentNode->value, params};
|
||||
}
|
||||
|
||||
return {nullptr, Params()};
|
||||
}
|
||||
|
||||
bool BellHTTPServer::handleGet(CivetServer* server,
|
||||
struct mg_connection* conn) {
|
||||
std::scoped_lock lock(this->responseMutex);
|
||||
auto requestInfo = mg_get_request_info(conn);
|
||||
auto handler = getRequestsRouter.find(requestInfo->local_uri);
|
||||
|
||||
if (handler.first == nullptr) {
|
||||
if (this->notFoundHandler != nullptr) {
|
||||
this->notFoundHandler(conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
mg_set_user_connection_data(conn, &handler.second);
|
||||
|
||||
try {
|
||||
auto reply = handler.first(conn);
|
||||
if (reply->body == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mg_printf(
|
||||
conn,
|
||||
"HTTP/1.1 %d OK\r\nContent-Type: "
|
||||
"%s\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n\r\n",
|
||||
reply->status, reply->headers["Content-Type"].c_str());
|
||||
mg_write(conn, reply->body, reply->bodySize);
|
||||
|
||||
return true;
|
||||
} catch (std::exception& e) {
|
||||
BELL_LOG(error, "HttpServer", "Exception occured in handler: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BellHTTPServer::handlePost(CivetServer* server,
|
||||
struct mg_connection* conn) {
|
||||
std::scoped_lock lock(this->responseMutex);
|
||||
auto requestInfo = mg_get_request_info(conn);
|
||||
auto handler = postRequestsRouter.find(requestInfo->local_uri);
|
||||
|
||||
if (handler.first == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mg_set_user_connection_data(conn, &handler.second);
|
||||
|
||||
try {
|
||||
auto reply = handler.first(conn);
|
||||
if (reply->body == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mg_printf(
|
||||
conn,
|
||||
"HTTP/1.1 %d OK\r\nContent-Type: "
|
||||
"%s\r\nAccess-Control-Allow-Origin: *\r\nConnection: close\r\n\r\n",
|
||||
reply->status, reply->headers["Content-Type"].c_str());
|
||||
mg_write(conn, reply->body, reply->bodySize);
|
||||
|
||||
return true;
|
||||
} catch (std::exception& e) {
|
||||
BELL_LOG(error, "HttpServer", "Exception occured in handler: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BellHTTPServer::BellHTTPServer(int serverPort) {
|
||||
mg_init_library(0);
|
||||
BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort);
|
||||
this->serverPort = serverPort;
|
||||
auto port = std::to_string(this->serverPort);
|
||||
const char* options[] = {"listening_ports", port.c_str(), 0};
|
||||
server = std::make_unique<CivetServer>(options);
|
||||
}
|
||||
|
||||
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
|
||||
const std::string& json, int status) {
|
||||
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
|
||||
|
||||
response->body = (uint8_t*)malloc(json.size());
|
||||
response->bodySize = json.size();
|
||||
response->headers["Content-Type"] = "application/json";
|
||||
response->status = status;
|
||||
|
||||
memcpy(response->body, json.c_str(), json.size());
|
||||
return response;
|
||||
}
|
||||
|
||||
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeEmptyResponse() {
|
||||
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
|
||||
return response;
|
||||
}
|
||||
|
||||
void BellHTTPServer::registerGet(const std::string& url,
|
||||
BellHTTPServer::HTTPHandler handler) {
|
||||
server->addHandler(url, this);
|
||||
getRequestsRouter.insert(url, handler);
|
||||
}
|
||||
|
||||
void BellHTTPServer::registerPost(const std::string& url,
|
||||
BellHTTPServer::HTTPHandler handler) {
|
||||
server->addHandler(url, this);
|
||||
postRequestsRouter.insert(url, handler);
|
||||
}
|
||||
|
||||
void BellHTTPServer::registerWS(const std::string& url,
|
||||
BellHTTPServer::WSDataHandler dataHandler,
|
||||
BellHTTPServer::WSStateHandler stateHandler) {
|
||||
server->addWebSocketHandler(url,
|
||||
new WebSocketHandler(dataHandler, stateHandler));
|
||||
}
|
||||
|
||||
void BellHTTPServer::registerNotFound(HTTPHandler handler) {
|
||||
this->notFoundHandler = handler;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> BellHTTPServer::extractParams(
|
||||
struct mg_connection* conn) {
|
||||
std::unordered_map<std::string, std::string>& params =
|
||||
*(std::unordered_map<std::string, std::string>*)
|
||||
mg_get_user_connection_data(conn);
|
||||
return params;
|
||||
}
|
||||
325
components/spotify/cspot/bell/main/io/BellTar.cpp
Normal file
325
components/spotify/cspot/bell/main/io/BellTar.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
#include "BellTar.h"
|
||||
#include <sys/stat.h>
|
||||
#include <fstream>
|
||||
|
||||
using namespace bell::BellTar;
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio> // for sprintf, snprintf and sscanf
|
||||
#include <cstdlib> // for rand
|
||||
#include <cstring> // for strlen and memset
|
||||
#include <ctime> // for time
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LOGGING
|
||||
#define LOG printf
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
#define LOG(fmt, ...) ((void)0)
|
||||
#else
|
||||
#define LOG(fmt, args...) ((void)0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const char FILL_CHAR = '\0';
|
||||
const int FILE_NAME_LENGTH = 100;
|
||||
|
||||
// From http://en.wikipedia.org/wiki/Tar_(computing)#UStar_format
|
||||
typedef enum tar_file_type {
|
||||
tar_file_type_normal = '0',
|
||||
tar_file_type_hard_link = '1',
|
||||
tar_file_type_soft_link = '2',
|
||||
tar_file_type_directory = '5'
|
||||
} tar_file_type_t;
|
||||
|
||||
struct tar_header {
|
||||
char name[FILE_NAME_LENGTH]; // file name
|
||||
char mode[8]; // file mode
|
||||
char uid[8]; // Owner's numeric user ID
|
||||
char gid[8]; // Group's numeric user ID
|
||||
char size[12]; // File size in bytes (octal base)
|
||||
char mtime[12]; // Last modification time in
|
||||
// numeric Unix time format (octal)
|
||||
char checksum[8]; // Checksum for header record
|
||||
char typeflag[1]; // file type, see tar_file_type_t
|
||||
char linkname[100]; // Name of linked file
|
||||
char magic[6]; // UStar indicator "ustar"
|
||||
char version[2]; // UStar version "00"
|
||||
char uname[32]; // Owner user name
|
||||
char gname[32]; // Owner group name
|
||||
char devmajor[8]; // Device major number
|
||||
char devminor[8]; // Device minor number
|
||||
char prefix[155]; // Filename prefix
|
||||
char pad[12]; // padding
|
||||
};
|
||||
|
||||
void header_set_metadata(tar_header* header) {
|
||||
std::memset(header, 0, sizeof(tar_header));
|
||||
|
||||
std::sprintf(header->magic, "ustar");
|
||||
std::sprintf(header->mtime, "%011lo", (unsigned long) std::time(NULL));
|
||||
std::sprintf(header->mode, "%07o", 0644);
|
||||
std::sprintf(header->uname, "unkown"); // ... a bit random
|
||||
std::sprintf(header->gname, "users");
|
||||
header->typeflag[0] = 0; // always just a normal file
|
||||
}
|
||||
|
||||
/* From Wikipedia: The checksum is calculated by taking the sum of the
|
||||
* unsigned byte values of the header record with the eight checksum
|
||||
* bytes taken to be ascii spaces. */
|
||||
void header_set_checksum(tar_header* header) {
|
||||
unsigned int sum = 0;
|
||||
|
||||
char* pointer = (char*)header;
|
||||
char* end = pointer + sizeof(tar_header);
|
||||
|
||||
// Iterate over header struct until we are at checksum field.
|
||||
while (pointer < header->checksum) {
|
||||
sum += *pointer & 0xff;
|
||||
pointer++;
|
||||
}
|
||||
|
||||
// ... then add eight 'ascii spaces' ...
|
||||
sum += ' ' * 8;
|
||||
pointer += 8;
|
||||
|
||||
// ... and go until the end.
|
||||
while (pointer < end) {
|
||||
sum += *pointer & 0xff;
|
||||
pointer++;
|
||||
}
|
||||
|
||||
std::sprintf(header->checksum, "%06o", sum);
|
||||
}
|
||||
|
||||
void header_set_filetype(tar_header* header, tar_file_type_t file_type) {
|
||||
header->typeflag[0] = file_type;
|
||||
}
|
||||
|
||||
tar_file_type_t header_get_filetype(tar_header* header) {
|
||||
return tar_file_type_t(header->typeflag[0]);
|
||||
}
|
||||
|
||||
void header_set_filesize(tar_header* header, file_size_t file_size) {
|
||||
std::sprintf(header->size, "%011llo", file_size);
|
||||
}
|
||||
|
||||
file_size_t header_get_filesize(tar_header* header) {
|
||||
file_size_t file_size;
|
||||
std::sscanf(header->size, "%011llo", &file_size);
|
||||
return file_size;
|
||||
}
|
||||
|
||||
void header_set_filename(tar_header* header, const char* file_name) {
|
||||
size_t len = std::strlen(file_name);
|
||||
|
||||
// len > 0 also ensures that the header does not start with \0
|
||||
if (len == 0 || len >= FILE_NAME_LENGTH) {
|
||||
LOG("Invalid file name for tar: %s\n", file_name);
|
||||
std::sprintf(header->name, "INVALID_%d", std::rand());
|
||||
} else {
|
||||
std::sprintf(header->name, "%s", file_name);
|
||||
}
|
||||
}
|
||||
|
||||
std::string header_get_filename(tar_header* header) {
|
||||
return std::string(header->name);
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
/* Every file in a tar file starts with the tar header */
|
||||
void _write_header(std::ostream& dst, const char* file_name,
|
||||
file_size_t file_size,
|
||||
tar_file_type_t file_type = tar_file_type_normal) {
|
||||
tar_header header;
|
||||
header_set_metadata(&header);
|
||||
header_set_filename(&header, file_name);
|
||||
header_set_filesize(&header, file_size);
|
||||
header_set_filetype(&header, file_type);
|
||||
header_set_checksum(&header);
|
||||
|
||||
dst.write((const char*)&header, sizeof(tar_header));
|
||||
}
|
||||
|
||||
void _read_header(std::istream& inp, tar_header* header) {
|
||||
inp.read((char*)header, sizeof(tar_header));
|
||||
}
|
||||
|
||||
/* The length of the data after the header must be rounded up to a
|
||||
multiple of 512 bytes, the length of the header. */
|
||||
void _fill(std::ostream& dst, unsigned long file_size) {
|
||||
while (file_size % sizeof(tar_header) != 0) {
|
||||
dst.put(FILL_CHAR);
|
||||
file_size++;
|
||||
}
|
||||
}
|
||||
bool _check_if_header_is_next(std::istream& inp) {
|
||||
if (inp.eof() || inp.peek() == EOF) {
|
||||
LOG("Can not read next file info, istream at EOF.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inp.peek() == FILL_CHAR) {
|
||||
LOG("Can not read next file info, istream is pointing "
|
||||
"to %d, which a tar header can not start with.\n",
|
||||
FILL_CHAR);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _seek_to_next_header(std::istream& inp) {
|
||||
// Advance to start of next header or to end of file
|
||||
// Works because
|
||||
// - header never starts with FILL_CHAR
|
||||
// - at end of file, peek() returns EOF.
|
||||
// - FILL_CHAR != EOF
|
||||
while (inp.peek() == FILL_CHAR)
|
||||
inp.get();
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// writer Implementation
|
||||
////////////////////////////////////////
|
||||
|
||||
void writer::put(std::string path_in_tar, char const* const data,
|
||||
const file_size_t data_size) {
|
||||
_write_header(_dst, path_in_tar.c_str(), data_size);
|
||||
_dst.write(data, data_size);
|
||||
_fill(_dst, data_size);
|
||||
}
|
||||
|
||||
void writer::put_directory(std::string path_in_tar) {
|
||||
_write_header(_dst, path_in_tar.c_str(), 0, tar_file_type_directory);
|
||||
}
|
||||
|
||||
/* The end of an tar is marked by at least two consecutive zero-filled
|
||||
* records, a record having the size of the header. */
|
||||
void writer::finish() {
|
||||
unsigned long i = 0;
|
||||
while (i < 2 * sizeof(tar_header)) {
|
||||
_dst.put(FILL_CHAR);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// reader Implementation
|
||||
////////////////////////////////////////
|
||||
|
||||
bool reader::contains_another_file() {
|
||||
return _check_if_header_is_next(_inp);
|
||||
}
|
||||
|
||||
void reader::_cache_header() {
|
||||
if (_cached_header_data_valid)
|
||||
return;
|
||||
|
||||
assert(contains_another_file());
|
||||
|
||||
tar_header h;
|
||||
_read_header(_inp, &h);
|
||||
|
||||
_cached_header_data.file_name = header_get_filename(&h);
|
||||
_cached_header_data.file_size = header_get_filesize(&h);
|
||||
_cached_header_data.file_type = h.typeflag[0];
|
||||
_cached_header_data_valid = true;
|
||||
}
|
||||
|
||||
std::string reader::get_next_file_name() {
|
||||
_cache_header();
|
||||
return _cached_header_data.file_name;
|
||||
}
|
||||
|
||||
file_size_t reader::get_next_file_size() {
|
||||
_cache_header();
|
||||
return _cached_header_data.file_size;
|
||||
}
|
||||
|
||||
void reader::read_next_file(char* const data) {
|
||||
_inp.read(data, get_next_file_size());
|
||||
|
||||
_cached_header_data_valid = false;
|
||||
_seek_to_next_header(_inp);
|
||||
}
|
||||
|
||||
void reader::skip_next_file() {
|
||||
_inp.seekg(get_next_file_size(), std::ios::cur);
|
||||
|
||||
_cached_header_data_valid = false;
|
||||
_seek_to_next_header(_inp);
|
||||
}
|
||||
|
||||
char reader::get_next_file_type() {
|
||||
_cache_header();
|
||||
return _cached_header_data.file_type;
|
||||
}
|
||||
|
||||
int reader::number_of_files() {
|
||||
if (_number_of_files == -1) {
|
||||
std::streampos current_position = _inp.tellg();
|
||||
_inp.seekg(0, std::ios::beg);
|
||||
|
||||
_number_of_files = 0;
|
||||
while (contains_another_file()) {
|
||||
_number_of_files++;
|
||||
skip_next_file();
|
||||
}
|
||||
|
||||
_inp.seekg(current_position);
|
||||
}
|
||||
|
||||
return _number_of_files;
|
||||
}
|
||||
|
||||
void reader::extract_all_files(std::string dest_directory) {
|
||||
std::vector<uint8_t> scratch_buffer(1024);
|
||||
|
||||
while (contains_another_file()) {
|
||||
char fileType = get_next_file_type();
|
||||
auto fileName = get_next_file_name();
|
||||
|
||||
// 0 is the normal file type, skip apple's ._ files
|
||||
if (fileType == '0' && !fileName.starts_with("._")) {
|
||||
std::string path = dest_directory + "/" + fileName;
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = path.find('/', pos)) != std::string::npos) {
|
||||
std::string dir = path.substr(0, pos);
|
||||
// Create the directory if it doesn't exist
|
||||
#ifdef _WIN32
|
||||
mkdir(dir.c_str());
|
||||
#else
|
||||
mkdir(dir.c_str(), 0777);
|
||||
#endif
|
||||
pos++;
|
||||
}
|
||||
|
||||
std::ofstream out(path, std::ios::binary);
|
||||
|
||||
size_t read_size = 0;
|
||||
size_t file_size = get_next_file_size();
|
||||
|
||||
while (read_size < file_size) {
|
||||
size_t to_read = std::min(file_size - read_size, scratch_buffer.size());
|
||||
|
||||
_inp.read((char*)scratch_buffer.data(), to_read);
|
||||
|
||||
// Move the read size forward
|
||||
read_size += _inp.gcount();
|
||||
|
||||
// Write the data to the destination file
|
||||
out.write((char*)scratch_buffer.data(), _inp.gcount());
|
||||
}
|
||||
|
||||
_cached_header_data_valid = false;
|
||||
_seek_to_next_header(_inp);
|
||||
} else {
|
||||
skip_next_file();
|
||||
}
|
||||
}
|
||||
}
|
||||
72
components/spotify/cspot/bell/main/io/BinaryReader.cpp
Normal file
72
components/spotify/cspot/bell/main/io/BinaryReader.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "BinaryReader.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
bell::BinaryReader::BinaryReader(std::shared_ptr<ByteStream> stream) {
|
||||
this->stream = stream;
|
||||
}
|
||||
|
||||
size_t bell::BinaryReader::position() {
|
||||
return stream->position();
|
||||
}
|
||||
|
||||
size_t bell::BinaryReader::size() {
|
||||
return stream->size();
|
||||
}
|
||||
|
||||
void bell::BinaryReader::close() {
|
||||
stream->close();
|
||||
}
|
||||
|
||||
void bell::BinaryReader::skip(size_t pos) {
|
||||
std::vector<uint8_t> b(pos);
|
||||
stream->read(&b[0], pos);
|
||||
}
|
||||
|
||||
int32_t bell::BinaryReader::readInt() {
|
||||
uint8_t b[4];
|
||||
if (stream->read((uint8_t *) b,4) != 4)
|
||||
return 0;
|
||||
|
||||
return static_cast<int32_t>(
|
||||
(b[3]) |
|
||||
(b[2] << 8) |
|
||||
(b[1] << 16)|
|
||||
(b[0] << 24) );
|
||||
}
|
||||
|
||||
int16_t bell::BinaryReader::readShort() {
|
||||
uint8_t b[2];
|
||||
if (stream->read((uint8_t *) b,2) != 2)
|
||||
return 0;
|
||||
|
||||
return static_cast<int16_t>(
|
||||
(b[1]) |
|
||||
(b[0] << 8));
|
||||
}
|
||||
|
||||
|
||||
uint32_t bell::BinaryReader::readUInt() {
|
||||
return readInt() & 0xffffffffL;
|
||||
}
|
||||
|
||||
uint8_t bell::BinaryReader::readByte() {
|
||||
uint8_t b[1];
|
||||
if (stream->read((uint8_t *) b,1) != 1)
|
||||
return 0;
|
||||
return b[0];
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bell::BinaryReader::readBytes(size_t size) {
|
||||
std::vector<uint8_t> data(size);
|
||||
stream->read(&data[0], size);
|
||||
return data;
|
||||
}
|
||||
|
||||
long long bell::BinaryReader::readLong() {
|
||||
long high = readInt();
|
||||
long low = readInt();
|
||||
|
||||
return static_cast<long long>(
|
||||
((long long) high << 32) | low );
|
||||
}
|
||||
|
||||
152
components/spotify/cspot/bell/main/io/BinaryStream.cpp
Normal file
152
components/spotify/cspot/bell/main/io/BinaryStream.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <BinaryStream.h>
|
||||
#include <sstream>
|
||||
|
||||
using namespace bell;
|
||||
|
||||
BinaryStream::BinaryStream(std::ostream* ostr) {
|
||||
this->ostr = ostr;
|
||||
byteOrder = std::endian::native;
|
||||
}
|
||||
|
||||
BinaryStream::BinaryStream(std::istream* istr) {
|
||||
this->istr = istr;
|
||||
byteOrder = std::endian::native;
|
||||
}
|
||||
|
||||
void BinaryStream::setByteOrder(std::endian byteOrder) {
|
||||
this->byteOrder = byteOrder;
|
||||
flipBytes = byteOrder != std::endian::native;
|
||||
}
|
||||
|
||||
void BinaryStream::ensureReadable() {
|
||||
if (istr == nullptr)
|
||||
throw std::runtime_error("No input provided for binary stream");
|
||||
}
|
||||
|
||||
void BinaryStream::ensureWritable() {
|
||||
if (ostr == nullptr)
|
||||
throw std::runtime_error("No output provided for binary stream");
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator>>(int16_t& value) {
|
||||
ensureReadable();
|
||||
istr->read((char*)&value, sizeof(value));
|
||||
|
||||
if (flipBytes)
|
||||
value = swap16(value);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator>>(uint16_t& value) {
|
||||
ensureReadable();
|
||||
istr->read((char*)&value, sizeof(value));
|
||||
if (flipBytes)
|
||||
swap16(value);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator>>(int32_t& value) {
|
||||
ensureReadable();
|
||||
istr->read((char*)&value, sizeof(value));
|
||||
if (flipBytes)
|
||||
value = swap32(value);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator>>(uint32_t& value) {
|
||||
ensureReadable();
|
||||
istr->read((char*)&value, sizeof(value));
|
||||
if (flipBytes)
|
||||
value = swap32(value);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator>>(int64_t& value) {
|
||||
ensureReadable();
|
||||
istr->read((char*)&value, sizeof(value));
|
||||
if (flipBytes)
|
||||
value = swap64(value);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator>>(uint64_t& value) {
|
||||
ensureReadable();
|
||||
istr->read((char*)&value, sizeof(value));
|
||||
if (flipBytes)
|
||||
value = swap64(value);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(char value) {
|
||||
ensureWritable();
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(std::byte value) {
|
||||
ensureWritable();
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(int16_t value) {
|
||||
ensureWritable();
|
||||
if (flipBytes)
|
||||
value = swap16(value);
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(uint16_t value) {
|
||||
ensureWritable();
|
||||
if (flipBytes)
|
||||
value = swap16(value);
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(int32_t value) {
|
||||
ensureWritable();
|
||||
if (flipBytes)
|
||||
value = swap32(value);
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(uint32_t value) {
|
||||
ensureWritable();
|
||||
if (flipBytes)
|
||||
value = swap32(value);
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(int64_t value) {
|
||||
ensureWritable();
|
||||
if (flipBytes)
|
||||
value = swap64(value);
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BinaryStream& BinaryStream::operator<<(uint64_t value) {
|
||||
ensureWritable();
|
||||
if (flipBytes)
|
||||
value = swap64(value);
|
||||
ostr->write((const char*)&value, sizeof(value));
|
||||
|
||||
return *this;
|
||||
}
|
||||
172
components/spotify/cspot/bell/main/io/BufferedStream.cpp
Normal file
172
components/spotify/cspot/bell/main/io/BufferedStream.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "BufferedStream.h"
|
||||
#include <cstring>
|
||||
|
||||
BufferedStream::BufferedStream(
|
||||
const std::string &taskName,
|
||||
uint32_t bufferSize,
|
||||
uint32_t readThreshold,
|
||||
uint32_t readSize,
|
||||
uint32_t readyThreshold,
|
||||
uint32_t notReadyThreshold,
|
||||
bool waitForReady)
|
||||
: bell::Task(taskName, 4096, 5, 0) {
|
||||
this->bufferSize = bufferSize;
|
||||
this->readAt = bufferSize - readThreshold;
|
||||
this->readSize = readSize;
|
||||
this->readyThreshold = readyThreshold;
|
||||
this->notReadyThreshold = notReadyThreshold;
|
||||
this->waitForReady = waitForReady;
|
||||
this->buf = static_cast<uint8_t *>(malloc(bufferSize));
|
||||
this->bufEnd = buf + bufferSize;
|
||||
reset();
|
||||
}
|
||||
|
||||
BufferedStream::~BufferedStream() {
|
||||
this->close();
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void BufferedStream::close() {
|
||||
this->terminate = true;
|
||||
this->readSem.give(); // force a read operation
|
||||
const std::lock_guard lock(runningMutex);
|
||||
if (this->source)
|
||||
this->source->close();
|
||||
this->source = nullptr;
|
||||
}
|
||||
|
||||
void BufferedStream::reset() {
|
||||
this->bufReadPtr = this->buf;
|
||||
this->bufWritePtr = this->buf;
|
||||
this->readTotal = 0;
|
||||
this->bufferTotal = 0;
|
||||
this->readAvailable = 0;
|
||||
this->terminate = false;
|
||||
}
|
||||
|
||||
bool BufferedStream::open(const std::shared_ptr<bell::ByteStream> &stream) {
|
||||
if (this->running)
|
||||
this->close();
|
||||
reset();
|
||||
this->source = stream;
|
||||
startTask();
|
||||
return source.get();
|
||||
}
|
||||
|
||||
bool BufferedStream::open(const StreamReader &newReader, uint32_t initialOffset) {
|
||||
if (this->running)
|
||||
this->close();
|
||||
reset();
|
||||
this->reader = newReader;
|
||||
this->bufferTotal = initialOffset;
|
||||
startTask();
|
||||
return source.get();
|
||||
}
|
||||
|
||||
bool BufferedStream::isReady() const {
|
||||
return readAvailable >= readyThreshold;
|
||||
}
|
||||
|
||||
bool BufferedStream::isNotReady() const {
|
||||
return readAvailable < notReadyThreshold;
|
||||
}
|
||||
|
||||
size_t BufferedStream::skip(size_t len) {
|
||||
return read(nullptr, len);
|
||||
}
|
||||
|
||||
size_t BufferedStream::position() {
|
||||
return readTotal;
|
||||
}
|
||||
|
||||
size_t BufferedStream::size() {
|
||||
return source->size();
|
||||
}
|
||||
|
||||
uint32_t BufferedStream::lengthBetween(uint8_t *me, uint8_t *other) {
|
||||
const std::lock_guard lock(readMutex);
|
||||
if (other <= me) {
|
||||
// buf .... other ...... me ........ bufEnd
|
||||
// buf .... me/other ........ bufEnd
|
||||
return bufEnd - me;
|
||||
} else {
|
||||
// buf ........ me ........ other .... bufEnd
|
||||
return other - me;
|
||||
}
|
||||
}
|
||||
|
||||
size_t BufferedStream::read(uint8_t *dst, size_t len) {
|
||||
if (waitForReady && isNotReady()) {
|
||||
while ((source || reader) && !isReady()) {} // end waiting after termination
|
||||
}
|
||||
if (!running && !readAvailable) {
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
uint32_t read = 0;
|
||||
uint32_t toReadTotal = std::min(readAvailable.load(), static_cast<uint32_t>(len));
|
||||
while (toReadTotal > 0) {
|
||||
uint32_t toRead = std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr));
|
||||
if (dst) {
|
||||
memcpy(dst, bufReadPtr, toRead);
|
||||
dst += toRead;
|
||||
}
|
||||
readAvailable -= toRead;
|
||||
bufReadPtr += toRead;
|
||||
if (bufReadPtr >= bufEnd)
|
||||
bufReadPtr = buf;
|
||||
toReadTotal -= toRead;
|
||||
read += toRead;
|
||||
readTotal += toRead;
|
||||
}
|
||||
this->readSem.give();
|
||||
return read;
|
||||
}
|
||||
|
||||
void BufferedStream::runTask() {
|
||||
const std::lock_guard lock(runningMutex);
|
||||
running = true;
|
||||
if (!source && reader) {
|
||||
// get the initial request on the task's thread
|
||||
source = reader(this->bufferTotal);
|
||||
}
|
||||
while (!terminate) {
|
||||
if (!source)
|
||||
break;
|
||||
if (isReady()) {
|
||||
// buffer ready, wait for any read operations
|
||||
this->readSem.wait();
|
||||
}
|
||||
if (terminate)
|
||||
break;
|
||||
if (readAvailable > readAt)
|
||||
continue;
|
||||
// here, the buffer needs re-filling
|
||||
uint32_t len;
|
||||
bool wasReady = isReady();
|
||||
do {
|
||||
uint32_t toRead = std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr));
|
||||
if (!source) {
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
len = source->read(bufWritePtr, toRead);
|
||||
readAvailable += len;
|
||||
bufferTotal += len;
|
||||
bufWritePtr += len;
|
||||
if (bufWritePtr >= bufEnd) // TODO is == enough here?
|
||||
bufWritePtr = buf;
|
||||
} while (len && readSize < bufferSize - readAvailable); // loop until there's no more free space in the buffer
|
||||
if (!len && reader)
|
||||
source = reader(bufferTotal);
|
||||
else if (!len)
|
||||
terminate = true;
|
||||
// signal that buffer is ready for reading
|
||||
if (!wasReady && isReady()) {
|
||||
this->readySem.give();
|
||||
}
|
||||
}
|
||||
source = nullptr;
|
||||
reader = nullptr;
|
||||
running = false;
|
||||
}
|
||||
89
components/spotify/cspot/bell/main/io/CircularBuffer.cpp
Normal file
89
components/spotify/cspot/bell/main/io/CircularBuffer.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "CircularBuffer.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
CircularBuffer::CircularBuffer(size_t dataCapacity)
|
||||
{
|
||||
this->dataCapacity = dataCapacity;
|
||||
buffer = std::vector<uint8_t>(dataCapacity);
|
||||
this->dataSemaphore = std::make_unique<bell::WrappedSemaphore>(5);
|
||||
};
|
||||
|
||||
size_t CircularBuffer::write(const uint8_t *data, size_t bytes)
|
||||
{
|
||||
if (bytes == 0)
|
||||
return 0;
|
||||
|
||||
std::lock_guard<std::mutex> guard(bufferMutex);
|
||||
size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize);
|
||||
// Write in a single step
|
||||
if (bytesToWrite <= dataCapacity - endIndex)
|
||||
{
|
||||
memcpy(buffer.data() + endIndex, data, bytesToWrite);
|
||||
endIndex += bytesToWrite;
|
||||
if (endIndex == dataCapacity)
|
||||
endIndex = 0;
|
||||
}
|
||||
|
||||
// Write in two steps
|
||||
else {
|
||||
size_t firstChunkSize = dataCapacity - endIndex;
|
||||
memcpy(buffer.data() + endIndex, data, firstChunkSize);
|
||||
size_t secondChunkSize = bytesToWrite - firstChunkSize;
|
||||
memcpy(buffer.data(), data + firstChunkSize, secondChunkSize);
|
||||
endIndex = secondChunkSize;
|
||||
}
|
||||
|
||||
dataSize += bytesToWrite;
|
||||
|
||||
// this->dataSemaphore->give();
|
||||
return bytesToWrite;
|
||||
}
|
||||
|
||||
void CircularBuffer::emptyBuffer() {
|
||||
std::lock_guard<std::mutex> guard(bufferMutex);
|
||||
begIndex = 0;
|
||||
dataSize = 0;
|
||||
endIndex = 0;
|
||||
}
|
||||
|
||||
void CircularBuffer::emptyExcept(size_t sizeToSet) {
|
||||
std::lock_guard<std::mutex> guard(bufferMutex);
|
||||
if (sizeToSet > dataSize)
|
||||
sizeToSet = dataSize;
|
||||
dataSize = sizeToSet;
|
||||
endIndex = begIndex + sizeToSet;
|
||||
if (endIndex > dataCapacity) {
|
||||
endIndex -= dataCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
size_t CircularBuffer::read(uint8_t *data, size_t bytes)
|
||||
{
|
||||
if (bytes == 0)
|
||||
return 0;
|
||||
|
||||
std::lock_guard<std::mutex> guard(bufferMutex);
|
||||
size_t bytesToRead = std::min(bytes, dataSize);
|
||||
|
||||
// Read in a single step
|
||||
if (bytesToRead <= dataCapacity - begIndex)
|
||||
{
|
||||
memcpy(data, buffer.data() + begIndex, bytesToRead);
|
||||
begIndex += bytesToRead;
|
||||
if (begIndex == dataCapacity)
|
||||
begIndex = 0;
|
||||
}
|
||||
// Read in two steps
|
||||
else
|
||||
{
|
||||
size_t firstChunkSize = dataCapacity - begIndex;
|
||||
memcpy(data, buffer.data() + begIndex, firstChunkSize);
|
||||
size_t secondChunkSize = bytesToRead - firstChunkSize;
|
||||
memcpy(data + firstChunkSize, buffer.data(), secondChunkSize);
|
||||
begIndex = secondChunkSize;
|
||||
}
|
||||
|
||||
dataSize -= bytesToRead;
|
||||
return bytesToRead;
|
||||
}
|
||||
175
components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp
Normal file
175
components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "EncodedAudioStream.h"
|
||||
|
||||
#include <iostream>
|
||||
using namespace bell;
|
||||
|
||||
EncodedAudioStream::EncodedAudioStream() {
|
||||
bell::decodersInstance->ensureAAC();
|
||||
bell::decodersInstance->ensureMP3();
|
||||
inputBuffer = std::vector<uint8_t>(AAC_READBUF_SIZE * 4);
|
||||
outputBuffer = std::vector<short>(AAC_MAX_NCHANS * AAC_MAX_NSAMPS * 4 * 4);
|
||||
decodePtr = inputBuffer.data();
|
||||
}
|
||||
|
||||
EncodedAudioStream::~EncodedAudioStream() {
|
||||
this->innerStream->close();
|
||||
}
|
||||
|
||||
void EncodedAudioStream::openWithStream(
|
||||
std::unique_ptr<bell::ByteStream> byteStream) {
|
||||
if (this->innerStream) {
|
||||
this->innerStream->close();
|
||||
}
|
||||
this->innerStream = std::move(byteStream);
|
||||
this->guessDataFormat();
|
||||
}
|
||||
|
||||
bool EncodedAudioStream::vectorStartsWith(std::vector<uint8_t>& vec,
|
||||
std::vector<uint8_t>& start) {
|
||||
if (vec.size() < start.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < start.size(); i++) {
|
||||
if (vec[i] != start[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t EncodedAudioStream::decodeFrame(uint8_t* dst) {
|
||||
if (innerStream->size() != 0 &&
|
||||
innerStream->position() >= innerStream->size()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (this->codec) {
|
||||
case AudioCodec::AAC:
|
||||
return decodeFrameAAC(dst);
|
||||
break;
|
||||
case AudioCodec::MP3:
|
||||
return decodeFrameMp3(dst);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t EncodedAudioStream::decodeFrameMp3(uint8_t* dst) {
|
||||
size_t writtenBytes = 0;
|
||||
int bufSize = MP3_READBUF_SIZE;
|
||||
|
||||
int readBytes = innerStream->read(inputBuffer.data() + bytesInBuffer,
|
||||
bufSize - bytesInBuffer);
|
||||
if (readBytes > 0) {
|
||||
bytesInBuffer += readBytes;
|
||||
decodePtr = inputBuffer.data();
|
||||
offset = MP3FindSyncWord(inputBuffer.data(), bytesInBuffer);
|
||||
|
||||
if (offset != -1) {
|
||||
bytesInBuffer -= offset;
|
||||
decodePtr += offset;
|
||||
|
||||
int decodeStatus =
|
||||
MP3Decode(bell::decodersInstance->mp3Decoder, &decodePtr,
|
||||
&bytesInBuffer, outputBuffer.data(), 0);
|
||||
MP3GetLastFrameInfo(bell::decodersInstance->mp3Decoder, &mp3FrameInfo);
|
||||
if (decodeStatus == ERR_MP3_NONE) {
|
||||
decodedSampleRate = mp3FrameInfo.samprate;
|
||||
writtenBytes =
|
||||
(mp3FrameInfo.bitsPerSample / 8) * mp3FrameInfo.outputSamps;
|
||||
|
||||
memcpy(dst, outputBuffer.data(), writtenBytes);
|
||||
|
||||
} else {
|
||||
BELL_LOG(info, TAG, "Error in frame, moving two bytes %d",
|
||||
decodeStatus);
|
||||
decodePtr += 1;
|
||||
bytesInBuffer -= 1;
|
||||
}
|
||||
} else {
|
||||
BELL_LOG(info, TAG, "Unexpected error in data, skipping a word");
|
||||
decodePtr += 3800;
|
||||
bytesInBuffer -= 3800;
|
||||
}
|
||||
|
||||
memmove(inputBuffer.data(), decodePtr, bytesInBuffer);
|
||||
}
|
||||
return writtenBytes;
|
||||
}
|
||||
|
||||
bool EncodedAudioStream::isReadable() {
|
||||
return this->codec != AudioCodec::NONE;
|
||||
}
|
||||
|
||||
size_t EncodedAudioStream::decodeFrameAAC(uint8_t* dst) {
|
||||
size_t writtenBytes = 0;
|
||||
auto bufSize = AAC_READBUF_SIZE;
|
||||
|
||||
int readBytes = innerStream->read(inputBuffer.data() + bytesInBuffer,
|
||||
bufSize - bytesInBuffer);
|
||||
if (readBytes > 0) {
|
||||
bytesInBuffer += readBytes;
|
||||
decodePtr = inputBuffer.data();
|
||||
offset = AACFindSyncWord(inputBuffer.data(), bytesInBuffer);
|
||||
|
||||
if (offset != -1) {
|
||||
bytesInBuffer -= offset;
|
||||
decodePtr += offset;
|
||||
|
||||
int decodeStatus =
|
||||
AACDecode(bell::decodersInstance->aacDecoder, &decodePtr,
|
||||
&bytesInBuffer, outputBuffer.data());
|
||||
AACGetLastFrameInfo(bell::decodersInstance->aacDecoder, &aacFrameInfo);
|
||||
if (decodeStatus == ERR_AAC_NONE) {
|
||||
decodedSampleRate = aacFrameInfo.sampRateOut;
|
||||
writtenBytes =
|
||||
(aacFrameInfo.bitsPerSample / 8) * aacFrameInfo.outputSamps;
|
||||
|
||||
memcpy(dst, outputBuffer.data(), writtenBytes);
|
||||
|
||||
} else {
|
||||
BELL_LOG(info, TAG, "Error in frame, moving two bytes %d",
|
||||
decodeStatus);
|
||||
decodePtr += 1;
|
||||
bytesInBuffer -= 1;
|
||||
}
|
||||
} else {
|
||||
BELL_LOG(info, TAG, "Unexpected error in data, skipping a word");
|
||||
decodePtr += 3800;
|
||||
bytesInBuffer -= 3800;
|
||||
}
|
||||
|
||||
memmove(inputBuffer.data(), decodePtr, bytesInBuffer);
|
||||
}
|
||||
return writtenBytes;
|
||||
}
|
||||
|
||||
void EncodedAudioStream::guessDataFormat() {
|
||||
// Read 14 bytes from the stream
|
||||
this->innerStream->read(inputBuffer.data(), 14);
|
||||
bytesInBuffer = 14;
|
||||
|
||||
BELL_LOG(info, TAG, "No codec set, reading secret bytes");
|
||||
|
||||
if (vectorStartsWith(inputBuffer, this->mp3MagicBytesIdc) ||
|
||||
vectorStartsWith(inputBuffer, this->mp3MagicBytesUntagged)) {
|
||||
BELL_LOG(info, TAG, "Detected MP3");
|
||||
codec = AudioCodec::MP3;
|
||||
} else if (vectorStartsWith(inputBuffer, this->aacMagicBytes) ||
|
||||
vectorStartsWith(inputBuffer, this->aacMagicBytes4)) {
|
||||
BELL_LOG(info, TAG, "Detected AAC");
|
||||
codec = AudioCodec::AAC;
|
||||
}
|
||||
|
||||
if (codec == AudioCodec::NONE) {
|
||||
throw std::runtime_error("Codec not supported");
|
||||
}
|
||||
}
|
||||
|
||||
void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) {
|
||||
}
|
||||
70
components/spotify/cspot/bell/main/io/FileStream.cpp
Normal file
70
components/spotify/cspot/bell/main/io/FileStream.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "FileStream.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
FileStream::FileStream(const std::string& path, std::string read)
|
||||
{
|
||||
file = fopen(path.c_str(), "rb");
|
||||
if (file == NULL)
|
||||
{
|
||||
throw std::runtime_error("Could not open file: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
FileStream::~FileStream()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
size_t FileStream::read(uint8_t *buf, size_t nbytes)
|
||||
{
|
||||
if (file == NULL)
|
||||
{
|
||||
throw std::runtime_error("Stream is closed");
|
||||
}
|
||||
|
||||
return fread(buf, 1, nbytes, file);
|
||||
}
|
||||
|
||||
size_t FileStream::skip(size_t nbytes)
|
||||
{
|
||||
if (file == NULL)
|
||||
{
|
||||
throw std::runtime_error("Stream is closed");
|
||||
}
|
||||
|
||||
return fseek(file, nbytes, SEEK_CUR);
|
||||
}
|
||||
|
||||
size_t FileStream::position()
|
||||
{
|
||||
if (file == NULL)
|
||||
{
|
||||
throw std::runtime_error("Stream is closed");
|
||||
}
|
||||
|
||||
return ftell(file);
|
||||
}
|
||||
|
||||
size_t FileStream::size()
|
||||
{
|
||||
if (file == NULL)
|
||||
{
|
||||
throw std::runtime_error("Stream is closed");
|
||||
}
|
||||
|
||||
size_t pos = ftell(file);
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t size = ftell(file);
|
||||
fseek(file, pos, SEEK_SET);
|
||||
return size;
|
||||
}
|
||||
|
||||
void FileStream::close()
|
||||
{
|
||||
if (file != NULL)
|
||||
{
|
||||
fclose(file);
|
||||
file = NULL;
|
||||
}
|
||||
}
|
||||
155
components/spotify/cspot/bell/main/io/HTTPClient.cpp
Normal file
155
components/spotify/cspot/bell/main/io/HTTPClient.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
#include "HTTPClient.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
void HTTPClient::Response::connect(const std::string& url) {
|
||||
urlParser = bell::URLParser::parse(url);
|
||||
|
||||
// Open socket of type
|
||||
this->socketStream.open(urlParser.host, urlParser.port,
|
||||
urlParser.schema == "https");
|
||||
}
|
||||
|
||||
HTTPClient::Response::~Response() {
|
||||
if (this->socketStream.isOpen()) {
|
||||
this->socketStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPClient::Response::rawRequest(const std::string& url,
|
||||
const std::string& method,
|
||||
const std::string& content,
|
||||
Headers& headers) {
|
||||
urlParser = bell::URLParser::parse(url);
|
||||
|
||||
// Prepare a request
|
||||
const char* reqEnd = "\r\n";
|
||||
|
||||
socketStream << method << " " << urlParser.path << " HTTP/1.1" << reqEnd;
|
||||
socketStream << "Host: " << urlParser.host << ":" << urlParser.port << reqEnd;
|
||||
socketStream << "Connection: keep-alive" << reqEnd;
|
||||
socketStream << "Accept: */*" << reqEnd;
|
||||
|
||||
// Write content
|
||||
if (content.size() > 0) {
|
||||
socketStream << "Content-Length: " << content.size() << reqEnd;
|
||||
}
|
||||
|
||||
// Write headers
|
||||
for (auto& header : headers) {
|
||||
socketStream << header.first << ": " << header.second << reqEnd;
|
||||
}
|
||||
|
||||
socketStream << reqEnd;
|
||||
socketStream.flush();
|
||||
|
||||
// Parse response
|
||||
readResponseHeaders();
|
||||
}
|
||||
|
||||
void HTTPClient::Response::readResponseHeaders() {
|
||||
char *method, *path;
|
||||
const char* msgPointer;
|
||||
|
||||
size_t msgLen;
|
||||
int pret, minorVersion, status;
|
||||
|
||||
size_t prevbuflen = 0, numHeaders;
|
||||
this->httpBufferAvailable = 0;
|
||||
|
||||
while (1) {
|
||||
socketStream.getline((char*)httpBuffer.data() + httpBufferAvailable,
|
||||
httpBuffer.size() - httpBufferAvailable);
|
||||
|
||||
prevbuflen = httpBufferAvailable;
|
||||
httpBufferAvailable += socketStream.gcount();
|
||||
|
||||
// Restore delimiters
|
||||
memcpy(httpBuffer.data() + httpBufferAvailable - 2, "\r\n", 2);
|
||||
|
||||
// Parse the request
|
||||
numHeaders = sizeof(phResponseHeaders) / sizeof(phResponseHeaders[0]);
|
||||
|
||||
pret =
|
||||
phr_parse_response((const char*)httpBuffer.data(), httpBufferAvailable,
|
||||
&minorVersion, &status, &msgPointer, &msgLen,
|
||||
phResponseHeaders, &numHeaders, prevbuflen);
|
||||
|
||||
if (pret > 0) {
|
||||
break; /* successfully parsed the request */
|
||||
} else if (pret == -1)
|
||||
throw std::runtime_error("Cannot parse http response");
|
||||
|
||||
/* request is incomplete, continue the loop */
|
||||
assert(pret == -2);
|
||||
if (httpBufferAvailable == httpBuffer.size())
|
||||
throw std::runtime_error("Response too large");
|
||||
}
|
||||
|
||||
this->responseHeaders = {};
|
||||
|
||||
// Headers have benen read
|
||||
for (int headerIndex = 0; headerIndex < numHeaders; headerIndex++) {
|
||||
this->responseHeaders.push_back(
|
||||
ValueHeader{std::string(phResponseHeaders[headerIndex].name,
|
||||
phResponseHeaders[headerIndex].name_len),
|
||||
std::string(phResponseHeaders[headerIndex].value,
|
||||
phResponseHeaders[headerIndex].value_len)});
|
||||
}
|
||||
|
||||
std::string contentLengthValue = std::string(header("content-length"));
|
||||
if (contentLengthValue.size() > 0) {
|
||||
this->hasContentSize = true;
|
||||
this->contentSize = std::stoi(contentLengthValue);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPClient::Response::get(const std::string& url, Headers headers) {
|
||||
std::string method = "GET";
|
||||
return this->rawRequest(url, method, "", headers);
|
||||
}
|
||||
|
||||
size_t HTTPClient::Response::contentLength() {
|
||||
return contentSize;
|
||||
}
|
||||
|
||||
std::string_view HTTPClient::Response::header(const std::string& headerName) {
|
||||
for (auto& header : this->responseHeaders) {
|
||||
std::string headerValue = header.first;
|
||||
std::transform(headerValue.begin(), headerValue.end(), headerValue.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
if (headerName == headerValue) {
|
||||
return header.second;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t HTTPClient::Response::totalLength() {
|
||||
auto rangeHeader = header("content-range");
|
||||
|
||||
if (rangeHeader.find("/") != std::string::npos) {
|
||||
return std::stoi(
|
||||
std::string(rangeHeader.substr(rangeHeader.find("/") + 1)));
|
||||
}
|
||||
|
||||
return this->contentLength();
|
||||
}
|
||||
|
||||
void HTTPClient::Response::readRawBody() {
|
||||
if (contentSize > 0 && rawBody.size() == 0) {
|
||||
rawBody = std::vector<uint8_t>(contentSize);
|
||||
socketStream.read((char*)rawBody.data(), contentSize);
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view HTTPClient::Response::body() {
|
||||
readRawBody();
|
||||
return std::string_view((char*)rawBody.data(), rawBody.size());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> HTTPClient::Response::bytes() {
|
||||
readRawBody();
|
||||
return rawBody;
|
||||
}
|
||||
105
components/spotify/cspot/bell/main/io/SocketStream.cpp
Normal file
105
components/spotify/cspot/bell/main/io/SocketStream.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "SocketStream.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
int SocketBuffer::open(const std::string& hostname, int port, bool isSSL) {
|
||||
if (internalSocket != nullptr) { close(); }
|
||||
if (isSSL) {
|
||||
internalSocket = std::make_unique<bell::TLSSocket>();
|
||||
} else {
|
||||
internalSocket = std::make_unique<bell::TCPSocket>();
|
||||
}
|
||||
|
||||
internalSocket->open(hostname, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SocketBuffer::close() {
|
||||
if (internalSocket != nullptr && isOpen()) {
|
||||
pubsync();
|
||||
internalSocket->close();
|
||||
internalSocket = nullptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SocketBuffer::sync() {
|
||||
ssize_t bw, n = pptr() - pbase();
|
||||
while (n > 0) {
|
||||
bw = internalSocket->write(reinterpret_cast<uint8_t*>(pptr() - n), n);
|
||||
if (bw < 0) {
|
||||
setp(pptr() - n, obuf + bufLen);
|
||||
pbump(n);
|
||||
return -1;
|
||||
}
|
||||
n -= bw;
|
||||
}
|
||||
setp(obuf, obuf + bufLen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SocketBuffer::int_type SocketBuffer::underflow() {
|
||||
ssize_t br = internalSocket->read(reinterpret_cast<uint8_t*>(ibuf), bufLen);
|
||||
if (br <= 0) {
|
||||
setg(NULL, NULL, NULL);
|
||||
return traits_type::eof();
|
||||
}
|
||||
setg(ibuf, ibuf, ibuf + br);
|
||||
return traits_type::to_int_type(*ibuf);
|
||||
}
|
||||
|
||||
SocketBuffer::int_type SocketBuffer::overflow(int_type c) {
|
||||
if (sync() < 0)
|
||||
return traits_type::eof();
|
||||
if (traits_type::eq_int_type(c, traits_type::eof()))
|
||||
return traits_type::not_eof(c);
|
||||
*pptr() = traits_type::to_char_type(c);
|
||||
pbump(1);
|
||||
return c;
|
||||
}
|
||||
|
||||
std::streamsize SocketBuffer::xsgetn(char_type* __s, std::streamsize __n) {
|
||||
const std::streamsize bn = egptr() - gptr();
|
||||
if (__n <= bn) {
|
||||
traits_type::copy(__s, gptr(), __n);
|
||||
gbump(__n);
|
||||
return __n;
|
||||
}
|
||||
traits_type::copy(__s, gptr(), bn);
|
||||
setg(NULL, NULL, NULL);
|
||||
std::streamsize remain = __n - bn;
|
||||
char_type* end = __s + __n;
|
||||
ssize_t br;
|
||||
while (remain > 0) {
|
||||
br = internalSocket->read(reinterpret_cast<uint8_t*>(end - remain), remain);
|
||||
if (br <= 0)
|
||||
return (__n - remain);
|
||||
remain -= br;
|
||||
}
|
||||
return __n;
|
||||
}
|
||||
|
||||
std::streamsize SocketBuffer::xsputn(const char_type* __s,
|
||||
std::streamsize __n) {
|
||||
if (pptr() + __n <= epptr()) {
|
||||
traits_type::copy(pptr(), __s, __n);
|
||||
pbump(__n);
|
||||
return __n;
|
||||
}
|
||||
if (sync() < 0)
|
||||
return 0;
|
||||
ssize_t bw;
|
||||
std::streamsize remain = __n;
|
||||
const char_type* end = __s + __n;
|
||||
while (remain > bufLen) {
|
||||
bw = internalSocket->write((uint8_t*)(end - remain), remain);
|
||||
if (bw < 0)
|
||||
return (__n - remain);
|
||||
remain -= bw;
|
||||
}
|
||||
if (remain > 0) {
|
||||
traits_type::copy(pptr(), end - remain, remain);
|
||||
pbump(remain);
|
||||
}
|
||||
return __n;
|
||||
}
|
||||
95
components/spotify/cspot/bell/main/io/TLSSocket.cpp
Normal file
95
components/spotify/cspot/bell/main/io/TLSSocket.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "TLSSocket.h"
|
||||
#include "X509Bundle.h"
|
||||
|
||||
/**
|
||||
* Platform TLSSocket implementation for the mbedtls
|
||||
*/
|
||||
bell::TLSSocket::TLSSocket() {
|
||||
this->isClosed = false;
|
||||
mbedtls_net_init(&server_fd);
|
||||
mbedtls_ssl_init(&ssl);
|
||||
mbedtls_ssl_config_init(&conf);
|
||||
|
||||
if (bell::X509Bundle::shouldVerify()) {
|
||||
bell::X509Bundle::attach(&conf);
|
||||
}
|
||||
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
mbedtls_entropy_init(&entropy);
|
||||
|
||||
const char* pers = "euphonium";
|
||||
int ret;
|
||||
if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
|
||||
(const unsigned char*)pers, strlen(pers))) !=
|
||||
0) {
|
||||
BELL_LOG(error, "http_tls",
|
||||
"failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret);
|
||||
throw std::runtime_error("mbedtls_ctr_drbg_seed failed");
|
||||
}
|
||||
}
|
||||
|
||||
void bell::TLSSocket::open(const std::string& hostUrl, uint16_t port) {
|
||||
int ret;
|
||||
if ((ret = mbedtls_net_connect(&server_fd, hostUrl.c_str(),
|
||||
std::to_string(port).c_str(),
|
||||
MBEDTLS_NET_PROTO_TCP)) != 0) {
|
||||
BELL_LOG(error, "http_tls", "failed! connect returned %d\n", ret);
|
||||
}
|
||||
|
||||
if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
|
||||
|
||||
BELL_LOG(error, "http_tls", "failed! config returned %d\n", ret);
|
||||
throw std::runtime_error("mbedtls_ssl_config_defaults failed");
|
||||
}
|
||||
|
||||
// Only verify if the X509 bundle is present
|
||||
if (bell::X509Bundle::shouldVerify()) {
|
||||
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
} else {
|
||||
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
|
||||
mbedtls_ssl_setup(&ssl, &conf);
|
||||
|
||||
if ((ret = mbedtls_ssl_set_hostname(&ssl, hostUrl.c_str())) != 0) {
|
||||
throw std::runtime_error("mbedtls_ssl_set_hostname failed");
|
||||
}
|
||||
mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv,
|
||||
NULL);
|
||||
|
||||
while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) {
|
||||
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
BELL_LOG(error, "http_tls", "failed! config returned %d\n", ret);
|
||||
throw std::runtime_error("mbedtls_ssl_handshake error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t bell::TLSSocket::read(uint8_t* buf, size_t len) {
|
||||
return mbedtls_ssl_read(&ssl, buf, len);
|
||||
}
|
||||
|
||||
size_t bell::TLSSocket::write(uint8_t* buf, size_t len) {
|
||||
return mbedtls_ssl_write(&ssl, buf, len);
|
||||
}
|
||||
|
||||
size_t bell::TLSSocket::poll() {
|
||||
return mbedtls_ssl_get_bytes_avail(&ssl);
|
||||
}
|
||||
bool bell::TLSSocket::isOpen() {
|
||||
return !isClosed;
|
||||
}
|
||||
|
||||
void bell::TLSSocket::close() {
|
||||
if (!isClosed) {
|
||||
mbedtls_net_free(&server_fd);
|
||||
mbedtls_ssl_free(&ssl);
|
||||
mbedtls_ssl_config_free(&conf);
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
this->isClosed = true;
|
||||
}
|
||||
}
|
||||
1
components/spotify/cspot/bell/main/io/URLParser.cpp
Normal file
1
components/spotify/cspot/bell/main/io/URLParser.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "URLParser.h"
|
||||
186
components/spotify/cspot/bell/main/io/X509Bundle.cpp
Normal file
186
components/spotify/cspot/bell/main/io/X509Bundle.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "X509Bundle.h"
|
||||
|
||||
using namespace bell::X509Bundle;
|
||||
|
||||
static mbedtls_x509_crt s_dummy_crt;
|
||||
static bool s_should_verify_certs = false;
|
||||
|
||||
#ifndef MBEDTLS_PRIVATE
|
||||
#define MBEDTLS_PRIVATE(member) member
|
||||
#endif
|
||||
|
||||
int bell::X509Bundle::crtCheckCertificate(mbedtls_x509_crt* child,
|
||||
const uint8_t* pub_key_buf,
|
||||
size_t pub_key_len) {
|
||||
int ret = 0;
|
||||
mbedtls_x509_crt parent;
|
||||
const mbedtls_md_info_t* md_info;
|
||||
unsigned char hash[MBEDTLS_MD_MAX_SIZE];
|
||||
|
||||
mbedtls_x509_crt_init(&parent);
|
||||
|
||||
if ((ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf,
|
||||
pub_key_len)) != 0) {
|
||||
BELL_LOG(error, TAG, "PK parse failed with error %X", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Fast check to avoid expensive computations when not necessary
|
||||
if (!mbedtls_pk_can_do(&parent.pk, child->MBEDTLS_PRIVATE(sig_pk))) {
|
||||
BELL_LOG(error, TAG, "Simple compare failed");
|
||||
ret = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
md_info = mbedtls_md_info_from_type(child->MBEDTLS_PRIVATE(sig_md));
|
||||
if ((ret = mbedtls_md(md_info, child->tbs.p, child->tbs.len, hash)) != 0) {
|
||||
BELL_LOG(error, TAG, "Internal mbedTLS error %X", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((ret = mbedtls_pk_verify_ext(
|
||||
child->MBEDTLS_PRIVATE(sig_pk), child->MBEDTLS_PRIVATE(sig_opts),
|
||||
&parent.pk, child->MBEDTLS_PRIVATE(sig_md), hash,
|
||||
mbedtls_md_get_size(md_info), child->MBEDTLS_PRIVATE(sig).p,
|
||||
child->MBEDTLS_PRIVATE(sig).len)) != 0) {
|
||||
|
||||
BELL_LOG(error, TAG, "PK verify failed with error %X", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
cleanup:
|
||||
mbedtls_x509_crt_free(&parent);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* This callback is called for every certificate in the chain. If the chain
|
||||
* is proper each intermediate certificate is validated through its parent
|
||||
* in the x509_crt_verify_chain() function. So this callback should
|
||||
* only verify the first untrusted link in the chain is signed by the
|
||||
* root certificate in the trusted bundle
|
||||
*/
|
||||
int bell::X509Bundle::crtVerifyCallback(void* buf, mbedtls_x509_crt* crt,
|
||||
int depth, uint32_t* flags) {
|
||||
mbedtls_x509_crt* child = crt;
|
||||
|
||||
/* It's OK for a trusted cert to have a weak signature hash alg.
|
||||
as we already trust this certificate */
|
||||
uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD);
|
||||
|
||||
if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (s_crt_bundle.crts == NULL) {
|
||||
BELL_LOG(error, TAG, "No certificates in bundle");
|
||||
return MBEDTLS_ERR_X509_FATAL_ERROR;
|
||||
}
|
||||
|
||||
BELL_LOG(debug, TAG, "%d certificates in bundle", s_crt_bundle.num_certs);
|
||||
|
||||
size_t name_len = 0;
|
||||
const uint8_t* crt_name;
|
||||
|
||||
bool crt_found = false;
|
||||
int start = 0;
|
||||
int end = s_crt_bundle.num_certs - 1;
|
||||
int middle = (end - start) / 2;
|
||||
|
||||
/* Look for the certificate using binary search on subject name */
|
||||
while (start <= end) {
|
||||
name_len = s_crt_bundle.crts[middle][0] << 8 | s_crt_bundle.crts[middle][1];
|
||||
crt_name = s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET;
|
||||
|
||||
int cmp_res = memcmp(child->issuer_raw.p, crt_name, name_len);
|
||||
if (cmp_res == 0) {
|
||||
crt_found = true;
|
||||
break;
|
||||
} else if (cmp_res < 0) {
|
||||
end = middle - 1;
|
||||
} else {
|
||||
start = middle + 1;
|
||||
}
|
||||
middle = (start + end) / 2;
|
||||
}
|
||||
|
||||
int ret = MBEDTLS_ERR_X509_FATAL_ERROR;
|
||||
if (crt_found) {
|
||||
size_t key_len =
|
||||
s_crt_bundle.crts[middle][2] << 8 | s_crt_bundle.crts[middle][3];
|
||||
ret = crtCheckCertificate(
|
||||
child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len,
|
||||
key_len);
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
BELL_LOG(info, TAG, "Certificate validated");
|
||||
*flags = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
BELL_LOG(info, TAG, "Failed to verify certificate");
|
||||
return MBEDTLS_ERR_X509_FATAL_ERROR;
|
||||
}
|
||||
|
||||
/* Initialize the bundle into an array so we can do binary search for certs,
|
||||
the bundle generated by the python utility is already presorted by subject name
|
||||
*/
|
||||
void bell::X509Bundle::init(const uint8_t* x509_bundle, size_t bundle_size) {
|
||||
if (bundle_size < BUNDLE_HEADER_OFFSET + CRT_HEADER_OFFSET) {
|
||||
throw std::runtime_error("Invalid certificate bundle");
|
||||
}
|
||||
|
||||
uint16_t num_certs = (x509_bundle[0] << 8) | x509_bundle[1];
|
||||
|
||||
const uint8_t** crts =
|
||||
(const uint8_t**)calloc(num_certs, sizeof(x509_bundle));
|
||||
if (crts == NULL) {
|
||||
throw std::runtime_error("Unable to allocate memory for bundle");
|
||||
}
|
||||
|
||||
const uint8_t* cur_crt;
|
||||
/* This is the maximum region that is allowed to access */
|
||||
const uint8_t* bundle_end = x509_bundle + bundle_size;
|
||||
cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET;
|
||||
|
||||
for (int i = 0; i < num_certs; i++) {
|
||||
crts[i] = cur_crt;
|
||||
if (cur_crt + CRT_HEADER_OFFSET > bundle_end) {
|
||||
free(crts);
|
||||
throw std::runtime_error("Invalid certificate bundle");
|
||||
}
|
||||
size_t name_len = cur_crt[0] << 8 | cur_crt[1];
|
||||
size_t key_len = cur_crt[2] << 8 | cur_crt[3];
|
||||
cur_crt = cur_crt + CRT_HEADER_OFFSET + name_len + key_len;
|
||||
}
|
||||
|
||||
if (cur_crt > bundle_end) {
|
||||
free(crts);
|
||||
throw std::runtime_error("Invalid certificate bundle");
|
||||
}
|
||||
|
||||
/* The previous crt bundle is only updated when initialization of the
|
||||
* current crt_bundle is successful */
|
||||
/* Free previous crt_bundle */
|
||||
free(s_crt_bundle.crts);
|
||||
s_crt_bundle.num_certs = num_certs;
|
||||
s_crt_bundle.crts = crts;
|
||||
|
||||
// Enable certificate verification
|
||||
s_should_verify_certs = true;
|
||||
}
|
||||
|
||||
void bell::X509Bundle::attach(mbedtls_ssl_config* conf) {
|
||||
/* point to a dummy certificate
|
||||
* This is only required so that the
|
||||
* cacert_ptr passes non-NULL check during handshake
|
||||
*/
|
||||
mbedtls_ssl_config* ssl_conf = (mbedtls_ssl_config*)conf;
|
||||
mbedtls_x509_crt_init(&s_dummy_crt);
|
||||
mbedtls_ssl_conf_ca_chain(ssl_conf, &s_dummy_crt, NULL);
|
||||
mbedtls_ssl_conf_verify(ssl_conf, crtVerifyCallback, NULL);
|
||||
}
|
||||
|
||||
bool bell::X509Bundle::shouldVerify() {
|
||||
return s_should_verify_certs;
|
||||
}
|
||||
106
components/spotify/cspot/bell/main/io/include/BellHTTPServer.h
Normal file
106
components/spotify/cspot/bell/main/io/include/BellHTTPServer.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <BellLogger.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include "CivetServer.h"
|
||||
#include "civetweb.h"
|
||||
|
||||
using namespace bell;
|
||||
namespace bell {
|
||||
class BellHTTPServer : public CivetHandler {
|
||||
public:
|
||||
BellHTTPServer(int serverPort);
|
||||
|
||||
enum class WSState { CONNECTED, READY, CLOSED };
|
||||
|
||||
struct HTTPResponse {
|
||||
uint8_t* body;
|
||||
size_t bodySize;
|
||||
std::map<std::string, std::string> headers;
|
||||
|
||||
int status;
|
||||
|
||||
HTTPResponse() {
|
||||
body = nullptr;
|
||||
bodySize = 0;
|
||||
status = 200;
|
||||
}
|
||||
|
||||
~HTTPResponse() {
|
||||
if (body != nullptr) {
|
||||
free(body);
|
||||
body = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
typedef std::function<std::unique_ptr<HTTPResponse>(struct mg_connection* conn)> HTTPHandler;
|
||||
typedef std::function<void(struct mg_connection* conn, WSState)>
|
||||
WSStateHandler;
|
||||
typedef std::function<void(struct mg_connection* conn, char*, size_t)>
|
||||
WSDataHandler;
|
||||
|
||||
class Router {
|
||||
public:
|
||||
struct RouterNode {
|
||||
std::unordered_map<std::string, std::unique_ptr<RouterNode>> children;
|
||||
HTTPHandler value = nullptr;
|
||||
std::string paramName = "";
|
||||
|
||||
bool isParam = false;
|
||||
bool isCatchAll = false;
|
||||
};
|
||||
|
||||
RouterNode root = RouterNode();
|
||||
|
||||
typedef std::unordered_map<std::string, std::string> Params;
|
||||
typedef std::pair<HTTPHandler, Params> HandlerAndParams;
|
||||
|
||||
std::vector<std::string> split(const std::string str,
|
||||
const std::string regex_str);
|
||||
|
||||
void insert(const std::string& route, HTTPHandler& value);
|
||||
|
||||
HandlerAndParams find(const std::string& route);
|
||||
};
|
||||
|
||||
std::vector<int> getListeningPorts() { return server->getListeningPorts(); };
|
||||
void close() { server->close(); }
|
||||
|
||||
std::unique_ptr<HTTPResponse> makeJsonResponse(const std::string& json, int status = 200);
|
||||
std::unique_ptr<HTTPResponse> makeEmptyResponse();
|
||||
|
||||
void registerNotFound(HTTPHandler handler);
|
||||
void registerGet(const std::string&, HTTPHandler handler);
|
||||
void registerPost(const std::string&, HTTPHandler handler);
|
||||
void registerWS(const std::string&, WSDataHandler dataHandler,
|
||||
WSStateHandler stateHandler);
|
||||
|
||||
static std::unordered_map<std::string, std::string> extractParams(struct mg_connection* conn);
|
||||
|
||||
private:
|
||||
std::unique_ptr<CivetServer> server;
|
||||
int serverPort = 8080;
|
||||
|
||||
Router getRequestsRouter;
|
||||
Router postRequestsRouter;
|
||||
std::mutex responseMutex;
|
||||
HTTPHandler notFoundHandler;
|
||||
|
||||
bool handleGet(CivetServer* server, struct mg_connection* conn);
|
||||
bool handlePost(CivetServer* server, struct mg_connection* conn);
|
||||
};
|
||||
|
||||
} // namespace bell
|
||||
19
components/spotify/cspot/bell/main/io/include/BellSocket.h
Normal file
19
components/spotify/cspot/bell/main/io/include/BellSocket.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace bell {
|
||||
class Socket {
|
||||
public:
|
||||
Socket(){};
|
||||
virtual ~Socket() = default;
|
||||
|
||||
virtual void open(const std::string& host, uint16_t port) = 0;
|
||||
virtual size_t poll() = 0;
|
||||
virtual size_t write(uint8_t* buf, size_t len) = 0;
|
||||
virtual size_t read(uint8_t* buf, size_t len) = 0;
|
||||
virtual bool isOpen() = 0;
|
||||
virtual void close() = 0;
|
||||
};
|
||||
} // namespace bell
|
||||
|
||||
76
components/spotify/cspot/bell/main/io/include/BellTar.h
Normal file
76
components/spotify/cspot/bell/main/io/include/BellTar.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bell::BellTar {
|
||||
typedef long long unsigned file_size_t;
|
||||
|
||||
////////////////////////////////////////
|
||||
// Writing raw data
|
||||
////////////////////////////////////////
|
||||
class writer {
|
||||
std::ostream& _dst;
|
||||
|
||||
public:
|
||||
writer(std::ostream& dst) : _dst(dst) {}
|
||||
~writer() { finish(); }
|
||||
|
||||
// Append the data specified by |data| and |file_size| into the
|
||||
// tar file represented by |dst|.
|
||||
// In the tar file, the data will be available at |path_in_tar|.
|
||||
void put(std::string path_in_tar, char const* const data,
|
||||
const file_size_t data_size);
|
||||
|
||||
// Write empty folder at |path_in_tar|.
|
||||
// NOTE: to specify folders for files, just use / in the path
|
||||
// passed to |put()|.
|
||||
void put_directory(std::string path_in_tar);
|
||||
|
||||
// Call after everything has been added to the tar represented
|
||||
// by |dst| to make it a valid tar file.
|
||||
void finish();
|
||||
};
|
||||
|
||||
////////////////////////////////////////
|
||||
// Reading raw data
|
||||
////////////////////////////////////////
|
||||
class reader {
|
||||
std::istream& _inp;
|
||||
struct {
|
||||
std::string file_name;
|
||||
file_size_t file_size;
|
||||
char file_type;
|
||||
} _cached_header_data;
|
||||
bool _cached_header_data_valid;
|
||||
void _cache_header();
|
||||
int _number_of_files;
|
||||
|
||||
public:
|
||||
// Constructor, pass input stream |inp| pointing to a tar file.
|
||||
reader(std::istream& inp)
|
||||
: _inp(inp), _cached_header_data_valid(false), _number_of_files(-1) {}
|
||||
|
||||
// Returns true iff another file can be read from |inp|.
|
||||
bool contains_another_file();
|
||||
|
||||
// Returns file name of next file in |inp|.
|
||||
std::string get_next_file_name();
|
||||
// Returns file size of next file in |inp|. Use to allocate
|
||||
// memory for the |read_next_file()| call.
|
||||
file_size_t get_next_file_size();
|
||||
|
||||
// Read next file in |inp| to |data|.
|
||||
void read_next_file(char* const data);
|
||||
|
||||
char get_next_file_type();
|
||||
|
||||
void extract_all_files(std::string output_dir);
|
||||
// Skip next file in |inp|.
|
||||
void skip_next_file();
|
||||
|
||||
// Returns number of files in tar at |inp|.
|
||||
int number_of_files();
|
||||
};
|
||||
} // namespace bell::BellTar
|
||||
31
components/spotify/cspot/bell/main/io/include/BinaryReader.h
Normal file
31
components/spotify/cspot/bell/main/io/include/BinaryReader.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include "ByteStream.h"
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class BinaryReader
|
||||
{
|
||||
std::shared_ptr<ByteStream> stream;
|
||||
size_t currentPos = 0;
|
||||
|
||||
public:
|
||||
BinaryReader(std::shared_ptr<ByteStream> stream);
|
||||
int32_t readInt();
|
||||
int16_t readShort();
|
||||
uint32_t readUInt();
|
||||
long long readLong();
|
||||
void close();
|
||||
uint8_t readByte();
|
||||
size_t size();
|
||||
size_t position();
|
||||
std::vector<uint8_t> readBytes(size_t);
|
||||
void skip(size_t);
|
||||
};
|
||||
}
|
||||
77
components/spotify/cspot/bell/main/io/include/BinaryStream.h
Normal file
77
components/spotify/cspot/bell/main/io/include/BinaryStream.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef ESP_PLATFORM
|
||||
#include <bit>
|
||||
#endif
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace bell {
|
||||
class BinaryStream {
|
||||
private:
|
||||
std::endian byteOrder;
|
||||
|
||||
std::istream* istr = nullptr;
|
||||
std::ostream* ostr = nullptr;
|
||||
|
||||
void ensureReadable();
|
||||
void ensureWritable();
|
||||
bool flipBytes = false;
|
||||
|
||||
template <typename T>
|
||||
T swap16(T value) {
|
||||
#ifdef _WIN32
|
||||
return _byteswap_ushort(value);
|
||||
#else
|
||||
return __builtin_bswap16(value);
|
||||
#endif
|
||||
}
|
||||
template <typename T>
|
||||
T swap32(T value) {
|
||||
#ifdef _WIN32
|
||||
return _byteswap_ulong(value);
|
||||
#else
|
||||
return __builtin_bswap32(value);
|
||||
#endif
|
||||
}
|
||||
template <typename T>
|
||||
T swap64(T value) {
|
||||
#ifdef _WIN32
|
||||
return _byteswap_uint64(value);
|
||||
#else
|
||||
return __builtin_bswap64(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
BinaryStream(std::ostream* ostr);
|
||||
BinaryStream(std::istream* istr);
|
||||
|
||||
/**
|
||||
* @brief Set byte order used by stream.
|
||||
*
|
||||
* @param byteOrder stream's byteorder. Defaults to native.
|
||||
*/
|
||||
void setByteOrder(std::endian byteOrder);
|
||||
|
||||
// Read operations
|
||||
BinaryStream& operator>>(char& value);
|
||||
BinaryStream& operator>>(std::byte& value);
|
||||
BinaryStream& operator>>(int16_t& value);
|
||||
BinaryStream& operator>>(uint16_t& value);
|
||||
BinaryStream& operator>>(int32_t& value);
|
||||
BinaryStream& operator>>(uint32_t& value);
|
||||
BinaryStream& operator>>(int64_t& value);
|
||||
BinaryStream& operator>>(uint64_t& value);
|
||||
|
||||
// Write operations
|
||||
BinaryStream& operator<<(char value);
|
||||
BinaryStream& operator<<(std::byte value);
|
||||
BinaryStream& operator<<(int16_t value);
|
||||
BinaryStream& operator<<(uint16_t value);
|
||||
BinaryStream& operator<<(int32_t value);
|
||||
BinaryStream& operator<<(uint32_t value);
|
||||
BinaryStream& operator<<(int64_t value);
|
||||
BinaryStream& operator<<(uint64_t value);
|
||||
};
|
||||
} // namespace bell
|
||||
125
components/spotify/cspot/bell/main/io/include/BufferedStream.h
Normal file
125
components/spotify/cspot/bell/main/io/include/BufferedStream.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "ByteStream.h"
|
||||
#include "BellTask.h"
|
||||
#include "WrappedSemaphore.h"
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
/**
|
||||
* This class implements a wrapper around an arbitrary bell::ByteStream,
|
||||
* providing a circular reading buffer with configurable thresholds.
|
||||
*
|
||||
* The BufferedStream runs a bell::Task when it's started, so the caller can
|
||||
* access the buffer's data asynchronously, whenever needed. The buffer is refilled
|
||||
* automatically from source stream.
|
||||
*
|
||||
* The class implements bell::ByteStream's methods, although for proper functioning,
|
||||
* the caller code should be modified to check isReady() and isNotReady() flags.
|
||||
*
|
||||
* If the actual reading code can't be modified, waitForReady allows to wait for buffer readiness
|
||||
* during reading. Keep in mind that using the semaphore is probably more resource effective.
|
||||
*
|
||||
* The source stream (passed to open() or returned by the reader) should implement the read()
|
||||
* method correctly, such as that 0 is returned if, and only if the stream ends.
|
||||
*/
|
||||
class BufferedStream : public bell::ByteStream, bell::Task {
|
||||
public:
|
||||
typedef std::shared_ptr<bell::ByteStream> StreamPtr;
|
||||
typedef std::function<StreamPtr(uint32_t rangeStart)> StreamReader;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param taskName name to use for the reading task
|
||||
* @param bufferSize total size of the reading buffer
|
||||
* @param readThreshold how much can be read before refilling the buffer
|
||||
* @param readSize amount of bytes to read from the source each time
|
||||
* @param readyThreshold minimum amount of available bytes to report isReady()
|
||||
* @param notReadyThreshold maximum amount of available bytes to report isNotReady()
|
||||
* @param waitForReady whether to wait for the buffer to be ready during reading
|
||||
* @param endWithSource whether to end the streaming as soon as source returns 0 from read()
|
||||
*/
|
||||
BufferedStream(
|
||||
const std::string &taskName,
|
||||
uint32_t bufferSize,
|
||||
uint32_t readThreshold,
|
||||
uint32_t readSize,
|
||||
uint32_t readyThreshold,
|
||||
uint32_t notReadyThreshold,
|
||||
bool waitForReady = false);
|
||||
~BufferedStream() override;
|
||||
bool open(const StreamPtr &stream);
|
||||
bool open(const StreamReader &newReader, uint32_t initialOffset = 0);
|
||||
void close() override;
|
||||
|
||||
// inherited methods
|
||||
public:
|
||||
/**
|
||||
* Read len bytes from the buffer to dst. If waitForReady is enabled
|
||||
* and readAvailable is lower than notReadyThreshold, the function
|
||||
* will block until readyThreshold bytes is available.
|
||||
*
|
||||
* @returns number of bytes copied to dst (might be lower than len,
|
||||
* if the buffer does not contain len bytes available), or 0 if the source
|
||||
* stream is already closed and there is no reader attached.
|
||||
*/
|
||||
size_t read(uint8_t *dst, size_t len) override;
|
||||
size_t skip(size_t len) override;
|
||||
size_t position() override;
|
||||
size_t size() override;
|
||||
|
||||
// stream status
|
||||
public:
|
||||
/**
|
||||
* Total amount of bytes served to read().
|
||||
*/
|
||||
uint32_t readTotal;
|
||||
/**
|
||||
* Total amount of bytes read from source.
|
||||
*/
|
||||
uint32_t bufferTotal;
|
||||
/**
|
||||
* Amount of bytes available to read from the buffer.
|
||||
*/
|
||||
std::atomic<uint32_t> readAvailable;
|
||||
/**
|
||||
* Whether the caller should start reading the data. This indicates that a safe
|
||||
* amount (determined by readyThreshold) of data is available in the buffer.
|
||||
*/
|
||||
bool isReady() const;
|
||||
/**
|
||||
* Whether the caller should stop reading the data. This indicates that the amount of data
|
||||
* available for reading is decreasing to a non-safe value, as data is being read
|
||||
* faster than it can be buffered.
|
||||
*/
|
||||
bool isNotReady() const;
|
||||
/**
|
||||
* Semaphore that is given when the buffer becomes ready (isReady() == true). Caller can
|
||||
* wait for the semaphore instead of continuously querying isReady().
|
||||
*/
|
||||
bell::WrappedSemaphore readySem;
|
||||
|
||||
private:
|
||||
std::mutex runningMutex;
|
||||
bool running = false;
|
||||
bool terminate = false;
|
||||
bell::WrappedSemaphore readSem; // signal to start writing to buffer after reading from it
|
||||
std::mutex readMutex; // mutex for locking read operations during writing, and vice versa
|
||||
uint32_t bufferSize;
|
||||
uint32_t readAt;
|
||||
uint32_t readSize;
|
||||
uint32_t readyThreshold;
|
||||
uint32_t notReadyThreshold;
|
||||
bool waitForReady;
|
||||
uint8_t *buf;
|
||||
uint8_t *bufEnd;
|
||||
uint8_t *bufReadPtr;
|
||||
uint8_t *bufWritePtr;
|
||||
StreamPtr source;
|
||||
StreamReader reader;
|
||||
void runTask() override;
|
||||
void reset();
|
||||
uint32_t lengthBetween(uint8_t *me, uint8_t *other);
|
||||
};
|
||||
27
components/spotify/cspot/bell/main/io/include/ByteStream.h
Normal file
27
components/spotify/cspot/bell/main/io/include/ByteStream.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef BELL_BYTE_READER_H
|
||||
#define BELL_BYTE_READER_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A class for reading bytes from a stream. Further implemented in HTTPStream.h
|
||||
*/
|
||||
namespace bell
|
||||
{
|
||||
class ByteStream
|
||||
{
|
||||
public:
|
||||
ByteStream(){};
|
||||
virtual ~ByteStream() = default;
|
||||
|
||||
virtual size_t read(uint8_t *buf, size_t nbytes) = 0;
|
||||
virtual size_t skip(size_t nbytes) = 0;
|
||||
|
||||
virtual size_t position() = 0;
|
||||
virtual size_t size() = 0;
|
||||
virtual void close() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "WrappedSemaphore.h"
|
||||
|
||||
|
||||
namespace bell {
|
||||
class CircularBuffer {
|
||||
public:
|
||||
CircularBuffer(size_t dataCapacity);
|
||||
|
||||
std::unique_ptr<bell::WrappedSemaphore> dataSemaphore;
|
||||
|
||||
size_t size() const {
|
||||
return dataSize;
|
||||
}
|
||||
|
||||
size_t capacity() const {
|
||||
return dataCapacity;
|
||||
}
|
||||
|
||||
size_t write(const uint8_t *data, size_t bytes);
|
||||
size_t read(uint8_t *data, size_t bytes);
|
||||
void emptyBuffer();
|
||||
void emptyExcept(size_t size);
|
||||
|
||||
private:
|
||||
std::mutex bufferMutex;
|
||||
size_t begIndex = 0;
|
||||
size_t endIndex = 0;
|
||||
size_t dataSize = 0;
|
||||
size_t dataCapacity = 0;
|
||||
std::vector<uint8_t> buffer;
|
||||
};
|
||||
} // namespace bell
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "BellLogger.h"
|
||||
#include "ByteStream.h"
|
||||
#include "DecoderGlobals.h"
|
||||
#include "aacdec.h"
|
||||
#include "mp3dec.h"
|
||||
|
||||
namespace bell {
|
||||
class EncodedAudioStream {
|
||||
public:
|
||||
EncodedAudioStream();
|
||||
~EncodedAudioStream();
|
||||
|
||||
// Codecs supported by this stream class
|
||||
enum class AudioCodec { AAC, MP3, OGG, NONE };
|
||||
|
||||
void openWithStream(std::unique_ptr<bell::ByteStream> byteStream);
|
||||
size_t decodeFrame(uint8_t* dst);
|
||||
bool isReadable();
|
||||
|
||||
private:
|
||||
std::shared_ptr<ByteStream> innerStream;
|
||||
std::vector<uint8_t> inputBuffer;
|
||||
std::vector<short> outputBuffer;
|
||||
std::string TAG = "EncryptedAudioStream";
|
||||
|
||||
uint8_t* decodePtr = 0;
|
||||
int bytesInBuffer = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
size_t decodedSampleRate = 44100;
|
||||
|
||||
AudioCodec codec = AudioCodec::NONE;
|
||||
|
||||
void guessDataFormat();
|
||||
|
||||
void readFully(uint8_t* dst, size_t nbytes);
|
||||
bool vectorStartsWith(std::vector<uint8_t>&, std::vector<uint8_t>&);
|
||||
|
||||
std::vector<uint8_t> aacMagicBytes = {0xFF, 0xF1};
|
||||
std::vector<uint8_t> aacMagicBytes4 = {0xFF, 0xF9};
|
||||
std::vector<uint8_t> mp3MagicBytesUntagged = {0xFF, 0xFB};
|
||||
std::vector<uint8_t> mp3MagicBytesIdc = {0x49, 0x44, 0x33};
|
||||
|
||||
AACFrameInfo aacFrameInfo;
|
||||
MP3FrameInfo mp3FrameInfo;
|
||||
|
||||
size_t decodeFrameMp3(uint8_t* dst);
|
||||
size_t decodeFrameAAC(uint8_t* dst);
|
||||
};
|
||||
} // namespace bell
|
||||
47
components/spotify/cspot/bell/main/io/include/FileStream.h
Normal file
47
components/spotify/cspot/bell/main/io/include/FileStream.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <BellLogger.h>
|
||||
#include <ByteStream.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* FileStream
|
||||
*
|
||||
* A class for reading and writing to files implementing the ByteStream interface.
|
||||
*
|
||||
*/
|
||||
namespace bell
|
||||
{
|
||||
class FileStream : public ByteStream
|
||||
{
|
||||
public:
|
||||
FileStream(const std::string& path, std::string mode);
|
||||
~FileStream();
|
||||
|
||||
FILE* file;
|
||||
|
||||
/*
|
||||
* Reads data from the stream.
|
||||
*
|
||||
* @param buf The buffer to read data into.
|
||||
* @param nbytes The size of the buffer.
|
||||
* @return The number of bytes read.
|
||||
* @throws std::runtime_error if the stream is closed.
|
||||
*/
|
||||
size_t read(uint8_t *buf, size_t nbytes);
|
||||
|
||||
/*
|
||||
* Skips nbytes bytes in the stream.
|
||||
*/
|
||||
size_t skip(size_t nbytes);
|
||||
|
||||
size_t position();
|
||||
|
||||
size_t size();
|
||||
|
||||
// Closes the connection
|
||||
void close();
|
||||
};
|
||||
}
|
||||
99
components/spotify/cspot/bell/main/io/include/HTTPClient.h
Normal file
99
components/spotify/cspot/bell/main/io/include/HTTPClient.h
Normal file
@@ -0,0 +1,99 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include "BellSocket.h"
|
||||
#include "ByteStream.h"
|
||||
#include "SocketStream.h"
|
||||
#include "URLParser.h"
|
||||
#include "fmt/core.h"
|
||||
#include "picohttpparser.h"
|
||||
|
||||
namespace bell {
|
||||
class HTTPClient {
|
||||
public:
|
||||
// most basic header type, represents by a key-val
|
||||
typedef std::pair<std::string, std::string> ValueHeader;
|
||||
|
||||
typedef std::vector<ValueHeader> Headers;
|
||||
|
||||
// Helper over ValueHeader, formatting a HTTP bytes range
|
||||
struct RangeHeader {
|
||||
static ValueHeader range(int32_t from, int32_t to) {
|
||||
return ValueHeader{"Range", fmt::format("bytes={}-{}", from, to)};
|
||||
}
|
||||
|
||||
static ValueHeader last(int32_t nbytes) {
|
||||
return ValueHeader{"Range", fmt::format("bytes=-{}", nbytes)};
|
||||
}
|
||||
};
|
||||
|
||||
class Response {
|
||||
public:
|
||||
Response(){};
|
||||
~Response();
|
||||
|
||||
/**
|
||||
* Initializes a connection with a given url.
|
||||
*/
|
||||
void connect(const std::string& url);
|
||||
|
||||
void rawRequest(const std::string& method, const std::string& url,
|
||||
const std::string& content, Headers& headers);
|
||||
void get(const std::string& url, Headers headers = {});
|
||||
|
||||
std::string_view body();
|
||||
std::vector<uint8_t> bytes();
|
||||
|
||||
std::string_view header(const std::string& headerName);
|
||||
bell::SocketStream& stream() { return this->socketStream; }
|
||||
|
||||
size_t contentLength();
|
||||
size_t totalLength();
|
||||
|
||||
private:
|
||||
bell::URLParser urlParser;
|
||||
bell::SocketStream socketStream;
|
||||
|
||||
struct phr_header phResponseHeaders[32];
|
||||
const size_t HTTP_BUF_SIZE = 1024;
|
||||
|
||||
std::vector<uint8_t> httpBuffer = std::vector<uint8_t>(HTTP_BUF_SIZE);
|
||||
std::vector<uint8_t> rawBody = std::vector<uint8_t>();
|
||||
size_t httpBufferAvailable;
|
||||
|
||||
size_t contentSize = 0;
|
||||
bool hasContentSize = false;
|
||||
|
||||
Headers responseHeaders;
|
||||
|
||||
void readResponseHeaders();
|
||||
void readRawBody();
|
||||
};
|
||||
|
||||
enum class Method : uint8_t { GET = 0, POST = 1 };
|
||||
|
||||
struct Request {
|
||||
std::string url;
|
||||
Method method;
|
||||
Headers headers;
|
||||
};
|
||||
|
||||
static std::unique_ptr<Response> get(const std::string& url,
|
||||
Headers headers = {}) {
|
||||
auto response = std::make_unique<Response>();
|
||||
response->connect(url);
|
||||
response->get(url, headers);
|
||||
return response;
|
||||
}
|
||||
};
|
||||
} // namespace bell
|
||||
69
components/spotify/cspot/bell/main/io/include/SocketStream.h
Normal file
69
components/spotify/cspot/bell/main/io/include/SocketStream.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include "TCPSocket.h"
|
||||
#include "TLSSocket.h"
|
||||
|
||||
namespace bell {
|
||||
class SocketBuffer : public std::streambuf {
|
||||
private:
|
||||
std::unique_ptr<bell::Socket> internalSocket;
|
||||
|
||||
static const int bufLen = 1024;
|
||||
char ibuf[bufLen], obuf[bufLen];
|
||||
|
||||
public:
|
||||
SocketBuffer() { internalSocket = nullptr; }
|
||||
|
||||
SocketBuffer(const std::string& hostname, int port, bool isSSL = false) {
|
||||
open(hostname, port);
|
||||
}
|
||||
|
||||
int open(const std::string& hostname, int port, bool isSSL = false);
|
||||
|
||||
int close();
|
||||
|
||||
bool isOpen() {
|
||||
return internalSocket != nullptr && internalSocket->isOpen();
|
||||
}
|
||||
|
||||
~SocketBuffer() { close(); }
|
||||
|
||||
protected:
|
||||
virtual int sync();
|
||||
|
||||
virtual int_type underflow();
|
||||
|
||||
virtual int_type overflow(int_type c = traits_type::eof());
|
||||
|
||||
virtual std::streamsize xsgetn(char_type* __s, std::streamsize __n);
|
||||
|
||||
virtual std::streamsize xsputn(const char_type* __s, std::streamsize __n);
|
||||
};
|
||||
|
||||
class SocketStream : public std::iostream {
|
||||
private:
|
||||
SocketBuffer socketBuf;
|
||||
|
||||
public:
|
||||
SocketStream() : std::iostream(&socketBuf) {}
|
||||
|
||||
SocketStream(const std::string& hostname, int port, bool isSSL = false)
|
||||
: std::iostream(&socketBuf) {
|
||||
open(hostname, port, isSSL);
|
||||
}
|
||||
|
||||
SocketBuffer* rdbuf() { return &socketBuf; }
|
||||
|
||||
int open(const std::string& hostname, int port, bool isSSL = false) {
|
||||
int err = socketBuf.open(hostname, port, isSSL);
|
||||
if (err)
|
||||
setstate(std::ios::failbit);
|
||||
return err;
|
||||
}
|
||||
|
||||
int close() { return socketBuf.close(); }
|
||||
|
||||
bool isOpen() { return socketBuf.isOpen(); }
|
||||
};
|
||||
} // namespace bell
|
||||
19
components/spotify/cspot/bell/main/io/include/StreamUtils.h
Normal file
19
components/spotify/cspot/bell/main/io/include/StreamUtils.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <istream>
|
||||
#include <streambuf>
|
||||
|
||||
namespace bell {
|
||||
struct MemoryBuffer : std::streambuf {
|
||||
MemoryBuffer(std::byte const* base, size_t size) {
|
||||
std::byte* p(const_cast<std::byte*>(base));
|
||||
this->setg((char*)p, (char*)p, (char*)p + size);
|
||||
}
|
||||
};
|
||||
struct IMemoryStream : virtual MemoryBuffer, std::istream {
|
||||
IMemoryStream(std::byte const* base, size_t size)
|
||||
: MemoryBuffer(base, size),
|
||||
std::istream(static_cast<std::streambuf*>(this)) {}
|
||||
};
|
||||
} // namespace bell
|
||||
121
components/spotify/cspot/bell/main/io/include/TCPSocket.h
Normal file
121
components/spotify/cspot/bell/main/io/include/TCPSocket.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#ifndef BELL_BASIC_SOCKET_H
|
||||
#define BELL_BASIC_SOCKET_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "BellSocket.h"
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include "win32shim.h"
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#ifdef __sun
|
||||
#include <sys/filio.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <BellLogger.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace bell {
|
||||
class TCPSocket : public bell::Socket {
|
||||
private:
|
||||
int sockFd;
|
||||
bool isClosed = true;
|
||||
|
||||
public:
|
||||
TCPSocket(){};
|
||||
~TCPSocket() { close(); };
|
||||
|
||||
void open(const std::string& host, uint16_t port) {
|
||||
int err;
|
||||
int domain = AF_INET;
|
||||
int socketType = SOCK_STREAM;
|
||||
|
||||
struct addrinfo hints {
|
||||
}, *addr;
|
||||
//fine-tune hints according to which socket you want to open
|
||||
hints.ai_family = domain;
|
||||
hints.ai_socktype = socketType;
|
||||
hints.ai_protocol =
|
||||
IPPROTO_IP; // no enum : possible value can be read in /etc/protocols
|
||||
hints.ai_flags = AI_CANONNAME | AI_ALL | AI_ADDRCONFIG;
|
||||
|
||||
// BELL_LOG(info, "http", "%s %d", host.c_str(), port);
|
||||
|
||||
char portStr[6];
|
||||
sprintf(portStr, "%u", port);
|
||||
err = getaddrinfo(host.c_str(), portStr, &hints, &addr);
|
||||
if (err != 0) {
|
||||
throw std::runtime_error("Resolve failed");
|
||||
}
|
||||
|
||||
sockFd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||
|
||||
err = connect(sockFd, addr->ai_addr, addr->ai_addrlen);
|
||||
if (err < 0) {
|
||||
close();
|
||||
BELL_LOG(error, "http", "Could not connect to %s. Error %d", host.c_str(),
|
||||
errno);
|
||||
throw std::runtime_error("Resolve failed");
|
||||
}
|
||||
|
||||
int flag = 1;
|
||||
setsockopt(sockFd, /* socket affected */
|
||||
IPPROTO_TCP, /* set option at TCP level */
|
||||
TCP_NODELAY, /* name of option */
|
||||
(char*)&flag, /* the cast is historical cruft */
|
||||
sizeof(int)); /* length of option value */
|
||||
|
||||
freeaddrinfo(addr);
|
||||
isClosed = false;
|
||||
}
|
||||
|
||||
size_t read(uint8_t* buf, size_t len) {
|
||||
return recv(sockFd, (char*)buf, len, 0);
|
||||
}
|
||||
|
||||
size_t write(uint8_t* buf, size_t len) {
|
||||
return send(sockFd, (char*)buf, len, 0);
|
||||
}
|
||||
|
||||
size_t poll() {
|
||||
#ifdef _WIN32
|
||||
unsigned long value;
|
||||
ioctlsocket(sockFd, FIONREAD, &value);
|
||||
#else
|
||||
int value;
|
||||
ioctl(sockFd, FIONREAD, &value);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
bool isOpen() { return !isClosed; }
|
||||
|
||||
void close() {
|
||||
if (!isClosed) {
|
||||
#ifdef _WIN32
|
||||
closesocket(sockFd);
|
||||
#else
|
||||
::close(sockFd);
|
||||
#endif
|
||||
sockFd = -1;
|
||||
isClosed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bell
|
||||
|
||||
#endif
|
||||
60
components/spotify/cspot/bell/main/io/include/TLSSocket.h
Normal file
60
components/spotify/cspot/bell/main/io/include/TLSSocket.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef BELL_TLS_SOCKET_H
|
||||
#define BELL_TLS_SOCKET_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include "BellLogger.h"
|
||||
#include "BellSocket.h"
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mbedtls/ctr_drbg.h"
|
||||
#include "mbedtls/debug.h"
|
||||
#include "mbedtls/entropy.h"
|
||||
#include "mbedtls/net_sockets.h"
|
||||
#include "mbedtls/ssl.h"
|
||||
|
||||
namespace bell {
|
||||
class TLSSocket : public bell::Socket {
|
||||
private:
|
||||
mbedtls_net_context server_fd;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_ssl_context ssl;
|
||||
mbedtls_ssl_config conf;
|
||||
|
||||
bool isClosed = true;
|
||||
|
||||
public:
|
||||
TLSSocket();
|
||||
~TLSSocket() { close(); };
|
||||
|
||||
void open(const std::string& host, uint16_t port);
|
||||
|
||||
size_t read(uint8_t* buf, size_t len);
|
||||
size_t write(uint8_t* buf, size_t len);
|
||||
size_t poll();
|
||||
bool isOpen();
|
||||
|
||||
void close();
|
||||
};
|
||||
|
||||
} // namespace bell
|
||||
|
||||
#endif
|
||||
100
components/spotify/cspot/bell/main/io/include/URLParser.h
Normal file
100
components/spotify/cspot/bell/main/io/include/URLParser.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace bell {
|
||||
class URLParser {
|
||||
public:
|
||||
static std::string urlEncode(const std::string& value) {
|
||||
std::string new_str = "";
|
||||
static auto hex_digt = "0123456789ABCDEF";
|
||||
|
||||
std::string result;
|
||||
result.reserve(value.size() << 1);
|
||||
|
||||
for (auto ch : value) {
|
||||
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= 'a' && ch <= 'z') || ch == '-' || ch == '_' || ch == '!' ||
|
||||
ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '~' ||
|
||||
ch == '.') // !'()*-._~
|
||||
{
|
||||
result.push_back(ch);
|
||||
} else {
|
||||
result += std::string("%") +
|
||||
hex_digt[static_cast<unsigned char>(ch) >> 4] +
|
||||
hex_digt[static_cast<unsigned char>(ch) & 15];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
static std::string urlDecode(const std::string& value) {
|
||||
std::string result;
|
||||
result.reserve(value.size());
|
||||
|
||||
for (std::size_t i = 0; i < value.size(); ++i) {
|
||||
auto ch = value[i];
|
||||
|
||||
if (ch == '%' && (i + 2) < value.size()) {
|
||||
auto hex = value.substr(i + 1, 2);
|
||||
auto dec = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
|
||||
result.push_back(dec);
|
||||
i += 2;
|
||||
} else if (ch == '+') {
|
||||
result.push_back(' ');
|
||||
} else {
|
||||
result.push_back(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string host;
|
||||
int port = -1;
|
||||
|
||||
std::string schema = "http";
|
||||
std::string path;
|
||||
std::regex urlParseRegex = std::regex(
|
||||
"^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?");
|
||||
|
||||
static URLParser parse(const std::string& url) {
|
||||
URLParser parser;
|
||||
|
||||
// apply parser.urlParseRegex to url
|
||||
std::cmatch match;
|
||||
|
||||
std::regex_match(url.c_str(), match, parser.urlParseRegex);
|
||||
|
||||
if (match.size() < 3) {
|
||||
throw std::invalid_argument("Invalid URL");
|
||||
}
|
||||
|
||||
parser.schema = match[1];
|
||||
parser.host = match[2];
|
||||
parser.path = match[3];
|
||||
|
||||
if (match[4] != "") {
|
||||
parser.path += match[4];
|
||||
}
|
||||
|
||||
// check if parser.host contains ':'
|
||||
if (parser.host.find(':') != std::string::npos) {
|
||||
auto port = std::stoi(
|
||||
parser.host.substr(parser.host.find(':') + 1, parser.host.size()));
|
||||
auto host = parser.host.substr(0, parser.host.find(':'));
|
||||
parser.port = port;
|
||||
parser.host = host;
|
||||
}
|
||||
|
||||
if (parser.port == -1) {
|
||||
parser.port = parser.schema == "http" ? 80 : 443;
|
||||
}
|
||||
|
||||
return parser;
|
||||
}
|
||||
};
|
||||
} // namespace bell
|
||||
40
components/spotify/cspot/bell/main/io/include/X509Bundle.h
Normal file
40
components/spotify/cspot/bell/main/io/include/X509Bundle.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include "BellLogger.h"
|
||||
#include "mbedtls/ssl.h"
|
||||
|
||||
namespace bell::X509Bundle {
|
||||
|
||||
typedef struct crt_bundle_t {
|
||||
const uint8_t** crts;
|
||||
uint16_t num_certs;
|
||||
size_t x509_crt_bundle_len;
|
||||
} crt_bundle_t;
|
||||
|
||||
static crt_bundle_t s_crt_bundle;
|
||||
|
||||
static constexpr auto TAG = "X509Bundle";
|
||||
static constexpr auto CRT_HEADER_OFFSET = 4;
|
||||
static constexpr auto BUNDLE_HEADER_OFFSET = 2;
|
||||
|
||||
int crtCheckCertificate(mbedtls_x509_crt* child, const uint8_t* pub_key_buf,
|
||||
size_t pub_key_len);
|
||||
/* This callback is called for every certificate in the chain. If the chain
|
||||
* is proper each intermediate certificate is validated through its parent
|
||||
* in the x509_crt_verify_chain() function. So this callback should
|
||||
* only verify the first untrusted link in the chain is signed by the
|
||||
* root certificate in the trusted bundle
|
||||
*/
|
||||
int crtVerifyCallback(void* buf, mbedtls_x509_crt* crt, int depth,
|
||||
uint32_t* flags);
|
||||
|
||||
/* Initialize the bundle into an array so we can do binary search for certs,
|
||||
the bundle generated by the python utility is already presorted by subject name
|
||||
*/
|
||||
void init(const uint8_t* x509_bundle, size_t bundle_size);
|
||||
|
||||
void attach(mbedtls_ssl_config* conf);
|
||||
|
||||
bool shouldVerify();
|
||||
}; // namespace bell::X509Bundle
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef picohttpparser_h
|
||||
#define picohttpparser_h
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#if defined(_MSC_VER) && !defined(ssize_t)
|
||||
#define ssize_t intptr_t
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* contains name and value of a header (name == NULL if is a continuing line
|
||||
* of a multiline header */
|
||||
struct phr_header {
|
||||
const char *name;
|
||||
size_t name_len;
|
||||
const char *value;
|
||||
size_t value_len;
|
||||
};
|
||||
|
||||
/* returns number of bytes consumed if successful, -2 if request is partial,
|
||||
* -1 if failed */
|
||||
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
|
||||
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||
struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* should be zero-filled before start */
|
||||
struct phr_chunked_decoder {
|
||||
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
|
||||
char consume_trailer; /* if trailing headers should be consumed */
|
||||
char _hex_count;
|
||||
char _state;
|
||||
};
|
||||
|
||||
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
|
||||
* encoding headers. When the function returns without an error, bufsz is
|
||||
* updated to the length of the decoded data available. Applications should
|
||||
* repeatedly call the function while it returns -2 (incomplete) every time
|
||||
* supplying newly arrived data. If the end of the chunked-encoded data is
|
||||
* found, the function returns a non-negative number indicating the number of
|
||||
* octets left undecoded, that starts from the offset returned by `*bufsz`.
|
||||
* Returns -1 on error.
|
||||
*/
|
||||
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
|
||||
|
||||
/* returns if the chunked decoder is in middle of chunked data */
|
||||
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
665
components/spotify/cspot/bell/main/io/picohttpparser.c
Normal file
665
components/spotify/cspot/bell/main/io/picohttpparser.c
Normal file
@@ -0,0 +1,665 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#ifdef __SSE4_2__
|
||||
#ifdef _MSC_VER
|
||||
#include <nmmintrin.h>
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
#include "picohttpparser.h"
|
||||
|
||||
#if __GNUC__ >= 3
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
#define likely(x) (x)
|
||||
#define unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define ALIGNED(n) _declspec(align(n))
|
||||
#else
|
||||
#define ALIGNED(n) __attribute__((aligned(n)))
|
||||
#endif
|
||||
|
||||
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
|
||||
|
||||
#define CHECK_EOF() \
|
||||
if (buf == buf_end) { \
|
||||
*ret = -2; \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define EXPECT_CHAR_NO_CHECK(ch) \
|
||||
if (*buf++ != ch) { \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define EXPECT_CHAR(ch) \
|
||||
CHECK_EOF(); \
|
||||
EXPECT_CHAR_NO_CHECK(ch);
|
||||
|
||||
#define ADVANCE_TOKEN(tok, toklen) \
|
||||
do { \
|
||||
const char *tok_start = buf; \
|
||||
static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \
|
||||
int found2; \
|
||||
buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
|
||||
if (!found2) { \
|
||||
CHECK_EOF(); \
|
||||
} \
|
||||
while (1) { \
|
||||
if (*buf == ' ') { \
|
||||
break; \
|
||||
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
|
||||
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
} \
|
||||
} \
|
||||
++buf; \
|
||||
CHECK_EOF(); \
|
||||
} \
|
||||
tok = tok_start; \
|
||||
toklen = buf - tok_start; \
|
||||
} while (0)
|
||||
|
||||
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
|
||||
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
|
||||
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||
|
||||
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
|
||||
{
|
||||
*found = 0;
|
||||
#if __SSE4_2__
|
||||
if (likely(buf_end - buf >= 16)) {
|
||||
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
|
||||
|
||||
size_t left = (buf_end - buf) & ~15;
|
||||
do {
|
||||
__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
|
||||
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
|
||||
if (unlikely(r != 16)) {
|
||||
buf += r;
|
||||
*found = 1;
|
||||
break;
|
||||
}
|
||||
buf += 16;
|
||||
left -= 16;
|
||||
} while (likely(left != 0));
|
||||
}
|
||||
#else
|
||||
/* suppress unused parameter warning */
|
||||
(void)buf_end;
|
||||
(void)ranges;
|
||||
(void)ranges_size;
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
|
||||
{
|
||||
const char *token_start = buf;
|
||||
|
||||
#ifdef __SSE4_2__
|
||||
static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */
|
||||
"\012\037" /* allow SP and up to but not including DEL */
|
||||
"\177\177"; /* allow chars w. MSB set */
|
||||
int found;
|
||||
buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
|
||||
if (found)
|
||||
goto FOUND_CTL;
|
||||
#else
|
||||
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
|
||||
while (likely(buf_end - buf >= 8)) {
|
||||
#define DOIT() \
|
||||
do { \
|
||||
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
|
||||
goto NonPrintable; \
|
||||
++buf; \
|
||||
} while (0)
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
#undef DOIT
|
||||
continue;
|
||||
NonPrintable:
|
||||
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||
goto FOUND_CTL;
|
||||
}
|
||||
++buf;
|
||||
}
|
||||
#endif
|
||||
for (;; ++buf) {
|
||||
CHECK_EOF();
|
||||
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
|
||||
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||
goto FOUND_CTL;
|
||||
}
|
||||
}
|
||||
}
|
||||
FOUND_CTL:
|
||||
if (likely(*buf == '\015')) {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
*token_len = buf - 2 - token_start;
|
||||
} else if (*buf == '\012') {
|
||||
*token_len = buf - token_start;
|
||||
++buf;
|
||||
} else {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
*token = token_start;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
|
||||
{
|
||||
int ret_cnt = 0;
|
||||
buf = last_len < 3 ? buf : buf + last_len - 3;
|
||||
|
||||
while (1) {
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
EXPECT_CHAR('\012');
|
||||
++ret_cnt;
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
++ret_cnt;
|
||||
} else {
|
||||
++buf;
|
||||
ret_cnt = 0;
|
||||
}
|
||||
if (ret_cnt == 2) {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = -2;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define PARSE_INT(valp_, mul_) \
|
||||
if (*buf < '0' || '9' < *buf) { \
|
||||
buf++; \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
} \
|
||||
*(valp_) = (mul_) * (*buf++ - '0');
|
||||
|
||||
#define PARSE_INT_3(valp_) \
|
||||
do { \
|
||||
int res_ = 0; \
|
||||
PARSE_INT(&res_, 100) \
|
||||
*valp_ = res_; \
|
||||
PARSE_INT(&res_, 10) \
|
||||
*valp_ += res_; \
|
||||
PARSE_INT(&res_, 1) \
|
||||
*valp_ += res_; \
|
||||
} while (0)
|
||||
|
||||
/* returned pointer is always within [buf, buf_end), or null */
|
||||
static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char,
|
||||
int *ret)
|
||||
{
|
||||
/* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128
|
||||
* bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */
|
||||
static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */
|
||||
"\"\"" /* 0x22 */
|
||||
"()" /* 0x28,0x29 */
|
||||
",," /* 0x2c */
|
||||
"//" /* 0x2f */
|
||||
":@" /* 0x3a-0x40 */
|
||||
"[]" /* 0x5b-0x5d */
|
||||
"{\xff"; /* 0x7b-0xff */
|
||||
const char *buf_start = buf;
|
||||
int found;
|
||||
buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
|
||||
if (!found) {
|
||||
CHECK_EOF();
|
||||
}
|
||||
while (1) {
|
||||
if (*buf == next_char) {
|
||||
break;
|
||||
} else if (!token_char_map[(unsigned char)*buf]) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
}
|
||||
*token = buf_start;
|
||||
*token_len = buf - buf_start;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* returned pointer is always within [buf, buf_end), or null */
|
||||
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
|
||||
{
|
||||
/* we want at least [HTTP/1.<two chars>] to try to parse */
|
||||
if (buf_end - buf < 9) {
|
||||
*ret = -2;
|
||||
return NULL;
|
||||
}
|
||||
EXPECT_CHAR_NO_CHECK('H');
|
||||
EXPECT_CHAR_NO_CHECK('T');
|
||||
EXPECT_CHAR_NO_CHECK('T');
|
||||
EXPECT_CHAR_NO_CHECK('P');
|
||||
EXPECT_CHAR_NO_CHECK('/');
|
||||
EXPECT_CHAR_NO_CHECK('1');
|
||||
EXPECT_CHAR_NO_CHECK('.');
|
||||
PARSE_INT(minor_version, 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
|
||||
size_t max_headers, int *ret)
|
||||
{
|
||||
for (;; ++*num_headers) {
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
break;
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
break;
|
||||
}
|
||||
if (*num_headers == max_headers) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
|
||||
/* parsing name, but do not discard SP before colon, see
|
||||
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
|
||||
if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (headers[*num_headers].name_len == 0) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
++buf;
|
||||
for (;; ++buf) {
|
||||
CHECK_EOF();
|
||||
if (!(*buf == ' ' || *buf == '\t')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
headers[*num_headers].name = NULL;
|
||||
headers[*num_headers].name_len = 0;
|
||||
}
|
||||
const char *value;
|
||||
size_t value_len;
|
||||
if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* remove trailing SPs and HTABs */
|
||||
const char *value_end = value + value_len;
|
||||
for (; value_end != value; --value_end) {
|
||||
const char c = *(value_end - 1);
|
||||
if (!(c == ' ' || c == '\t')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
headers[*num_headers].value = value;
|
||||
headers[*num_headers].value_len = value_end - value;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
|
||||
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
|
||||
size_t max_headers, int *ret)
|
||||
{
|
||||
/* skip first empty line (some clients add CRLF after POST content) */
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
}
|
||||
|
||||
/* parse request line */
|
||||
if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
do {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
} while (*buf == ' ');
|
||||
ADVANCE_TOKEN(*path, *path_len);
|
||||
do {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
} while (*buf == ' ');
|
||||
if (*method_len == 0 || *path_len == 0) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
} else {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||
}
|
||||
|
||||
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
|
||||
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf_start + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*method = NULL;
|
||||
*method_len = 0;
|
||||
*path = NULL;
|
||||
*path_len = 0;
|
||||
*minor_version = -1;
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the request is complete (a fast countermeasure
|
||||
againt slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
|
||||
&r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
|
||||
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
|
||||
{
|
||||
/* parse "HTTP/1.x" */
|
||||
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* skip space */
|
||||
if (*buf != ' ') {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
do {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
} while (*buf == ' ');
|
||||
/* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
|
||||
if (buf_end - buf < 4) {
|
||||
*ret = -2;
|
||||
return NULL;
|
||||
}
|
||||
PARSE_INT_3(status);
|
||||
|
||||
/* get message including preceding space */
|
||||
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (*msg_len == 0) {
|
||||
/* ok */
|
||||
} else if (**msg == ' ') {
|
||||
/* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP
|
||||
* before running past the end of the given buffer. */
|
||||
do {
|
||||
++*msg;
|
||||
--*msg_len;
|
||||
} while (**msg == ' ');
|
||||
} else {
|
||||
/* garbage found after status code */
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||
}
|
||||
|
||||
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||
struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*minor_version = -1;
|
||||
*status = 0;
|
||||
*msg = NULL;
|
||||
*msg_len = 0;
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||
against slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||
against slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
enum {
|
||||
CHUNKED_IN_CHUNK_SIZE,
|
||||
CHUNKED_IN_CHUNK_EXT,
|
||||
CHUNKED_IN_CHUNK_DATA,
|
||||
CHUNKED_IN_CHUNK_CRLF,
|
||||
CHUNKED_IN_TRAILERS_LINE_HEAD,
|
||||
CHUNKED_IN_TRAILERS_LINE_MIDDLE
|
||||
};
|
||||
|
||||
static int decode_hex(int ch)
|
||||
{
|
||||
if ('0' <= ch && ch <= '9') {
|
||||
return ch - '0';
|
||||
} else if ('A' <= ch && ch <= 'F') {
|
||||
return ch - 'A' + 0xa;
|
||||
} else if ('a' <= ch && ch <= 'f') {
|
||||
return ch - 'a' + 0xa;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
|
||||
{
|
||||
size_t dst = 0, src = 0, bufsz = *_bufsz;
|
||||
ssize_t ret = -2; /* incomplete */
|
||||
|
||||
while (1) {
|
||||
switch (decoder->_state) {
|
||||
case CHUNKED_IN_CHUNK_SIZE:
|
||||
for (;; ++src) {
|
||||
int v;
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if ((v = decode_hex(buf[src])) == -1) {
|
||||
if (decoder->_hex_count == 0) {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (decoder->_hex_count == sizeof(size_t) * 2) {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
|
||||
++decoder->_hex_count;
|
||||
}
|
||||
decoder->_hex_count = 0;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_EXT;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_EXT:
|
||||
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] == '\012')
|
||||
break;
|
||||
}
|
||||
++src;
|
||||
if (decoder->bytes_left_in_chunk == 0) {
|
||||
if (decoder->consume_trailer) {
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||
break;
|
||||
} else {
|
||||
goto Complete;
|
||||
}
|
||||
}
|
||||
decoder->_state = CHUNKED_IN_CHUNK_DATA;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_DATA: {
|
||||
size_t avail = bufsz - src;
|
||||
if (avail < decoder->bytes_left_in_chunk) {
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, avail);
|
||||
src += avail;
|
||||
dst += avail;
|
||||
decoder->bytes_left_in_chunk -= avail;
|
||||
goto Exit;
|
||||
}
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
|
||||
src += decoder->bytes_left_in_chunk;
|
||||
dst += decoder->bytes_left_in_chunk;
|
||||
decoder->bytes_left_in_chunk = 0;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
|
||||
}
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_CRLF:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] != '\015')
|
||||
break;
|
||||
}
|
||||
if (buf[src] != '\012') {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
++src;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
|
||||
break;
|
||||
case CHUNKED_IN_TRAILERS_LINE_HEAD:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] != '\015')
|
||||
break;
|
||||
}
|
||||
if (buf[src++] == '\012')
|
||||
goto Complete;
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] == '\012')
|
||||
break;
|
||||
}
|
||||
++src;
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||
break;
|
||||
default:
|
||||
assert(!"decoder is corrupt");
|
||||
}
|
||||
}
|
||||
|
||||
Complete:
|
||||
ret = bufsz - src;
|
||||
Exit:
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, bufsz - src);
|
||||
*_bufsz = dst;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
|
||||
{
|
||||
return decoder->_state == CHUNKED_IN_CHUNK_DATA;
|
||||
}
|
||||
|
||||
#undef CHECK_EOF
|
||||
#undef EXPECT_CHAR
|
||||
#undef ADVANCE_TOKEN
|
||||
Reference in New Issue
Block a user