diff --git a/contrib/lpass-att-export.sh b/contrib/lpass-att-export.sh index df32c095..47be1b64 100755 --- a/contrib/lpass-att-export.sh +++ b/contrib/lpass-att-export.sh @@ -33,34 +33,43 @@ fi command -v lpass >/dev/null 2>&1 || { echo >&2 "I require lpass but it's not installed. Aborting."; exit 1; } -if [ ! -d ${outdir} ]; then +# shellcheck disable=SC2034 # ignore unused version variables +IFS='.' read -r lpass_ver_major lpass_ver_minor lpass_ver_patch lpass_ver_vcs <<< "$(lpass --version | sed 's/LastPass CLI v//')" +if [ "${lpass_ver_major}" -gt 1 ] || [ "${lpass_ver_major}" -eq 1 ] && [ "${lpass_ver_minor}" -gt 6 ]; then + path_format="%/_as%/_ag%_an" +else + path_format="%/as%/ag%an" +fi + +if [ ! -d "${outdir}" ]; then echo "${outdir} does not exist. Exiting." exit 1 fi if ! lpass status; then - if [ -z ${email} ]; then + if [ -z "${email}" ]; then echo "No login data found, Please login with -l or use lpass login before." exit 1; fi - lpass login ${email} + lpass login "${email}" fi -if [ -z ${id} ]; then - ids=$(lpass ls | sed -n "s/^.*id:\s*\([0-9]*\).*$/\1/p") +if [ -z "${id}" ]; then + # Get the ids of items that might have an attachment + # remove trailing carriage return if it's there + ids=$(lpass export --fields=id,attachpresent | grep ',1' | sed 's/,1\r\{0,1\}//') else ids=${id} fi for id in ${ids}; do - show=$(lpass show ${id}) + show=$(lpass show "${id}") attcount=$(echo "${show}" | grep -c "att-") - path=$(lpass show --format="%/as%/ag%an" ${id} | uniq | tail -1) + path=$(lpass show --format="${path_format}" "${id}" | uniq | tail -1) - until [ ${attcount} -lt 1 ]; do - att=`lpass show ${id} | grep att- | sed "${attcount}q;d" | tr -d :` - attid=$(echo ${att} | awk '{print $1}') - attname=$(echo ${att} | awk '{print $2}') + until [ "${attcount}" -lt 1 ]; do + # switch to read because the original way truncated filenames containing spaces + read -r attid attname <<< "$(lpass show "${id}" | grep att- | sed "${attcount}q;d" | tr -d :)" if [[ -z ${attname} ]]; then attname=${path#*/} @@ -74,11 +83,11 @@ for id in ${ids}; do out=${outdir}/${path}/${attcount}_${attname} fi - echo ${id} - ${path} ": " ${attid} "-" ${attname} " > " ${out} + echo "${id} - ${path} : ${attid} - ${attname} > ${out}" - lpass show --attach=${attid} ${id} --quiet > "${out}" + lpass show "--attach=${attid}" "${id}" --quiet > "${out}" - let attcount-=1 + (( attcount-=1 )) || true done done diff --git a/format.c b/format.c index 51269e8a..8731d312 100644 --- a/format.c +++ b/format.c @@ -80,19 +80,28 @@ char *format_timestamp(char *timestamp, bool utc) } static -void append_str(struct buffer *buf, char *str, bool add_slash) +void append_str(struct buffer *buf, char *str, bool add_slash, bool make_fs_safe) { + char *fs_parse_point = NULL; + /* can't set fs_parse_point here since the buffer might get reallocated by buffer_append_str */ + size_t orignal_len = buf->len; + if (!str || !strlen(str)) return; buffer_append_str(buf, str); + if (make_fs_safe) { + fs_parse_point = buf->bytes + orignal_len; + while ( (fs_parse_point = strpbrk(fs_parse_point, "/\r\n\t")) ) + *fs_parse_point = '_'; + } if (add_slash) buffer_append_char(buf, '/'); } static void format_account_item(struct buffer *buf, char fmt, - struct account *account, bool add_slash) + struct account *account, bool add_slash, bool make_fs_safe) { _cleanup_free_ char *name = NULL; _cleanup_free_ char *ts = NULL; @@ -100,47 +109,47 @@ void format_account_item(struct buffer *buf, char fmt, switch (fmt) { case 'i': /* id */ - append_str(buf, account->id, add_slash); + append_str(buf, account->id, add_slash, make_fs_safe); break; case 'n': /* shortname */ - append_str(buf, account->name, add_slash); + append_str(buf, account->name, add_slash, make_fs_safe); break; case 'N': /* fullname */ name = get_display_fullname(account); - append_str(buf, name, add_slash); + append_str(buf, name, add_slash, make_fs_safe); break; case 'u': /* username */ - append_str(buf, account->username, add_slash); + append_str(buf, account->username, add_slash, make_fs_safe); break; case 'p': /* password */ - append_str(buf, account->password, add_slash); + append_str(buf, account->password, add_slash, make_fs_safe); break; case 'm': /* mtime */ ts = format_timestamp(account->last_modified_gmt, true); - append_str(buf, ts, add_slash); + append_str(buf, ts, add_slash, make_fs_safe); break; case 'U': /* last touch time */ ts = format_timestamp(account->last_touch, false); - append_str(buf, ts, add_slash); + append_str(buf, ts, add_slash, make_fs_safe); break; case 's': /* sharename */ if (account->share) - append_str(buf, account->share->name, add_slash); + append_str(buf, account->share->name, add_slash, make_fs_safe); break; case 'g': /* group name */ - append_str(buf, account->group, add_slash); + append_str(buf, account->group, add_slash, make_fs_safe); break; case 'l': /* URL */ - append_str(buf, account->url, add_slash); + append_str(buf, account->url, add_slash, make_fs_safe); break; default: break; @@ -149,12 +158,12 @@ void format_account_item(struct buffer *buf, char fmt, void format_field_item(struct buffer *buf, char fmt, char *field_name, char *field_value, - bool add_slash) + bool add_slash, bool make_fs_safe) { if (fmt == 'n' && field_name) { - append_str(buf, field_name, add_slash); + append_str(buf, field_name, add_slash, make_fs_safe); } else if (fmt == 'v' && field_value) { - append_str(buf, field_value, add_slash); + append_str(buf, field_value, add_slash, make_fs_safe); } } @@ -165,6 +174,7 @@ void format_field(struct buffer *buf, const char *format_str, const char *p = format_str; bool in_format = false; bool add_slash = false; + bool make_fs_safe = false; while (*p) { char ch = *p++; @@ -188,6 +198,10 @@ void format_field(struct buffer *buf, const char *format_str, /* append trailing slash, if nonempty */ add_slash = true; continue; + case '_': + /* transform slashes to underscores in field value */ + make_fs_safe = true; + continue; case 'f': /* field name/value */ if (!*p) { @@ -196,7 +210,7 @@ void format_field(struct buffer *buf, const char *format_str, break; } ch = *p++; - format_field_item(buf, ch, field_name, field_value, add_slash); + format_field_item(buf, ch, field_name, field_value, add_slash, make_fs_safe); break; case 'a': /* account item */ @@ -206,13 +220,14 @@ void format_field(struct buffer *buf, const char *format_str, break; } ch = *p++; - format_account_item(buf, ch, account, add_slash); + format_account_item(buf, ch, account, add_slash, make_fs_safe); break; default: buffer_append_char(buf, '%'); buffer_append_char(buf, ch); } add_slash = false; + make_fs_safe = false; in_format = false; } } diff --git a/lpass.1.txt b/lpass.1.txt index 870d7986..1051af05 100644 --- a/lpass.1.txt +++ b/lpass.1.txt @@ -171,7 +171,11 @@ the following placeholders: A slash can be added between the '%' and the placeholder to indicate that a slash should be appended, only if the printed value is expanded to a non-empty string. For example, this command will properly show the full path to -an account: `lpass ls --format="%/as%/ag%an"`. +an account: `lpass ls --format="%/as%/ag%an"`. In the same way, an underscore may +be added between the '%' and the placeholder to indicate that the value should +be made file-system-safe by replacing forward slashes, tabs, carriage returns, +and new lines with an underscore. This may be combined with the slash. For +example: `lpass show --format="%/_as%/_ag%_an"`. Modifying ~~~~~~~~~