s7 0.0.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.
- data/ChangeLog +311 -0
- data/LICENCE +24 -0
- data/README +56 -0
- data/Rakefile +128 -0
- data/VERSION +1 -0
- data/bin/s7cli +27 -0
- data/lib/gettext.rb +28 -0
- data/lib/s7.rb +12 -0
- data/lib/s7/action.rb +7 -0
- data/lib/s7/attribute.rb +226 -0
- data/lib/s7/cipher.rb +110 -0
- data/lib/s7/configuration.rb +41 -0
- data/lib/s7/entry.rb +106 -0
- data/lib/s7/entry_collection.rb +44 -0
- data/lib/s7/entry_template.rb +10 -0
- data/lib/s7/exception.rb +116 -0
- data/lib/s7/file.rb +77 -0
- data/lib/s7/gpass_file.rb +202 -0
- data/lib/s7/key.rb +83 -0
- data/lib/s7/message_catalog.rb +5 -0
- data/lib/s7/s7_file.rb +47 -0
- data/lib/s7/s7cli.rb +213 -0
- data/lib/s7/s7cli/attribute_command.rb +69 -0
- data/lib/s7/s7cli/command.rb +210 -0
- data/lib/s7/s7cli/entry_collection_command.rb +63 -0
- data/lib/s7/s7cli/entry_command.rb +728 -0
- data/lib/s7/s7cli/option.rb +101 -0
- data/lib/s7/secret_generator.rb +30 -0
- data/lib/s7/undo_stack.rb +7 -0
- data/lib/s7/unicode_data.rb +29 -0
- data/lib/s7/utils.rb +37 -0
- data/lib/s7/world.rb +150 -0
- data/setup.rb +1585 -0
- data/test/s7/attribute_test.rb +45 -0
- data/test/s7/gpass_file_test.rb +169 -0
- data/test/s7/gpass_file_test/passwords.gps.empty +1 -0
- data/test/s7/gpass_file_test/passwords.gps.one_entry +2 -0
- data/test/s7/gpass_file_test/passwords.gps.three_entries +3 -0
- data/test/s7/gpass_file_test/passwords.gps.with_folders +0 -0
- data/test/s7/secret_generator_test.rb +29 -0
- data/test/s7/unicode_data_test.rb +28 -0
- data/test/s7/world_test.rb +35 -0
- data/test/test_helper.rb +19 -0
- metadata +108 -0
data/lib/s7/entry.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "gettext"
|
4
|
+
|
5
|
+
module S7
|
6
|
+
# 機密情報を表現する。
|
7
|
+
class Entry
|
8
|
+
include GetText
|
9
|
+
|
10
|
+
# タグ名の配列。
|
11
|
+
attr_reader :tags
|
12
|
+
|
13
|
+
# 属性の配列。
|
14
|
+
attr_reader :attributes
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@attributes = []
|
18
|
+
attr = NumericAttribute.new("name" => N_("id"), "editabled" => false,
|
19
|
+
"protected" => true)
|
20
|
+
@attributes.push(attr)
|
21
|
+
attr = TextAttribute.new("name" => N_("name"), "protected" => true)
|
22
|
+
@attributes.push(attr)
|
23
|
+
attr = NumericAttribute.new("name" => N_("rate"), "protected" => true,
|
24
|
+
"value" => 1)
|
25
|
+
@attributes.push(attr)
|
26
|
+
|
27
|
+
@tags = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# ID を取得する。
|
31
|
+
def id
|
32
|
+
return get_attribute("id").value
|
33
|
+
end
|
34
|
+
|
35
|
+
# ID を設定する。
|
36
|
+
def id=(val)
|
37
|
+
get_attribute("id").value = val
|
38
|
+
end
|
39
|
+
|
40
|
+
# name で指定した名前の属性を取得する。
|
41
|
+
# 存在しない場合、nil を返す。
|
42
|
+
def get_attribute(name)
|
43
|
+
return @attributes.find { |attr| attr.name == name }
|
44
|
+
end
|
45
|
+
|
46
|
+
# attributes で指定した属性を追加する。
|
47
|
+
# 名前が同じ属性が存在する場合、値を書き換える。
|
48
|
+
# 名前が同じ属性が存在し、なおかつ型が異なる場合、ApplicationError
|
49
|
+
# 例外を発生させる。
|
50
|
+
def add_attributes(*attributes)
|
51
|
+
[attributes].flatten.each do |attribute|
|
52
|
+
attr = @attributes.find { |a|
|
53
|
+
a.name == attribute.name
|
54
|
+
}
|
55
|
+
if attr
|
56
|
+
if attr.class == attribute.class
|
57
|
+
attr.value = attribute.value
|
58
|
+
else
|
59
|
+
raise ApplicationError, _("not same attribute type: name=<%s> exist=<%s> argument=<%s>") % [attr.name, attr.class, attribute.class]
|
60
|
+
end
|
61
|
+
else
|
62
|
+
@attributes.push(attribute)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# 全ての属性の複製を取得する。
|
68
|
+
def duplicate_attributes(exclude_uneditable = true)
|
69
|
+
if exclude_uneditable
|
70
|
+
attrs = @attributes
|
71
|
+
else
|
72
|
+
attrs = @attributes.select { |attr|
|
73
|
+
attr.editable?
|
74
|
+
}
|
75
|
+
end
|
76
|
+
return attrs.collect { |attr|
|
77
|
+
a = attr.dup
|
78
|
+
begin
|
79
|
+
a.value = attr.value.dup
|
80
|
+
rescue TypeError
|
81
|
+
end
|
82
|
+
a
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_missing(symbol, *args)
|
87
|
+
attr_name = symbol.to_s
|
88
|
+
if attr_name[-1] == "="
|
89
|
+
attr_name = attr_name[0..-2]
|
90
|
+
setter = true
|
91
|
+
end
|
92
|
+
attr = @attributes.find { |a|
|
93
|
+
a.name == attr_name
|
94
|
+
}
|
95
|
+
if attr
|
96
|
+
if setter
|
97
|
+
attr.value = args.first
|
98
|
+
else
|
99
|
+
return attr.value
|
100
|
+
end
|
101
|
+
else
|
102
|
+
super
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "s7/entry"
|
4
|
+
|
5
|
+
module S7
|
6
|
+
# 機密情報の集合を表現する。
|
7
|
+
class EntryCollection
|
8
|
+
# 機密情報の配列。
|
9
|
+
attr_reader :entries
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@entries = []
|
13
|
+
@max_id = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
# 機密情報を追加する。
|
17
|
+
def add_entries(*entries)
|
18
|
+
[entries].flatten.each do |entry|
|
19
|
+
@max_id += 1
|
20
|
+
entry.id = @max_id
|
21
|
+
@entries.push(entry)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# 機密情報を削除し、削除した機密情報を返す。
|
26
|
+
def delete_entries(*entries)
|
27
|
+
entries = [entries].flatten
|
28
|
+
return @entries.delete_if { |entry|
|
29
|
+
entries.include?(entry)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# other で指定した EntryCollection オブジェクトの entries をマージ
|
34
|
+
# する。
|
35
|
+
def merge(other)
|
36
|
+
add_entries(other.entries)
|
37
|
+
end
|
38
|
+
|
39
|
+
# 指定した id にマッチする Entry オブジェクトを entries から探す。
|
40
|
+
def find(id)
|
41
|
+
return @entries.find { |entry| entry.id == id }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/s7/exception.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "gettext"
|
4
|
+
|
5
|
+
module S7
|
6
|
+
# s7で定義する例外クラスの親クラス。
|
7
|
+
class ApplicationError < StandardError
|
8
|
+
include GetText
|
9
|
+
end
|
10
|
+
|
11
|
+
# 不正なパスフレーズを表現する例外クラス。
|
12
|
+
class InvalidPassphrase < ApplicationError
|
13
|
+
def to_s
|
14
|
+
return _("Invlaid passphrase.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# 不正なパスを表現する例外クラス。
|
19
|
+
class InvalidPath < ApplicationError
|
20
|
+
def initialize(path)
|
21
|
+
@path = path
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
return _("Invalid path: path=<%s>") % @path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# ファイルやディレクトリが存在しないことを表現する例外クラス。
|
30
|
+
class NotExist < ApplicationError
|
31
|
+
def initialize(path)
|
32
|
+
@path = path
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
return _("Does not exit: path=<%s>") % @path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# 機密情報が存在しないことを表現する例外クラス。
|
41
|
+
class NoSuchEntry < ApplicationError
|
42
|
+
def initialize(options = {})
|
43
|
+
@options = options
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
if !@options.empty?
|
48
|
+
s = ""
|
49
|
+
@options.each do |key, value|
|
50
|
+
s << " #{key}=<#{value}>"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if s.empty?
|
54
|
+
return _("No such entry.")
|
55
|
+
else
|
56
|
+
return _("No such entry:%s") % s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# 機密情報の属性が存在しないことを表現する例外クラス。
|
62
|
+
class NoSuchAttribute < ApplicationError
|
63
|
+
def initialize(options = {})
|
64
|
+
@options = options
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
if !@options.empty?
|
69
|
+
s = ""
|
70
|
+
@options.each do |key, value|
|
71
|
+
s << " #{key}=<#{value}>"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
if s.empty?
|
75
|
+
return _("No such attribute.")
|
76
|
+
else
|
77
|
+
return _("No such attribute:%s") % s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# データ長が不正であることを表現する例外クラス。
|
83
|
+
class DataLengthError < ApplicationError
|
84
|
+
def initialize(actual, needed)
|
85
|
+
@actual = actual
|
86
|
+
@needed = needed
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
return _("Invalid data length: actual=<%s> needed=<%s>") % [@actual, @needed]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# キャンセルを表現する例外クラス。
|
95
|
+
class Canceled < ApplicationError
|
96
|
+
MESSAGE = _("Canceled.")
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
return MESSAGE
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# OS のコマンドの実行に失敗したことを表現する例外クラス。
|
104
|
+
class CommandFailed < ApplicationError
|
105
|
+
# command:: 失敗したコマンド
|
106
|
+
# status:: コマンドの終了ステータス
|
107
|
+
def initialize(command, status)
|
108
|
+
@command = command
|
109
|
+
@status = status
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s
|
113
|
+
return _("Failed the command: command=<%s> status=<%d>") % [@command, @status]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/s7/file.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require "s7/exception"
|
2
|
+
|
3
|
+
module S7
|
4
|
+
class File
|
5
|
+
# パスワードコレクションのファイルのバージョン。
|
6
|
+
VERSION = "1.1.0"
|
7
|
+
|
8
|
+
# パスワードコレクションの先頭記述するマジック文字列。
|
9
|
+
MAGIC = "GPassFile version #{VERSION}"
|
10
|
+
|
11
|
+
# Cipherで使用するIV(Initial Vector)
|
12
|
+
IV = [5, 23, 1, 123, 12, 3, 54, 94].pack("C8")
|
13
|
+
|
14
|
+
# パスワードコレクションを格納するファイルを作成する。
|
15
|
+
# +path+:: 作成するファイルのパスを文字列で指定する。
|
16
|
+
# +passphrase+:: ファイルを暗号化するマスターパスフレーズを
|
17
|
+
# 文字列で指定する。
|
18
|
+
#
|
19
|
+
# すでにファイルが存在した場合、例外を発生させる。
|
20
|
+
def self.create(path, passphrase)
|
21
|
+
path = ::File.expand_path(path)
|
22
|
+
if ::File.exist?(path)
|
23
|
+
raise S7::Exception, "already exist file or directory: #{path}"
|
24
|
+
end
|
25
|
+
::File.open(path, "w") do |f|
|
26
|
+
key = S7::Key.create_instance("SHA-1", passphrase)
|
27
|
+
cipher =
|
28
|
+
S7::Cipher.create_instance("BF-CBC", :key => key, :iv => S7::File::IV)
|
29
|
+
magic = cipher.encrypt(StringIO.new(S7::File::MAGIC))
|
30
|
+
f.write(magic)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.open(path, passphrase)
|
35
|
+
path = ::File.expand_path(path)
|
36
|
+
if !::File.exist?(path)
|
37
|
+
raise S7::Exception, "no such file or directory: #{path}"
|
38
|
+
end
|
39
|
+
if !block_given?
|
40
|
+
raise ArgumentError, "no block given"
|
41
|
+
end
|
42
|
+
begin
|
43
|
+
f = self.new(path, passphrase)
|
44
|
+
yield(f)
|
45
|
+
ensure
|
46
|
+
f.close
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# パスワードコレクションのパス。
|
51
|
+
attr_accessor :path
|
52
|
+
|
53
|
+
# パスフレーズから生成したCipher用のキー。
|
54
|
+
attr_accessor :key
|
55
|
+
|
56
|
+
# キーとIVから生成したCipher。
|
57
|
+
attr_accessor :cipher
|
58
|
+
|
59
|
+
# 操作対象のファイル。
|
60
|
+
attr_accessor :file
|
61
|
+
|
62
|
+
def initialize(path, passphrase)
|
63
|
+
self.path = ::File.expand_path(path)
|
64
|
+
self.key = S7::Key.create_instance("SHA-1", passphrase)
|
65
|
+
self.cipher = S7::Cipher.create_instance("BF-CBC", :key => key,
|
66
|
+
:iv => S7::File::IV)
|
67
|
+
self.file = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# ファイルを閉じる。
|
71
|
+
def close
|
72
|
+
if self.file
|
73
|
+
self.file.close
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "s7/key"
|
4
|
+
require "s7/cipher"
|
5
|
+
require "s7/entry_collection"
|
6
|
+
require "s7/attribute"
|
7
|
+
require "gettext"
|
8
|
+
|
9
|
+
module S7
|
10
|
+
# GPass のファイルに関する処理を表現する。
|
11
|
+
class GPassFile
|
12
|
+
include GetText
|
13
|
+
|
14
|
+
# GPass のファイルが格納されているデフォルトのパス。
|
15
|
+
DEFAULT_PATH = "~/.gpass/passwords.gps"
|
16
|
+
|
17
|
+
# Blowfish の初期化配列。
|
18
|
+
IV = [5, 23, 1, 123, 12, 3, 54, 94].pack("C8")
|
19
|
+
|
20
|
+
# GPass のファイルであることを示すマジック。
|
21
|
+
MAGIC_PATTERN = /\AGPassFile version (\d\.\d\.\d)/
|
22
|
+
|
23
|
+
# GPass で扱う秘密情報の種類と属性。
|
24
|
+
ENTRY_ATTRIBUTE_TEMPLATES = {
|
25
|
+
"entry" => [TextAttribute.new("name" => N_("name")),
|
26
|
+
TextAttribute.new("name" => N_("description")),
|
27
|
+
DateTimeAttribute.new("name" => N_("created_at")),
|
28
|
+
DateTimeAttribute.new("name" => N_("updated_at")),
|
29
|
+
BooleanAttribute.new("name" => N_("expiration")),
|
30
|
+
DateTimeAttribute.new("name" => N_("expire_at"))],
|
31
|
+
"folder" => ["entry"],
|
32
|
+
"password" => ["entry",
|
33
|
+
TextAttribute.new("name" => N_("username")),
|
34
|
+
TextAttribute.new("name" => N_("password"),
|
35
|
+
"secret" => true)],
|
36
|
+
"general" => ["password",
|
37
|
+
TextAttribute.new("name" => N_("hostname"))],
|
38
|
+
"shell" => ["general"],
|
39
|
+
"website" => ["password",
|
40
|
+
TextAttribute.new("name" => N_("url"))],
|
41
|
+
}
|
42
|
+
|
43
|
+
ENTRY_ATTRIBUTE_TEMPLATES.each do |name, template|
|
44
|
+
loop do
|
45
|
+
replaced = false
|
46
|
+
template.each_with_index do |attr, i|
|
47
|
+
if attr.is_a?(String)
|
48
|
+
template[i] = ENTRY_ATTRIBUTE_TEMPLATES[attr]
|
49
|
+
replaced = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if replaced
|
53
|
+
template.flatten!
|
54
|
+
else
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def read(passphrase, path = DEFAULT_PATH)
|
61
|
+
res = EntryCollection.new
|
62
|
+
File.open(File.expand_path(path), "r") do |f|
|
63
|
+
f.flock(File::LOCK_EX)
|
64
|
+
s = decrypt(f, passphrase)
|
65
|
+
parse(s, res)
|
66
|
+
end
|
67
|
+
return res
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def decrypt(io, passphrase)
|
73
|
+
key = Key.create_instance("SHA-1", passphrase)
|
74
|
+
cipher = Cipher.create_instance("BF-CBC", :key => key, :iv => IV)
|
75
|
+
return cipher.decrypt(io)
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse(s, entry_collection)
|
79
|
+
if s.slice!(MAGIC_PATTERN).nil?
|
80
|
+
raise InvalidPassphrase
|
81
|
+
end
|
82
|
+
version = $1
|
83
|
+
# TODO: 1.1.0 より前のバージョンもサポートする。
|
84
|
+
if version != "1.1.0"
|
85
|
+
raise GPassFileError, _("not supported version: %s") % version
|
86
|
+
end
|
87
|
+
id_entry = {}
|
88
|
+
while !s.empty?
|
89
|
+
res = parse_gpass_entry(s)
|
90
|
+
id = res["id"]
|
91
|
+
entry = res["entry"]
|
92
|
+
if id_entry.key?(id)
|
93
|
+
raise GPassFileError, _("duplicated entry id: %s") % id
|
94
|
+
end
|
95
|
+
id_entry[id] = entry
|
96
|
+
parent = id_entry[res["parent_id"]]
|
97
|
+
if parent
|
98
|
+
entry.tags.push(*parent.tags)
|
99
|
+
end
|
100
|
+
if res["type"] == "folder"
|
101
|
+
entry.tags.push(entry.name)
|
102
|
+
else
|
103
|
+
entry_collection.add_entries(entry)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse_gpass_entry(s)
|
109
|
+
id = unpack_number(s)
|
110
|
+
parent_id = unpack_number(s)
|
111
|
+
type = unpack_string(s, unpack_number(s))
|
112
|
+
bytes = unpack_number(s)
|
113
|
+
if s.length < bytes
|
114
|
+
raise DataLengthError.new(s.length, bytes)
|
115
|
+
end
|
116
|
+
entry = Entry.new
|
117
|
+
unpack_attributes(entry, type, s.slice!(0, bytes))
|
118
|
+
return {
|
119
|
+
"id" => id,
|
120
|
+
"parent_id" => parent_id,
|
121
|
+
"type" => type,
|
122
|
+
"entry" => entry,
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def unpack_attributes(entry, type, s)
|
127
|
+
template = ENTRY_ATTRIBUTE_TEMPLATES[type]
|
128
|
+
if !template
|
129
|
+
raise GPassFileError, _("invalid entry type: type=<%s>") % type
|
130
|
+
end
|
131
|
+
template.each do |a|
|
132
|
+
attr = a.dup
|
133
|
+
case attr
|
134
|
+
when NumericAttribute
|
135
|
+
attr.value = unpack_variable_number(s)
|
136
|
+
when BooleanAttribute
|
137
|
+
attr.value = unpack_boolean(s)
|
138
|
+
when DateTimeAttribute
|
139
|
+
attr.value = unpack_time(s)
|
140
|
+
when TextAttribute
|
141
|
+
attr.value = unpack_string(s, unpack_variable_number(s))
|
142
|
+
end
|
143
|
+
entry.add_attributes(attr)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# s で指定したバイト列から 4 バイト取り出し、リトルエンディアンの数
|
148
|
+
# 値として unpack した結果を返す。
|
149
|
+
def unpack_number(s)
|
150
|
+
if s.length < 4
|
151
|
+
raise DataLengthError.new(s.length, 4)
|
152
|
+
end
|
153
|
+
return s.slice!(0, 4).unpack("V").first
|
154
|
+
end
|
155
|
+
|
156
|
+
# s で指定したバイト列から 8 ビット目が 0 のバイトを見つけるまで 1
|
157
|
+
# バイトずつ取り出し、4 バイトのリトルエンディアンの数値として
|
158
|
+
# unpack した結果を返す。
|
159
|
+
def unpack_variable_number(s)
|
160
|
+
n = 0
|
161
|
+
base = 1
|
162
|
+
i = 0
|
163
|
+
while (t = s.slice!(0)) && i <= 5
|
164
|
+
t = t.ord
|
165
|
+
if t & 0x80 == 0
|
166
|
+
n += base * t
|
167
|
+
break
|
168
|
+
else
|
169
|
+
n += base * (t & 0x7f)
|
170
|
+
base *= 0x80
|
171
|
+
end
|
172
|
+
i += 1
|
173
|
+
end
|
174
|
+
return [n].pack("V").unpack("I").first
|
175
|
+
end
|
176
|
+
|
177
|
+
# s で指定したバイト列から n で指定した長さを取り出し、それを文字列
|
178
|
+
# として返す。
|
179
|
+
def unpack_string(s, n)
|
180
|
+
if s.length < n
|
181
|
+
raise DataLengthError.new(s.length, n)
|
182
|
+
end
|
183
|
+
return s.slice!(0, n).force_encoding("utf-8")
|
184
|
+
end
|
185
|
+
|
186
|
+
# s で指定したバイト列から unpack_variable_number を使用して数値を
|
187
|
+
# 取り出す。それを time_t の値として扱い、DateTimeオブジェクトを返す。
|
188
|
+
def unpack_time(s)
|
189
|
+
return Time.at(unpack_variable_number(s)).to_datetime
|
190
|
+
end
|
191
|
+
|
192
|
+
# s で指定したバイト列から unpack_variable_number を使用して数値を
|
193
|
+
# 取り出す。それが 0 であれば false を、そうでなければ true を返す。
|
194
|
+
def unpack_boolean(s)
|
195
|
+
return unpack_variable_number(s) != 0
|
196
|
+
end
|
197
|
+
|
198
|
+
# GPass のファイルの処理中に発生したエラーを表現する例外クラス。
|
199
|
+
class GPassFileError < ApplicationError
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|