1use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
41use crate::xml::regex::{
42 xmlRegExecPushString, xmlRegFreeExecCtxt, xmlRegFreeRegexp, xmlRegNewExecCtxt,
43 xmlRegexpCompile, xmlRegexpIsDeterministic, XmlRegexp,
44};
45use core::ffi::c_int;
46use core::ptr;
47
48pub type XmlAutomataStatePtr = *mut XmlAutomataState;
50
51pub type XmlAutomataPtr = *mut XmlAutomata;
53
54#[repr(C)]
56pub struct XmlAutomata {
57 regexp: Option<Box<XmlRegexp>>,
59 states: Vec<*mut XmlAutomataState>,
61 init_state: Option<*mut XmlAutomataState>,
63 error: c_int,
65}
66
67#[repr(C)]
69pub struct XmlAutomataState {
70 transitions: Vec<AutomataTransition>,
72}
73
74#[repr(C)]
76pub struct AutomataTransition {
77 token: Option<u8>,
79 min: c_int,
81 max: c_int,
83 to: Option<*mut XmlAutomataState>,
85 once: bool,
87 all: bool,
89 epsilon: bool,
91 counter: c_int,
93 data: *mut core::ffi::c_void,
95}
96
97unsafe impl Send for XmlAutomata {}
100unsafe impl Sync for XmlAutomata {}
101unsafe impl Send for XmlAutomataState {}
102unsafe impl Sync for XmlAutomataState {}
103
104#[no_mangle]
108pub unsafe extern "C" fn xmlNewAutomata() -> XmlAutomataPtr {
109 let am = xmlMallocImpl(core::mem::size_of::<XmlAutomata>()) as XmlAutomataPtr;
110 if am.is_null() {
111 return ptr::null_mut();
112 }
113 unsafe {
114 core::ptr::write(&mut (*am).regexp, None as Option<Box<XmlRegexp>>);
115 core::ptr::write(&mut (*am).states, Vec::new());
116 (*am).init_state = None;
117 (*am).error = 0;
118 }
119 am
120}
121
122#[no_mangle]
126pub unsafe extern "C" fn xmlFreeAutomata(am: XmlAutomataPtr) {
127 if am.is_null() {
128 return;
129 }
130 unsafe {
131 for &state in &(*am).states {
133 if !state.is_null() {
134 core::ptr::drop_in_place(&mut (*state).transitions);
135 xmlFreeImpl(state as *mut core::ffi::c_void);
136 }
137 }
138 core::ptr::drop_in_place(&mut (*am).states);
140 let _ = (*am).regexp.take();
142 xmlFreeImpl(am as *mut core::ffi::c_void);
143 }
144}
145
146#[no_mangle]
150pub unsafe extern "C" fn xmlAutomataNewState(am: XmlAutomataPtr) -> XmlAutomataStatePtr {
151 if am.is_null() {
152 return ptr::null_mut();
153 }
154 let state = xmlMallocImpl(core::mem::size_of::<XmlAutomataState>()) as XmlAutomataStatePtr;
155 if state.is_null() {
156 return ptr::null_mut();
157 }
158 unsafe {
159 core::ptr::write(&mut (*state).transitions, Vec::new());
160 (*am).states.push(state);
162 if (*am).init_state.is_none() {
164 (*am).init_state = Some(state);
165 }
166 }
167 state
168}
169
170#[no_mangle]
174pub unsafe extern "C" fn xmlAutomataSetFinalState(
175 _am: XmlAutomataPtr,
176 _state: XmlAutomataStatePtr,
177) -> c_int {
178 0
182}
183
184#[no_mangle]
188pub unsafe extern "C" fn xmlAutomataGetInitState(am: XmlAutomataPtr) -> XmlAutomataStatePtr {
189 if am.is_null() {
190 return ptr::null_mut();
191 }
192 unsafe { (*am).init_state.unwrap_or(ptr::null_mut()) }
193}
194
195#[no_mangle]
199pub unsafe extern "C" fn xmlAutomataNewEpsilon(
200 am: XmlAutomataPtr,
201 from: XmlAutomataStatePtr,
202 to: XmlAutomataStatePtr,
203) -> XmlAutomataStatePtr {
204 if am.is_null() || from.is_null() || to.is_null() {
205 return ptr::null_mut();
206 }
207 unsafe {
208 (*from).transitions.push(AutomataTransition {
209 token: None,
210 min: 0,
211 max: 0,
212 to: Some(to),
213 once: false,
214 all: false,
215 epsilon: true,
216 counter: -1,
217 data: ptr::null_mut(),
218 });
219 }
220 from
221}
222
223#[no_mangle]
227pub unsafe extern "C" fn xmlAutomataNewTransition(
228 am: XmlAutomataPtr,
229 from: XmlAutomataStatePtr,
230 to: XmlAutomataStatePtr,
231 token: *const core::ffi::c_char,
232 _data: *mut core::ffi::c_void,
233) -> XmlAutomataStatePtr {
234 if am.is_null() || from.is_null() || to.is_null() {
235 return ptr::null_mut();
236 }
237 let tok = if token.is_null() {
238 None
239 } else {
240 unsafe { Some(*token as u8) }
242 };
243 unsafe {
244 (*from).transitions.push(AutomataTransition {
245 token: tok,
246 min: 0,
247 max: 0,
248 to: Some(to),
249 once: false,
250 all: false,
251 epsilon: false,
252 counter: -1,
253 data: ptr::null_mut(),
254 });
255 }
256 from
257}
258
259#[no_mangle]
263pub unsafe extern "C" fn xmlAutomataNewCountTrans(
264 am: XmlAutomataPtr,
265 from: XmlAutomataStatePtr,
266 to: XmlAutomataStatePtr,
267 token: *const core::ffi::c_char,
268 _data: *mut core::ffi::c_void,
269 min: c_int,
270 max: c_int,
271) -> XmlAutomataStatePtr {
272 if am.is_null() || from.is_null() || to.is_null() {
273 return ptr::null_mut();
274 }
275 let tok = if token.is_null() {
276 None
277 } else {
278 unsafe { Some(*token as u8) }
279 };
280 unsafe {
281 (*from).transitions.push(AutomataTransition {
282 token: tok,
283 min,
284 max,
285 to: Some(to),
286 once: false,
287 all: false,
288 epsilon: false,
289 counter: -1,
290 data: ptr::null_mut(),
291 });
292 }
293 from
294}
295
296#[no_mangle]
300pub unsafe extern "C" fn xmlAutomataNewOnceTrans(
301 am: XmlAutomataPtr,
302 from: XmlAutomataStatePtr,
303 to: XmlAutomataStatePtr,
304 token: *const core::ffi::c_char,
305 _data: *mut core::ffi::c_void,
306 min: c_int,
307 max: c_int,
308) -> XmlAutomataStatePtr {
309 if am.is_null() || from.is_null() || to.is_null() {
310 return ptr::null_mut();
311 }
312 let tok = if token.is_null() {
313 None
314 } else {
315 unsafe { Some(*token as u8) }
316 };
317 unsafe {
318 (*from).transitions.push(AutomataTransition {
319 token: tok,
320 min,
321 max,
322 to: Some(to),
323 once: true,
324 all: false,
325 epsilon: false,
326 counter: -1,
327 data: ptr::null_mut(),
328 });
329 }
330 from
331}
332
333#[no_mangle]
337pub unsafe extern "C" fn xmlAutomataNewAllTrans(
338 am: XmlAutomataPtr,
339 from: XmlAutomataStatePtr,
340 to: XmlAutomataStatePtr,
341 _lax: c_int,
342) -> XmlAutomataStatePtr {
343 if am.is_null() || from.is_null() || to.is_null() {
344 return ptr::null_mut();
345 }
346 unsafe {
347 (*from).transitions.push(AutomataTransition {
348 token: None,
349 min: 0,
350 max: 0,
351 to: Some(to),
352 once: false,
353 all: true,
354 epsilon: false,
355 counter: -1,
356 data: ptr::null_mut(),
357 });
358 }
359 from
360}
361
362#[no_mangle]
366pub unsafe extern "C" fn xmlAutomataNewCountedTrans(
367 am: XmlAutomataPtr,
368 from: XmlAutomataStatePtr,
369 to: XmlAutomataStatePtr,
370 counter: c_int,
371) -> XmlAutomataStatePtr {
372 if am.is_null() || from.is_null() || to.is_null() {
373 return ptr::null_mut();
374 }
375 unsafe {
376 (*from).transitions.push(AutomataTransition {
377 token: None,
378 min: 0,
379 max: 0,
380 to: Some(to),
381 once: false,
382 all: false,
383 epsilon: false,
384 counter,
385 data: ptr::null_mut(),
386 });
387 }
388 from
389}
390
391#[no_mangle]
395pub unsafe extern "C" fn xmlAutomataNewCounterTrans(
396 am: XmlAutomataPtr,
397 from: XmlAutomataStatePtr,
398 to: XmlAutomataStatePtr,
399 counter: c_int,
400) -> XmlAutomataStatePtr {
401 if am.is_null() || from.is_null() || to.is_null() {
402 return ptr::null_mut();
403 }
404 unsafe {
405 (*from).transitions.push(AutomataTransition {
406 token: None,
407 min: 0,
408 max: 0,
409 to: Some(to),
410 once: false,
411 all: false,
412 epsilon: false,
413 counter,
414 data: ptr::null_mut(),
415 });
416 }
417 from
418}
419
420#[no_mangle]
424pub unsafe extern "C" fn xmlAutomataNewCounter(
425 _am: XmlAutomataPtr,
426 _min: c_int,
427 _max: c_int,
428) -> c_int {
429 0
432}
433
434#[no_mangle]
441pub unsafe extern "C" fn xmlAutomataCompile(am: XmlAutomataPtr) -> c_int {
442 if am.is_null() {
443 return -1;
444 }
445 unsafe {
446 let mut pattern = Vec::new();
450 let init = match (*am).init_state {
451 Some(s) => s,
452 None => return 0, };
454
455 if build_pattern_from_automata(&*am, init, &mut pattern).is_err() {
458 (*am).error = -1;
459 return -1;
460 }
461
462 if pattern.is_empty() {
463 return 0;
464 }
465
466 pattern.push(0); let compiled = xmlRegexpCompile(pattern.as_ptr());
469 if compiled.is_null() {
470 (*am).error = -1;
471 return -1;
472 }
473
474 (*am).regexp = Some(Box::from_raw(compiled));
475 0
476 }
477}
478
479unsafe fn build_pattern_from_automata(
484 am: &XmlAutomata,
485 state: XmlAutomataStatePtr,
486 pattern: &mut Vec<u8>,
487) -> Result<(), ()> {
488 if state.is_null() {
489 return Ok(());
490 }
491
492 let transitions = &(*state).transitions;
493 if transitions.is_empty() {
494 return Ok(());
495 }
496
497 if transitions.len() == 1 {
498 let t = &transitions[0];
499 if t.epsilon {
500 if let Some(to) = t.to {
502 return build_pattern_from_automata(am, to, pattern);
503 }
504 } else if t.all {
505 pattern.push(b'.');
506 if let Some(to) = t.to {
507 return build_pattern_from_automata(am, to, pattern);
508 }
509 } else if let Some(tok) = t.token {
510 pattern.push(tok);
511 if let Some(to) = t.to {
512 return build_pattern_from_automata(am, to, pattern);
513 }
514 }
515 } else {
516 pattern.push(b'(');
518 for (i, t) in transitions.iter().enumerate() {
519 if i > 0 {
520 pattern.push(b'|');
521 }
522 if let Some(tok) = t.token {
523 pattern.push(tok);
524 } else if t.all {
525 pattern.push(b'.');
526 }
527 if let Some(to) = t.to {
528 if !(*to).transitions.is_empty() {
530 let mut sub = Vec::new();
532 let _ = build_pattern_from_automata(am, to, &mut sub);
533 pattern.extend(sub);
534 }
535 }
536 }
537 pattern.push(b')');
538 }
539
540 Ok(())
541}
542
543#[no_mangle]
547pub unsafe extern "C" fn xmlAutomataIsDeterministic(am: XmlAutomataPtr) -> c_int {
548 if am.is_null() {
549 return 0;
550 }
551 unsafe {
552 match &(*am).regexp {
553 Some(regexp) => xmlRegexpIsDeterministic(&**regexp as *const XmlRegexp),
554 None => 1, }
556 }
557}
558
559#[cfg(test)]
564mod tests {
565 use super::*;
566 use core::ptr;
567
568 #[test]
569 fn test_new_automata() {
570 unsafe {
571 let am = xmlNewAutomata();
572 assert!(!am.is_null());
573 xmlFreeAutomata(am);
574 }
575 }
576
577 #[test]
578 fn test_new_automata_null_safety() {
579 unsafe {
580 xmlFreeAutomata(ptr::null_mut());
581 assert!(xmlAutomataGetInitState(ptr::null_mut()).is_null());
582 assert_eq!(xmlAutomataCompile(ptr::null_mut()), -1);
583 }
584 }
585
586 #[test]
587 fn test_new_state() {
588 unsafe {
589 let am = xmlNewAutomata();
590 let state = xmlAutomataNewState(am);
591 assert!(!state.is_null());
592 let init = xmlAutomataGetInitState(am);
593 assert_eq!(init, state);
594 xmlFreeAutomata(am);
595 }
596 }
597
598 #[test]
599 fn test_epsilon_transition() {
600 unsafe {
601 let am = xmlNewAutomata();
602 let s1 = xmlAutomataNewState(am);
603 let s2 = xmlAutomataNewState(am);
604 let result = xmlAutomataNewEpsilon(am, s1, s2);
605 assert!(!result.is_null());
606 assert_eq!(result, s1);
607 xmlFreeAutomata(am);
608 }
609 }
610
611 #[test]
612 fn test_char_transition() {
613 unsafe {
614 let am = xmlNewAutomata();
615 let s1 = xmlAutomataNewState(am);
616 let s2 = xmlAutomataNewState(am);
617 let token = b"a\0".as_ptr() as *const core::ffi::c_char;
618 let result = xmlAutomataNewTransition(am, s1, s2, token, ptr::null_mut());
619 assert!(!result.is_null());
620 assert_eq!(result, s1);
621 xmlFreeAutomata(am);
622 }
623 }
624
625 #[test]
626 fn test_count_transition() {
627 unsafe {
628 let am = xmlNewAutomata();
629 let s1 = xmlAutomataNewState(am);
630 let s2 = xmlAutomataNewState(am);
631 let token = b"a\0".as_ptr() as *const core::ffi::c_char;
632 let result = xmlAutomataNewCountTrans(am, s1, s2, token, ptr::null_mut(), 1, 5);
633 assert!(!result.is_null());
634 xmlFreeAutomata(am);
635 }
636 }
637
638 #[test]
639 fn test_all_transition() {
640 unsafe {
641 let am = xmlNewAutomata();
642 let s1 = xmlAutomataNewState(am);
643 let s2 = xmlAutomataNewState(am);
644 let result = xmlAutomataNewAllTrans(am, s1, s2, 0);
645 assert!(!result.is_null());
646 xmlFreeAutomata(am);
647 }
648 }
649
650 #[test]
651 fn test_once_transition() {
652 unsafe {
653 let am = xmlNewAutomata();
654 let s1 = xmlAutomataNewState(am);
655 let s2 = xmlAutomataNewState(am);
656 let token = b"x\0".as_ptr() as *const core::ffi::c_char;
657 let result = xmlAutomataNewOnceTrans(am, s1, s2, token, ptr::null_mut(), 0, 1);
658 assert!(!result.is_null());
659 xmlFreeAutomata(am);
660 }
661 }
662
663 #[test]
664 fn test_counter_transition() {
665 unsafe {
666 let am = xmlNewAutomata();
667 let s1 = xmlAutomataNewState(am);
668 let s2 = xmlAutomataNewState(am);
669 let cid = xmlAutomataNewCounter(am, 0, 10);
670 let r1 = xmlAutomataNewCountedTrans(am, s1, s2, cid);
671 assert!(!r1.is_null());
672 let r2 = xmlAutomataNewCounterTrans(am, s2, s1, cid);
673 assert!(!r2.is_null());
674 xmlFreeAutomata(am);
675 }
676 }
677
678 #[test]
679 fn test_compile_empty() {
680 unsafe {
681 let am = xmlNewAutomata();
682 let result = xmlAutomataCompile(am);
683 assert_eq!(result, 0);
684 xmlFreeAutomata(am);
685 }
686 }
687
688 #[test]
689 fn test_set_final_state() {
690 unsafe {
691 let am = xmlNewAutomata();
692 let state = xmlAutomataNewState(am);
693 let result = xmlAutomataSetFinalState(am, state);
694 assert_eq!(result, 0);
695 xmlFreeAutomata(am);
696 }
697 }
698
699 #[test]
700 fn test_is_deterministic_not_compiled() {
701 unsafe {
702 let am = xmlNewAutomata();
703 assert_eq!(xmlAutomataIsDeterministic(am), 1);
705 xmlFreeAutomata(am);
706 }
707 }
708
709 #[test]
710 fn test_null_automata_returns_null_state() {
711 unsafe {
712 let state = xmlAutomataNewState(ptr::null_mut());
713 assert!(state.is_null());
714 }
715 }
716
717 #[test]
718 fn test_null_automata_returns_null_init() {
719 unsafe {
720 assert!(xmlAutomataGetInitState(ptr::null_mut()).is_null());
721 }
722 }
723
724 #[test]
725 fn test_new_state_adds_to_list() {
726 unsafe {
727 let am = xmlNewAutomata();
728 let s1 = xmlAutomataNewState(am);
729 let s2 = xmlAutomataNewState(am);
730 assert!(!s1.is_null());
731 assert!(!s2.is_null());
732 assert_ne!(s1, s2);
733 assert_eq!((*am).states.len(), 2);
734 xmlFreeAutomata(am);
735 }
736 }
737
738 #[test]
739 fn test_compile_simple_chain() {
740 unsafe {
741 let am = xmlNewAutomata();
742 let s1 = xmlAutomataNewState(am);
743 let s2 = xmlAutomataNewState(am);
744 let s3 = xmlAutomataNewState(am);
745 let token_a = b"a\0".as_ptr() as *const core::ffi::c_char;
746 let token_b = b"b\0".as_ptr() as *const core::ffi::c_char;
747 xmlAutomataNewTransition(am, s1, s2, token_a, ptr::null_mut());
748 xmlAutomataNewTransition(am, s2, s3, token_b, ptr::null_mut());
749 let result = xmlAutomataCompile(am);
750 assert_eq!(result, 0);
751 xmlFreeAutomata(am);
752 }
753 }
754}