mirror of https://github.com/dnomd343/ClearDNS
Dnomd343
2 years ago
42 changed files with 2042 additions and 350 deletions
@ -1,7 +1,9 @@ |
|||||
/bin/ |
/bin/ |
||||
/build/ |
|
||||
/.idea/ |
/.idea/ |
||||
/assets/*.txt |
/src/target/ |
||||
|
/cmake-build/ |
||||
/cmake-build-debug/ |
/cmake-build-debug/ |
||||
/cmake-build-release/ |
/cmake-build-release/ |
||||
/src/to-json/target/ |
|
||||
|
/assets/*.txt |
||||
|
/include/constant.h |
||||
|
@ -1,6 +1,41 @@ |
|||||
cmake_minimum_required(VERSION 2.8.12) |
cmake_minimum_required(VERSION 2.8.12) |
||||
project(cleardns) |
|
||||
|
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) |
project(cleardns LANGUAGES C) |
||||
|
|
||||
|
set(CMAKE_C_STANDARD 99) |
||||
|
|
||||
|
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/) |
||||
|
|
||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror") |
||||
|
|
||||
|
############################################################### |
||||
|
|
||||
|
if(NOT CMAKE_BUILD_TYPE) |
||||
|
set(CMAKE_BUILD_TYPE Release) |
||||
|
endif() |
||||
|
|
||||
|
############################################################### |
||||
|
|
||||
|
macro(git_tag _tag) |
||||
|
find_package(Git QUIET) |
||||
|
if (GIT_FOUND) |
||||
|
execute_process( |
||||
|
COMMAND ${GIT_EXECUTABLE} describe --tags |
||||
|
OUTPUT_VARIABLE ${_tag} |
||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE |
||||
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} |
||||
|
) |
||||
|
endif() |
||||
|
endmacro() |
||||
|
|
||||
|
set(VERSION "") |
||||
|
git_tag(VERSION) |
||||
|
if(VERSION STREQUAL "") # without git tag |
||||
|
message(FATAL_ERROR "Git command not found") |
||||
|
endif() |
||||
|
|
||||
|
############################################################### |
||||
|
|
||||
add_subdirectory(src) |
add_subdirectory(src) |
||||
|
|
||||
|
############################################################### |
||||
|
@ -1,12 +1,17 @@ |
|||||
#pragma once |
#pragma once |
||||
|
|
||||
/* Generated with cbindgen:0.23.0 */ |
|
||||
|
|
||||
#include <stdarg.h> |
#include <stdarg.h> |
||||
#include <stdbool.h> |
#include <stdbool.h> |
||||
#include <stdint.h> |
#include <stdint.h> |
||||
#include <stdlib.h> |
#include <stdlib.h> |
||||
|
|
||||
|
/**
|
||||
|
* Free the exported c-style string. |
||||
|
*/ |
||||
void free_rust_string(const char *ptr); |
void free_rust_string(const char *ptr); |
||||
|
|
||||
const char *to_json_ffi(const char *content); |
/**
|
||||
|
* Format the input text into JSON format and return a c-style string, or return |
||||
|
* `NULL` if an error occurs. |
||||
|
*/ |
||||
|
const char *to_json(const char *content); |
||||
|
@ -1,15 +1,29 @@ |
|||||
#ifndef ASSETS_H_ |
#ifndef ASSETS_H_ |
||||
#define ASSETS_H_ |
#define ASSETS_H_ |
||||
|
|
||||
typedef struct { |
#include <stdint.h> |
||||
char **update_file; |
|
||||
char **update_url; |
extern char **custom_gfwlist; |
||||
} assets; |
extern char **custom_china_ip; |
||||
|
extern char **custom_chinalist; |
||||
|
|
||||
assets* assets_init(); |
typedef struct { |
||||
void assets_free(assets *info); |
char *file; // string
|
||||
void assets_load(assets *info); |
char **sources; // string list
|
||||
|
} asset; |
||||
|
|
||||
void assets_extract(); |
void assets_extract(); |
||||
|
void assets_load(asset **info); |
||||
|
|
||||
|
asset** assets_init(); |
||||
|
asset* asset_init(const char *name); |
||||
|
void assets_dump(asset **asset_list); |
||||
|
void assets_free(asset **asset_list); |
||||
|
uint32_t assets_size(asset **asset_list); |
||||
|
void assets_append(asset ***asset_list, asset *res); |
||||
|
|
||||
|
/// Rust assets interface
|
||||
|
void assets_log_init(uint8_t verbose); |
||||
|
uint8_t asset_update(const char *file, char *const *sources, const char *assets_dir); |
||||
|
|
||||
#endif |
#endif |
||||
|
File diff suppressed because it is too large
@ -0,0 +1,6 @@ |
|||||
|
[workspace] |
||||
|
|
||||
|
members = [ |
||||
|
"assets", |
||||
|
"to-json" |
||||
|
] |
@ -0,0 +1,15 @@ |
|||||
|
[package] |
||||
|
name = "assets" |
||||
|
version = "0.1.0" |
||||
|
edition = "2021" |
||||
|
|
||||
|
[lib] |
||||
|
crate-type = ["staticlib"] |
||||
|
|
||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
|
||||
|
[dependencies] |
||||
|
env_logger = "0.10.0" |
||||
|
log = "0.4.17" |
||||
|
reqwest = { version = "0.11.14", features = ["deflate", "gzip", "brotli"] } |
||||
|
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] } |
@ -0,0 +1,3 @@ |
|||||
|
language = "C" |
||||
|
pragma_once = true |
||||
|
include_version = false |
@ -0,0 +1,193 @@ |
|||||
|
use std::fs::File; |
||||
|
use std::io::Read; |
||||
|
use reqwest::Client; |
||||
|
use std::time::Duration; |
||||
|
use log::{debug, info, warn}; |
||||
|
use std::collections::HashSet; |
||||
|
|
||||
|
/// Http download timeout limit
|
||||
|
const TIMEOUT: u64 = 120; |
||||
|
|
||||
|
/// Cut text line by line and remove invisible characters on both sides.
|
||||
|
fn asset_tidy(data: &str) -> Vec<String> { |
||||
|
data.lines() |
||||
|
.map(|x| String::from(x.trim())) |
||||
|
.filter(|x| !x.is_empty()) |
||||
|
.collect() |
||||
|
} |
||||
|
|
||||
|
/// Remove duplicate elements from an array.
|
||||
|
fn remove_dup(data: &Vec<String>) -> Vec<String> { |
||||
|
let mut result: Vec<String> = vec![]; |
||||
|
let mut tmp: HashSet<String> = HashSet::new(); |
||||
|
for val in data { |
||||
|
if tmp.insert(val.clone()) { // value already exist
|
||||
|
result.push(val.clone()); |
||||
|
} |
||||
|
} |
||||
|
result |
||||
|
} |
||||
|
|
||||
|
/// Download the specified text file and organize it into a String array.
|
||||
|
async fn http_fetch(url: &str, timeout: u64) -> Result<Vec<String>, String> { |
||||
|
let client = Client::builder() |
||||
|
.timeout(Duration::from_secs(timeout)) |
||||
|
.build().unwrap(); |
||||
|
debug!("Start downloading `{}`", url); |
||||
|
match client.get(url).send().await { |
||||
|
Ok(response) => { |
||||
|
match response.text().await { |
||||
|
Ok(text) => { |
||||
|
info!("Remote file `{}` download success", url); |
||||
|
Ok(asset_tidy(&text)) |
||||
|
}, |
||||
|
Err(err) => Err(format!("http content error: {}", err)) |
||||
|
} |
||||
|
}, |
||||
|
Err(err) => Err(format!("http request failed: {}", err)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// Read the specified text file and organize it into a String array.
|
||||
|
async fn local_fetch(path: &str) -> Result<Vec<String>, String> { |
||||
|
match File::open(path) { |
||||
|
Ok(mut file) => { |
||||
|
let mut text = String::new(); |
||||
|
if let Err(err) = file.read_to_string(&mut text) { |
||||
|
return Err(format!("file `{}` read failed: {}", path, err)); |
||||
|
}; |
||||
|
info!("Local file `{}` read success", path); |
||||
|
Ok(asset_tidy(&text)) |
||||
|
}, |
||||
|
Err(err) => Err(format!("file `{}` open failed: {}", path, err)), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// Get multiple resource data and merge them.
|
||||
|
pub(crate) async fn asset_fetch(name: &str, sources: &Vec<String>) -> Option<Vec<String>> { |
||||
|
let is_remote = |src: &str| { |
||||
|
src.starts_with("http://") || src.starts_with("https://") |
||||
|
}; |
||||
|
let mut contents: Vec<Vec<String>> = vec![]; |
||||
|
for source in sources { |
||||
|
contents.push(match if is_remote(&source) { |
||||
|
http_fetch(source.trim(), TIMEOUT).await // from remote text file
|
||||
|
} else { |
||||
|
local_fetch(source.trim()).await // from local text file
|
||||
|
} { |
||||
|
Ok(data) => { |
||||
|
debug!("Asset source `{}` fetch success with {} items", source.trim(), data.len()); |
||||
|
data |
||||
|
}, |
||||
|
Err(err) => { |
||||
|
warn!("Asset source `{}` fetch failed: {}", source.trim(), err); |
||||
|
return None; // stop fetch process
|
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
let contents = remove_dup(&contents |
||||
|
.into_iter() |
||||
|
.flatten() |
||||
|
.collect::<Vec<String>>()); |
||||
|
info!("Asset `{}` fetch complete with {} items", name, contents.len()); |
||||
|
Some(contents) |
||||
|
} |
||||
|
|
||||
|
#[cfg(test)] |
||||
|
mod tests { |
||||
|
use std::fs; |
||||
|
use std::pin::Pin; |
||||
|
use std::future::Future; |
||||
|
use std::fs::OpenOptions; |
||||
|
use std::io::Write; |
||||
|
use super::{asset_tidy, remove_dup}; |
||||
|
use super::{http_fetch, local_fetch, asset_fetch}; |
||||
|
|
||||
|
const TEST_DATA: &str = "\tabc \n123 \n 456\r\nabc\n\n789 "; |
||||
|
|
||||
|
#[test] |
||||
|
fn basic() { |
||||
|
assert_eq!(asset_tidy(TEST_DATA), vec![ |
||||
|
String::from("abc"), |
||||
|
String::from("123"), |
||||
|
String::from("456"), |
||||
|
String::from("abc"), |
||||
|
String::from("789"), |
||||
|
]); |
||||
|
assert_eq!(remove_dup(&asset_tidy(TEST_DATA)), vec![ |
||||
|
String::from("abc"), |
||||
|
String::from("123"), |
||||
|
String::from("456"), |
||||
|
String::from("789"), |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
fn run_async<T>(func: Pin<Box<impl Future<Output=T>>>) { |
||||
|
tokio::runtime::Builder::new_multi_thread() |
||||
|
.enable_all() |
||||
|
.build() |
||||
|
.unwrap() |
||||
|
.block_on(func); |
||||
|
} |
||||
|
|
||||
|
fn gen_test_file() { |
||||
|
let mut fp = OpenOptions::new() |
||||
|
.write(true) |
||||
|
.create(true) |
||||
|
.truncate(true) |
||||
|
.open("/tmp/assets_test_file") |
||||
|
.unwrap(); |
||||
|
fp.write_all(TEST_DATA.as_ref()).expect("test file create error"); |
||||
|
} |
||||
|
|
||||
|
#[test] |
||||
|
fn asset() { |
||||
|
// test http_fetch function
|
||||
|
run_async(Box::pin(async { |
||||
|
assert!(http_fetch("invalid url", 10).await.is_err()); |
||||
|
assert_eq!( |
||||
|
http_fetch("https://gstatic.com/generate_204", 10).await, |
||||
|
Ok(vec![]) |
||||
|
); |
||||
|
})); |
||||
|
|
||||
|
// test local_fetch function
|
||||
|
gen_test_file(); |
||||
|
run_async(Box::pin(async { |
||||
|
assert!(local_fetch("/").await.is_err()); |
||||
|
assert_eq!( |
||||
|
local_fetch("/tmp/assets_test_file").await, |
||||
|
Ok(vec![ |
||||
|
String::from("abc"), |
||||
|
String::from("123"), |
||||
|
String::from("456"), |
||||
|
String::from("abc"), |
||||
|
String::from("789"), |
||||
|
]) |
||||
|
); |
||||
|
})); |
||||
|
|
||||
|
// test combine asset_fetch function
|
||||
|
run_async(Box::pin(async { |
||||
|
assert!( |
||||
|
asset_fetch("", &vec![]).await.unwrap().is_empty() |
||||
|
); |
||||
|
assert!( |
||||
|
asset_fetch("", &vec![String::from("...")]).await.is_none() |
||||
|
); |
||||
|
assert_eq!( |
||||
|
asset_fetch("", &vec![ |
||||
|
String::from("/tmp/assets_test_file"), |
||||
|
String::from("https://gstatic.com/generate_204") |
||||
|
]).await, |
||||
|
Some(vec![ |
||||
|
String::from("abc"), |
||||
|
String::from("123"), |
||||
|
String::from("456"), |
||||
|
String::from("789"), |
||||
|
]) |
||||
|
); |
||||
|
})); |
||||
|
fs::remove_file("/tmp/assets_test_file").expect("test file delete error"); |
||||
|
} |
||||
|
} |
@ -0,0 +1,97 @@ |
|||||
|
use std::io::Write; |
||||
|
use std::env::set_var; |
||||
|
use std::path::PathBuf; |
||||
|
use std::fs::OpenOptions; |
||||
|
use std::os::raw::c_char; |
||||
|
use log::{debug, trace, warn}; |
||||
|
use std::ffi::{CStr, CString}; |
||||
|
use crate::fetch::asset_fetch; |
||||
|
|
||||
|
/// Compatible with C89 bool value.
|
||||
|
const TRUE: u8 = 1; |
||||
|
const FALSE: u8 = 0; |
||||
|
|
||||
|
/// Load c-style string from `char *` pointer.
|
||||
|
unsafe fn load_c_string(ptr: *const c_char) -> String { |
||||
|
CString::from(CStr::from_ptr(ptr)) |
||||
|
.into_string() |
||||
|
.unwrap() |
||||
|
} |
||||
|
|
||||
|
/// Load c-style string list from `char **` pointer.
|
||||
|
unsafe fn load_c_string_list(ptr: *const *const c_char) -> Vec<String> { |
||||
|
let mut index = 0; |
||||
|
let mut string_list: Vec<String> = vec![]; |
||||
|
while *ptr.offset(index) != std::ptr::null() { // traverse until `NULL`
|
||||
|
string_list.push(load_c_string(*ptr.offset(index))); |
||||
|
index += 1; |
||||
|
} |
||||
|
string_list |
||||
|
} |
||||
|
|
||||
|
/// Initialize the rust module log, enable trace level log when verbose is not `0`.
|
||||
|
#[no_mangle] |
||||
|
pub unsafe extern "C" fn assets_log_init(verbose: u8) { |
||||
|
if verbose == FALSE { // bool value `FALSE`
|
||||
|
set_var("RUST_LOG", "info"); |
||||
|
} else { |
||||
|
set_var("RUST_LOG", "trace"); |
||||
|
} |
||||
|
env_logger::init(); |
||||
|
} |
||||
|
|
||||
|
/// Update the specified resource file, return `0` on failure.
|
||||
|
#[no_mangle] |
||||
|
#[tokio::main] |
||||
|
pub async unsafe extern "C" fn asset_update( |
||||
|
file: *const c_char, sources: *const *const c_char, assets_dir: *const c_char) -> u8 { |
||||
|
|
||||
|
let name = load_c_string(file); // import c-style string
|
||||
|
let sources = load_c_string_list(sources); |
||||
|
let assets_dir = load_c_string(assets_dir); |
||||
|
trace!("Working folder is `{}`", assets_dir); |
||||
|
trace!("Updating `{}` from {:?}", name, sources); |
||||
|
|
||||
|
let assets_dir = PathBuf::from(&assets_dir); |
||||
|
let is_remote = |src: &str| { |
||||
|
src.starts_with("http://") || src.starts_with("https://") |
||||
|
}; |
||||
|
let sources = sources.iter() |
||||
|
.map(|src| { |
||||
|
if !is_remote(&src) && !src.starts_with("/") { // local relative path
|
||||
|
let file_path = assets_dir.join(src); |
||||
|
String::from(file_path.to_str().unwrap()) |
||||
|
} else { |
||||
|
src.clone() |
||||
|
} |
||||
|
}) |
||||
|
.collect::<Vec<String>>(); |
||||
|
let file = String::from(assets_dir.join(&name).to_str().unwrap()); |
||||
|
debug!("Asset sources -> {:?}", sources); |
||||
|
debug!("Asset target -> `{}`", file); |
||||
|
|
||||
|
match asset_fetch(&name, &sources).await { |
||||
|
Some(data) => { |
||||
|
let mut content = String::new(); |
||||
|
let _ = data.iter().map(|item| { |
||||
|
content.push_str(item); |
||||
|
content.push('\n'); |
||||
|
}).collect::<Vec<()>>(); |
||||
|
match OpenOptions::new() |
||||
|
.write(true) |
||||
|
.create(true) |
||||
|
.truncate(true) |
||||
|
.open(&file) { // open target file
|
||||
|
Ok(mut fp) => { |
||||
|
match fp.write_all(content.as_ref()) { |
||||
|
Err(err) => warn!("File `{}` save error: {}", file, err), |
||||
|
_ => debug!("File `{}` save success", file), |
||||
|
} |
||||
|
}, |
||||
|
Err(err) => warn!("File `{}` open failed: {}", file, err), |
||||
|
}; |
||||
|
TRUE // asset fetch success
|
||||
|
}, |
||||
|
None => FALSE, // asset fetch failed
|
||||
|
} |
||||
|
} |
@ -0,0 +1,2 @@ |
|||||
|
mod ffi; |
||||
|
mod fetch; |
@ -1,91 +0,0 @@ |
|||||
# This file is automatically @generated by Cargo. |
|
||||
# It is not intended for manual editing. |
|
||||
version = 3 |
|
||||
|
|
||||
[[package]] |
|
||||
name = "autocfg" |
|
||||
version = "1.1.0" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" |
|
||||
|
|
||||
[[package]] |
|
||||
name = "hashbrown" |
|
||||
version = "0.12.3" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" |
|
||||
|
|
||||
[[package]] |
|
||||
name = "indexmap" |
|
||||
version = "1.9.2" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" |
|
||||
dependencies = [ |
|
||||
"autocfg", |
|
||||
"hashbrown", |
|
||||
] |
|
||||
|
|
||||
[[package]] |
|
||||
name = "itoa" |
|
||||
version = "1.0.4" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" |
|
||||
|
|
||||
[[package]] |
|
||||
name = "ryu" |
|
||||
version = "1.0.11" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" |
|
||||
|
|
||||
[[package]] |
|
||||
name = "serde" |
|
||||
version = "1.0.149" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" |
|
||||
|
|
||||
[[package]] |
|
||||
name = "serde_json" |
|
||||
version = "1.0.89" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" |
|
||||
dependencies = [ |
|
||||
"itoa", |
|
||||
"ryu", |
|
||||
"serde", |
|
||||
] |
|
||||
|
|
||||
[[package]] |
|
||||
name = "serde_yaml" |
|
||||
version = "0.9.14" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" |
|
||||
dependencies = [ |
|
||||
"indexmap", |
|
||||
"itoa", |
|
||||
"ryu", |
|
||||
"serde", |
|
||||
"unsafe-libyaml", |
|
||||
] |
|
||||
|
|
||||
[[package]] |
|
||||
name = "to-json" |
|
||||
version = "0.1.0" |
|
||||
dependencies = [ |
|
||||
"serde_json", |
|
||||
"serde_yaml", |
|
||||
"toml", |
|
||||
] |
|
||||
|
|
||||
[[package]] |
|
||||
name = "toml" |
|
||||
version = "0.5.9" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" |
|
||||
dependencies = [ |
|
||||
"serde", |
|
||||
] |
|
||||
|
|
||||
[[package]] |
|
||||
name = "unsafe-libyaml" |
|
||||
version = "0.2.4" |
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index" |
|
||||
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" |
|
@ -1,3 +1,3 @@ |
|||||
language = "C" |
language = "C" |
||||
pragma_once = true |
pragma_once = true |
||||
include_version = true |
include_version = false |
||||
|
@ -1,27 +1,130 @@ |
|||||
use crate::json::to_json; |
use std::os::raw::c_char; |
||||
use std::ffi::{c_char, CStr, CString}; |
use std::ffi::{CStr, CString}; |
||||
|
use crate::parser::{parser, Value}; |
||||
|
|
||||
fn to_c_string(string: String) -> *const c_char { // fetch c-style ptr of string
|
/// Load c-style string from `const char *` pointer.
|
||||
CString::new(string).unwrap().into_raw() |
#[inline] |
||||
|
unsafe fn load_c_string(ptr: *const c_char) -> String { |
||||
|
CString::from(CStr::from_ptr(ptr)) |
||||
|
.into_string() |
||||
|
.unwrap() |
||||
} |
} |
||||
|
|
||||
unsafe fn load_c_string(ptr: *const c_char) -> String { // load string from c-style ptr
|
/// Export c-style string as `const char *` pointer.
|
||||
String::from( |
/// # NOTE
|
||||
CStr::from_ptr(ptr).to_str().unwrap() |
/// The exported string cannot be freed by the c language `free(void *)` function,
|
||||
) |
/// but should use the `free_rust_string` callback function, if this interface is
|
||||
|
/// not called, a memory leak will occur.
|
||||
|
#[inline] |
||||
|
fn export_c_string(string: String) -> *const c_char { |
||||
|
CString::new(string).unwrap().into_raw() |
||||
} |
} |
||||
|
|
||||
|
/// Free the exported c-style string.
|
||||
#[no_mangle] |
#[no_mangle] |
||||
pub unsafe extern "C" fn free_rust_string(ptr: *const c_char) { // free string memory
|
pub unsafe extern "C" fn free_rust_string(ptr: *const c_char) { |
||||
let _ = CString::from_raw(ptr as *mut _); |
let _ = CString::from_raw(ptr as *mut _); // reclaim rust ownership
|
||||
} |
} |
||||
|
|
||||
|
/// Deserialize text content and serialize to JSON format.
|
||||
|
fn json_format(content: &str) -> Option<String> { |
||||
|
let result = match parser(&content) { |
||||
|
Ok(value) => match value { |
||||
|
Value::JSON(json) => serde_json::to_string(&json), |
||||
|
Value::YAML(yaml) => serde_json::to_string(&yaml), |
||||
|
Value::TOML(toml) => serde_json::to_string(&toml), |
||||
|
}, |
||||
|
_ => return None, |
||||
|
}; |
||||
|
match result { |
||||
|
Ok(data) => Some(data), |
||||
|
Err(_) => None, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// Format the input text into JSON format and return a c-style string, or return
|
||||
|
/// `NULL` if an error occurs.
|
||||
#[no_mangle] |
#[no_mangle] |
||||
pub unsafe extern "C" fn to_json_ffi(content: *const c_char) -> *const c_char { |
pub unsafe extern "C" fn to_json(content: *const c_char) -> *const c_char { |
||||
let content = load_c_string(content); |
let content = load_c_string(content); |
||||
let result = match to_json(&content) { // convert to JSON format
|
match json_format(&content) { |
||||
|
Some(data) => export_c_string(data), |
||||
|
None => std::ptr::null(), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[cfg(test)] |
||||
|
mod tests { |
||||
|
use super::json_format; |
||||
|
|
||||
|
const JSON_TEST_STR: &str = r#" |
||||
|
{ |
||||
|
"int": 123, |
||||
|
"bool": true, |
||||
|
"float": 3.141592, |
||||
|
"string": "json test", |
||||
|
"array": [1, 2, 3, 4, 5], |
||||
|
"object": { |
||||
|
"sub": "test" |
||||
|
} |
||||
|
} |
||||
|
"#; |
||||
|
|
||||
|
const YAML_TEST_STR: &str = r#" |
||||
|
int: 123 |
||||
|
bool: true |
||||
|
float: 3.141592 |
||||
|
string: "json test" |
||||
|
array: |
||||
|
- 1 |
||||
|
- 2 |
||||
|
- 3 |
||||
|
- 4 |
||||
|
- 5 |
||||
|
object: |
||||
|
sub: test |
||||
|
"#; |
||||
|
|
||||
|
const TOML_TEST_STR: &str = r#" |
||||
|
int = 123 |
||||
|
bool = true |
||||
|
float = 3.141592 |
||||
|
string = "json test" |
||||
|
array = [ 1, 2, 3, 4, 5 ] |
||||
|
|
||||
|
[object] |
||||
|
sub = "test" |
||||
|
"#; |
||||
|
|
||||
|
#[inline] |
||||
|
fn format(raw: &str) -> String { |
||||
|
match json_format(raw) { |
||||
Some(data) => data, |
Some(data) => data, |
||||
None => String::new(), // convert failed -> empty string
|
None => panic!("format error"), |
||||
}; |
} |
||||
to_c_string(result) // return c-style ptr
|
} |
||||
|
|
||||
|
#[test] |
||||
|
fn json_input() { |
||||
|
assert_eq!( |
||||
|
format(JSON_TEST_STR), |
||||
|
format(&json_format(JSON_TEST_STR).unwrap()), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
#[test] |
||||
|
fn yaml_input() { |
||||
|
assert_eq!( |
||||
|
format(JSON_TEST_STR), |
||||
|
format(&json_format(YAML_TEST_STR).unwrap()), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
#[test] |
||||
|
fn toml_input() { |
||||
|
assert_eq!( |
||||
|
format(JSON_TEST_STR), |
||||
|
format(&json_format(TOML_TEST_STR).unwrap()), |
||||
|
); |
||||
|
} |
||||
} |
} |
||||
|
@ -1,21 +0,0 @@ |
|||||
use serde_json as json; |
|
||||
use crate::parser::{parser, Value}; |
|
||||
|
|
||||
fn json_convert(content: &str) -> Result<String, String> { // convert to JSON format
|
|
||||
let data = match parser(content)? { |
|
||||
Value::JSON(_json) => json::to_string(&_json), |
|
||||
Value::YAML(_yaml) => json::to_string(&_yaml), |
|
||||
Value::TOML(_toml) => json::to_string(&_toml), |
|
||||
}; |
|
||||
match data { |
|
||||
Ok(data) => Ok(data), |
|
||||
Err(err) => Err(err.to_string()), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub fn to_json(content: &str) -> Option<String> { // to JSON string
|
|
||||
match json_convert(content) { |
|
||||
Ok(data) => Some(data), |
|
||||
Err(_) => None, // convert failed
|
|
||||
} |
|
||||
} |
|
@ -1,4 +1,2 @@ |
|||||
mod ffi; |
mod ffi; |
||||
mod json; |
|
||||
mod tests; |
|
||||
mod parser; |
mod parser; |
||||
|
@ -1,80 +0,0 @@ |
|||||
use crate::json::to_json; |
|
||||
|
|
||||
#[allow(dead_code)] |
|
||||
const JSON_TEST_CONTENT: &str = r#" |
|
||||
{ |
|
||||
"int": 123, |
|
||||
"bool": true, |
|
||||
"float": 3.141592, |
|
||||
"string": "json test", |
|
||||
"array": [1, 2, 3, 4, 5], |
|
||||
"object": { |
|
||||
"sub": "test" |
|
||||
} |
|
||||
} |
|
||||
"#; |
|
||||
|
|
||||
#[allow(dead_code)] |
|
||||
const YAML_TEST_CONTENT: &str = r#" |
|
||||
int: 123 |
|
||||
bool: true |
|
||||
float: 3.141592 |
|
||||
string: "json test" |
|
||||
array: |
|
||||
- 1 |
|
||||
- 2 |
|
||||
- 3 |
|
||||
- 4 |
|
||||
- 5 |
|
||||
object: |
|
||||
sub: test |
|
||||
"#; |
|
||||
|
|
||||
#[allow(dead_code)] |
|
||||
const TOML_TEST_CONTENT: &str = r#" |
|
||||
int = 123 |
|
||||
bool = true |
|
||||
float = 3.141592 |
|
||||
string = "json test" |
|
||||
array = [ 1, 2, 3, 4, 5 ] |
|
||||
|
|
||||
[object] |
|
||||
sub = "test" |
|
||||
"#; |
|
||||
|
|
||||
|
|
||||
mod tests { |
|
||||
use super::*; |
|
||||
|
|
||||
#[allow(dead_code)] |
|
||||
fn format_json(raw: &str) -> String { |
|
||||
match to_json(raw) { |
|
||||
Some(data) => data, |
|
||||
None => panic!("JSON format error"), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn json_to_json() { |
|
||||
assert_eq!( |
|
||||
format_json(JSON_TEST_CONTENT), |
|
||||
format_json(&to_json(JSON_TEST_CONTENT).unwrap()), |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn yaml_to_json() { |
|
||||
assert_eq!( |
|
||||
format_json(JSON_TEST_CONTENT), |
|
||||
format_json(&to_json(YAML_TEST_CONTENT).unwrap()), |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
#[test] |
|
||||
fn toml_to_json() { |
|
||||
assert_eq!( |
|
||||
format_json(JSON_TEST_CONTENT), |
|
||||
format_json(&to_json(TOML_TEST_CONTENT).unwrap()), |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -1,3 +1,4 @@ |
|||||
cmake_minimum_required(VERSION 2.8.12) |
cmake_minimum_required(VERSION 2.8.12) |
||||
|
|
||||
add_library(utils cJSON.c assets.c logger.c process.c) |
add_library(utils cJSON.c assets.c logger.c process.c) |
||||
|
target_link_libraries(utils assets ssl crypto) |
||||
|
Loading…
Reference in new issue