From f5a93532349379425d4e3f7669ab4ac9b08052bc Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Fri, 20 Mar 2015 01:59:48 +0100 Subject: Added a 'ls' command. --- icevault | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- icevault.1 | 11 ++++++ 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/icevault b/icevault index 943f1bf..7127c23 100755 --- a/icevault +++ b/icevault @@ -75,6 +75,7 @@ sub usage($) { ." or: $0 [OPTIONS] dump scheme://hostname/identity\n" ." or: $0 [OPTIONS] clip scheme://hostname/identity\n" ." or: $0 [OPTIONS] edit scheme://hostname/identity\n" + ." or: $0 [OPTIONS] ls [scheme://[hostname/[identity]]]\n" . "Consult the manual page for more information.\n"; exit $rv; } @@ -222,6 +223,76 @@ sub sendCommand(@) { getResponse(); } +# Get all identities with the given $prefix. If there are multiple +# matches and $all is false, limit the output to one depth. +sub complete($;$) { + my $prefix = shift // ''; + my $all = shift; + require 'File/Glob.pm'; + + my $pat = $CONFIG{store}; + my ($s, $h, $i); # extract URI components from the prefix + if ($prefix =~ /\A([A-Za-z0-9-]+):\/\/([^\P{Graph}:\/]+(?::\d+)?)\/([^\P{Print}\/]*)\z/) { + ($s, $h, $i) = ($1, $2, $3); + } elsif ($prefix =~ /\A([A-Za-z0-9-]+):\/\/([^\P{Graph}\/]*)\z/) { + ($s, $h, $i) = ($1, $2, undef); + } elsif ($prefix =~ /\A([A-Za-z0-9-]*)(:\/?)?\z/) { + ($s, $h, $i) = ($1, (defined $2 ? '' : undef), undef); + } else { + exit; + } + + # construct a glob pattern with these URI components + my ($gs, $gh, $gi) = ($s, $h, $i); + s/([\\\[\]\{\}\*\?\~])/\\$1/g foreach grep defined, ($gs, $gh, $gi); # escape meta chars + + # add trailing wildcards + $gs .= '*' if defined $gs and !defined $gh; + $gh .= '*' if defined $gh and !defined $gi; + $gi .= '*' if defined $gi; + + my $glob = $pat; + $glob =~ s/([\\\[\]\{\}\*\?\~])/\\$1/g; + $glob =~ s{\%(.)}{ $1 eq '%' ? '%' : + $1 eq 's' ? $gs // '*' : + $1 eq 'h' ? $gh // '*' : + $1 eq 'i' ? $gi // '*' : + die "Invalid placeholder %$1" }ge; + + # construct regexp to extract the URI compontents of the matching URIs + my ($ps, $ph, $pi) = ($s, $h, $i); + $ps = defined $h ? qr/(?\Q$s\E)/ : (defined $s and $s ne '') ? qr/(?\Q$s\E[A-Za-z0-9-]*)/ : qr/(?[A-Za-z0-9-]+)/; + $ph = defined $i ? qr/(?\Q$h\E)/ : (defined $h and $h ne '') ? qr/(?\Q$h\E[^\P{Graph}\/]*)/ : qr/(?[^\P{Graph}\/]+)/; + $pi = (defined $i and $i ne '') ? qr/(?\Q$i\E[^\P{Print}\/]*)/ : qr/(?[^\P{Print}\/]+)/; + + $pat =~ s/(\%.)([^\%]*)\z/$1.quotemeta($2)/e; + $pat =~ s{(.*?)\%(.)}{$2 eq '%' ? '%' : + $2 eq 's' ? quotemeta($1).$ps : + $2 eq 'h' ? quotemeta($1).$ph : + $2 eq 'i' ? quotemeta($1).$pi : + die "Invalid placeholder %$1"}ge; + $pat = qr/\A$pat\z/; + + myprintf \*STDERR, "Using glob pattern C<%s>", $glob if $CONFIG{debug}; + myprintf \*STDERR, "Using regexp C<%s>", "$pat" if $CONFIG{debug}; + + my @matches; + foreach my $filename (File::Glob::bsd_glob($glob)) { + $LOCALE->decode($filename) =~ $pat or die "$filename doesn't match $pat"; + push @matches, "$+{s}://$+{h}/$+{i}"; + } + return @matches if $all or $#matches < 1; + + if ($prefix =~ /\A[A-Za-z0-9-]+:\/\/[^\P{Graph}:\/]+(?::\d+)?\//) { + } elsif ($prefix =~ /\A[A-Za-z0-9-]+:\/\//) { + s#/[^\P{Print}\/]+\z#/# foreach @matches; + } elsif (defined $s) { + s#://[^\P{Graph}\/]+/[^\P{Print}\/]+\z#://# foreach @matches; + } + my %matches = map {( $_ => 1 )} @matches; + return keys %matches; +} + # Redact passwords, unless $CONFIG{'show-passwords'} is set. sub safeValue($;$) { my $field = shift; @@ -450,7 +521,7 @@ sub sha256_file($) { ####################################################################### -GetOptions(\%CONFIG, qw/debug show-passwords|p socket|s=s help|?/) or usage(1); +GetOptions(\%CONFIG, qw/debug show-passwords|p socket|s=s help|? zero|0/) or usage(1); usage(0) if $CONFIG{help}; # Load configuration @@ -473,7 +544,15 @@ usage(1) unless @ARGV; my $command = $ARGV[0] =~ /\A[A-Za-z0-9-]+:\/\//aa ? 'fill' : shift; # Process the commands -if ($command eq 'insert') { +if ($command eq '_complete') { + # used internaly for auto-completion + usage(1) unless $#ARGV == 0; + my $delim = $CONFIG{zero} ? "\0" : "\n"; + print $LOCALE->encode($_), $delim foreach complete(shift @ARGV); + exit; +} + +elsif ($command eq 'insert') { usage(1) unless $#ARGV < 1; my $uri = &connect($CONFIG{socket}); myprintf "Importing HTML form from URI C<%s>", $uri; @@ -776,13 +855,39 @@ elsif ($command eq 'clip') { my $pid = open my $fh, '|-', qw/xclip -loop 1 -selection clipboard/ or error "Can't fork: %s", $!; - print $fh $LOCALE->encode($pw->{value}); + print $fh encode_utf8($pw->{value}); close $fh; waitpid $pid, 0; error "C<%s> exited with value %d", 'xclip', ($? >> 8) if $? and $? != -1; exit 0; } +elsif ($command eq 'ls') { + usage(1) if $#ARGV > 0; + my $prefix = shift @ARGV; + my @matches = complete $prefix // '', 1; + + if (!defined $prefix) { + s/:\/\/.*// foreach @matches; + } elsif ($prefix =~ /\A[A-Za-z0-9-]+:\/\/[^\P{Graph}:\/]+(?::\d+)?\/[^\P{Print}\/]+\z/) { + @matches = grep /\A\Q$prefix\E\z/, @matches; + } elsif ($prefix =~ /\A([A-Za-z0-9-]+:\/\/[^\P{Graph}\/]+)\/?\z/) { + my $x = $1; + @matches = grep defined, map { !s/\A\Q$x\E\/// ? undef : $_ } @matches; + } elsif ($prefix =~ /\A([A-Za-z0-9-]+)(:\/{0,2})?\z/) { + my $x = $1; + @matches = grep defined, map { !s/\A\Q$x\E:\/\/// ? undef : + !s/\/[^\P{Print}\/]+\z// ? undef : $_ } @matches; + } else { + @matches = (); + } + error "No such identity C<%s>", $prefix // '' unless @matches; + + my $delim = $CONFIG{zero} ? "\0" : "\n"; + my %matches = map {($_ => 1)} @matches; + print $LOCALE->encode($_), $delim foreach keys %matches; +} + else { myprintf "Unknown command: C<%s>", $command; usage(1); diff --git a/icevault.1 b/icevault.1 index f2b3b76..e903b9c 100644 --- a/icevault.1 +++ b/icevault.1 @@ -13,6 +13,8 @@ IceVault \- IceVault client user interface .B icevault\fR [\fIOPTIONS\fR] \fBclip\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR .br .B icevault\fR [\fIOPTIONS\fR] \fBedit\fR \fIscheme\fR://\fIhostname\fR/\fIidentity\fR +.br +.B icevault\fR [\fIOPTIONS\fR] \fBls\fR [\fIscheme\fR://[\fIhostname\fR/[\fIidentity\fR]]] .SH DESCRIPTION @@ -93,6 +95,10 @@ editor exits, the file is reencrypted if the SHA-256 digest of its content differs. Note that formatting and comments may not be preserved by subsequent updates of the \fIidentity\fR file. +.TP +.B icevault\fR [\fIOPTIONS\fR] \fBls\fR [\fIscheme\fR://[\fIhostname\fR/[\fIidentity\fR]]] +List content of the given identity prefix. + .SH OPTIONS .TP @@ -119,6 +125,11 @@ there is no default profile) in the "~/.mozilla/firefox" directory. .B \-\-version Show the version number and exit. +.TP +.B \-0\fR, \fB\-\-zero +With the \fBls\fR command, use NUL instead of newline as line delimiter. + + .SH CONFIGURATION FILE \fBicevault\fR reads it configuration from -- cgit v1.2.3