mirror of
https://github.com/clap-rs/clap
synced 2025-01-18 23:53:54 +00:00
fix: Provide path to avoid UTF-8 panics
Before, validating UTF-8 was all-or-nothing and would cause a `panic` if someone used the right API with non-UTF-8 input. Now, all arguments are validated for UTF-8, unless opted-out. This ensures a non-panicing path forward at the cost of people using the builder API that previously did `value_of_os` need to now set this flag. Fixes #751
This commit is contained in:
parent
2fd26423e2
commit
aeaf01e3e7
17 changed files with 563 additions and 185 deletions
|
@ -40,6 +40,12 @@ Added `unicode_help`, `env` features.
|
|||
* **ErrorKind**
|
||||
* `ErrorKind::MissingArgumentOrSubcommand` => `ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand`
|
||||
* **Changed**
|
||||
* `AppSettings::StrictUtf8` is now default and it and `AppSettings::AllowInvalidUtf8` are replaced by
|
||||
* `AppSettings::AllowInvalidUtf8ForExternalSubcommands`
|
||||
* This only applies to the subcommand args. Before we paniced if the
|
||||
subcommand itself was invalid but now we will report an error up to the
|
||||
user.
|
||||
* `ArgSettings::AllowInvalidUtf8`
|
||||
* Allowing empty values is the default again with `ArgSettings::AllowEmptyValues` changing to
|
||||
`ArgSettings::ForbidEmptyValues`
|
||||
* `AppSettings::GlobalVersion` renamed to `AppSettings::PropagateVersion` and it is not applied
|
||||
|
|
|
@ -228,7 +228,22 @@ pub fn gen_augment(
|
|||
ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
|
||||
.validator_os(|s| #func(s).map(|_: #convert_type| ()))
|
||||
},
|
||||
_ => quote!(),
|
||||
ParserKind::FromStr
|
||||
| ParserKind::FromOsStr
|
||||
| ParserKind::FromFlag
|
||||
| ParserKind::FromOccurrences => quote!(),
|
||||
};
|
||||
let allow_invalid_utf8 = match *parser.kind {
|
||||
_ if attrs.is_enum() => quote!(),
|
||||
ParserKind::FromOsStr | ParserKind::TryFromOsStr => {
|
||||
quote_spanned! { func.span()=>
|
||||
.allow_invalid_utf8(true)
|
||||
}
|
||||
}
|
||||
ParserKind::FromStr
|
||||
| ParserKind::TryFromStr
|
||||
| ParserKind::FromFlag
|
||||
| ParserKind::FromOccurrences => quote!(),
|
||||
};
|
||||
|
||||
let value_name = attrs.value_name();
|
||||
|
@ -250,6 +265,7 @@ pub fn gen_augment(
|
|||
.value_name(#value_name)
|
||||
#possible_values
|
||||
#validator
|
||||
#allow_invalid_utf8
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,6 +276,7 @@ pub fn gen_augment(
|
|||
.max_values(1)
|
||||
.multiple_values(false)
|
||||
#validator
|
||||
#allow_invalid_utf8
|
||||
},
|
||||
|
||||
Ty::OptionVec => quote_spanned! { ty.span()=>
|
||||
|
@ -268,6 +285,7 @@ pub fn gen_augment(
|
|||
.multiple_values(true)
|
||||
.min_values(0)
|
||||
#validator
|
||||
#allow_invalid_utf8
|
||||
},
|
||||
|
||||
Ty::Vec => {
|
||||
|
@ -285,6 +303,7 @@ pub fn gen_augment(
|
|||
.multiple_values(true)
|
||||
#possible_values
|
||||
#validator
|
||||
#allow_invalid_utf8
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,6 +330,7 @@ pub fn gen_augment(
|
|||
.required(#required)
|
||||
#possible_values
|
||||
#validator
|
||||
#allow_invalid_utf8
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -131,8 +131,34 @@ fn gen_augment(
|
|||
|
||||
match &*kind {
|
||||
Kind::ExternalSubcommand => {
|
||||
quote_spanned! { kind.span()=>
|
||||
let app = app.setting(clap::AppSettings::AllowExternalSubcommands);
|
||||
let ty = match variant.fields {
|
||||
Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
|
||||
|
||||
_ => abort!(
|
||||
variant,
|
||||
"The enum variant marked with `external_subcommand` must be \
|
||||
a single-typed tuple, and the type must be either `Vec<String>` \
|
||||
or `Vec<OsString>`."
|
||||
),
|
||||
};
|
||||
match subty_if_name(ty, "Vec") {
|
||||
Some(subty) => {
|
||||
if is_simple_ty(subty, "OsString") {
|
||||
quote_spanned! { kind.span()=>
|
||||
let app = app.setting(clap::AppSettings::AllowExternalSubcommands).setting(clap::AppSettings::AllowInvalidUtf8ForExternalSubcommands);
|
||||
}
|
||||
} else {
|
||||
quote_spanned! { kind.span()=>
|
||||
let app = app.setting(clap::AppSettings::AllowExternalSubcommands);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None => abort!(
|
||||
ty.span(),
|
||||
"The type must be `Vec<_>` \
|
||||
to be used with `external_subcommand`."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,7 +374,7 @@ fn gen_from_arg_matches(
|
|||
|
||||
_ => abort!(
|
||||
variant,
|
||||
"The enum variant marked with `external_attribute` must be \
|
||||
"The enum variant marked with `external_subcommand` must be \
|
||||
a single-typed tuple, and the type must be either `Vec<String>` \
|
||||
or `Vec<OsString>`."
|
||||
),
|
||||
|
|
|
@ -10,7 +10,7 @@ error: The type must be either `Vec<String>` or `Vec<OsString>` to be used with
|
|||
13 | Other(String),
|
||||
| ^^^^^^
|
||||
|
||||
error: The enum variant marked with `external_attribute` must be a single-typed tuple, and the type must be either `Vec<String>` or `Vec<OsString>`.
|
||||
error: The enum variant marked with `external_subcommand` must be a single-typed tuple, and the type must be either `Vec<String>` or `Vec<OsString>`.
|
||||
--> $DIR/external_subcommand_wrong_type.rs:18:5
|
||||
|
|
||||
18 | / #[clap(external_subcommand)]
|
||||
|
|
226
clap_derive/tests/utf8.rs
Normal file
226
clap_derive/tests/utf8.rs
Normal file
|
@ -0,0 +1,226 @@
|
|||
#![cfg(not(windows))]
|
||||
|
||||
use clap::{Clap, ErrorKind};
|
||||
use std::ffi::OsString;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
#[derive(Clap, Debug, PartialEq, Eq)]
|
||||
struct Positional {
|
||||
arg: String,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug, PartialEq, Eq)]
|
||||
struct Named {
|
||||
#[clap(short, long)]
|
||||
arg: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_strict_positional() {
|
||||
let m = Positional::try_parse_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_strict_option_short_space() {
|
||||
let m = Named::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("-a"),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_strict_option_short_equals() {
|
||||
let m = Named::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]),
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_strict_option_short_no_space() {
|
||||
let m = Named::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0xe9]),
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_strict_option_long_space() {
|
||||
let m = Named::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("--arg"),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_strict_option_long_equals() {
|
||||
let m = Named::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]),
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug, PartialEq, Eq)]
|
||||
struct PositionalOs {
|
||||
#[clap(parse(from_os_str))]
|
||||
arg: OsString,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug, PartialEq, Eq)]
|
||||
struct NamedOs {
|
||||
#[clap(short, long, parse(from_os_str))]
|
||||
arg: OsString,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_positional() {
|
||||
let r = PositionalOs::try_parse_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
PositionalOs {
|
||||
arg: OsString::from_vec(vec![0xe9])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_option_short_space() {
|
||||
let r = NamedOs::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("-a"),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
NamedOs {
|
||||
arg: OsString::from_vec(vec![0xe9])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_option_short_equals() {
|
||||
let r = NamedOs::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]),
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
NamedOs {
|
||||
arg: OsString::from_vec(vec![0xe9])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_option_short_no_space() {
|
||||
let r = NamedOs::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0xe9]),
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
NamedOs {
|
||||
arg: OsString::from_vec(vec![0xe9])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_option_long_space() {
|
||||
let r = NamedOs::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("--arg"),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
NamedOs {
|
||||
arg: OsString::from_vec(vec![0xe9])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_option_long_equals() {
|
||||
let r = NamedOs::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]),
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
NamedOs {
|
||||
arg: OsString::from_vec(vec![0xe9])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clap)]
|
||||
enum External {
|
||||
#[clap(external_subcommand)]
|
||||
Other(Vec<String>),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuse_invalid_utf8_subcommand_with_allow_external_subcommands() {
|
||||
let m = External::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
OsString::from("normal"),
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuse_invalid_utf8_subcommand_args_with_allow_external_subcommands() {
|
||||
let m = External::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("subcommand"),
|
||||
OsString::from("normal"),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
OsString::from("--another_normal"),
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clap)]
|
||||
enum ExternalOs {
|
||||
#[clap(external_subcommand)]
|
||||
Other(Vec<OsString>),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_invalid_utf8_subcommand_args_with_allow_external_subcommands() {
|
||||
let m = ExternalOs::try_parse_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("subcommand"),
|
||||
OsString::from("normal"),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
OsString::from("--another_normal"),
|
||||
]);
|
||||
assert_eq!(
|
||||
m.unwrap(),
|
||||
ExternalOs::Other(vec![
|
||||
OsString::from("subcommand"),
|
||||
OsString::from("normal"),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
OsString::from("--another_normal"),
|
||||
])
|
||||
);
|
||||
}
|
|
@ -260,6 +260,7 @@ pub(crate) fn assert_app(app: &App) {
|
|||
detect_duplicate_flags(&short_flags, "short");
|
||||
|
||||
app._panic_on_missing_help(app.g_settings.is_set(AppSettings::HelpRequired));
|
||||
assert_app_flags(app);
|
||||
}
|
||||
|
||||
fn detect_duplicate_flags(flags: &[Flag], short_or_long: &str) {
|
||||
|
@ -301,3 +302,27 @@ fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)>
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_app_flags(app: &App) {
|
||||
use AppSettings::*;
|
||||
|
||||
macro_rules! checker {
|
||||
($a:ident requires $($b:ident)|+) => {
|
||||
if app.is_set($a) {
|
||||
let mut s = String::new();
|
||||
|
||||
$(
|
||||
if !app.is_set($b) {
|
||||
s.push_str(&format!("\nAppSettings::{} is required when AppSettings::{} is set.\n", std::stringify!($b), std::stringify!($a)));
|
||||
}
|
||||
)+
|
||||
|
||||
if !s.is_empty() {
|
||||
panic!("{}", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker!(AllowInvalidUtf8ForExternalSubcommands requires AllowExternalSubcommands);
|
||||
}
|
||||
|
|
|
@ -21,36 +21,35 @@ bitflags! {
|
|||
const TRAILING_VARARG = 1 << 12;
|
||||
const NO_BIN_NAME = 1 << 13;
|
||||
const ALLOW_UNK_SC = 1 << 14;
|
||||
const UTF8_STRICT = 1 << 15;
|
||||
const UTF8_NONE = 1 << 16;
|
||||
const LEADING_HYPHEN = 1 << 17;
|
||||
const NO_POS_VALUES = 1 << 18;
|
||||
const NEXT_LINE_HELP = 1 << 19;
|
||||
const DERIVE_DISP_ORDER = 1 << 20;
|
||||
const COLORED_HELP = 1 << 21;
|
||||
const COLOR_ALWAYS = 1 << 22;
|
||||
const COLOR_AUTO = 1 << 23;
|
||||
const COLOR_NEVER = 1 << 24;
|
||||
const DONT_DELIM_TRAIL = 1 << 25;
|
||||
const ALLOW_NEG_NUMS = 1 << 26;
|
||||
const DISABLE_HELP_SC = 1 << 28;
|
||||
const DONT_COLLAPSE_ARGS = 1 << 29;
|
||||
const ARGS_NEGATE_SCS = 1 << 30;
|
||||
const PROPAGATE_VALS_DOWN = 1 << 31;
|
||||
const ALLOW_MISSING_POS = 1 << 32;
|
||||
const TRAILING_VALUES = 1 << 33;
|
||||
const BUILT = 1 << 34;
|
||||
const BIN_NAME_BUILT = 1 << 35;
|
||||
const VALID_ARG_FOUND = 1 << 36;
|
||||
const INFER_SUBCOMMANDS = 1 << 37;
|
||||
const CONTAINS_LAST = 1 << 38;
|
||||
const ARGS_OVERRIDE_SELF = 1 << 39;
|
||||
const HELP_REQUIRED = 1 << 40;
|
||||
const SUBCOMMAND_PRECEDENCE_OVER_ARG = 1 << 41;
|
||||
const DISABLE_HELP_FLAG = 1 << 42;
|
||||
const USE_LONG_FORMAT_FOR_HELP_SC = 1 << 43;
|
||||
const INFER_LONG_ARGS = 1 << 44;
|
||||
const IGNORE_ERRORS = 1 << 45;
|
||||
const SC_UTF8_NONE = 1 << 15;
|
||||
const LEADING_HYPHEN = 1 << 16;
|
||||
const NO_POS_VALUES = 1 << 17;
|
||||
const NEXT_LINE_HELP = 1 << 18;
|
||||
const DERIVE_DISP_ORDER = 1 << 19;
|
||||
const COLORED_HELP = 1 << 20;
|
||||
const COLOR_ALWAYS = 1 << 21;
|
||||
const COLOR_AUTO = 1 << 22;
|
||||
const COLOR_NEVER = 1 << 23;
|
||||
const DONT_DELIM_TRAIL = 1 << 24;
|
||||
const ALLOW_NEG_NUMS = 1 << 25;
|
||||
const DISABLE_HELP_SC = 1 << 27;
|
||||
const DONT_COLLAPSE_ARGS = 1 << 28;
|
||||
const ARGS_NEGATE_SCS = 1 << 29;
|
||||
const PROPAGATE_VALS_DOWN = 1 << 30;
|
||||
const ALLOW_MISSING_POS = 1 << 31;
|
||||
const TRAILING_VALUES = 1 << 32;
|
||||
const BUILT = 1 << 33;
|
||||
const BIN_NAME_BUILT = 1 << 34;
|
||||
const VALID_ARG_FOUND = 1 << 35;
|
||||
const INFER_SUBCOMMANDS = 1 << 36;
|
||||
const CONTAINS_LAST = 1 << 37;
|
||||
const ARGS_OVERRIDE_SELF = 1 << 38;
|
||||
const HELP_REQUIRED = 1 << 39;
|
||||
const SUBCOMMAND_PRECEDENCE_OVER_ARG = 1 << 40;
|
||||
const DISABLE_HELP_FLAG = 1 << 41;
|
||||
const USE_LONG_FORMAT_FOR_HELP_SC = 1 << 42;
|
||||
const INFER_LONG_ARGS = 1 << 43;
|
||||
const IGNORE_ERRORS = 1 << 44;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +65,7 @@ impl BitOr for AppFlags {
|
|||
|
||||
impl Default for AppFlags {
|
||||
fn default() -> Self {
|
||||
AppFlags(Flags::UTF8_NONE | Flags::COLOR_AUTO)
|
||||
AppFlags(Flags::COLOR_AUTO)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,8 +78,8 @@ impl_settings! { AppSettings, AppFlags,
|
|||
=> Flags::ARGS_NEGATE_SCS,
|
||||
AllowExternalSubcommands("allowexternalsubcommands")
|
||||
=> Flags::ALLOW_UNK_SC,
|
||||
AllowInvalidUtf8("allowinvalidutf8")
|
||||
=> Flags::UTF8_NONE,
|
||||
AllowInvalidUtf8ForExternalSubcommands("allowinvalidutf8forexternalsubcommands")
|
||||
=> Flags::SC_UTF8_NONE,
|
||||
AllowLeadingHyphen("allowleadinghyphen")
|
||||
=> Flags::LEADING_HYPHEN,
|
||||
AllowNegativeNumbers("allownegativenumbers")
|
||||
|
@ -121,8 +120,6 @@ impl_settings! { AppSettings, AppFlags,
|
|||
=> Flags::NO_AUTO_VERSION,
|
||||
NoBinaryName("nobinaryname")
|
||||
=> Flags::NO_BIN_NAME,
|
||||
StrictUtf8("strictutf8")
|
||||
=> Flags::UTF8_STRICT,
|
||||
SubcommandsNegateReqs("subcommandsnegatereqs")
|
||||
=> Flags::SC_NEGATE_REQS,
|
||||
SubcommandRequired("subcommandrequired")
|
||||
|
@ -163,16 +160,13 @@ impl_settings! { AppSettings, AppFlags,
|
|||
/// [`App`]: crate::App
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum AppSettings {
|
||||
/// Specifies that any invalid UTF-8 code points should *not* be treated as an error.
|
||||
/// This is the default behavior of `clap`.
|
||||
/// Specifies that external subcommands that are invalid UTF-8 should *not* be treated as an error.
|
||||
///
|
||||
/// **NOTE:** Using argument values with invalid UTF-8 code points requires using
|
||||
/// [`ArgMatches::value_of_os`], [`ArgMatches::values_of_os`], [`ArgMatches::value_of_lossy`],
|
||||
/// or [`ArgMatches::values_of_lossy`] for those particular arguments which may contain invalid
|
||||
/// UTF-8 values
|
||||
/// **NOTE:** Using external subcommand argument values with invalid UTF-8 requires using
|
||||
/// [`ArgMatches::values_of_os`] or [`ArgMatches::values_of_lossy`] for those particular
|
||||
/// arguments which may contain invalid UTF-8 values
|
||||
///
|
||||
/// **NOTE:** This rule only applies to argument values. Flags, options, and
|
||||
/// [`subcommands`] themselves only allow valid UTF-8 code points.
|
||||
/// **NOTE:** Setting this requires [`AppSettings::AllowExternalSubcommands`]
|
||||
///
|
||||
/// # Platform Specific
|
||||
///
|
||||
|
@ -183,29 +177,30 @@ pub enum AppSettings {
|
|||
#[cfg_attr(not(unix), doc = " ```ignore")]
|
||||
#[cfg_attr(unix, doc = " ```")]
|
||||
/// # use clap::{App, AppSettings};
|
||||
/// use std::ffi::OsString;
|
||||
/// use std::os::unix::ffi::{OsStrExt,OsStringExt};
|
||||
/// // Assume there is an external subcommand named "subcmd"
|
||||
/// let m = App::new("myprog")
|
||||
/// .setting(AppSettings::AllowInvalidUtf8ForExternalSubcommands)
|
||||
/// .setting(AppSettings::AllowExternalSubcommands)
|
||||
/// .get_matches_from(vec![
|
||||
/// "myprog", "subcmd", "--option", "value", "-fff", "--flag"
|
||||
/// ]);
|
||||
///
|
||||
/// let r = App::new("myprog")
|
||||
/// //.setting(AppSettings::AllowInvalidUtf8)
|
||||
/// .arg("<arg> 'some positional arg'")
|
||||
/// .try_get_matches_from(
|
||||
/// vec![
|
||||
/// OsString::from("myprog"),
|
||||
/// OsString::from_vec(vec![0xe9])]);
|
||||
///
|
||||
/// assert!(r.is_ok());
|
||||
/// let m = r.unwrap();
|
||||
/// assert_eq!(m.value_of_os("arg").unwrap().as_bytes(), &[0xe9]);
|
||||
/// // All trailing arguments will be stored under the subcommand's sub-matches using an empty
|
||||
/// // string argument name
|
||||
/// match m.subcommand() {
|
||||
/// Some((external, ext_m)) => {
|
||||
/// let ext_args: Vec<&std::ffi::OsStr> = ext_m.values_of_os("").unwrap().collect();
|
||||
/// assert_eq!(external, "subcmd");
|
||||
/// assert_eq!(ext_args, ["--option", "value", "-fff", "--flag"]);
|
||||
/// },
|
||||
/// _ => {},
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`ArgMatches::value_of_os`]: crate::ArgMatches::value_of_os()
|
||||
/// [`ArgMatches::values_of_os`]: crate::ArgMatches::values_of_os()
|
||||
/// [`ArgMatches::value_of_lossy`]: crate::ArgMatches::value_of_lossy()
|
||||
/// [`ArgMatches::values_of_lossy`]: crate::ArgMatches::values_of_lossy()
|
||||
/// [`subcommands`]: crate::App::subcommand()
|
||||
// TODO: Either this or StrictUtf8
|
||||
AllowInvalidUtf8,
|
||||
AllowInvalidUtf8ForExternalSubcommands,
|
||||
|
||||
/// Specifies that leading hyphens are allowed in all argument *values*, such as negative numbers
|
||||
/// like `-10`. (which would otherwise be parsed as another flag or option)
|
||||
|
@ -968,40 +963,6 @@ pub enum AppSettings {
|
|||
/// [long format]: crate::App::long_about
|
||||
UseLongFormatForHelpSubcommand,
|
||||
|
||||
/// Specifies that any invalid UTF-8 code points should be treated as an error and fail
|
||||
/// with a [`ErrorKind::InvalidUtf8`] error.
|
||||
///
|
||||
/// **NOTE:** This rule only applies to argument values; Things such as flags, options, and
|
||||
/// [`subcommands`] themselves only allow valid UTF-8 code points.
|
||||
///
|
||||
/// # Platform Specific
|
||||
///
|
||||
/// Non Windows systems only
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(not(unix), doc = " ```ignore")]
|
||||
#[cfg_attr(unix, doc = " ```")]
|
||||
/// # use clap::{App, AppSettings, ErrorKind};
|
||||
/// use std::ffi::OsString;
|
||||
/// use std::os::unix::ffi::OsStringExt;
|
||||
///
|
||||
/// let m = App::new("myprog")
|
||||
/// .setting(AppSettings::StrictUtf8)
|
||||
/// .arg("<arg> 'some positional arg'")
|
||||
/// .try_get_matches_from(
|
||||
/// vec![
|
||||
/// OsString::from("myprog"),
|
||||
/// OsString::from_vec(vec![0xe9])]);
|
||||
///
|
||||
/// assert!(m.is_err());
|
||||
/// assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
/// ```
|
||||
///
|
||||
/// [`subcommands`]: crate::App::subcommand()
|
||||
/// [`ErrorKind::InvalidUtf8`]: crate::ErrorKind::InvalidUtf8
|
||||
StrictUtf8,
|
||||
|
||||
/// Allows specifying that if no [`subcommand`] is present at runtime,
|
||||
/// error and exit gracefully.
|
||||
///
|
||||
|
@ -1126,8 +1087,10 @@ mod test {
|
|||
AppSettings::AllowExternalSubcommands
|
||||
);
|
||||
assert_eq!(
|
||||
"allowinvalidutf8".parse::<AppSettings>().unwrap(),
|
||||
AppSettings::AllowInvalidUtf8
|
||||
"allowinvalidutf8forexternalsubcommands"
|
||||
.parse::<AppSettings>()
|
||||
.unwrap(),
|
||||
AppSettings::AllowInvalidUtf8ForExternalSubcommands
|
||||
);
|
||||
assert_eq!(
|
||||
"allowleadinghyphen".parse::<AppSettings>().unwrap(),
|
||||
|
@ -1215,10 +1178,6 @@ mod test {
|
|||
.unwrap(),
|
||||
AppSettings::UseLongFormatForHelpSubcommand
|
||||
);
|
||||
assert_eq!(
|
||||
"strictutf8".parse::<AppSettings>().unwrap(),
|
||||
AppSettings::StrictUtf8
|
||||
);
|
||||
assert_eq!(
|
||||
"trailingvararg".parse::<AppSettings>().unwrap(),
|
||||
AppSettings::TrailingVarArg
|
||||
|
|
|
@ -52,10 +52,10 @@ pub(crate) fn assert_arg(arg: &Arg) {
|
|||
);
|
||||
}
|
||||
|
||||
assert_app_flags(arg);
|
||||
assert_arg_flags(arg);
|
||||
}
|
||||
|
||||
fn assert_app_flags(arg: &Arg) {
|
||||
fn assert_arg_flags(arg: &Arg) {
|
||||
use ArgSettings::*;
|
||||
|
||||
macro_rules! checker {
|
||||
|
@ -85,4 +85,5 @@ fn assert_app_flags(arg: &Arg) {
|
|||
checker!(HideDefaultValue requires TakesValue);
|
||||
checker!(MultipleValues requires TakesValue);
|
||||
checker!(IgnoreCase requires TakesValue);
|
||||
checker!(AllowInvalidUtf8 requires TakesValue);
|
||||
}
|
||||
|
|
|
@ -2082,6 +2082,46 @@ impl<'help> Arg<'help> {
|
|||
self.takes_value(true).multiple_values(true)
|
||||
}
|
||||
|
||||
/// Specifies that option values that are invalid UTF-8 should *not* be treated as an error.
|
||||
///
|
||||
/// **NOTE:** Using argument values with invalid UTF-8 code points requires using
|
||||
/// [`ArgMatches::value_of_os`], [`ArgMatches::values_of_os`], [`ArgMatches::value_of_lossy`],
|
||||
/// or [`ArgMatches::values_of_lossy`] for those particular arguments which may contain invalid
|
||||
/// UTF-8 values.
|
||||
///
|
||||
/// **NOTE:** Setting this requires [`ArgSettings::TakesValue`]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(not(unix), doc = " ```ignore")]
|
||||
#[cfg_attr(unix, doc = " ```rust")]
|
||||
/// # use clap::{App, Arg};
|
||||
/// use std::ffi::OsString;
|
||||
/// use std::os::unix::ffi::{OsStrExt,OsStringExt};
|
||||
/// let r = App::new("myprog")
|
||||
/// .arg(Arg::new("arg").allow_invalid_utf8(true))
|
||||
/// .try_get_matches_from(vec![
|
||||
/// OsString::from("myprog"),
|
||||
/// OsString::from_vec(vec![0xe9])
|
||||
/// ]);
|
||||
///
|
||||
/// assert!(r.is_ok());
|
||||
/// let m = r.unwrap();
|
||||
/// assert_eq!(m.value_of_os("arg").unwrap().as_bytes(), &[0xe9]);
|
||||
/// ```
|
||||
/// [`ArgMatches::value_of_os`]: crate::ArgMatches::value_of_os()
|
||||
/// [`ArgMatches::values_of_os`]: crate::ArgMatches::values_of_os()
|
||||
/// [`ArgMatches::value_of_lossy`]: crate::ArgMatches::value_of_lossy()
|
||||
/// [`ArgMatches::values_of_lossy`]: crate::ArgMatches::values_of_lossy()
|
||||
#[inline]
|
||||
pub fn allow_invalid_utf8(self, tv: bool) -> Self {
|
||||
if tv {
|
||||
self.setting(ArgSettings::AllowInvalidUtf8)
|
||||
} else {
|
||||
self.unset_setting(ArgSettings::AllowInvalidUtf8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows one to perform a custom validation on the argument value. You provide a closure
|
||||
/// which accepts a [`String`] value, and return a [`Result`] where the [`Err(String)`] is a
|
||||
/// message displayed to the user.
|
||||
|
|
|
@ -30,6 +30,7 @@ bitflags! {
|
|||
const MULTIPLE_VALS = 1 << 20;
|
||||
#[cfg(feature = "env")]
|
||||
const HIDE_ENV = 1 << 21;
|
||||
const UTF8_NONE = 1 << 22;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +60,8 @@ impl_settings! { ArgSettings, ArgFlags,
|
|||
HideEnvValues("hideenvvalues") => Flags::HIDE_ENV_VALS,
|
||||
HideDefaultValue("hidedefaultvalue") => Flags::HIDE_DEFAULT_VAL,
|
||||
HiddenShortHelp("hiddenshorthelp") => Flags::HIDDEN_SHORT_H,
|
||||
HiddenLongHelp("hiddenlonghelp") => Flags::HIDDEN_LONG_H
|
||||
HiddenLongHelp("hiddenlonghelp") => Flags::HIDDEN_LONG_H,
|
||||
AllowInvalidUtf8("allowinvalidutf8") => Flags::UTF8_NONE
|
||||
}
|
||||
|
||||
impl Default for ArgFlags {
|
||||
|
@ -121,6 +123,9 @@ pub enum ArgSettings {
|
|||
HiddenShortHelp,
|
||||
/// The argument should **not** be shown in long help text
|
||||
HiddenLongHelp,
|
||||
/// Specifies that option values that are invalid UTF-8 should *not* be treated as an error.
|
||||
AllowInvalidUtf8,
|
||||
|
||||
#[doc(hidden)]
|
||||
RequiredUnlessAll,
|
||||
}
|
||||
|
@ -202,6 +207,10 @@ mod test {
|
|||
"hiddenlonghelp".parse::<ArgSettings>().unwrap(),
|
||||
ArgSettings::HiddenLongHelp
|
||||
);
|
||||
assert_eq!(
|
||||
"allowinvalidutf8".parse::<ArgSettings>().unwrap(),
|
||||
ArgSettings::AllowInvalidUtf8
|
||||
);
|
||||
assert!("hahahaha".parse::<ArgSettings>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -310,8 +310,12 @@ pub enum ErrorKind {
|
|||
/// ```
|
||||
UnexpectedMultipleUsage,
|
||||
|
||||
/// Occurs when the user provides a value containing invalid UTF-8 for an argument and
|
||||
/// [`AppSettings::StrictUtf8`] is set.
|
||||
/// Occurs when the user provides a value containing invalid UTF-8.
|
||||
///
|
||||
/// To allow arbitrary data
|
||||
/// - Set [`ArgSettings::AllowInvalidUtf8`] for argument values
|
||||
/// - Set [`AppSettings::AllowInvalidUtf8ForExternalSubcommands`] for external-subcommand
|
||||
/// values
|
||||
///
|
||||
/// # Platform Specific
|
||||
///
|
||||
|
@ -325,7 +329,6 @@ pub enum ErrorKind {
|
|||
/// # use std::os::unix::ffi::OsStringExt;
|
||||
/// # use std::ffi::OsString;
|
||||
/// let result = App::new("prog")
|
||||
/// .setting(AppSettings::StrictUtf8)
|
||||
/// .arg(Arg::new("utf8")
|
||||
/// .short('u')
|
||||
/// .takes_value(true))
|
||||
|
@ -336,7 +339,8 @@ pub enum ErrorKind {
|
|||
/// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
/// ```
|
||||
///
|
||||
/// [`AppSettings::StrictUtf8`]: crate::AppSettings::StrictUtf8
|
||||
/// [`ArgSettings::AllowInvalidUtf8`]: crate::ArgSettings::AllowInvalidUtf8
|
||||
/// [`AppSettings::AllowInvalidUtf8ForExternalSubcommands`]: crate::AppSettings::AllowInvalidUtf8ForExternalSubcommands
|
||||
InvalidUtf8,
|
||||
|
||||
/// Not a true "error" as it means `--help` or similar was used.
|
||||
|
|
|
@ -101,7 +101,8 @@ impl ArgMatches {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will [`panic!`] if the value contains invalid UTF-8 code points.
|
||||
/// This method will [`panic!`] if the value is invalid UTF-8. See
|
||||
/// [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -129,9 +130,11 @@ impl ArgMatches {
|
|||
}
|
||||
|
||||
/// Gets the lossy value of a specific argument. If the argument wasn't present at runtime
|
||||
/// it returns `None`. A lossy value is one which contains invalid UTF-8 code points, those
|
||||
/// it returns `None`. A lossy value is one which contains invalid UTF-8, those
|
||||
/// invalid points will be replaced with `\u{FFFD}`
|
||||
///
|
||||
/// *NOTE:* Recommend having set [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// *NOTE:* If getting a value for an option or positional argument that allows multiples,
|
||||
/// prefer [`Arg::values_of_lossy`] as `value_of_lossy()` will only return the *first* value.
|
||||
///
|
||||
|
@ -147,7 +150,8 @@ impl ArgMatches {
|
|||
/// use std::os::unix::ffi::{OsStrExt,OsStringExt};
|
||||
///
|
||||
/// let m = App::new("utf8")
|
||||
/// .arg(Arg::from("<arg> 'some arg'"))
|
||||
/// .arg(Arg::from("<arg> 'some arg'")
|
||||
/// .allow_invalid_utf8(true))
|
||||
/// .get_matches_from(vec![OsString::from("myprog"),
|
||||
/// // "Hi {0xe9}!"
|
||||
/// OsString::from_vec(vec![b'H', b'i', b' ', 0xe9, b'!'])]);
|
||||
|
@ -171,6 +175,8 @@ impl ArgMatches {
|
|||
/// Rust are guaranteed to be valid UTF-8, a valid filename on a Unix system as an argument
|
||||
/// value may contain invalid UTF-8 code points.
|
||||
///
|
||||
/// *NOTE:* Recommend having set [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// *NOTE:* If getting a value for an option or positional argument that allows multiples,
|
||||
/// prefer [`ArgMatches::values_of_os`] as `Arg::value_of_os` will only return the *first*
|
||||
/// value.
|
||||
|
@ -187,7 +193,8 @@ impl ArgMatches {
|
|||
/// use std::os::unix::ffi::{OsStrExt,OsStringExt};
|
||||
///
|
||||
/// let m = App::new("utf8")
|
||||
/// .arg(Arg::from("<arg> 'some arg'"))
|
||||
/// .arg(Arg::from("<arg> 'some arg'")
|
||||
/// .allow_invalid_utf8(true))
|
||||
/// .get_matches_from(vec![OsString::from("myprog"),
|
||||
/// // "Hi {0xe9}!"
|
||||
/// OsString::from_vec(vec![b'H', b'i', b' ', 0xe9, b'!'])]);
|
||||
|
@ -208,7 +215,8 @@ impl ArgMatches {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if any of the values contain invalid UTF-8 code points.
|
||||
/// This method will [`panic!`] if the value is invalid UTF-8. See
|
||||
/// [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -260,6 +268,8 @@ impl ArgMatches {
|
|||
/// it returns `None`. A lossy value is one where if it contains invalid UTF-8 code points,
|
||||
/// those invalid points will be replaced with `\u{FFFD}`
|
||||
///
|
||||
/// *NOTE:* Recommend having set [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(not(unix), doc = " ```ignore")]
|
||||
|
@ -269,7 +279,8 @@ impl ArgMatches {
|
|||
/// use std::os::unix::ffi::OsStringExt;
|
||||
///
|
||||
/// let m = App::new("utf8")
|
||||
/// .arg(Arg::from("<arg>... 'some arg'"))
|
||||
/// .arg(Arg::from("<arg>... 'some arg'")
|
||||
/// .allow_invalid_utf8(true))
|
||||
/// .get_matches_from(vec![OsString::from("myprog"),
|
||||
/// // "Hi"
|
||||
/// OsString::from_vec(vec![b'H', b'i']),
|
||||
|
@ -294,6 +305,8 @@ impl ArgMatches {
|
|||
/// valid UTF-8 code points. Since [`String`]s in Rust are guaranteed to be valid UTF-8, a valid
|
||||
/// filename as an argument value on Linux (for example) may contain invalid UTF-8 code points.
|
||||
///
|
||||
/// *NOTE:* Recommend having set [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(not(unix), doc = " ```ignore")]
|
||||
|
@ -303,7 +316,8 @@ impl ArgMatches {
|
|||
/// use std::os::unix::ffi::{OsStrExt,OsStringExt};
|
||||
///
|
||||
/// let m = App::new("utf8")
|
||||
/// .arg(Arg::from("<arg>... 'some arg'"))
|
||||
/// .arg(Arg::from("<arg>... 'some arg'")
|
||||
/// .allow_invalid_utf8(true))
|
||||
/// .get_matches_from(vec![OsString::from("myprog"),
|
||||
/// // "Hi"
|
||||
/// OsString::from_vec(vec![b'H', b'i']),
|
||||
|
@ -340,7 +354,8 @@ impl ArgMatches {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will [`panic!`] if the value contains invalid UTF-8 code points.
|
||||
/// This method will [`panic!`] if the value is invalid UTF-8. See
|
||||
/// [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -395,7 +410,8 @@ impl ArgMatches {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will [`panic!`] if the value contains invalid UTF-8 code points.
|
||||
/// This method will [`panic!`] if the value is invalid UTF-8. See
|
||||
/// [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -432,7 +448,8 @@ impl ArgMatches {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will [`panic!`] if any of the values contains invalid UTF-8 code points.
|
||||
/// This method will [`panic!`] if the value is invalid UTF-8. See
|
||||
/// [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -485,7 +502,8 @@ impl ArgMatches {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will [`panic!`] if any of the values contains invalid UTF-8 code points.
|
||||
/// This method will [`panic!`] if the value is invalid UTF-8. See
|
||||
/// [`ArgSettings::AllowInvalidUtf8`][crate::ArgSettings::AllowInvalidUtf8].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -1065,7 +1083,8 @@ impl<'a> Default for GroupedValues<'a> {
|
|||
/// use std::os::unix::ffi::{OsStrExt,OsStringExt};
|
||||
///
|
||||
/// let m = App::new("utf8")
|
||||
/// .arg(Arg::from("<arg> 'some arg'"))
|
||||
/// .arg(Arg::from("<arg> 'some arg'")
|
||||
/// .allow_invalid_utf8(true))
|
||||
/// .get_matches_from(vec![OsString::from("myprog"),
|
||||
/// // "Hi {0xe9}!"
|
||||
/// OsString::from_vec(vec![b'H', b'i', b' ', 0xe9, b'!'])]);
|
||||
|
|
|
@ -688,13 +688,10 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let sc_name = match arg_os.to_str() {
|
||||
Some(s) => s.to_string(),
|
||||
None => {
|
||||
if self.is_set(AS::StrictUtf8) {
|
||||
return Err(ClapError::invalid_utf8(
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
));
|
||||
}
|
||||
arg_os.to_string_lossy().into_owned()
|
||||
return Err(ClapError::invalid_utf8(
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -702,7 +699,9 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let mut sc_m = ArgMatcher::default();
|
||||
|
||||
while let Some((v, _)) = it.next() {
|
||||
if self.is_set(AS::StrictUtf8) && v.to_str().is_none() {
|
||||
if !self.is_set(AS::AllowInvalidUtf8ForExternalSubcommands)
|
||||
&& v.to_str().is_none()
|
||||
{
|
||||
return Err(ClapError::invalid_utf8(
|
||||
Usage::new(self).create_usage_with_title(&[]),
|
||||
self.app.color(),
|
||||
|
|
|
@ -88,7 +88,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
|
|||
) -> ClapResult<()> {
|
||||
debug!("Validator::validate_arg_values: arg={:?}", arg.name);
|
||||
for val in ma.vals_flatten() {
|
||||
if self.p.is_set(AS::StrictUtf8) && val.to_str().is_none() {
|
||||
if !arg.is_set(ArgSettings::AllowInvalidUtf8) && val.to_str().is_none() {
|
||||
debug!(
|
||||
"Validator::validate_arg_values: invalid UTF-8 found in val {:?}",
|
||||
val
|
||||
|
|
|
@ -595,13 +595,9 @@ fn unset_setting() {
|
|||
#[test]
|
||||
fn unset_settings() {
|
||||
let m = App::new("unset_settings");
|
||||
assert!(&m.is_set(AppSettings::AllowInvalidUtf8));
|
||||
assert!(&m.is_set(AppSettings::ColorAuto));
|
||||
|
||||
let m = m
|
||||
.unset_global_setting(AppSettings::AllowInvalidUtf8)
|
||||
.unset_global_setting(AppSettings::ColorAuto);
|
||||
assert!(!m.is_set(AppSettings::AllowInvalidUtf8), "{:#?}", m);
|
||||
let m = m.unset_global_setting(AppSettings::ColorAuto);
|
||||
assert!(!m.is_set(AppSettings::ColorAuto), "{:#?}", m);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ fn bad_osstring(ascii: &[u8]) -> OsString {
|
|||
#[test]
|
||||
fn invalid_utf16_lossy_positional() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("<arg> 'some arg'"))
|
||||
.arg(Arg::from("<arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -34,7 +34,7 @@ fn invalid_utf16_lossy_positional() {
|
|||
#[test]
|
||||
fn invalid_utf16_lossy_option_short_space() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("-a"),
|
||||
|
@ -49,7 +49,7 @@ fn invalid_utf16_lossy_option_short_space() {
|
|||
#[test]
|
||||
fn invalid_utf16_lossy_option_short_equals() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"-a=")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -60,7 +60,7 @@ fn invalid_utf16_lossy_option_short_equals() {
|
|||
#[test]
|
||||
fn invalid_utf16_lossy_option_short_no_space() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"-a")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -71,7 +71,7 @@ fn invalid_utf16_lossy_option_short_no_space() {
|
|||
#[test]
|
||||
fn invalid_utf16_lossy_option_long_space() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("--arg"),
|
||||
|
@ -86,7 +86,7 @@ fn invalid_utf16_lossy_option_long_space() {
|
|||
#[test]
|
||||
fn invalid_utf16_lossy_option_long_equals() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"--arg=")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -97,7 +97,7 @@ fn invalid_utf16_lossy_option_long_equals() {
|
|||
#[test]
|
||||
fn invalid_utf16_positional() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("<arg> 'some arg'"))
|
||||
.arg(Arg::from("<arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -108,7 +108,7 @@ fn invalid_utf16_positional() {
|
|||
#[test]
|
||||
fn invalid_utf16_option_short_space() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("-a"),
|
||||
|
@ -123,7 +123,7 @@ fn invalid_utf16_option_short_space() {
|
|||
#[test]
|
||||
fn invalid_utf16_option_short_equals() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"-a=")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -134,7 +134,7 @@ fn invalid_utf16_option_short_equals() {
|
|||
#[test]
|
||||
fn invalid_utf16_option_short_no_space() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"-a")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -145,7 +145,7 @@ fn invalid_utf16_option_short_no_space() {
|
|||
#[test]
|
||||
fn invalid_utf16_option_long_space() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("--arg"),
|
||||
|
@ -160,7 +160,7 @@ fn invalid_utf16_option_long_space() {
|
|||
#[test]
|
||||
fn invalid_utf16_option_long_equals() {
|
||||
let r = App::new("bad_utf16")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), bad_osstring(b"--arg=")]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
|
118
tests/utf8.rs
118
tests/utf8.rs
|
@ -7,8 +7,7 @@ use std::os::unix::ffi::OsStringExt;
|
|||
#[test]
|
||||
fn invalid_utf8_strict_positional() {
|
||||
let m = App::new("bad_utf8")
|
||||
.arg(Arg::from("<arg> 'some arg'"))
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.arg(Arg::new("arg"))
|
||||
.try_get_matches_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
|
@ -17,8 +16,7 @@ fn invalid_utf8_strict_positional() {
|
|||
#[test]
|
||||
fn invalid_utf8_strict_option_short_space() {
|
||||
let m = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.arg(Arg::new("arg").short('a').long("arg").takes_value(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("-a"),
|
||||
|
@ -31,8 +29,7 @@ fn invalid_utf8_strict_option_short_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_strict_option_short_equals() {
|
||||
let m = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.arg(Arg::new("arg").short('a').long("arg").takes_value(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]),
|
||||
|
@ -44,8 +41,7 @@ fn invalid_utf8_strict_option_short_equals() {
|
|||
#[test]
|
||||
fn invalid_utf8_strict_option_short_no_space() {
|
||||
let m = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.arg(Arg::new("arg").short('a').long("arg").takes_value(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0xe9]),
|
||||
|
@ -57,8 +53,7 @@ fn invalid_utf8_strict_option_short_no_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_strict_option_long_space() {
|
||||
let m = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.arg(Arg::new("arg").short('a').long("arg").takes_value(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("--arg"),
|
||||
|
@ -71,8 +66,7 @@ fn invalid_utf8_strict_option_long_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_strict_option_long_equals() {
|
||||
let m = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.arg(Arg::new("arg").short('a').long("arg").takes_value(true))
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]),
|
||||
|
@ -84,7 +78,7 @@ fn invalid_utf8_strict_option_long_equals() {
|
|||
#[test]
|
||||
fn invalid_utf8_lossy_positional() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("<arg> 'some arg'"))
|
||||
.arg(Arg::new("arg").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -95,7 +89,13 @@ fn invalid_utf8_lossy_positional() {
|
|||
#[test]
|
||||
fn invalid_utf8_lossy_option_short_space() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("-a"),
|
||||
|
@ -110,7 +110,13 @@ fn invalid_utf8_lossy_option_short_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_lossy_option_short_equals() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]),
|
||||
|
@ -124,7 +130,13 @@ fn invalid_utf8_lossy_option_short_equals() {
|
|||
#[test]
|
||||
fn invalid_utf8_lossy_option_short_no_space() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0xe9]),
|
||||
|
@ -138,7 +150,13 @@ fn invalid_utf8_lossy_option_short_no_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_lossy_option_long_space() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("--arg"),
|
||||
|
@ -153,7 +171,13 @@ fn invalid_utf8_lossy_option_long_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_lossy_option_long_equals() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]),
|
||||
|
@ -167,7 +191,7 @@ fn invalid_utf8_lossy_option_long_equals() {
|
|||
#[test]
|
||||
fn invalid_utf8_positional() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("<arg> 'some arg'"))
|
||||
.arg(Arg::new("arg").allow_invalid_utf8(true))
|
||||
.try_get_matches_from(vec![OsString::from(""), OsString::from_vec(vec![0xe9])]);
|
||||
assert!(r.is_ok());
|
||||
let m = r.unwrap();
|
||||
|
@ -181,7 +205,13 @@ fn invalid_utf8_positional() {
|
|||
#[test]
|
||||
fn invalid_utf8_option_short_space() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("-a"),
|
||||
|
@ -199,7 +229,13 @@ fn invalid_utf8_option_short_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_option_short_equals() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0x3d, 0xe9]),
|
||||
|
@ -216,7 +252,13 @@ fn invalid_utf8_option_short_equals() {
|
|||
#[test]
|
||||
fn invalid_utf8_option_short_no_space() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x61, 0xe9]),
|
||||
|
@ -233,7 +275,13 @@ fn invalid_utf8_option_short_no_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_option_long_space() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("--arg"),
|
||||
|
@ -251,7 +299,13 @@ fn invalid_utf8_option_long_space() {
|
|||
#[test]
|
||||
fn invalid_utf8_option_long_equals() {
|
||||
let r = App::new("bad_utf8")
|
||||
.arg(Arg::from("-a, --arg <arg> 'some arg'"))
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.short('a')
|
||||
.long("arg")
|
||||
.takes_value(true)
|
||||
.allow_invalid_utf8(true),
|
||||
)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0x2d, 0x2d, 0x61, 0x72, 0x67, 0x3d, 0xe9]),
|
||||
|
@ -268,7 +322,6 @@ fn invalid_utf8_option_long_equals() {
|
|||
#[test]
|
||||
fn refuse_invalid_utf8_subcommand_with_allow_external_subcommands() {
|
||||
let m = App::new("bad_utf8")
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.setting(AppSettings::AllowExternalSubcommands)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
|
@ -280,28 +333,22 @@ fn refuse_invalid_utf8_subcommand_with_allow_external_subcommands() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn allow_invalid_utf8_subcommand_with_allow_external_subcommands() {
|
||||
fn refuse_invalid_utf8_subcommand_when_args_are_allowed_with_allow_external_subcommands() {
|
||||
let m = App::new("bad_utf8")
|
||||
.setting(AppSettings::AllowExternalSubcommands)
|
||||
.setting(AppSettings::AllowInvalidUtf8ForExternalSubcommands)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from_vec(vec![0xe9]),
|
||||
OsString::from("normal"),
|
||||
]);
|
||||
assert!(m.is_ok());
|
||||
let m = m.unwrap();
|
||||
let (_subcommand, args) = m.subcommand().unwrap();
|
||||
let args = args.values_of_os("").unwrap().collect::<Vec<_>>();
|
||||
assert_eq!(args, vec![OsString::from("normal"),]);
|
||||
// TODO: currently subcommand can only be &str, which means following code
|
||||
// will panic.
|
||||
// assert_eq!(_subcommand, OsString::from_vec(vec![0xe9]));
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuse_invalid_utf8_subcommand_args_with_allow_external_subcommands() {
|
||||
let m = App::new("bad_utf8")
|
||||
.setting(AppSettings::StrictUtf8)
|
||||
.setting(AppSettings::AllowExternalSubcommands)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
|
@ -318,6 +365,7 @@ fn refuse_invalid_utf8_subcommand_args_with_allow_external_subcommands() {
|
|||
fn allow_invalid_utf8_subcommand_args_with_allow_external_subcommands() {
|
||||
let m = App::new("bad_utf8")
|
||||
.setting(AppSettings::AllowExternalSubcommands)
|
||||
.setting(AppSettings::AllowInvalidUtf8ForExternalSubcommands)
|
||||
.try_get_matches_from(vec![
|
||||
OsString::from(""),
|
||||
OsString::from("subcommand"),
|
||||
|
|
Loading…
Reference in a new issue