glima 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.org +53 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/config_example.yml +8 -0
- data/exe/glima +287 -0
- data/glima.gemspec +39 -0
- data/lib/glima.rb +18 -0
- data/lib/glima/cli.rb +151 -0
- data/lib/glima/command.rb +35 -0
- data/lib/glima/command/base.rb +23 -0
- data/lib/glima/command/dezip.rb +45 -0
- data/lib/glima/command/guess.rb +30 -0
- data/lib/glima/command/label.rb +29 -0
- data/lib/glima/command/labels.rb +63 -0
- data/lib/glima/command/profile.rb +15 -0
- data/lib/glima/command/push.rb +26 -0
- data/lib/glima/command/relabel.rb +65 -0
- data/lib/glima/command/scan.rb +32 -0
- data/lib/glima/command/trash.rb +22 -0
- data/lib/glima/command/watch.rb +45 -0
- data/lib/glima/command/xzip.rb +103 -0
- data/lib/glima/config.rb +205 -0
- data/lib/glima/context.rb +32 -0
- data/lib/glima/datastore.rb +70 -0
- data/lib/glima/gmail_client.rb +270 -0
- data/lib/glima/imap.rb +219 -0
- data/lib/glima/query_parameter.rb +30 -0
- data/lib/glima/resource.rb +31 -0
- data/lib/glima/resource/history.rb +94 -0
- data/lib/glima/resource/label.rb +22 -0
- data/lib/glima/resource/mail.rb +155 -0
- data/lib/glima/resource/message.rb +74 -0
- data/lib/glima/version.rb +3 -0
- data/lib/glima/zip.rb +156 -0
- data/spec/glima_spec.rb +11 -0
- data/spec/spec_helper.rb +2 -0
- metadata +213 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module Glima
|
2
|
+
class QueryParameter
|
3
|
+
class FormatError < StandardError; end
|
4
|
+
|
5
|
+
def initialize(folder, query_string, context = nil)
|
6
|
+
@params = {}
|
7
|
+
@folder, @query_string = folder, query_string
|
8
|
+
|
9
|
+
if folder == "+all"
|
10
|
+
@params[:q] = ""
|
11
|
+
elsif /^\+(\S+)/ =~ folder
|
12
|
+
@params[:q] = "in:\"#{$1}\""
|
13
|
+
else
|
14
|
+
fail "Unknown folder: #{folder}."
|
15
|
+
end
|
16
|
+
|
17
|
+
if query_string == "next"
|
18
|
+
@params[:page_token] = context&.load_page_token
|
19
|
+
raise FormatError.new("No more page") if @params[:page_token].to_s == ""
|
20
|
+
else
|
21
|
+
@params[:q] += " #{query_string}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
@params
|
27
|
+
end
|
28
|
+
|
29
|
+
end # class QueryParameter
|
30
|
+
end # module Glima
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class String
|
2
|
+
def indent_heredoc(indent = 0)
|
3
|
+
strip_heredoc.gsub(/^/, ' ' * indent)
|
4
|
+
end
|
5
|
+
|
6
|
+
def strip_heredoc
|
7
|
+
indent = scan(/^[ \t]*(?=\S)/).min.size rescue 0
|
8
|
+
gsub(/^[ \t]{#{indent}}/, '')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Glima
|
13
|
+
module Resource
|
14
|
+
class ParseError < StandardError; end
|
15
|
+
|
16
|
+
class Base
|
17
|
+
def initialize(raw_resource)
|
18
|
+
@raw_resource = raw_resource
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
dir = File.dirname(__FILE__) + "/resource"
|
23
|
+
|
24
|
+
autoload :History, "#{dir}/history.rb"
|
25
|
+
autoload :Label, "#{dir}/label.rb"
|
26
|
+
autoload :Mail, "#{dir}/mail.rb"
|
27
|
+
autoload :Message, "#{dir}/message.rb"
|
28
|
+
autoload :Thread, "#{dir}/thread.rb"
|
29
|
+
autoload :User, "#{dir}/user.rb"
|
30
|
+
end # modlue Resource
|
31
|
+
end # module Glima
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Glima
|
2
|
+
module Resource
|
3
|
+
class History < Base
|
4
|
+
class Event
|
5
|
+
attr_reader :history_id, :message, :type, :label_ids
|
6
|
+
|
7
|
+
def initialize(history_id:, message:, type:, label_ids: nil)
|
8
|
+
@history_id, @message, @type, @label_ids = history_id, message, type, label_ids
|
9
|
+
end
|
10
|
+
|
11
|
+
def dump
|
12
|
+
str = "history: #{history_id}, messgae: #{message.id}, type: #{type}"
|
13
|
+
str += ", label_ids: #{label_ids.join(',')}" if label_ids
|
14
|
+
str
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Single history entry will be converted to multiple events
|
19
|
+
def to_events
|
20
|
+
events = []
|
21
|
+
h = @raw_resource
|
22
|
+
id = h.id
|
23
|
+
|
24
|
+
h.messages_added.each do |ent|
|
25
|
+
events << Event.new(history_id: id, message: ent.message, type: :added)
|
26
|
+
end if h.messages_added
|
27
|
+
|
28
|
+
h.messages_deleted.each do |ent|
|
29
|
+
events << Event.new(history_id: id, message: ent.message, type: :deleted)
|
30
|
+
end if h.messages_deleted
|
31
|
+
|
32
|
+
h.labels_added.each do |ent|
|
33
|
+
events << Event.new(history_id: id, message: ent.message, type: :labels_added, label_ids: ent.label_ids)
|
34
|
+
end if h.labels_added
|
35
|
+
|
36
|
+
h.labels_removed.each do |ent|
|
37
|
+
events << Event.new(history_id: id, message: ent.message, type: :labels_removed, label_ids: ent.label_ids)
|
38
|
+
end if h.labels_removed
|
39
|
+
|
40
|
+
return events
|
41
|
+
end
|
42
|
+
|
43
|
+
def dump
|
44
|
+
h = @raw_resource
|
45
|
+
|
46
|
+
str = ""
|
47
|
+
types = []
|
48
|
+
|
49
|
+
msgs = h.messages
|
50
|
+
str += "** Messages: (#{msgs.length})\n"
|
51
|
+
msgs.each do |m|
|
52
|
+
str += Message.new(m).dump
|
53
|
+
end
|
54
|
+
|
55
|
+
if msgs = h.messages_added
|
56
|
+
types << :messages_added
|
57
|
+
str += "** Messages Added (#{msgs.length}):\n"
|
58
|
+
msgs.map(&:message).each do |m|
|
59
|
+
str += Message.new(m).dump
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if msgs = h.messages_deleted
|
64
|
+
types << :messages_deleted
|
65
|
+
str += "** Messages Deleted (#{msgs.length}):\n"
|
66
|
+
msgs.map(&:message).each do |m|
|
67
|
+
str += Message.new(m).dump
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if msgs = h.labels_added
|
72
|
+
types << :labels_added
|
73
|
+
str += "** Labels Added (#{msgs.length}):\n"
|
74
|
+
h.labels_added.each do |lm|
|
75
|
+
str += Message.new(lm.message).dump
|
76
|
+
str += " label_ids: " + lm.label_ids.join(',')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if msgs = h.labels_removed
|
81
|
+
types << :labels_removed
|
82
|
+
str += "** Labels Removed (#{msgs.length}):\n"
|
83
|
+
h.labels_removed.each do |lm|
|
84
|
+
str += Message.new(lm.message).dump
|
85
|
+
str += " label_ids: " + lm.label_ids.join(',')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
return "* Id: #{h.id}, types: " + types.join(",") + "\n" + str
|
90
|
+
end
|
91
|
+
|
92
|
+
end # class History
|
93
|
+
end # module Resource
|
94
|
+
end # modlue Glima
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Glima
|
2
|
+
module Resource
|
3
|
+
class Label < Base
|
4
|
+
|
5
|
+
def dump
|
6
|
+
label = @raw_resource
|
7
|
+
str =
|
8
|
+
"id: #{label.id}\n" +
|
9
|
+
"name: #{label.name}\n" +
|
10
|
+
"messageListVisibility: #{label.message_list_visibility}\n" +
|
11
|
+
"labelListVisibility: #{label.label_list_visibility}\n" +
|
12
|
+
"type: #{label.type}\n" +
|
13
|
+
"messagesTotal: #{label.messages_total}\n" +
|
14
|
+
"messagesUnread: #{label.messages_unread}\n" +
|
15
|
+
"threadsTotal: #{label.threads_total}\n" +
|
16
|
+
"threadsUnread: #{label.threads_unread}\n"
|
17
|
+
return str
|
18
|
+
end
|
19
|
+
|
20
|
+
end # class Label
|
21
|
+
end # module Resource
|
22
|
+
end # modlue Glima
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require "mail"
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Glima
|
5
|
+
module Resource
|
6
|
+
class Mail < ::Mail::Message
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@gmail_message,
|
10
|
+
# Users.histoy
|
11
|
+
:internal_date,
|
12
|
+
:snippet
|
13
|
+
|
14
|
+
def self.read(mail_filename)
|
15
|
+
new(File.open(mail_filename, 'rb') {|f| f.read })
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(message)
|
19
|
+
if message.respond_to?(:raw)
|
20
|
+
@gmail_message = message
|
21
|
+
super(message.raw)
|
22
|
+
else
|
23
|
+
super(message)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def gm_msgid
|
28
|
+
@gmail_message&.id
|
29
|
+
end
|
30
|
+
alias_method :id, :gm_msgid
|
31
|
+
|
32
|
+
def gm_thrid
|
33
|
+
@gmail_message&.thread_id
|
34
|
+
end
|
35
|
+
alias_method :thread_id, :gm_thrid
|
36
|
+
|
37
|
+
def gm_label_ids
|
38
|
+
@gmail_message&.label_ids
|
39
|
+
end
|
40
|
+
alias_method :label_ids, :gm_label_ids
|
41
|
+
|
42
|
+
def raw
|
43
|
+
@gmail_message&.raw
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_plain_text
|
47
|
+
mail_to_plain_text(self)
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_passwordish_strings
|
51
|
+
mail = self
|
52
|
+
body = mail.to_plain_text
|
53
|
+
|
54
|
+
password_candidates = []
|
55
|
+
|
56
|
+
# gather passwordish ASCII strings.
|
57
|
+
body.scan(/(?:^|[^!-~])([!-~]{4,16})[^!-~]/) do |str|
|
58
|
+
password_candidates += str
|
59
|
+
end
|
60
|
+
return password_candidates
|
61
|
+
end
|
62
|
+
|
63
|
+
def unlock_zip!(password_candidates = [""], logger = nil)
|
64
|
+
# Unlock all zip attachments in mail
|
65
|
+
transformed = false
|
66
|
+
|
67
|
+
self.attachments.each do |attachment|
|
68
|
+
next unless attachment.filename =~ /\.zip$/i
|
69
|
+
|
70
|
+
zip = Glima::Zip.new(attachment.body.decoded)
|
71
|
+
# try all passwords
|
72
|
+
if zip.unlock_password!(password_candidates, logger)
|
73
|
+
attachment.body = zip.to_decrypted_unicode_zip.to_base64
|
74
|
+
attachment.content_transfer_encoding = "base64"
|
75
|
+
transformed = true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
return transformed
|
79
|
+
end
|
80
|
+
|
81
|
+
def format_summary(count = nil)
|
82
|
+
date = Time.at(internal_date.to_i/1000).strftime("%m/%d %H:%M")
|
83
|
+
count = if count then ("%4d " % count) else "" end
|
84
|
+
return "#{count}#{date} #{id} #{CGI.unescapeHTML(snippet)[0..30]}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_mew(count = nil)
|
88
|
+
date = Time.at(internal_date.to_i/1000).strftime("%m/%d ")
|
89
|
+
|
90
|
+
mark1 = " "
|
91
|
+
mark1 = "U" if label_ids.include?("UNREAD")
|
92
|
+
|
93
|
+
mark2 = " "
|
94
|
+
mark2 = "-" if content_type =~ /multipart\/alternative/
|
95
|
+
mark2 = "M" unless attachments.empty?
|
96
|
+
|
97
|
+
folder = File.expand_path("~/Mail/all") # XXX
|
98
|
+
|
99
|
+
summary = "#{mark1}#{mark2}#{date} #{CGI.unescapeHTML(snippet)}"
|
100
|
+
summary += "\r +#{folder} #{id} <#{id}>"
|
101
|
+
summary += if id != thread_id then " <#{thread_id}>" else " " end
|
102
|
+
|
103
|
+
return summary + " 1 2"
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def mail_to_plain_text(mail)
|
109
|
+
parts = if mail.multipart? then mail.parts else [mail] end
|
110
|
+
|
111
|
+
body = parts.map do |part|
|
112
|
+
part_to_plain_text(part)
|
113
|
+
end.join("----PART----PART----PART----PART----PART----\n")
|
114
|
+
|
115
|
+
return pretty_hearder + "\n" + body
|
116
|
+
end
|
117
|
+
|
118
|
+
def part_to_plain_text(part)
|
119
|
+
case part.content_type
|
120
|
+
when /text\/plain/
|
121
|
+
convert_to_utf8(part.body.decoded.to_s,
|
122
|
+
part.content_type_parameters["charset"])
|
123
|
+
when /multipart\/alternative/
|
124
|
+
part_to_plain_text(part.text_part)
|
125
|
+
|
126
|
+
when /message\/rfc822/
|
127
|
+
mail_to_plain_text(::Mail.new(part.body.decoded.to_s))
|
128
|
+
|
129
|
+
else
|
130
|
+
"NOT_TEXT_PART (#{part.content_type})\n"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def convert_to_utf8(string, from_charset = nil)
|
135
|
+
if from_charset && from_charset != "utf-8"
|
136
|
+
string.encode("utf-8", from_charset,
|
137
|
+
:invalid => :replace, :undef => :replace)
|
138
|
+
else
|
139
|
+
string.force_encoding("utf-8")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def pretty_hearder
|
144
|
+
mail = self
|
145
|
+
["Subject: #{mail.subject}",
|
146
|
+
"From: #{mail.header['from']&.decoded}",
|
147
|
+
"Date: #{mail.header['date']}",
|
148
|
+
"Message-Id: #{mail.header['message_id']}",
|
149
|
+
"To: #{mail.header['to']&.decoded}",
|
150
|
+
"Cc: #{mail.header['cc']&.decoded}"
|
151
|
+
].join("\n") + "\n"
|
152
|
+
end
|
153
|
+
end # class Mail
|
154
|
+
end # module Resource
|
155
|
+
end # module Glima
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Glima
|
2
|
+
module Resource
|
3
|
+
class Message < Base
|
4
|
+
|
5
|
+
def dump
|
6
|
+
dump_message(@raw_resource)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def dump_message(msg, indent = 0)
|
11
|
+
str1 = <<-EOF.indent_heredoc(indent)
|
12
|
+
id: #{msg.id}
|
13
|
+
threadId: #{msg.thread_id}
|
14
|
+
labelIds: #{msg.label_ids&.join(', ')}
|
15
|
+
snippet: #{msg.snippet&.slice(0..20)}...
|
16
|
+
historyId: #{msg.history_id}
|
17
|
+
internalDate: #{msg.internal_date}
|
18
|
+
sizeEstimate: #{msg.size_estimate}
|
19
|
+
payload:
|
20
|
+
EOF
|
21
|
+
str1 += dump_message_part(msg.payload, indent + 2)
|
22
|
+
|
23
|
+
str2 = <<-EOF.indent_heredoc(indent)
|
24
|
+
raw:
|
25
|
+
EOF
|
26
|
+
str2 += (msg.raw.force_encoding("UTF-8")) if msg.raw
|
27
|
+
return str1 + str2
|
28
|
+
end
|
29
|
+
|
30
|
+
def dump_message_part(part, indent)
|
31
|
+
return (" " * indent) + "part is NULL\n" unless part
|
32
|
+
|
33
|
+
str1 = <<-EOF.indent_heredoc(indent)
|
34
|
+
partId: #{part.part_id}
|
35
|
+
mimeType: #{part.mime_type}
|
36
|
+
filename: #{part.filename}
|
37
|
+
headers: #{dump_message_headers(part.headers)}
|
38
|
+
body:
|
39
|
+
EOF
|
40
|
+
str1 += dump_message_attachment(part.body, indent + 2) if part.body
|
41
|
+
|
42
|
+
str2 = <<-EOF.indent_heredoc(indent)
|
43
|
+
parts:
|
44
|
+
EOF
|
45
|
+
str2 += dump_message_parts(part.parts, indent + 2)
|
46
|
+
|
47
|
+
return str1 + str2
|
48
|
+
end
|
49
|
+
|
50
|
+
def dump_message_headers(headers, all = nil)
|
51
|
+
return "headers is empty" unless headers
|
52
|
+
str = headers.map{|h| h.name + ": " + h.value}.join("\n")
|
53
|
+
return str if all
|
54
|
+
return str.split("\n").first
|
55
|
+
end
|
56
|
+
|
57
|
+
def dump_message_parts(parts, indent = 0)
|
58
|
+
return (' ' * indent) + "parts is empty\n" unless parts
|
59
|
+
return parts.map{|p| dump_message_part(p, indent)}.join("\n") + "\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
def dump_message_body(body, indent)
|
63
|
+
str = <<-EOF.indent_heredoc(indent)
|
64
|
+
attachmentId: #{(body.attachment_id.to_s)[0..20]}
|
65
|
+
size: #{body&.size}
|
66
|
+
data: #{if body.data then body.data.force_encoding("UTF-8")&.gsub(/\r?\n/, "")[0..20] else 'NULL' end}...
|
67
|
+
EOF
|
68
|
+
return str
|
69
|
+
end
|
70
|
+
alias_method :dump_message_attachment, :dump_message_body
|
71
|
+
|
72
|
+
end # class Message
|
73
|
+
end # module Resource
|
74
|
+
end # module Glima
|
data/lib/glima/zip.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require "zip"
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
module Glima
|
5
|
+
class Zip
|
6
|
+
attr_accessor :password
|
7
|
+
|
8
|
+
def self.read(zip_filename, password = "")
|
9
|
+
new(File.open(File.expand_path(zip_file)).read, password)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(zip_string, password = "")
|
13
|
+
@zip_string = zip_string
|
14
|
+
@password = password
|
15
|
+
end
|
16
|
+
|
17
|
+
def correct_password?(password)
|
18
|
+
with_input_stream(password) do |zip|
|
19
|
+
begin
|
20
|
+
# Looking the first entry is not enough, because
|
21
|
+
# some zip files have directory entry which size is zero
|
22
|
+
# and no error is emitted even with wrong password.
|
23
|
+
while entry = zip.get_next_entry
|
24
|
+
size = zip.read.size # Exception if invalid password
|
25
|
+
return false if size != entry.size
|
26
|
+
return true if size > 0 # short cut
|
27
|
+
end
|
28
|
+
rescue Zlib::DataError => e
|
29
|
+
puts "*** #{e} ***" if $DEBUG
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
# False-positive if all files are emtpy.
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def unlock_password!(password_candidates, logger = nil)
|
39
|
+
list = sort_by_password_strength(password_candidates.uniq).unshift("")
|
40
|
+
|
41
|
+
list.each do |password|
|
42
|
+
msg = "Try password:'#{password}' (#{password_strength(password)})..."
|
43
|
+
|
44
|
+
if correct_password?(password)
|
45
|
+
logger.info(msg + " OK.") if logger
|
46
|
+
@password = password
|
47
|
+
return password # Found password
|
48
|
+
else
|
49
|
+
logger.info(msg + " NG.") if logger
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return nil # No luck
|
53
|
+
end
|
54
|
+
|
55
|
+
def encrypted?
|
56
|
+
correct_password?("")
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_to_file(file)
|
60
|
+
return file.write(@zip_string) if file.respond_to?(:write)
|
61
|
+
|
62
|
+
File.open(file, "w") do |f|
|
63
|
+
f.write(@zip_string)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
@zip_string
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_base64
|
72
|
+
Base64.encode64(@zip_string)
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_decrypted_unicode_zip()
|
76
|
+
::Zip.unicode_names = true
|
77
|
+
|
78
|
+
out = ::Zip::OutputStream.write_buffer(StringIO.new) do |zos|
|
79
|
+
with_input_stream(@password) do |zis|
|
80
|
+
while entry = zis.get_next_entry
|
81
|
+
name = cp932_path_to_utf8_path(entry.name)
|
82
|
+
|
83
|
+
# Two types of Exception will occur on encrypted zip:
|
84
|
+
# 1) "invalid block type (Zlib::DataError)" if password is not specified.
|
85
|
+
# 2) "invalid stored block lengths (Zlib::DataError)" if password is wrong.
|
86
|
+
content = zis.read
|
87
|
+
raise Zlib::DataError if content.size != entry.size
|
88
|
+
|
89
|
+
zos.put_next_entry(name)
|
90
|
+
zos.write(content)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
Zip.new(out.string)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def password_strength(password)
|
100
|
+
password = password.to_s
|
101
|
+
score = Math.log2(password.length + 1)
|
102
|
+
|
103
|
+
password.scan(/[a-z]+|[A-Z]+|\d+|[!"#$%&'()*+,-.\/:;<=>?@\[\\\]^_`{|}~]+/) do |s|
|
104
|
+
score += 1.0
|
105
|
+
end
|
106
|
+
return score
|
107
|
+
end
|
108
|
+
|
109
|
+
def sort_by_password_strength(password_array)
|
110
|
+
password_array.sort{|a,b|
|
111
|
+
password_strength(b) <=> password_strength(a)
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def with_input_stream(password = "", &block)
|
116
|
+
::Zip::InputStream.open(StringIO.new(@zip_string), 0, decrypter(password)) do |zis|
|
117
|
+
yield zis
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def decrypter(password = "")
|
122
|
+
if password.empty?
|
123
|
+
nil # return empty decrypter
|
124
|
+
else
|
125
|
+
::Zip::TraditionalDecrypter.new(password)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# 1) Convert CP932 (SJIS) to UTF8.
|
130
|
+
# 2) Replace path-separators from backslash (\) to slash (/).
|
131
|
+
#
|
132
|
+
# Example:
|
133
|
+
# path = io.get_next_entry.name # rubyzip returns ASCII-8BIT string as name.
|
134
|
+
# path is:
|
135
|
+
# + ASCII-8BIT
|
136
|
+
# + Every backslash is replaced to '/' even in second-byte of CP932.
|
137
|
+
#
|
138
|
+
# See also:
|
139
|
+
# https://github.com/rubyzip/rubyzip/blob/master/lib/zip/entry.rb#L223
|
140
|
+
# Zip::Entry#read_local_entry does gsub('\\', '/')
|
141
|
+
#
|
142
|
+
def cp932_path_to_utf8_path(cp932_path_string)
|
143
|
+
# Replace-back all '/' to '\'
|
144
|
+
name = cp932_path_string.force_encoding("BINARY").gsub('/', '\\')
|
145
|
+
|
146
|
+
# Change endoding to CP932 (SJIS) and replace all '\' to '/'
|
147
|
+
# In this replacement, '\' in second-byte of CP932 will be preserved.
|
148
|
+
name = name.force_encoding("CP932").gsub('\\', '/')
|
149
|
+
|
150
|
+
# Convert CP932 to UTF-8
|
151
|
+
return name.encode("utf-8", "CP932",
|
152
|
+
:invalid => :replace, :undef => :replace)
|
153
|
+
end
|
154
|
+
|
155
|
+
end # class Zip
|
156
|
+
end # module Glima
|