lib.rs (8252B)
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 core::fmt::Write; 6 use std::borrow::Cow; 7 8 use idna::uts46::AsciiDenyList; 9 use idna::uts46::ErrorPolicy; 10 use idna::uts46::Hyphens; 11 use idna::uts46::ProcessingError; 12 use idna::uts46::ProcessingSuccess; 13 use idna::uts46::Uts46; 14 use nserror::*; 15 use nsstring::*; 16 use percent_encoding::percent_decode; 17 18 /// The URL deny list plus asterisk and double quote. 19 /// Using AsciiDenyList::URL is https://bugzilla.mozilla.org/show_bug.cgi?id=1815926 . 20 const GECKO: AsciiDenyList = AsciiDenyList::new(true, "%#/:<>?@[\\]^|*\""); 21 22 /// Deny only glyphless ASCII to accommodate legacy callers. 23 const GLYPHLESS: AsciiDenyList = AsciiDenyList::new(true, ""); 24 25 extern "C" { 26 #[allow(improper_ctypes)] 27 // char is now actually guaranteed to have the same representation as u32 28 fn mozilla_net_is_label_safe( 29 label: *const char, 30 label_len: usize, 31 tld: *const char, 32 tld_len: usize, 33 ) -> bool; 34 } 35 36 #[no_mangle] 37 pub unsafe extern "C" fn mozilla_net_domain_to_ascii_impl( 38 src: *const nsACString, 39 allow_any_glyphful_ascii: bool, 40 dst: *mut nsACString, 41 ) -> nsresult { 42 debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias"); 43 process(|_, _, _| false, allow_any_glyphful_ascii, &*src, &mut *dst) 44 } 45 46 #[no_mangle] 47 pub unsafe extern "C" fn mozilla_net_domain_to_unicode_impl( 48 src: *const nsACString, 49 allow_any_glyphful_ascii: bool, 50 dst: *mut nsACString, 51 ) -> nsresult { 52 debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias"); 53 process(|_, _, _| true, allow_any_glyphful_ascii, &*src, &mut *dst) 54 } 55 56 #[no_mangle] 57 pub unsafe extern "C" fn mozilla_net_domain_to_display_impl( 58 src: *const nsACString, 59 allow_any_glyphful_ascii: bool, 60 dst: *mut nsACString, 61 ) -> nsresult { 62 debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias"); 63 // XXX do we want to change this not to fail fast? 64 process( 65 |label, tld, _| unsafe { 66 debug_assert!(!label.is_empty()); 67 mozilla_net_is_label_safe( 68 label.as_ptr(), 69 label.len(), 70 if tld.is_empty() { 71 core::ptr::null() 72 } else { 73 tld.as_ptr() 74 }, 75 tld.len(), 76 ) 77 }, 78 allow_any_glyphful_ascii, 79 &*src, 80 &mut *dst, 81 ) 82 } 83 84 #[no_mangle] 85 pub unsafe extern "C" fn mozilla_net_domain_to_display_and_ascii_impl( 86 src: *const nsACString, 87 dst: *mut nsACString, 88 ascii_dst: *mut nsACString, 89 ) -> nsresult { 90 debug_assert_ne!(src, dst as *const nsACString, "src and dst must not alias"); 91 debug_assert_ne!( 92 src, ascii_dst as *const nsACString, 93 "src and ascii_dst must not alias" 94 ); 95 debug_assert_ne!(dst, ascii_dst, "dst and ascii_dst must not alias"); 96 { 97 let src = &*src; 98 let dst: &mut nsACString = &mut *dst; 99 let ascii_dst: &mut nsACString = &mut *ascii_dst; 100 dst.truncate(); 101 ascii_dst.truncate(); 102 #[cfg(feature = "mailnews")] 103 { 104 if src == "Local%20Folders" || src == "smart%20mailboxes" { 105 dst.assign(src); 106 return nserror::NS_OK; 107 } 108 } 109 let unpercent: Cow<'_, [u8]> = percent_decode(src).into(); 110 match Uts46::new().process( 111 &unpercent, 112 GECKO, 113 Hyphens::Allow, 114 ErrorPolicy::FailFast, 115 |label, tld, _| unsafe { 116 debug_assert!(!label.is_empty()); 117 mozilla_net_is_label_safe( 118 label.as_ptr(), 119 label.len(), 120 if tld.is_empty() { 121 core::ptr::null() 122 } else { 123 tld.as_ptr() 124 }, 125 tld.len(), 126 ) 127 }, 128 &mut IdnaWriteWrapper::new(dst), 129 Some(&mut IdnaWriteWrapper::new(ascii_dst)), 130 ) { 131 Ok(ProcessingSuccess::Passthrough) => { 132 // Let the borrow the `IdnaWriteWrapper`s end and fall through. 133 } 134 Ok(ProcessingSuccess::WroteToSink) => return nserror::NS_OK, 135 136 Err(ProcessingError::ValidityError) => return nserror::NS_ERROR_MALFORMED_URI, 137 Err(ProcessingError::SinkError) => unreachable!(), 138 } 139 match unpercent { 140 Cow::Borrowed(_) => dst.assign(src), 141 Cow::Owned(vec) => dst.append(&vec), 142 } 143 nserror::NS_OK 144 } 145 } 146 147 type BufferString = arraystring::ArrayString<arraystring::typenum::U255>; 148 149 /// Buffering type to avoid atomic check of destination 150 /// `nsACString` on a per character basis. 151 struct IdnaWriteWrapper<'a> { 152 sink: &'a mut nsACString, 153 buffer: BufferString, 154 } 155 156 impl<'a> IdnaWriteWrapper<'a> { 157 fn new(sink: &'a mut nsACString) -> IdnaWriteWrapper<'a> { 158 IdnaWriteWrapper { 159 sink, 160 buffer: BufferString::new(), 161 } 162 } 163 } 164 165 impl<'a> Write for IdnaWriteWrapper<'a> { 166 fn write_str(&mut self, s: &str) -> std::fmt::Result { 167 if self.buffer.try_push_str(s).is_ok() { 168 return Ok(()); 169 } 170 if !self.buffer.is_empty() { 171 self.sink.append(self.buffer.as_bytes()); 172 self.buffer.clear(); 173 if self.buffer.try_push_str(s).is_ok() { 174 return Ok(()); 175 } 176 } 177 // Input too long to fit in the buffer. 178 self.sink.append(s.as_bytes()); 179 Ok(()) 180 } 181 } 182 183 impl<'a> Drop for IdnaWriteWrapper<'a> { 184 fn drop(&mut self) { 185 if !self.buffer.is_empty() { 186 self.sink.append(self.buffer.as_bytes()); 187 } 188 } 189 } 190 191 fn process<OutputUnicode: FnMut(&[char], &[char], bool) -> bool>( 192 output_as_unicode: OutputUnicode, 193 allow_any_glyphful_ascii: bool, 194 src: &nsACString, 195 dst: &mut nsACString, 196 ) -> nsresult { 197 dst.truncate(); 198 #[cfg(feature = "mailnews")] 199 { 200 if src == "Local Folders" || src == "local folders" { 201 dst.assign("Local%20Folders"); 202 return nserror::NS_OK; 203 } else if src == "smart mailboxes" { 204 dst.assign("smart%20mailboxes"); 205 return nserror::NS_OK; 206 } 207 } 208 match Uts46::new().process( 209 &src, 210 if allow_any_glyphful_ascii { 211 GLYPHLESS 212 } else { 213 AsciiDenyList::URL 214 }, 215 Hyphens::Allow, 216 ErrorPolicy::FailFast, 217 output_as_unicode, 218 &mut IdnaWriteWrapper::new(dst), 219 None, 220 ) { 221 Ok(ProcessingSuccess::Passthrough) => { 222 // Let the borrow of `dst` inside `IdnaWriteWrapper` end and fall through. 223 } 224 Ok(ProcessingSuccess::WroteToSink) => return nserror::NS_OK, 225 Err(ProcessingError::ValidityError) => return nserror::NS_ERROR_MALFORMED_URI, 226 Err(ProcessingError::SinkError) => unreachable!(), 227 } 228 dst.assign(src); 229 nserror::NS_OK 230 } 231 232 /// Not general-purpose! Only to be used from `nsDocShell::AttemptURIFixup`. 233 #[no_mangle] 234 pub unsafe extern "C" fn mozilla_net_recover_keyword_from_punycode( 235 src: *const nsACString, 236 dst: *mut nsACString, 237 ) { 238 let sink = &mut (*dst); 239 let mut seen_label = false; 240 for label in (*src).split(|b| *b == b'.') { 241 if seen_label { 242 sink.append("."); 243 } 244 seen_label = true; 245 // We know the Punycode prefix is in lower case if we got it from 246 // our own IDNA conversion code. 247 if let Some(punycode) = label.strip_prefix(b"xn--") { 248 // Not bothering to optimize this. 249 // Just unwrap, since we know our IDNA conversion code gives 250 // us ASCII here. 251 let utf8 = std::str::from_utf8(punycode).unwrap(); 252 if let Some(decoded) = idna::punycode::decode_to_string(utf8) { 253 sink.append(&decoded); 254 } else { 255 sink.append(label); 256 } 257 } else { 258 sink.append(label); 259 } 260 } 261 }