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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//! A CORS middleware for Iron.
//!
//! See https://www.html5rocks.com/static/images/cors_server_flowchart.png for
//! reference.
//!
//! The middleware will return `HTTP 400 Bad Request` if the Origin host is
//! missing or not allowed.
//!
//! Preflight requests are not yet supported.
//!
//! # Usage
//!
//! There are two modes available:
//!
//! ## Mode 1: Whitelist
//!
//! The user of the middleware must specify a list of allowed hosts (port or
//! protocol aren't being checked by the middleware). The wrapped handler will only
//! be executed if the hostname in the `Origin` header matches one of the allowed
//! hosts. Requests without an `Origin` header will be rejected.
//!
//! Initialize the middleware with a `HashSet` of allowed host strings:
//!
//! ```rust
//! use std::collections::HashSet;
//! use iron_cors::CorsMiddleware;
//!
//! let allowed_hosts = ["example.com"].iter()
//!                                    .map(ToString::to_string)
//!                                    .collect::<HashSet<_>>();
//! let middleware = CorsMiddleware::with_whitelist(allowed_hosts);
//! ```
//!
//! See
//! [`examples/whitelist.rs`](https://github.com/dbrgn/iron-cors-rs/blob/master/examples/whitelist.rs)
//! for a full usage example.
//!
//! ## Mode 2: Allow Any
//!
//! Alternatively, the user of the middleware can choose to allow requests from
//! any origin.
//!
//! ```rust
//! use iron_cors::CorsMiddleware;
//!
//! let middleware = CorsMiddleware::with_allow_any(true);
//! ```
//!
//! The boolean flag specifies whether requests without an `Origin` header are
//! acceptable. When set to `false`, requests without that header will be
//! answered with a HTTP 400 response.
//!
//! See
//! [`examples/allow_any.rs`](https://github.com/dbrgn/iron-cors-rs/blob/master/examples/allow_any.rs)
//! for a full usage example.

extern crate iron;
#[macro_use] extern crate log;

use std::collections::HashSet;

use iron::{Request, Response, IronResult, AroundMiddleware, Handler};
use iron::{headers, status};

/// The struct that holds the CORS configuration.
pub struct CorsMiddleware {
    allowed_hosts: Option<HashSet<String>>,
    allow_invalid: bool,
}

impl CorsMiddleware {
    /// Specify which origin hosts are allowed to access the resource.
    ///
    /// Requests without an `Origin` header will be rejected.
    pub fn with_whitelist(allowed_hosts: HashSet<String>) -> Self {
        CorsMiddleware {
            allowed_hosts: Some(allowed_hosts),
            allow_invalid: false,
        }
    }

    /// Allow all origins to access the resource. The
    /// `Access-Control-Allow-Origin` header of the response will be set to
    /// `*`.
    ///
    /// The `allow_invalid` parameter specifies whether requests without an
    /// `Origin` header should be accepted or not. When set to `false`,
    /// requests without that header will be answered with a HTTP 400
    /// response.
    pub fn with_allow_any(allow_invalid: bool) -> Self {
        CorsMiddleware {
            allowed_hosts: None,
            allow_invalid: allow_invalid,
        }
    }
}

impl AroundMiddleware for CorsMiddleware {
    fn around(self, handler: Box<Handler>) -> Box<Handler> {
        match self.allowed_hosts {
            Some(allowed_hosts) => Box::new(CorsHandlerWhitelist {
                handler: handler,
                allowed_hosts: allowed_hosts,
            }),
            None => Box::new(CorsHandlerAllowAny {
                handler: handler,
                allow_invalid: self.allow_invalid,
            }),
        }
    }
}

/// Handler for whitelist based rules.
struct CorsHandlerWhitelist {
    handler: Box<Handler>,
    allowed_hosts: HashSet<String>,
}

/// Handler if allowing any origin.
struct CorsHandlerAllowAny {
    handler: Box<Handler>,
    allow_invalid: bool,
}

/// The handler that acts as an AroundMiddleware.
///
/// It first checks an incoming request for appropriate CORS headers. If the
/// request is allowed, then process it as usual. If not, return a proper error
/// response.
impl Handler for CorsHandlerWhitelist {

    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        // Extract origin header
        let origin = match req.headers.get::<headers::Origin>() {
            Some(origin) => origin.clone(),
            None => {
                warn!("Not a valid CORS request: Missing Origin header");
                return Ok(Response::with((status::BadRequest, "Invalid CORS request: Origin header missing")));
            }
        };

        // Verify origin header
        let may_process = self.allowed_hosts.contains(&origin.host.hostname);

        // Process request
        if may_process {
            // Everything OK, process request
            let mut res = try!(self.handler.handle(req));

            // Add Access-Control-Allow-Origin header to response
            let header = match origin.host.port {
                Some(port) => format!("{}://{}:{}", &origin.scheme, &origin.host.hostname, &port),
                None => format!("{}://{}", &origin.scheme, &origin.host.hostname),
            };
            res.headers.set(headers::AccessControlAllowOrigin::Value(header));

            Ok(res)
        } else {
            warn!("Got disallowed CORS request from {}", &origin.host.hostname);
            Ok(Response::with((status::BadRequest, "Invalid CORS request: Origin not allowed")))
        }
    }

}

/// The handler that acts as an AroundMiddleware.
///
/// It first checks an incoming request for appropriate CORS headers. If the
/// `Origin` header is present, or if invalid CORS requests are allowed, then
/// process it as usual. If not, return a proper error response.
impl Handler for CorsHandlerAllowAny {

    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        // Extract origin header
        match req.headers.get::<headers::Origin>() {
            // If `Origin` wasn't set, abort if the user disallows invalid
            // CORS requests.
            None if !self.allow_invalid => {
                warn!("Not a valid CORS request: Missing Origin header");
                return Ok(Response::with((status::BadRequest, "Invalid CORS request: Origin header missing")));
            },
            _ => {},
        }

        // Everything OK, process request
        let mut res = try!(self.handler.handle(req));

        // Add Access-Control-Allow-Origin header to response
        res.headers.set(headers::AccessControlAllowOrigin::Value("*".into()));

        Ok(res)
    }

}