1#![allow(clippy::missing_safety_doc)]
11#![allow(clippy::not_unsafe_ptr_arg_deref)]
12
13use crate::abi::allocator;
14use crate::abi::types::*;
15use core::ffi::c_void;
16use core::ptr;
17use std::os::raw::{c_char, c_int};
18
19fn is_unreserved(b: u8) -> bool {
24 b.is_ascii_alphanumeric() || b == b'-' || b == b'.' || b == b'_' || b == b'~'
25}
26
27fn is_reserved(b: u8) -> bool {
30 is_gen_delim(b) || is_sub_delim(b)
31}
32
33fn is_scheme_char(b: u8) -> bool {
36 b.is_ascii_alphanumeric() || b == b'+' || b == b'-' || b == b'.'
37}
38
39fn is_gen_delim(b: u8) -> bool {
41 matches!(b, b':' | b'/' | b'?' | b'#' | b'[' | b']' | b'@')
42}
43
44fn is_sub_delim(b: u8) -> bool {
46 matches!(
47 b,
48 b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
49 )
50}
51
52fn hex_val(c: u8) -> Option<u8> {
56 match c {
57 b'0'..=b'9' => Some(c - b'0'),
58 b'a'..=b'f' => Some(c - b'a' + 10),
59 b'A'..=b'F' => Some(c - b'A' + 10),
60 _ => None,
61 }
62}
63
64fn percent_decode(data: &[u8]) -> Vec<u8> {
67 let mut result = Vec::with_capacity(data.len());
68 let mut i = 0;
69 while i < data.len() {
70 if data[i] == b'%' && i + 2 < data.len() {
71 if let Some(h) = hex_val(data[i + 1]) {
72 if let Some(l) = hex_val(data[i + 2]) {
73 result.push((h << 4) | l);
74 i += 3;
75 continue;
76 }
77 }
78 }
79 result.push(data[i]);
80 i += 1;
81 }
82 result
83}
84
85fn percent_encode(data: &[u8]) -> Vec<u8> {
88 let mut result = Vec::with_capacity(data.len());
89 for &b in data {
90 if is_unreserved(b) || is_reserved(b) || b == b'%' {
91 result.push(b);
92 } else {
93 result.extend_from_slice(format!("%{:02X}", b).as_bytes());
94 }
95 }
96 result
97}
98
99#[derive(Debug, Clone, Default)]
106pub(crate) struct UriParts {
107 pub scheme: Option<Vec<u8>>, pub opaque: Option<Vec<u8>>, pub authority: Option<Vec<u8>>, pub server: Option<Vec<u8>>, pub user: Option<Vec<u8>>, pub host: Option<Vec<u8>>, pub port: c_int, pub path: Option<Vec<u8>>, pub query: Option<Vec<u8>>, pub fragment: Option<Vec<u8>>, pub path_raw: Option<Vec<u8>>, pub clean_path: Option<Vec<u8>>, }
120
121fn find_scheme(uri: &[u8]) -> Option<(usize, usize)> {
127 if uri.is_empty() {
128 return None;
129 }
130 if !uri[0].is_ascii_alphabetic() {
132 return None;
133 }
134 let mut i = 1;
136 while i < uri.len() && is_scheme_char(uri[i]) {
137 i += 1;
138 }
139 if i < uri.len() && uri[i] == b':' {
140 Some((0, i))
142 } else {
143 None
144 }
145}
146
147fn parse_authority(auth: &[u8]) -> (Option<Vec<u8>>, Option<Vec<u8>>, c_int) {
151 let mut user: Option<Vec<u8>> = None;
152 let mut host: Option<Vec<u8>> = None;
153 let mut port: c_int = 0;
154
155 if auth.is_empty() {
156 return (None, None, 0);
157 }
158
159 let (user_part, host_part) = if let Some(at_pos) = auth.iter().position(|&b| b == b'@') {
161 user = Some(auth[..at_pos].to_vec());
162 (&auth[at_pos + 1..], true)
163 } else {
164 (auth, false)
165 };
166
167 if user_part.starts_with(b"[") {
170 if let Some(close_bracket) = user_part.iter().position(|&b| b == b']') {
172 let host_end = close_bracket + 1;
173 host = Some(user_part[..host_end].to_vec());
174 if host_end < user_part.len() && user_part[host_end] == b':' {
176 let port_str = &user_part[host_end + 1..];
177 if !port_str.is_empty() {
178 let port_str_decoded = core::str::from_utf8(port_str).unwrap_or("");
179 port = port_str_decoded.parse::<c_int>().unwrap_or(0);
180 }
181 }
182 } else {
183 host = Some(user_part.to_vec());
185 }
186 } else {
187 if let Some(colon_pos) = user_part.iter().position(|&b| b == b':') {
189 host = Some(user_part[..colon_pos].to_vec());
190 let port_str = &user_part[colon_pos + 1..];
191 if !port_str.is_empty() {
192 let port_str_decoded = core::str::from_utf8(port_str).unwrap_or("");
193 port = port_str_decoded.parse::<c_int>().unwrap_or(0);
194 }
195 } else {
196 host = Some(user_part.to_vec());
197 }
198 }
199
200 (user, host, port)
201}
202
203pub(crate) fn parse_uri(str: &[u8]) -> Option<UriParts> {
210 if str.is_empty() {
211 return None;
212 }
213
214 let mut parts = UriParts::default();
215 let mut remaining = str;
216
217 if let Some((_start, end)) = find_scheme(remaining) {
219 parts.scheme = Some(remaining[..end].to_vec());
220 remaining = &remaining[end + 1..]; if remaining.starts_with(b"//") {
224 remaining = &remaining[2..];
225 let auth_end = remaining
227 .iter()
228 .position(|&b| b == b'/' || b == b'?' || b == b'#')
229 .unwrap_or(remaining.len());
230 let authority = &remaining[..auth_end];
231 parts.authority = if authority.is_empty() {
233 Some(Vec::new())
234 } else {
235 Some(authority.to_vec())
236 };
237 if !authority.is_empty() {
238 let (user, host, port) = parse_authority(authority);
239 parts.user = user;
240 parts.host = host;
241 parts.port = port;
242 if let Some(ref host_val) = parts.host {
243 let mut server = host_val.clone();
245 if port != 0 {
246 server.extend_from_slice(format!(":{}", port).as_bytes());
247 }
248 parts.server = Some(server);
249 }
250 }
251 remaining = &remaining[auth_end..];
252 } else {
253 if let Some(frag_pos) = remaining.iter().position(|&b| b == b'#') {
256 parts.opaque = Some(remaining[..frag_pos].to_vec());
257 parts.fragment = Some(remaining[frag_pos + 1..].to_vec());
258 } else {
259 parts.opaque = Some(remaining.to_vec());
260 }
261 parts.path = parts.opaque.clone();
263 return Some(parts);
264 }
265 }
266
267 let query_pos = remaining.iter().position(|&b| b == b'?');
270 let frag_pos = remaining.iter().position(|&b| b == b'#');
271
272 let path_end = match (query_pos, frag_pos) {
273 (Some(q), Some(f)) => q.min(f),
274 (Some(q), None) => q,
275 (None, Some(f)) => f,
276 (None, None) => remaining.len(),
277 };
278
279 if path_end > 0 {
280 let path = remaining[..path_end].to_vec();
281 parts.path = Some(path.clone());
282 parts.path_raw = Some(path);
283 }
284
285 if let Some(qpos) = query_pos {
287 let qstart = qpos + 1;
288 let qend = frag_pos.unwrap_or(remaining.len());
289 if qstart < qend {
290 parts.query = Some(remaining[qstart..qend].to_vec());
291 }
292 }
293
294 if let Some(fpos) = frag_pos {
296 let fstart = fpos + 1;
297 if fstart < remaining.len() {
298 parts.fragment = Some(remaining[fstart..].to_vec());
299 }
300 }
301
302 Some(parts)
303}
304
305pub(crate) fn parse_uri_cstr(str: *const xmlChar) -> *mut UriParts {
310 if str.is_null() {
311 return ptr::null_mut();
312 }
313
314 let len = unsafe { libc::strlen(str as *const libc::c_char) };
315 let slice = unsafe { core::slice::from_raw_parts(str, len) };
316
317 match parse_uri(slice) {
318 Some(parts) => {
319 let boxed = Box::new(parts);
320 Box::into_raw(boxed)
321 }
322 None => ptr::null_mut(),
323 }
324}
325
326pub(crate) unsafe fn free_uri_parts(parts: *mut UriParts) {
332 if !parts.is_null() {
333 drop(Box::from_raw(parts));
334 }
335}
336
337pub(crate) fn build_uri(parts: &UriParts) -> Vec<u8> {
341 let mut result = Vec::new();
342
343 if let Some(ref scheme) = parts.scheme {
345 result.extend_from_slice(scheme);
346 result.push(b':');
347 }
348
349 if let Some(ref authority) = parts.authority {
351 result.extend_from_slice(b"//");
352 result.extend_from_slice(authority);
353 } else if parts.host.is_some() {
354 result.extend_from_slice(b"//");
356 if let Some(ref user) = parts.user {
357 result.extend_from_slice(user);
358 result.push(b'@');
359 }
360 if let Some(ref host) = parts.host {
361 result.extend_from_slice(host);
362 }
363 if parts.port != 0 {
364 result.push(b':');
365 result.extend_from_slice(format!("{}", parts.port).as_bytes());
366 }
367 }
368
369 if let Some(ref path) = parts.path {
371 result.extend_from_slice(path);
372 } else if let Some(ref opaque) = parts.opaque {
373 result.extend_from_slice(opaque);
374 }
375
376 if let Some(ref query) = parts.query {
378 result.push(b'?');
379 result.extend_from_slice(query);
380 }
381
382 if let Some(ref fragment) = parts.fragment {
384 result.push(b'#');
385 result.extend_from_slice(fragment);
386 }
387
388 result
389}
390
391pub(crate) fn normalize_uri_path(uri: &[u8]) -> Vec<u8> {
396 if uri.is_empty() {
397 return Vec::new();
398 }
399
400 let absolute = uri.starts_with(b"/");
401 let ends_with_slash = uri.ends_with(b"/");
402
403 let parts: Vec<&[u8]> = uri.split(|&b| b == b'/').collect();
404 let mut segments: Vec<&[u8]> = Vec::new();
405
406 for segment in parts {
407 if segment == b"." || segment.is_empty() {
408 continue;
410 }
411 if segment == b".." {
412 segments.pop();
414 } else {
415 segments.push(segment);
416 }
417 }
418
419 let mut result = Vec::new();
420 if absolute {
421 result.push(b'/');
422 }
423 for (i, seg) in segments.iter().enumerate() {
424 if i > 0 {
425 result.push(b'/');
426 }
427 result.extend_from_slice(seg);
428 }
429
430 if ends_with_slash && !segments.is_empty() {
432 result.push(b'/');
433 }
434
435 if result.is_empty() && absolute {
437 result.push(b'/');
438 }
439
440 result
441}
442
443pub(crate) fn get_scheme(uri: &[u8]) -> Option<Vec<u8>> {
445 if let Some((_start, end)) = find_scheme(uri) {
446 Some(uri[_start..end].to_vec())
447 } else {
448 None
449 }
450}
451
452pub(crate) fn is_absolute(uri: &[u8]) -> bool {
454 find_scheme(uri).is_some()
455}
456
457pub(crate) fn resolve_uri(base: &[u8], relative: &[u8]) -> Option<Vec<u8>> {
464 if base.is_empty() {
465 return if relative.is_empty() {
466 None
467 } else {
468 Some(relative.to_vec())
469 };
470 }
471
472 if is_absolute(relative) {
474 return Some(relative.to_vec());
475 }
476
477 let base_parts = parse_uri(base)?;
479
480 if relative.is_empty() {
482 return Some(build_uri(&base_parts));
483 }
484
485 let rel_str = relative;
487
488 let mut result = UriParts {
489 scheme: base_parts.scheme.clone(),
490 ..Default::default()
491 };
492
493 if rel_str.starts_with(b"//") {
494 let rest = &rel_str[2..];
497 let auth_end = rest.iter().position(|&b| b == b'/').unwrap_or(rest.len());
498 let auth = rest[..auth_end].to_vec();
499 let (user, host, port) = parse_authority(&auth);
500 result.authority = Some(auth);
501 result.user = user;
502 result.host = host;
503 result.port = port;
504
505 let path_rest = if auth_end < rest.len() {
506 &rest[auth_end..]
507 } else {
508 b""
509 };
510 parse_path_query_fragment(path_rest, &mut result);
512 } else if rel_str.starts_with(b"/") {
513 parse_path_query_fragment(rel_str, &mut result);
515 result.authority = base_parts.authority.clone();
517 result.user = base_parts.user.clone();
518 result.host = base_parts.host.clone();
519 result.port = base_parts.port;
520 } else {
521 let base_path = base_parts.path.as_deref().unwrap_or(b"");
524 let base_dir = if let Some(last_slash) = base_path.iter().rposition(|&b| b == b'/') {
525 &base_path[..=last_slash]
526 } else {
527 b""
528 };
529
530 let mut combined = Vec::from(base_dir);
532 combined.extend_from_slice(rel_str);
533 parse_path_query_fragment(&combined, &mut result);
534
535 result.authority = base_parts.authority.clone();
537 result.user = base_parts.user.clone();
538 result.host = base_parts.host.clone();
539 result.port = base_parts.port;
540 }
541
542 if let Some(ref path) = result.path {
544 let normalized = normalize_uri_path(path);
545 result.path = Some(normalized);
546 }
547
548 Some(build_uri(&result))
549}
550
551fn parse_path_query_fragment(input: &[u8], parts: &mut UriParts) {
553 let query_pos = input.iter().position(|&b| b == b'?');
555 let frag_pos = input.iter().position(|&b| b == b'#');
556
557 let path_end = match (query_pos, frag_pos) {
558 (Some(q), Some(f)) => q.min(f),
559 (Some(q), None) => q,
560 (None, Some(f)) => f,
561 (None, None) => input.len(),
562 };
563
564 if path_end > 0 {
565 parts.path = Some(input[..path_end].to_vec());
566 parts.path_raw = parts.path.clone();
567 }
568
569 if let Some(qpos) = query_pos {
571 let qstart = qpos + 1;
572 let qend = frag_pos.unwrap_or(input.len());
573 if qstart < qend {
574 parts.query = Some(input[qstart..qend].to_vec());
575 }
576 }
577
578 if let Some(fpos) = frag_pos {
580 let fstart = fpos + 1;
581 if fstart < input.len() {
582 parts.fragment = Some(input[fstart..].to_vec());
583 }
584 }
585}
586
587pub(crate) unsafe fn xmlParseURI(str: *const c_char) -> *mut c_void {
600 if str.is_null() {
601 return ptr::null_mut();
602 }
603 let len = libc::strlen(str);
604 let slice = unsafe { core::slice::from_raw_parts(str as *const u8, len) };
605 match parse_uri(slice) {
606 Some(parts) => {
607 let boxed = Box::new(parts);
608 Box::into_raw(boxed) as *mut c_void
609 }
610 None => ptr::null_mut(),
611 }
612}
613
614pub(crate) unsafe fn xmlFreeURI(uri: *mut c_void) {
622 if !uri.is_null() {
623 drop(Box::from_raw(uri as *mut UriParts));
624 }
625}
626
627pub(crate) fn xmlCreateURI() -> *mut c_void {
634 let parts = UriParts::default();
635 let boxed = Box::new(parts);
636 Box::into_raw(boxed) as *mut c_void
637}
638
639pub(crate) unsafe fn xmlSaveUri(uri: *mut c_void) -> *mut xmlChar {
652 if uri.is_null() {
653 return ptr::null_mut();
654 }
655 let parts = unsafe { &*(uri as *const UriParts) };
656 let result = build_uri(parts);
657 if result.is_empty() {
658 return ptr::null_mut();
659 }
660 let len = result.len();
662 let ptr = unsafe { allocator::xmlMalloc(len + 1) as *mut u8 };
663 if ptr.is_null() {
664 return ptr::null_mut();
665 }
666 unsafe {
667 ptr::copy_nonoverlapping(result.as_ptr(), ptr, len);
668 *ptr.add(len) = 0; }
670 ptr as *mut xmlChar
671}
672
673pub(crate) unsafe fn xmlURIEscapeStr(str: *const xmlChar, list: *const xmlChar) -> *mut xmlChar {
685 if str.is_null() {
686 return ptr::null_mut();
687 }
688 let str_len = unsafe { libc::strlen(str as *const libc::c_char) };
689 let str_slice = unsafe { core::slice::from_raw_parts(str, str_len) };
690
691 let mut safe_set = [false; 256];
693 for b in 0u8..=255 {
694 if is_unreserved(b) || b == b'%' {
695 safe_set[b as usize] = true;
696 }
697 }
698 if !list.is_null() {
699 let list_len = unsafe { libc::strlen(list as *const libc::c_char) };
700 let list_slice = unsafe { core::slice::from_raw_parts(list, list_len) };
701 for &b in list_slice {
702 safe_set[b as usize] = true;
703 }
704 }
705
706 let mut result = Vec::with_capacity(str_slice.len() * 3);
708 for &b in str_slice {
709 if safe_set[b as usize] {
710 result.push(b);
711 } else {
712 result.extend_from_slice(format!("%{:02X}", b).as_bytes());
713 }
714 }
715
716 let len = result.len();
717 let ptr = unsafe { allocator::xmlMalloc(len + 1) as *mut u8 };
718 if ptr.is_null() {
719 return ptr::null_mut();
720 }
721 unsafe {
722 ptr::copy_nonoverlapping(result.as_ptr(), ptr, len);
723 *ptr.add(len) = 0;
724 }
725 ptr as *mut xmlChar
726}
727
728pub(crate) unsafe fn xmlURIUnescapeString(
743 str: *const c_char,
744 len: c_int,
745 target: *mut c_char,
746) -> *mut c_char {
747 if str.is_null() {
748 return ptr::null_mut();
749 }
750 let slice = if len < 0 {
751 let cstr_len = unsafe { libc::strlen(str) };
752 unsafe { core::slice::from_raw_parts(str as *const u8, cstr_len) }
753 } else {
754 unsafe { core::slice::from_raw_parts(str as *const u8, len as usize) }
755 };
756
757 let decoded = percent_decode(slice);
758
759 if !target.is_null() {
760 unsafe {
761 ptr::copy_nonoverlapping(decoded.as_ptr(), target as *mut u8, decoded.len());
762 *((target as *mut u8).add(decoded.len())) = 0;
763 }
764 return target;
765 }
766
767 let out_len = decoded.len();
768 let ptr = unsafe { allocator::xmlMalloc(out_len + 1) as *mut u8 };
769 if ptr.is_null() {
770 return ptr::null_mut();
771 }
772 unsafe {
773 ptr::copy_nonoverlapping(decoded.as_ptr(), ptr, out_len);
774 *ptr.add(out_len) = 0;
775 }
776 ptr as *mut c_char
777}
778
779pub(crate) unsafe fn xmlParseURIRaw(str: *const c_char, _raw: c_int) -> *mut c_void {
792 unsafe { xmlParseURI(str) }
793}
794
795#[cfg(test)]
798mod tests {
799 use super::*;
800
801 #[test]
804 fn test_is_unreserved() {
805 assert!(is_unreserved(b'a'));
806 assert!(is_unreserved(b'Z'));
807 assert!(is_unreserved(b'0'));
808 assert!(is_unreserved(b'-'));
809 assert!(is_unreserved(b'.'));
810 assert!(is_unreserved(b'_'));
811 assert!(is_unreserved(b'~'));
812 assert!(!is_unreserved(b':'));
813 assert!(!is_unreserved(b'/'));
814 assert!(!is_unreserved(b'%'));
815 assert!(!is_unreserved(b' '));
816 }
817
818 #[test]
819 fn test_is_reserved() {
820 assert!(is_reserved(b':'));
821 assert!(is_reserved(b'/'));
822 assert!(is_reserved(b'?'));
823 assert!(is_reserved(b'#'));
824 assert!(is_reserved(b'@'));
825 assert!(is_reserved(b'!'));
826 assert!(is_reserved(b'$'));
827 assert!(is_reserved(b'&'));
828 assert!(is_reserved(b'('));
829 assert!(is_reserved(b')'));
830 assert!(!is_reserved(b'a'));
831 assert!(!is_reserved(b' '));
832 }
833
834 #[test]
835 fn test_is_scheme_char() {
836 assert!(is_scheme_char(b'a'));
837 assert!(is_scheme_char(b'Z'));
838 assert!(is_scheme_char(b'0'));
839 assert!(is_scheme_char(b'+'));
840 assert!(is_scheme_char(b'-'));
841 assert!(is_scheme_char(b'.'));
842 assert!(!is_scheme_char(b':'));
843 assert!(!is_scheme_char(b'/'));
844 assert!(!is_scheme_char(b' '));
845 }
846
847 #[test]
850 fn test_percent_decode_simple() {
851 assert_eq!(percent_decode(b"hello"), b"hello");
852 assert_eq!(percent_decode(b"%68%65%6C%6C%6F"), b"hello");
853 assert_eq!(percent_decode(b"%48%65%6C%6C%6F"), b"Hello");
854 assert_eq!(percent_decode(b"a%20b"), b"a b");
855 }
856
857 #[test]
858 fn test_percent_decode_invalid() {
859 assert_eq!(percent_decode(b"%XX"), b"%XX");
861 assert_eq!(percent_decode(b"%2"), b"%2");
862 assert_eq!(percent_decode(b"%"), b"%");
863 assert_eq!(percent_decode(b"%%20"), b"% ");
864 }
865
866 #[test]
867 fn test_percent_decode_empty() {
868 assert_eq!(percent_decode(b""), b"");
869 }
870
871 #[test]
872 fn test_percent_encode() {
873 assert_eq!(percent_encode(b"hello"), b"hello");
874 assert_eq!(percent_encode(b"hello world"), b"hello%20world");
875 assert_eq!(percent_encode(b"a/b"), b"a/b"); }
877
878 #[test]
881 fn test_parse_http_uri() {
882 let parts =
883 parse_uri(b"http://example.com/path/to/file.xml?query=1#frag").expect("should parse");
884 assert_eq!(parts.scheme, Some(b"http".to_vec()));
885 assert_eq!(parts.authority, Some(b"example.com".to_vec()));
886 assert_eq!(parts.host, Some(b"example.com".to_vec()));
887 assert_eq!(parts.port, 0);
888 assert_eq!(parts.path, Some(b"/path/to/file.xml".to_vec()));
889 assert_eq!(parts.query, Some(b"query=1".to_vec()));
890 assert_eq!(parts.fragment, Some(b"frag".to_vec()));
891 }
892
893 #[test]
894 fn test_parse_https_uri() {
895 let parts = parse_uri(b"https://example.com:443/path").expect("should parse");
896 assert_eq!(parts.scheme, Some(b"https".to_vec()));
897 assert_eq!(parts.host, Some(b"example.com".to_vec()));
898 assert_eq!(parts.port, 443);
899 assert_eq!(parts.path, Some(b"/path".to_vec()));
900 }
901
902 #[test]
903 fn test_parse_file_uri() {
904 let parts = parse_uri(b"file:///etc/hosts").expect("should parse");
905 assert_eq!(parts.scheme, Some(b"file".to_vec()));
906 assert!(parts.authority.is_none() || parts.authority.as_deref() == Some(b""));
907 assert_eq!(parts.path, Some(b"/etc/hosts".to_vec()));
908 }
909
910 #[test]
911 fn test_parse_file_uri_with_host() {
912 let parts = parse_uri(b"file://localhost/etc/hosts").expect("should parse");
914 assert_eq!(parts.scheme, Some(b"file".to_vec()));
915 assert_eq!(parts.host, Some(b"localhost".to_vec()));
916 assert_eq!(parts.path, Some(b"/etc/hosts".to_vec()));
917 }
918
919 #[test]
920 fn test_parse_relative_uri() {
921 let parts = parse_uri(b"/path/to/file.xml").expect("should parse");
922 assert!(parts.scheme.is_none());
923 assert_eq!(parts.path, Some(b"/path/to/file.xml".to_vec()));
924 }
925
926 #[test]
927 fn test_parse_relative_uri_with_query() {
928 let parts = parse_uri(b"file.xml?query=1").expect("should parse");
929 assert!(parts.scheme.is_none());
930 assert_eq!(parts.path, Some(b"file.xml".to_vec()));
931 assert_eq!(parts.query, Some(b"query=1".to_vec()));
932 }
933
934 #[test]
935 fn test_parse_uri_with_user_info() {
936 let parts = parse_uri(b"ftp://user@host.com:21/path").expect("should parse");
937 assert_eq!(parts.scheme, Some(b"ftp".to_vec()));
938 assert_eq!(parts.user, Some(b"user".to_vec()));
939 assert_eq!(parts.host, Some(b"host.com".to_vec()));
940 assert_eq!(parts.port, 21);
941 assert_eq!(parts.path, Some(b"/path".to_vec()));
942 }
943
944 #[test]
945 fn test_parse_uri_with_user_password() {
946 let parts = parse_uri(b"ftp://user:pass@host.com/path").expect("should parse");
947 assert_eq!(parts.scheme, Some(b"ftp".to_vec()));
948 assert_eq!(parts.user, Some(b"user:pass".to_vec()));
949 assert_eq!(parts.host, Some(b"host.com".to_vec()));
950 assert_eq!(parts.path, Some(b"/path".to_vec()));
951 }
952
953 #[test]
954 fn test_parse_opaque_uri() {
955 let parts = parse_uri(b"mailto:user@example.com").expect("should parse");
956 assert_eq!(parts.scheme, Some(b"mailto".to_vec()));
957 assert_eq!(parts.opaque, Some(b"user@example.com".to_vec()));
958 assert!(parts.authority.is_none());
959 }
960
961 #[test]
962 fn test_parse_opaque_uri_with_fragment() {
963 let parts = parse_uri(b"urn:isbn:0-395-36341-1#frag").expect("should parse");
964 assert_eq!(parts.scheme, Some(b"urn".to_vec()));
965 assert_eq!(parts.opaque, Some(b"isbn:0-395-36341-1".to_vec()));
966 assert_eq!(parts.fragment, Some(b"frag".to_vec()));
967 }
968
969 #[test]
970 fn test_parse_empty_uri() {
971 assert!(parse_uri(b"").is_none());
972 }
973
974 #[test]
975 fn test_parse_uri_fragment_only() {
976 let parts = parse_uri(b"#fragment").expect("should parse");
977 assert!(parts.scheme.is_none());
978 assert!(parts.path.is_none());
979 assert_eq!(parts.fragment, Some(b"fragment".to_vec()));
980 }
981
982 #[test]
983 fn test_parse_uri_query_only() {
984 let parts = parse_uri(b"?query").expect("should parse");
985 assert!(parts.scheme.is_none());
986 assert!(parts.path.is_none());
987 assert_eq!(parts.query, Some(b"query".to_vec()));
988 }
989
990 #[test]
991 fn test_parse_uri_with_ipv6_host() {
992 let parts = parse_uri(b"http://[::1]:8080/path").expect("should parse");
993 assert_eq!(parts.scheme, Some(b"http".to_vec()));
994 assert_eq!(parts.host, Some(b"[::1]".to_vec()));
995 assert_eq!(parts.port, 8080);
996 assert_eq!(parts.path, Some(b"/path".to_vec()));
997 }
998
999 #[test]
1000 fn test_parse_uri_no_path() {
1001 let parts = parse_uri(b"http://example.com").expect("should parse");
1002 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1003 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1004 assert!(parts.path.is_none());
1005 }
1006
1007 #[test]
1008 fn test_parse_uri_no_path_with_query() {
1009 let parts = parse_uri(b"http://example.com?query").expect("should parse");
1010 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1011 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1012 assert!(parts.path.is_none());
1013 assert_eq!(parts.query, Some(b"query".to_vec()));
1014 }
1015
1016 #[test]
1019 fn test_build_uri() {
1020 let parts = UriParts {
1021 scheme: Some(b"http".to_vec()),
1022 host: Some(b"example.com".to_vec()),
1023 port: 8080,
1024 path: Some(b"/path".to_vec()),
1025 query: Some(b"q=1".to_vec()),
1026 fragment: Some(b"frag".to_vec()),
1027 ..Default::default()
1028 };
1029 assert_eq!(build_uri(&parts), b"http://example.com:8080/path?q=1#frag");
1030 }
1031
1032 #[test]
1033 fn test_build_uri_simple() {
1034 let parts = UriParts {
1035 scheme: Some(b"http".to_vec()),
1036 host: Some(b"example.com".to_vec()),
1037 path: Some(b"/".to_vec()),
1038 ..Default::default()
1039 };
1040 assert_eq!(build_uri(&parts), b"http://example.com/");
1041 }
1042
1043 #[test]
1044 fn test_build_uri_opaque() {
1045 let parts = UriParts {
1046 scheme: Some(b"mailto".to_vec()),
1047 opaque: Some(b"user@example.com".to_vec()),
1048 ..Default::default()
1049 };
1050 assert_eq!(build_uri(&parts), b"mailto:user@example.com");
1051 }
1052
1053 #[test]
1054 fn test_build_uri_relative() {
1055 let parts = UriParts {
1056 path: Some(b"/relative/path".to_vec()),
1057 ..Default::default()
1058 };
1059 assert_eq!(build_uri(&parts), b"/relative/path");
1060 }
1061
1062 #[test]
1065 fn test_normalize_uri_path_simple() {
1066 assert_eq!(normalize_uri_path(b"/foo/bar"), b"/foo/bar");
1067 assert_eq!(normalize_uri_path(b"/foo/./bar"), b"/foo/bar");
1068 assert_eq!(normalize_uri_path(b"/foo/../bar"), b"/bar");
1069 assert_eq!(normalize_uri_path(b"/foo/bar/.."), b"/foo");
1070 assert_eq!(normalize_uri_path(b"/"), b"/");
1071 }
1072
1073 #[test]
1074 fn test_normalize_uri_path_relative() {
1075 assert_eq!(normalize_uri_path(b"foo/bar"), b"foo/bar");
1076 assert_eq!(normalize_uri_path(b"foo/./bar"), b"foo/bar");
1077 assert_eq!(normalize_uri_path(b"foo/../bar"), b"bar");
1078 }
1079
1080 #[test]
1081 fn test_normalize_uri_path_double_dot_overflow() {
1082 assert_eq!(normalize_uri_path(b"/a/../../b"), b"/b");
1084 assert_eq!(normalize_uri_path(b"/../b"), b"/b");
1085 }
1086
1087 #[test]
1088 fn test_normalize_uri_path_empty() {
1089 assert_eq!(normalize_uri_path(b""), b"");
1090 }
1091
1092 #[test]
1093 fn test_normalize_uri_path_dots_only() {
1094 assert_eq!(normalize_uri_path(b"./././."), b"");
1095 assert_eq!(normalize_uri_path(b"/./././"), b"/");
1096 }
1097
1098 #[test]
1101 fn test_get_scheme() {
1102 assert_eq!(get_scheme(b"http://example.com"), Some(b"http".to_vec()));
1103 assert_eq!(get_scheme(b"https://example.com"), Some(b"https".to_vec()));
1104 assert_eq!(get_scheme(b"file:///path"), Some(b"file".to_vec()));
1105 assert_eq!(get_scheme(b"ftp://host"), Some(b"ftp".to_vec()));
1106 assert_eq!(get_scheme(b"mailto:user@host"), Some(b"mailto".to_vec()));
1107 assert_eq!(get_scheme(b"urn:isbn:1234"), Some(b"urn".to_vec()));
1108 assert_eq!(get_scheme(b"/path"), None);
1109 assert_eq!(get_scheme(b"relative"), None);
1110 assert_eq!(get_scheme(b""), None);
1111 }
1112
1113 #[test]
1114 fn test_is_absolute() {
1115 assert!(is_absolute(b"http://example.com"));
1116 assert!(is_absolute(b"file:///path"));
1117 assert!(is_absolute(b"mailto:user@host"));
1118 assert!(!is_absolute(b"/path"));
1119 assert!(!is_absolute(b"relative"));
1120 assert!(!is_absolute(b""));
1121 }
1122
1123 #[test]
1126 fn test_resolve_uri_absolute_relative() {
1127 let result =
1128 resolve_uri(b"http://example.com/base/", b"relative.xml").expect("should resolve");
1129 assert_eq!(result, b"http://example.com/base/relative.xml");
1130 }
1131
1132 #[test]
1133 fn test_resolve_uri_absolute_absolute() {
1134 let result = resolve_uri(
1135 b"http://example.com/base/",
1136 b"http://other.com/absolute.xml",
1137 )
1138 .expect("should resolve");
1139 assert_eq!(result, b"http://other.com/absolute.xml");
1140 }
1141
1142 #[test]
1143 fn test_resolve_uri_root_relative() {
1144 let result =
1145 resolve_uri(b"http://example.com/base/file.xml", b"/root.xml").expect("should resolve");
1146 assert_eq!(result, b"http://example.com/root.xml");
1147 }
1148
1149 #[test]
1150 fn test_resolve_uri_network_path() {
1151 let result = resolve_uri(b"http://example.com/base/file.xml", b"//other.com/root.xml")
1152 .expect("should resolve");
1153 assert_eq!(result, b"http://other.com/root.xml");
1154 }
1155
1156 #[test]
1157 fn test_resolve_uri_parent_traversal() {
1158 let result = resolve_uri(b"http://example.com/a/b/c/file.xml", b"../../d/file.xml")
1159 .expect("should resolve");
1160 assert_eq!(result, b"http://example.com/a/d/file.xml");
1161 }
1162
1163 #[test]
1164 fn test_resolve_uri_with_query() {
1165 let result =
1166 resolve_uri(b"http://example.com/base/", b"file.xml?query=1").expect("should resolve");
1167 assert_eq!(result, b"http://example.com/base/file.xml?query=1");
1168 }
1169
1170 #[test]
1171 fn test_resolve_uri_with_fragment() {
1172 let result =
1173 resolve_uri(b"http://example.com/base/file.xml", b"#frag").expect("should resolve");
1174 assert_eq!(result, b"http://example.com/base/#frag");
1177 }
1178
1179 #[test]
1180 fn test_resolve_uri_empty_base() {
1181 let result = resolve_uri(b"", b"relative.xml");
1182 assert_eq!(result, Some(b"relative.xml".to_vec()));
1183 }
1184
1185 #[test]
1186 fn test_resolve_uri_empty_relative() {
1187 let result = resolve_uri(b"http://example.com/base/", b"");
1188 assert!(result.is_some());
1189 assert_eq!(result.unwrap(), b"http://example.com/base/");
1191 }
1192
1193 #[test]
1194 fn test_resolve_uri_both_empty() {
1195 assert!(resolve_uri(b"", b"").is_none());
1196 }
1197
1198 #[test]
1199 fn test_resolve_uri_file_scheme() {
1200 let result = resolve_uri(b"file:///base/dir/", b"file.xml").expect("should resolve");
1201 assert_eq!(result, b"file:///base/dir/file.xml");
1202 }
1203
1204 #[test]
1205 fn test_resolve_uri_deep_relative() {
1206 let result = resolve_uri(
1207 b"http://example.com/a/b/c/d/e/file.xml",
1208 b"../../../../x/y/z/file.xml",
1209 )
1210 .expect("should resolve");
1211 assert_eq!(result, b"http://example.com/a/x/y/z/file.xml");
1212 }
1213
1214 #[test]
1217 fn test_xml_create_and_free_uri() {
1218 unsafe {
1219 let uri = xmlCreateURI();
1220 assert!(!uri.is_null());
1221 xmlFreeURI(uri);
1222 }
1223 }
1224
1225 #[test]
1226 fn test_xml_parse_uri() {
1227 unsafe {
1228 let cstr = b"http://example.com/path\0".as_ptr() as *const c_char;
1229 let uri = xmlParseURI(cstr);
1230 assert!(!uri.is_null());
1231 let parts = &*(uri as *const UriParts);
1232 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1233 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1234 xmlFreeURI(uri);
1235 }
1236 }
1237
1238 #[test]
1239 fn test_xml_parse_uri_null() {
1240 unsafe {
1241 let uri = xmlParseURI(ptr::null());
1242 assert!(uri.is_null());
1243 }
1244 }
1245
1246 #[test]
1247 fn test_xml_save_uri() {
1248 unsafe {
1249 let cstr = b"http://example.com:8080/path?q=1#f\0".as_ptr() as *const c_char;
1250 let uri = xmlParseURI(cstr);
1251 assert!(!uri.is_null());
1252 let saved = xmlSaveUri(uri);
1253 assert!(!saved.is_null());
1254 let saved_str = std::ffi::CStr::from_ptr(saved as *const c_char);
1255 assert_eq!(saved_str.to_bytes(), b"http://example.com:8080/path?q=1#f");
1256 allocator::xmlFree(saved as *mut core::ffi::c_void);
1257 xmlFreeURI(uri);
1258 }
1259 }
1260
1261 #[test]
1262 fn test_xml_escape_str() {
1263 unsafe {
1264 let cstr = b"hello world\0".as_ptr() as *const xmlChar;
1265 let result = xmlURIEscapeStr(cstr, ptr::null());
1266 assert!(!result.is_null());
1267 let result_str = std::ffi::CStr::from_ptr(result as *const c_char);
1268 assert_eq!(result_str.to_bytes(), b"hello%20world");
1269 allocator::xmlFree(result as *mut core::ffi::c_void);
1270 }
1271 }
1272
1273 #[test]
1274 fn test_xml_escape_str_with_safe_list() {
1275 unsafe {
1276 let cstr = b"hello world\0".as_ptr() as *const xmlChar;
1277 let safe = b" \0".as_ptr() as *const xmlChar;
1278 let result = xmlURIEscapeStr(cstr, safe);
1279 assert!(!result.is_null());
1280 let result_str = std::ffi::CStr::from_ptr(result as *const c_char);
1281 assert_eq!(result_str.to_bytes(), b"hello world"); allocator::xmlFree(result as *mut core::ffi::c_void);
1283 }
1284 }
1285
1286 #[test]
1287 fn test_xml_unescape_string() {
1288 unsafe {
1289 let cstr = b"hello%20world\0".as_ptr() as *const c_char;
1290 let result = xmlURIUnescapeString(cstr, -1, ptr::null_mut());
1291 assert!(!result.is_null());
1292 let result_str = std::ffi::CStr::from_ptr(result);
1293 assert_eq!(result_str.to_bytes(), b"hello world");
1294 allocator::xmlFree(result as *mut core::ffi::c_void);
1295 }
1296 }
1297
1298 #[test]
1299 fn test_xml_unescape_string_with_len() {
1300 unsafe {
1301 let cstr = b"hello%20world\0".as_ptr() as *const c_char;
1302 let result = xmlURIUnescapeString(cstr, 13, ptr::null_mut());
1303 assert!(!result.is_null());
1304 let result_str = std::ffi::CStr::from_ptr(result);
1305 assert_eq!(result_str.to_bytes(), b"hello world");
1306 allocator::xmlFree(result as *mut core::ffi::c_void);
1307 }
1308 }
1309
1310 #[test]
1311 fn test_xml_parse_uri_raw() {
1312 unsafe {
1313 let cstr = b"http://example.com\0".as_ptr() as *const c_char;
1314 let uri = xmlParseURIRaw(cstr, 0);
1315 assert!(!uri.is_null());
1316 let parts = &*(uri as *const UriParts);
1317 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1318 xmlFreeURI(uri);
1319 }
1320 }
1321
1322 #[test]
1323 fn test_xml_free_null() {
1324 unsafe {
1325 xmlFreeURI(ptr::null_mut());
1327 }
1328 }
1329
1330 #[test]
1333 fn test_parse_uri_scheme_only() {
1334 let parts = parse_uri(b"http:").expect("should parse");
1335 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1336 assert!(parts.opaque.is_none() || parts.opaque.as_deref() == Some(b""));
1337 }
1338
1339 #[test]
1340 fn test_parse_uri_with_trailing_slash() {
1341 let parts = parse_uri(b"http://example.com/").expect("should parse");
1342 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1343 assert_eq!(parts.path, Some(b"/".to_vec()));
1344 }
1345
1346 #[test]
1347 fn test_parse_uri_with_double_slash_path() {
1348 let parts = parse_uri(b"http://example.com//path").expect("should parse");
1349 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1350 assert_eq!(parts.path, Some(b"//path".to_vec()));
1351 }
1352
1353 #[test]
1354 fn test_parse_uri_no_scheme_colon() {
1355 let parts = parse_uri(b"123:path");
1357 assert!(parts.is_some());
1359 let p = parts.unwrap();
1360 assert!(p.scheme.is_none());
1361 assert_eq!(p.path, Some(b"123:path".to_vec()));
1362 }
1363
1364 #[test]
1365 fn test_parse_uri_ftp_with_home_dir() {
1366 let parts = parse_uri(b"ftp://host/home/user/file.txt").expect("should parse");
1367 assert_eq!(parts.scheme, Some(b"ftp".to_vec()));
1368 assert_eq!(parts.host, Some(b"host".to_vec()));
1369 assert_eq!(parts.path, Some(b"/home/user/file.txt".to_vec()));
1370 }
1371
1372 #[test]
1373 fn test_parse_uri_scheme_case() {
1374 let parts = parse_uri(b"HTTP://example.com/Path").expect("should parse");
1375 assert_eq!(parts.scheme, Some(b"HTTP".to_vec()));
1376 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1377 assert_eq!(parts.path, Some(b"/Path".to_vec()));
1378 }
1379
1380 #[test]
1381 fn test_normalize_path_complex() {
1382 assert_eq!(normalize_uri_path(b"/a/b/c/./../../g"), b"/a/g");
1383 assert_eq!(normalize_uri_path(b"mid/content=5/../6"), b"mid/6");
1384 }
1385
1386 #[test]
1387 fn test_resolve_uri_same_directory() {
1388 let result =
1389 resolve_uri(b"http://example.com/a/b/c.html", b"d.html").expect("should resolve");
1390 assert_eq!(result, b"http://example.com/a/b/d.html");
1391 }
1392
1393 #[test]
1394 fn test_resolve_uri_complex_traversal() {
1395 let result = resolve_uri(b"http://a/b/c/d;p?q", b"g/h/../i/./j#f").expect("should resolve");
1396 let result_str = core::str::from_utf8(&result).unwrap_or("");
1397 assert!(result_str.contains("http://a/b/c/g/i/j"));
1398 }
1399
1400 #[test]
1403 fn test_hex_val() {
1404 assert_eq!(hex_val(b'0'), Some(0));
1405 assert_eq!(hex_val(b'9'), Some(9));
1406 assert_eq!(hex_val(b'a'), Some(10));
1407 assert_eq!(hex_val(b'f'), Some(15));
1408 assert_eq!(hex_val(b'A'), Some(10));
1409 assert_eq!(hex_val(b'F'), Some(15));
1410 assert_eq!(hex_val(b'g'), None);
1411 assert_eq!(hex_val(b'z'), None);
1412 assert_eq!(hex_val(b'%'), None);
1413 }
1414
1415 #[test]
1418 fn test_parse_uri_cstr() {
1419 let cstr = b"http://example.com/path\0".as_ptr() as *const xmlChar;
1420 let ptr = parse_uri_cstr(cstr);
1421 assert!(!ptr.is_null());
1422 unsafe {
1423 assert_eq!((*ptr).scheme, Some(b"http".to_vec()));
1424 assert_eq!((*ptr).host, Some(b"example.com".to_vec()));
1425 free_uri_parts(ptr);
1426 }
1427 }
1428
1429 #[test]
1430 fn test_parse_uri_cstr_null() {
1431 let ptr = parse_uri_cstr(ptr::null());
1432 assert!(ptr.is_null());
1433 }
1434
1435 #[test]
1436 fn test_parse_uri_cstr_invalid() {
1437 let cstr = b"\0".as_ptr() as *const xmlChar;
1438 let ptr = parse_uri_cstr(cstr);
1439 assert!(ptr.is_null());
1440 }
1441}