tgoerg
tgoerg

Reputation: 143

Validate http get request with c++

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

Answers (1)

Useless
Useless

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

Related Questions