capabilities.rs (31969B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use crate::common::MAX_SAFE_INTEGER; 6 use crate::error::{ErrorStatus, WebDriverError, WebDriverResult}; 7 use serde_json::{Map, Value}; 8 use url::Url; 9 10 pub type Capabilities = Map<String, Value>; 11 12 /// Trait for objects that can be used to inspect browser capabilities 13 /// 14 /// The main methods in this trait are called with a Capabilites object 15 /// resulting from a full set of potential capabilites for the session. Given 16 /// those Capabilities they return a property of the browser instance that 17 /// would be initiated. In many cases this will be independent of the input, 18 /// but in the case of e.g. browser version, it might depend on a path to the 19 /// binary provided as a capability. 20 pub trait BrowserCapabilities { 21 /// Set up the Capabilites object 22 /// 23 /// Typically used to create any internal caches 24 fn init(&mut self, _: &Capabilities); 25 26 /// Name of the browser 27 fn browser_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>>; 28 29 /// Version number of the browser 30 fn browser_version(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>>; 31 32 /// Compare actual browser version to that provided in a version specifier 33 /// 34 /// Parameters are the actual browser version and the comparison string, 35 /// respectively. The format of the comparison string is 36 /// implementation-defined. 37 fn compare_browser_version(&mut self, version: &str, comparison: &str) 38 -> WebDriverResult<bool>; 39 40 /// Name of the platform/OS 41 fn platform_name(&mut self, _: &Capabilities) -> WebDriverResult<Option<String>>; 42 43 /// Whether insecure certificates are supported 44 fn accept_insecure_certs(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 45 46 /// Indicates whether driver supports all of the window resizing and 47 /// repositioning commands. 48 fn set_window_rect(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 49 50 /// Indicates that interactability checks will be applied to `<input type=file>`. 51 fn strict_file_interactability(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 52 53 /// Whether a WebSocket URL for the created session has to be returned 54 fn web_socket_url(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 55 56 /// Indicates whether the endpoint node supports all Virtual Authenticators commands. 57 fn webauthn_virtual_authenticators(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 58 59 /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the User 60 /// Verification Method extension. 61 fn webauthn_extension_uvm(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 62 63 /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the prf 64 /// extension. 65 fn webauthn_extension_prf(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 66 67 /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the 68 /// largeBlob extension. 69 fn webauthn_extension_large_blob(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 70 71 /// Indicates whether the endpoint node WebAuthn WebDriver implementation supports the credBlob 72 /// extension. 73 fn webauthn_extension_cred_blob(&mut self, _: &Capabilities) -> WebDriverResult<bool>; 74 75 fn accept_proxy( 76 &mut self, 77 proxy_settings: &Map<String, Value>, 78 _: &Capabilities, 79 ) -> WebDriverResult<bool>; 80 81 /// Type check custom properties 82 /// 83 /// Check that custom properties containing ":" have the correct data types. 84 /// Properties that are unrecognised must be ignored i.e. return without 85 /// error. 86 fn validate_custom(&mut self, name: &str, value: &Value) -> WebDriverResult<()>; 87 88 /// Check if custom properties are accepted capabilites 89 /// 90 /// Check that custom properties containing ":" are compatible with 91 /// the implementation. 92 fn accept_custom( 93 &mut self, 94 name: &str, 95 value: &Value, 96 merged: &Capabilities, 97 ) -> WebDriverResult<bool>; 98 } 99 100 /// Trait to abstract over various version of the new session parameters 101 /// 102 /// This trait is expected to be implemented on objects holding the capabilities 103 /// from a new session command. 104 pub trait CapabilitiesMatching { 105 /// Match the BrowserCapabilities against some candidate capabilites 106 /// 107 /// Takes a BrowserCapabilites object and returns a set of capabilites that 108 /// are valid for that browser, if any, or None if there are no matching 109 /// capabilities. 110 fn match_browser<T: BrowserCapabilities>( 111 &self, 112 browser_capabilities: &mut T, 113 ) -> WebDriverResult<Option<Capabilities>>; 114 } 115 116 #[derive(Debug, PartialEq, Serialize, Deserialize)] 117 pub struct SpecNewSessionParameters { 118 #[serde(default = "Capabilities::default")] 119 pub alwaysMatch: Capabilities, 120 #[serde(default = "firstMatch_default")] 121 pub firstMatch: Vec<Capabilities>, 122 } 123 124 impl Default for SpecNewSessionParameters { 125 fn default() -> Self { 126 SpecNewSessionParameters { 127 alwaysMatch: Capabilities::new(), 128 firstMatch: vec![Capabilities::new()], 129 } 130 } 131 } 132 133 fn firstMatch_default() -> Vec<Capabilities> { 134 vec![Capabilities::default()] 135 } 136 137 impl SpecNewSessionParameters { 138 fn validate<T: BrowserCapabilities>( 139 &self, 140 mut capabilities: Capabilities, 141 browser_capabilities: &mut T, 142 ) -> WebDriverResult<Capabilities> { 143 // Filter out entries with the value `null` 144 let null_entries = capabilities 145 .iter() 146 .filter(|&(_, value)| *value == Value::Null) 147 .map(|(k, _)| k.clone()) 148 .collect::<Vec<String>>(); 149 for key in null_entries { 150 capabilities.remove(&key); 151 } 152 153 for (key, value) in &capabilities { 154 match &**key { 155 x @ "acceptInsecureCerts" 156 | x @ "setWindowRect" 157 | x @ "strictFileInteractability" 158 | x @ "webSocketUrl" 159 | x @ "webauthn:virtualAuthenticators" 160 | x @ "webauthn:extension:uvm" 161 | x @ "webauthn:extension:prf" 162 | x @ "webauthn:extension:largeBlob" 163 | x @ "webauthn:extension:credBlob" => { 164 if !value.is_boolean() { 165 return Err(WebDriverError::new( 166 ErrorStatus::InvalidArgument, 167 format!("{} is not boolean: {}", x, value), 168 )); 169 } 170 } 171 x @ "browserName" | x @ "browserVersion" | x @ "platformName" => { 172 if !value.is_string() { 173 return Err(WebDriverError::new( 174 ErrorStatus::InvalidArgument, 175 format!("{} is not a string: {}", x, value), 176 )); 177 } 178 } 179 "pageLoadStrategy" => SpecNewSessionParameters::validate_page_load_strategy(value)?, 180 "proxy" => SpecNewSessionParameters::validate_proxy(value)?, 181 "timeouts" => SpecNewSessionParameters::validate_timeouts(value)?, 182 "unhandledPromptBehavior" => { 183 SpecNewSessionParameters::validate_unhandled_prompt_behavior(value)? 184 } 185 x => { 186 if !x.contains(':') { 187 return Err(WebDriverError::new( 188 ErrorStatus::InvalidArgument, 189 format!( 190 "{} is not the name of a known capability or extension capability", 191 x 192 ), 193 )); 194 } else { 195 browser_capabilities.validate_custom(x, value)? 196 } 197 } 198 } 199 } 200 201 // With a value of `false` the capability needs to be removed. 202 if let Some(Value::Bool(false)) = capabilities.get(&"webSocketUrl".to_string()) { 203 capabilities.remove(&"webSocketUrl".to_string()); 204 } 205 206 Ok(capabilities) 207 } 208 209 fn validate_page_load_strategy(value: &Value) -> WebDriverResult<()> { 210 match value { 211 Value::String(x) => match &**x { 212 "normal" | "eager" | "none" => {} 213 x => { 214 return Err(WebDriverError::new( 215 ErrorStatus::InvalidArgument, 216 format!("Invalid page load strategy: {}", x), 217 )) 218 } 219 }, 220 _ => { 221 return Err(WebDriverError::new( 222 ErrorStatus::InvalidArgument, 223 "pageLoadStrategy is not a string", 224 )) 225 } 226 } 227 Ok(()) 228 } 229 230 fn validate_proxy(proxy_value: &Value) -> WebDriverResult<()> { 231 let obj = try_opt!( 232 proxy_value.as_object(), 233 ErrorStatus::InvalidArgument, 234 "proxy is not an object" 235 ); 236 237 for (key, value) in obj { 238 match &**key { 239 "proxyType" => match value.as_str() { 240 Some("pac") | Some("direct") | Some("autodetect") | Some("system") 241 | Some("manual") => {} 242 Some(x) => { 243 return Err(WebDriverError::new( 244 ErrorStatus::InvalidArgument, 245 format!("Invalid proxyType value: {}", x), 246 )) 247 } 248 None => { 249 return Err(WebDriverError::new( 250 ErrorStatus::InvalidArgument, 251 format!("proxyType is not a string: {}", value), 252 )) 253 } 254 }, 255 256 "proxyAutoconfigUrl" => match value.as_str() { 257 Some(x) => { 258 Url::parse(x).map_err(|_| { 259 WebDriverError::new( 260 ErrorStatus::InvalidArgument, 261 format!("proxyAutoconfigUrl is not a valid URL: {}", x), 262 ) 263 })?; 264 } 265 None => { 266 return Err(WebDriverError::new( 267 ErrorStatus::InvalidArgument, 268 "proxyAutoconfigUrl is not a string", 269 )) 270 } 271 }, 272 273 "httpProxy" => SpecNewSessionParameters::validate_host(value, "httpProxy")?, 274 "noProxy" => SpecNewSessionParameters::validate_no_proxy(value)?, 275 "sslProxy" => SpecNewSessionParameters::validate_host(value, "sslProxy")?, 276 "socksProxy" => SpecNewSessionParameters::validate_host(value, "socksProxy")?, 277 "socksVersion" => { 278 if !value.is_number() { 279 return Err(WebDriverError::new( 280 ErrorStatus::InvalidArgument, 281 format!("socksVersion is not a number: {}", value), 282 )); 283 } 284 } 285 286 x => { 287 return Err(WebDriverError::new( 288 ErrorStatus::InvalidArgument, 289 format!("Invalid proxy configuration entry: {}", x), 290 )) 291 } 292 } 293 } 294 295 Ok(()) 296 } 297 298 fn validate_no_proxy(value: &Value) -> WebDriverResult<()> { 299 match value.as_array() { 300 Some(hosts) => { 301 for host in hosts { 302 match host.as_str() { 303 Some(_) => {} 304 None => { 305 return Err(WebDriverError::new( 306 ErrorStatus::InvalidArgument, 307 format!("noProxy item is not a string: {}", host), 308 )) 309 } 310 } 311 } 312 } 313 None => { 314 return Err(WebDriverError::new( 315 ErrorStatus::InvalidArgument, 316 format!("noProxy is not an array: {}", value), 317 )) 318 } 319 } 320 321 Ok(()) 322 } 323 324 /// Validate whether a named capability is JSON value is a string 325 /// containing a host and possible port 326 fn validate_host(value: &Value, entry: &str) -> WebDriverResult<()> { 327 match value.as_str() { 328 Some(host) => { 329 if host.contains("://") { 330 return Err(WebDriverError::new( 331 ErrorStatus::InvalidArgument, 332 format!("{} must not contain a scheme: {}", entry, host), 333 )); 334 } 335 336 // Temporarily add a scheme so the host can be parsed as URL 337 let url = Url::parse(&format!("http://{}", host)).map_err(|_| { 338 WebDriverError::new( 339 ErrorStatus::InvalidArgument, 340 format!("{} is not a valid URL: {}", entry, host), 341 ) 342 })?; 343 344 if url.username() != "" 345 || url.password().is_some() 346 || url.path() != "/" 347 || url.query().is_some() 348 || url.fragment().is_some() 349 { 350 return Err(WebDriverError::new( 351 ErrorStatus::InvalidArgument, 352 format!("{} is not of the form host[:port]: {}", entry, host), 353 )); 354 } 355 } 356 357 None => { 358 return Err(WebDriverError::new( 359 ErrorStatus::InvalidArgument, 360 format!("{} is not a string: {}", entry, value), 361 )) 362 } 363 } 364 365 Ok(()) 366 } 367 368 fn validate_timeouts(value: &Value) -> WebDriverResult<()> { 369 let obj = try_opt!( 370 value.as_object(), 371 ErrorStatus::InvalidArgument, 372 "timeouts capability is not an object" 373 ); 374 375 for (key, value) in obj { 376 match &**key { 377 _x @ "script" if value.is_null() => {} 378 379 x @ "script" | x @ "pageLoad" | x @ "implicit" => { 380 let timeout = try_opt!( 381 value.as_f64(), 382 ErrorStatus::InvalidArgument, 383 format!("{} timeouts value is not a number: {}", x, value) 384 ); 385 if timeout < 0.0 || timeout.fract() != 0.0 { 386 return Err(WebDriverError::new( 387 ErrorStatus::InvalidArgument, 388 format!( 389 "'{}' timeouts value is not a positive Integer: {}", 390 x, timeout 391 ), 392 )); 393 } 394 if (timeout as u64) > MAX_SAFE_INTEGER { 395 return Err(WebDriverError::new( 396 ErrorStatus::InvalidArgument, 397 format!( 398 "'{}' timeouts value is greater than maximum safe integer: {}", 399 x, timeout 400 ), 401 )); 402 } 403 } 404 405 x => { 406 return Err(WebDriverError::new( 407 ErrorStatus::InvalidArgument, 408 format!("Invalid timeouts capability entry: {}", x), 409 )) 410 } 411 } 412 } 413 414 Ok(()) 415 } 416 417 fn validate_unhandled_prompt_behavior(value: &Value) -> WebDriverResult<()> { 418 match value { 419 Value::Object(obj) => { 420 // Unhandled Prompt Behavior type as used by WebDriver BiDi 421 for (key, value) in obj { 422 match &**key { 423 x @ "alert" 424 | x @ "beforeUnload" 425 | x @ "confirm" 426 | x @ "default" 427 | x @ "file" 428 | x @ "prompt" => { 429 let behavior = try_opt!( 430 value.as_str(), 431 ErrorStatus::InvalidArgument, 432 format!( 433 "'{}' unhandledPromptBehavior value is not a string: {}", 434 x, value 435 ) 436 ); 437 438 match behavior { 439 "accept" | "accept and notify" | "dismiss" 440 | "dismiss and notify" | "ignore" => {} 441 x => { 442 return Err(WebDriverError::new( 443 ErrorStatus::InvalidArgument, 444 format!( 445 "'{}' unhandledPromptBehavior value is invalid: {}", 446 x, behavior 447 ), 448 )) 449 } 450 } 451 } 452 x => { 453 return Err(WebDriverError::new( 454 ErrorStatus::InvalidArgument, 455 format!("Invalid unhandledPromptBehavior entry: {}", x), 456 )) 457 } 458 } 459 } 460 } 461 Value::String(behavior) => match behavior.as_str() { 462 "accept" | "accept and notify" | "dismiss" | "dismiss and notify" | "ignore" => {} 463 x => { 464 return Err(WebDriverError::new( 465 ErrorStatus::InvalidArgument, 466 format!("Invalid unhandledPromptBehavior value: {}", x), 467 )) 468 } 469 }, 470 _ => { 471 return Err(WebDriverError::new( 472 ErrorStatus::InvalidArgument, 473 format!( 474 "unhandledPromptBehavior is neither an object nor a string: {}", 475 value 476 ), 477 )) 478 } 479 } 480 481 Ok(()) 482 } 483 } 484 485 impl CapabilitiesMatching for SpecNewSessionParameters { 486 fn match_browser<T: BrowserCapabilities>( 487 &self, 488 browser_capabilities: &mut T, 489 ) -> WebDriverResult<Option<Capabilities>> { 490 let default = vec![Map::new()]; 491 let capabilities_list = if self.firstMatch.is_empty() { 492 &default 493 } else { 494 &self.firstMatch 495 }; 496 497 let merged_capabilities = capabilities_list 498 .iter() 499 .map(|first_match_entry| { 500 if first_match_entry 501 .keys() 502 .any(|k| self.alwaysMatch.contains_key(k)) 503 { 504 return Err(WebDriverError::new( 505 ErrorStatus::InvalidArgument, 506 "firstMatch key shadowed a value in alwaysMatch", 507 )); 508 } 509 let mut merged = self.alwaysMatch.clone(); 510 for (key, value) in first_match_entry.clone() { 511 merged.insert(key, value); 512 } 513 Ok(merged) 514 }) 515 .map(|merged| merged.and_then(|x| self.validate(x, browser_capabilities))) 516 .collect::<WebDriverResult<Vec<Capabilities>>>()?; 517 518 let selected = merged_capabilities 519 .iter() 520 .find(|merged| { 521 browser_capabilities.init(merged); 522 523 for (key, value) in merged.iter() { 524 match &**key { 525 "browserName" => { 526 let browserValue = browser_capabilities 527 .browser_name(merged) 528 .ok() 529 .and_then(|x| x); 530 531 if value.as_str() != browserValue.as_deref() { 532 return false; 533 } 534 } 535 "browserVersion" => { 536 let browserValue = browser_capabilities 537 .browser_version(merged) 538 .ok() 539 .and_then(|x| x); 540 // We already validated this was a string 541 let version_cond = value.as_str().unwrap_or(""); 542 if let Some(version) = browserValue { 543 if !browser_capabilities 544 .compare_browser_version(&version, version_cond) 545 .unwrap_or(false) 546 { 547 return false; 548 } 549 } else { 550 return false; 551 } 552 } 553 "platformName" => { 554 let browserValue = browser_capabilities 555 .platform_name(merged) 556 .ok() 557 .and_then(|x| x); 558 if value.as_str() != browserValue.as_deref() { 559 return false; 560 } 561 } 562 "acceptInsecureCerts" => { 563 if value.as_bool().unwrap_or(false) 564 && !browser_capabilities 565 .accept_insecure_certs(merged) 566 .unwrap_or(false) 567 { 568 return false; 569 } 570 } 571 "setWindowRect" => { 572 if value.as_bool().unwrap_or(false) 573 && !browser_capabilities 574 .set_window_rect(merged) 575 .unwrap_or(false) 576 { 577 return false; 578 } 579 } 580 "strictFileInteractability" => { 581 if value.as_bool().unwrap_or(false) 582 && !browser_capabilities 583 .strict_file_interactability(merged) 584 .unwrap_or(false) 585 { 586 return false; 587 } 588 } 589 "proxy" => { 590 let default = Map::new(); 591 let proxy = value.as_object().unwrap_or(&default); 592 if !browser_capabilities 593 .accept_proxy(proxy, merged) 594 .unwrap_or(false) 595 { 596 return false; 597 } 598 } 599 "webSocketUrl" => { 600 if value.as_bool().unwrap_or(false) 601 && !browser_capabilities.web_socket_url(merged).unwrap_or(false) 602 { 603 return false; 604 } 605 } 606 "webauthn:virtualAuthenticators" => { 607 if value.as_bool().unwrap_or(false) 608 && !browser_capabilities 609 .webauthn_virtual_authenticators(merged) 610 .unwrap_or(false) 611 { 612 return false; 613 } 614 } 615 "webauthn:extension:uvm" => { 616 if value.as_bool().unwrap_or(false) 617 && !browser_capabilities 618 .webauthn_extension_uvm(merged) 619 .unwrap_or(false) 620 { 621 return false; 622 } 623 } 624 "webauthn:extension:prf" => { 625 if value.as_bool().unwrap_or(false) 626 && !browser_capabilities 627 .webauthn_extension_prf(merged) 628 .unwrap_or(false) 629 { 630 return false; 631 } 632 } 633 "webauthn:extension:largeBlob" => { 634 if value.as_bool().unwrap_or(false) 635 && !browser_capabilities 636 .webauthn_extension_large_blob(merged) 637 .unwrap_or(false) 638 { 639 return false; 640 } 641 } 642 "webauthn:extension:credBlob" => { 643 if value.as_bool().unwrap_or(false) 644 && !browser_capabilities 645 .webauthn_extension_cred_blob(merged) 646 .unwrap_or(false) 647 { 648 return false; 649 } 650 } 651 name => { 652 if name.contains(':') { 653 if !browser_capabilities 654 .accept_custom(name, value, merged) 655 .unwrap_or(false) 656 { 657 return false; 658 } 659 } else { 660 // Accept the capability 661 } 662 } 663 } 664 } 665 666 true 667 }) 668 .cloned(); 669 Ok(selected) 670 } 671 } 672 673 #[cfg(test)] 674 mod tests { 675 use super::*; 676 use crate::test::assert_de; 677 use serde_json::{self, json}; 678 679 #[test] 680 fn test_json_spec_new_session_parameters_alwaysMatch_only() { 681 let caps = SpecNewSessionParameters { 682 alwaysMatch: Capabilities::new(), 683 firstMatch: vec![Capabilities::new()], 684 }; 685 assert_de(&caps, json!({"alwaysMatch": {}})); 686 } 687 688 #[test] 689 fn test_json_spec_new_session_parameters_firstMatch_only() { 690 let caps = SpecNewSessionParameters { 691 alwaysMatch: Capabilities::new(), 692 firstMatch: vec![Capabilities::new()], 693 }; 694 assert_de(&caps, json!({"firstMatch": [{}]})); 695 } 696 697 #[test] 698 fn test_json_spec_new_session_parameters_alwaysMatch_null() { 699 let json = json!({ 700 "alwaysMatch": null, 701 "firstMatch": [{}], 702 }); 703 assert!(serde_json::from_value::<SpecNewSessionParameters>(json).is_err()); 704 } 705 706 #[test] 707 fn test_json_spec_new_session_parameters_firstMatch_null() { 708 let json = json!({ 709 "alwaysMatch": {}, 710 "firstMatch": null, 711 }); 712 assert!(serde_json::from_value::<SpecNewSessionParameters>(json).is_err()); 713 } 714 715 #[test] 716 fn test_json_spec_new_session_parameters_both_empty() { 717 let json = json!({ 718 "alwaysMatch": {}, 719 "firstMatch": [{}], 720 }); 721 let caps = SpecNewSessionParameters { 722 alwaysMatch: Capabilities::new(), 723 firstMatch: vec![Capabilities::new()], 724 }; 725 726 assert_de(&caps, json); 727 } 728 729 #[test] 730 fn test_json_spec_new_session_parameters_both_with_capability() { 731 let json = json!({ 732 "alwaysMatch": {"foo": "bar"}, 733 "firstMatch": [{"foo2": "bar2"}], 734 }); 735 let mut caps = SpecNewSessionParameters { 736 alwaysMatch: Capabilities::new(), 737 firstMatch: vec![Capabilities::new()], 738 }; 739 caps.alwaysMatch.insert("foo".into(), "bar".into()); 740 caps.firstMatch[0].insert("foo2".into(), "bar2".into()); 741 742 assert_de(&caps, json); 743 } 744 745 #[test] 746 fn test_validate_unhandled_prompt_behavior() { 747 fn validate_prompt_behavior(v: Value) -> WebDriverResult<()> { 748 SpecNewSessionParameters::validate_unhandled_prompt_behavior(&v) 749 } 750 751 // capability as string 752 validate_prompt_behavior(json!("accept")).unwrap(); 753 validate_prompt_behavior(json!("accept and notify")).unwrap(); 754 validate_prompt_behavior(json!("dismiss")).unwrap(); 755 validate_prompt_behavior(json!("dismiss and notify")).unwrap(); 756 validate_prompt_behavior(json!("ignore")).unwrap(); 757 assert!(validate_prompt_behavior(json!("foo")).is_err()); 758 759 // capability as object 760 let types = ["alert", "beforeUnload", "confirm", "default", "file", "prompt"]; 761 let handlers = [ 762 "accept", 763 "accept and notify", 764 "dismiss", 765 "dismiss and notify", 766 "ignore", 767 ]; 768 for promptType in types { 769 assert!(validate_prompt_behavior(json!({promptType: "foo"})).is_err()); 770 for handler in handlers { 771 validate_prompt_behavior(json!({promptType: handler})).unwrap(); 772 } 773 } 774 775 for handler in handlers { 776 assert!(validate_prompt_behavior(json!({"foo": handler})).is_err()); 777 } 778 } 779 780 #[test] 781 fn test_validate_proxy() { 782 fn validate_proxy(v: Value) -> WebDriverResult<()> { 783 SpecNewSessionParameters::validate_proxy(&v) 784 } 785 786 // proxy hosts 787 validate_proxy(json!({"httpProxy": "127.0.0.1"})).unwrap(); 788 validate_proxy(json!({"httpProxy": "127.0.0.1:"})).unwrap(); 789 validate_proxy(json!({"httpProxy": "127.0.0.1:3128"})).unwrap(); 790 validate_proxy(json!({"httpProxy": "localhost"})).unwrap(); 791 validate_proxy(json!({"httpProxy": "localhost:3128"})).unwrap(); 792 validate_proxy(json!({"httpProxy": "[2001:db8::1]"})).unwrap(); 793 validate_proxy(json!({"httpProxy": "[2001:db8::1]:3128"})).unwrap(); 794 validate_proxy(json!({"httpProxy": "example.org"})).unwrap(); 795 validate_proxy(json!({"httpProxy": "example.org:3128"})).unwrap(); 796 797 assert!(validate_proxy(json!({"httpProxy": "http://example.org"})).is_err()); 798 assert!(validate_proxy(json!({"httpProxy": "example.org:-1"})).is_err()); 799 assert!(validate_proxy(json!({"httpProxy": "2001:db8::1"})).is_err()); 800 801 // no proxy for manual proxy type 802 validate_proxy(json!({"noProxy": ["foo"]})).unwrap(); 803 804 assert!(validate_proxy(json!({"noProxy": "foo"})).is_err()); 805 assert!(validate_proxy(json!({"noProxy": [42]})).is_err()); 806 } 807 }