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 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
require 'rbconfig'
class Netrc
VERSION = "0.11.0"
# see http://stackoverflow.com/questions/4871309/what-is-the-correct-way-to-detect-if-ruby-is-running-on-windows
WINDOWS = RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
CYGWIN = RbConfig::CONFIG["host_os"] =~ /cygwin/
def self.default_path
File.join(ENV['NETRC'] || home_path, netrc_filename)
end
def self.home_path
home = Dir.respond_to?(:home) ? Dir.home : ENV['HOME']
if WINDOWS && !CYGWIN
home ||= File.join(ENV['HOMEDRIVE'], ENV['HOMEPATH']) if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
home ||= ENV['USERPROFILE']
# XXX: old stuff; most likely unnecessary
home = home.tr("\\", "/") unless home.nil?
end
(home && File.readable?(home)) ? home : Dir.pwd
rescue ArgumentError
return Dir.pwd
end
def self.netrc_filename
WINDOWS && !CYGWIN ? "_netrc" : ".netrc"
end
def self.config
@config ||= {}
end
def self.configure
yield(self.config) if block_given?
self.config
end
def self.check_permissions(path)
perm = File.stat(path).mode & 0777
if perm != 0600 && !(WINDOWS) && !(Netrc.config[:allow_permissive_netrc_file])
raise Error, "Permission bits for '#{path}' should be 0600, but are "+perm.to_s(8)
end
end
# Reads path and parses it as a .netrc file. If path doesn't
# exist, returns an empty object. Decrypt paths ending in .gpg.
def self.read(path=default_path)
check_permissions(path)
data = if path =~ /\.gpg$/
decrypted = `gpg --batch --quiet --decrypt #{path}`
if $?.success?
decrypted
else
raise Error.new("Decrypting #{path} failed.") unless $?.success?
end
else
File.read(path)
end
new(path, parse(lex(data.lines.to_a)))
rescue Errno::ENOENT
new(path, parse(lex([])))
end
class TokenArray < Array
def take
if length < 1
raise Error, "unexpected EOF"
end
shift
end
def readto
l = []
while length > 0 && ! yield(self[0])
l << shift
end
return l.join
end
end
def self.lex(lines)
tokens = TokenArray.new
for line in lines
content, comment = line.split(/(\s*#.*)/m)
content.each_char do |char|
case char
when /\s/
if tokens.last && tokens.last[-1..-1] =~ /\s/
tokens.last << char
else
tokens << char
end
else
if tokens.last && tokens.last[-1..-1] =~ /\S/
tokens.last << char
else
tokens << char
end
end
end
if comment
tokens << comment
end
end
tokens
end
def self.skip?(s)
s =~ /^\s/
end
# Returns two values, a header and a list of items.
# Each item is a tuple, containing some or all of:
# - machine keyword (including trailing whitespace+comments)
# - machine name
# - login keyword (including surrounding whitespace+comments)
# - login
# - password keyword (including surrounding whitespace+comments)
# - password
# - trailing chars
# This lets us change individual fields, then write out the file
# with all its original formatting.
def self.parse(ts)
cur, item = [], []
unless ts.is_a?(TokenArray)
ts = TokenArray.new(ts)
end
pre = ts.readto{|t| t == "machine" || t == "default"}
while ts.length > 0
if ts[0] == 'default'
cur << ts.take.to_sym
cur << ''
else
cur << ts.take + ts.readto{|t| ! skip?(t)}
cur << ts.take
end
if ts.include?('login')
cur << ts.readto{|t| t == "login"} + ts.take + ts.readto{|t| ! skip?(t)}
cur << ts.take
end
if ts.include?('password')
cur << ts.readto{|t| t == "password"} + ts.take + ts.readto{|t| ! skip?(t)}
cur << ts.take
end
cur << ts.readto{|t| t == "machine" || t == "default"}
item << cur
cur = []
end
[pre, item]
end
def initialize(path, data)
@new_item_prefix = ''
@path = path
@pre, @data = data
if @data && @data.last && :default == @data.last[0]
@default = @data.pop
else
@default = nil
end
end
attr_accessor :new_item_prefix
def [](k)
if item = @data.detect {|datum| datum[1] == k}
Entry.new(item[3], item[5])
elsif @default
Entry.new(@default[3], @default[5])
end
end
def []=(k, info)
if item = @data.detect {|datum| datum[1] == k}
item[3], item[5] = info
else
@data << new_item(k, info[0], info[1])
end
end
def length
@data.length
end
def delete(key)
datum = nil
for value in @data
if value[1] == key
datum = value
break
end
end
@data.delete(datum)
end
def each(&block)
@data.each(&block)
end
def new_item(m, l, p)
[new_item_prefix+"machine ", m, "\n login ", l, "\n password ", p, "\n"]
end
def save
if @path =~ /\.gpg$/
e = IO.popen("gpg -a --batch --default-recipient-self -e", "r+") do |gpg|
gpg.puts(unparse)
gpg.close_write
gpg.read
end
raise Error.new("Encrypting #{@path} failed.") unless $?.success?
File.open(@path, 'w', 0600) {|file| file.print(e)}
else
File.open(@path, 'w', 0600) {|file| file.print(unparse)}
end
end
def unparse
@pre + @data.map do |datum|
datum = datum.join
unless datum[-1..-1] == "\n"
datum << "\n"
else
datum
end
end.join
end
Entry = Struct.new(:login, :password) do
alias to_ary to_a
end
end
class Netrc::Error < ::StandardError
end
|