glima 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|