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
121#[repr(C)]
147struct CXmlUri {
148 scheme: *mut c_char,
149 opaque: *mut c_char,
150 authority: *mut c_char,
151 server: *mut c_char,
152 user: *mut c_char,
153 port: c_int,
154 path: *mut c_char,
155 query: *mut c_char,
156 fragment: *mut c_char,
157 cleanup: c_int,
158 query_raw: *mut c_char,
159}
160
161impl Default for CXmlUri {
162 fn default() -> Self {
163 CXmlUri {
164 scheme: ptr::null_mut(),
165 opaque: ptr::null_mut(),
166 authority: ptr::null_mut(),
167 server: ptr::null_mut(),
168 user: ptr::null_mut(),
169 port: 0,
170 path: ptr::null_mut(),
171 query: ptr::null_mut(),
172 fragment: ptr::null_mut(),
173 cleanup: 0,
174 query_raw: ptr::null_mut(),
175 }
176 }
177}
178
179unsafe fn to_c_str(bytes: Option<&[u8]>) -> *mut c_char {
181 let b = match bytes {
182 Some(b) if !b.is_empty() => b,
183 _ => return ptr::null_mut(),
184 };
185 let p = unsafe { allocator::xmlMallocImpl(b.len() + 1) as *mut u8 };
186 if p.is_null() {
187 return ptr::null_mut();
188 }
189 unsafe {
190 ptr::copy_nonoverlapping(b.as_ptr(), p, b.len());
191 *p.add(b.len()) = 0;
192 }
193 p as *mut c_char
194}
195
196unsafe fn from_c_str(p: *const c_char) -> Option<Vec<u8>> {
198 if p.is_null() {
199 return None;
200 }
201 let len = unsafe { libc::strlen(p) };
202 if len == 0 {
203 return None;
204 }
205 let slice = unsafe { core::slice::from_raw_parts(p as *const u8, len) };
206 Some(slice.to_vec())
207}
208
209unsafe fn free_c_uri(uri: *mut CXmlUri) {
211 if uri.is_null() {
212 return;
213 }
214 unsafe {
215 let u = &*uri;
216 for p in [
217 u.scheme,
218 u.opaque,
219 u.authority,
220 u.server,
221 u.user,
222 u.path,
223 u.query,
224 u.fragment,
225 u.query_raw,
226 ] {
227 if !p.is_null() {
228 allocator::xmlFreeImpl(p as *mut c_void);
229 }
230 }
231 drop(Box::from_raw(uri));
232 }
233}
234
235unsafe fn parts_to_c(parts: &UriParts) -> *mut CXmlUri {
237 let boxed = Box::new(CXmlUri {
238 scheme: unsafe { to_c_str(parts.scheme.as_deref()) },
239 opaque: unsafe { to_c_str(parts.opaque.as_deref()) },
240 authority: unsafe { to_c_str(parts.authority.as_deref()) },
241 server: unsafe { to_c_str(parts.server.as_deref()) },
242 user: unsafe { to_c_str(parts.user.as_deref()) },
243 port: parts.port,
244 path: unsafe { to_c_str(parts.path.as_deref()) },
245 query: unsafe { to_c_str(parts.query.as_deref()) },
246 fragment: unsafe { to_c_str(parts.fragment.as_deref()) },
247 cleanup: 0,
248 query_raw: unsafe { to_c_str(parts.query.as_deref()) },
249 });
250 Box::into_raw(boxed)
251}
252
253unsafe fn c_to_parts(uri: *const CXmlUri) -> UriParts {
255 let u = unsafe { &*uri };
256 UriParts {
257 scheme: unsafe { from_c_str(u.scheme) },
258 opaque: unsafe { from_c_str(u.opaque) },
259 authority: unsafe { from_c_str(u.authority) },
260 server: unsafe { from_c_str(u.server) },
261 user: unsafe { from_c_str(u.user) },
262 host: None,
263 port: u.port,
264 path: unsafe { from_c_str(u.path) },
265 query: unsafe { from_c_str(u.query) },
266 fragment: unsafe { from_c_str(u.fragment) },
267 path_raw: None,
268 clean_path: None,
269 }
270}
271
272fn find_scheme(uri: &[u8]) -> Option<(usize, usize)> {
278 if uri.is_empty() {
279 return None;
280 }
281 if !uri[0].is_ascii_alphabetic() {
283 return None;
284 }
285 let mut i = 1;
287 while i < uri.len() && is_scheme_char(uri[i]) {
288 i += 1;
289 }
290 if i < uri.len() && uri[i] == b':' {
291 Some((0, i))
293 } else {
294 None
295 }
296}
297
298fn parse_authority(auth: &[u8]) -> (Option<Vec<u8>>, Option<Vec<u8>>, c_int) {
302 let mut user: Option<Vec<u8>> = None;
303 let mut host: Option<Vec<u8>> = None;
304 let mut port: c_int = 0;
305
306 if auth.is_empty() {
307 return (None, None, 0);
308 }
309
310 let (user_part, host_part) = if let Some(at_pos) = auth.iter().position(|&b| b == b'@') {
312 user = Some(auth[..at_pos].to_vec());
313 (&auth[at_pos + 1..], true)
314 } else {
315 (auth, false)
316 };
317
318 if user_part.starts_with(b"[") {
321 if let Some(close_bracket) = user_part.iter().position(|&b| b == b']') {
323 let host_end = close_bracket + 1;
324 host = Some(user_part[..host_end].to_vec());
325 if host_end < user_part.len() && user_part[host_end] == b':' {
327 let port_str = &user_part[host_end + 1..];
328 if !port_str.is_empty() {
329 let port_str_decoded = core::str::from_utf8(port_str).unwrap_or("");
330 port = port_str_decoded.parse::<c_int>().unwrap_or(0);
331 }
332 }
333 } else {
334 host = Some(user_part.to_vec());
336 }
337 } else {
338 if let Some(colon_pos) = user_part.iter().position(|&b| b == b':') {
340 host = Some(user_part[..colon_pos].to_vec());
341 let port_str = &user_part[colon_pos + 1..];
342 if !port_str.is_empty() {
343 let port_str_decoded = core::str::from_utf8(port_str).unwrap_or("");
344 port = port_str_decoded.parse::<c_int>().unwrap_or(0);
345 }
346 } else {
347 host = Some(user_part.to_vec());
348 }
349 }
350
351 (user, host, port)
352}
353
354pub(crate) fn parse_uri(str: &[u8]) -> Option<UriParts> {
361 if str.is_empty() {
362 return None;
363 }
364
365 let mut parts = UriParts::default();
366 let mut remaining = str;
367
368 if let Some((_start, end)) = find_scheme(remaining) {
370 parts.scheme = Some(remaining[..end].to_vec());
371 remaining = &remaining[end + 1..]; if remaining.starts_with(b"//") {
375 remaining = &remaining[2..];
376 let auth_end = remaining
378 .iter()
379 .position(|&b| b == b'/' || b == b'?' || b == b'#')
380 .unwrap_or(remaining.len());
381 let authority = &remaining[..auth_end];
382 parts.authority = if authority.is_empty() {
384 Some(Vec::new())
385 } else {
386 Some(authority.to_vec())
387 };
388 if !authority.is_empty() {
389 let (user, host, port) = parse_authority(authority);
390 parts.user = user;
391 parts.host = host;
392 parts.port = port;
393 if let Some(ref host_val) = parts.host {
394 let mut server = host_val.clone();
396 if port != 0 {
397 server.extend_from_slice(format!(":{}", port).as_bytes());
398 }
399 parts.server = Some(server);
400 }
401 }
402 remaining = &remaining[auth_end..];
403 } else {
404 if let Some(frag_pos) = remaining.iter().position(|&b| b == b'#') {
407 parts.opaque = Some(remaining[..frag_pos].to_vec());
408 parts.fragment = Some(remaining[frag_pos + 1..].to_vec());
409 } else {
410 parts.opaque = Some(remaining.to_vec());
411 }
412 parts.path = parts.opaque.clone();
414 return Some(parts);
415 }
416 }
417
418 let query_pos = remaining.iter().position(|&b| b == b'?');
421 let frag_pos = remaining.iter().position(|&b| b == b'#');
422
423 let path_end = match (query_pos, frag_pos) {
424 (Some(q), Some(f)) => q.min(f),
425 (Some(q), None) => q,
426 (None, Some(f)) => f,
427 (None, None) => remaining.len(),
428 };
429
430 if path_end > 0 {
431 let path = remaining[..path_end].to_vec();
432 parts.path = Some(path.clone());
433 parts.path_raw = Some(path);
434 }
435
436 if let Some(qpos) = query_pos {
438 let qstart = qpos + 1;
439 let qend = frag_pos.unwrap_or(remaining.len());
440 if qstart < qend {
441 parts.query = Some(remaining[qstart..qend].to_vec());
442 }
443 }
444
445 if let Some(fpos) = frag_pos {
447 let fstart = fpos + 1;
448 if fstart < remaining.len() {
449 parts.fragment = Some(remaining[fstart..].to_vec());
450 }
451 }
452
453 Some(parts)
454}
455
456pub(crate) fn parse_uri_cstr(str: *const xmlChar) -> *mut UriParts {
461 if str.is_null() {
462 return ptr::null_mut();
463 }
464
465 let len = unsafe { libc::strlen(str as *const libc::c_char) };
466 let slice = unsafe { core::slice::from_raw_parts(str, len) };
467
468 match parse_uri(slice) {
469 Some(parts) => {
470 let boxed = Box::new(parts);
471 Box::into_raw(boxed)
472 }
473 None => ptr::null_mut(),
474 }
475}
476
477pub(crate) unsafe fn free_uri_parts(parts: *mut UriParts) {
483 if !parts.is_null() {
484 drop(Box::from_raw(parts));
485 }
486}
487
488pub(crate) fn build_uri(parts: &UriParts) -> Vec<u8> {
492 let mut result = Vec::new();
493
494 if let Some(ref scheme) = parts.scheme {
496 result.extend_from_slice(scheme);
497 result.push(b':');
498 }
499
500 if let Some(ref authority) = parts.authority {
502 result.extend_from_slice(b"//");
503 result.extend_from_slice(authority);
504 } else if parts.host.is_some() {
505 result.extend_from_slice(b"//");
507 if let Some(ref user) = parts.user {
508 result.extend_from_slice(user);
509 result.push(b'@');
510 }
511 if let Some(ref host) = parts.host {
512 result.extend_from_slice(host);
513 }
514 if parts.port != 0 {
515 result.push(b':');
516 result.extend_from_slice(format!("{}", parts.port).as_bytes());
517 }
518 }
519
520 if let Some(ref path) = parts.path {
522 result.extend_from_slice(path);
523 } else if let Some(ref opaque) = parts.opaque {
524 result.extend_from_slice(opaque);
525 }
526
527 if let Some(ref query) = parts.query {
529 result.push(b'?');
530 result.extend_from_slice(query);
531 }
532
533 if let Some(ref fragment) = parts.fragment {
535 result.push(b'#');
536 result.extend_from_slice(fragment);
537 }
538
539 result
540}
541
542pub(crate) fn normalize_uri_path(uri: &[u8]) -> Vec<u8> {
547 if uri.is_empty() {
548 return Vec::new();
549 }
550
551 let absolute = uri.starts_with(b"/");
552 let ends_with_slash = uri.ends_with(b"/");
553
554 let parts: Vec<&[u8]> = uri.split(|&b| b == b'/').collect();
555 let mut segments: Vec<&[u8]> = Vec::new();
556
557 for segment in parts {
558 if segment == b"." || segment.is_empty() {
559 continue;
561 }
562 if segment == b".." {
563 segments.pop();
565 } else {
566 segments.push(segment);
567 }
568 }
569
570 let mut result = Vec::new();
571 if absolute {
572 result.push(b'/');
573 }
574 for (i, seg) in segments.iter().enumerate() {
575 if i > 0 {
576 result.push(b'/');
577 }
578 result.extend_from_slice(seg);
579 }
580
581 if ends_with_slash && !segments.is_empty() {
583 result.push(b'/');
584 }
585
586 if result.is_empty() && absolute {
588 result.push(b'/');
589 }
590
591 result
592}
593
594pub(crate) fn get_scheme(uri: &[u8]) -> Option<Vec<u8>> {
596 if let Some((_start, end)) = find_scheme(uri) {
597 Some(uri[_start..end].to_vec())
598 } else {
599 None
600 }
601}
602
603pub(crate) fn is_absolute(uri: &[u8]) -> bool {
605 find_scheme(uri).is_some()
606}
607
608pub(crate) fn resolve_uri(base: &[u8], relative: &[u8]) -> Option<Vec<u8>> {
615 if base.is_empty() {
616 return if relative.is_empty() {
617 None
618 } else {
619 Some(relative.to_vec())
620 };
621 }
622
623 if is_absolute(relative) {
625 return Some(relative.to_vec());
626 }
627
628 let base_parts = parse_uri(base)?;
630
631 if relative.is_empty() {
633 return Some(build_uri(&base_parts));
634 }
635
636 let rel_str = relative;
638
639 let mut result = UriParts {
640 scheme: base_parts.scheme.clone(),
641 ..Default::default()
642 };
643
644 if rel_str.starts_with(b"//") {
645 let rest = &rel_str[2..];
648 let auth_end = rest.iter().position(|&b| b == b'/').unwrap_or(rest.len());
649 let auth = rest[..auth_end].to_vec();
650 let (user, host, port) = parse_authority(&auth);
651 result.authority = Some(auth);
652 result.user = user;
653 result.host = host;
654 result.port = port;
655
656 let path_rest = if auth_end < rest.len() {
657 &rest[auth_end..]
658 } else {
659 b""
660 };
661 parse_path_query_fragment(path_rest, &mut result);
663 } else if rel_str.starts_with(b"/") {
664 parse_path_query_fragment(rel_str, &mut result);
666 result.authority = base_parts.authority.clone();
668 result.user = base_parts.user.clone();
669 result.host = base_parts.host.clone();
670 result.port = base_parts.port;
671 } else {
672 let base_path = base_parts.path.as_deref().unwrap_or(b"");
675 let base_dir = if let Some(last_slash) = base_path.iter().rposition(|&b| b == b'/') {
676 &base_path[..=last_slash]
677 } else {
678 b""
679 };
680
681 let mut combined = Vec::from(base_dir);
683 combined.extend_from_slice(rel_str);
684 parse_path_query_fragment(&combined, &mut result);
685
686 result.authority = base_parts.authority.clone();
688 result.user = base_parts.user.clone();
689 result.host = base_parts.host.clone();
690 result.port = base_parts.port;
691 }
692
693 if let Some(ref path) = result.path {
695 let normalized = normalize_uri_path(path);
696 result.path = Some(normalized);
697 }
698
699 Some(build_uri(&result))
700}
701
702fn parse_path_query_fragment(input: &[u8], parts: &mut UriParts) {
704 let query_pos = input.iter().position(|&b| b == b'?');
706 let frag_pos = input.iter().position(|&b| b == b'#');
707
708 let path_end = match (query_pos, frag_pos) {
709 (Some(q), Some(f)) => q.min(f),
710 (Some(q), None) => q,
711 (None, Some(f)) => f,
712 (None, None) => input.len(),
713 };
714
715 if path_end > 0 {
716 parts.path = Some(input[..path_end].to_vec());
717 parts.path_raw = parts.path.clone();
718 }
719
720 if let Some(qpos) = query_pos {
722 let qstart = qpos + 1;
723 let qend = frag_pos.unwrap_or(input.len());
724 if qstart < qend {
725 parts.query = Some(input[qstart..qend].to_vec());
726 }
727 }
728
729 if let Some(fpos) = frag_pos {
731 let fstart = fpos + 1;
732 if fstart < input.len() {
733 parts.fragment = Some(input[fstart..].to_vec());
734 }
735 }
736}
737
738pub(crate) unsafe fn xmlParseURI(str: *const c_char) -> *mut c_void {
751 if str.is_null() {
752 return ptr::null_mut();
753 }
754 let len = libc::strlen(str);
755 let slice = unsafe { core::slice::from_raw_parts(str as *const u8, len) };
756 match parse_uri(slice) {
757 Some(parts) => unsafe { parts_to_c(&parts) as *mut c_void },
758 None => ptr::null_mut(),
759 }
760}
761
762pub(crate) unsafe fn xmlFreeURI(uri: *mut c_void) {
770 if !uri.is_null() {
771 unsafe { free_c_uri(uri as *mut CXmlUri) };
772 }
773}
774
775pub(crate) fn xmlCreateURI() -> *mut c_void {
783 let boxed = Box::new(CXmlUri::default());
784 Box::into_raw(boxed) as *mut c_void
785}
786
787pub(crate) unsafe fn xmlSaveUri(uri: *mut c_void) -> *mut xmlChar {
800 if uri.is_null() {
801 return ptr::null_mut();
802 }
803 let parts = unsafe { c_to_parts(uri as *const CXmlUri) };
804 let result = build_uri(&parts);
805 if result.is_empty() {
806 return ptr::null_mut();
807 }
808 let len = result.len();
810 let ptr = unsafe { allocator::xmlMallocImpl(len + 1) as *mut u8 };
811 if ptr.is_null() {
812 return ptr::null_mut();
813 }
814 unsafe {
815 ptr::copy_nonoverlapping(result.as_ptr(), ptr, len);
816 *ptr.add(len) = 0; }
818 ptr as *mut xmlChar
819}
820
821pub(crate) unsafe fn xmlParseURIReference(uri: *mut c_void, str: *const c_char) -> c_int {
834 if uri.is_null() || str.is_null() {
835 return -1;
836 }
837 let len = unsafe { libc::strlen(str) };
838 let slice = unsafe { core::slice::from_raw_parts(str as *const u8, len) };
839 let parts = match parse_uri(slice) {
840 Some(p) => p,
841 None => return -1,
842 };
843 let fresh = unsafe { parts_to_c(&parts) };
844 if fresh.is_null() {
845 return -1;
846 }
847 unsafe {
850 let dst = &mut *(uri as *mut CXmlUri);
851 let src = &mut *fresh;
852 core::mem::swap(dst, src);
853 free_c_uri(fresh);
854 }
855 0
856}
857
858pub(crate) unsafe fn xmlNormalizeURIPath(path: *mut c_char) -> c_int {
873 if path.is_null() {
874 return -1;
875 }
876 unsafe {
881 let mut cur = path;
882 if *cur == b'/' as c_char {
883 cur = cur.add(1);
884 } else {
885 return -1;
886 }
887 let mut out = path;
888 while *cur != 0 {
889 let c0 = *cur as u8;
890 let c1 = *cur.add(1) as u8;
891 let c2 = *cur.add(2) as u8;
892 if c0 == b'.' && c1 == b'/' {
894 cur = cur.add(2);
895 continue;
896 }
897 if c0 == b'.' && c1 == b'.' && c2 == b'/' {
899 if out == path {
900 return -1;
901 }
902 out = out.sub(1);
903 while out > path && *out.sub(1) != b'/' as c_char {
904 out = out.sub(1);
905 }
906 cur = cur.add(3);
907 continue;
908 }
909 if c0 == b'.' && c1 == 0 {
911 break;
912 }
913 if c0 == b'.' && c1 == b'.' && c2 == 0 {
915 if out == path {
916 return -1;
917 }
918 out = out.sub(1);
919 while out > path && *out.sub(1) != b'/' as c_char {
920 out = out.sub(1);
921 }
922 break;
923 }
924 *out = *cur;
925 out = out.add(1);
926 cur = cur.add(1);
927 }
928 *out = 0;
929 }
930 0
931}
932
933pub(crate) unsafe fn xmlURIEscapeStr(str: *const xmlChar, list: *const xmlChar) -> *mut xmlChar {
945 if str.is_null() {
946 return ptr::null_mut();
947 }
948 let str_len = unsafe { libc::strlen(str as *const libc::c_char) };
949 let str_slice = unsafe { core::slice::from_raw_parts(str, str_len) };
950
951 let mut safe_set = [false; 256];
953 for b in 0u8..=255 {
954 if is_unreserved(b) || b == b'%' {
955 safe_set[b as usize] = true;
956 }
957 }
958 if !list.is_null() {
959 let list_len = unsafe { libc::strlen(list as *const libc::c_char) };
960 let list_slice = unsafe { core::slice::from_raw_parts(list, list_len) };
961 for &b in list_slice {
962 safe_set[b as usize] = true;
963 }
964 }
965
966 let mut result = Vec::with_capacity(str_slice.len() * 3);
968 for &b in str_slice {
969 if safe_set[b as usize] {
970 result.push(b);
971 } else {
972 result.extend_from_slice(format!("%{:02X}", b).as_bytes());
973 }
974 }
975
976 let len = result.len();
977 let ptr = unsafe { allocator::xmlMallocImpl(len + 1) as *mut u8 };
978 if ptr.is_null() {
979 return ptr::null_mut();
980 }
981 unsafe {
982 ptr::copy_nonoverlapping(result.as_ptr(), ptr, len);
983 *ptr.add(len) = 0;
984 }
985 ptr as *mut xmlChar
986}
987
988pub(crate) unsafe fn xmlURIUnescapeString(
1003 str: *const c_char,
1004 len: c_int,
1005 target: *mut c_char,
1006) -> *mut c_char {
1007 if str.is_null() {
1008 return ptr::null_mut();
1009 }
1010 let slice = if len < 0 {
1011 let cstr_len = unsafe { libc::strlen(str) };
1012 unsafe { core::slice::from_raw_parts(str as *const u8, cstr_len) }
1013 } else {
1014 unsafe { core::slice::from_raw_parts(str as *const u8, len as usize) }
1015 };
1016
1017 let decoded = percent_decode(slice);
1018
1019 if !target.is_null() {
1020 unsafe {
1021 ptr::copy_nonoverlapping(decoded.as_ptr(), target as *mut u8, decoded.len());
1022 *((target as *mut u8).add(decoded.len())) = 0;
1023 }
1024 return target;
1025 }
1026
1027 let out_len = decoded.len();
1028 let ptr = unsafe { allocator::xmlMallocImpl(out_len + 1) as *mut u8 };
1029 if ptr.is_null() {
1030 return ptr::null_mut();
1031 }
1032 unsafe {
1033 ptr::copy_nonoverlapping(decoded.as_ptr(), ptr, out_len);
1034 *ptr.add(out_len) = 0;
1035 }
1036 ptr as *mut c_char
1037}
1038
1039pub(crate) unsafe fn xmlParseURIRaw(str: *const c_char, _raw: c_int) -> *mut c_void {
1052 unsafe { xmlParseURI(str) }
1053}
1054
1055#[cfg(test)]
1058mod tests {
1059 use super::*;
1060
1061 #[test]
1064 fn test_is_unreserved() {
1065 assert!(is_unreserved(b'a'));
1066 assert!(is_unreserved(b'Z'));
1067 assert!(is_unreserved(b'0'));
1068 assert!(is_unreserved(b'-'));
1069 assert!(is_unreserved(b'.'));
1070 assert!(is_unreserved(b'_'));
1071 assert!(is_unreserved(b'~'));
1072 assert!(!is_unreserved(b':'));
1073 assert!(!is_unreserved(b'/'));
1074 assert!(!is_unreserved(b'%'));
1075 assert!(!is_unreserved(b' '));
1076 }
1077
1078 #[test]
1079 fn test_is_reserved() {
1080 assert!(is_reserved(b':'));
1081 assert!(is_reserved(b'/'));
1082 assert!(is_reserved(b'?'));
1083 assert!(is_reserved(b'#'));
1084 assert!(is_reserved(b'@'));
1085 assert!(is_reserved(b'!'));
1086 assert!(is_reserved(b'$'));
1087 assert!(is_reserved(b'&'));
1088 assert!(is_reserved(b'('));
1089 assert!(is_reserved(b')'));
1090 assert!(!is_reserved(b'a'));
1091 assert!(!is_reserved(b' '));
1092 }
1093
1094 #[test]
1095 fn test_is_scheme_char() {
1096 assert!(is_scheme_char(b'a'));
1097 assert!(is_scheme_char(b'Z'));
1098 assert!(is_scheme_char(b'0'));
1099 assert!(is_scheme_char(b'+'));
1100 assert!(is_scheme_char(b'-'));
1101 assert!(is_scheme_char(b'.'));
1102 assert!(!is_scheme_char(b':'));
1103 assert!(!is_scheme_char(b'/'));
1104 assert!(!is_scheme_char(b' '));
1105 }
1106
1107 #[test]
1110 fn test_percent_decode_simple() {
1111 assert_eq!(percent_decode(b"hello"), b"hello");
1112 assert_eq!(percent_decode(b"%68%65%6C%6C%6F"), b"hello");
1113 assert_eq!(percent_decode(b"%48%65%6C%6C%6F"), b"Hello");
1114 assert_eq!(percent_decode(b"a%20b"), b"a b");
1115 }
1116
1117 #[test]
1118 fn test_percent_decode_invalid() {
1119 assert_eq!(percent_decode(b"%XX"), b"%XX");
1121 assert_eq!(percent_decode(b"%2"), b"%2");
1122 assert_eq!(percent_decode(b"%"), b"%");
1123 assert_eq!(percent_decode(b"%%20"), b"% ");
1124 }
1125
1126 #[test]
1127 fn test_percent_decode_empty() {
1128 assert_eq!(percent_decode(b""), b"");
1129 }
1130
1131 #[test]
1132 fn test_percent_encode() {
1133 assert_eq!(percent_encode(b"hello"), b"hello");
1134 assert_eq!(percent_encode(b"hello world"), b"hello%20world");
1135 assert_eq!(percent_encode(b"a/b"), b"a/b"); }
1137
1138 #[test]
1141 fn test_parse_http_uri() {
1142 let parts =
1143 parse_uri(b"http://example.com/path/to/file.xml?query=1#frag").expect("should parse");
1144 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1145 assert_eq!(parts.authority, Some(b"example.com".to_vec()));
1146 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1147 assert_eq!(parts.port, 0);
1148 assert_eq!(parts.path, Some(b"/path/to/file.xml".to_vec()));
1149 assert_eq!(parts.query, Some(b"query=1".to_vec()));
1150 assert_eq!(parts.fragment, Some(b"frag".to_vec()));
1151 }
1152
1153 #[test]
1154 fn test_parse_https_uri() {
1155 let parts = parse_uri(b"https://example.com:443/path").expect("should parse");
1156 assert_eq!(parts.scheme, Some(b"https".to_vec()));
1157 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1158 assert_eq!(parts.port, 443);
1159 assert_eq!(parts.path, Some(b"/path".to_vec()));
1160 }
1161
1162 #[test]
1163 fn test_parse_file_uri() {
1164 let parts = parse_uri(b"file:///etc/hosts").expect("should parse");
1165 assert_eq!(parts.scheme, Some(b"file".to_vec()));
1166 assert!(parts.authority.is_none() || parts.authority.as_deref() == Some(b""));
1167 assert_eq!(parts.path, Some(b"/etc/hosts".to_vec()));
1168 }
1169
1170 #[test]
1171 fn test_parse_file_uri_with_host() {
1172 let parts = parse_uri(b"file://localhost/etc/hosts").expect("should parse");
1174 assert_eq!(parts.scheme, Some(b"file".to_vec()));
1175 assert_eq!(parts.host, Some(b"localhost".to_vec()));
1176 assert_eq!(parts.path, Some(b"/etc/hosts".to_vec()));
1177 }
1178
1179 #[test]
1180 fn test_parse_relative_uri() {
1181 let parts = parse_uri(b"/path/to/file.xml").expect("should parse");
1182 assert!(parts.scheme.is_none());
1183 assert_eq!(parts.path, Some(b"/path/to/file.xml".to_vec()));
1184 }
1185
1186 #[test]
1187 fn test_parse_relative_uri_with_query() {
1188 let parts = parse_uri(b"file.xml?query=1").expect("should parse");
1189 assert!(parts.scheme.is_none());
1190 assert_eq!(parts.path, Some(b"file.xml".to_vec()));
1191 assert_eq!(parts.query, Some(b"query=1".to_vec()));
1192 }
1193
1194 #[test]
1195 fn test_parse_uri_with_user_info() {
1196 let parts = parse_uri(b"ftp://user@host.com:21/path").expect("should parse");
1197 assert_eq!(parts.scheme, Some(b"ftp".to_vec()));
1198 assert_eq!(parts.user, Some(b"user".to_vec()));
1199 assert_eq!(parts.host, Some(b"host.com".to_vec()));
1200 assert_eq!(parts.port, 21);
1201 assert_eq!(parts.path, Some(b"/path".to_vec()));
1202 }
1203
1204 #[test]
1205 fn test_parse_uri_with_user_password() {
1206 let parts = parse_uri(b"ftp://user:pass@host.com/path").expect("should parse");
1207 assert_eq!(parts.scheme, Some(b"ftp".to_vec()));
1208 assert_eq!(parts.user, Some(b"user:pass".to_vec()));
1209 assert_eq!(parts.host, Some(b"host.com".to_vec()));
1210 assert_eq!(parts.path, Some(b"/path".to_vec()));
1211 }
1212
1213 #[test]
1214 fn test_parse_opaque_uri() {
1215 let parts = parse_uri(b"mailto:user@example.com").expect("should parse");
1216 assert_eq!(parts.scheme, Some(b"mailto".to_vec()));
1217 assert_eq!(parts.opaque, Some(b"user@example.com".to_vec()));
1218 assert!(parts.authority.is_none());
1219 }
1220
1221 #[test]
1222 fn test_parse_opaque_uri_with_fragment() {
1223 let parts = parse_uri(b"urn:isbn:0-395-36341-1#frag").expect("should parse");
1224 assert_eq!(parts.scheme, Some(b"urn".to_vec()));
1225 assert_eq!(parts.opaque, Some(b"isbn:0-395-36341-1".to_vec()));
1226 assert_eq!(parts.fragment, Some(b"frag".to_vec()));
1227 }
1228
1229 #[test]
1230 fn test_parse_empty_uri() {
1231 assert!(parse_uri(b"").is_none());
1232 }
1233
1234 #[test]
1235 fn test_parse_uri_fragment_only() {
1236 let parts = parse_uri(b"#fragment").expect("should parse");
1237 assert!(parts.scheme.is_none());
1238 assert!(parts.path.is_none());
1239 assert_eq!(parts.fragment, Some(b"fragment".to_vec()));
1240 }
1241
1242 #[test]
1243 fn test_parse_uri_query_only() {
1244 let parts = parse_uri(b"?query").expect("should parse");
1245 assert!(parts.scheme.is_none());
1246 assert!(parts.path.is_none());
1247 assert_eq!(parts.query, Some(b"query".to_vec()));
1248 }
1249
1250 #[test]
1251 fn test_parse_uri_with_ipv6_host() {
1252 let parts = parse_uri(b"http://[::1]:8080/path").expect("should parse");
1253 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1254 assert_eq!(parts.host, Some(b"[::1]".to_vec()));
1255 assert_eq!(parts.port, 8080);
1256 assert_eq!(parts.path, Some(b"/path".to_vec()));
1257 }
1258
1259 #[test]
1260 fn test_parse_uri_no_path() {
1261 let parts = parse_uri(b"http://example.com").expect("should parse");
1262 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1263 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1264 assert!(parts.path.is_none());
1265 }
1266
1267 #[test]
1268 fn test_parse_uri_no_path_with_query() {
1269 let parts = parse_uri(b"http://example.com?query").expect("should parse");
1270 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1271 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1272 assert!(parts.path.is_none());
1273 assert_eq!(parts.query, Some(b"query".to_vec()));
1274 }
1275
1276 #[test]
1279 fn test_build_uri() {
1280 let parts = UriParts {
1281 scheme: Some(b"http".to_vec()),
1282 host: Some(b"example.com".to_vec()),
1283 port: 8080,
1284 path: Some(b"/path".to_vec()),
1285 query: Some(b"q=1".to_vec()),
1286 fragment: Some(b"frag".to_vec()),
1287 ..Default::default()
1288 };
1289 assert_eq!(build_uri(&parts), b"http://example.com:8080/path?q=1#frag");
1290 }
1291
1292 #[test]
1293 fn test_build_uri_simple() {
1294 let parts = UriParts {
1295 scheme: Some(b"http".to_vec()),
1296 host: Some(b"example.com".to_vec()),
1297 path: Some(b"/".to_vec()),
1298 ..Default::default()
1299 };
1300 assert_eq!(build_uri(&parts), b"http://example.com/");
1301 }
1302
1303 #[test]
1304 fn test_build_uri_opaque() {
1305 let parts = UriParts {
1306 scheme: Some(b"mailto".to_vec()),
1307 opaque: Some(b"user@example.com".to_vec()),
1308 ..Default::default()
1309 };
1310 assert_eq!(build_uri(&parts), b"mailto:user@example.com");
1311 }
1312
1313 #[test]
1314 fn test_build_uri_relative() {
1315 let parts = UriParts {
1316 path: Some(b"/relative/path".to_vec()),
1317 ..Default::default()
1318 };
1319 assert_eq!(build_uri(&parts), b"/relative/path");
1320 }
1321
1322 #[test]
1325 fn test_normalize_uri_path_simple() {
1326 assert_eq!(normalize_uri_path(b"/foo/bar"), b"/foo/bar");
1327 assert_eq!(normalize_uri_path(b"/foo/./bar"), b"/foo/bar");
1328 assert_eq!(normalize_uri_path(b"/foo/../bar"), b"/bar");
1329 assert_eq!(normalize_uri_path(b"/foo/bar/.."), b"/foo");
1330 assert_eq!(normalize_uri_path(b"/"), b"/");
1331 }
1332
1333 #[test]
1334 fn test_normalize_uri_path_relative() {
1335 assert_eq!(normalize_uri_path(b"foo/bar"), b"foo/bar");
1336 assert_eq!(normalize_uri_path(b"foo/./bar"), b"foo/bar");
1337 assert_eq!(normalize_uri_path(b"foo/../bar"), b"bar");
1338 }
1339
1340 #[test]
1341 fn test_normalize_uri_path_double_dot_overflow() {
1342 assert_eq!(normalize_uri_path(b"/a/../../b"), b"/b");
1344 assert_eq!(normalize_uri_path(b"/../b"), b"/b");
1345 }
1346
1347 #[test]
1348 fn test_normalize_uri_path_empty() {
1349 assert_eq!(normalize_uri_path(b""), b"");
1350 }
1351
1352 #[test]
1353 fn test_normalize_uri_path_dots_only() {
1354 assert_eq!(normalize_uri_path(b"./././."), b"");
1355 assert_eq!(normalize_uri_path(b"/./././"), b"/");
1356 }
1357
1358 #[test]
1361 fn test_get_scheme() {
1362 assert_eq!(get_scheme(b"http://example.com"), Some(b"http".to_vec()));
1363 assert_eq!(get_scheme(b"https://example.com"), Some(b"https".to_vec()));
1364 assert_eq!(get_scheme(b"file:///path"), Some(b"file".to_vec()));
1365 assert_eq!(get_scheme(b"ftp://host"), Some(b"ftp".to_vec()));
1366 assert_eq!(get_scheme(b"mailto:user@host"), Some(b"mailto".to_vec()));
1367 assert_eq!(get_scheme(b"urn:isbn:1234"), Some(b"urn".to_vec()));
1368 assert_eq!(get_scheme(b"/path"), None);
1369 assert_eq!(get_scheme(b"relative"), None);
1370 assert_eq!(get_scheme(b""), None);
1371 }
1372
1373 #[test]
1374 fn test_is_absolute() {
1375 assert!(is_absolute(b"http://example.com"));
1376 assert!(is_absolute(b"file:///path"));
1377 assert!(is_absolute(b"mailto:user@host"));
1378 assert!(!is_absolute(b"/path"));
1379 assert!(!is_absolute(b"relative"));
1380 assert!(!is_absolute(b""));
1381 }
1382
1383 #[test]
1386 fn test_resolve_uri_absolute_relative() {
1387 let result =
1388 resolve_uri(b"http://example.com/base/", b"relative.xml").expect("should resolve");
1389 assert_eq!(result, b"http://example.com/base/relative.xml");
1390 }
1391
1392 #[test]
1393 fn test_resolve_uri_absolute_absolute() {
1394 let result = resolve_uri(
1395 b"http://example.com/base/",
1396 b"http://other.com/absolute.xml",
1397 )
1398 .expect("should resolve");
1399 assert_eq!(result, b"http://other.com/absolute.xml");
1400 }
1401
1402 #[test]
1403 fn test_resolve_uri_root_relative() {
1404 let result =
1405 resolve_uri(b"http://example.com/base/file.xml", b"/root.xml").expect("should resolve");
1406 assert_eq!(result, b"http://example.com/root.xml");
1407 }
1408
1409 #[test]
1410 fn test_resolve_uri_network_path() {
1411 let result = resolve_uri(b"http://example.com/base/file.xml", b"//other.com/root.xml")
1412 .expect("should resolve");
1413 assert_eq!(result, b"http://other.com/root.xml");
1414 }
1415
1416 #[test]
1417 fn test_resolve_uri_parent_traversal() {
1418 let result = resolve_uri(b"http://example.com/a/b/c/file.xml", b"../../d/file.xml")
1419 .expect("should resolve");
1420 assert_eq!(result, b"http://example.com/a/d/file.xml");
1421 }
1422
1423 #[test]
1424 fn test_resolve_uri_with_query() {
1425 let result =
1426 resolve_uri(b"http://example.com/base/", b"file.xml?query=1").expect("should resolve");
1427 assert_eq!(result, b"http://example.com/base/file.xml?query=1");
1428 }
1429
1430 #[test]
1431 fn test_resolve_uri_with_fragment() {
1432 let result =
1433 resolve_uri(b"http://example.com/base/file.xml", b"#frag").expect("should resolve");
1434 assert_eq!(result, b"http://example.com/base/#frag");
1437 }
1438
1439 #[test]
1440 fn test_resolve_uri_empty_base() {
1441 let result = resolve_uri(b"", b"relative.xml");
1442 assert_eq!(result, Some(b"relative.xml".to_vec()));
1443 }
1444
1445 #[test]
1446 fn test_resolve_uri_empty_relative() {
1447 let result = resolve_uri(b"http://example.com/base/", b"");
1448 assert!(result.is_some());
1449 assert_eq!(result.unwrap(), b"http://example.com/base/");
1451 }
1452
1453 #[test]
1454 fn test_resolve_uri_both_empty() {
1455 assert!(resolve_uri(b"", b"").is_none());
1456 }
1457
1458 #[test]
1459 fn test_resolve_uri_file_scheme() {
1460 let result = resolve_uri(b"file:///base/dir/", b"file.xml").expect("should resolve");
1461 assert_eq!(result, b"file:///base/dir/file.xml");
1462 }
1463
1464 #[test]
1465 fn test_resolve_uri_deep_relative() {
1466 let result = resolve_uri(
1467 b"http://example.com/a/b/c/d/e/file.xml",
1468 b"../../../../x/y/z/file.xml",
1469 )
1470 .expect("should resolve");
1471 assert_eq!(result, b"http://example.com/a/x/y/z/file.xml");
1472 }
1473
1474 #[test]
1477 fn test_xml_create_and_free_uri() {
1478 unsafe {
1479 let uri = xmlCreateURI();
1480 assert!(!uri.is_null());
1481 xmlFreeURI(uri);
1482 }
1483 }
1484
1485 #[test]
1486 fn test_xml_parse_uri() {
1487 unsafe {
1488 let cstr = b"http://example.com/path\0".as_ptr() as *const c_char;
1489 let uri = xmlParseURI(cstr);
1490 assert!(!uri.is_null());
1491 let parts = &*(uri as *const CXmlUri);
1492 assert_eq!(from_c_str(parts.scheme), Some(b"http".to_vec()));
1493 assert_eq!(from_c_str(parts.server), Some(b"example.com".to_vec()));
1494 assert_eq!(from_c_str(parts.path), Some(b"/path".to_vec()));
1495 xmlFreeURI(uri);
1496 }
1497 }
1498
1499 #[test]
1500 fn test_xml_parse_uri_null() {
1501 unsafe {
1502 let uri = xmlParseURI(ptr::null());
1503 assert!(uri.is_null());
1504 }
1505 }
1506
1507 #[test]
1508 fn test_xml_save_uri() {
1509 unsafe {
1510 let cstr = b"http://example.com:8080/path?q=1#f\0".as_ptr() as *const c_char;
1511 let uri = xmlParseURI(cstr);
1512 assert!(!uri.is_null());
1513 let saved = xmlSaveUri(uri);
1514 assert!(!saved.is_null());
1515 let saved_str = std::ffi::CStr::from_ptr(saved as *const c_char);
1516 assert_eq!(saved_str.to_bytes(), b"http://example.com:8080/path?q=1#f");
1517 allocator::xmlFreeImpl(saved as *mut core::ffi::c_void);
1518 xmlFreeURI(uri);
1519 }
1520 }
1521
1522 #[test]
1523 fn test_xml_escape_str() {
1524 unsafe {
1525 let cstr = b"hello world\0".as_ptr() as *const xmlChar;
1526 let result = xmlURIEscapeStr(cstr, ptr::null());
1527 assert!(!result.is_null());
1528 let result_str = std::ffi::CStr::from_ptr(result as *const c_char);
1529 assert_eq!(result_str.to_bytes(), b"hello%20world");
1530 allocator::xmlFreeImpl(result as *mut core::ffi::c_void);
1531 }
1532 }
1533
1534 #[test]
1535 fn test_xml_escape_str_with_safe_list() {
1536 unsafe {
1537 let cstr = b"hello world\0".as_ptr() as *const xmlChar;
1538 let safe = b" \0".as_ptr() as *const xmlChar;
1539 let result = xmlURIEscapeStr(cstr, safe);
1540 assert!(!result.is_null());
1541 let result_str = std::ffi::CStr::from_ptr(result as *const c_char);
1542 assert_eq!(result_str.to_bytes(), b"hello world"); allocator::xmlFreeImpl(result as *mut core::ffi::c_void);
1544 }
1545 }
1546
1547 #[test]
1548 fn test_xml_unescape_string() {
1549 unsafe {
1550 let cstr = b"hello%20world\0".as_ptr() as *const c_char;
1551 let result = xmlURIUnescapeString(cstr, -1, ptr::null_mut());
1552 assert!(!result.is_null());
1553 let result_str = std::ffi::CStr::from_ptr(result);
1554 assert_eq!(result_str.to_bytes(), b"hello world");
1555 allocator::xmlFreeImpl(result as *mut core::ffi::c_void);
1556 }
1557 }
1558
1559 #[test]
1560 fn test_xml_unescape_string_with_len() {
1561 unsafe {
1562 let cstr = b"hello%20world\0".as_ptr() as *const c_char;
1563 let result = xmlURIUnescapeString(cstr, 13, ptr::null_mut());
1564 assert!(!result.is_null());
1565 let result_str = std::ffi::CStr::from_ptr(result);
1566 assert_eq!(result_str.to_bytes(), b"hello world");
1567 allocator::xmlFreeImpl(result as *mut core::ffi::c_void);
1568 }
1569 }
1570
1571 #[test]
1572 fn test_xml_parse_uri_raw() {
1573 unsafe {
1574 let cstr = b"http://example.com\0".as_ptr() as *const c_char;
1575 let uri = xmlParseURIRaw(cstr, 0);
1576 assert!(!uri.is_null());
1577 let parts = &*(uri as *const CXmlUri);
1578 assert_eq!(from_c_str(parts.scheme), Some(b"http".to_vec()));
1579 assert_eq!(from_c_str(parts.server), Some(b"example.com".to_vec()));
1580 xmlFreeURI(uri);
1581 }
1582 }
1583
1584 #[test]
1585 fn test_xml_free_null() {
1586 unsafe {
1587 xmlFreeURI(ptr::null_mut());
1589 }
1590 }
1591
1592 #[test]
1595 fn test_parse_uri_scheme_only() {
1596 let parts = parse_uri(b"http:").expect("should parse");
1597 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1598 assert!(parts.opaque.is_none() || parts.opaque.as_deref() == Some(b""));
1599 }
1600
1601 #[test]
1602 fn test_parse_uri_with_trailing_slash() {
1603 let parts = parse_uri(b"http://example.com/").expect("should parse");
1604 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1605 assert_eq!(parts.path, Some(b"/".to_vec()));
1606 }
1607
1608 #[test]
1609 fn test_parse_uri_with_double_slash_path() {
1610 let parts = parse_uri(b"http://example.com//path").expect("should parse");
1611 assert_eq!(parts.scheme, Some(b"http".to_vec()));
1612 assert_eq!(parts.path, Some(b"//path".to_vec()));
1613 }
1614
1615 #[test]
1616 fn test_parse_uri_no_scheme_colon() {
1617 let parts = parse_uri(b"123:path");
1619 assert!(parts.is_some());
1621 let p = parts.unwrap();
1622 assert!(p.scheme.is_none());
1623 assert_eq!(p.path, Some(b"123:path".to_vec()));
1624 }
1625
1626 #[test]
1627 fn test_parse_uri_ftp_with_home_dir() {
1628 let parts = parse_uri(b"ftp://host/home/user/file.txt").expect("should parse");
1629 assert_eq!(parts.scheme, Some(b"ftp".to_vec()));
1630 assert_eq!(parts.host, Some(b"host".to_vec()));
1631 assert_eq!(parts.path, Some(b"/home/user/file.txt".to_vec()));
1632 }
1633
1634 #[test]
1635 fn test_parse_uri_scheme_case() {
1636 let parts = parse_uri(b"HTTP://example.com/Path").expect("should parse");
1637 assert_eq!(parts.scheme, Some(b"HTTP".to_vec()));
1638 assert_eq!(parts.host, Some(b"example.com".to_vec()));
1639 assert_eq!(parts.path, Some(b"/Path".to_vec()));
1640 }
1641
1642 #[test]
1643 fn test_normalize_path_complex() {
1644 assert_eq!(normalize_uri_path(b"/a/b/c/./../../g"), b"/a/g");
1645 assert_eq!(normalize_uri_path(b"mid/content=5/../6"), b"mid/6");
1646 }
1647
1648 #[test]
1649 fn test_resolve_uri_same_directory() {
1650 let result =
1651 resolve_uri(b"http://example.com/a/b/c.html", b"d.html").expect("should resolve");
1652 assert_eq!(result, b"http://example.com/a/b/d.html");
1653 }
1654
1655 #[test]
1656 fn test_resolve_uri_complex_traversal() {
1657 let result = resolve_uri(b"http://a/b/c/d;p?q", b"g/h/../i/./j#f").expect("should resolve");
1658 let result_str = core::str::from_utf8(&result).unwrap_or("");
1659 assert!(result_str.contains("http://a/b/c/g/i/j"));
1660 }
1661
1662 #[test]
1665 fn test_hex_val() {
1666 assert_eq!(hex_val(b'0'), Some(0));
1667 assert_eq!(hex_val(b'9'), Some(9));
1668 assert_eq!(hex_val(b'a'), Some(10));
1669 assert_eq!(hex_val(b'f'), Some(15));
1670 assert_eq!(hex_val(b'A'), Some(10));
1671 assert_eq!(hex_val(b'F'), Some(15));
1672 assert_eq!(hex_val(b'g'), None);
1673 assert_eq!(hex_val(b'z'), None);
1674 assert_eq!(hex_val(b'%'), None);
1675 }
1676
1677 #[test]
1680 fn test_parse_uri_cstr() {
1681 let cstr = b"http://example.com/path\0".as_ptr() as *const xmlChar;
1682 let ptr = parse_uri_cstr(cstr);
1683 assert!(!ptr.is_null());
1684 unsafe {
1685 assert_eq!((*ptr).scheme, Some(b"http".to_vec()));
1686 assert_eq!((*ptr).host, Some(b"example.com".to_vec()));
1687 free_uri_parts(ptr);
1688 }
1689 }
1690
1691 #[test]
1692 fn test_parse_uri_cstr_null() {
1693 let ptr = parse_uri_cstr(ptr::null());
1694 assert!(ptr.is_null());
1695 }
1696
1697 #[test]
1698 fn test_parse_uri_cstr_invalid() {
1699 let cstr = b"\0".as_ptr() as *const xmlChar;
1700 let ptr = parse_uri_cstr(cstr);
1701 assert!(ptr.is_null());
1702 }
1703}