diff --git a/Cargo.toml b/Cargo.toml index 89efc92..ef55204 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "packeteer" description = "An attempt at a Rust library that can be used to assist in programmatically analysing, serving and handling received protocol packets." -version = "0.1.3" +version = "0.1.4" edition = "2021" authors = ["Celeste "] license = "LGPL-3.0-or-later" diff --git a/src/http1.rs b/src/http1.rs new file mode 100644 index 0000000..f580ec8 --- /dev/null +++ b/src/http1.rs @@ -0,0 +1,232 @@ +//! 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}; +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: String, + 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: location.to_string(), 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. +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: "".to_string(), 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 = 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 = 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. +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, request.location, 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(), + } +} + diff --git a/src/lib.rs b/src/lib.rs index 5fcf767..c8eab0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,238 +43,5 @@ pub fn generate_kvheader(key: &str, value: &str) -> KVHeader { return result } -/// 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. #[cfg(feature = "http1")] -pub mod http1 { - use super::{KVHeader, generate_kvheader}; - 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: String, - 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: location.to_string(), 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. - 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: "".to_string(), 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 = 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 = 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. - 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, request.location, 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(), - } - } -} +pub mod http1;