hbci 0.2.0
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 +13 -0
- data/.rubocop.yml +26 -0
- data/.rubocop_todo.yml +26 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +107 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +7 -0
- data/examples/credentials.rb +62 -0
- data/examples/get_accounts.rb +11 -0
- data/examples/get_balance.rb +8 -0
- data/examples/get_system_id.rb +6 -0
- data/examples/get_transactions.rb +15 -0
- data/hbci.gemspec +36 -0
- data/lib/development_profiler.rb +26 -0
- data/lib/hbci.rb +66 -0
- data/lib/hbci/connector.rb +33 -0
- data/lib/hbci/dialog.rb +77 -0
- data/lib/hbci/element_group.rb +93 -0
- data/lib/hbci/element_groups/generated_element_groups.rb +969 -0
- data/lib/hbci/element_groups/segment_head.rb +12 -0
- data/lib/hbci/element_groups/unknown.rb +11 -0
- data/lib/hbci/element_unparser.rb +21 -0
- data/lib/hbci/message.rb +47 -0
- data/lib/hbci/message_factory.rb +17 -0
- data/lib/hbci/parser.rb +114 -0
- data/lib/hbci/request.rb +32 -0
- data/lib/hbci/response.rb +25 -0
- data/lib/hbci/segment.rb +118 -0
- data/lib/hbci/segment_factory.rb +15 -0
- data/lib/hbci/segments/hikaz.rb +29 -0
- data/lib/hbci/segments/hikazs.rb +31 -0
- data/lib/hbci/segments/hirmg.rb +10 -0
- data/lib/hbci/segments/hirms.rb +17 -0
- data/lib/hbci/segments/hisal.rb +100 -0
- data/lib/hbci/segments/hisals.rb +19 -0
- data/lib/hbci/segments/hisyn.rb +11 -0
- data/lib/hbci/segments/hiupa.rb +12 -0
- data/lib/hbci/segments/hiupd.rb +37 -0
- data/lib/hbci/segments/hkend.rb +14 -0
- data/lib/hbci/segments/hkidn.rb +22 -0
- data/lib/hbci/segments/hkkaz.rb +25 -0
- data/lib/hbci/segments/hksal.rb +31 -0
- data/lib/hbci/segments/hksyn.rb +12 -0
- data/lib/hbci/segments/hkvvb.rb +18 -0
- data/lib/hbci/segments/hnhbk.rb +27 -0
- data/lib/hbci/segments/hnhbs.rb +14 -0
- data/lib/hbci/segments/hnsha.rb +31 -0
- data/lib/hbci/segments/hnshk.rb +79 -0
- data/lib/hbci/segments/hnvsd.rb +50 -0
- data/lib/hbci/segments/hnvsk.rb +60 -0
- data/lib/hbci/segments/segment_head.rb +11 -0
- data/lib/hbci/segments/unknown.rb +21 -0
- data/lib/hbci/services/accounts_receiver.rb +19 -0
- data/lib/hbci/services/balance_receiver.rb +69 -0
- data/lib/hbci/services/base_receiver.rb +43 -0
- data/lib/hbci/services/system_id_receiver.rb +35 -0
- data/lib/hbci/services/transactions_receiver.rb +75 -0
- data/lib/hbci/version.rb +5 -0
- metadata +315 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class ElementUnparser
|
5
|
+
attr_reader :element
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def initialize(element, type)
|
9
|
+
@element = element
|
10
|
+
@type = type
|
11
|
+
end
|
12
|
+
|
13
|
+
def unparse
|
14
|
+
if type == :binary
|
15
|
+
"@#{element.size}@#{element}"
|
16
|
+
else
|
17
|
+
element.to_s.gsub('?', '??').gsub('+', '?+').gsub(':', '?:').gsub('\'', '?\'')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/hbci/message.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Message
|
5
|
+
attr_reader :dialog, :segments, :sec_ref
|
6
|
+
attr_accessor :next_position
|
7
|
+
|
8
|
+
def initialize(dialog = nil)
|
9
|
+
@dialog = dialog
|
10
|
+
@sec_ref = generate_security_reference
|
11
|
+
@segments = []
|
12
|
+
@next_position = 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_segment(segment)
|
16
|
+
segment.build(self)
|
17
|
+
@segments.push(segment)
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
@segments.each_with_index do |segment, index|
|
22
|
+
segment.compile
|
23
|
+
unless segment.head.position
|
24
|
+
segment.head.position = @next_position
|
25
|
+
@next_position += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@segments.each do |segment|
|
29
|
+
segment.after_compile if segment.respond_to?(:after_compile)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
segments.join('')
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_base64
|
38
|
+
Base64.encode64(to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def generate_security_reference
|
44
|
+
rand(1..23) * 999_999 + 1_000_000
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Hbci
|
2
|
+
module MessageFactory
|
3
|
+
def self.build(dialog)
|
4
|
+
request_message = Message.new(dialog)
|
5
|
+
request_message.add_segment(Segments::HNHBKv3.new)
|
6
|
+
request_message.add_segment(Segments::HNVSKv3.new)
|
7
|
+
hnvsd = Segments::HNVSDv1.new do |s|
|
8
|
+
s.add_segment(Segments::HNSHKv4.new)
|
9
|
+
yield s
|
10
|
+
s.add_segment(Segments::HNSHAv2.new)
|
11
|
+
end
|
12
|
+
request_message.add_segment(hnvsd)
|
13
|
+
request_message.add_segment(Segments::HNHBSv1.new)
|
14
|
+
request_message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/hbci/parser.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Parser
|
5
|
+
attr_reader :scanner
|
6
|
+
attr_accessor :segments
|
7
|
+
|
8
|
+
ELEMENT_DELIMITER = ':'
|
9
|
+
ELEMENT_GROUP_DELIMITER = '+'
|
10
|
+
SEGMENT_DELIMITER = '\''
|
11
|
+
|
12
|
+
# ELEMENT_REGEX matches everything until a a delimiter that is not escaped
|
13
|
+
#
|
14
|
+
# NODE EXPLANATION
|
15
|
+
# ------------------------------------------------------------------------
|
16
|
+
# ( group and capture to \1:
|
17
|
+
# ------------------------------------------------------------------------
|
18
|
+
# .*? any character except \n (0 or more times
|
19
|
+
# (matching the least amount possible))
|
20
|
+
# ------------------------------------------------------------------------
|
21
|
+
# (?= look ahead to see if there is:
|
22
|
+
# ------------------------------------------------------------------------
|
23
|
+
# (?<! look behind to see if there is not:
|
24
|
+
# ------------------------------------------------------------------------
|
25
|
+
# \? '?'
|
26
|
+
# ------------------------------------------------------------------------
|
27
|
+
# ) end of look-behind
|
28
|
+
# ------------------------------------------------------------------------
|
29
|
+
# [:+'] any character of: ':', '+', '''
|
30
|
+
# ------------------------------------------------------------------------
|
31
|
+
# ) end of look-ahead
|
32
|
+
# ------------------------------------------------------------------------
|
33
|
+
# ) end of \1
|
34
|
+
ELEMENT_REGEX = /(.*?(?=(?<!\?)[:+']))/
|
35
|
+
|
36
|
+
# Binary Elements may contain unescaped delimiters. Thus they are not
|
37
|
+
# terminated by regular delimiters. But their content is preceeded with
|
38
|
+
# its length surrounded by '@'s. e.g.:
|
39
|
+
#
|
40
|
+
# '@6@mydata' or '@12@mydatamydata'
|
41
|
+
#
|
42
|
+
# The BINARY_ELEMENT_LENGTH_REGEx matches only the length.
|
43
|
+
BINARY_ELEMENT_LENGTH_REGEX = /@(\d+)@/
|
44
|
+
|
45
|
+
def self.parse(string)
|
46
|
+
new(string).parse
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(string)
|
50
|
+
@scanner = StringScanner.new(string)
|
51
|
+
@segments = []
|
52
|
+
add_segment
|
53
|
+
add_element_group
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse
|
57
|
+
parse_element
|
58
|
+
while scanner.rest_size > 1
|
59
|
+
parse_delimiter
|
60
|
+
parse_element
|
61
|
+
end
|
62
|
+
segments
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def parse_regular_element
|
68
|
+
str = scanner.scan(ELEMENT_REGEX)
|
69
|
+
current_element_group << str.gsub('??', '?').gsub('?:', ':').gsub("?'", "'").gsub('?+', '+')
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_element
|
73
|
+
binary_element_ahead? ? parse_binary_element : parse_regular_element
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_delimiter
|
77
|
+
delimiter = scanner.getch
|
78
|
+
return if scanner.eos?
|
79
|
+
if delimiter == ELEMENT_GROUP_DELIMITER
|
80
|
+
add_element_group
|
81
|
+
elsif delimiter == SEGMENT_DELIMITER
|
82
|
+
add_segment
|
83
|
+
add_element_group
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_binary_element
|
88
|
+
scanner.scan(BINARY_ELEMENT_LENGTH_REGEX)
|
89
|
+
binary = scanner.rest.byteslice(0, scanner[1].to_i)
|
90
|
+
scanner.pos = scanner.pos + scanner[1].to_i
|
91
|
+
current_element_group << binary
|
92
|
+
end
|
93
|
+
|
94
|
+
def binary_element_ahead?
|
95
|
+
scanner.peek(1) == '@' && scanner.check(BINARY_ELEMENT_LENGTH_REGEX)
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_segment
|
99
|
+
segments << []
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_element_group
|
103
|
+
current_segment << []
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_segment
|
107
|
+
segments.last
|
108
|
+
end
|
109
|
+
|
110
|
+
def current_element_group
|
111
|
+
current_segment.last
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/hbci/request.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Request < Message
|
5
|
+
attr_reader :dialog
|
6
|
+
attr_reader :sec_ref
|
7
|
+
|
8
|
+
def head
|
9
|
+
@head ||= Segments::HNHBKv3.build(dialog: dialog, message: self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def enc_head
|
13
|
+
@enc_head ||= Segments::HNVSKv3.build(dialog: dialog)
|
14
|
+
end
|
15
|
+
|
16
|
+
def sig_head
|
17
|
+
@sig_head ||= Segments::HNSHKv4.build(dialog: dialog, message: self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def encrypted_payload
|
21
|
+
@encrypted_payload ||= Segments::HNVSDv1.build(message: self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def sig_tail
|
25
|
+
@sig_tail ||= Segments::HNSHAv2.build(dialog: dialog, message: self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def tail
|
29
|
+
@tail ||= Segments::HNHBSv1.build(dialog: dialog, message: self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Hbci
|
5
|
+
class Response
|
6
|
+
def initialize(raw_response)
|
7
|
+
@raw_response = raw_response
|
8
|
+
@raw_segments = Parser.parse(raw_response.force_encoding('iso-8859-1'))
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(segment_type)
|
12
|
+
segments = find_all(segment_type)
|
13
|
+
warn "more then one #{segment_type} segment available" if segments.size > 1
|
14
|
+
segments.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_all(segment_type)
|
18
|
+
@raw_segments.select { |sd| sd[0][0] == segment_type }.map{ |sd| Hbci::SegmentFactory.build(sd) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@raw_response
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/hbci/segment.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class Segment
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :element_groups
|
8
|
+
attr_reader :defined_element_groups
|
9
|
+
attr_accessor :message
|
10
|
+
attr_accessor :dialog
|
11
|
+
|
12
|
+
attr_accessor :request_message
|
13
|
+
|
14
|
+
def self.element_groups_to_be_defined
|
15
|
+
@element_groups_to_be_defined ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.element_group(name, definition = {}, &block)
|
19
|
+
element_groups_to_be_defined << definition.merge(name: name, block: block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.element(name, definition = {})
|
23
|
+
element_groups_to_be_defined << definition.merge(name: name, block: nil, passthrough: true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def build(message)
|
27
|
+
self.request_message = message
|
28
|
+
|
29
|
+
type, version = self.class.name.split('::').last.split('v')
|
30
|
+
|
31
|
+
head.type = type
|
32
|
+
head.version = version
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.fill(segment_data)
|
36
|
+
segment_data.each_with_object(new).with_index do |(element_group_data, segment), element_group_index|
|
37
|
+
element_group_data.each_with_index do |element_data, element_index|
|
38
|
+
segment.element_groups[element_group_index][element_index] = element_data
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@element_groups = []
|
45
|
+
@defined_element_groups ||= []
|
46
|
+
define_element_groups
|
47
|
+
end
|
48
|
+
|
49
|
+
def compile
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
element_groups.join('+').gsub(/\+*$/, '') << '\''
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def define_element_groups
|
59
|
+
self.class.element_groups_to_be_defined.each { |element_group| define_element_group(element_group) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def index_of_element_group(name)
|
63
|
+
defined_element_groups.index(name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_element_group(definition)
|
67
|
+
defined_element_groups << definition[:name]
|
68
|
+
|
69
|
+
if definition[:passthrough]
|
70
|
+
initiate_passthrough_element_group(definition)
|
71
|
+
define_element_group_passthrough_reader(definition[:name])
|
72
|
+
define_element_group_passthrough_writer(definition[:name])
|
73
|
+
else
|
74
|
+
initiate_element_group(definition)
|
75
|
+
define_element_group_reader(definition[:name])
|
76
|
+
define_element_group_writer(definition[:name])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def initiate_passthrough_element_group(definition)
|
81
|
+
element_group = Hbci::ElementGroup.new
|
82
|
+
element_group.define_element(definition)
|
83
|
+
element_groups[index_of_element_group(definition[:name])] = element_group
|
84
|
+
end
|
85
|
+
|
86
|
+
def initiate_element_group(definition)
|
87
|
+
element_group = definition[:type] ? definition[:type].new : Hbci::ElementGroup.new
|
88
|
+
element_group.instance_eval(&definition[:block]) if definition[:block]
|
89
|
+
element_groups[index_of_element_group(definition[:name])] = element_group
|
90
|
+
end
|
91
|
+
|
92
|
+
def define_element_group_passthrough_reader(name)
|
93
|
+
define_singleton_method(name.to_s) do
|
94
|
+
element_groups[index_of_element_group(name)][0]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def define_element_group_reader(name)
|
99
|
+
define_singleton_method(name.to_s) do
|
100
|
+
element_groups[index_of_element_group(name)]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def define_element_group_writer(name)
|
105
|
+
define_singleton_method("#{name}=") do |value|
|
106
|
+
element_groups[index_of_element_group(name)] = value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def define_element_group_passthrough_writer(name)
|
111
|
+
define_singleton_method("#{name}=") do |value|
|
112
|
+
element_groups[index_of_element_group(name)][0] = value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def after_build; end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
class SegmentFactory
|
5
|
+
def self.build(segment_data)
|
6
|
+
segment_class_name = "#{segment_data[0][0]}v#{segment_data[0][2]}"
|
7
|
+
segment_class = begin
|
8
|
+
Object.const_get("Hbci::Segments::#{segment_class_name}")
|
9
|
+
rescue StandardError
|
10
|
+
Hbci::Segments::Unknown
|
11
|
+
end
|
12
|
+
segment_class.fill(segment_data)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbci
|
4
|
+
module Segments
|
5
|
+
class HIKAZv4 < Segment
|
6
|
+
element_group :head, type: ElementGroups::SegmentHead
|
7
|
+
element :booked
|
8
|
+
element :notbooked
|
9
|
+
end
|
10
|
+
|
11
|
+
class HIKAZv5 < Segment
|
12
|
+
element_group :head, type: ElementGroups::SegmentHead
|
13
|
+
element :booked
|
14
|
+
element :notbooked
|
15
|
+
end
|
16
|
+
|
17
|
+
class HIKAZv6 < Segment
|
18
|
+
element_group :head, type: ElementGroups::SegmentHead
|
19
|
+
element :booked
|
20
|
+
element :notbooked
|
21
|
+
end
|
22
|
+
|
23
|
+
class HIKAZv7 < Segment
|
24
|
+
element_group :head, type: ElementGroups::SegmentHead
|
25
|
+
element :booked
|
26
|
+
element :notbooked
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|