lib.rs (14215B)
1 /* -*- Mode: rust; rust-indent-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #![expect( 7 clippy::missing_safety_doc, 8 clippy::missing_panics_doc, 9 reason = "OK here" 10 )] 11 #![allow(unknown_lints, mismatched_lifetime_syntaxes)] 12 13 extern crate url; 14 use url::{quirks, ParseOptions, Position, Url}; 15 16 extern crate nsstring; 17 use nsstring::{nsACString, nsCString}; 18 19 extern crate nserror; 20 use nserror::{nsresult, NS_ERROR_MALFORMED_URI, NS_ERROR_UNEXPECTED, NS_OK}; 21 22 extern crate xpcom; 23 use xpcom::{AtomicRefcnt, RefCounted, RefPtr}; 24 25 extern crate uuid; 26 use std::{fmt::Write as _, marker::PhantomData, ops, ptr, str}; 27 28 use uuid::Uuid; 29 30 extern "C" { 31 fn Gecko_StrictFileOriginPolicy() -> bool; 32 } 33 34 /// Helper macro. If the expression $e is Ok(t) evaluates to t, otherwise, 35 /// returns `NS_ERROR_MALFORMED_URI`. 36 macro_rules! try_or_malformed { 37 ($e:expr) => { 38 match $e { 39 Ok(v) => v, 40 Err(_) => return NS_ERROR_MALFORMED_URI, 41 } 42 }; 43 } 44 45 fn parser<'a>() -> ParseOptions<'a> { 46 Url::options() 47 } 48 49 fn default_port(scheme: &str) -> Option<u16> { 50 match scheme { 51 "ftp" => Some(21), 52 "gopher" => Some(70), 53 "http" | "ws" => Some(80), 54 "https" | "wss" | "rtsp" | "android" => Some(443), 55 _ => None, 56 } 57 } 58 59 /// A slice into the backing string. 60 /// 61 /// This type is only valid as long as the [`MozURL`] which it was pulled from is 62 /// valid. In C++, this type implicitly converts to a `nsDependentCString`, and is 63 /// an implementation detail. 64 /// 65 /// This type exists because, unlike `&str`, this type is safe to return over FFI. 66 #[repr(C)] 67 pub struct SpecSlice<'a> { 68 data: *const u8, 69 len: u32, 70 _marker: PhantomData<&'a [u8]>, 71 } 72 73 impl<'a> From<&'a str> for SpecSlice<'a> { 74 fn from(s: &'a str) -> Self { 75 SpecSlice { 76 data: s.as_ptr(), 77 len: u32::try_from(s.len()).expect("string length not representable in u32"), 78 _marker: PhantomData, 79 } 80 } 81 } 82 83 /// The [`MozURL`] reference-counted threadsafe URL type. This type intentionally 84 /// implements no XPCOM interfaces, and all method calls are non-virtual. 85 #[repr(C)] 86 pub struct MozURL { 87 pub url: Url, 88 refcnt: AtomicRefcnt, 89 } 90 91 impl MozURL { 92 #[must_use] 93 pub fn from_url(url: Url) -> RefPtr<Self> { 94 // Actually allocate the URL on the heap. This is the only place we actually 95 // create a [`MozURL`], other than in `clone()`. 96 unsafe { 97 RefPtr::from_raw(Box::into_raw(Box::new(Self { 98 url, 99 refcnt: AtomicRefcnt::new(), 100 }))) 101 .expect("MozURL created OK") 102 } 103 } 104 } 105 106 impl ops::Deref for MozURL { 107 type Target = Url; 108 fn deref(&self) -> &Url { 109 &self.url 110 } 111 } 112 impl ops::DerefMut for MozURL { 113 fn deref_mut(&mut self) -> &mut Url { 114 &mut self.url 115 } 116 } 117 118 // Memory Management for MozURL 119 #[no_mangle] 120 pub unsafe extern "C" fn mozurl_addref(url: &MozURL) { 121 url.refcnt.inc(); 122 } 123 124 #[no_mangle] 125 pub unsafe extern "C" fn mozurl_release(url: &MozURL) { 126 let rc = url.refcnt.dec(); 127 if rc == 0 { 128 drop(Box::from_raw(ptr::from_ref::<MozURL>(url).cast_mut())); 129 } 130 } 131 132 // xpcom::RefPtr support 133 unsafe impl RefCounted for MozURL { 134 unsafe fn addref(&self) { 135 mozurl_addref(self); 136 } 137 unsafe fn release(&self) { 138 mozurl_release(self); 139 } 140 } 141 142 // Allocate a new MozURL object with a RefCnt of 1, and store a pointer to it 143 // into url. 144 #[no_mangle] 145 pub extern "C" fn mozurl_new( 146 result: &mut *const MozURL, 147 spec: &nsACString, 148 base: Option<&MozURL>, 149 ) -> nsresult { 150 *result = ptr::null_mut(); 151 152 let spec = try_or_malformed!(str::from_utf8(spec)); 153 let url = if let Some(base) = base { 154 try_or_malformed!(base.url.join(spec)) 155 } else { 156 try_or_malformed!(parser().parse(spec)) 157 }; 158 159 MozURL::from_url(url).forget(result); 160 NS_OK 161 } 162 163 /// Allocate a new [`MozURL`] object which is a clone of the original, and store a 164 /// pointer to it into newurl. 165 #[no_mangle] 166 pub extern "C" fn mozurl_clone(url: &MozURL, newurl: &mut *const MozURL) { 167 MozURL::from_url(url.url.clone()).forget(newurl); 168 } 169 170 #[no_mangle] 171 pub extern "C" fn mozurl_spec(url: &MozURL) -> SpecSlice { 172 url.as_ref().into() 173 } 174 175 #[no_mangle] 176 pub extern "C" fn mozurl_scheme(url: &MozURL) -> SpecSlice { 177 url.scheme().into() 178 } 179 180 #[no_mangle] 181 pub extern "C" fn mozurl_username(url: &MozURL) -> SpecSlice { 182 if url.cannot_be_a_base() { 183 "".into() 184 } else { 185 url.username().into() 186 } 187 } 188 189 #[no_mangle] 190 pub extern "C" fn mozurl_password(url: &MozURL) -> SpecSlice { 191 url.password().unwrap_or("").into() 192 } 193 194 #[no_mangle] 195 pub extern "C" fn mozurl_host(url: &MozURL) -> SpecSlice { 196 url.host_str().unwrap_or("").into() 197 } 198 199 #[no_mangle] 200 pub extern "C" fn mozurl_port(url: &MozURL) -> i32 { 201 // NOTE: Gecko uses -1 to represent the default port. 202 url.port().map_or(-1, i32::from) 203 } 204 205 #[no_mangle] 206 pub extern "C" fn mozurl_real_port(url: &MozURL) -> i32 { 207 url.port() 208 .or_else(|| default_port(url.scheme())) 209 .map_or(-1, i32::from) 210 } 211 212 #[no_mangle] 213 pub extern "C" fn mozurl_host_port(url: &MozURL) -> SpecSlice { 214 if url.port().is_some() { 215 return (&url[Position::BeforeHost..Position::BeforePath]).into(); 216 } 217 url.host_str().unwrap_or("").into() 218 } 219 220 #[no_mangle] 221 pub extern "C" fn mozurl_filepath(url: &MozURL) -> SpecSlice { 222 url.path().into() 223 } 224 225 #[no_mangle] 226 pub extern "C" fn mozurl_path(url: &MozURL) -> SpecSlice { 227 (&url[Position::BeforePath..]).into() 228 } 229 230 #[no_mangle] 231 pub extern "C" fn mozurl_query(url: &MozURL) -> SpecSlice { 232 url.query().unwrap_or("").into() 233 } 234 235 #[no_mangle] 236 pub extern "C" fn mozurl_fragment(url: &MozURL) -> SpecSlice { 237 url.fragment().unwrap_or("").into() 238 } 239 240 #[no_mangle] 241 pub extern "C" fn mozurl_spec_no_ref(url: &MozURL) -> SpecSlice { 242 (&url[..Position::AfterQuery]).into() 243 } 244 245 #[no_mangle] 246 pub extern "C" fn mozurl_has_fragment(url: &MozURL) -> bool { 247 url.fragment().is_some() 248 } 249 250 #[no_mangle] 251 pub extern "C" fn mozurl_has_query(url: &MozURL) -> bool { 252 url.query().is_some() 253 } 254 255 #[no_mangle] 256 pub extern "C" fn mozurl_directory(url: &MozURL) -> SpecSlice { 257 url.path().rfind('/').map_or_else( 258 || url.path().into(), 259 |position| url.path()[..=position].into(), 260 ) 261 } 262 263 #[no_mangle] 264 pub extern "C" fn mozurl_prepath(url: &MozURL) -> SpecSlice { 265 (&url[..Position::BeforePath]).into() 266 } 267 268 fn get_origin(url: &MozURL) -> Option<String> { 269 match url.scheme() { 270 "blob" | "ftp" | "http" | "https" | "ws" | "wss" => { 271 Some(url.origin().ascii_serialization()) 272 } 273 "indexeddb" | "moz-extension" | "resource" => { 274 let host = url.host_str().unwrap_or(""); 275 276 let port = url.port().or_else(|| default_port(url.scheme())); 277 278 if port == default_port(url.scheme()) { 279 Some(format!("{}://{}", url.scheme(), host)) 280 } else { 281 Some(format!( 282 "{}://{}:{}", 283 url.scheme(), 284 host, 285 port.expect("got a port") 286 )) 287 } 288 } 289 "file" => { 290 if unsafe { Gecko_StrictFileOriginPolicy() } { 291 Some(url[..Position::AfterPath].to_owned()) 292 } else { 293 Some("file://UNIVERSAL_FILE_URI_ORIGIN".to_owned()) 294 } 295 } 296 "about" | "moz-safe-about" => Some(url[..Position::AfterPath].to_owned()), 297 _ => None, 298 } 299 } 300 301 #[no_mangle] 302 pub extern "C" fn mozurl_origin(url: &MozURL, origin: &mut nsACString) { 303 let origin_str = if url.as_ref().starts_with("about:blank") { 304 None 305 } else { 306 get_origin(url) 307 }; 308 309 let origin_str = origin_str.unwrap_or_else(|| { 310 // nsIPrincipal stores the uuid, so the same uuid is returned everytime. 311 // We can't do that for MozURL because it can be used across threads. 312 // Storing uuid would mutate the object which would cause races between 313 // threads. 314 format!("moz-nullprincipal:{{{}}}", Uuid::new_v4()) 315 }); 316 317 // NOTE: Try to re-use the allocation we got from rust-url, and transfer 318 // ownership of the buffer to C++. 319 let mut o = nsCString::from(origin_str); 320 origin.take_from(&mut o); 321 } 322 323 // Helper macro for debug asserting that we're the only reference to MozURL. 324 macro_rules! debug_assert_mut { 325 ($e:expr) => { 326 debug_assert_eq!($e.refcnt.get(), 1, "Cannot mutate an aliased MozURL!"); 327 }; 328 } 329 330 #[no_mangle] 331 pub extern "C" fn mozurl_cannot_be_a_base(url: &mut MozURL) -> bool { 332 debug_assert_mut!(url); 333 url.cannot_be_a_base() 334 } 335 336 #[no_mangle] 337 pub extern "C" fn mozurl_set_scheme(url: &mut MozURL, scheme: &nsACString) -> nsresult { 338 debug_assert_mut!(url); 339 let scheme = try_or_malformed!(str::from_utf8(scheme)); 340 try_or_malformed!(quirks::set_protocol(url, scheme)); 341 NS_OK 342 } 343 344 #[no_mangle] 345 pub extern "C" fn mozurl_set_username(url: &mut MozURL, username: &nsACString) -> nsresult { 346 debug_assert_mut!(url); 347 let username = try_or_malformed!(str::from_utf8(username)); 348 try_or_malformed!(quirks::set_username(url, username)); 349 NS_OK 350 } 351 352 #[no_mangle] 353 pub extern "C" fn mozurl_set_password(url: &mut MozURL, password: &nsACString) -> nsresult { 354 debug_assert_mut!(url); 355 let password = try_or_malformed!(str::from_utf8(password)); 356 try_or_malformed!(quirks::set_password(url, password)); 357 NS_OK 358 } 359 360 #[no_mangle] 361 pub extern "C" fn mozurl_set_host_port(url: &mut MozURL, hostport: &nsACString) -> nsresult { 362 debug_assert_mut!(url); 363 let hostport = try_or_malformed!(str::from_utf8(hostport)); 364 try_or_malformed!(quirks::set_host(url, hostport)); 365 NS_OK 366 } 367 368 #[no_mangle] 369 pub extern "C" fn mozurl_set_hostname(url: &mut MozURL, host: &nsACString) -> nsresult { 370 debug_assert_mut!(url); 371 let host = try_or_malformed!(str::from_utf8(host)); 372 try_or_malformed!(quirks::set_hostname(url, host)); 373 NS_OK 374 } 375 376 #[no_mangle] 377 pub extern "C" fn mozurl_set_port_no(url: &mut MozURL, new_port: i32) -> nsresult { 378 debug_assert_mut!(url); 379 380 if new_port > i32::from(u16::MAX) { 381 return NS_ERROR_UNEXPECTED; 382 } 383 384 if url.cannot_be_a_base() { 385 return NS_ERROR_MALFORMED_URI; 386 } 387 388 #[expect(clippy::cast_sign_loss, reason = "not possible due to first match arm")] 389 let port = match new_port { 390 new if new < 0 || i32::from(u16::MAX) <= new => None, 391 new if Some(new as u16) == default_port(url.scheme()) => None, 392 new => Some(new as u16), 393 }; 394 try_or_malformed!(url.set_port(port)); 395 NS_OK 396 } 397 398 #[no_mangle] 399 pub extern "C" fn mozurl_set_pathname(url: &mut MozURL, path: &nsACString) -> nsresult { 400 debug_assert_mut!(url); 401 let path = try_or_malformed!(str::from_utf8(path)); 402 quirks::set_pathname(url, path); 403 NS_OK 404 } 405 406 #[no_mangle] 407 pub extern "C" fn mozurl_set_query(url: &mut MozURL, query: &nsACString) -> nsresult { 408 debug_assert_mut!(url); 409 let query = try_or_malformed!(str::from_utf8(query)); 410 quirks::set_search(url, query); 411 NS_OK 412 } 413 414 #[no_mangle] 415 pub extern "C" fn mozurl_set_fragment(url: &mut MozURL, fragment: &nsACString) -> nsresult { 416 debug_assert_mut!(url); 417 let fragment = try_or_malformed!(str::from_utf8(fragment)); 418 quirks::set_hash(url, fragment); 419 NS_OK 420 } 421 422 #[no_mangle] 423 pub extern "C" fn mozurl_sizeof(url: &MozURL) -> usize { 424 debug_assert_mut!(url); 425 size_of::<MozURL>() + url.as_str().len() 426 } 427 428 #[no_mangle] 429 pub extern "C" fn mozurl_common_base( 430 url1: &MozURL, 431 url2: &MozURL, 432 result: &mut *const MozURL, 433 ) -> nsresult { 434 *result = ptr::null(); 435 if url1.url == url2.url { 436 RefPtr::new(url1).forget(result); 437 return NS_OK; 438 } 439 440 if url1.scheme() != url2.scheme() 441 || url1.host() != url2.host() 442 || url1.username() != url2.username() 443 || url1.password() != url2.password() 444 || url1.port() != url2.port() 445 { 446 return NS_OK; 447 } 448 449 match (url1.path_segments(), url2.path_segments()) { 450 (Some(path1), Some(path2)) => { 451 // Take the shared prefix of path segments 452 let mut url = url1.url.clone(); 453 if let Ok(mut segs) = url.path_segments_mut() { 454 segs.clear(); 455 segs.extend(path1.zip(path2).take_while(|&(a, b)| a == b).map(|p| p.0)); 456 } else { 457 return NS_OK; 458 } 459 460 MozURL::from_url(url).forget(result); 461 NS_OK 462 } 463 _ => NS_OK, 464 } 465 } 466 467 #[no_mangle] 468 pub extern "C" fn mozurl_relative( 469 url1: &MozURL, 470 url2: &MozURL, 471 result: &mut nsACString, 472 ) -> nsresult { 473 if url1.url == url2.url { 474 result.truncate(); 475 return NS_OK; 476 } 477 478 if url1.scheme() != url2.scheme() 479 || url1.host() != url2.host() 480 || url1.username() != url2.username() 481 || url1.password() != url2.password() 482 || url1.port() != url2.port() 483 { 484 result.assign(url2.as_ref()); 485 return NS_OK; 486 } 487 488 match (url1.path_segments(), url2.path_segments()) { 489 (Some(mut path1), Some(mut path2)) => { 490 // Exhaust the part of the iterators that match 491 while let (Some(p1), Some(p2)) = (&path1.next(), &path2.next()) { 492 if p1 != p2 { 493 break; 494 } 495 } 496 497 result.truncate(); 498 for _ in path1 { 499 result.append("../"); 500 } 501 for p2 in path2 { 502 result.append(p2); 503 result.append("/"); 504 } 505 } 506 _ => { 507 result.assign(url2.as_ref()); 508 } 509 } 510 NS_OK 511 } 512 513 /// This type is used by nsStandardURL 514 #[no_mangle] 515 pub extern "C" fn rusturl_parse_ipv6addr(input: &nsACString, addr: &mut nsACString) -> nsresult { 516 let ip6 = try_or_malformed!(str::from_utf8(input)); 517 let host = try_or_malformed!(url::Host::parse(ip6)); 518 _ = write!(addr, "{host}"); 519 NS_OK 520 }