Skip to content

Instantly share code, notes, and snippets.

@yujiorama
Created December 31, 2022 06:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yujiorama/a3241cd3d4d2ddb41b15eb0cfd4c2871 to your computer and use it in GitHub Desktop.
Save yujiorama/a3241cd3d4d2ddb41b15eb0cfd4c2871 to your computer and use it in GitHub Desktop.
password formatter (google account password manager/firefox sync/lastpass)
#!/usr/bin/env ruby
require 'csv'
require 'optparse'
require 'pp'
require 'securerandom'
require 'time'
require 'uri'
# ==> chrome.csv <==
# name,url,username,password
# ==> firefox.csv <==
# "url","username","password","httpRealm","formActionOrigin","guid","timeCreated","timeLastUsed","timePasswordChanged"
# ==> lastpass.csv <==
# url,username,password,totp,extra,name,grouping,fav
csv_headers = {
chrome: %w(name url username password),
firefox: %w(url username password httpRealm formActionOrigin guid timeCreated timeLastUsed timePasswordChanged),
lastpass: %w(url username password totp extra name grouping fav)
}
accept_formats = csv_headers.keys.join("|")
sort_keys = {
chrome: %w(url username),
firefox: %w(url username),
lastpass: %w(url username),
}
params = {
:from => nil,
:to => :firefox,
:filter_url => [Regexp.union],
:filter_username => [Regexp.union],
}
opt = OptionParser.new
opt.on("--from [#{accept_formats}]", %r(\A#{accept_formats}\Z)) { |v| params[:from] = v.to_sym }
opt.on("--to [#{accept_formats}]", %r(\A#{accept_formats}\Z), "default value \"firefox\"") { |v| params[:to] = v.to_sym }
opt.on('--filter_url [url1,url2,url3,...]', Array) { |v|
params[:filter_url] = v.map { |x| Regexp.compile(%r{.*#{x}.*}) } unless v.nil? || v.empty?
}
opt.on('--filter_username [username1,username2,username3,...]', Array) { |v|
params[:filter_username] = v.map { |x| Regexp.compile(%r{.*#{x}.*}) } unless v.nil? || v.empty?
}
opt.parse!(ARGV)
to_headers = csv_headers[params[:to]]
to_sort_keys = sort_keys[params[:to]]
filter_url = params[:filter_url]
filter_username = params[:filter_username]
now = Time.now.to_i
src_lines = readlines
from_headers = if params[:from].nil?
src_headers = CSV.parse_line(src_lines[0])
params_from = csv_headers.invert.fetch(src_headers)
csv_headers[params_from]
else
csv_headers[params[:from]]
end
CSV.new(src_lines.join("\n"), headers: true, skip_blanks: true, skip_lines: %r(\A\s*#.*\Z))
.filter { |row|
url = URI.parse(row['url'])
if url.scheme =~ %r(http|https) then
! [url.host, row['username'], row['password']].any? {|v| v.nil? || v.empty?}
else
true
end
}.map { |row|
begin
url = URI.parse(row['url'])
if url.scheme =~ %r(http|https) then
build_args = {scheme: url.scheme, host: url.host.downcase, path: url.path}
if url.port != 80 && url.port != 443 then
build_args[:port] = url.port
end
row['url'] = URI::Generic.build(build_args).to_s
end
rescue
end
row
}.map { |row|
row['name'] ||= row['formActionOrigin'] ||= URI.parse(row['url']).host
row['httpRealm'] ||= ''
row['guid'] ||= SecureRandom.uuid
row['timePasswordChanged'] ||= row['timeLastUsed'] ||= row['timeCreated'] ||= now.to_s
row['totp'] ||= ''
row['extra'] ||= ''
row['grouping'] ||= ''
row['fav'] ||= 0
CSV::Row.new(to_headers, to_headers.map {|v| row[v]}, header_row: false)
}.sort { |a, b|
to_sort_keys.map {|v| a[v].downcase} <=> to_sort_keys.map {|v| b[v].downcase}
}.inject(
CSV.new($stdout, headers: to_headers, write_headers: true, force_quotes: true)
) { |acc, row|
if filter_url.any? {|v| v.match?(row['url'])} then
acc
elsif filter_username.any? {|v| v.match?(row['username'])} then
acc
else
acc << row
end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment