quirks.rs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // Copyright 2016 The rust-url developers.
  2. //
  3. // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  4. // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  5. // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  6. // option. This file may not be copied, modified, or distributed
  7. // except according to those terms.
  8. //! Getters and setters for URL components implemented per https://url.spec.whatwg.org/#api
  9. //!
  10. //! Unless you need to be interoperable with web browsers,
  11. //! you probably want to use `Url` method instead.
  12. use crate::parser::{default_port, Context, Input, Parser, SchemeType};
  13. use crate::{Host, ParseError, Position, Url};
  14. /// https://url.spec.whatwg.org/#dom-url-domaintoascii
  15. pub fn domain_to_ascii(domain: &str) -> String {
  16. match Host::parse(domain) {
  17. Ok(Host::Domain(domain)) => domain,
  18. _ => String::new(),
  19. }
  20. }
  21. /// https://url.spec.whatwg.org/#dom-url-domaintounicode
  22. #[cfg(feature = "idna")]
  23. pub fn domain_to_unicode(domain: &str) -> String {
  24. match Host::parse(domain) {
  25. Ok(Host::Domain(ref domain)) => {
  26. let (unicode, _errors) = idna::domain_to_unicode(domain);
  27. unicode
  28. }
  29. _ => String::new(),
  30. }
  31. }
  32. /// Getter for https://url.spec.whatwg.org/#dom-url-href
  33. pub fn href(url: &Url) -> &str {
  34. url.as_str()
  35. }
  36. /// Setter for https://url.spec.whatwg.org/#dom-url-href
  37. pub fn set_href(url: &mut Url, value: &str) -> Result<(), ParseError> {
  38. *url = Url::parse(value)?;
  39. Ok(())
  40. }
  41. /// Getter for https://url.spec.whatwg.org/#dom-url-origin
  42. pub fn origin(url: &Url) -> String {
  43. url.origin().ascii_serialization()
  44. }
  45. /// Getter for https://url.spec.whatwg.org/#dom-url-protocol
  46. #[inline]
  47. pub fn protocol(url: &Url) -> &str {
  48. &url.as_str()[..url.scheme().len() + ":".len()]
  49. }
  50. /// Setter for https://url.spec.whatwg.org/#dom-url-protocol
  51. #[allow(clippy::result_unit_err)]
  52. pub fn set_protocol(url: &mut Url, mut new_protocol: &str) -> Result<(), ()> {
  53. // The scheme state in the spec ignores everything after the first `:`,
  54. // but `set_scheme` errors if there is more.
  55. if let Some(position) = new_protocol.find(':') {
  56. new_protocol = &new_protocol[..position];
  57. }
  58. url.set_scheme(new_protocol)
  59. }
  60. /// Getter for https://url.spec.whatwg.org/#dom-url-username
  61. #[inline]
  62. pub fn username(url: &Url) -> &str {
  63. url.username()
  64. }
  65. /// Setter for https://url.spec.whatwg.org/#dom-url-username
  66. #[allow(clippy::result_unit_err)]
  67. pub fn set_username(url: &mut Url, new_username: &str) -> Result<(), ()> {
  68. url.set_username(new_username)
  69. }
  70. /// Getter for https://url.spec.whatwg.org/#dom-url-password
  71. #[inline]
  72. pub fn password(url: &Url) -> &str {
  73. url.password().unwrap_or("")
  74. }
  75. /// Setter for https://url.spec.whatwg.org/#dom-url-password
  76. #[allow(clippy::result_unit_err)]
  77. pub fn set_password(url: &mut Url, new_password: &str) -> Result<(), ()> {
  78. url.set_password(if new_password.is_empty() {
  79. None
  80. } else {
  81. Some(new_password)
  82. })
  83. }
  84. /// Getter for https://url.spec.whatwg.org/#dom-url-host
  85. #[inline]
  86. pub fn host(url: &Url) -> &str {
  87. &url[Position::BeforeHost..Position::AfterPort]
  88. }
  89. /// Setter for https://url.spec.whatwg.org/#dom-url-host
  90. #[allow(clippy::result_unit_err)]
  91. pub fn set_host(url: &mut Url, new_host: &str) -> Result<(), ()> {
  92. // If context object’s url’s cannot-be-a-base-URL flag is set, then return.
  93. if url.cannot_be_a_base() {
  94. return Err(());
  95. }
  96. // Host parsing rules are strict,
  97. // We don't want to trim the input
  98. let input = Input::no_trim(new_host);
  99. let host;
  100. let opt_port;
  101. {
  102. let scheme = url.scheme();
  103. let scheme_type = SchemeType::from(scheme);
  104. if scheme_type == SchemeType::File && new_host.is_empty() {
  105. url.set_host_internal(Host::Domain(String::new()), None);
  106. return Ok(());
  107. }
  108. if let Ok((h, remaining)) = Parser::parse_host(input, scheme_type) {
  109. host = h;
  110. opt_port = if let Some(remaining) = remaining.split_prefix(':') {
  111. if remaining.is_empty() {
  112. None
  113. } else {
  114. Parser::parse_port(remaining, || default_port(scheme), Context::Setter)
  115. .ok()
  116. .map(|(port, _remaining)| port)
  117. }
  118. } else {
  119. None
  120. };
  121. } else {
  122. return Err(());
  123. }
  124. }
  125. // Make sure we won't set an empty host to a url with a username or a port
  126. if host == Host::Domain("".to_string())
  127. && (!username(url).is_empty() || matches!(opt_port, Some(Some(_))) || url.port().is_some())
  128. {
  129. return Err(());
  130. }
  131. url.set_host_internal(host, opt_port);
  132. Ok(())
  133. }
  134. /// Getter for https://url.spec.whatwg.org/#dom-url-hostname
  135. #[inline]
  136. pub fn hostname(url: &Url) -> &str {
  137. url.host_str().unwrap_or("")
  138. }
  139. /// Setter for https://url.spec.whatwg.org/#dom-url-hostname
  140. #[allow(clippy::result_unit_err)]
  141. pub fn set_hostname(url: &mut Url, new_hostname: &str) -> Result<(), ()> {
  142. if url.cannot_be_a_base() {
  143. return Err(());
  144. }
  145. // Host parsing rules are strict we don't want to trim the input
  146. let input = Input::no_trim(new_hostname);
  147. let scheme_type = SchemeType::from(url.scheme());
  148. if scheme_type == SchemeType::File && new_hostname.is_empty() {
  149. url.set_host_internal(Host::Domain(String::new()), None);
  150. return Ok(());
  151. }
  152. if let Ok((host, _remaining)) = Parser::parse_host(input, scheme_type) {
  153. if let Host::Domain(h) = &host {
  154. if h.is_empty() {
  155. // Empty host on special not file url
  156. if SchemeType::from(url.scheme()) == SchemeType::SpecialNotFile
  157. // Port with an empty host
  158. ||!port(url).is_empty()
  159. // Empty host that includes credentials
  160. || !url.username().is_empty()
  161. || !url.password().unwrap_or("").is_empty()
  162. {
  163. return Err(());
  164. }
  165. }
  166. }
  167. url.set_host_internal(host, None);
  168. Ok(())
  169. } else {
  170. Err(())
  171. }
  172. }
  173. /// Getter for https://url.spec.whatwg.org/#dom-url-port
  174. #[inline]
  175. pub fn port(url: &Url) -> &str {
  176. &url[Position::BeforePort..Position::AfterPort]
  177. }
  178. /// Setter for https://url.spec.whatwg.org/#dom-url-port
  179. #[allow(clippy::result_unit_err)]
  180. pub fn set_port(url: &mut Url, new_port: &str) -> Result<(), ()> {
  181. let result;
  182. {
  183. // has_host implies !cannot_be_a_base
  184. let scheme = url.scheme();
  185. if !url.has_host() || url.host() == Some(Host::Domain("")) || scheme == "file" {
  186. return Err(());
  187. }
  188. result = Parser::parse_port(
  189. Input::new(new_port),
  190. || default_port(scheme),
  191. Context::Setter,
  192. )
  193. }
  194. if let Ok((new_port, _remaining)) = result {
  195. url.set_port_internal(new_port);
  196. Ok(())
  197. } else {
  198. Err(())
  199. }
  200. }
  201. /// Getter for https://url.spec.whatwg.org/#dom-url-pathname
  202. #[inline]
  203. pub fn pathname(url: &Url) -> &str {
  204. url.path()
  205. }
  206. /// Setter for https://url.spec.whatwg.org/#dom-url-pathname
  207. pub fn set_pathname(url: &mut Url, new_pathname: &str) {
  208. if url.cannot_be_a_base() {
  209. return;
  210. }
  211. if new_pathname.starts_with('/')
  212. || (SchemeType::from(url.scheme()).is_special()
  213. // \ is a segment delimiter for 'special' URLs"
  214. && new_pathname.starts_with('\\'))
  215. {
  216. url.set_path(new_pathname)
  217. } else {
  218. let mut path_to_set = String::from("/");
  219. path_to_set.push_str(new_pathname);
  220. url.set_path(&path_to_set)
  221. }
  222. }
  223. /// Getter for https://url.spec.whatwg.org/#dom-url-search
  224. pub fn search(url: &Url) -> &str {
  225. trim(&url[Position::AfterPath..Position::AfterQuery])
  226. }
  227. /// Setter for https://url.spec.whatwg.org/#dom-url-search
  228. pub fn set_search(url: &mut Url, new_search: &str) {
  229. url.set_query(match new_search {
  230. "" => None,
  231. _ if new_search.starts_with('?') => Some(&new_search[1..]),
  232. _ => Some(new_search),
  233. })
  234. }
  235. /// Getter for https://url.spec.whatwg.org/#dom-url-hash
  236. pub fn hash(url: &Url) -> &str {
  237. trim(&url[Position::AfterQuery..])
  238. }
  239. /// Setter for https://url.spec.whatwg.org/#dom-url-hash
  240. pub fn set_hash(url: &mut Url, new_hash: &str) {
  241. url.set_fragment(match new_hash {
  242. // If the given value is the empty string,
  243. // then set context object’s url’s fragment to null and return.
  244. "" => None,
  245. // Let input be the given value with a single leading U+0023 (#) removed, if any.
  246. _ if new_hash.starts_with('#') => Some(&new_hash[1..]),
  247. _ => Some(new_hash),
  248. })
  249. }
  250. fn trim(s: &str) -> &str {
  251. if s.len() == 1 {
  252. ""
  253. } else {
  254. s
  255. }
  256. }