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
use crate::js_values;
use crate::{Error, Method};
use serde::de::DeserializeOwned;
use std::borrow::Cow;
use url::Url;
use wasm_bindgen::JsValue;

/// Incoming HTTP request (to Worker).
#[derive(Debug, Clone)]
pub struct Request {
    method: Method,
    url: Url,
    headers: web_sys::Headers,
    body: Option<Vec<u8>>,
}
unsafe impl Sync for Request {}

impl Request {
    /// Creates Request object representing incoming HTTP request
    pub fn new(
        method: Method,
        url: Url,
        headers: web_sys::Headers,
        body: Option<Vec<u8>>,
    ) -> Request {
        Request {
            method,
            url,
            headers,
            body,
        }
    }

    /// Creates Request from javascript object
    pub(crate) fn from_js(map: &js_sys::Map) -> Result<Self, JsValue> {
        Ok(Request::new(
            Method::from(
                &js_values::get_map_str(&map, "method")
                    .ok_or_else(|| JsValue::from_str("invalid_req.method"))?,
            )?,
            Url::parse(
                &js_values::get_map_str(&map, "url")
                    .ok_or_else(|| JsValue::from_str("invalid_req.url"))?,
            )
            .map_err(|e| JsValue::from_str(&format!("invalid req.url:{}", e.to_string())))?,
            js_values::get_map_headers(&map, "headers")
                .ok_or_else(|| JsValue::from_str("invalid_req"))?,
            js_values::get_map_bytes(&map, "body"),
        ))
    }

    /// Returns the HTTP method
    pub fn method(&self) -> Method {
        self.method
    }

    /// Returns the parsed url
    pub fn url(&self) -> &Url {
        &self.url
    }

    /// Returns the set of request headers
    pub fn headers(&self) -> &web_sys::Headers {
        &self.headers
    }

    /// Returns the value of the header, or None if the header is not set.
    /// Header name search is case-insensitive.
    pub fn get_header(&self, name: &str) -> Option<String> {
        match self.headers.get(name) {
            Ok(v) => v,
            Err(_) => None,
        }
    }

    /// Returns true if the header is set. Name is case-insensitive.
    pub fn has_header(&self, name: &str) -> bool {
        self.headers.has(name).unwrap_or(false)
    }

    /// Returns request body as byte vector, or None if body is empty
    pub fn body(&self) -> Option<&Vec<u8>> {
        self.body.as_ref()
    }

    /// Interpret body as json object.
    pub fn json<T: DeserializeOwned>(&self) -> Result<T, Error> {
        if let Some(vec) = self.body.as_ref() {
            Ok(serde_json::from_slice(vec)?)
        } else {
            Err(Error::Other("body is empty".to_string()))
        }
    }

    /// Returns the cookie string, if set
    pub fn get_cookie_value(&self, cookie_name: &str) -> Option<String> {
        self.get_header("cookie")
            .map(|cookie| {
                (&cookie)
                    .split(';')
                    // allow spaces around ';'
                    .map(|s| s.trim())
                    // if name=value, return value
                    .find_map(|part| cookie_value(part, cookie_name))
                    .map(|v| v.to_string())
            })
            .unwrap_or_default()
    }

    /// returns the query variable from the url, or None if not found
    pub fn get_query_value<'req>(&'req self, key: &'_ str) -> Option<Cow<'req, str>> {
        self.url()
            .query_pairs()
            .find(|(k, _)| k == key)
            .map(|(_, v)| v)
    }
}

// If 'part' is of the form 'name=value', return value
fn cookie_value<'cookie>(part: &'cookie str, name: &str) -> Option<&'cookie str> {
    if part.len() > name.len() {
        let (left, right) = part.split_at(name.len());
        if left == name && right.starts_with('=') {
            return Some(&right[1..]);
        }
    }
    None
}

#[test]
// test cookie_value function. Additional tests of Request are in tests/request.rs
fn test_cookie_value() {
    // short value
    assert_eq!(cookie_value("x=y", "x"), Some("y"));

    // longer value
    assert_eq!(cookie_value("foo=bar", "foo"), Some("bar"));

    // missing value
    assert_eq!(cookie_value("x=y", "z"), None);

    // empty value
    assert_eq!(cookie_value("foo=", "foo"), Some(""));
}