Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  capabilities.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::common::MAX_SAFE_INTEGER;
use crate::error::{ErrorStatus, WebDriverError, WebDriverResult};
use serde_json::{Map, Value};
use url::Url;

pub type Capabilities = Map<String, Value>;

/// Trait for objects that can be used to inspect browser capabilities
///
/// The main methods in this trait are called with a Capabilites object
/// resulting from a full set of potential capabilites for the session.  Given
/// those Capabilities they return a property of the browser instance that
/// would be initiated. In many cases this will be independent of the input,
/// but in the case of e.g. browser version, it might depend on a path to the
/// binary provided as a capability.
pub trait BrowserCapabilities {
    /// Set up the Capabilites object
    ///
    /// Typically used to create any internal caches
    fn init(&mut self, _: &Capabilities);

    /// Name of the browser
    fn browser_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>>;

    /// Version number of the browser
    fn browser_version(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>>;

    /// Compare actual browser version to that provided in a version specifier
    ///
    /// Parameters are the actual browser version and the comparison string,
    /// respectively. The format of the comparison string is
    /// implementation-defined.
    fn compare_browser_version(&mut self, version: &str, comparison: &str)
        -> WebDriverResult<bool>;

    /// Name of the platform/OS
    fn platform_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>>;

    /// Whether insecure certificates are supported
    fn accept_insecure_certs(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Indicates whether driver supports all of the window resizing and
    /// repositioning commands.
    fn set_window_rect(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Indicates that interactability checks will be applied to `<input type=file>`.
    fn strict_file_interactability(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Whether a WebSocket URL for the created session has to be returned
    fn web_socket_url(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Indicates whether the endpoint node supports all Virtual Authenticators commands.
    fn webauthn_virtual_authenticators(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the User
    /// Verification Method extension.
    fn webauthn_extension_uvm(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the prf
    /// extension.
    fn webauthn_extension_prf(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the
    /// largeBlob extension.
    fn webauthn_extension_large_blob(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the credBlob
    /// extension.
    fn webauthn_extension_cred_blob(&mut self, _: &Capabilities) -> WebDriverResult<bool>;

    fn accept_proxy(
        &mut self,
        proxy_settings: &Map<String, Value>,
        _: &Capabilities,
    ) -> WebDriverResult<bool>;

    /// Type check custom properties
    ///
    /// Check that custom properties containing ":" have the correct data types.
    /// Properties that are unrecognised must be ignored i.e. return without
    /// error.
    fn validate_custom(&mut self, name: &str, value: &Value) -> WebDriverResult<()>;

    /// Check if custom properties are accepted capabilites
    ///
    /// Check that custom properties containing ":" are compatible with
    /// the implementation.
    fn accept_custom(
        &mut self,
        name: &str,
        value: &Value,
        merged: &Capabilities,
    ) -> WebDriverResult<bool>;
}

/// Trait to abstract over various version of the new session parameters
///
/// This trait is expected to be implemented on objects holding the capabilities
/// from a new session command.
pub trait CapabilitiesMatching {
    /// Match the BrowserCapabilities against some candidate capabilites
    ///
    /// Takes a BrowserCapabilites object and returns a set of capabilites that
    /// are valid for that browser, if any, or None if there are no matching
    /// capabilities.
    fn match_browser<T: BrowserCapabilities>(
        &self,
        browser_capabilities: &mut T,
    ) -> WebDriverResult<Option<Capabilities>>;
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct SpecNewSessionParameters {
    #[serde(default = "Capabilities::default")]
    pub alwaysMatch: Capabilities,
    #[serde(default = "firstMatch_default")]
    pub firstMatch: Vec<Capabilities>,
}

impl Default for SpecNewSessionParameters {
    fn default() -> Self {
        SpecNewSessionParameters {
            alwaysMatch: Capabilities::new(),
            firstMatch: vec![Capabilities::new()],
        }
    }
}

fn firstMatch_default() -> Vec<Capabilities> {
    vec![Capabilities::default()]
}

impl SpecNewSessionParameters {
    fn validate<T: BrowserCapabilities>(
        &self,
        mut capabilities: Capabilities,
        browser_capabilities: &mut T,
    ) -> WebDriverResult<Capabilities> {
        // Filter out entries with the value `null`
        let null_entries = capabilities
            .iter()
            .filter(|&(_, value)| *value == Value::Null)
            .map(|(k, _)| k.clone())
            .collect::<Vec<String>>();
        for key in null_entries {
            capabilities.remove(&key);
        }

        for (key, value) in &capabilities {
            match &**key {
                x @ "acceptInsecureCerts"
                | x @ "setWindowRect"
                | x @ "strictFileInteractability"
                | x @ "webSocketUrl"
                | x @ "webauthn:virtualAuthenticators"
                | x @ "webauthn:extension:uvm"
                | x @ "webauthn:extension:prf"
                | x @ "webauthn:extension:largeBlob"
                | x @ "webauthn:extension:credBlob" => {
                    if !value.is_boolean() {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!("{} is not boolean: {}", x, value),
                        ));
                    }
                }
                x @ "browserName" | x @ "browserVersion" | x @ "platformName" => {
                    if !value.is_string() {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!("{} is not a string: {}", x, value),
                        ));
                    }
                }
                "pageLoadStrategy" => SpecNewSessionParameters::validate_page_load_strategy(value)?,
                "proxy" => SpecNewSessionParameters::validate_proxy(value)?,
                "timeouts" => SpecNewSessionParameters::validate_timeouts(value)?,
                "unhandledPromptBehavior" => {
                    SpecNewSessionParameters::validate_unhandled_prompt_behavior(value)?
                }
                x => {
                    if !x.contains(':') {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!(
                                "{} is not the name of a known capability or extension capability",
                                x
                            ),
                        ));
                    } else {
                        browser_capabilities.validate_custom(x, value)?
                    }
                }
            }
        }

        // With a value of `false` the capability needs to be removed.
        if let Some(Value::Bool(false)) = capabilities.get(&"webSocketUrl".to_string()) {
            capabilities.remove(&"webSocketUrl".to_string());
        }

        Ok(capabilities)
    }

    fn validate_page_load_strategy(value: &Value) -> WebDriverResult<()> {
        match value {
            Value::String(x) => match &**x {
                "normal" | "eager" | "none" => {}
                x => {
                    return Err(WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        format!("Invalid page load strategy: {}", x),
                    ))
                }
            },
            _ => {
                return Err(WebDriverError::new(
                    ErrorStatus::InvalidArgument,
                    "pageLoadStrategy is not a string",
                ))
            }
        }
        Ok(())
    }

    fn validate_proxy(proxy_value: &Value) -> WebDriverResult<()> {
        let obj = try_opt!(
            proxy_value.as_object(),
            ErrorStatus::InvalidArgument,
            "proxy is not an object"
        );

        for (key, value) in obj {
            match &**key {
                "proxyType" => match value.as_str() {
                    Some("pac") | Some("direct") | Some("autodetect") | Some("system")
                    | Some("manual") => {}
                    Some(x) => {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!("Invalid proxyType value: {}", x),
                        ))
                    }
                    None => {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!("proxyType is not a string: {}", value),
                        ))
                    }
                },

                "proxyAutoconfigUrl" => match value.as_str() {
                    Some(x) => {
                        Url::parse(x).map_err(|_| {
                            WebDriverError::new(
                                ErrorStatus::InvalidArgument,
                                format!("proxyAutoconfigUrl is not a valid URL: {}", x),
                            )
                        })?;
                    }
                    None => {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            "proxyAutoconfigUrl is not a string",
                        ))
                    }
                },

                "ftpProxy" => SpecNewSessionParameters::validate_host(value, "ftpProxy")?,
                "httpProxy" => SpecNewSessionParameters::validate_host(value, "httpProxy")?,
                "noProxy" => SpecNewSessionParameters::validate_no_proxy(value)?,
                "sslProxy" => SpecNewSessionParameters::validate_host(value, "sslProxy")?,
                "socksProxy" => SpecNewSessionParameters::validate_host(value, "socksProxy")?,
                "socksVersion" => {
                    if !value.is_number() {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!("socksVersion is not a number: {}", value),
                        ));
                    }
                }

                x => {
                    return Err(WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        format!("Invalid proxy configuration entry: {}", x),
                    ))
                }
            }
        }

        Ok(())
    }

    fn validate_no_proxy(value: &Value) -> WebDriverResult<()> {
        match value.as_array() {
            Some(hosts) => {
                for host in hosts {
                    match host.as_str() {
                        Some(_) => {}
                        None => {
                            return Err(WebDriverError::new(
                                ErrorStatus::InvalidArgument,
                                format!("noProxy item is not a string: {}", host),
                            ))
                        }
                    }
                }
            }
            None => {
                return Err(WebDriverError::new(
                    ErrorStatus::InvalidArgument,
                    format!("noProxy is not an array: {}", value),
                ))
            }
        }

        Ok(())
    }

    /// Validate whether a named capability is JSON value is a string
    /// containing a host and possible port
    fn validate_host(value: &Value, entry: &str) -> WebDriverResult<()> {
        match value.as_str() {
            Some(host) => {
                if host.contains("://") {
                    return Err(WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        format!("{} must not contain a scheme: {}", entry, host),
                    ));
                }

                // Temporarily add a scheme so the host can be parsed as URL
                let url = Url::parse(&format!("http://{}", host)).map_err(|_| {
                    WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        format!("{} is not a valid URL: {}", entry, host),
                    )
                })?;

                if url.username() != ""
                    || url.password().is_some()
                    || url.path() != "/"
                    || url.query().is_some()
                    || url.fragment().is_some()
                {
                    return Err(WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        format!("{} is not of the form host[:port]: {}", entry, host),
                    ));
                }
            }

            None => {
                return Err(WebDriverError::new(
                    ErrorStatus::InvalidArgument,
                    format!("{} is not a string: {}", entry, value),
                ))
            }
        }

        Ok(())
    }

    fn validate_timeouts(value: &Value) -> WebDriverResult<()> {
        let obj = try_opt!(
            value.as_object(),
            ErrorStatus::InvalidArgument,
            "timeouts capability is not an object"
        );

        for (key, value) in obj {
            match &**key {
                _x @ "script" if value.is_null() => {}

                x @ "script" | x @ "pageLoad" | x @ "implicit" => {
                    let timeout = try_opt!(
                        value.as_f64(),
                        ErrorStatus::InvalidArgument,
                        format!("{} timeouts value is not a number: {}", x, value)
                    );
                    if timeout < 0.0 || timeout.fract() != 0.0 {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!(
                                "'{}' timeouts value is not a positive Integer: {}",
                                x, timeout
                            ),
                        ));
                    }
                    if (timeout as u64) > MAX_SAFE_INTEGER {
                        return Err(WebDriverError::new(
                            ErrorStatus::InvalidArgument,
                            format!(
                                "'{}' timeouts value is greater than maximum safe integer: {}",
                                x, timeout
                            ),
                        ));
                    }
                }

                x => {
                    return Err(WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        format!("Invalid timeouts capability entry: {}", x),
                    ))
                }
            }
        }

        Ok(())
    }

    fn validate_unhandled_prompt_behavior(value: &Value) -> WebDriverResult<()> {
        match value {
            Value::Object(obj) => {
                // Unhandled Prompt Behavior type as used by WebDriver BiDi
                for (key, value) in obj {
                    match &**key {
                        x @ "alert"
                        | x @ "beforeUnload"
                        | x @ "confirm"
                        | x @ "default"
                        | x @ "prompt" => {
                            let behavior = try_opt!(
                                value.as_str(),
                                ErrorStatus::InvalidArgument,
                                format!(
                                    "'{}' unhandledPromptBehavior value is not a string: {}",
                                    x, value
                                )
                            );

                            match behavior {
                                "accept" | "accept and notify" | "dismiss"
                                | "dismiss and notify" | "ignore" => {}
                                x => {
                                    return Err(WebDriverError::new(
                                        ErrorStatus::InvalidArgument,
                                        format!(
                                            "'{}' unhandledPromptBehavior value is invalid: {}",
                                            x, behavior
                                        ),
                                    ))
                                }
                            }
                        }
                        x => {
                            return Err(WebDriverError::new(
                                ErrorStatus::InvalidArgument,
                                format!("Invalid unhandledPromptBehavior entry: {}", x),
                            ))
                        }
                    }
                }
            }
            Value::String(behavior) => match behavior.as_str() {
                "accept" | "accept and notify" | "dismiss" | "dismiss and notify" | "ignore" => {}
                x => {
                    return Err(WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        format!("Invalid unhandledPromptBehavior value: {}", x),
                    ))
                }
            },
            _ => {
                return Err(WebDriverError::new(
                    ErrorStatus::InvalidArgument,
                    format!(
                        "unhandledPromptBehavior is neither an object nor a string: {}",
                        value
                    ),
                ))
            }
        }

        Ok(())
    }
}

impl CapabilitiesMatching for SpecNewSessionParameters {
    fn match_browser<T: BrowserCapabilities>(
        &self,
        browser_capabilities: &mut T,
    ) -> WebDriverResult<Option<Capabilities>> {
        let default = vec![Map::new()];
        let capabilities_list = if self.firstMatch.is_empty() {
            &default
        } else {
            &self.firstMatch
        };

        let merged_capabilities = capabilities_list
            .iter()
            .map(|first_match_entry| {
                if first_match_entry
                    .keys()
                    .any(|k| self.alwaysMatch.contains_key(k))
                {
                    return Err(WebDriverError::new(
                        ErrorStatus::InvalidArgument,
                        "firstMatch key shadowed a value in alwaysMatch",
                    ));
                }
                let mut merged = self.alwaysMatch.clone();
                for (key, value) in first_match_entry.clone() {
                    merged.insert(key, value);
                }
                Ok(merged)
            })
            .map(|merged| merged.and_then(|x| self.validate(x, browser_capabilities)))
            .collect::<WebDriverResult<Vec<Capabilities>>>()?;

        let selected = merged_capabilities
            .iter()
            .find(|merged| {
                browser_capabilities.init(merged);

                for (key, value) in merged.iter() {
                    match &**key {
                        "browserName" => {
                            let browserValue = browser_capabilities
                                .browser_name(merged)
                                .ok()
                                .and_then(|x| x);

                            if value.as_str() != browserValue.as_deref() {
                                return false;
                            }
                        }
                        "browserVersion" => {
                            let browserValue = browser_capabilities
                                .browser_version(merged)
                                .ok()
                                .and_then(|x| x);
                            // We already validated this was a string
                            let version_cond = value.as_str().unwrap_or("");
                            if let Some(version) = browserValue {
                                if !browser_capabilities
                                    .compare_browser_version(&version, version_cond)
                                    .unwrap_or(false)
                                {
                                    return false;
                                }
                            } else {
                                return false;
                            }
                        }
                        "platformName" => {
                            let browserValue = browser_capabilities
                                .platform_name(merged)
                                .ok()
                                .and_then(|x| x);
                            if value.as_str() != browserValue.as_deref() {
                                return false;
                            }
                        }
                        "acceptInsecureCerts" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .accept_insecure_certs(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "setWindowRect" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .set_window_rect(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "strictFileInteractability" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .strict_file_interactability(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "proxy" => {
                            let default = Map::new();
                            let proxy = value.as_object().unwrap_or(&default);
                            if !browser_capabilities
                                .accept_proxy(proxy, merged)
                                .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "webSocketUrl" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities.web_socket_url(merged).unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "webauthn:virtualAuthenticators" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .webauthn_virtual_authenticators(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "webauthn:extension:uvm" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .webauthn_extension_uvm(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "webauthn:extension:prf" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .webauthn_extension_prf(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "webauthn:extension:largeBlob" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .webauthn_extension_large_blob(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        "webauthn:extension:credBlob" => {
                            if value.as_bool().unwrap_or(false)
                                && !browser_capabilities
                                    .webauthn_extension_cred_blob(merged)
                                    .unwrap_or(false)
                            {
                                return false;
                            }
                        }
                        name => {
                            if name.contains(':') {
                                if !browser_capabilities
                                    .accept_custom(name, value, merged)
                                    .unwrap_or(false)
                                {
                                    return false;
                                }
                            } else {
                                // Accept the capability
                            }
                        }
                    }
                }

                true
            })
            .cloned();
        Ok(selected)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test::assert_de;
    use serde_json::{self, json};

    #[test]
    fn test_json_spec_new_session_parameters_alwaysMatch_only() {
        let caps = SpecNewSessionParameters {
            alwaysMatch: Capabilities::new(),
            firstMatch: vec![Capabilities::new()],
        };
        assert_de(&caps, json!({"alwaysMatch": {}}));
    }

    #[test]
    fn test_json_spec_new_session_parameters_firstMatch_only() {
        let caps = SpecNewSessionParameters {
            alwaysMatch: Capabilities::new(),
            firstMatch: vec![Capabilities::new()],
        };
        assert_de(&caps, json!({"firstMatch": [{}]}));
    }

    #[test]
    fn test_json_spec_new_session_parameters_alwaysMatch_null() {
        let json = json!({
            "alwaysMatch": null,
            "firstMatch": [{}],
        });
        assert!(serde_json::from_value::<SpecNewSessionParameters>(json).is_err());
    }

    #[test]
    fn test_json_spec_new_session_parameters_firstMatch_null() {
        let json = json!({
            "alwaysMatch": {},
            "firstMatch": null,
        });
        assert!(serde_json::from_value::<SpecNewSessionParameters>(json).is_err());
    }

    #[test]
    fn test_json_spec_new_session_parameters_both_empty() {
        let json = json!({
            "alwaysMatch": {},
            "firstMatch": [{}],
        });
        let caps = SpecNewSessionParameters {
            alwaysMatch: Capabilities::new(),
            firstMatch: vec![Capabilities::new()],
        };

        assert_de(&caps, json);
    }

    #[test]
    fn test_json_spec_new_session_parameters_both_with_capability() {
        let json = json!({
            "alwaysMatch": {"foo": "bar"},
            "firstMatch": [{"foo2": "bar2"}],
        });
        let mut caps = SpecNewSessionParameters {
            alwaysMatch: Capabilities::new(),
            firstMatch: vec![Capabilities::new()],
        };
        caps.alwaysMatch.insert("foo".into(), "bar".into());
        caps.firstMatch[0].insert("foo2".into(), "bar2".into());

        assert_de(&caps, json);
    }

    #[test]
    fn test_validate_unhandled_prompt_behavior() {
        fn validate_prompt_behavior(v: Value) -> WebDriverResult<()> {
            SpecNewSessionParameters::validate_unhandled_prompt_behavior(&v)
        }

        // capability as string
        validate_prompt_behavior(json!("accept")).unwrap();
        validate_prompt_behavior(json!("accept and notify")).unwrap();
        validate_prompt_behavior(json!("dismiss")).unwrap();
        validate_prompt_behavior(json!("dismiss and notify")).unwrap();
        validate_prompt_behavior(json!("ignore")).unwrap();
        assert!(validate_prompt_behavior(json!("foo")).is_err());

        // capability as object
        let types = ["alert", "beforeUnload", "confirm", "default", "prompt"];
        let handlers = [
            "accept",
            "accept and notify",
            "dismiss",
            "dismiss and notify",
            "ignore",
        ];
        for promptType in types {
            assert!(validate_prompt_behavior(json!({promptType: "foo"})).is_err());
            for handler in handlers {
                validate_prompt_behavior(json!({promptType: handler})).unwrap();
            }
        }

        for handler in handlers {
            assert!(validate_prompt_behavior(json!({"foo": handler})).is_err());
        }
    }

    #[test]
    fn test_validate_proxy() {
        fn validate_proxy(v: Value) -> WebDriverResult<()> {
            SpecNewSessionParameters::validate_proxy(&v)
        }

        // proxy hosts
        validate_proxy(json!({"httpProxy":  "127.0.0.1"})).unwrap();
        validate_proxy(json!({"httpProxy": "127.0.0.1:"})).unwrap();
        validate_proxy(json!({"httpProxy": "127.0.0.1:3128"})).unwrap();
        validate_proxy(json!({"httpProxy": "localhost"})).unwrap();
        validate_proxy(json!({"httpProxy": "localhost:3128"})).unwrap();
        validate_proxy(json!({"httpProxy": "[2001:db8::1]"})).unwrap();
        validate_proxy(json!({"httpProxy": "[2001:db8::1]:3128"})).unwrap();
        validate_proxy(json!({"httpProxy": "example.org"})).unwrap();
        validate_proxy(json!({"httpProxy": "example.org:3128"})).unwrap();

        assert!(validate_proxy(json!({"httpProxy": "http://example.org"})).is_err());
        assert!(validate_proxy(json!({"httpProxy": "example.org:-1"})).is_err());
        assert!(validate_proxy(json!({"httpProxy": "2001:db8::1"})).is_err());

        // no proxy for manual proxy type
        validate_proxy(json!({"noProxy": ["foo"]})).unwrap();

        assert!(validate_proxy(json!({"noProxy": "foo"})).is_err());
        assert!(validate_proxy(json!({"noProxy": [42]})).is_err());
    }
}

[ Dauer der Verarbeitung: 0.44 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge