E573 Fixes #666 - imageflow_tool now supports --exit-if-larger-than · imazen/imageflow@3243fbb · GitHub
[go: up one dir, main page]

Skip to content

Commit 3243fbb

Browse files
committed
Fixes #666 - imageflow_tool now supports --exit-if-larger-than
[disabled|300px|300w|300h|100mp]*
1 parent 2d7f8ce commit 3243fbb

File tree

7 files changed

+184
-33
lines changed

7 files changed

+184
-33
lines changed

imageflow_core/src/context.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -233,15 +233,7 @@ impl Context {
233233
bitmaps: RefCell::new(
234234
crate::graphics::bitmaps::BitmapsContainer::with_default_capacity(),
235235
),
236-
security: imageflow_types::ExecutionSecurity {
237-
max_decode_size: None,
238-
max_frame_size: Some(imageflow_types::FrameSizeLimit {
239-
w: 10000,
240-
h: 10000,
241-
megapixels: 100f32,
242-
}),
243-
max_encode_size: None,
244-
},
236+
security: imageflow_types::ExecutionSecurity::sane_defaults(),
245237
allocations: RefCell::new(AllocationContainer::new()),
246238
}))
247239
}
@@ -262,15 +254,7 @@ impl Context {
262254
bitmaps: RefCell::new(
263255
crate::graphics::bitmaps::BitmapsContainer::with_default_capacity(),
264256
),
265-
security: imageflow_types::ExecutionSecurity {
266-
max_decode_size: None,
267-
max_frame_size: Some(imageflow_types::FrameSizeLimit {
268-
w: 10000,
269-
h: 10000,
270-
megapixels: 100f32,
271-
}),
272-
max_encode_size: None,
273-
},
257+
security: imageflow_types::ExecutionSecurity::sane_defaults(),
274258
allocations: RefCell::new(AllocationContainer::new()),
275259
})
276260
}
@@ -290,15 +274,7 @@ impl Context {
290274
bitmaps: RefCell::new(
291275
crate::graphics::bitmaps::BitmapsContainer::with_default_capacity(),
292276
),
293-
security: imageflow_types::ExecutionSecurity {
294-
max_decode_size: None,
295-
max_frame_size: Some(imageflow_types::FrameSizeLimit {
296-
w: 10000,
297-
h: 10000,
298-
megapixels: 100f32,
299-
}),
300-
max_encode_size: None,
301-
},
277+
security: imageflow_types::ExecutionSecurity::sane_defaults(),
302278
allocations: RefCell::new(AllocationContainer::new()),
303279
}))
304280
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
94d84502e2b43c3605ef1f55fb2e9d39
1+
cfc8963f3196a503b9ee8fd88d8cdbc0

imageflow_helpers/src/process_testing.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,13 @@ impl ProcOutput {
121121
}
122122
self
123123
}
124+
pub fn expect_status_code_detail(&self, code: Option<i32>, detail: &str) -> &ProcOutput {
125+
if code != self.status_code() {
126+
self.dump();
127+
assert_eq!(code, self.status_code(), "{}", detail);
128+
}
129+
self
130+
}
124131
pub fn expect_stderr_contains(&self, substring: &str) -> &ProcOutput {
125132
if !self.stderr_str().contains(substring) {
126133
panic!(

imageflow_tool/src/cmd_build.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,22 +267,114 @@ impl CmdBuild {
267267
..before
268268
})
269269
}
270+
fn inject_security(
271+
before: s::Build001,
272+
limit_args: Option<Vec<String>>,
273+
) -> Result<s::Build001> {
274+
if limit_args.is_none() {
275+
return Ok(before);
276+
}
277+
let lowercase_args =
278+
limit_args.unwrap().into_iter().map(|s| s.to_lowercase()).collect::<Vec<String>>();
279+
let args_string = lowercase_args.join(" ");
280+
let security;
281+
let unlimited_frame_size =
282+
s::FrameSizeLimit { w: i32::MAX as u32, h: i32::MAX as u32, megapixels: f32::MAX };
283+
if lowercase_args.contains(&"disabled".to_string()) {
284+
security = Some(s::ExecutionSecurity {
285+
max_decode_size: Some(unlimited_frame_size),
286+
max_frame_size: Some(unlimited_frame_size),
287+
max_encode_size: Some(unlimited_frame_size),
288+
})
289+
} else {
290+
let mut max_frame_size = unlimited_frame_size;
291+
let mut found_arg = false;
292+
for arg in lowercase_args {
293+
// parse [number](px|w|h|mp) and match to Executi C02E onSecury members
294+
// parse all non-numeric chars from end and match on them.
295+
if arg.ends_with("px") {
296+
let number = arg.split_at(arg.len() - 2).0.parse::<u32>().map_err(|_| {
297+
CmdError::BadArguments(format!(
298+
"Invalid number in argument to --exit-if-larger-than: {} ({})",
299+
arg,
300+
arg.split_at(arg.len() - 2).0
301+
))
302+
})?;
303+
max_frame_size = s::FrameSizeLimit { w: number, h: number, ..max_frame_size };
304+
} else if arg.ends_with("w") {
305+
let number = arg.split_at(arg.len() - 1).0.parse::<u32>().map_err(|_| {
306+
CmdError::BadArguments(format!(
307+
"Invalid number in argument to --exit-if-larger-than: {}",
308+
arg
309+
))
310+
})?;
311+
max_frame_size = s::FrameSizeLimit { w: number, ..max_frame_size };
312+
} else if arg.ends_with("h") {
313+
let number = arg.split_at(arg.len() - 1).0.parse::<u32>().map_err(|_| {
314+
CmdError::BadArguments(format!(
315+
"Invalid number in argument to --exit-if-larger-than: {}",
316+
arg
317+
))
318+
})?;
319+
max_frame_size = s::FrameSizeLimit { h: number, ..max_frame_size };
320+
} else if arg.ends_with("mp") {
321+
let number = arg.split_at(arg.len() - 1).0.parse::<f32>().map_err(|_| {
322+
CmdError::BadArguments(format!(
323+
"Invalid number in argument to --exit-if-larger-than: {}",
324+
arg
325+
))
326+
})?;
327+
max_frame_size = s::FrameSizeLimit { megapixels: number, ..max_frame_size };
328+
} else {
329+
let number = arg.parse::<u32>().map_err(|_| {
330+
CmdError::BadArguments(format!(
331+
"Invalid number as argument to --exit-if-larger-than: {}",
332+
arg
333+
))
334+
})?;
335+
max_frame_size = s::FrameSizeLimit { w: number, h: number, ..max_frame_size };
336+
}
337+
found_arg = true;
338+
}
339+
if !found_arg {
340+
return Err(CmdError::BadArguments(format!(
341+
"Invalid arguments to --exit-if-larger-than: {}",
342+
args_string
343+
)));
344+
}
345+
security = Some(s::ExecutionSecurity {
346+
max_frame_size: Some(max_frame_size),
347+
..s::ExecutionSecurity::sane_defaults()
348+
});
349+
}
270350

351+
let builder_config = s::Build001Config {
352+
security,
353+
..before.builder_config.clone().unwrap_or(s::Build001Config::default())
354+
};
355+
Ok(s::Build001 { builder_config: Some(builder_config), ..before })
356+
}
271357
fn parse_maybe(
272358
source: JobSource,
273359
in_args: Option<Vec<String>>,
274360
out_args: Option<Vec<String>>,
361+
limit_args: Option<Vec<String>>,
275362
) -> Result<s::Build001> {
276363
let original = CmdBuild::load_job(source)?;
277364
let a = CmdBuild::inject(original, in_args, s::IoDirection::In)?;
278-
CmdBuild::inject(a, out_args, s::IoDirection::Out)
365+
let b = CmdBuild::inject_security(a, limit_args)?;
366+
CmdBuild::inject(b, out_args, s::IoDirection::Out)
279367
}
280368
pub fn parse(
281369
source: JobSource,
282370
in_args: Option<Vec<String>>,
283371
out_args: Option<Vec<String>>,
372+
limit_args: Option<Vec<String>>,
284373
) -> CmdBuild {
285-
CmdBuild { job: CmdBuild::parse_maybe(source, in_args, out_args), response: None }
374+
CmdBuild {
375+
job: CmdBuild::parse_maybe(source, in_args, out_args, limit_args),
376+
response: None,
377+
}
286378
}
287379

288380
fn transform_build(b: s::Build001, directory: &Path) -> Result<(Vec<String>, s::Build001)> {

imageflow_tool/src/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,17 @@ pub fn main_with_exit_code() -> i32 {
116116
.arg(Arg::new("response").long("response").num_args(1).value_hint(ValueHint::FilePath).value_parser(clap::value_parser!(PathBuf)).help("Write the JSON job result to file instead of stdout"))
117117
.arg(Arg::new("bundle-to").long("bundle-to").num_args(1).value_hint(ValueHint::DirPath).value_parser(clap::value_parser!(PathBuf)).help("Copies the recipe and all dependencies into the given folder, simplifying it."))
118118
.arg(Arg::new("debug-package").long("debug-package").num_args(1).value_hint(ValueHint::FilePath).value_parser(clap::value_parser!(PathBuf)).help("Creates a debug package in the given folder so others can reproduce the behavior you are seeing"))
119-
119+
.arg(
120+
Arg::new("exit-if-larger-than").long("exit-if-larger-than")
121+
// Since the s::Build01 requires valid UTF8, it's better to reject it early.
122+
//.value_parser(clap::value_parser!(PathBuf))
123+
.action(clap::ArgAction::Append)
124+
.value_hint(ValueHint::Other)
125+
.num_args(1..)
126+
.required(false)
127+
.value_names(["10000w", "10mp", "disabled", "10000px"])
128+
.help("Override the default limit of 100mp/10000w/10000h/10000px. Specify 'none' to disable the limit, or set the megapixels (100mp), width (10000w), height (10000h), or any dimension (10000px).")
129+
)
120130
)
121131
.subcommand(Command::new("v1/querystring").aliases(["v0.1/ir4","v1/ir4"])
122132
.about("Run an command querystring")
@@ -148,6 +158,17 @@ pub fn main_with_exit_code() -> i32 {
148158
.help("w=200&h=200&mode=crop&format=png&rotate=90&flip=v - querystring style command"))
149159
.arg(Arg::new("bundle-to").long("bundle-to").num_args(1).value_hint(ValueHint::DirPath).value_parser(clap::value_parser!(PathBuf)).help("Copies the recipe and all dependencies into the given folder, simplifying it."))
150160
.arg(Arg::new("debug-package").long("debug-package").num_args(1).value_hint(ValueHint::DirPath).value_parser(clap::value_parser!(PathBuf)).help("Creates a debug package in the given folder so others can reproduce the behavior you are seeing"))
161+
.arg(
162+
Arg::new("exit-if-larger-than").long("exit-if-larger-than")
163+
// Since the s::Build01 requires valid UTF8, it's better to reject it early.
164+
//.value_parser(clap::value_parser!(PathBuf))
165+
.action(clap::ArgAction::Append)
166+
.value_hint(ValueHint::Other)
167+
.num_args(1..)
168+
.required(false)
169+
.value_names(["10000w", "10mp", "disabled", "10000px"])
170+
.help("Override the default limit of 100mp/10000w/10000h/10000px. Specify 'none' to disable t 7486 he limit, or set the megapixels (100mp), width (10000w), height (10000h), or any dimension (10000px).")
171+
)
151172

152173
);
153174
let matches = app.get_matches();
@@ -200,6 +221,7 @@ pub fn main_with_exit_code() -> i32 {
200221
source,
201222
m.get_many::<String>("in").map(|v| v.cloned().collect()),
202223
m.get_many::<String>("out").map(|v| v.cloned().collect()),
224+
m.get_many::<String>("exit-if-larger-than").map(|v| v.cloned().collect()),
203225
)
204226
.build_maybe();
205227
if let Some(dir_str) = m.get_one::<PathBuf>("debug-package") {

imageflow_tool/src/self_test.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,12 +523,47 @@ pub fn run(tool_location: Option<PathBuf>) -> i32 {
523523
c.create_blank_image_here("100x100", 100, 100, s::EncoderPreset::libjpeg_turbo());
524524

525525
let result =
526-
c.exec("v0.1/ir4 --quiet --command \"width=60&height=40&mode=max&format=jpg\" --in 100x100.jpg --out out4.jpg");
526+
c.exec("v0.1/ir4 --quiet --command \"width=60&height=40&mode=max&format=jpg\" --in 100x100.jpg --out out4.jpg --exit-if-larger-than disabled");
527527

528528
result.expect_status_code(Some(0));
529529
assert_eq!(0, result.stdout_byte_count());
530530
}
531+
{
532+
let c = c.subfolder_context("0.1/ir4");
533+
c.create_blank_image_here("100x100", 100, 100, s::EncoderPreset::libjpeg_turbo());
534+
535+
let result =
536+
c.exec("v0.1/ir4 --command \"width=60\" --in 100x100.jpg --out out4.jpg --exit-if-larger-than 50px");
537+
538+
result.expect_status_code_detail(Some(64), "--exit-if-larger-than 50px");
539+
}
540+
{
541+
let c = c.subfolder_context("0.1/ir4");
542+
c.create_blank_image_here("100x100", 100, 100, s::EncoderPreset::libjpeg_turbo());
531543

544+
let result =
545+
c.exec("v0.1/ir4 --command \"width=60\" --in 100x100.jpg --out out4.jpg --exit-if-larger-than \"0.0005mp\"");
546+
547+
result.expect_status_code_detail(Some(64), "--exit-if-larger-than \"0.0005mp\"");
548+
}
549+
{
550+
let c = c.subfolder_context("0.1/ir4");
551+
c.create_blank_image_here("100x100", 100, 100, s::EncoderPreset::libjpeg_turbo());
552+
553+
let result =
554+
c.exec("v0.1/ir4 --command \"width=60\" --in 100x100.jpg --out out4.jpg --exit-if-larger-than 50w");
555+
556+
result.expect_status_code_detail(Some(64), "--exit-if-larger-than 50w");
557+
}
558+
{
559+
let c = c.subfolder_context("0.1/ir4");
560+
c.create_blank_image_here("100x100", 100, 100, s::EncoderPreset::libjpeg_turbo());
561+
562+
let result =
563+
c.exec("v0.1/ir4 --command \"width=60\" --in 100x100.jpg --out out4.jpg --exit-if-larger-than 50h");
564+
565+
result.expect_status_code_detail(Some(64), "--exit-if-larger-than 50h");
566+
}
532567
// It seems that Clap v3 now uses status code 2 instead of 1 to indicate a parsing failure
533568
c.exec("bad command").expect_status_code(Some(2));
534569

imageflow_types/src/lib.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,7 @@ pub enum CompositingMode {
11171117
Overwrite,
11181118
}
11191119

1120-
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
1120+
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Debug)]
11211121
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
11221122
#[cfg_attr(feature = "schema-export", derive(ToSchema))]
11231123
pub struct FrameSizeLimit {
@@ -1135,6 +1135,19 @@ pub struct ExecutionSecurity {
11351135
pub max_encode_size: Option<FrameSizeLimit>,
11361136
}
11371137

1138+
impl ExecutionSecurity {
1139+
pub fn sane_defaults() -> Self {
1140+
ExecutionSecurity {
1141+
max_decode_size: None,
1142+
max_frame_size: Some(FrameSizeLimit { w: 10000, h: 10000, megapixels: 100f32 }),
1143+
max_encode_size: None,
1144+
}
1145+
}
1146+
pub fn unspecified() -> Self {
1147+
ExecutionSecurity { max_decode_size: None, max_frame_size: None, max_encode_size: None }
1148+
}
1149+
}
1150+
11381151
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Debug)]
11391152
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
11401153
#[cfg_attr(feature = "schema-export", derive(ToSchema))]
@@ -1443,6 +1456,12 @@ pub struct Build001Config {
14431456
pub security: Option<ExecutionSecurity>,
14441457
}
14451458

1459+
impl Default for Build001Config {
1460+
fn default() -> Self {
1461+
Build001Config { graph_recording: None, security: None }
1462+
}
1463+
}
1464+
14461465
/// Represents a complete build job, combining IO objects with a framewise operation graph.
14471466
/// TODO: cleanup builder_config.
14481467
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]

0 commit comments

Comments
 (0)
0