websocket 0.17.0

A WebSocket (RFC6455) library for Rust.
Documentation
//! Utility functions for dealing with URLs

use url::{Url, Position};
use url::Host as UrlHost;
use hyper::header::Host;
use result::{WebSocketResult, WSUrlErrorKind};

/// Trait that gets required WebSocket URL components
pub trait ToWebSocketUrlComponents {
	/// Retrieve the required WebSocket URL components from this
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)>;
}

impl ToWebSocketUrlComponents for str {
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		parse_url_str(&self)
	}
}

impl ToWebSocketUrlComponents for Url {
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		parse_url(&self)
	}
}

impl ToWebSocketUrlComponents for (Host, String, bool) {
	/// Convert a Host, resource name and secure flag to WebSocket URL components.
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		let (mut host, mut resource_name, secure) = self.clone();
		host.port = Some(match host.port {
			Some(port) => port,
			None => if secure { 443 } else { 80 },
		});
		if resource_name.is_empty() {
			resource_name = "/".to_owned();
		}
		Ok((host, resource_name, secure))
	}
}

impl<'a> ToWebSocketUrlComponents for (Host, &'a str, bool) {
	/// Convert a Host, resource name and secure flag to WebSocket URL components.
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		(self.0.clone(), self.1.to_owned(), self.2).to_components()
	}
}

impl<'a> ToWebSocketUrlComponents for (Host, &'a str) {
	/// Convert a Host and resource name to WebSocket URL components, assuming an insecure connection.
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		(self.0.clone(), self.1.to_owned(), false).to_components()
	}
}

impl ToWebSocketUrlComponents for (Host, String) {
	/// Convert a Host and resource name to WebSocket URL components, assuming an insecure connection.
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		(self.0.clone(), self.1.clone(), false).to_components()
	}
}

impl ToWebSocketUrlComponents for (UrlHost, u16, String, bool) {
	/// Convert a Host, port, resource name and secure flag to WebSocket URL components.
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		(Host {
			hostname: self.0.to_string(),
			port: Some(self.1)
		}, self.2.clone(), self.3).to_components()
	}
}

impl<'a> ToWebSocketUrlComponents for (UrlHost, u16, &'a str, bool) {
	/// Convert a Host, port, resource name and secure flag to WebSocket URL components.
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		(Host {
			hostname: self.0.to_string(),
			port: Some(self.1)
		}, self.2, self.3).to_components()
	}
}

impl<'a, T: ToWebSocketUrlComponents> ToWebSocketUrlComponents for &'a T {
	fn to_components(&self) -> WebSocketResult<(Host, String, bool)> {
		(**self).to_components()
	}
}

/// Gets the host, port, resource and secure from the string representation of a url
pub fn parse_url_str(url_str: &str) -> WebSocketResult<(Host, String, bool)> {
    // https://html.spec.whatwg.org/multipage/#parse-a-websocket-url's-components
    // Steps 1 and 2
    let parsed_url = try!(Url::parse(url_str));
    parse_url(&parsed_url)
}

/// Gets the host, port, resource, and secure flag from a url
pub fn parse_url(url: &Url) -> WebSocketResult<(Host, String, bool)> {
    // https://html.spec.whatwg.org/multipage/#parse-a-websocket-url's-components

    // Step 4
    if url.fragment().is_some() {
        return Err(From::from(WSUrlErrorKind::CannotSetFragment));
    }

    let secure = match url.scheme() {
        // step 5
        "ws" => false,
        "wss" => true,
        // step 3
        _ => return Err(From::from(WSUrlErrorKind::InvalidScheme)),
    };

    let host = url.host_str().unwrap().to_owned(); // Step 6
    let port = url.port_or_known_default(); // Steps 7 and 8

    // steps 9, 10, 11
    let resource = url[Position::BeforePath..Position::AfterQuery].to_owned();

    // Step 12
    Ok((Host { hostname: host, port: port }, resource, secure))
}

#[cfg(all(feature = "nightly", test))]
mod tests {
    use super::*;
    //use test;
    use url::Url;
    use result::{WebSocketError, WSUrlErrorKind};

    fn url_for_test() -> Url {
        Url::parse("ws://www.example.com:8080/some/path?a=b&c=d").unwrap()
    }

    #[test]
    fn test_parse_url_fragments_not_accepted() {
        let url = &mut url_for_test();
        url.set_fragment(Some("non_null_fragment"));

        let result = parse_url(url);
        match result {
            Err(WebSocketError::WebSocketUrlError(
                WSUrlErrorKind::CannotSetFragment)) => (),
            Err(e) => panic!("Expected WSUrlErrorKind::CannotSetFragment but got {}", e),
            Ok(_) => panic!("Expected WSUrlErrorKind::CannotSetFragment but got Ok")
        }
    }

    #[test]
    fn test_parse_url_invalid_schemes_return_error() {
        let url = &mut url_for_test();

        let invalid_schemes = &["http", "https", "gopher", "file", "ftp", "other"];
        for scheme in invalid_schemes {
            url.set_scheme(scheme).unwrap();

            let result = parse_url(url);
            match result {
                Err(WebSocketError::WebSocketUrlError(
                    WSUrlErrorKind::InvalidScheme)) => (),
                Err(e) => panic!("Expected WSUrlErrorKind::InvalidScheme but got {}", e),
                Ok(_) => panic!("Expected WSUrlErrorKind::InvalidScheme but got Ok")
            }
        }
    }

    #[test]
    fn test_parse_url_valid_schemes_return_ok() {
        let url = &mut url_for_test();

        let valid_schemes = &["ws", "wss"];
        for scheme in valid_schemes {
            url.set_scheme(scheme).unwrap();

            let result = parse_url(url);
            match result {
                Ok(_) => (),
                Err(e) => panic!("Expected Ok, but got {}", e)
            }
        }
    }

    #[test]
    fn test_parse_url_ws_returns_unset_secure_flag() {
        let url = &mut url_for_test();
        url.set_scheme("ws").unwrap();

        let result = parse_url(url);
        let secure = match result {
            Ok((_, _, secure)) => secure,
            Err(e) => panic!(e),
        };
        assert!(!secure);
    }

    #[test]
    fn test_parse_url_wss_returns_set_secure_flag() {
        let url = &mut url_for_test();
        url.set_scheme("wss").unwrap();

        let result = parse_url(url);
        let secure = match result {
            Ok((_, _, secure)) => secure,
            Err(e) => panic!(e),
        };
        assert!(secure);
    }

    #[test]
    fn test_parse_url_generates_proper_output() {
        let url = &url_for_test();

        let result = parse_url(url);
        let (host, resource) = match result {
            Ok((host, resource, _)) => (host, resource),
            Err(e) => panic!(e),
        };

        assert_eq!(host.hostname, "www.example.com".to_owned());
        assert_eq!(resource, "/some/path?a=b&c=d".to_owned());

        match host.port {
            Some(port) => assert_eq!(port, 8080),
            _ => panic!("Port should not be None"),
        }
    }

    #[test]
    fn test_parse_url_empty_path_should_give_slash() {
        let url = &mut url_for_test();
        url.set_path("/");

        let result = parse_url(url);
        let resource = match result {
            Ok((_, resource, _)) => resource,
            Err(e) => panic!(e),
        };

        assert_eq!(resource, "/?a=b&c=d".to_owned());
    }

    #[test]
    fn test_parse_url_none_query_should_not_append_question_mark() {
        let url = &mut url_for_test();
        url.set_query(None);

        let result = parse_url(url);
        let resource = match result {
            Ok((_, resource, _)) => resource,
            Err(e) => panic!(e),
        };

        assert_eq!(resource, "/some/path".to_owned());
    }

    #[test]
    fn test_parse_url_none_port_should_use_default_port() {
        let url = &mut url_for_test();
        url.set_port(None).unwrap();

        let result = parse_url(url);
        let host = match result {
            Ok((host, _, _)) => host,
            Err(e) => panic!(e),
        };

        match host.port {
            Some(80) => (),
            Some(p) => panic!("Expected port to be 80 but got {}", p),
            None => panic!("Expected port to be 80 but got `None`"),
        }
    }

    #[test]
    fn test_parse_url_str_valid_url1() {
        let url_str = "ws://www.example.com/some/path?a=b&c=d";
        let result = parse_url_str(url_str);
        let (host, resource, secure) = match result {
            Ok((host, resource, secure)) => (host, resource, secure),
            Err(e) => panic!(e),
        };

        match host.port {
            Some(80) => (),
            Some(p) => panic!("Expected port 80 but got {}", p),
            None => panic!("Expected port 80 but got `None`")
        }
        assert_eq!(host.hostname, "www.example.com".to_owned());
        assert_eq!(resource, "/some/path?a=b&c=d".to_owned());
        assert!(!secure);
    }

    #[test]
    fn test_parse_url_str_valid_url2() {
        let url_str = "wss://www.example.com";
        let result = parse_url_str(url_str);
        let (host, resource, secure) = match result {
            Ok((host, resource, secure)) => (host, resource, secure),
            Err(e) => panic!(e)
        };

        match host.port {
            Some(443) => (),
            Some(p) => panic!("Expected port 443 but got {}", p),
            None => panic!("Expected port 443 but got `None`")
        }
        assert_eq!(host.hostname, "www.example.com".to_owned());
        assert_eq!(resource, "/".to_owned());
        assert!(secure);
    }

    #[test]
    fn test_parse_url_str_invalid_relative_url() {
        let url_str = "/some/relative/path?a=b&c=d";
        let result = parse_url_str(url_str);
        match result {
            Err(WebSocketError::UrlError(_)) => (),
            Err(e) => panic!("Expected UrlError, but got unexpected error {}", e),
            Ok(_) => panic!("Expected UrlError, but got Ok"),
        }
    }

    #[test]
    fn test_parse_url_str_invalid_url_scheme() {
        let url_str = "http://www.example.com/some/path?a=b&c=d";
        let result = parse_url_str(url_str);
        match result {
            Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::InvalidScheme)) => (),
            Err(e) => panic!("Expected InvalidScheme, but got unexpected error {}", e),
            Ok(_) => panic!("Expected InvalidScheme, but got Ok"),
        }
    }

    #[test]
    fn test_parse_url_str_invalid_url_fragment() {
        let url_str = "http://www.example.com/some/path#some-id";
        let result = parse_url_str(url_str);
        match result {
            Err(WebSocketError::WebSocketUrlError(WSUrlErrorKind::CannotSetFragment)) => (),
            Err(e) => panic!("Expected CannotSetFragment, but got unexpected error {}", e),
            Ok(_) => panic!("Expected CannotSetFragment, but got Ok"),
        }
    }
}