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