use std::ffi::c_void;
use std::os::raw::{c_char, c_int};
use std::ptr;
use libxml_rs::abi::exports_xml2::*;
use libxml_rs::abi::structs::*;
use libxml_rs::abi::types::*;
const CATALOG_NS: &[u8] = b"urn:oasis:names:tc:entity:xmlns:xml:catalog\0";
const CATALOG_DTD_PUBLIC: &[u8] = b"-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN\0";
const CATALOG_DTD_SYSTEM: &[u8] =
b"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd\0";
struct Cli {
sgml: bool,
shell: bool,
create: bool,
noout: bool,
no_super_update: bool,
verbose: bool,
add: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)>, del: Vec<Vec<u8>>,
catalog_file: Option<String>,
entities: Vec<String>,
}
impl Default for Cli {
fn default() -> Self {
Cli {
sgml: false,
shell: false,
create: false,
noout: false,
no_super_update: false,
verbose: false,
add: Vec::new(),
del: Vec::new(),
catalog_file: None,
entities: Vec::new(),
}
}
}
fn usage() {
let argv0 = std::env::args()
.next()
.unwrap_or_else(|| "xmlcatalog".to_string());
println!("Usage : {} [options] catalogfile entities...", argv0);
println!("\tParse the catalog file (void specification possibly expressed as \"\"");
println!("\tappoints the default system one) and query it for the entities");
println!("\t--sgml : handle SGML Super catalogs for --add and --del");
println!("\t--shell : run a shell allowing interactive queries");
println!("\t--create : create a new catalog");
println!("\t--add 'type' 'orig' 'replace' : add an XML entry");
println!("\t--add 'entry' : add an SGML entry");
println!("\t--del 'values' : remove values");
println!("\t--noout: avoid dumping the result on stdout");
println!("\t used with --add or --del, it saves the catalog changes");
println!("\t and with --sgml it automatically updates the super catalog");
println!("\t--no-super-update: do not update the SGML super catalog");
println!("\t-v --verbose : provide debug information");
}
unsafe fn cstr_alloc(s: &[u8]) -> *mut c_char {
let p = libc::malloc(s.len() + 1) as *mut c_char;
if p.is_null() {
return p;
}
libc::memcpy(
p as *mut libc::c_void,
s.as_ptr() as *const libc::c_void,
s.len(),
);
*p.add(s.len()) = 0;
p
}
unsafe fn free_cstr(p: *mut c_char) {
if !p.is_null() {
libc::free(p as *mut libc::c_void);
}
}
unsafe fn build_catalog_doc() -> *mut _xmlDoc {
libxml_rs::xml::catalog::dump_doc()
}
unsafe fn dump_catalog() {
let fp = libc::fdopen(1, b"w\0".as_ptr() as *const c_char);
if !fp.is_null() {
xmlCatalogDump(fp as *mut c_void, ptr::null_mut());
libc::fflush(fp);
}
}
unsafe fn save_catalog(path: &str) {
let cpath = cstr_alloc(path.as_bytes());
xmlCatalogSave(cpath as *const c_char);
free_cstr(cpath);
}
unsafe fn load_catalog_file(path: &str) {
let cpath = cstr_alloc(path.as_bytes());
xmlCatalogLoad(cpath as *const c_char);
free_cstr(cpath);
}
unsafe fn shell_public(id: &str) {
let cid = cstr_alloc(id.as_bytes());
let res = xmlCatalogResolvePublic(cid as *const xmlChar);
if res.is_null() {
println!("No entry for PUBLIC {}", id);
} else {
let len = libc::strlen(res as *const c_char);
libc::write(1, res as *const c_void, len);
libc::write(1, b"\n".as_ptr() as *const c_void, 1);
libxml_rs::abi::allocator::xmlFree(res as *mut c_void);
}
free_cstr(cid);
}
unsafe fn shell_system(id: &str) {
let cid = cstr_alloc(id.as_bytes());
let res = xmlCatalogResolveSystem(cid as *const xmlChar);
if res.is_null() {
println!("No entry for SYSTEM {}", id);
} else {
let len = libc::strlen(res as *const c_char);
libc::write(1, res as *const c_void, len);
libc::write(1, b"\n".as_ptr() as *const c_void, 1);
libxml_rs::abi::allocator::xmlFree(res as *mut c_void);
}
free_cstr(cid);
}
unsafe fn shell_resolve(pub_id: &str, sys_id: &str) {
let cpub = cstr_alloc(pub_id.as_bytes());
let res = xmlCatalogResolvePublic(cpub as *const xmlChar);
free_cstr(cpub);
if res.is_null() {
let csys = cstr_alloc(sys_id.as_bytes());
let res2 = xmlCatalogResolveSystem(csys as *const xmlChar);
free_cstr(csys);
if res2.is_null() {
println!("Resolver failed to find an answer");
return;
}
let len = libc::strlen(res2 as *const c_char);
libc::write(1, res2 as *const c_void, len);
libc::write(1, b"\n".as_ptr() as *const c_void, 1);
libxml_rs::abi::allocator::xmlFree(res2 as *mut c_void);
return;
}
let len = libc::strlen(res as *const c_char);
libc::write(1, res as *const c_void, len);
libc::write(1, b"\n".as_ptr() as *const c_void, 1);
libxml_rs::abi::allocator::xmlFree(res as *mut c_void);
}
unsafe fn shell_add(args: &[&str]) {
let ret = if args.len() >= 3 {
let type_c = cstr_alloc(args[0].as_bytes());
let orig_c = cstr_alloc(args[1].as_bytes());
let repl_c = cstr_alloc(args[2].as_bytes());
let r = xmlCatalogAdd(
type_c as *const xmlChar,
orig_c as *const xmlChar,
repl_c as *const xmlChar,
);
free_cstr(type_c);
free_cstr(orig_c);
free_cstr(repl_c);
r
} else if args.len() == 2 {
let type_c = cstr_alloc(args[0].as_bytes());
let repl_c = cstr_alloc(args[1].as_bytes());
let r = xmlCatalogAdd(
type_c as *const xmlChar,
ptr::null(),
repl_c as *const xmlChar,
);
free_cstr(type_c);
free_cstr(repl_c);
r
} else {
-1
};
if ret != 0 {
println!("add command failed");
}
}
unsafe fn shell_del(value: &str) {
let cval = cstr_alloc(value.as_bytes());
xmlCatalogRemove(cval as *const xmlChar);
free_cstr(cval);
println!("del command failed");
}
unsafe fn shell_loop() {
use std::io::Write;
loop {
print!("> ");
std::io::stdout().flush().ok();
let mut line = String::new();
if std::io::stdin().read_line(&mut line).unwrap_or(0) == 0 {
break;
}
let trimmed = line.trim().to_string();
if trimmed.is_empty() {
continue;
}
let parts: Vec<&str> = trimmed.split_whitespace().collect();
match parts[0] {
"quit" | "exit" | "bye" | "q" => break,
"public" => {
if parts.len() < 2 {
println!("public requires 1 arguments");
} else {
shell_public(parts[1]);
}
}
"system" => {
if parts.len() < 2 {
println!("system requires 1 arguments");
} else {
shell_system(parts[1]);
}
}
"resolve" => {
if parts.len() < 3 {
println!("resolve requires 2 arguments");
} else {
shell_resolve(parts[1], parts[2]);
}
}
"add" => {
if parts.len() < 3 {
println!("add requires 2 or 3 arguments");
} else {
shell_add(&parts[1..]);
}
}
"del" => {
if parts.len() < 2 {
println!("del requires 1");
} else {
shell_del(parts[1]);
}
}
"dump" => {
if parts.len() != 1 {
println!("dump has no arguments");
} else {
dump_catalog();
}
}
"debug" => {
if parts.len() != 1 {
println!("debug has no arguments");
} else {
}
}
"quiet" => {
if parts.len() != 1 {
println!("quiet has no arguments");
} else {
}
}
_ => {
if parts[0] != "help" {
println!("Unrecognized command {}", parts[0]);
}
println!("Commands available:");
println!("\tpublic PublicID: make a PUBLIC identifier lookup");
println!("\tsystem SystemID: make a SYSTEM identifier lookup");
println!("\tresolve PublicID SystemID: do a full resolver lookup");
println!("\tadd 'type' 'orig' 'replace' : add an entry");
println!("\tdel 'values' : remove values");
println!("\tdump: print the current catalog state");
println!("\tdebug: increase the verbosity level");
println!("\tquiet: decrease the verbosity level");
println!("\texit: quit the shell");
}
}
}
}
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let mut cli = Cli::default();
let mut positionals: Vec<String> = Vec::new();
let mut i = 0;
while i < args.len() {
let arg = args[i].as_str();
match arg {
"--sgml" => cli.sgml = true,
"--shell" => cli.shell = true,
"--create" => cli.create = true,
"--noout" => cli.noout = true,
"--no-super-update" => cli.no_super_update = true,
"-v" | "--verbose" => cli.verbose = true,
"--add" => {
let mut vals: Vec<String> = Vec::new();
let mut j = i + 1;
while j < args.len() && vals.len() < 3 {
vals.push(args[j].clone());
j += 1;
}
if vals.is_empty() {
usage();
std::process::exit(1);
}
if vals.len() < 3 {
eprintln!("--add requires 'type' 'orig' 'replace'");
std::process::exit(1);
}
cli.add.push((
vals[0].as_bytes().to_vec(),
vals[1].as_bytes().to_vec(),
vals[2].as_bytes().to_vec(),
));
i += 3;
}
"--del" => {
if i + 1 >= args.len() {
usage();
std::process::exit(1);
}
cli.del.push(args[i + 1].as_bytes().to_vec());
i += 1;
}
_ => {
if arg.starts_with('-') && arg.len() > 1 {
eprintln!("Unknown option {}", arg);
usage();
std::process::exit(1);
}
positionals.push(arg.to_string());
for a in &args[i + 1..] {
positionals.push(a.clone());
}
break;
}
}
i += 1;
}
if positionals.is_empty() {
usage();
std::process::exit(1);
}
let catalog_file = positionals[0].clone();
cli.entities = positionals[1..].to_vec();
unsafe {
if !cli.create && !catalog_file.is_empty() {
load_catalog_file(&catalog_file);
}
let mut modified = false;
let mut exit_value: c_int = 0;
for (t, o, r) in &cli.add {
let type_c = cstr_alloc(t);
let orig_c = cstr_alloc(o);
let repl_c = cstr_alloc(r);
let ret = xmlCatalogAdd(
type_c as *const xmlChar,
orig_c as *const xmlChar,
repl_c as *const xmlChar,
);
free_cstr(type_c);
free_cstr(orig_c);
free_cstr(repl_c);
if ret != 0 {
println!("add command failed");
exit_value = 3;
} else {
modified = true;
}
}
for v in &cli.del {
let val_c = cstr_alloc(v);
let ret = xmlCatalogRemove(val_c as *const xmlChar);
free_cstr(val_c);
if ret < 0 {
eprintln!("Failed to remove entry {}", String::from_utf8_lossy(v));
exit_value = 1;
} else {
modified = true;
}
}
if cli.shell {
shell_loop();
} else if !cli.entities.is_empty() && !modified && !cli.create {
for id in &cli.entities {
let cid = cstr_alloc(id.as_bytes());
let uri = xmlParseURI(cid as *const c_char);
let has_space = id.bytes().any(|b| b.is_ascii_whitespace());
let is_uri = !uri.is_null() && !has_space;
if !uri.is_null() {
xmlFreeURI(uri);
}
if !is_uri {
let res = xmlCatalogResolvePublic(cid as *const xmlChar);
if res.is_null() {
println!("No entry for PUBLIC {}", id);
exit_value = 4;
} else {
let len = libc::strlen(res as *const c_char);
libc::write(1, res as *const c_void, len);
libc::write(1, b"\n".as_ptr() as *const c_void, 1);
libxml_rs::abi::allocator::xmlFree(res as *mut c_void);
}
} else {
let res = xmlCatalogResolveSystem(cid as *const xmlChar);
if res.is_null() {
println!("No entry for SYSTEM {}", id);
let res2 = xmlCatalogResolveURI(cid as *const xmlChar);
if res2.is_null() {
println!("No entry for URI {}", id);
exit_value = 4;
} else {
let len = libc::strlen(res2 as *const c_char);
libc::write(1, res2 as *const c_void, len);
libc::write(1, b"\n".as_ptr() as *const c_void, 1);
libxml_rs::abi::allocator::xmlFree(res2 as *mut c_void);
}
} else {
let len = libc::strlen(res as *const c_char);
libc::write(1, res as *const c_void, len);
libc::write(1, b"\n".as_ptr() as *const c_void, 1);
libxml_rs::abi::allocator::xmlFree(res as *mut c_void);
}
}
free_cstr(cid);
}
} else if modified || cli.create {
if cli.noout {
save_catalog(&catalog_file);
} else {
dump_catalog();
}
}
std::process::exit(exit_value);
}
}