logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use std::fmt;

use crate::{Request, Data};
use crate::http::{Status, Method};
use crate::http::uri::Origin;

use super::{Client, LocalResponse};

/// An `async` local request as returned by [`Client`](super::Client).
///
/// For details, see [the top-level documentation](../index.html#localrequest).
///
/// ## Example
///
/// The following snippet uses the available builder methods to construct and
/// dispatch a `POST` request to `/` with a JSON body:
///
/// ```rust,no_run
/// use rocket::local::asynchronous::{Client, LocalRequest};
/// use rocket::http::{ContentType, Cookie};
///
/// # rocket::async_test(async {
/// let client = Client::tracked(rocket::build()).await.expect("valid rocket");
/// let req = client.post("/")
///     .header(ContentType::JSON)
///     .remote("127.0.0.1:8000".parse().unwrap())
///     .cookie(Cookie::new("name", "value"))
///     .body(r#"{ "value": 42 }"#);
///
/// let response = req.dispatch().await;
/// # });
/// ```
pub struct LocalRequest<'c> {
    pub(in super) client: &'c Client,
    pub(in super) request: Request<'c>,
    data: Vec<u8>,
    // The `Origin` on the right is INVALID! It should _not_ be used!
    uri: Result<Origin<'c>, Origin<'static>>,
}

impl<'c> LocalRequest<'c> {
    pub(crate) fn new<'u: 'c, U>(client: &'c Client, method: Method, uri: U) -> Self
        where U: TryInto<Origin<'u>> + fmt::Display
    {
        // Try to parse `uri` into an `Origin`, storing whether it's good.
        let uri_str = uri.to_string();
        let try_origin = uri.try_into().map_err(|_| Origin::path_only(uri_str));

        // Create a request. We'll handle bad URIs later, in `_dispatch`.
        let origin = try_origin.clone().unwrap_or_else(|bad| bad);
        let mut request = Request::new(client.rocket(), method, origin);

        // Add any cookies we know about.
        if client.tracked {
            client._with_raw_cookies(|jar| {
                for cookie in jar.iter() {
                    request.cookies_mut().add_original(cookie.clone());
                }
            })
        }

        LocalRequest { client, request, uri: try_origin, data: vec![] }
    }

    pub(crate) fn _request(&self) -> &Request<'c> {
        &self.request
    }

    pub(crate) fn _request_mut(&mut self) -> &mut Request<'c> {
        &mut self.request
    }

    pub(crate) fn _body_mut(&mut self) -> &mut Vec<u8> {
        &mut self.data
    }

    // Performs the actual dispatch.
    async fn _dispatch(mut self) -> LocalResponse<'c> {
        // First, revalidate the URI, returning an error response (generated
        // from an error catcher) immediately if it's invalid. If it's valid,
        // then `request` already contains a correct URI.
        let rocket = self.client.rocket();
        if let Err(ref invalid) = self.uri {
            // The user may have changed the URI in the request in which case we
            // _shouldn't_ error. Check that now and error only if not.
            if self.inner().uri() == invalid {
                error!("invalid request URI: {:?}", invalid.path());
                return LocalResponse::new(self.request, move |req| {
                    rocket.handle_error(Status::BadRequest, req)
                }).await
            }
        }

        // Actually dispatch the request.
        let mut data = Data::local(self.data);
        let token = rocket.preprocess_request(&mut self.request, &mut data).await;
        let response = LocalResponse::new(self.request, move |req| {
            rocket.dispatch(token, req, data)
        }).await;

        // If the client is tracking cookies, updates the internal cookie jar
        // with the changes reflected by `response`.
        if self.client.tracked {
            self.client._with_raw_cookies_mut(|jar| {
                let current_time = time::OffsetDateTime::now_utc();
                for cookie in response.cookies().iter() {
                    if let Some(expires) = cookie.expires_datetime() {
                        if expires <= current_time {
                            jar.force_remove(cookie);
                            continue;
                        }
                    }

                    jar.add_original(cookie.clone());
                }
            })
        }

        response
    }

    pub_request_impl!("# use rocket::local::asynchronous::Client;\n\
        use rocket::local::asynchronous::LocalRequest;" async await);
}

impl<'c> Clone for LocalRequest<'c> {
    fn clone(&self) -> Self {
        LocalRequest {
            client: self.client,
            request: self.request.clone(),
            data: self.data.clone(),
            uri: self.uri.clone(),
        }
    }
}

impl std::fmt::Debug for LocalRequest<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self._request().fmt(f)
    }
}

impl<'c> std::ops::Deref for LocalRequest<'c> {
    type Target = Request<'c>;

    fn deref(&self) -> &Self::Target {
        self.inner()
    }
}

impl<'c> std::ops::DerefMut for LocalRequest<'c> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.inner_mut()
    }
}