browser-commander 0.18.0

Universal browser automation library that supports multiple browser engines with a unified API
Documentation
use async_tungstenite::tungstenite::handshake::server::Request;

use super::{RelayError, RelayOptions};

pub const DEFAULT_RELAY_PORT: u16 = 9333;
pub const RELAY_PATH: &str = "/browser-commander";

pub fn extension_id_from_origin(origin: &str) -> Option<&str> {
    let id = origin.strip_prefix("chrome-extension://")?;
    (!id.is_empty()
        && id
            .bytes()
            .all(|value| value.is_ascii_lowercase() || value.is_ascii_digit()))
    .then_some(id)
}

pub(super) fn authorize(request: &Request, options: &RelayOptions) -> Result<String, u16> {
    if request.uri().path() != RELAY_PATH {
        return Err(404);
    }
    let mut origins = request.headers().get_all("Origin").iter();
    let origin = origins
        .next()
        .and_then(|value| value.to_str().ok())
        .ok_or(403_u16)?;
    if origins.next().is_some() {
        return Err(403);
    }
    let id = extension_id_from_origin(origin).ok_or(403_u16)?;
    if options
        .allowed_extension_ids
        .as_ref()
        .is_some_and(|ids| !ids.iter().any(|allowed| allowed == id))
    {
        return Err(403);
    }
    Ok(id.to_owned())
}

pub(super) fn validate(options: &RelayOptions) -> Result<(), RelayError> {
    if !matches!(options.host.as_str(), "127.0.0.1" | "::1" | "localhost") {
        return Err(RelayError::InvalidOptions(
            "The extension relay must listen on loopback".into(),
        ));
    }
    if options.timeout.is_zero() || options.request_timeout.is_zero() {
        return Err(RelayError::InvalidOptions(
            "Relay timeouts must be positive".into(),
        ));
    }
    Ok(())
}