1#![allow(clippy::items_after_test_module)]
2
3use super::*;
4use crate::plugins::registry::PluginRegistry;
5use std::fs;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9pub struct PluginLoader {
11 registry: Arc<PluginRegistry>,
12 search_paths: Vec<PathBuf>,
13 loaded_plugins: std::collections::HashMap<String, LoadedPlugin>,
14}
15
16#[derive(Clone)]
18pub struct LoadedPlugin {
19 pub name: String,
20 pub path: Option<PathBuf>,
21 pub metadata: PluginMetadata,
22 pub load_time: chrono::DateTime<chrono::Utc>,
23 #[allow(dead_code)]
24 library: Option<std::sync::Arc<LibraryHandle>>,
25}
26
27#[derive(Debug, Clone)]
29pub struct LoadConfig {
30 pub auto_resolve_dependencies: bool,
31 pub allow_version_conflicts: bool,
32 pub validate_signatures: bool,
33 pub sandbox_mode: bool,
34}
35
36impl Default for LoadConfig {
37 fn default() -> Self {
38 Self {
39 auto_resolve_dependencies: true,
40 allow_version_conflicts: false,
41 validate_signatures: false,
42 sandbox_mode: true,
43 }
44 }
45}
46
47impl PluginLoader {
48 pub fn new(registry: Arc<PluginRegistry>) -> Self {
49 Self {
50 registry,
51 search_paths: Vec::new(),
52 loaded_plugins: std::collections::HashMap::new(),
53 }
54 }
55
56 pub fn add_search_path<P: AsRef<Path>>(&mut self, path: P) {
58 self.search_paths.push(path.as_ref().to_path_buf());
59 }
60
61 pub fn load_plugin(&mut self, plugin: Arc<dyn AstPlugin>) -> PluginResult {
63 let metadata = plugin.metadata().clone();
64 let name = metadata.name.clone();
65
66 match self.registry.register(plugin) {
68 PluginResult::Success => {
69 let loaded_plugin = LoadedPlugin {
71 name: name.clone(),
72 path: None,
73 metadata,
74 load_time: chrono::Utc::now(),
75 library: None,
76 };
77 self.loaded_plugins.insert(name, loaded_plugin);
78 PluginResult::Success
79 }
80 result => result,
81 }
82 }
83
84 pub fn load_from_config(
86 &mut self,
87 config: &serde_json::Value,
88 load_config: &LoadConfig,
89 ) -> PluginResult {
90 let plugin_name = match config.get("name").and_then(|v| v.as_str()) {
92 Some(name) => name,
93 None => return PluginResult::Error("Missing plugin name in config".to_string()),
94 };
95
96 let plugin_type = config
97 .get("type")
98 .and_then(|v| v.as_str())
99 .unwrap_or("dynamic");
100
101 match plugin_type {
102 "builtin" => self.load_builtin_plugin(plugin_name, config, load_config),
103 "dynamic" => self.load_dynamic_plugin(plugin_name, config, load_config),
104 _ => PluginResult::Error(format!("Unknown plugin type: {}", plugin_type)),
105 }
106 }
107
108 pub fn load_plugins(
110 &mut self,
111 configs: &[serde_json::Value],
112 load_config: &LoadConfig,
113 ) -> Vec<PluginResult> {
114 let mut results = Vec::new();
115
116 if load_config.auto_resolve_dependencies {
117 let plugin_names: Vec<String> = configs
119 .iter()
120 .filter_map(|config| {
121 config
122 .get("name")
123 .and_then(|v| v.as_str())
124 .map(|s| s.to_string())
125 })
126 .collect();
127
128 match self.registry.get_dependency_order(&plugin_names) {
129 Ok(ordered_names) => {
130 for name in ordered_names {
132 if let Some(config) = configs.iter().find(|c| {
133 c.get("name")
134 .and_then(|v| v.as_str())
135 .map(|s| s == name)
136 .unwrap_or(false)
137 }) {
138 let result = self.load_from_config(config, load_config);
139 results.push(result);
140 }
141 }
142 }
143 Err(err) => {
144 results.push(PluginResult::Error(err));
145 }
146 }
147 } else {
148 for config in configs {
150 let result = self.load_from_config(config, load_config);
151 results.push(result);
152 }
153 }
154
155 results
156 }
157
158 pub fn discover_plugins(&self) -> Vec<PathBuf> {
160 let mut discovered = Vec::new();
161
162 for search_path in &self.search_paths {
163 if let Ok(entries) = fs::read_dir(search_path) {
164 for entry in entries.flatten() {
165 let path = entry.path();
166
167 if path.extension().and_then(|s| s.to_str()) == Some("json") {
169 discovered.push(path);
171 } else if path.extension().and_then(|s| s.to_str()) == Some("so")
172 || path.extension().and_then(|s| s.to_str()) == Some("dll")
173 || path.extension().and_then(|s| s.to_str()) == Some("dylib")
174 {
175 discovered.push(path);
177 }
178 }
179 }
180 }
181
182 discovered
183 }
184
185 pub fn unload_plugin(&mut self, name: &str) -> PluginResult {
187 let registry_result = self.registry.unregister(name);
189
190 self.loaded_plugins.remove(name);
192
193 registry_result
194 }
195
196 pub fn get_loaded_plugins(&self) -> Vec<LoadedPlugin> {
198 self.loaded_plugins.values().cloned().collect()
199 }
200
201 pub fn is_loaded(&self, name: &str) -> bool {
203 self.loaded_plugins.contains_key(name)
204 }
205
206 pub fn reload_plugin(&mut self, name: &str, load_config: &LoadConfig) -> PluginResult {
208 let loaded_plugin = self.loaded_plugins.get(name).cloned();
210
211 if let Some(plugin_info) = loaded_plugin {
212 let unload_result = self.unload_plugin(name);
214 if let PluginResult::Error(msg) = unload_result {
215 return PluginResult::Error(msg);
216 }
217
218 if let Some(path) = plugin_info.path {
220 self.load_from_path(&path, load_config)
222 } else {
223 PluginResult::Error("Cannot reload in-memory plugin without source".to_string())
224 }
225 } else {
226 PluginResult::Error(format!("Plugin '{}' is not loaded", name))
227 }
228 }
229
230 pub fn load_from_path(&mut self, path: &Path, load_config: &LoadConfig) -> PluginResult {
232 match path.extension().and_then(|s| s.to_str()) {
233 Some("json") => self.load_from_manifest(path, load_config),
234 Some("so") | Some("dll") | Some("dylib") => {
235 self.load_dynamic_library(path, load_config)
236 }
237 _ => PluginResult::Error(format!("Unsupported plugin file: {:?}", path)),
238 }
239 }
240
241 fn load_from_manifest(&mut self, path: &Path, load_config: &LoadConfig) -> PluginResult {
243 match fs::read_to_string(path) {
244 Ok(content) => match serde_json::from_str::<serde_json::Value>(&content) {
245 Ok(config) => self.load_from_config(&config, load_config),
246 Err(err) => PluginResult::Error(format!("Invalid plugin manifest: {}", err)),
247 },
248 Err(err) => PluginResult::Error(format!("Failed to read manifest: {}", err)),
249 }
250 }
251
252 fn load_builtin_plugin(
254 &mut self,
255 name: &str,
256 config: &serde_json::Value,
257 _load_config: &LoadConfig,
258 ) -> PluginResult {
259 let plugin: Arc<dyn AstPlugin> = match name {
261 "basic_validator" => Arc::new(BasicValidatorPlugin::new()),
262 "basic_transformer" => Arc::new(BasicTransformerPlugin::new()),
263 "structure_analyzer" => Arc::new(StructureAnalyzerPlugin::new()),
264 "security_scanner" => Arc::new(SecurityScannerPlugin::new()),
265 "metadata_extractor" => Arc::new(MetadataExtractorPlugin::new()),
266 _ => return PluginResult::Error(format!("Unknown built-in plugin: {}", name)),
267 };
268
269 if let Some(params) = config.get("parameters") {
271 let _ = params;
273 }
274
275 self.load_plugin(plugin)
276 }
277
278 fn load_dynamic_plugin(
280 &mut self,
281 name: &str,
282 config: &serde_json::Value,
283 load_config: &LoadConfig,
284 ) -> PluginResult {
285 let lib_path = match config.get("path").and_then(|v| v.as_str()) {
287 Some(path) => PathBuf::from(path),
288 None => {
289 if let Some(path) = self.find_plugin_library(name) {
291 path
292 } else {
293 return PluginResult::Error(format!(
294 "Cannot find library for plugin: {}",
295 name
296 ));
297 }
298 }
299 };
300
301 self.load_dynamic_library(&lib_path, load_config)
302 }
303
304 fn load_dynamic_library(&mut self, path: &Path, load_config: &LoadConfig) -> PluginResult {
306 if !path.exists() {
308 return PluginResult::Error(format!("Library not found: {:?}", path));
309 }
310
311 if load_config.validate_signatures && !self.validate_library_signature(path) {
313 return PluginResult::Error(format!("Invalid library signature: {:?}", path));
314 }
315
316 match DynamicLibraryLoader::load(path, load_config.sandbox_mode) {
318 Ok(library) => {
319 let library_handle = library.handle();
320 match library.get_plugin_factory() {
322 Ok(plugin) => {
323 let metadata = plugin.metadata().clone();
325 let name = metadata.name.clone();
326
327 let loaded_plugin = LoadedPlugin {
329 name: name.clone(),
330 path: Some(path.to_path_buf()),
331 metadata,
332 load_time: chrono::Utc::now(),
333 library: Some(library_handle),
334 };
335
336 self.loaded_plugins.insert(name, loaded_plugin);
338 self.load_plugin(plugin)
339 }
340 Err(err) => {
341 PluginResult::Error(format!("Failed to get plugin factory: {}", err))
342 }
343 }
344 }
345 Err(err) => PluginResult::Error(format!("Failed to load library: {}", err)),
346 }
347 }
348
349 fn find_plugin_library(&self, name: &str) -> Option<PathBuf> {
351 let lib_extensions = if cfg!(target_os = "windows") {
352 vec!["dll"]
353 } else if cfg!(target_os = "macos") {
354 vec!["dylib"]
355 } else {
356 vec!["so"]
357 };
358
359 for search_path in &self.search_paths {
360 for ext in &lib_extensions {
361 let lib_name = format!("lib{}.{}", name, ext);
362 let lib_path = search_path.join(&lib_name);
363 if lib_path.exists() {
364 return Some(lib_path);
365 }
366
367 let lib_name = format!("{}.{}", name, ext);
369 let lib_path = search_path.join(&lib_name);
370 if lib_path.exists() {
371 return Some(lib_path);
372 }
373 }
374 }
375
376 None
377 }
378
379 fn validate_library_signature(&self, path: &Path) -> bool {
381 path.exists() && path.is_file()
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn test_load_missing_dynamic_library() {
393 let registry = Arc::new(PluginRegistry::new());
394 let mut loader = PluginLoader::new(registry);
395 let load_config = LoadConfig::default();
396 let path = Path::new("nonexistent_plugin.so");
397 let result = loader.load_from_path(path, &load_config);
398 assert!(matches!(result, PluginResult::Error(_)));
399 }
400}
401
402pub struct BuiltinPlugins;
404
405impl BuiltinPlugins {
406 pub fn create_basic_validator() -> Box<dyn AstPlugin> {
408 Box::new(BasicValidatorPlugin::new())
409 }
410
411 pub fn create_basic_transformer() -> Box<dyn AstPlugin> {
413 Box::new(BasicTransformerPlugin::new())
414 }
415
416 pub fn list_available() -> Vec<String> {
418 vec![
419 "basic_validator".to_string(),
420 "basic_transformer".to_string(),
421 ]
422 }
423}
424
425pub struct BasicValidatorPlugin {
427 metadata: PluginMetadata,
428}
429
430impl Default for BasicValidatorPlugin {
431 fn default() -> Self {
432 Self::new()
433 }
434}
435
436impl BasicValidatorPlugin {
437 pub fn new() -> Self {
438 Self {
439 metadata: PluginMetadata::new(
440 "basic_validator",
441 "1.0.0",
442 "Basic document validation plugin",
443 "PDF-AST",
444 )
445 .with_tags(vec!["validation", "builtin"])
446 .with_supported_types(vec![NodeType::Catalog, NodeType::Pages]),
447 }
448 }
449}
450
451impl AstPlugin for BasicValidatorPlugin {
452 fn metadata(&self) -> &PluginMetadata {
453 &self.metadata
454 }
455
456 fn process_document(
457 &self,
458 document: &mut crate::ast::PdfDocument,
459 _context: &mut PluginContext,
460 ) -> PluginResult {
461 if document.ast.get_root().is_none() {
463 PluginResult::Error("Document missing root catalog".to_string())
464 } else {
465 PluginResult::Success
466 }
467 }
468
469 fn clone_plugin(&self) -> Box<dyn AstPlugin> {
470 Box::new(Self::new())
471 }
472}
473
474pub struct BasicTransformerPlugin {
476 metadata: PluginMetadata,
477}
478
479impl Default for BasicTransformerPlugin {
480 fn default() -> Self {
481 Self::new()
482 }
483}
484
485impl BasicTransformerPlugin {
486 pub fn new() -> Self {
487 Self {
488 metadata: PluginMetadata::new(
489 "basic_transformer",
490 "1.0.0",
491 "Basic document transformation plugin",
492 "PDF-AST",
493 )
494 .with_tags(vec!["transformation", "builtin"]),
495 }
496 }
497}
498
499impl AstPlugin for BasicTransformerPlugin {
500 fn metadata(&self) -> &PluginMetadata {
501 &self.metadata
502 }
503
504 fn capabilities(&self) -> PluginCapabilities {
505 PluginCapabilities {
506 can_modify_nodes: true,
507 can_add_nodes: false,
508 can_remove_nodes: false,
509 can_validate: false,
510 can_transform: true,
511 requires_document_context: false,
512 thread_safe: true,
513 }
514 }
515
516 fn clone_plugin(&self) -> Box<dyn AstPlugin> {
517 Box::new(Self::new())
518 }
519}
520
521pub struct StructureAnalyzerPlugin {
523 metadata: PluginMetadata,
524}
525
526impl Default for StructureAnalyzerPlugin {
527 fn default() -> Self {
528 Self::new()
529 }
530}
531
532impl StructureAnalyzerPlugin {
533 pub fn new() -> Self {
534 Self {
535 metadata: PluginMetadata::new(
536 "structure_analyzer",
537 "1.0.0",
538 "Analyzes document structure and complexity",
539 "PDF-AST",
540 )
541 .with_tags(vec!["analysis", "builtin"]),
542 }
543 }
544}
545
546impl AstPlugin for StructureAnalyzerPlugin {
547 fn metadata(&self) -> &PluginMetadata {
548 &self.metadata
549 }
550
551 fn process_document(
552 &self,
553 document: &mut crate::ast::PdfDocument,
554 context: &mut PluginContext,
555 ) -> PluginResult {
556 let node_count = document.ast.get_all_nodes().len();
558 let depth = document.ast.get_max_depth();
559
560 context.set_data("node_count", node_count.to_string());
562 context.set_data("max_depth", depth.to_string());
563
564 if node_count > 10000 {
566 context.add_warning("Document has high complexity (>10000 nodes)");
567 }
568
569 if depth > 50 {
570 context.add_warning("Document has deep nesting (>50 levels)");
571 }
572
573 PluginResult::Success
574 }
575
576 fn clone_plugin(&self) -> Box<dyn AstPlugin> {
577 Box::new(Self::new())
578 }
579}
580
581pub struct SecurityScannerPlugin {
583 metadata: PluginMetadata,
584}
585
586impl Default for SecurityScannerPlugin {
587 fn default() -> Self {
588 Self::new()
589 }
590}
591
592impl SecurityScannerPlugin {
593 pub fn new() -> Self {
594 Self {
595 metadata: PluginMetadata::new(
596 "security_scanner",
597 "1.0.0",
598 "Scans for security issues and suspicious patterns",
599 "PDF-AST",
600 )
601 .with_tags(vec!["security", "builtin"])
602 .with_supported_types(vec![
603 NodeType::JavaScriptAction,
604 NodeType::LaunchAction,
605 NodeType::URIAction,
606 NodeType::EmbeddedFile,
607 ]),
608 }
609 }
610}
611
612impl AstPlugin for SecurityScannerPlugin {
613 fn metadata(&self) -> &PluginMetadata {
614 &self.metadata
615 }
616
617 fn process_node(
618 &self,
619 node: &mut crate::ast::AstNode,
620 context: &mut PluginContext,
621 ) -> PluginResult {
622 match node.node_type {
623 NodeType::JavaScriptAction | NodeType::EmbeddedJS => {
624 context.add_warning(&format!("JavaScript detected at node {:?}", node.id));
625 }
626 NodeType::LaunchAction => {
627 context.add_warning(&format!("Launch action detected at node {:?}", node.id));
628 }
629 NodeType::URIAction => {
630 if let Some(uri) = node.metadata.get_property("URI") {
631 if uri.starts_with("http://") {
632 context.add_warning(&format!("Insecure HTTP URI at node {:?}", node.id));
633 }
634 }
635 }
636 NodeType::EmbeddedFile => {
637 context.add_info(format!("Embedded file detected at node {:?}", node.id));
638 }
639 _ => {}
640 }
641
642 PluginResult::Success
643 }
644
645 fn clone_plugin(&self) -> Box<dyn AstPlugin> {
646 Box::new(Self::new())
647 }
648}
649
650pub struct MetadataExtractorPlugin {
652 metadata: PluginMetadata,
653}
654
655impl Default for MetadataExtractorPlugin {
656 fn default() -> Self {
657 Self::new()
658 }
659}
660
661impl MetadataExtractorPlugin {
662 pub fn new() -> Self {
663 Self {
664 metadata: PluginMetadata::new(
665 "metadata_extractor",
666 "1.0.0",
667 "Extracts and consolidates document metadata",
668 "PDF-AST",
669 )
670 .with_tags(vec!["metadata", "builtin"])
671 .with_supported_types(vec![NodeType::Metadata, NodeType::Catalog]),
672 }
673 }
674}
675
676impl AstPlugin for MetadataExtractorPlugin {
677 fn metadata(&self) -> &PluginMetadata {
678 &self.metadata
679 }
680
681 fn process_document(
682 &self,
683 document: &mut crate::ast::PdfDocument,
684 context: &mut PluginContext,
685 ) -> PluginResult {
686 context.set_data("title", document.metadata.title.clone().unwrap_or_default());
688 context.set_data(
689 "author",
690 document.metadata.author.clone().unwrap_or_default(),
691 );
692 context.set_data(
693 "subject",
694 document.metadata.subject.clone().unwrap_or_default(),
695 );
696 context.set_data(
697 "creator",
698 document.metadata.creator.clone().unwrap_or_default(),
699 );
700 context.set_data(
701 "producer",
702 document.metadata.producer.clone().unwrap_or_default(),
703 );
704
705 if let Some(created) = &document.metadata.creation_date {
706 context.set_data("creation_date", created.to_string());
707 }
708
709 if let Some(modified) = &document.metadata.modification_date {
710 context.set_data("modification_date", modified.to_string());
711 }
712
713 context.set_data("page_count", document.metadata.page_count.to_string());
714 context.set_data("encrypted", document.metadata.encrypted.to_string());
715
716 PluginResult::Success
717 }
718
719 fn clone_plugin(&self) -> Box<dyn AstPlugin> {
720 Box::new(Self::new())
721 }
722}
723
724struct DynamicLibraryLoader {
726 handle: std::sync::Arc<LibraryHandle>,
727}
728
729impl DynamicLibraryLoader {
730 fn load(path: &Path, sandbox: bool) -> Result<Self, String> {
732 let handle = LibraryHandle::load(path, sandbox)?;
734 Ok(Self {
735 handle: std::sync::Arc::new(handle),
736 })
737 }
738
739 fn get_plugin_factory(&self) -> Result<Arc<dyn AstPlugin>, String> {
741 self.handle.get_plugin_factory()
742 }
743
744 fn handle(&self) -> std::sync::Arc<LibraryHandle> {
745 std::sync::Arc::clone(&self.handle)
746 }
747}
748
749#[allow(dead_code)]
751enum LibraryHandle {
752 #[cfg(unix)]
753 Unix(UnixLibrary),
754 #[cfg(windows)]
755 Windows(WindowsLibrary),
756 Stub,
757}
758
759unsafe impl Send for LibraryHandle {}
762unsafe impl Sync for LibraryHandle {}
763
764impl LibraryHandle {
765 fn load(path: &Path, sandbox: bool) -> Result<Self, String> {
766 if !path.exists() {
768 return Err(format!("Library file not found: {:?}", path));
769 }
770
771 #[cfg(unix)]
772 {
773 UnixLibrary::load(path, sandbox).map(LibraryHandle::Unix)
774 }
775
776 #[cfg(windows)]
777 {
778 WindowsLibrary::load(path, sandbox).map(LibraryHandle::Windows)
779 }
780
781 #[cfg(not(any(unix, windows)))]
782 {
783 let _ = (path, sandbox);
784 Ok(LibraryHandle::Stub)
785 }
786 }
787
788 fn get_plugin_factory(&self) -> Result<Arc<dyn AstPlugin>, String> {
789 match self {
790 #[cfg(unix)]
791 LibraryHandle::Unix(lib) => lib.get_plugin_factory(),
792 #[cfg(windows)]
793 LibraryHandle::Windows(lib) => lib.get_plugin_factory(),
794 LibraryHandle::Stub => {
795 Err("Dynamic loading not supported on this platform".to_string())
796 }
797 }
798 }
799}
800
801#[cfg(unix)]
802struct UnixLibrary {
803 _handle: *mut std::ffi::c_void,
804}
805
806#[cfg(unix)]
807impl UnixLibrary {
808 fn load(path: &Path, sandbox: bool) -> Result<Self, String> {
809 use std::ffi::CString;
810
811 let path_str = path.to_str().ok_or("Invalid path")?;
813 let c_path = CString::new(path_str).map_err(|e| e.to_string())?;
814
815 let flags = if sandbox {
817 0x0002 | 0x0100 } else {
819 0x0002 };
821
822 unsafe {
823 let handle = libc::dlopen(c_path.as_ptr(), flags);
824 if handle.is_null() {
825 let error = std::ffi::CStr::from_ptr(libc::dlerror());
826 return Err(format!("Failed to load library: {:?}", error));
827 }
828
829 Ok(Self { _handle: handle })
830 }
831 }
832
833 fn get_plugin_factory(&self) -> Result<Arc<dyn AstPlugin>, String> {
834 use std::ffi::{CStr, CString};
835
836 type PluginFactory = unsafe extern "C" fn() -> *mut std::ffi::c_void;
837
838 let symbol_name = CString::new("pdf_ast_plugin_factory")
839 .map_err(|e| format!("Invalid symbol name: {}", e))?;
840
841 unsafe {
842 let symbol = libc::dlsym(self._handle, symbol_name.as_ptr());
843 if symbol.is_null() {
844 let error = libc::dlerror();
845 let message = if error.is_null() {
846 "Failed to resolve symbol".to_string()
847 } else {
848 CStr::from_ptr(error).to_string_lossy().into_owned()
849 };
850 return Err(format!("Failed to resolve plugin factory: {}", message));
851 }
852
853 let factory: PluginFactory = std::mem::transmute(symbol);
854 let plugin_ptr = factory();
855 if plugin_ptr.is_null() {
856 return Err("Plugin factory returned null".to_string());
857 }
858 let boxed_plugin = Box::from_raw(plugin_ptr as *mut Box<dyn AstPlugin>);
859 Ok(Arc::from(*boxed_plugin))
860 }
861 }
862}
863
864#[cfg(unix)]
865impl Drop for UnixLibrary {
866 fn drop(&mut self) {
867 unsafe {
868 libc::dlclose(self._handle);
869 }
870 }
871}
872
873#[cfg(windows)]
874struct WindowsLibrary {
875 _handle: *mut std::ffi::c_void,
876}
877
878#[cfg(windows)]
879impl WindowsLibrary {
880 fn load(path: &Path, _sandbox: bool) -> Result<Self, String> {
881 use std::ffi::OsStr;
882 use std::os::windows::ffi::OsStrExt;
883
884 let wide_path: Vec<u16> = OsStr::new(path.as_os_str())
886 .encode_wide()
887 .chain(Some(0))
888 .collect();
889
890 unsafe {
891 let handle = LoadLibraryW(wide_path.as_ptr());
892 if handle.is_null() {
893 return Err("Failed to load library".to_string());
894 }
895
896 Ok(Self { _handle: handle })
897 }
898 }
899
900 fn get_plugin_factory(&self) -> Result<Arc<dyn AstPlugin>, String> {
901 use std::ffi::CString;
902
903 type PluginFactory = unsafe extern "C" fn() -> *mut std::ffi::c_void;
904
905 let symbol_name = CString::new("pdf_ast_plugin_factory")
906 .map_err(|e| format!("Invalid symbol name: {}", e))?;
907
908 unsafe {
909 let symbol = GetProcAddress(self._handle, symbol_name.as_ptr());
910 if symbol.is_null() {
911 return Err("Failed to resolve plugin factory".to_string());
912 }
913 let factory: PluginFactory = std::mem::transmute(symbol);
914 let plugin_ptr = factory();
915 if plugin_ptr.is_null() {
916 return Err("Plugin factory returned null".to_string());
917 }
918 let boxed_plugin = Box::from_raw(plugin_ptr as *mut Box<dyn AstPlugin>);
919 Ok(Arc::from(*boxed_plugin))
920 }
921 }
922}
923
924#[cfg(windows)]
925impl Drop for WindowsLibrary {
926 fn drop(&mut self) {
927 unsafe {
928 FreeLibrary(self._handle);
929 }
930 }
931}
932
933#[cfg(unix)]
935#[allow(dead_code)]
936extern "C" {
937 fn dlopen(filename: *const std::ffi::c_char, flag: std::ffi::c_int) -> *mut std::ffi::c_void;
938 fn dlerror() -> *const std::ffi::c_char;
939}
940
941#[cfg(windows)]
943extern "system" {
944 fn LoadLibraryW(lpFileName: *const u16) -> *mut std::ffi::c_void;
945 fn GetProcAddress(
946 hModule: *mut std::ffi::c_void,
947 lpProcName: *const i8,
948 ) -> *mut std::ffi::c_void;
949 fn FreeLibrary(hModule: *mut std::ffi::c_void) -> i32;
950}