Added callback support to HTTP parser

This commit is contained in:
Ramoe 2020-02-20 10:46:42 +01:00 committed by Thulinma
parent 1d0e68c5a4
commit 4ed5dd21e3
4 changed files with 111 additions and 39 deletions

View file

@ -1,19 +1,20 @@
/// \file http_parser.cpp /// \file http_parser.cpp
/// Holds all code for the HTTP namespace. /// Holds all code for the HTTP namespace.
#include "http_parser.h"
#include "util.h"
#include "auth.h" #include "auth.h"
#include "defines.h" #include "defines.h"
#include "encode.h" #include "encode.h"
#include "http_parser.h"
#include "timing.h" #include "timing.h"
#include "url.h" #include "url.h"
#include "util.h"
#include <iomanip> #include <iomanip>
/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
/// All this constructor does is call HTTP::Parser::Clean(). /// All this constructor does is call HTTP::Parser::Clean().
HTTP::Parser::Parser(){ HTTP::Parser::Parser(){
headerOnly = false; headerOnly = false;
bodyCallback = 0;
Clean(); Clean();
std::stringstream nStr; std::stringstream nStr;
nStr << std::hex << std::setw(16) << std::setfill('0') << (uint64_t)(Util::bootMS()); nStr << std::hex << std::setw(16) << std::setfill('0') << (uint64_t)(Util::bootMS());
@ -267,8 +268,7 @@ void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::C
void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser &request, void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser &request,
Socket::Connection &conn, bool bufferAllChunks){ Socket::Connection &conn, bool bufferAllChunks){
std::string prot = request.protocol; std::string prot = request.protocol;
sendingChunks = sendingChunks = (!bufferAllChunks && protocol == "HTTP/1.1" && request.GetHeader("Connection") != "close");
(!bufferAllChunks && protocol == "HTTP/1.1" && request.GetHeader("Connection") != "close");
CleanPreserveHeaders(); CleanPreserveHeaders();
protocol = prot; protocol = prot;
if (sendingChunks){ if (sendingChunks){
@ -286,8 +286,7 @@ void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Pa
/// a zero-content-length HTTP/1.0 response. This call simply calls StartResponse("200", "OK", /// a zero-content-length HTTP/1.0 response. This call simply calls StartResponse("200", "OK",
/// request, conn) \param request The HTTP request to respond to. \param conn The connection to send /// request, conn) \param request The HTTP request to respond to. \param conn The connection to send
/// over. /// over.
void HTTP::Parser::StartResponse(HTTP::Parser &request, Socket::Connection &conn, void HTTP::Parser::StartResponse(HTTP::Parser &request, Socket::Connection &conn, bool bufferAllChunks){
bool bufferAllChunks){
StartResponse("200", "OK", request, conn, bufferAllChunks); StartResponse("200", "OK", request, conn, bufferAllChunks);
} }
@ -300,8 +299,7 @@ void HTTP::Parser::Proxy(Socket::Connection &from, Socket::Connection &to){
if (getChunks){ if (getChunks){
unsigned int proxyingChunk = 0; unsigned int proxyingChunk = 0;
while (to.connected() && from.connected()){ while (to.connected() && from.connected()){
if ((from.Received().size() && if ((from.Received().size() && (from.Received().size() > 1 || *(from.Received().get().rbegin()) == '\n')) ||
(from.Received().size() > 1 || *(from.Received().get().rbegin()) == '\n')) ||
from.spool()){ from.spool()){
if (proxyingChunk){ if (proxyingChunk){
while (proxyingChunk && from.Received().size()){ while (proxyingChunk && from.Received().size()){
@ -483,26 +481,30 @@ void HTTP::Parser::SetVar(std::string i, std::string v){
/// If a whole request could be read, it is removed from the front of the socket buffer and true /// If a whole request could be read, it is removed from the front of the socket buffer and true
/// returned. If not, as much as can be interpreted is removed and false returned. \param conn The /// returned. If not, as much as can be interpreted is removed and false returned. \param conn The
/// socket to read from. \return True if a whole request or response was read, false otherwise. /// socket to read from. \return True if a whole request or response was read, false otherwise.
bool HTTP::Parser::Read(Socket::Connection &conn){ bool HTTP::Parser::Read(Socket::Connection &conn, Util::DataCallback &cb){
// Make sure the received data ends in a newline (\n). while (conn.Received().size()){
while ((!seenHeaders || (getChunks && !doingChunk)) && conn.Received().get().size() && // Make sure the received data ends in a newline (\n).
*(conn.Received().get().rbegin()) != '\n'){ while ((!seenHeaders || (getChunks && !doingChunk)) && conn.Received().get().size() &&
if (conn.Received().size() > 1){ *(conn.Received().get().rbegin()) != '\n'){
// make a copy of the first part if (conn.Received().size() > 1){
std::string tmp = conn.Received().get(); // make a copy of the first part
// clear the first part, wiping it from the partlist std::string tmp = conn.Received().get();
conn.Received().get().clear(); // clear the first part, wiping it from the partlist
conn.Received().size(); conn.Received().get().clear();
// take the now first (was second) part, insert the stored part in front of it conn.Received().size();
conn.Received().get().insert(0, tmp); // take the now first (was second) part, insert the stored part in front of it
}else{ conn.Received().get().insert(0, tmp);
return false; }else{
return false;
}
}
// return true if a parse succeeds, and is not a request
if (parse(conn.Received().get(), cb) && (!JSON::Value(url).isInt() || headerOnly || length ||
getChunks || (!conn && !conn.Received().size()))){
return true;
} }
} }
// if a parse succeeds, simply return true
if (parse(conn.Received().get())){return true;}
// otherwise, if we have parts left, call ourselves recursively
if (conn.Received().size()){return Read(conn);}
return false; return false;
}// HTTPReader::Read }// HTTPReader::Read
@ -526,7 +528,7 @@ uint8_t HTTP::Parser::getPercentage() const{
/// from the data buffer. /// from the data buffer.
/// \param HTTPbuffer The data buffer to read from. /// \param HTTPbuffer The data buffer to read from.
/// \return True on success, false otherwise. /// \return True on success, false otherwise.
bool HTTP::Parser::parse(std::string &HTTPbuffer){ bool HTTP::Parser::parse(std::string &HTTPbuffer, Util::DataCallback &cb){
size_t f; size_t f;
std::string tmpA, tmpB, tmpC; std::string tmpA, tmpB, tmpC;
/// \todo Make this not resize HTTPbuffer in parts, but read all at once and then remove the /// \todo Make this not resize HTTPbuffer in parts, but read all at once and then remove the
@ -588,11 +590,15 @@ bool HTTP::Parser::parse(std::string &HTTPbuffer){
body.clear(); body.clear();
if (GetHeader("Content-Length") != ""){ if (GetHeader("Content-Length") != ""){
length = atoi(GetHeader("Content-Length").c_str()); length = atoi(GetHeader("Content-Length").c_str());
if (body.capacity() < length){body.reserve(length);} if (!bodyCallback && (&cb == &Util::defaultDataCallback) && body.capacity() < length){
body.reserve(length);
}
} }
if (GetHeader("Content-length") != ""){ if (GetHeader("Content-length") != ""){
length = atoi(GetHeader("Content-length").c_str()); length = atoi(GetHeader("Content-length").c_str());
if (body.capacity() < length){body.reserve(length);} if (!bodyCallback && (&cb == &Util::defaultDataCallback) && body.capacity() < length){
body.reserve(length);
}
} }
if (GetHeader("Transfer-Encoding") == "chunked"){ if (GetHeader("Transfer-Encoding") == "chunked"){
getChunks = true; getChunks = true;
@ -611,12 +617,33 @@ bool HTTP::Parser::parse(std::string &HTTPbuffer){
if (length > 0){ if (length > 0){
if (headerOnly){return true;} if (headerOnly){return true;}
unsigned int toappend = length - body.length(); unsigned int toappend = length - body.length();
// limit the amount of bytes that will be appended to the amount there
// is available
if (toappend > HTTPbuffer.size()){toappend = HTTPbuffer.size();}
if (toappend > 0){ if (toappend > 0){
body.append(HTTPbuffer, 0, toappend); bool shouldAppend = true;
// check if pointer callback function is set and run callback. remove partial data from buffer
if (bodyCallback){
bodyCallback(HTTPbuffer.data(), toappend);
length -= toappend;
shouldAppend = false;
}
// check if reference callback function is set and run callback. remove partial data from buffer
if (&cb != &Util::defaultDataCallback){
cb.dataCallback(HTTPbuffer.data(), toappend);
length -= toappend;
shouldAppend = false;
}
if (shouldAppend){body.append(HTTPbuffer, 0, toappend);}
HTTPbuffer.erase(0, toappend); HTTPbuffer.erase(0, toappend);
currentLength += toappend;
} }
if (length == body.length()){ if (length == body.length()){
//parse POST variables // parse POST variables
if (method == "POST"){parseVars(body, vars);} if (method == "POST"){parseVars(body, vars);}
return true; return true;
}else{ }else{
@ -624,16 +651,32 @@ bool HTTP::Parser::parse(std::string &HTTPbuffer){
} }
}else{ }else{
if (getChunks){ if (getChunks){
// toappend
currentLength += HTTPbuffer.size();
if (headerOnly){return true;} if (headerOnly){return true;}
if (doingChunk){ if (doingChunk){
unsigned int toappend = HTTPbuffer.size(); unsigned int toappend = HTTPbuffer.size();
if (toappend > doingChunk){toappend = doingChunk;} if (toappend > doingChunk){toappend = doingChunk;}
body.append(HTTPbuffer, 0, toappend);
bool shouldAppend = true;
if (bodyCallback){
bodyCallback(HTTPbuffer.data(), toappend);
shouldAppend = false;
}
if (&cb != &Util::defaultDataCallback){
cb.dataCallback(HTTPbuffer.data(), toappend);
shouldAppend = false;
}
if (shouldAppend){body.append(HTTPbuffer, 0, toappend);}
HTTPbuffer.erase(0, toappend); HTTPbuffer.erase(0, toappend);
doingChunk -= toappend; doingChunk -= toappend;
}else{ }else{
f = HTTPbuffer.find('\n'); f = HTTPbuffer.find('\n');
if (f == std::string::npos) return false; if (f == std::string::npos){return false;}
tmpA = HTTPbuffer.substr(0, f); tmpA = HTTPbuffer.substr(0, f);
while (tmpA.find('\r') != std::string::npos){tmpA.erase(tmpA.find('\r'));} while (tmpA.find('\r') != std::string::npos){tmpA.erase(tmpA.find('\r'));}
unsigned int chunkLen = 0; unsigned int chunkLen = 0;
@ -655,7 +698,23 @@ bool HTTP::Parser::parse(std::string &HTTPbuffer){
} }
return false; return false;
}else{ }else{
return true; unsigned int toappend = HTTPbuffer.size();
bool shouldAppend = true;
if (bodyCallback){
bodyCallback(HTTPbuffer.data(), toappend);
shouldAppend = false;
}
if (&cb != &Util::defaultDataCallback){
cb.dataCallback(HTTPbuffer.data(), toappend);
shouldAppend = false;
}
if (shouldAppend){body.append(HTTPbuffer, 0, toappend);}
HTTPbuffer.erase(0, toappend);
// return false when callbacks are used.
return shouldAppend;
} }
} }
} }
@ -743,4 +802,3 @@ void HTTP::Parser::Chunkify(const char *data, unsigned int size, Socket::Connect
} }
} }
} }

View file

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "socket.h" #include "socket.h"
#include "util.h"
#include <map> #include <map>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -16,10 +17,10 @@ namespace HTTP{
void parseVars(const std::string &data, std::map<std::string, std::string> &storage); void parseVars(const std::string &data, std::map<std::string, std::string> &storage);
/// Simple class for reading and writing HTTP 1.0 and 1.1. /// Simple class for reading and writing HTTP 1.0 and 1.1.
class Parser{ class Parser : public Util::DataCallback{
public: public:
Parser(); Parser();
bool Read(Socket::Connection &conn); bool Read(Socket::Connection &conn, Util::DataCallback &cb = Util::defaultDataCallback);
bool Read(std::string &strbuf); bool Read(std::string &strbuf);
const std::string &GetHeader(const std::string &i) const; const std::string &GetHeader(const std::string &i) const;
bool hasHeader(const std::string &i) const; bool hasHeader(const std::string &i) const;
@ -54,10 +55,12 @@ namespace HTTP{
std::string url; std::string url;
std::string protocol; std::string protocol;
unsigned int length; unsigned int length;
unsigned int currentLength;
bool headerOnly; ///< If true, do not parse body if the length is a known size. bool headerOnly; ///< If true, do not parse body if the length is a known size.
bool bufferChunks; bool bufferChunks;
// this bool was private // this bool was private
bool sendingChunks; bool sendingChunks;
void (*bodyCallback)(const char *, size_t);
private: private:
std::string cnonce; std::string cnonce;
@ -65,7 +68,7 @@ namespace HTTP{
bool seenReq; bool seenReq;
bool getChunks; bool getChunks;
unsigned int doingChunk; unsigned int doingChunk;
bool parse(std::string &HTTPbuffer); bool parse(std::string &HTTPbuffer, Util::DataCallback &cb = Util::defaultDataCallback);
std::string builder; std::string builder;
std::string read_buffer; std::string read_buffer;
std::map<std::string, std::string> headers; std::map<std::string, std::string> headers;
@ -74,4 +77,3 @@ namespace HTTP{
}; };
}// namespace HTTP }// namespace HTTP

View file

@ -29,6 +29,8 @@
#define RAX_REQDFIELDS_LEN 36 #define RAX_REQDFIELDS_LEN 36
namespace Util{ namespace Util{
Util::DataCallback defaultDataCallback;
/// Helper function that cross-platform checks if a given directory exists. /// Helper function that cross-platform checks if a given directory exists.
bool isDirectory(const std::string &path){ bool isDirectory(const std::string &path){
#if defined(_WIN32) #if defined(_WIN32)

View file

@ -6,6 +6,7 @@
#include <map> #include <map>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include "defines.h"
namespace Util{ namespace Util{
bool isDirectory(const std::string &path); bool isDirectory(const std::string &path);
@ -18,6 +19,15 @@ namespace Util{
uint64_t ftell(FILE *stream); uint64_t ftell(FILE *stream);
uint64_t fseek(FILE *stream, uint64_t offset, int whence); uint64_t fseek(FILE *stream, uint64_t offset, int whence);
class DataCallback{
public:
virtual void dataCallback(const char * ptr, size_t size){
INFO_MSG("default callback, size: %llu", size);
}
};
extern Util::DataCallback defaultDataCallback;
//Forward declaration //Forward declaration
class FieldAccX; class FieldAccX;