Reputation: 143
I am writing my own http server. I need to check each header from the given list (if it was given an invalid value). I also can not use any third party libraries.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
Yes, I was looking for a solution, I have seen these and other questions:
Parse HTTP headers in C++
How to correctly parse incoming HTTP requests
How to parse HTTP response using c++
I also tried to find the source file \ example of implementing that in libcurl here, but I couldn't.
https://curl.se/libcurl/c/example.html
My own work according to this article:
https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header
void HttpServer::ParseQuery(HttpServer::Connection socket_) noexcept{
std::vector<std::string> elems_ = split(socket_.data,"\r\n");
std::vector<std::string> request_line_ = split( elems_[0]+=" " ," ");
std::map<std::string,HttpServer::HeaderValue> header_fields_;
//if( (request_line_.size()< 3) || !strcmp(request_line_[0].c_str(),"GET") || !strcmp(request_line_[2].c_str(),"HTTP/1.1")) return;
std::size_t index = 1,pos;
while(index < elems_.size()){
if ((pos = elems_[index].find(":")) == std::string::npos){
}
else{
std::string first = elems_[index].substr(0,pos), second = elems_[index].substr(pos,elems_[index].length()-pos);
std::transform(first.begin(), first.end(), first.begin(),
[](unsigned char c){
return std::tolower(c);
});
if( second[0] == ' ')
second.erase(0,1);
if( second[second.length()-1] == ' ')
second.erase(second.length()-1,1);
header_fields_[first] = {second , 1 };
}
++index;
}
for( auto &a : header_fields_){
//For any header: the value’s length can't be greater than 128.
//For Accept-Language and Content-Language: can only have values consisting of 0-9, A-Z, a-z, space or *,-.;=.
if(!strcmp(a.first.c_str(),"accept-language")|| !strcmp(a.first.c_str(),"content-language"))
{
if( (a.second.field_name.length() > 128) || ((pos = a.second.field_name.find_first_not_of("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM*,-.;=") )!= std::string::npos))
a.second.correct = 0;
}
//For Accept and Content-Type: can't contain a CORS-unsafe request header byte: 0x00-0x1F (except for 0x09 (HT), which is allowed), "():<>?@[\]{}, and 0x7F (DEL).
else if ((a.second.field_name.length() > 128) || (!strcmp(a.first.c_str(),"accept")|| !strcmp(a.first.c_str(),"content-type"))){
if( (pos = a.second.field_name.find_first_of("\x01\x02\x03\x04\x05\x06\x07\x08\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\0x7f():<>?@[\\]{}") )!= std::string::npos)
a.second.correct = 0;
}
}
}
where the data types are:
struct Connection
{
Connection(const int &socket,const std::chrono::time_point<std::chrono::system_clock> &tp);
int socket;
std::chrono::time_point<std::chrono::system_clock> tp;
std::string data;
std::string respons;
};
struct HeaderValue
{
std::string field_name;
bool correct = 1;
};
Function above divides the request received over the network into rows, the first row of the request into 3 more parts and stores it in the vector. Next, I remove the OWS before and after the value, if any. After the forming of the map, I check, for example, these two headers. Tests of my code look like this:
wget http://localhost:8080/server.cpp
--2021-02-22 18:07:33-- http://localhost:8080/server.cpp
Resolving localhost (localhost)... 127.0.0.1
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
HTTP request sent, awaiting response... 200 No headers, assuming HTTP/0.9
Length: unspecified
Saving to: ‘server.cpp.6’
server.cpp.6 [ <=> ] 16,39K --.-KB/s
my compiler:
g++ --version
g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
My question is, is there a better way to do this than a huge number of else-if for every possible header?
Upvotes: 0
Views: 853
Reputation: 67772
My question is, is there a better way to do this than a huge number of else-if for every possible header?
It's exactly the same answer as for any other case where you're hard-coding a lot of magic values: stop it.
Group all the hard-coded magic values together in one place, so at least they're not polluting your logic: build a map of header name strings to validators. The validators can be regular expressions or actual functors (eg, std::function<bool(std::string)>
) if you need more flexibility.
Your code becomes something like
for (auto &a : header_fields_) {
auto v = header_validators_.find(a.first);
if (v == header_validators_.end()) {
// error, unknown header
} else {
if (!v->second(a.first, a.second)) {
// error, invalid header
}
}
}
It's often even better to load the magic values from a file instead of hard-coding them at all, but there's an implementation cost that isn't necessarily justified unless being able to edit that file without re-compiling is an actual benefit.
Upvotes: 1