aboutsummaryrefslogtreecommitdiffstats
path: root/src/bin/rbw
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/rbw')
-rw-r--r--src/bin/rbw/commands.rs818
-rw-r--r--src/bin/rbw/main.rs68
2 files changed, 720 insertions, 166 deletions
diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs
index 6d36eb3..fc858e1 100644
--- a/src/bin/rbw/commands.rs
+++ b/src/bin/rbw/commands.rs
@@ -39,7 +39,9 @@ pub fn parse_needle(arg: &str) -> Result<Needle, std::convert::Infallible> {
return Ok(Needle::Uuid(uuid));
}
if let Ok(url) = Url::parse(arg) {
- return Ok(Needle::Uri(url));
+ if url.is_special() {
+ return Ok(Needle::Uri(url));
+ }
}
Ok(Needle::Name(arg.to_string()))
@@ -521,10 +523,14 @@ impl DecryptedCipher {
username: Option<&str>,
folder: Option<&str>,
try_match_folder: bool,
+ ignore_case: bool,
) -> bool {
match needle {
Needle::Name(name) => {
- if &self.name != name {
+ if !((ignore_case
+ && name.to_lowercase() == self.name.to_lowercase())
+ || *name == self.name)
+ {
return false;
}
}
@@ -591,8 +597,12 @@ impl DecryptedCipher {
username: Option<&str>,
folder: Option<&str>,
try_match_folder: bool,
+ ignore_case: bool,
) -> bool {
- if !self.name.contains(name) {
+ if !((ignore_case
+ && self.name.to_lowercase().contains(&name.to_lowercase()))
+ || self.name.contains(name))
+ {
return false;
}
@@ -602,7 +612,12 @@ impl DecryptedCipher {
username: Some(found_username),
..
} => {
- if !found_username.contains(given_username) {
+ if !((ignore_case
+ && found_username
+ .to_lowercase()
+ .contains(&given_username.to_lowercase()))
+ || found_username.contains(given_username))
+ {
return false;
}
}
@@ -629,6 +644,41 @@ impl DecryptedCipher {
true
}
+
+ fn search_match(&self, term: &str, folder: Option<&str>) -> bool {
+ if let Some(folder) = folder {
+ if self.folder.as_deref() != Some(folder) {
+ return false;
+ }
+ }
+
+ let fields = [
+ Some(self.name.as_str()),
+ self.notes.as_deref(),
+ if let DecryptedData::Login {
+ username: Some(username),
+ ..
+ } = &self.data
+ {
+ Some(username)
+ } else {
+ None
+ },
+ ];
+ for field in fields
+ .iter()
+ .filter_map(|field| field.map(std::string::ToString::to_string))
+ .chain(self.fields.iter().filter_map(|field| {
+ field.value.as_ref().map(std::string::ToString::to_string)
+ }))
+ {
+ if field.to_lowercase().contains(&term.to_lowercase()) {
+ return true;
+ }
+ }
+
+ false
+ }
}
fn val_display_or_store(clipboard: bool, password: &str) -> bool {
@@ -691,6 +741,25 @@ enum DecryptedData {
struct DecryptedField {
name: Option<String>,
value: Option<String>,
+ #[serde(serialize_with = "serialize_field_type", rename = "type")]
+ ty: rbw::api::FieldType,
+}
+
+#[allow(clippy::trivially_copy_pass_by_ref)]
+fn serialize_field_type<S>(
+ ty: &rbw::api::FieldType,
+ serializer: S,
+) -> Result<S::Ok, S::Error>
+where
+ S: serde::Serializer,
+{
+ let s = match ty {
+ rbw::api::FieldType::Text => "text",
+ rbw::api::FieldType::Hidden => "hidden",
+ rbw::api::FieldType::Boolean => "boolean",
+ rbw::api::FieldType::Linked => "linked",
+ };
+ serializer.serialize_str(s)
}
#[derive(Debug, Clone, Serialize)]
@@ -823,8 +892,10 @@ pub fn config_set(key: &str, value: &str) -> anyhow::Result<()> {
.unwrap_or_else(|_| rbw::config::Config::new());
match key {
"email" => config.email = Some(value.to_string()),
+ "sso_id" => config.sso_id = Some(value.to_string()),
"base_url" => config.base_url = Some(value.to_string()),
"identity_url" => config.identity_url = Some(value.to_string()),
+ "ui_url" => config.ui_url = Some(value.to_string()),
"notifications_url" => {
config.notifications_url = Some(value.to_string());
}
@@ -868,8 +939,10 @@ pub fn config_unset(key: &str) -> anyhow::Result<()> {
.unwrap_or_else(|_| rbw::config::Config::new());
match key {
"email" => config.email = None,
+ "sso_id" => config.sso_id = None,
"base_url" => config.base_url = None,
"identity_url" => config.identity_url = None,
+ "ui_url" => config.ui_url = None,
"notifications_url" => config.notifications_url = None,
"client_cert_path" => config.client_cert_path = None,
"lock_timeout" => {
@@ -983,6 +1056,7 @@ pub fn list(fields: &[String]) -> anyhow::Result<()> {
Ok(())
}
+#[allow(clippy::fn_params_excessive_bools)]
pub fn get(
needle: &Needle,
user: Option<&str>,
@@ -991,6 +1065,7 @@ pub fn get(
full: bool,
raw: bool,
clipboard: bool,
+ ignore_case: bool,
) -> anyhow::Result<()> {
unlock()?;
@@ -1002,8 +1077,9 @@ pub fn get(
needle
);
- let (_, decrypted) = find_entry(&db, needle, user, folder)
- .with_context(|| format!("couldn't find entry for '{desc}'"))?;
+ let (_, decrypted) =
+ find_entry(&db, needle, user, folder, ignore_case)
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
if raw {
decrypted.display_json(&desc)?;
} else if full {
@@ -1017,11 +1093,52 @@ pub fn get(
Ok(())
}
+pub fn search(term: &str, folder: Option<&str>) -> anyhow::Result<()> {
+ unlock()?;
+
+ let db = load_db()?;
+
+ let found_entries: Vec<_> = db
+ .entries
+ .iter()
+ .map(decrypt_cipher)
+ .filter_map(|entry| {
+ entry
+ .map(|decrypted| {
+ if decrypted.search_match(term, folder) {
+ let mut display = decrypted.name;
+ if let DecryptedData::Login {
+ username: Some(username),
+ ..
+ } = decrypted.data
+ {
+ display = format!("{username}@{display}");
+ }
+ if let Some(folder) = decrypted.folder {
+ display = format!("{folder}/{display}");
+ }
+ Some(display)
+ } else {
+ None
+ }
+ })
+ .transpose()
+ })
+ .collect::<Result<_, anyhow::Error>>()?;
+
+ for name in found_entries {
+ println!("{name}");
+ }
+
+ Ok(())
+}
+
pub fn code(
needle: &Needle,
user: Option<&str>,
folder: Option<&str>,
clipboard: bool,
+ ignore_case: bool,
) -> anyhow::Result<()> {
unlock()?;
@@ -1033,8 +1150,9 @@ pub fn code(
needle
);
- let (_, decrypted) = find_entry(&db, needle, user, folder)
- .with_context(|| format!("couldn't find entry for '{desc}'"))?;
+ let (_, decrypted) =
+ find_entry(&db, needle, user, folder, ignore_case)
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
if let DecryptedData::Login { totp, .. } = decrypted.data {
if let Some(totp) = totp {
@@ -1095,7 +1213,7 @@ pub fn add(
let (new_access_token, folders) =
rbw::actions::list_folders(&access_token, refresh_token)?;
if let Some(new_access_token) = new_access_token {
- access_token = new_access_token.clone();
+ access_token.clone_from(&new_access_token);
db.access_token = Some(new_access_token);
save_db(&db)?;
}
@@ -1118,7 +1236,7 @@ pub fn add(
&crate::actions::encrypt(folder_name, None)?,
)?;
if let Some(new_access_token) = new_access_token {
- access_token = new_access_token.clone();
+ access_token.clone_from(&new_access_token);
db.access_token = Some(new_access_token);
save_db(&db)?;
}
@@ -1188,7 +1306,7 @@ pub fn generate(
let (new_access_token, folders) =
rbw::actions::list_folders(&access_token, refresh_token)?;
if let Some(new_access_token) = new_access_token {
- access_token = new_access_token.clone();
+ access_token.clone_from(&new_access_token);
db.access_token = Some(new_access_token);
save_db(&db)?;
}
@@ -1213,7 +1331,7 @@ pub fn generate(
&crate::actions::encrypt(folder_name, None)?,
)?;
if let Some(new_access_token) = new_access_token {
- access_token = new_access_token.clone();
+ access_token.clone_from(&new_access_token);
db.access_token = Some(new_access_token);
save_db(&db)?;
}
@@ -1248,6 +1366,7 @@ pub fn edit(
name: &str,
username: Option<&str>,
folder: Option<&str>,
+ ignore_case: bool,
) -> anyhow::Result<()> {
unlock()?;
@@ -1261,9 +1380,14 @@ pub fn edit(
name
);
- let (entry, decrypted) =
- find_entry(&db, &Needle::Name(name.to_string()), username, folder)
- .with_context(|| format!("couldn't find entry for '{desc}'"))?;
+ let (entry, decrypted) = find_entry(
+ &db,
+ &Needle::Name(name.to_string()),
+ username,
+ folder,
+ ignore_case,
+ )
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
let (data, fields, notes, history) = match &decrypted.data {
DecryptedData::Login { password, .. } => {
@@ -1372,6 +1496,7 @@ pub fn remove(
name: &str,
username: Option<&str>,
folder: Option<&str>,
+ ignore_case: bool,
) -> anyhow::Result<()> {
unlock()?;
@@ -1385,9 +1510,14 @@ pub fn remove(
name
);
- let (entry, _) =
- find_entry(&db, &Needle::Name(name.to_string()), username, folder)
- .with_context(|| format!("couldn't find entry for '{desc}'"))?;
+ let (entry, _) = find_entry(
+ &db,
+ &Needle::Name(name.to_string()),
+ username,
+ folder,
+ ignore_case,
+ )
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
if let (Some(access_token), ()) =
rbw::actions::remove(access_token, refresh_token, &entry.id)?
@@ -1405,6 +1535,7 @@ pub fn history(
name: &str,
username: Option<&str>,
folder: Option<&str>,
+ ignore_case: bool,
) -> anyhow::Result<()> {
unlock()?;
@@ -1416,9 +1547,14 @@ pub fn history(
name
);
- let (_, decrypted) =
- find_entry(&db, &Needle::Name(name.to_string()), username, folder)
- .with_context(|| format!("couldn't find entry for '{desc}'"))?;
+ let (_, decrypted) = find_entry(
+ &db,
+ &Needle::Name(name.to_string()),
+ username,
+ folder,
+ ignore_case,
+ )
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
for history in decrypted.history {
println!("{}: {}", history.last_used_date, history.password);
}
@@ -1516,6 +1652,7 @@ fn find_entry(
needle: &Needle,
username: Option<&str>,
folder: Option<&str>,
+ ignore_case: bool,
) -> anyhow::Result<(rbw::db::Entry, DecryptedCipher)> {
if let Needle::Uuid(uuid) = needle {
for cipher in &db.entries {
@@ -1533,7 +1670,7 @@ fn find_entry(
decrypt_cipher(&entry).map(|decrypted| (entry, decrypted))
})
.collect::<anyhow::Result<_>>()?;
- find_entry_raw(&ciphers, needle, username, folder)
+ find_entry_raw(&ciphers, needle, username, folder, ignore_case)
}
}
@@ -1542,11 +1679,18 @@ fn find_entry_raw(
needle: &Needle,
username: Option<&str>,
folder: Option<&str>,
+ ignore_case: bool,
) -> anyhow::Result<(rbw::db::Entry, DecryptedCipher)> {
let mut matches: Vec<(rbw::db::Entry, DecryptedCipher)> = entries
.iter()
.filter(|&(_, decrypted_cipher)| {
- decrypted_cipher.exact_match(needle, username, folder, true)
+ decrypted_cipher.exact_match(
+ needle,
+ username,
+ folder,
+ true,
+ ignore_case,
+ )
})
.cloned()
.collect();
@@ -1559,7 +1703,13 @@ fn find_entry_raw(
matches = entries
.iter()
.filter(|&(_, decrypted_cipher)| {
- decrypted_cipher.exact_match(needle, username, folder, false)
+ decrypted_cipher.exact_match(
+ needle,
+ username,
+ folder,
+ false,
+ ignore_case,
+ )
})
.cloned()
.collect();
@@ -1573,7 +1723,13 @@ fn find_entry_raw(
matches = entries
.iter()
.filter(|&(_, decrypted_cipher)| {
- decrypted_cipher.partial_match(name, username, folder, true)
+ decrypted_cipher.partial_match(
+ name,
+ username,
+ folder,
+ true,
+ ignore_case,
+ )
})
.cloned()
.collect();
@@ -1586,8 +1742,13 @@ fn find_entry_raw(
matches = entries
.iter()
.filter(|&(_, decrypted_cipher)| {
- decrypted_cipher
- .partial_match(name, username, folder, false)
+ decrypted_cipher.partial_match(
+ name,
+ username,
+ folder,
+ false,
+ ignore_case,
+ )
})
.cloned()
.collect();
@@ -1664,6 +1825,7 @@ fn decrypt_cipher(entry: &rbw::db::Entry) -> anyhow::Result<DecryptedCipher> {
)
})
.transpose()?,
+ ty: field.ty,
})
})
.collect::<anyhow::Result<_>>()?;
@@ -1951,7 +2113,7 @@ struct TotpParams {
fn decode_totp_secret(secret: &str) -> anyhow::Result<Vec<u8>> {
base32::decode(
- base32::Alphabet::RFC4648 { padding: false },
+ base32::Alphabet::Rfc4648 { padding: false },
&secret.replace(' ', ""),
)
.ok_or_else(|| anyhow::anyhow!("totp secret was not valid base32"))
@@ -2066,59 +2228,155 @@ mod test {
];
assert!(
- one_match(entries, "github", Some("foo"), None, 0),
+ one_match(entries, "github", Some("foo"), None, 0, false),
"foo@github"
);
- assert!(one_match(entries, "github", None, None, 0), "github");
assert!(
- one_match(entries, "gitlab", Some("foo"), None, 1),
+ one_match(entries, "GITHUB", Some("foo"), None, 0, true),
+ "foo@GITHUB"
+ );
+ assert!(one_match(entries, "github", None, None, 0, false), "github");
+ assert!(one_match(entries, "GITHUB", None, None, 0, true), "GITHUB");
+ assert!(
+ one_match(entries, "gitlab", Some("foo"), None, 1, false),
"foo@gitlab"
);
- assert!(one_match(entries, "git", Some("bar"), None, 2), "bar@git");
assert!(
- one_match(entries, "gitter", Some("ba"), None, 3),
+ one_match(entries, "GITLAB", Some("foo"), None, 1, true),
+ "foo@GITLAB"
+ );
+ assert!(
+ one_match(entries, "git", Some("bar"), None, 2, false),
+ "bar@git"
+ );
+ assert!(
+ one_match(entries, "GIT", Some("bar"), None, 2, true),
+ "bar@GIT"
+ );
+ assert!(
+ one_match(entries, "gitter", Some("ba"), None, 3, false),
"ba@gitter"
);
- assert!(one_match(entries, "git", Some("foo"), None, 4), "foo@git");
- assert!(one_match(entries, "git", None, None, 4), "git");
- assert!(one_match(entries, "bitwarden", None, None, 5), "bitwarden");
assert!(
- one_match(entries, "github", Some("foo"), Some("websites"), 6),
+ one_match(entries, "GITTER", Some("ba"), None, 3, true),
+ "ba@GITTER"
+ );
+ assert!(
+ one_match(entries, "git", Some("foo"), None, 4, false),
+ "foo@git"
+ );
+ assert!(
+ one_match(entries, "GIT", Some("foo"), None, 4, true),
+ "foo@GIT"
+ );
+ assert!(one_match(entries, "git", None, None, 4, false), "git");
+ assert!(one_match(entries, "GIT", None, None, 4, true), "GIT");
+ assert!(
+ one_match(entries, "bitwarden", None, None, 5, false),
+ "bitwarden"
+ );
+ assert!(
+ one_match(entries, "BITWARDEN", None, None, 5, true),
+ "BITWARDEN"
+ );
+ assert!(
+ one_match(
+ entries,
+ "github",
+ Some("foo"),
+ Some("websites"),
+ 6,
+ false
+ ),
"websites/foo@github"
);
assert!(
- one_match(entries, "github", Some("foo"), Some("ssh"), 7),
+ one_match(
+ entries,
+ "GITHUB",
+ Some("foo"),
+ Some("websites"),
+ 6,
+ true
+ ),
+ "websites/foo@GITHUB"
+ );
+ assert!(
+ one_match(entries, "github", Some("foo"), Some("ssh"), 7, false),
"ssh/foo@github"
);
assert!(
- one_match(entries, "github", Some("root"), None, 8),
+ one_match(entries, "GITHUB", Some("foo"), Some("ssh"), 7, true),
+ "ssh/foo@GITHUB"
+ );
+ assert!(
+ one_match(entries, "github", Some("root"), None, 8, false),
"ssh/root@github"
);
+ assert!(
+ one_match(entries, "GITHUB", Some("root"), None, 8, true),
+ "ssh/root@GITHUB"
+ );
assert!(
- no_matches(entries, "gitlab", Some("baz"), None),
+ no_matches(entries, "gitlab", Some("baz"), None, false),
"baz@gitlab"
);
assert!(
- no_matches(entries, "bitbucket", Some("foo"), None),
+ no_matches(entries, "GITLAB", Some("baz"), None, true),
+ "baz@"
+ );
+ assert!(
+ no_matches(entries, "bitbucket", Some("foo"), None, false),
"foo@bitbucket"
);
assert!(
- no_matches(entries, "github", Some("foo"), Some("bar")),
+ no_matches(entries, "BITBUCKET", Some("foo"), None, true),
+ "foo@BITBUCKET"
+ );
+ assert!(
+ no_matches(entries, "github", Some("foo"), Some("bar"), false),
"bar/foo@github"
);
assert!(
- no_matches(entries, "gitlab", Some("foo"), Some("bar")),
+ no_matches(entries, "GITHUB", Some("foo"), Some("bar"), true),
+ "bar/foo@"
+ );
+ assert!(
+ no_matches(entries, "gitlab", Some("foo"), Some("bar"), false),
"bar/foo@gitlab"
);
+ assert!(
+ no_matches(entries, "GITLAB", Some("foo"), Some("bar"), true),
+ "bar/foo@GITLAB"
+ );
- assert!(many_matches(entries, "gitlab", None, None), "gitlab");
- assert!(many_matches(entries, "gi", Some("foo"), None), "foo@gi");
- assert!(many_matches(entries, "git", Some("ba"), None), "ba@git");
+ assert!(many_matches(entries, "gitlab", None, None, false), "gitlab");
+ assert!(many_matches(entries, "gitlab", None, None, true), "GITLAB");
+ assert!(
+ many_matches(entries, "gi", Some("foo"), None, false),
+ "foo@gi"
+ );
+ assert!(
+ many_matches(entries, "GI", Some("foo"), None, true),
+ "foo@GI"
+ );
+ assert!(
+ many_matches(entries, "git", Some("ba"), None, false),
+ "ba@git"
+ );
assert!(
- many_matches(entries, "github", Some("foo"), Some("s")),
+ many_matches(entries, "GIT", Some("ba"), None, true),
+ "ba@GIT"
+ );
+ assert!(
+ many_matches(entries, "github", Some("foo"), Some("s"), false),
"s/foo@github"
);
+ assert!(
+ many_matches(entries, "GITHUB", Some("foo"), Some("s"), true),
+ "s/foo@GITHUB"
+ );
}
#[test]
@@ -2130,15 +2388,15 @@ mod test {
];
assert!(
- one_match(entries, &entries[0].0.id, None, None, 0),
+ one_match(entries, &entries[0].0.id, None, None, 0, false),
"foo@github"
);
assert!(
- one_match(entries, &entries[1].0.id, None, None, 1),
+ one_match(entries, &entries[1].0.id, None, None, 1, false),
"foo@gitlab"
);
assert!(
- one_match(entries, &entries[2].0.id, None, None, 2),
+ one_match(entries, &entries[2].0.id, None, None, 2, false),
"bar@gitlab"
);
@@ -2148,7 +2406,8 @@ mod test {
&entries[0].0.id.to_uppercase(),
None,
None,
- 0
+ 0,
+ false
),
"foo@github"
);
@@ -2158,7 +2417,8 @@ mod test {
&entries[0].0.id.to_lowercase(),
None,
None,
- 0
+ 0,
+ false
),
"foo@github"
);
@@ -2185,51 +2445,94 @@ mod test {
make_entry("six", None, None, &[("six.com:8080", None)]),
];
- assert!(one_match(entries, "https://one.com/", None, None, 0), "one");
assert!(
- one_match(entries, "https://login.one.com/", None, None, 0),
+ one_match(entries, "https://one.com/", None, None, 0, false),
+ "one"
+ );
+ assert!(
+ one_match(
+ entries,
+ "https://login.one.com/",
+ None,
+ None,
+ 0,
+ false
+ ),
+ "one"
+ );
+ assert!(
+ one_match(entries, "https://one.com:443/", None, None, 0, false),
"one"
);
+ assert!(no_matches(entries, "one.com", None, None, false), "one");
+ assert!(no_matches(entries, "https", None, None, false), "one");
+ assert!(no_matches(entries, "com", None, None, false), "one");
assert!(
- one_match(entries, "https://one.com:443/", None, None, 0),
+ no_matches(entries, "https://com/", None, None, false),
"one"
);
- assert!(no_matches(entries, "one.com", None, None), "one");
- assert!(no_matches(entries, "https", None, None), "one");
- assert!(no_matches(entries, "com", None, None), "one");
- assert!(no_matches(entries, "https://com/", None, None), "one");
- assert!(one_match(entries, "https://two.com/", None, None, 1), "two");
assert!(
- one_match(entries, "https://two.com/other-page", None, None, 1),
+ one_match(entries, "https://two.com/", None, None, 1, false),
+ "two"
+ );
+ assert!(
+ one_match(
+ entries,
+ "https://two.com/other-page",
+ None,
+ None,
+ 1,
+ false
+ ),
"two"
);
assert!(
- one_match(entries, "https://login.three.com/", None, None, 2),
+ one_match(
+ entries,
+ "https://login.three.com/",
+ None,
+ None,
+ 2,
+ false
+ ),
"three"
);
assert!(
- no_matches(entries, "https://three.com/", None, None),
+ no_matches(entries, "https://three.com/", None, None, false),
"three"
);
assert!(
- one_match(entries, "https://four.com/", None, None, 3),
+ one_match(entries, "https://four.com/", None, None, 3, false),
"four"
);
assert!(
- one_match(entries, "https://five.com:8080/", None, None, 4),
+ one_match(
+ entries,
+ "https://five.com:8080/",
+ None,
+ None,
+ 4,
+ false
+ ),
+ "five"
+ );
+ assert!(
+ no_matches(entries, "https://five.com/", None, None, false),
"five"
);
- assert!(no_matches(entries, "https://five.com/", None, None), "five");
assert!(
- one_match(entries, "https://six.com:8080/", None, None, 5),
+ one_match(entries, "https://six.com:8080/", None, None, 5, false),
+ "six"
+ );
+ assert!(
+ no_matches(entries, "https://six.com/", None, None, false),
"six"
);
- assert!(no_matches(entries, "https://six.com/", None, None), "six");
}
#[test]
@@ -2282,51 +2585,94 @@ mod test {
),
];
- assert!(one_match(entries, "https://one.com/", None, None, 0), "one");
assert!(
- one_match(entries, "https://login.one.com/", None, None, 0),
+ one_match(entries, "https://one.com/", None, None, 0, false),
+ "one"
+ );
+ assert!(
+ one_match(
+ entries,
+ "https://login.one.com/",
+ None,
+ None,
+ 0,
+ false
+ ),
+ "one"
+ );
+ assert!(
+ one_match(entries, "https://one.com:443/", None, None, 0, false),
"one"
);
+ assert!(no_matches(entries, "one.com", None, None, false), "one");
+ assert!(no_matches(entries, "https", None, None, false), "one");
+ assert!(no_matches(entries, "com", None, None, false), "one");
assert!(
- one_match(entries, "https://one.com:443/", None, None, 0),
+ no_matches(entries, "https://com/", None, None, false),
"one"
);
- assert!(no_matches(entries, "one.com", None, None), "one");
- assert!(no_matches(entries, "https", None, None), "one");
- assert!(no_matches(entries, "com", None, None), "one");
- assert!(no_matches(entries, "https://com/", None, None), "one");
- assert!(one_match(entries, "https://two.com/", None, None, 1), "two");
assert!(
- one_match(entries, "https://two.com/other-page", None, None, 1),
+ one_match(entries, "https://two.com/", None, None, 1, false),
+ "two"
+ );
+ assert!(
+ one_match(
+ entries,
+ "https://two.com/other-page",
+ None,
+ None,
+ 1,
+ false
+ ),
"two"
);
assert!(
- one_match(entries, "https://login.three.com/", None, None, 2),
+ one_match(
+ entries,
+ "https://login.three.com/",
+ None,
+ None,
+ 2,
+ false
+ ),
"three"
);
assert!(
- no_matches(entries, "https://three.com/", None, None),
+ no_matches(entries, "https://three.com/", None, None, false),
"three"
);
assert!(
- one_match(entries, "https://four.com/", None, None, 3),
+ one_match(entries, "https://four.com/", None, None, 3, false),
"four"
);
assert!(
- one_match(entries, "https://five.com:8080/", None, None, 4),
+ one_match(
+ entries,
+ "https://five.com:8080/",
+ None,
+ None,
+ 4,
+ false
+ ),
+ "five"
+ );
+ assert!(
+ no_matches(entries, "https://five.com/", None, None, false),
"five"
);
- assert!(no_matches(entries, "https://five.com/", None, None), "five");
assert!(
- one_match(entries, "https://six.com:8080/", None, None, 5),
+ one_match(entries, "https://six.com:8080/", None, None, 5, false),
+ "six"
+ );
+ assert!(
+ no_matches(entries, "https://six.com/", None, None, false),
"six"
);
- assert!(no_matches(entries, "https://six.com/", None, None), "six");
}
#[test]
@@ -2379,51 +2725,87 @@ mod test {
),
];
- assert!(one_match(entries, "https://one.com/", None, None, 0), "one");
assert!(
- no_matches(entries, "https://login.one.com/", None, None),
+ one_match(entries, "https://one.com/", None, None, 0, false),
+ "one"
+ );
+ assert!(
+ no_matches(entries, "https://login.one.com/", None, None, false),
"one"
);
assert!(
- one_match(entries, "https://one.com:443/", None, None, 0),
+ one_match(entries, "https://one.com:443/", None, None, 0, false),
+ "one"
+ );
+ assert!(no_matches(entries, "one.com", None, None, false), "one");
+ assert!(no_matches(entries, "https", None, None, false), "one");
+ assert!(no_matches(entries, "com", None, None, false), "one");
+ assert!(
+ no_matches(entries, "https://com/", None, None, false),
"one"
);
- assert!(no_matches(entries, "one.com", None, None), "one");
- assert!(no_matches(entries, "https", None, None), "one");
- assert!(no_matches(entries, "com", None, None), "one");
- assert!(no_matches(entries, "https://com/", None, None), "one");
- assert!(one_match(entries, "https://two.com/", None, None, 1), "two");
assert!(
- one_match(entries, "https://two.com/other-page", None, None, 1),
+ one_match(entries, "https://two.com/", None, None, 1, false),
+ "two"
+ );
+ assert!(
+ one_match(
+ entries,
+ "https://two.com/other-page",
+ None,
+ None,
+ 1,
+ false
+ ),
"two"
);
assert!(
- one_match(entries, "https://login.three.com/", None, None, 2),
+ one_match(
+ entries,
+ "https://login.three.com/",
+ None,
+ None,
+ 2,
+ false
+ ),
"three"
);
assert!(
- no_matches(entries, "https://three.com/", None, None),
+ no_matches(entries, "https://three.com/", None, None, false),
"three"
);
assert!(
- one_match(entries, "https://four.com/", None, None, 3),
+ one_match(entries, "https://four.com/", None, None, 3, false),
"four"
);
assert!(
- one_match(entries, "https://five.com:8080/", None, None, 4),
+ one_match(
+ entries,
+ "https://five.com:8080/",
+ None,
+ None,
+ 4,
+ false
+ ),
+ "five"
+ );
+ assert!(
+ no_matches(entries, "https://five.com/", None, None, false),
"five"
);
- assert!(no_matches(entries, "https://five.com/", None, None), "five");
assert!(
- one_match(entries, "https://six.com:8080/", None, None, 5),
+ one_match(entries, "https://six.com:8080/", None, None, 5, false),
+ "six"
+ );
+ assert!(
+ no_matches(entries, "https://six.com/", None, None, false),
"six"
);
- assert!(no_matches(entries, "https://six.com/", None, None), "six");
}
#[test]
@@ -2458,40 +2840,69 @@ mod test {
),
];
- assert!(one_match(entries, "https://one.com/", None, None, 0), "one");
assert!(
- no_matches(entries, "https://login.one.com/", None, None),
+ one_match(entries, "https://one.com/", None, None, 0, false),
+ "one"
+ );
+ assert!(
+ no_matches(entries, "https://login.one.com/", None, None, false),
+ "one"
+ );
+ assert!(
+ one_match(entries, "https://one.com:443/", None, None, 0, false),
"one"
);
+ assert!(no_matches(entries, "one.com", None, None, false), "one");
+ assert!(no_matches(entries, "https", None, None, false), "one");
+ assert!(no_matches(entries, "com", None, None, false), "one");
assert!(
- one_match(entries, "https://one.com:443/", None, None, 0),
+ no_matches(entries, "https://com/", None, None, false),
"one"
);
- assert!(no_matches(entries, "one.com", None, None), "one");
- assert!(no_matches(entries, "https", None, None), "one");
- assert!(no_matches(entries, "com", None, None), "one");
- assert!(no_matches(entries, "https://com/", None, None), "one");
assert!(
- one_match(entries, "https://two.com/login", None, None, 1),
+ one_match(entries, "https://two.com/login", None, None, 1, false),
+ "two"
+ );
+ assert!(
+ one_match(
+ entries,
+ "https://two.com/login/sso",
+ None,
+ None,
+ 1,
+ false
+ ),
"two"
);
assert!(
- one_match(entries, "https://two.com/login/sso", None, None, 1),
+ no_matches(entries, "https://two.com/", None, None, false),
"two"
);
- assert!(no_matches(entries, "https://two.com/", None, None), "two");
assert!(
- no_matches(entries, "https://two.com/other-page", None, None),
+ no_matches(
+ entries,
+ "https://two.com/other-page",
+ None,
+ None,
+ false
+ ),
"two"
);
assert!(
- one_match(entries, "https://login.three.com/", None, None, 2),
+ one_match(
+ entries,
+ "https://login.three.com/",
+ None,
+ None,
+ 2,
+ false
+ ),
"three"
);
assert!(
- no_matches(entries, "https://three.com/", None, None),
+ no_matches(entries, "https://three.com/", None, None, false),
"three"
);
}
@@ -2525,40 +2936,68 @@ mod test {
),
];
- assert!(one_match(entries, "https://one.com/", None, None, 0), "one");
assert!(
- no_matches(entries, "https://login.one.com/", None, None),
+ one_match(entries, "https://one.com/", None, None, 0, false),
+ "one"
+ );
+ assert!(
+ no_matches(entries, "https://login.one.com/", None, None, false),
"one"
);
assert!(
- one_match(entries, "https://one.com:443/", None, None, 0),
+ one_match(entries, "https://one.com:443/", None, None, 0, false),
+ "one"
+ );
+ assert!(no_matches(entries, "one.com", None, None, false), "one");
+ assert!(no_matches(entries, "https", None, None, false), "one");
+ assert!(no_matches(entries, "com", None, None, false), "one");
+ assert!(
+ no_matches(entries, "https://com/", None, None, false),
"one"
);
- assert!(no_matches(entries, "one.com", None, None), "one");
- assert!(no_matches(entries, "https", None, None), "one");
- assert!(no_matches(entries, "com", None, None), "one");
- assert!(no_matches(entries, "https://com/", None, None), "one");
assert!(
- one_match(entries, "https://two.com/login", None, None, 1),
+ one_match(entries, "https://two.com/login", None, None, 1, false),
"two"
);
assert!(
- no_matches(entries, "https://two.com/login/sso", None, None),
+ no_matches(
+ entries,
+ "https://two.com/login/sso",
+ None,
+ None,
+ false
+ ),
+ "two"
+ );
+ assert!(
+ no_matches(entries, "https://two.com/", None, None, false),
"two"
);
- assert!(no_matches(entries, "https://two.com/", None, None), "two");
assert!(
- no_matches(entries, "https://two.com/other-page", None, None),
+ no_matches(
+ entries,
+ "https://two.com/other-page",
+ None,
+ None,
+ false
+ ),
"two"
);
assert!(
- one_match(entries, "https://login.three.com/", None, None, 2),
+ one_match(
+ entries,
+ "https://login.three.com/",
+ None,
+ None,
+ 2,
+ false
+ ),
"three"
);
assert!(
- no_matches(entries, "https://three.com/", None, None),
+ no_matches(entries, "https://three.com/", None, None, false),
"three"
);
}
@@ -2595,48 +3034,77 @@ mod test {
),
];
- assert!(one_match(entries, "https://one.com/", None, None, 0), "one");
assert!(
- no_matches(entries, "https://login.one.com/", None, None),
+ one_match(entries, "https://one.com/", None, None, 0, false),
+ "one"
+ );
+ assert!(
+ no_matches(entries, "https://login.one.com/", None, None, false),
+ "one"
+ );
+ assert!(
+ one_match(entries, "https://one.com:443/", None, None, 0, false),
"one"
);
+ assert!(no_matches(entries, "one.com", None, None, false), "one");
+ assert!(no_matches(entries, "https", None, None, false), "one");
+ assert!(no_matches(entries, "com", None, None, false), "one");
assert!(
- one_match(entries, "https://one.com:443/", None, None, 0),
+ no_matches(entries, "https://com/", None, None, false),
"one"
);
- assert!(no_matches(entries, "one.com", None, None), "one");
- assert!(no_matches(entries, "https", None, None), "one");
- assert!(no_matches(entries, "com", None, None), "one");
- assert!(no_matches(entries, "https://com/", None, None), "one");
assert!(
- one_match(entries, "https://two.com/login", None, None, 1),
+ one_match(entries, "https://two.com/login", None, None, 1, false),
"two"
);
assert!(
- one_match(entries, "https://two.com/start", None, None, 1),
+ one_match(entries, "https://two.com/start", None, None, 1, false),
"two"
);
assert!(
- one_match(entries, "https://two.com/login/sso", None, None, 1),
+ one_match(
+ entries,
+ "https://two.com/login/sso",
+ None,
+ None,
+ 1,
+ false
+ ),
"two"
);
- assert!(no_matches(entries, "https://two.com/", None, None), "two");
assert!(
- no_matches(entries, "https://two.com/other-page", None, None),
+ no_matches(entries, "https://two.com/", None, None, false),
+ "two"
+ );
+ assert!(
+ no_matches(
+ entries,
+ "https://two.com/other-page",
+ None,
+ None,
+ false
+ ),
"two"
);
assert!(
- one_match(entries, "https://login.three.com/", None, None, 2),
+ one_match(
+ entries,
+ "https://login.three.com/",
+ None,
+ None,
+ 2,
+ false
+ ),
"three"
);
assert!(
- one_match(entries, "https://three.com/", None, None, 2),
+ one_match(entries, "https://three.com/", None, None, 2, false),
"three"
);
assert!(
- no_matches(entries, "https://www.three.com/", None, None),
+ no_matches(entries, "https://www.three.com/", None, None, false),
"three"
);
}
@@ -2691,48 +3159,78 @@ mod test {
),
];
- assert!(no_matches(entries, "https://one.com/", None, None), "one");
assert!(
- no_matches(entries, "https://login.one.com/", None, None),
+ no_matches(entries, "https://one.com/", None, None, false),
+ "one"
+ );
+ assert!(
+ no_matches(entries, "https://login.one.com/", None, None, false),
"one"
);
assert!(
- no_matches(entries, "https://one.com:443/", None, None),
+ no_matches(entries, "https://one.com:443/", None, None, false),
+ "one"
+ );
+ assert!(no_matches(entries, "one.com", None, None, false), "one");
+ assert!(no_matches(entries, "https", None, None, false), "one");
+ assert!(no_matches(entries, "com", None, None, false), "one");
+ assert!(
+ no_matches(entries, "https://com/", None, None, false),
"one"
);
- assert!(no_matches(entries, "one.com", None, None), "one");
- assert!(no_matches(entries, "https", None, None), "one");
- assert!(no_matches(entries, "com", None, None), "one");
- assert!(no_matches(entries, "https://com/", None, None), "one");
- assert!(no_matches(entries, "https://two.com/", None, None), "two");
assert!(
- no_matches(entries, "https://two.com/other-page", None, None),
+ no_matches(entries, "https://two.com/", None, None, false),
+ "two"
+ );
+ assert!(
+ no_matches(
+ entries,
+ "https://two.com/other-page",
+ None,
+ None,
+ false
+ ),
"two"
);
assert!(
- no_matches(entries, "https://login.three.com/", None, None),
+ no_matches(
+ entries,
+ "https://login.three.com/",
+ None,
+ None,
+ false
+ ),
"three"
);
assert!(
- no_matches(entries, "https://three.com/", None, None),
+ no_matches(entries, "https://three.com/", None, None, false),
"three"
);
- assert!(no_matches(entries, "https://four.com/", None, None), "four");
+ assert!(
+ no_matches(entries, "https://four.com/", None, None, false),
+ "four"
+ );
assert!(
- no_matches(entries, "https://five.com:8080/", None, None),
+ no_matches(entries, "https://five.com:8080/", None, None, false),
+ "five"
+ );
+ assert!(
+ no_matches(entries, "https://five.com/", None, None, false),
"five"
);
- assert!(no_matches(entries, "https://five.com/", None, None), "five");
assert!(
- no_matches(entries, "https://six.com:8080/", None, None),
+ no_matches(entries, "https://six.com:8080/", None, None, false),
+ "six"
+ );
+ assert!(
+ no_matches(entries, "https://six.com/", None, None, false),
"six"
);
- assert!(no_matches(entries, "https://six.com/", None, None), "six");
}
#[track_caller]
@@ -2742,6 +3240,7 @@ mod test {
username: Option<&str>,
folder: Option<&str>,
idx: usize,
+ ignore_case: bool,
) -> bool {
entries_eq(
&find_entry_raw(
@@ -2749,6 +3248,7 @@ mod test {
&parse_needle(needle).unwrap(),
username,
folder,
+ ignore_case,
)
.unwrap(),
&entries[idx],
@@ -2761,12 +3261,14 @@ mod test {
needle: &str,
username: Option<&str>,
folder: Option<&str>,
+ ignore_case: bool,
) -> bool {
let res = find_entry_raw(
entries,
&parse_needle(needle).unwrap(),
username,
folder,
+ ignore_case,
);
if let Err(e) = res {
format!("{e}").contains("no entry found")
@@ -2781,12 +3283,14 @@ mod test {
needle: &str,
username: Option<&str>,
folder: Option<&str>,
+ ignore_case: bool,
) -> bool {
let res = find_entry_raw(
entries,
&parse_needle(needle).unwrap(),
username,
folder,
+ ignore_case,
);
if let Err(e) = res {
format!("{e}").contains("multiple entries found")
diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs
index 2fb96bf..e30c3a9 100644
--- a/src/bin/rbw/main.rs
+++ b/src/bin/rbw/main.rs
@@ -85,6 +85,16 @@ enum Opt {
raw: bool,
#[structopt(long, help = "Copy result to clipboard")]
clipboard: bool,
+ #[structopt(short, long, help = "Ignore case")]
+ ignorecase: bool,
+ },
+
+ #[command(about = "Search for entries")]
+ Search {
+ #[arg(help = "Search term to locate entries")]
+ term: String,
+ #[arg(long, help = "Folder name to search in")]
+ folder: Option<String>,
},
#[command(
@@ -100,6 +110,8 @@ enum Opt {
folder: Option<String>,
#[structopt(long, help = "Copy result to clipboard")]
clipboard: bool,
+ #[arg(short, long, help = "Ignore case")]
+ ignorecase: bool,
},
#[command(
@@ -198,6 +210,8 @@ enum Opt {
user: Option<String>,
#[arg(long, help = "Folder name to search in")]
folder: Option<String>,
+ #[arg(short, long, help = "Ignore case")]
+ ignorecase: bool,
},
#[command(about = "Remove a given entry", visible_alias = "rm")]
@@ -208,6 +222,8 @@ enum Opt {
user: Option<String>,
#[arg(long, help = "Folder name to search in")]
folder: Option<String>,
+ #[arg(short, long, help = "Ignore case")]
+ ignorecase: bool,
},
#[command(about = "View the password history for a given entry")]
@@ -218,6 +234,8 @@ enum Opt {
user: Option<String>,
#[arg(long, help = "Folder name to search in")]
folder: Option<String>,
+ #[arg(short, long, help = "Ignore case")]
+ ignorecase: bool,
},
#[command(about = "Lock the password database")]
@@ -249,6 +267,7 @@ impl Opt {
Self::Sync => "sync".to_string(),
Self::List { .. } => "list".to_string(),
Self::Get { .. } => "get".to_string(),
+ Self::Search { .. } => "search".to_string(),
Self::Code { .. } => "code".to_string(),
Self::Add { .. } => "add".to_string(),
Self::Generate { .. } => "generate".to_string(),
@@ -330,6 +349,7 @@ fn main() {
full,
raw,
clipboard,
+ ignorecase,
} => commands::get(
needle,
user.as_deref(),
@@ -338,17 +358,23 @@ fn main() {
*full,
*raw,
*clipboard,
+ *ignorecase,
),
+ Opt::Search { term, folder } => {
+ commands::search(term, folder.as_deref())
+ }
Opt::Code {
needle,
user,
folder,
clipboard,
+ ignorecase,
} => commands::code(
needle,
user.as_deref(),
folder.as_deref(),
*clipboard,
+ *ignorecase,
),
Opt::Add {
name,
@@ -400,15 +426,39 @@ fn main() {
ty,
)
}
- Opt::Edit { name, user, folder } => {
- commands::edit(name, user.as_deref(), folder.as_deref())
- }
- Opt::Remove { name, user, folder } => {
- commands::remove(name, user.as_deref(), folder.as_deref())
- }
- Opt::History { name, user, folder } => {
- commands::history(name, user.as_deref(), folder.as_deref())
- }
+ Opt::Edit {
+ name,
+ user,
+ folder,
+ ignorecase,
+ } => commands::edit(
+ name,
+ user.as_deref(),
+ folder.as_deref(),
+ *ignorecase,
+ ),
+ Opt::Remove {
+ name,
+ user,
+ folder,
+ ignorecase,
+ } => commands::remove(
+ name,
+ user.as_deref(),
+ folder.as_deref(),
+ *ignorecase,
+ ),
+ Opt::History {
+ name,
+ user,
+ folder,
+ ignorecase,
+ } => commands::history(
+ name,
+ user.as_deref(),
+ folder.as_deref(),
+ *ignorecase,
+ ),
Opt::Lock => commands::lock(),
Opt::Purge => commands::purge(),
Opt::StopAgent => commands::stop_agent(),