1use std::collections::HashMap;
5use std::io::Read;
6
7use url::Url;
8
9use go_lib::context::Context;
10use crate::error::HttpError;
11use crate::header::Header;
12use crate::parse::transfer::Body;
13
14pub struct Request {
17 pub method: String,
19 pub url: Url,
21 pub proto: String,
23 pub proto_major: u8,
24 pub proto_minor: u8,
25 pub header: Header,
27 pub body: Option<Body>,
29 pub content_length: i64,
31 pub transfer_encoding: Vec<String>,
33 pub host: String,
35 pub trailer: Header,
37 pub remote_addr: String,
39 pub path_params: HashMap<String, String>,
42 form: Option<HashMap<String, Vec<String>>>,
44 ctx: Context,
46}
47
48impl Request {
49 pub fn new(method: &str, url: &str, body: Option<Body>) -> Result<Self, HttpError> {
54 let ctx = go_lib::context::background();
55 Self::new_with_context(method, url, body, ctx)
56 }
57
58 pub fn new_with_context(
61 method: &str,
62 url: &str,
63 body: Option<Body>,
64 ctx: Context,
65 ) -> Result<Self, HttpError> {
66 if !crate::method::is_valid(method) {
67 return Err(HttpError::InvalidUrl(format!("invalid method: {method}")));
68 }
69 let parsed = Url::parse(url).map_err(|e| HttpError::InvalidUrl(e.to_string()))?;
70 let host = parsed.host_str().unwrap_or("").to_owned();
71 let content_length = match &body {
72 None => 0,
73 Some(_) => -1,
74 };
75 Ok(Self {
76 method: method.to_owned(),
77 url: parsed,
78 proto: "HTTP/1.1".into(),
79 proto_major: 1,
80 proto_minor: 1,
81 header: Header::new(),
82 body,
83 content_length,
84 transfer_encoding: Vec::new(),
85 host,
86 trailer: Header::new(),
87 remote_addr: String::new(),
88 path_params: HashMap::new(),
89 form: None,
90 ctx,
91 })
92 }
93
94 pub fn context(&self) -> &Context {
97 &self.ctx
98 }
99
100 pub fn with_context(mut self, ctx: Context) -> Self {
103 self.ctx = ctx;
104 self
105 }
106
107 pub fn path_value(&self, name: &str) -> &str {
113 self.path_params.get(name).map(String::as_str).unwrap_or("")
114 }
115
116 pub fn user_agent(&self) -> &str {
119 self.header.get("User-Agent").unwrap_or("")
120 }
121
122 pub fn referer(&self) -> &str {
123 self.header.get("Referer").unwrap_or("")
124 }
125
126 pub fn basic_auth(&self) -> Option<(String, String)> {
129 let val = self.header.get("Authorization")?;
130 let rest = val.strip_prefix("Basic ")?;
131 let decoded = base64::Engine::decode(
132 &base64::engine::general_purpose::STANDARD,
133 rest.trim(),
134 )
135 .ok()?;
136 let s = String::from_utf8(decoded).ok()?;
137 let colon = s.find(':')?;
138 Some((s[..colon].to_owned(), s[colon + 1..].to_owned()))
139 }
140
141 pub fn cookies(&self) -> Vec<crate::cookie::Cookie> {
145 crate::cookie::parse_request_cookies(&self.header)
146 }
147
148 pub fn cookie(&self, name: &str) -> Option<crate::cookie::Cookie> {
150 self.cookies().into_iter().find(|c| c.name == name)
151 }
152
153 pub fn body_bytes(&mut self) -> Result<Vec<u8>, HttpError> {
158 let mut out = Vec::new();
159 if let Some(ref mut body) = self.body {
160 body.read_to_end(&mut out).map_err(|_| HttpError::BodyRead)?;
161 self.trailer = body.trailers().clone();
162 }
163 Ok(out)
164 }
165
166 pub fn trailers(&self) -> &Header {
169 &self.trailer
170 }
171
172 pub fn parse_form(&mut self) -> Result<(), HttpError> {
177 if self.form.is_some() {
178 return Ok(());
179 }
180 let mut values: HashMap<String, Vec<String>> = HashMap::new();
181
182 for (k, v) in self.url.query_pairs() {
184 values.entry(k.into_owned()).or_default().push(v.into_owned());
185 }
186
187 let ct = self
189 .header
190 .get("Content-Type")
191 .unwrap_or("")
192 .to_ascii_lowercase();
193 if matches!(self.method.as_str(), "POST" | "PUT" | "PATCH")
194 && ct.starts_with("application/x-www-form-urlencoded")
195 && let Some(body) = self.body.take()
196 {
197 let mut raw = Vec::new();
198 BodyReader(body)
199 .read_to_end(&mut raw)
200 .map_err(|_| HttpError::BodyRead)?;
201 let s = String::from_utf8_lossy(&raw);
202 for pair in s.split('&') {
203 if let Some((k, v)) = pair.split_once('=') {
204 let k = url_decode(k);
205 let v = url_decode(v);
206 values.entry(k).or_default().push(v);
207 }
208 }
209 }
210
211 self.form = Some(values);
212 Ok(())
213 }
214
215 pub fn form_value(&self, key: &str) -> Option<&str> {
217 self.form
218 .as_ref()?
219 .get(key)?
220 .first()
221 .map(String::as_str)
222 }
223
224 pub fn write_header_to(&self, w: &mut impl std::io::Write) -> Result<(), HttpError> {
229 let path = if self.url.path().is_empty() { "/" } else { self.url.path() };
230 let query = self
231 .url
232 .query()
233 .map(|q| format!("?{q}"))
234 .unwrap_or_default();
235
236 write!(w, "{} {}{} {}\r\n", self.method, path, query, self.proto)?;
237 write!(w, "Host: {}\r\n", self.host)?;
238 self.header.write_to(w)?;
239 w.write_all(b"\r\n")?;
240 Ok(())
241 }
242
243 pub fn write_header_absolute_to(&self, w: &mut impl std::io::Write) -> Result<(), HttpError> {
247 write!(w, "{} {} {}\r\n", self.method, self.absolute_target(), self.proto)?;
248 write!(w, "Host: {}\r\n", self.host)?;
249 self.header.write_to(w)?;
250 w.write_all(b"\r\n")?;
251 Ok(())
252 }
253
254 fn absolute_target(&self) -> String {
257 let u = &self.url;
258 let authority = match (u.host_str(), u.port()) {
259 (Some(h), Some(p)) => format!("{h}:{p}"),
260 (Some(h), None) => h.to_owned(),
261 (None, _) => String::new(),
262 };
263 let path = if u.path().is_empty() { "/" } else { u.path() };
264 let query = u.query().map(|q| format!("?{q}")).unwrap_or_default();
265 format!("{}://{}{}{}", u.scheme(), authority, path, query)
266 }
267}
268
269struct BodyReader(Body);
271impl Read for BodyReader {
272 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
273 self.0.read(buf)
274 }
275}
276
277fn url_decode(s: &str) -> String {
278 let s = s.replace('+', " ");
280 url::form_urlencoded::parse(s.as_bytes())
281 .map(|(k, _)| k.into_owned())
282 .next()
283 .unwrap_or(s.clone())
284}
285
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn new_get_request() {
293 let req = Request::new("GET", "http://example.com/path?q=1", None).unwrap();
294 assert_eq!(req.method, "GET");
295 assert_eq!(req.host, "example.com");
296 assert_eq!(req.url.path(), "/path");
297 }
298
299 #[test]
300 fn invalid_method_rejected() {
301 assert!(Request::new("GÉT", "http://example.com/", None).is_err());
302 }
303
304 #[test]
305 fn write_header() {
306 let mut req = Request::new("GET", "http://example.com/", None).unwrap();
307 req.header.set("Accept", "text/html");
308 let mut out = Vec::new();
309 req.write_header_to(&mut out).unwrap();
310 let s = String::from_utf8(out).unwrap();
311 assert!(s.starts_with("GET / HTTP/1.1\r\n"));
312 assert!(s.contains("Host: example.com\r\n"));
313 assert!(s.contains("Accept: text/html\r\n"));
314 }
315
316 #[test]
317 fn write_header_includes_query() {
318 let req = Request::new("GET", "http://example.com/search?q=rust&n=1", None).unwrap();
319 let mut out = Vec::new();
320 req.write_header_to(&mut out).unwrap();
321 let s = String::from_utf8(out).unwrap();
322 assert!(s.starts_with("GET /search?q=rust&n=1 HTTP/1.1\r\n"), "got: {s:?}");
323 }
324
325 #[test]
326 fn body_sets_content_length_unknown() {
327 let body = Body::Unbounded(Box::new(std::io::Cursor::new(b"abc".to_vec())));
328 let req = Request::new("POST", "http://example.com/", Some(body)).unwrap();
329 assert_eq!(req.content_length, -1);
330 let none = Request::new("GET", "http://example.com/", None).unwrap();
331 assert_eq!(none.content_length, 0);
332 }
333
334 #[test]
335 fn parse_form_query_only() {
336 let mut req = Request::new("GET", "http://x/p?a=1&a=2&b=hello", None).unwrap();
337 req.parse_form().unwrap();
338 assert_eq!(req.form_value("a"), Some("1"));
339 assert_eq!(req.form_value("b"), Some("hello"));
340 assert_eq!(req.form_value("missing"), None);
341 req.parse_form().unwrap();
343 assert_eq!(req.form_value("a"), Some("1"));
344 }
345
346 #[test]
347 fn parse_form_urlencoded_body() {
348 let body = Body::Unbounded(Box::new(std::io::Cursor::new(
349 b"name=Alice&city=New+York".to_vec(),
350 )));
351 let mut req = Request::new("POST", "http://x/submit?q=top", Some(body)).unwrap();
352 req.header.set("Content-Type", "application/x-www-form-urlencoded");
353 req.parse_form().unwrap();
354 assert_eq!(req.form_value("q"), Some("top"));
356 assert_eq!(req.form_value("name"), Some("Alice"));
357 assert_eq!(req.form_value("city"), Some("New York"));
358 }
359
360 #[test]
361 fn basic_auth_valid() {
362 use base64::Engine;
363 let creds = base64::engine::general_purpose::STANDARD.encode("alice:s3cret");
364 let mut req = Request::new("GET", "http://x/", None).unwrap();
365 req.header.set("Authorization", format!("Basic {creds}"));
366 let (user, pass) = req.basic_auth().unwrap();
367 assert_eq!(user, "alice");
368 assert_eq!(pass, "s3cret");
369 }
370
371 #[test]
372 fn basic_auth_absent_or_wrong_scheme() {
373 let req = Request::new("GET", "http://x/", None).unwrap();
374 assert!(req.basic_auth().is_none());
375
376 let mut bearer = Request::new("GET", "http://x/", None).unwrap();
377 bearer.header.set("Authorization", "Bearer token123");
378 assert!(bearer.basic_auth().is_none());
379 }
380
381 #[test]
382 fn user_agent_and_referer() {
383 let mut req = Request::new("GET", "http://x/", None).unwrap();
384 assert_eq!(req.user_agent(), "");
385 assert_eq!(req.referer(), "");
386 req.header.set("User-Agent", "go-http/0.1");
387 req.header.set("Referer", "http://ref/");
388 assert_eq!(req.user_agent(), "go-http/0.1");
389 assert_eq!(req.referer(), "http://ref/");
390 }
391
392 #[test]
393 fn cookies_parsed_from_header() {
394 let mut req = Request::new("GET", "http://x/", None).unwrap();
395 req.header.set("Cookie", "session=abc; theme=dark");
396 let all = req.cookies();
397 assert_eq!(all.len(), 2);
398 assert_eq!(req.cookie("session").unwrap().value, "abc");
399 assert_eq!(req.cookie("theme").unwrap().value, "dark");
400 assert!(req.cookie("nope").is_none());
401 }
402
403 #[test]
404 fn with_context_replaces_context() {
405 let req = Request::new("GET", "http://x/", None).unwrap();
406 let ctx = go_lib::context::background();
407 let req = req.with_context(ctx);
408 let _ = req.context();
410 }
411}