aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/contrib/importers/roboform2pass.rb
blob: df0f266605182607b615760b1b6398fae6905cf6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env ruby

# Copyright 2015 Sergey Makridenkov <sergey@makridenkov.com>.
# Released under MIT License.
#
# Usage:
#    1. Save roboform print lists (like File > Print lists > Logins) to ~/roboform_print_lists.
#    2. Run import: ./roboform2pass.rb ~/roboform_print_lists


require 'nokogiri'

class Login
  def initialize
    self.fields = {}
    self.raw = []
  end

  attr_accessor :key
  attr_accessor :password
  attr_accessor :url
  attr_accessor :fields
  attr_accessor :raw

  def ask_required_info
    ask!(:key) if blank?(key)
    find_password!
    if blank?(password)
      ask!(:password)
      puts ''
    end
  end

  def save
    if valid?
      IO.popen("pass insert -m -f '#{path}' > /dev/null", "w") do |pass_io|
        pass_io.puts password
        pass_io.puts "Url: #{url}" if present?(url)
        pass_io.puts "Fields: #{fields}" if fields.any?
        pass_io.puts "Notes: #{raw}" if raw.any?
      end

      $? == 0
    end
  end

  private

  def path
    key = self.key.downcase.gsub(/[^\w\.\/]/, '_').gsub(/_{2,}/, '_')
    "roboform/#{key}"
  end

  def valid?
    present?(key) && present?(password)
  end

  def find_password!
    fields.each do |key, val|
      key = key.downcase
      if key.include?('password') || key.include?('pwd') || key.include?('pass')
        self.password = val
      end
    end
  end

  def ask!(field)
    puts Colorize.red("#{field.capitalize} is empty for login:")
    print_self
    print Colorize.green "Please type #{field}: "
    self.send("#{field}=", gets.chomp)
  end

  def print_self
    puts Colorize.yellow "\tKey:\t#{key}" if present?(key)
    puts Colorize.yellow "\tPassword:\t#{password}" if present?(password)
    puts Colorize.yellow "\tUrl:\t#{url}" if present?(url)
    puts Colorize.yellow "\tFields:\t#{fields}" if fields.any?
    puts Colorize.yellow "\tRaw:\t#{raw}" if raw.any?
  end

  def blank?(str)
    !str || str.strip.empty?
  end

  def present?(str)
    !blank?(str)
  end
end

class Colorize
  class << self
    def red(mes)
      colorize(31, mes)
    end

    def green(mes)
      colorize(32, mes)
    end

    def yellow(mes)
      colorize(33, mes)
    end

    def pink(mes)
      colorize(35, mes)
    end

    private

    def colorize(color_code, mes)
      "\e[#{color_code}m#{mes}\e[0m"
    end
  end
end


print_list_dir = ARGV.pop
unless print_list_dir
  raise "No dir/to/roboform/print_lists"
end
print_list_dir = File.expand_path(print_list_dir)

# parse logins
logins_path = Dir.glob("#{print_list_dir}/RoboForm Logins*.html").first
unless logins_path
  raise 'Login HTML (RoboForm Logins*.html) not found'
end

html_logins = Nokogiri::HTML(File.open(logins_path))

saved_logins = 0

html_logins.css('table').each do |table|
  login = Login.new

  table.css('tr').each do |tr|
    caption = tr.at_css('.caption')
    subcaption = tr.at_css('.subcaption')
    key = tr.at_css('.field')

    if caption
      login.key = caption.text()
    elsif subcaption
      login.url = subcaption.text()
    elsif key
      login.fields[key.text()] = tr.at_css('.wordbreakfield').text()
    else
      login.raw << tr.at_css('.wordbreakfield').text()
    end
  end

  if login.fields.any?
    login.ask_required_info
    if login.save
      saved_logins += 1
    end
  end
end

puts "Imported passwords: #{saved_logins}"