/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::common::MAX_SAFE_INTEGER; usecrate::error::{ErrorStatus, WebDriverError, WebDriverResult}; use serde_json::{Map, Value}; use url::Url;
pubtype 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. pubtrait BrowserCapabilities { /// Set up the Capabilites object /// /// Typically used to create any internal caches fn init(&mutself, _: &Capabilities);
/// Name of the browser fn browser_name(&mutself, _: &Capabilities) -> WebDriverResult<Option<String>>;
/// Version number of the browser fn browser_version(&mutself, _: &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(&mutself, version: &str, comparison: &str)
-> WebDriverResult<bool>;
/// Name of the platform/OS fn platform_name(&mutself, _: &Capabilities) -> WebDriverResult<Option<String>>;
/// Indicates whether driver supports all of the window resizing and /// repositioning commands. fn set_window_rect(&mutself, _: &Capabilities) -> WebDriverResult<bool>;
/// Indicates that interactability checks will be applied to `<input type=file>`. fn strict_file_interactability(&mutself, _: &Capabilities) -> WebDriverResult<bool>;
/// Whether a WebSocket URL for the created session has to be returned fn web_socket_url(&mutself, _: &Capabilities) -> WebDriverResult<bool>;
/// Indicates whether the endpoint node supports all Virtual Authenticators commands. fn webauthn_virtual_authenticators(&mutself, _: &Capabilities) -> WebDriverResult<bool>;
/// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the User /// Verification Method extension. fn webauthn_extension_uvm(&mutself, _: &Capabilities) -> WebDriverResult<bool>;
/// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the prf /// extension. fn webauthn_extension_prf(&mutself, _: &Capabilities) -> WebDriverResult<bool>;
/// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the /// largeBlob extension. fn webauthn_extension_large_blob(&mutself, _: &Capabilities) -> WebDriverResult<bool>;
/// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the credBlob /// extension. fn webauthn_extension_cred_blob(&mutself, _: &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(&mutself, 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(
&mutself,
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. pubtrait 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>>;
}
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. iflet 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),
));
}
}
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
),
));
}
}
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
),
))
}
}
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",
));
} letmut 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() { returnfalse;
}
} "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(""); iflet Some(version) = browserValue { if !browser_capabilities
.compare_browser_version(&version, version_cond)
.unwrap_or(false)
{ returnfalse;
}
} else { returnfalse;
}
} "platformName" => { let browserValue = browser_capabilities
.platform_name(merged)
.ok()
.and_then(|x| x); if value.as_str() != browserValue.as_deref() { returnfalse;
}
} "acceptInsecureCerts" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.accept_insecure_certs(merged)
.unwrap_or(false)
{ returnfalse;
}
} "setWindowRect" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.set_window_rect(merged)
.unwrap_or(false)
{ returnfalse;
}
} "strictFileInteractability" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.strict_file_interactability(merged)
.unwrap_or(false)
{ returnfalse;
}
} "proxy" => { let default = Map::new(); let proxy = value.as_object().unwrap_or(&default); if !browser_capabilities
.accept_proxy(proxy, merged)
.unwrap_or(false)
{ returnfalse;
}
} "webSocketUrl" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities.web_socket_url(merged).unwrap_or(false)
{ returnfalse;
}
} "webauthn:virtualAuthenticators" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.webauthn_virtual_authenticators(merged)
.unwrap_or(false)
{ returnfalse;
}
} "webauthn:extension:uvm" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.webauthn_extension_uvm(merged)
.unwrap_or(false)
{ returnfalse;
}
} "webauthn:extension:prf" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.webauthn_extension_prf(merged)
.unwrap_or(false)
{ returnfalse;
}
} "webauthn:extension:largeBlob" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.webauthn_extension_large_blob(merged)
.unwrap_or(false)
{ returnfalse;
}
} "webauthn:extension:credBlob" => { if value.as_bool().unwrap_or(false)
&& !browser_capabilities
.webauthn_extension_cred_blob(merged)
.unwrap_or(false)
{ returnfalse;
}
}
name => { if name.contains(':') { if !browser_capabilities
.accept_custom(name, value, merged)
.unwrap_or(false)
{ returnfalse;
}
} else { // Accept the capability
}
}
}
}
true
})
.cloned();
Ok(selected)
}
}
#[cfg(test)] mod tests { usesuper::*; usecrate::test::assert_de; use serde_json::{self, json};
// 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());
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.