//! 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, 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, 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 = 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(), } }