packeteer/src/http1.rs

234 lines
9.9 KiB
Rust

//! The module handling HTTP/1.0 and HTTP/1.1 operations.
//! It contains structures for HTTP/1.x requests and responses as well as functions for generating, unpacking and constructing them.
//! It can also upgrade and downgrade HTTP/1.x requests.
use super::{KVHeader, generate_kvheader, Url, wrap_url, unwrap_url};
use std::str::FromStr;
/// A structure for HTTP/1.x requests.
/// Generated with packeteer::http1::generate_request.
#[derive(Debug)]
pub struct Http1Request {
pub method: String,
pub location: Url,
pub version: String,
pub headers: Vec<KVHeader>,
pub body: String,
}
/// A structure for HTTP/1.x responses.
/// Generated with packeteer::http1::generate_response.
#[derive(Debug)]
pub struct Http1Response {
pub version: String,
pub status: String,
pub headers: Vec<KVHeader>,
pub body: String,
pub code: i32,
}
/// A function for generating Http1Request structures.
/// By default, packeteer generates HTTP/1.1 requests.
pub fn generate_request(method: &str, host: &str, location: &str, body: &str) -> Http1Request {
let hostkv = generate_kvheader("Host", host);
let request = Http1Request { method: method.to_string(), location: wrap_url(&format!("https://root/{}", location)), version: "1.1".to_string(), headers: vec![hostkv], body: body.to_string() };
return request
}
/// A function for generating Http1Response structures.
/// By default, packeteer generates HTTP/1.1 responses.
/// Invalid status codes will automatically be turned into code 500 (Internal Server Error)
pub fn generate_response(code: i32, body: &str) -> Http1Response {
let mut xcode = code;
if code_to_string(xcode) == "500 Internal Server Error" {
xcode = 500;
}
let response = Http1Response { version: "1.1".to_string(), status: code_to_string(xcode), headers: vec![], body: body.to_string(), code: xcode };
return response
}
/// A function for converting HTTP/1.x request strings into Http1Request.
/// Construction functions aim to be as transparent as possible so apart from turning it into a structure, the request stays relatively unchanged.
pub fn construct_request(request: &str) -> Http1Request {
let split = request.split("\r\n");
let mut request = Http1Request { method: "".to_string(), location: wrap_url("https://example.com"), version: "".to_string(), headers: vec![], body: "".to_string() };
let mut reachedbody = false;
for v in split {
if reachedbody != true {
if v.contains("HTTP/1.1") {
let split1: Vec<&str> = v.split(" ").collect();
request.method = split1[0].to_string();
request.location = wrap_url(&format!("https://root/{}", split1[1].to_string()));
request.version = "1.1".to_string();
} else if v.contains("HTTP/1.0") {
let split1: Vec<&str> = v.split(" ").collect();
request.method = split1[0].to_string();
request.location = wrap_url(&format!("https://root/{}", split1[1].to_string()));
request.version = "1.0".to_string();
} else if v == "" {
reachedbody = true;
} else {
let split1: Vec<&str> = v.split(": ").collect();
let header = generate_kvheader(split1[0], split1[1]);
request.headers.push(header)
}
} else {
request.body = v.to_string();
}
}
return request
}
/// A function for converting HTTP/1.x response strings into Http1Response.
/// Construction functions aim to be as transparent as possible so apart from turning it into a structure, the response stays relatively unchanged.
/// Responses passed here with invalid status codes will keep their invalid status code.
pub fn construct_response(response: &str) -> Http1Response {
let split = response.split("\r\n");
let mut response = Http1Response { version: "".to_string(), status: "".to_string(), headers: vec![], body: "".to_string(), code: 0 };
let mut reachedbody = false;
for v in split {
if reachedbody != true {
if v.contains("HTTP/1.1") {
let split1: Vec<&str> = v.split(" ").collect();
let split2: Vec<&str> = v.split("HTTP/1.1 ").collect();
response.version = "1.1".to_string();
let n: i32 = FromStr::from_str(split1[1]).unwrap();
response.status = split2[1].to_string();
response.code = n;
} else if v.contains("HTTP/1.0") {
let split1: Vec<&str> = v.split(" ").collect();
let split2: Vec<&str> = v.split("HTTP/1.0 ").collect();
response.version = "1.0".to_string();
let n: i32 = FromStr::from_str(split1[1]).unwrap();
response.status = split2[1].to_string();
response.code = n;
} else if v == "" {
reachedbody = true;
} else {
let split1: Vec<&str> = v.split(": ").collect();
let header = generate_kvheader(split1[0], split1[1]);
response.headers.push(header)
}
} else {
response.body = v.to_string();
}
}
return response
}
/// A function for converting Http1Request structures into HTTP/1.x request strings.
/// Unpacking functions are simply taking the data stored in the structure and concatenating it into a string so nothing changes apart from the conversion.
pub fn unpack_request(request: Http1Request) -> String {
let mut unpacked = format!("{} {} HTTP/{}\r\n", request.method, unwrap_url(request.location).replace("https://root/","/"), request.version);
for header in request.headers {
unpacked = format!("{}{}: {}\r\n", unpacked, header.key, header.value);
}
unpacked = format!("{}\r\n{}",unpacked,request.body);
return unpacked;
}
/// A function for converting Http1Response structures into HTTP/1.x response strings.
/// Unpacking functions are simply taking the data stored in the structure and concatenating it into a string so nothing changes apart from the conversion.
pub fn unpack_response(response: Http1Response) -> String {
let mut unpacked = format!("HTTP/{} {}\r\n", response.version, response.status);
for header in response.headers {
unpacked = format!("{}{}: {}\r\n", unpacked, header.key, header.value);
}
unpacked = format!("{}\r\n{}",unpacked,response.body);
return unpacked;
}
/// A function for downgrading request structures to HTTP/1.0, this is destructive as it removes the keep-alive and Host headers for compatibility.
/// I don't recommend using this unless you have a specific purpose for downgrading (like accommodating strict HTTP/1.0 clients).
pub fn downgrade_request(mut request: Http1Request) -> Http1Request {
let mut i: usize = 0;
let mut earmark: Vec<usize> = vec![];
for header in &request.headers {
if header.key == "Connection" && header.value == "keep-alive" {
earmark.push(i);
}
if header.key == "Host" {
earmark.push(i);
}
i = i + 1
}
for i in earmark {
request.headers.remove(i);
}
request.version = "1.0".to_string();
return request
}
/// A function for upgrading request structures to HTTP/1.1, this function doesn't remove any headers.
/// It adds on Host and keep-alive headers at user request.
pub fn upgrade_request(mut request: Http1Request, host: &str, keepalive: bool) -> Http1Request {
if keepalive {
request.headers.push(generate_kvheader("Connection", "keep-alive"));
}
if host != "" {
request.headers.push(generate_kvheader("Host", host));
}
request.version = "1.1".to_string();
return request
}
/// A lookup table-like function for switching i32 codes for their human-readable string equivalent.
fn code_to_string(code: i32) -> String {
match code {
100 => "100 Continue".to_string(),
101 => "101 Switching Protocols".to_string(),
102 => "102 Processing".to_string(),
103 => "103 Early Hints".to_string(),
200 => "200 OK".to_string(),
201 => "201 Created".to_string(),
202 => "202 Accepted".to_string(),
203 => "203 Non-Authoritative Information".to_string(),
204 => "204 No Content".to_string(),
205 => "205 Reset Content".to_string(),
206 => "206 Partial Content".to_string(),
207 => "207 Multi-Status".to_string(),
208 => "208 Already Reported".to_string(),
226 => "226 IM Used".to_string(),
300 => "300 Multiple Choices".to_string(),
301 => "301 Moved Permanently".to_string(),
302 => "302 Found".to_string(),
303 => "303 See Other".to_string(),
304 => "304 Not Modified".to_string(),
305 => "305 Use Proxy".to_string(),
306 => "306 Switch Proxy".to_string(),
307 => "307 Temporary Redirect".to_string(),
308 => "308 Permanent Redirect".to_string(),
400 => "400 Bad Request".to_string(),
401 => "401 Unauthorized".to_string(),
402 => "402 Payment Required".to_string(),
403 => "403 Forbidden".to_string(),
404 => "404 Not Found".to_string(),
405 => "405 Method Not Allowed".to_string(),
406 => "406 Not Acceptable".to_string(),
407 => "407 Proxy Authentication Required".to_string(),
408 => "408 Request Timeout".to_string(),
409 => "409 Conflict".to_string(),
410 => "410 Gone".to_string(),
411 => "411 Length Required".to_string(),
412 => "412 Precondition Failed".to_string(),
413 => "413 Payload Too Large".to_string(),
414 => "414 URI Too Long".to_string(),
415 => "415 Unsupported Media Type".to_string(),
416 => "416 Range Not Satisfiable".to_string(),
417 => "417 Expectation Failed".to_string(),
418 => "418 I'm a teapot".to_string(),
421 => "421 Misdirected Request".to_string(),
422 => "422 Unprocessable Entity".to_string(),
423 => "423 Locked".to_string(),
424 => "424 Failed Dependency".to_string(),
425 => "425 Too Early".to_string(),
426 => "426 Upgrade Required".to_string(),
428 => "428 Precondition Required".to_string(),
429 => "429 Too Many Requests".to_string(),
431 => "431 Request Header Fields Too Large".to_string(),
451 => "451 Unavailable For Legal Reasons".to_string(),
500 => "500 Internal Server Error".to_string(),
501 => "501 Not Implemented".to_string(),
502 => "502 Bad Gateway".to_string(),
503 => "503 Service Unavailable".to_string(),
504 => "504 Gateway Timeout".to_string(),
505 => "505 HTTP Version Not Supported".to_string(),
506 => "506 Variant Also Negotiates".to_string(),
507 => "507 Insufficient Storage".to_string(),
508 => "508 Loop Detected".to_string(),
510 => "510 Not Extended".to_string(),
511 => "511 Network Authentication Required".to_string(),
_ => "500 Internal Server Error".to_string(),
}
}