Skip to content

Commit 2f483ea

Browse files
committed
basic support for binary files via base64
1 parent ee34265 commit 2f483ea

File tree

6 files changed

+109
-32
lines changed

6 files changed

+109
-32
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = "2018"
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10+
base64 = "0.13.0"
1011
clap = "2.0"
1112
fuser = "0.8"
1213
libc = "0.2.51"

binary/twitter.ico

1.12 KB
Binary file not shown.

src/config.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub struct Config {
1313
pub dirmode: u16,
1414
pub add_newlines: bool,
1515
pub pad_element_names: bool,
16+
pub base64: base64::Config,
17+
pub try_decode_base64: bool,
1618
pub read_only: bool,
1719
pub output: Output,
1820
}
@@ -41,7 +43,6 @@ impl Config {
4143
.replace("=", "equal")
4244
.replace(" ", "space")
4345
}
44-
4546
}
4647

4748
impl Default for Config {
@@ -56,8 +57,10 @@ impl Default for Config {
5657
dirmode: 0o755,
5758
add_newlines: false,
5859
pad_element_names: true,
60+
base64: base64::STANDARD,
61+
try_decode_base64: false,
5962
read_only: false,
6063
output: Output::Stdout,
6164
}
6265
}
63-
}
66+
}

src/format.rs

+43-30
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ where
119119
/// Characterizes the outermost value. Drives the worklist algorithm.
120120
fn node(self, config: &Config) -> Node<Self>;
121121

122-
#[allow(clippy::ptr_arg)]
123-
fn from_bytes(v: &Vec<u8>, config: &Config) -> Self;
122+
fn from_bytes<T>(v: T, config: &Config) -> Self
123+
where
124+
T: AsRef<[u8]>;
125+
fn from_string(v: String, config: &Config) -> Self;
124126
fn from_list_dir(files: Vec<Self>, config: &Config) -> Self;
125127
fn from_named_dir(files: HashMap<String, Self>, config: &Config) -> Self;
126128
}
@@ -238,7 +240,15 @@ where
238240
V: Nodelike,
239241
{
240242
match &fs.get(inum).unwrap().entry {
241-
Entry::File(contents) => V::from_bytes(contents, &fs.config),
243+
Entry::File(contents) => match String::from_utf8(contents.clone()) {
244+
Ok(mut contents) => {
245+
if fs.config.add_newlines && contents.ends_with('\n') {
246+
contents.truncate(contents.len() - 1);
247+
}
248+
V::from_string(contents, &fs.config)
249+
}
250+
Err(_) => V::from_bytes(contents, &fs.config),
251+
},
242252
Entry::Directory(DirType::List, files) => {
243253
let mut entries = Vec::with_capacity(files.len());
244254

@@ -294,25 +304,20 @@ mod json {
294304
Value::Bool(b) => Node::Bytes(format!("{}{}", b, nl).into_bytes()),
295305
Value::Number(n) => Node::Bytes(format!("{}{}", n, nl).into_bytes()),
296306
Value::String(s) => {
297-
let contents = if s.ends_with('\n') { s } else { s + nl };
298-
Node::Bytes(contents.into_bytes())
307+
if config.try_decode_base64 {
308+
if let Ok(bytes) = base64::decode_config(&s, config.base64) {
309+
return Node::Bytes(bytes);
310+
}
311+
}
312+
313+
Node::String(if s.ends_with('\n') { s } else { s + nl })
299314
}
300315
Value::Array(vs) => Node::List(vs),
301316
Value::Object(fvs) => Node::Map(fvs.into_iter().collect()),
302317
}
303318
}
304319

305-
fn from_bytes(contents: &Vec<u8>, config: &Config) -> Self {
306-
let contents = match String::from_utf8(contents.clone()) {
307-
Ok(mut contents) => {
308-
if config.add_newlines && contents.ends_with('\n') {
309-
contents.truncate(contents.len() - 1);
310-
}
311-
contents
312-
}
313-
Err(_) => unimplemented!("binary data JSON serialization"),
314-
};
315-
320+
fn from_string(contents: String, _config: &Config) -> Self {
316321
if contents.is_empty() {
317322
Value::Null
318323
} else if contents == "true" {
@@ -326,6 +331,13 @@ mod json {
326331
}
327332
}
328333

334+
fn from_bytes<T>(contents: T, config: &Config) -> Self
335+
where
336+
T: AsRef<[u8]>,
337+
{
338+
Value::String(base64::encode_config(contents, config.base64))
339+
}
340+
329341
fn from_list_dir(files: Vec<Self>, _config: &Config) -> Self {
330342
Value::Array(files)
331343
}
@@ -340,7 +352,6 @@ mod toml {
340352
use super::*;
341353

342354
use serde_toml::Value;
343-
344355
#[derive(Debug)]
345356
pub enum Error<E> {
346357
Io(std::io::Error),
@@ -392,25 +403,20 @@ mod toml {
392403
Value::Float(n) => Node::Bytes(format!("{}{}", n, nl).into_bytes()),
393404
Value::Integer(n) => Node::Bytes(format!("{}{}", n, nl).into_bytes()),
394405
Value::String(s) => {
395-
let contents = if s.ends_with('\n') { s } else { s + nl };
396-
Node::Bytes(contents.into_bytes())
406+
if config.try_decode_base64 {
407+
if let Ok(bytes) = base64::decode_config(&s, config.base64) {
408+
return Node::Bytes(bytes);
409+
}
410+
}
411+
412+
Node::String(if s.ends_with('\n') { s } else { s + nl })
397413
}
398414
Value::Array(vs) => Node::List(vs),
399415
Value::Table(fvs) => Node::Map(fvs.into_iter().collect()),
400416
}
401417
}
402418

403-
fn from_bytes(contents: &Vec<u8>, config: &Config) -> Self {
404-
let contents = match String::from_utf8(contents.clone()) {
405-
Ok(mut contents) => {
406-
if config.add_newlines && contents.ends_with('\n') {
407-
contents.truncate(contents.len() - 1);
408-
}
409-
contents
410-
}
411-
Err(_) => unimplemented!("binary data TOML serialization"),
412-
};
413-
419+
fn from_string(contents: String, _config: &Config) -> Self {
414420
if contents == "true" {
415421
Value::Boolean(true)
416422
} else if contents == "false" {
@@ -424,6 +430,13 @@ mod toml {
424430
}
425431
}
426432

433+
fn from_bytes<T>(contents: T, config: &Config) -> Self
434+
where
435+
T: AsRef<[u8]>,
436+
{
437+
Value::String(base64::encode_config(contents, config.base64))
438+
}
439+
427440
fn from_list_dir(files: Vec<Self>, _config: &Config) -> Self {
428441
Value::Array(files)
429442
}

tests/binary.sh

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/sh
2+
3+
fail() {
4+
echo FAILED: $1
5+
if [ "$MNT" ]
6+
then
7+
cd
8+
umount "$MNT"
9+
rmdir "$MNT"
10+
rm "$TGT"
11+
rm "$TGT2"
12+
rm "$ICO"
13+
fi
14+
exit 1
15+
}
16+
17+
MNT=$(mktemp -d)
18+
TGT=$(mktemp)
19+
TGT2=$(mktemp)
20+
21+
ffs "$MNT" ../json/object.json >"$TGT" &
22+
PID=$!
23+
sleep 2
24+
cp ../binary/twitter.ico "$MNT"/favicon
25+
umount "$MNT" || fail unmount1
26+
sleep 1
27+
kill -0 $PID >/dev/null 2>&1 && fail process1
28+
29+
# easiest to just test using ffs, but would be cool to get outside validation
30+
[ -f "$TGT" ] || fail output1
31+
[ -s "$TGT" ] || fail output2
32+
grep favicon "$TGT" >/dev/null 2>&1 || fail text
33+
ffs --no-output "$MNT" "$TGT" >"$TGT2" &
34+
PID=$!
35+
sleep 2
36+
37+
ICO=$(mktemp)
38+
39+
ls "$MNT" | grep favicon >/dev/null 2>&1 || fail field
40+
base64 -D -i "$MNT"/favicon -o "$ICO"
41+
diff ../binary/twitter.ico "$ICO" || fail diff
42+
43+
umount "$MNT" || fail unmount2
44+
sleep 1
45+
kill -0 $PID >/dev/null 2>&1 && fail process2
46+
47+
[ -f "$TGT2" ] || fail tgt2
48+
[ -s "$TGT2" ] && fail tgt2_nonempty
49+
50+
rmdir "$MNT" || fail mount
51+
rm "$TGT"
52+
rm "$TGT2"
53+
rm "$ICO"

0 commit comments

Comments
 (0)