/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-proto-0.5.3/src/client/sendreq.rs
Line | Count | Source |
1 | | use std::io::Write; |
2 | | |
3 | | use base64::prelude::BASE64_STANDARD; |
4 | | use base64::Engine; |
5 | | use http::uri::Scheme; |
6 | | use http::{header, HeaderMap, HeaderName, HeaderValue, Method, Uri, Version}; |
7 | | |
8 | | use crate::client::amended::AmendedRequest; |
9 | | use crate::ext::{AuthorityExt, MethodExt, SchemeExt}; |
10 | | use crate::util::Writer; |
11 | | use crate::Error; |
12 | | |
13 | | use super::state::SendRequest; |
14 | | use super::{BodyState, Call, RequestPhase, SendRequestResult}; |
15 | | |
16 | | impl Call<SendRequest> { |
17 | | /// Write the request to the buffer. |
18 | | /// |
19 | | /// Writes incrementally, it can be called repeatedly in situations where the output |
20 | | /// buffer is small. |
21 | | /// |
22 | | /// This includes the first row, i.e. `GET / HTTP/1.1` and all headers. |
23 | | /// The output buffer needs to be large enough for the longest row. |
24 | | /// |
25 | | /// Example: |
26 | | /// |
27 | | /// ```text |
28 | | /// POST /bar HTTP/1.1\r\n |
29 | | /// Host: my.server.test\r\n |
30 | | /// User-Agent: myspecialthing\r\n |
31 | | /// \r\n |
32 | | /// <body data> |
33 | | /// ``` |
34 | | /// |
35 | | /// The buffer would need to be at least 28 bytes big, since the `User-Agent` row is |
36 | | /// 28 bytes long. |
37 | | /// |
38 | | /// If the output is too small for the longest line, the result is an `OutputOverflow` error. |
39 | | /// |
40 | | /// The `Ok(usize)` is the number of bytes of the `output` buffer that was used. |
41 | 0 | pub fn write(&mut self, output: &mut [u8]) -> Result<usize, Error> { |
42 | 0 | self.maybe_analyze_request()?; |
43 | | |
44 | 0 | let mut w = Writer::new(output); |
45 | 0 | try_write_prelude(&self.inner.request, &mut self.inner.state, &mut w)?; |
46 | | |
47 | 0 | let output_used = w.len(); |
48 | | |
49 | 0 | Ok(output_used) |
50 | 0 | } |
51 | | |
52 | | /// The configured method. |
53 | 0 | pub fn method(&self) -> &Method { |
54 | 0 | self.inner.request.method() |
55 | 0 | } |
56 | | |
57 | | /// The uri being requested. |
58 | 0 | pub fn uri(&self) -> &Uri { |
59 | 0 | self.inner.request.uri() |
60 | 0 | } |
61 | | |
62 | | /// Version of the request. |
63 | | /// |
64 | | /// This can only be 1.0 or 1.1. |
65 | 0 | pub fn version(&self) -> Version { |
66 | 0 | self.inner.request.version() |
67 | 0 | } |
68 | | |
69 | | /// The configured headers. |
70 | 0 | pub fn headers_map(&mut self) -> Result<HeaderMap, Error> { |
71 | 0 | self.maybe_analyze_request()?; |
72 | 0 | let mut map = HeaderMap::new(); |
73 | 0 | for (k, v) in self.inner.request.headers() { |
74 | 0 | map.insert(k, v.clone()); |
75 | 0 | } |
76 | 0 | Ok(map) |
77 | 0 | } |
78 | | |
79 | | /// Check whether the entire request has been sent. |
80 | | /// |
81 | | /// This is useful when the output buffer is small and we need to repeatedly |
82 | | /// call `write()` to send the entire request. |
83 | 0 | pub fn can_proceed(&self) -> bool { |
84 | 0 | !self.inner.state.phase.is_prelude() |
85 | 0 | } |
86 | | |
87 | | /// Attempt to proceed from this state to the next. |
88 | | /// |
89 | | /// Returns `None` if the entire request has not been sent. It is guaranteed that if |
90 | | /// `can_proceed()` returns `true`, this will return `Some`. |
91 | 0 | pub fn proceed(mut self) -> Result<Option<SendRequestResult>, Error> { |
92 | 0 | if !self.can_proceed() { |
93 | 0 | return Ok(None); |
94 | 0 | } |
95 | | |
96 | 0 | if self.inner.state.writer.has_body() { |
97 | 0 | if self.inner.await_100_continue { |
98 | 0 | Ok(Some(SendRequestResult::Await100(Call::wrap(self.inner)))) |
99 | | } else { |
100 | | // TODO(martin): is this needed? |
101 | 0 | self.maybe_analyze_request()?; |
102 | 0 | let call = Call::wrap(self.inner); |
103 | 0 | Ok(Some(SendRequestResult::SendBody(call))) |
104 | | } |
105 | | } else { |
106 | 0 | let call = Call::wrap(self.inner); |
107 | 0 | Ok(Some(SendRequestResult::RecvResponse(call))) |
108 | | } |
109 | 0 | } |
110 | | |
111 | 0 | pub(crate) fn maybe_analyze_request(&mut self) -> Result<(), Error> { |
112 | 0 | if self.inner.analyzed { |
113 | 0 | return Ok(()); |
114 | 0 | } |
115 | | |
116 | 0 | let info = self.inner.request.analyze( |
117 | 0 | self.inner.state.writer, |
118 | 0 | self.inner.state.allow_non_standard_methods, |
119 | 0 | )?; |
120 | | |
121 | 0 | let method = self.inner.request.method(); |
122 | 0 | let send_body = (method.allow_request_body() || self.inner.force_send_body) |
123 | 0 | && info.body_mode.has_body(); |
124 | | |
125 | 0 | if !send_body && info.body_mode.has_body() { |
126 | 0 | return Err(Error::BodyNotAllowed); |
127 | 0 | } |
128 | | |
129 | 0 | if !info.req_host_header && method != Method::CONNECT { |
130 | 0 | if let Some(host) = self.inner.request.uri().host() { |
131 | | // User did not set a host header, and there is one in uri, we set that. |
132 | | // We need an owned value to set the host header. |
133 | | |
134 | | // This might append the port if it differs from the scheme default. |
135 | 0 | let value = maybe_with_port(host, self.inner.request.uri())?; |
136 | | |
137 | 0 | self.inner.request.set_header(header::HOST, value)?; |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | 0 | if let Some(auth) = self.inner.request.uri().authority() { |
142 | 0 | if self.inner.request.method() != Method::CONNECT { |
143 | 0 | if auth.userinfo().is_some() && !info.req_auth_header { |
144 | 0 | let user = auth.username().unwrap_or_default(); |
145 | 0 | let pass = auth.password().unwrap_or_default(); |
146 | 0 | let creds = BASE64_STANDARD.encode(format!("{}:{}", user, pass)); |
147 | 0 | let auth = format!("Basic {}", creds); |
148 | 0 | self.inner.request.set_header(header::AUTHORIZATION, auth)?; |
149 | 0 | } |
150 | 0 | } else if !info.req_host_header { |
151 | 0 | self.inner |
152 | 0 | .request |
153 | 0 | .set_header(header::HOST, auth.clone().as_str())?; |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | 0 | if !info.req_body_header && info.body_mode.has_body() { |
158 | | // User did not set a body header, we set one. |
159 | 0 | let header = info.body_mode.body_header(); |
160 | 0 | self.inner.request.set_header(header.0, header.1)?; |
161 | 0 | } |
162 | | |
163 | 0 | self.inner.state.writer = info.body_mode; |
164 | | |
165 | 0 | self.inner.analyzed = true; |
166 | 0 | Ok(()) |
167 | 0 | } |
168 | | } |
169 | | |
170 | 0 | fn maybe_with_port(host: &str, uri: &Uri) -> Result<HeaderValue, Error> { |
171 | 0 | fn from_str(src: &str) -> Result<HeaderValue, Error> { |
172 | 0 | HeaderValue::from_str(src).map_err(|e| Error::BadHeader(e.to_string())) |
173 | 0 | } |
174 | | |
175 | 0 | if let Some(port) = uri.port() { |
176 | 0 | let scheme = uri.scheme().unwrap_or(&Scheme::HTTP); |
177 | 0 | if let Some(scheme_default) = scheme.default_port() { |
178 | 0 | if port != scheme_default { |
179 | | // This allocates, so we only do it if we absolutely have to. |
180 | 0 | let host_port = format!("{}:{}", host, port); |
181 | 0 | return from_str(&host_port); |
182 | 0 | } |
183 | 0 | } |
184 | 0 | } |
185 | | |
186 | | // Fall back on no port (without allocating). |
187 | 0 | from_str(host) |
188 | 0 | } |
189 | | |
190 | 0 | fn try_write_prelude( |
191 | 0 | request: &AmendedRequest, |
192 | 0 | state: &mut BodyState, |
193 | 0 | w: &mut Writer, |
194 | 0 | ) -> Result<(), Error> { |
195 | 0 | let at_start = w.len(); |
196 | | |
197 | | loop { |
198 | 0 | if try_write_prelude_part(request, state, w) { |
199 | 0 | continue; |
200 | 0 | } |
201 | | |
202 | 0 | let written = w.len() - at_start; |
203 | | |
204 | 0 | if written > 0 || state.phase.is_body() { |
205 | 0 | return Ok(()); |
206 | | } else { |
207 | 0 | return Err(Error::OutputOverflow); |
208 | | } |
209 | | } |
210 | 0 | } |
211 | | |
212 | 0 | fn try_write_prelude_part(request: &AmendedRequest, state: &mut BodyState, w: &mut Writer) -> bool { |
213 | 0 | match &mut state.phase { |
214 | | RequestPhase::Line => { |
215 | 0 | let success = do_write_send_line(request.prelude(), w); |
216 | 0 | if success { |
217 | 0 | state.phase = RequestPhase::Headers(0); |
218 | 0 | } |
219 | 0 | success |
220 | | } |
221 | | |
222 | 0 | RequestPhase::Headers(index) => { |
223 | 0 | let header_count = request.headers_len(); |
224 | 0 | let all = request.headers(); |
225 | 0 | let skipped = all.skip(*index); |
226 | | |
227 | 0 | if header_count > 0 { |
228 | 0 | do_write_headers(skipped, index, header_count - 1, w); |
229 | 0 | } |
230 | | |
231 | 0 | if *index == header_count { |
232 | 0 | state.phase = RequestPhase::Body; |
233 | 0 | } |
234 | 0 | false |
235 | | } |
236 | | |
237 | | // We're past the header. |
238 | 0 | _ => false, |
239 | | } |
240 | 0 | } |
241 | | |
242 | 0 | fn do_write_send_line(line: (&Method, &str, Version), w: &mut Writer) -> bool { |
243 | | // Ensure origin-form request-target starts with "/" when only a query is present |
244 | | // per RFC 9112 ยง3.2.1 (@https://datatracker.ietf.org/doc/html/rfc9112#section-3.2.1). |
245 | 0 | let need_initial_slash = line.1.starts_with('?'); |
246 | 0 | let slash = if need_initial_slash { "/" } else { "" }; |
247 | | |
248 | 0 | w.try_write(|w| write!(w, "{} {}{} {:?}\r\n", line.0, slash, line.1, line.2)) |
249 | 0 | } |
250 | | |
251 | 0 | fn do_write_headers<'a, I>(headers: I, index: &mut usize, last_index: usize, w: &mut Writer) |
252 | 0 | where |
253 | 0 | I: Iterator<Item = (&'a HeaderName, &'a HeaderValue)>, |
254 | | { |
255 | 0 | for h in headers { |
256 | 0 | let success = w.try_write(|w| { |
257 | 0 | write!(w, "{}: ", h.0)?; |
258 | 0 | w.write_all(h.1.as_bytes())?; |
259 | 0 | write!(w, "\r\n")?; |
260 | 0 | if *index == last_index { |
261 | 0 | write!(w, "\r\n")?; |
262 | 0 | } |
263 | 0 | Ok(()) |
264 | 0 | }); |
265 | | |
266 | 0 | if success { |
267 | 0 | *index += 1; |
268 | 0 | } else { |
269 | 0 | break; |
270 | | } |
271 | | } |
272 | 0 | } |