Home > Enterprise >  How can i parse a raw http request in Rust?
How can i parse a raw http request in Rust?

Time:11-03

What I want to do is parse a raw http response. I get http responses from a PHP file, for example my php file has the following content:

<?php

echo "Hello";

header("Location: form.php");

?>

And that returns this response:

Status: 302 Found\r\nX-Powered-By: PHP/8.1.11\r\nLocation: form.php\r\nContent-type: text/html; charset=UTF-8\r\n\r\nHello

So far, so good. But if the file has an error, like a syntax error like this:

<?php

echo "Hello"//;

header("Location: form.php");

?>

I get this response:

PHP message: PHP Parse error:  syntax error, unexpected identifier \"header\", expecting \",\" or \";\" in/var/www/public/index.php on line 5Status: 500 Internal Server Error\r\nX-Powered-By: PHP/8.1.11\r\nContent-type: text/html; charset=UTF-8\r\n\r\n

As you can see the response is clearly different and the order of the headers is different (I want to clarify that I printed this using "{:?}", so the \ after some " are not really part of the response) So, that has caused some libraries like this to return errors, so I decided to parse the responses myself.

So how can I parse the response in a correct way? How do I handle it knowing that I can have results like the first and the second? Should I use something like regular expressions? Thanks in advance.

Just to clarify, I get the http response using the FastCGI protocol so i can't get the responses any other way. And I would also like to say that I am not looking for a solution to the error I had with the libraries, I would just like to know how to parse the http response by myself.

Edit

So you can see how I get the responses I'm going to show you my rust code. Which is this:

use std::os::unix::net::{UnixStream};
use std::io::{Read, Write};
use std::str;

fn main() {
    const FCGI_VERSION_1: u8    = 1;

    const FCGI_BEGIN_REQUEST:u8 = 1;
    const FCGI_END_REQUEST: u8  = 3;
    const FCGI_STDIN: u8        = 5;
    const FCGI_STDOUT: u8       = 6;
    const FCGI_STDERR: u8       = 7;

    const FCGI_RESPONDER: u16  = 1;

    const FCGI_PARAMS: u8 = 4;

    let socket_path = "/run/php-fpm/php-fpm.sock";

    let mut socket = match UnixStream::connect(socket_path) {
        Ok(sock) => sock,
        Err(e) => {
            println!("Couldn't connect: {e:?}");
            return
        }
    };

    let requestId: u16 = 1;

    let role: u16 = FCGI_RESPONDER;

    let beginRequest = vec![
       // FCGI_Header
       FCGI_VERSION_1, FCGI_BEGIN_REQUEST,
       (requestId >> 8) as u8, (requestId & 0xFF) as u8,
       0x00, 0x08, // This is the size of `FCGI_BeginRequestBody`
       0, 0,
       // FCGI_BeginRequestBody
       (role >> 8) as u8, (role & 0xFF) as u8,
       0, // Flags
       0, 0, 0, 0, 0, // Reserved
    ];

    socket.write_all(&beginRequest).unwrap();

    // write the FCGI_PARAMS

    let param1_name = "SCRIPT_FILENAME".as_bytes();
    let param1_value = "/home/davebook-arch/projects/so/index.php".as_bytes();
    let lengths1 = [ param1_name.len() as u8, param1_value.len() as u8 ];
    let params1_len: u16 = (param1_name.len()   param1_value.len()   lengths1.len()) as u16;

    let param2_name = b"REQUEST_METHOD";
    let param2_value = b"GET";
    let lengths2 = [ param2_name.len() as u8, param2_value.len() as u8 ];
    let params2_len: u16 = (param2_name.len()   param2_value.len()   lengths2.len()) as u16;

    let params_len = params1_len   params2_len;
    let paramsRequest = vec![
       FCGI_VERSION_1, FCGI_PARAMS,
       (requestId >> 8) as u8, (requestId & 0xFF) as u8,
       (params_len >> 8) as u8, (params_len & 0xFF) as u8,
       0, 0,
    ];

    socket.write_all (&paramsRequest).unwrap();
    socket.write_all (&lengths1).unwrap();
    socket.write_all (param1_name).unwrap();
    socket.write_all (param1_value).unwrap();
    socket.write_all (&lengths2).unwrap();
    socket.write_all (param2_name).unwrap();
    socket.write_all (param2_value).unwrap();

    let mut stdout: String = String::new();

    // get the response
    let requestHeader = vec![
        FCGI_VERSION_1, FCGI_STDOUT,
        (requestId >> 8) as u8, (requestId & 0xFF) as u8,
        0, 0,
        0, 0,
    ];

    socket.write_all(&requestHeader).unwrap();

    loop {
        // read the response header
        let mut responseHeader = [0u8; 8];
        socket.read_exact (&mut responseHeader).unwrap();
    
        if responseHeader[1] != FCGI_STDOUT && responseHeader[1] != FCGI_STDERR{

            if responseHeader[1] == FCGI_END_REQUEST {
                println!("FCGI_END_REQUEST: {:?}", responseHeader);
                break;
            } else {
                println!("NOT FCGI_END_REQUEST: {}", responseHeader[1]);
                break;
            }
        }

        // read the body
        let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);

        let mut responseBody = vec![0; responseLength];

        socket.read_exact (&mut responseBody).unwrap();

        stdout.push_str(&String::from_utf8_lossy(&responseBody));

        // read the padding
        let mut pad = vec![0; responseHeader[6] as usize];

        socket.read_exact (&mut pad).unwrap();
    }
    
    println!("Output: {:?}", stdout);
}

The FULL and DETAILED context is in this question (and in my answer of that question there are more details). It seems to me that it is better to show you the question so that you can see how I explain everything there in detail.

Edit 2

I have executed the code that is in the answer of this question. And it works perfectly, but only with the first case, in the second case, what could I do? Am I the one collecting the wrong data? Or how can I parse that http response?

CodePudding user response:

I've never done this in Rust before, and i haven't been doing Rust for long, don't take this as an authoritative answer or anything, but doing

fn main(){
    let raw="Status: 302 Found\r\nX-Powered-By: PHP/8.1.11\r\nLocation: form.php\r\nContent-type: text/html; charset=UTF-8\r\n\r\nHello\r\n\r\nboody contains separator, account for that";
    let raw_split = raw.splitn(2, "\r\n\r\n").collect::<Vec<&str>>();
    let headers = raw_split[0].split("\r\n").collect::<Vec<&str>>();
    let body = raw_split[1];
    let mut headers_map = std::collections::HashMap::new();
    for header in headers {
        let header = header.splitn(2,": ").collect::<Vec<&str>>();
        headers_map.insert(header[0], header[1]);
    }
    println!("{:?}", headers_map);
    println!("{}", body);
}

gives me

{"X-Powered-By": "PHP/8.1.11", "Status": "302 Found", "Location": "form.php", "Content-type": "text/html; charset=UTF-8"}
Hello

boody contains separator, account for that

and changing raw into

    let raw = "PHP message: PHP Parse error:  syntax error, unexpected identifier \"header\", expecting \",\" or \";\" in/var/www/public/index.php on line 5\r\nStatus: 500 Internal Server Error\r\nX-Powered-By: PHP/8.1.11\r\nContent-type: text/html; charset=UTF-8\r\n\r\n";

gives:

{"PHP message": "PHP Parse error:  syntax error, unexpected identifier \"header\", expecting \",\" or \";\" in/var/www/public/index.php on line 5", "X-Powered-By": "PHP/8.1.11", "Content-type": "text/html; charset=UTF-8", "Status": "500 Internal Server Error"}

so you can use

    if headers_map.contains_key("PHP message"){
        what to do if its a PHP message?
    }

to check if its a "PHP message" or not, in this case the PHP message is PHP Parse error: syntax error, unexpected identifier \"header\", expecting \",\" or \";\" in/var/www/public/index.php on line 5

  • Related