ronin-fuzzer 0.1.0.beta1
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/.document +5 -0
- data/.github/workflows/ruby.yml +31 -0
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/COPYING.txt +165 -0
- data/ChangeLog.md +9 -0
- data/Gemfile +39 -0
- data/README.md +91 -0
- data/Rakefile +34 -0
- data/bin/ronin-fuzzer +37 -0
- data/gemspec.yml +30 -0
- data/lib/ronin/fuzzer/cli/command.rb +37 -0
- data/lib/ronin/fuzzer/cli/commands/fuzz.rb +326 -0
- data/lib/ronin/fuzzer/cli.rb +39 -0
- data/lib/ronin/fuzzer/root.rb +29 -0
- data/lib/ronin/fuzzer/version.rb +27 -0
- data/lib/ronin/fuzzing/core_ext/string.rb +203 -0
- data/lib/ronin/fuzzing/core_ext.rb +22 -0
- data/lib/ronin/fuzzing/fuzzer.rb +112 -0
- data/lib/ronin/fuzzing/mutator.rb +163 -0
- data/lib/ronin/fuzzing/repeater.rb +81 -0
- data/lib/ronin/fuzzing/template.rb +133 -0
- data/lib/ronin/fuzzing.rb +365 -0
- data/man/ronin-fuzzer-fuzz.1 +95 -0
- data/man/ronin-fuzzer-fuzz.1.md +73 -0
- data/ronin-fuzzer.gemspec +61 -0
- data/spec/core_ext/string_spec.rb +87 -0
- data/spec/fuzzer_spec.rb +109 -0
- data/spec/fuzzing_spec.rb +24 -0
- data/spec/mutator_spec.rb +112 -0
- data/spec/repeater_spec.rb +57 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/template_spec.rb +54 -0
- metadata +149 -0
data/spec/fuzzer_spec.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/fuzzing/fuzzer'
|
3
|
+
|
4
|
+
describe Ronin::Fuzzing::Fuzzer do
|
5
|
+
let(:string) { 'GET /one/two/three' }
|
6
|
+
|
7
|
+
describe "#initialize" do
|
8
|
+
subject { described_class }
|
9
|
+
|
10
|
+
context "patterns" do
|
11
|
+
let(:substitutions) { ['bar'] }
|
12
|
+
|
13
|
+
it "should accept Regexps" do
|
14
|
+
fuzzer = subject.new(/foo/ => substitutions)
|
15
|
+
|
16
|
+
expect(fuzzer.rules).to have_key(/foo/)
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when given Strings" do
|
20
|
+
subject { described_class.new('foo' => substitutions) }
|
21
|
+
|
22
|
+
it "should convert to Regexp" do
|
23
|
+
expect(subject.rules).to have_key(/foo/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when given Symbols" do
|
28
|
+
subject { described_class.new(:word => substitutions) }
|
29
|
+
|
30
|
+
it "should lookup the Ronin::Support::Text::Patterns constant" do
|
31
|
+
expect(subject.rules).to have_key(Ronin::Support::Text::Patterns::WORD)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "otherwise" do
|
36
|
+
it "should raise a TypeError" do
|
37
|
+
expect {
|
38
|
+
subject.new(Object.new => substitutions)
|
39
|
+
}.to raise_error(TypeError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "substitutions" do
|
45
|
+
let(:pattern) { /foo/ }
|
46
|
+
|
47
|
+
it "should accept Enumerable values" do
|
48
|
+
fuzzer = subject.new(pattern => ['bar'])
|
49
|
+
|
50
|
+
expect(fuzzer.rules[pattern]).to eq(['bar'])
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when given Symbols" do
|
54
|
+
subject { described_class.new(pattern => :bad_strings) }
|
55
|
+
|
56
|
+
it "should map to an Enumerator for a Fuzzing method" do
|
57
|
+
expect(subject.rules[pattern]).to be_kind_of(Enumerable)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "otherwise" do
|
62
|
+
it "should raise a TypeError" do
|
63
|
+
expect {
|
64
|
+
subject.new(pattern => Object.new)
|
65
|
+
}.to raise_error(TypeError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#each" do
|
72
|
+
let(:string) { "foo bar" }
|
73
|
+
|
74
|
+
subject { described_class.new(/o/ => ['O', '0'], /a/ => ['A', '@']) }
|
75
|
+
|
76
|
+
it "should apply each fuzzing rule individually" do
|
77
|
+
expect(subject.each(string).to_a).to match_array([
|
78
|
+
"fOo bar",
|
79
|
+
"f0o bar",
|
80
|
+
"foO bar",
|
81
|
+
"fo0 bar",
|
82
|
+
"foo bAr",
|
83
|
+
"foo b@r"
|
84
|
+
])
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when mutations contain Integers" do
|
88
|
+
subject { described_class.new(/o/ => [48]) }
|
89
|
+
|
90
|
+
it "should convert them to characters" do
|
91
|
+
expect(subject.each(string).to_a).to match_array([
|
92
|
+
"f0o bar",
|
93
|
+
"fo0 bar"
|
94
|
+
])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when mutations contain Procs" do
|
99
|
+
subject { described_class.new(/o/ => [lambda { |str| str.upcase }]) }
|
100
|
+
|
101
|
+
it "should call them with the matched String" do
|
102
|
+
expect(subject.each(string).to_a).to match_array([
|
103
|
+
"fOo bar",
|
104
|
+
"foO bar"
|
105
|
+
])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/fuzzing'
|
3
|
+
|
4
|
+
describe Ronin::Fuzzing do
|
5
|
+
describe "[]" do
|
6
|
+
let(:method) { :bad_strings }
|
7
|
+
|
8
|
+
it "should return Enumerators for fuzzing methods" do
|
9
|
+
expect(subject[method]).to be_kind_of(Enumerable)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should raise NoMethodError for unknown methods" do
|
13
|
+
expect {
|
14
|
+
subject[:foo]
|
15
|
+
}.to raise_error(NoMethodError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should not allow accessing inherited methods" do
|
19
|
+
expect {
|
20
|
+
subject[:instance_eval]
|
21
|
+
}.to raise_error(NoMethodError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/fuzzing/mutator'
|
3
|
+
|
4
|
+
describe Ronin::Fuzzing::Mutator do
|
5
|
+
let(:string) { 'GET /one/two/three' }
|
6
|
+
|
7
|
+
describe "#initialize" do
|
8
|
+
subject { described_class }
|
9
|
+
|
10
|
+
context "patterns" do
|
11
|
+
let(:substitutions) { ['bar'] }
|
12
|
+
|
13
|
+
it "should accept Regexps" do
|
14
|
+
fuzzer = subject.new(/foo/ => substitutions)
|
15
|
+
|
16
|
+
expect(fuzzer.rules).to have_key(/foo/)
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when given Strings" do
|
20
|
+
subject { described_class.new('foo' => substitutions) }
|
21
|
+
|
22
|
+
it "should convert to Regexp" do
|
23
|
+
expect(subject.rules).to have_key(/foo/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when given Symbols" do
|
28
|
+
subject { described_class.new(:word => substitutions) }
|
29
|
+
|
30
|
+
it "should lookup the Ronin::Support::Text::Patterns constant" do
|
31
|
+
expect(subject.rules).to have_key(Ronin::Support::Text::Patterns::WORD)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "otherwise" do
|
36
|
+
it "should raise a TypeError" do
|
37
|
+
expect {
|
38
|
+
subject.new(Object.new => substitutions)
|
39
|
+
}.to raise_error(TypeError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "mutations" do
|
45
|
+
let(:pattern) { /foo/ }
|
46
|
+
|
47
|
+
it "should accept Enumerable values" do
|
48
|
+
fuzzer = subject.new(pattern => ['bar'])
|
49
|
+
|
50
|
+
expect(fuzzer.rules[pattern]).to eq(['bar'])
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when given Symbols" do
|
54
|
+
subject { described_class.new(pattern => :bad_strings) }
|
55
|
+
|
56
|
+
it "should map to an Enumerator for a Fuzzing method" do
|
57
|
+
expect(subject.rules[pattern]).to be_kind_of(Enumerable)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "otherwise" do
|
62
|
+
it "should raise a TypeError" do
|
63
|
+
expect {
|
64
|
+
subject.new(pattern => Object.new)
|
65
|
+
}.to raise_error(TypeError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#each" do
|
72
|
+
let(:string) { "foo bar" }
|
73
|
+
|
74
|
+
subject { described_class.new(/o/ => ['0'], /a/ => ['@']) }
|
75
|
+
|
76
|
+
it "should apply every combination of mutation rules" do
|
77
|
+
expect(subject.each(string).to_a).to match_array([
|
78
|
+
"f0o bar",
|
79
|
+
"fo0 bar",
|
80
|
+
"f00 bar",
|
81
|
+
"foo b@r",
|
82
|
+
"f0o b@r",
|
83
|
+
"fo0 b@r",
|
84
|
+
"f00 b@r"
|
85
|
+
])
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when mutations contain Integers" do
|
89
|
+
subject { described_class.new(/o/ => [48]) }
|
90
|
+
|
91
|
+
it "should convert them to characters" do
|
92
|
+
expect(subject.each(string).to_a).to match_array([
|
93
|
+
"f0o bar",
|
94
|
+
"fo0 bar",
|
95
|
+
"f00 bar"
|
96
|
+
])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when mutations contain Procs" do
|
101
|
+
subject { described_class.new(/o/ => [lambda { |str| str.upcase }]) }
|
102
|
+
|
103
|
+
it "should call them with the matched String" do
|
104
|
+
expect(subject.each(string).to_a).to match_array([
|
105
|
+
"fOo bar",
|
106
|
+
"foO bar",
|
107
|
+
"fOO bar"
|
108
|
+
])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/fuzzing/repeater'
|
3
|
+
|
4
|
+
describe Ronin::Fuzzing::Repeater do
|
5
|
+
describe "#initialize" do
|
6
|
+
subject { described_class }
|
7
|
+
|
8
|
+
context "when lengths is an Integer" do
|
9
|
+
it "should coerce lengths to an Enumerable" do
|
10
|
+
repeator = subject.new(10)
|
11
|
+
|
12
|
+
expect(repeator.lengths).to be_kind_of(Enumerable)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when lengths is not Enumerable or an Integer" do
|
17
|
+
it "should raise a TypeError" do
|
18
|
+
expect {
|
19
|
+
subject.new(Object.new)
|
20
|
+
}.to raise_error(TypeError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#each" do
|
26
|
+
let(:repeatable) { 'A' }
|
27
|
+
|
28
|
+
context "when lengths was an Integer" do
|
29
|
+
let(:length) { 10 }
|
30
|
+
|
31
|
+
subject { described_class.new(length) }
|
32
|
+
|
33
|
+
it "should yield one repeated value" do
|
34
|
+
values = subject.each(repeatable).to_a
|
35
|
+
|
36
|
+
expect(values).to eq([repeatable * length])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when lengths was Enumerable" do
|
41
|
+
let(:lengths) { (1..4) }
|
42
|
+
|
43
|
+
subject { described_class.new(lengths) }
|
44
|
+
|
45
|
+
it "should yield repeated values for each length" do
|
46
|
+
values = subject.each(repeatable).to_a
|
47
|
+
|
48
|
+
expect(values).to eq([
|
49
|
+
repeatable * 1,
|
50
|
+
repeatable * 2,
|
51
|
+
repeatable * 3,
|
52
|
+
repeatable * 4
|
53
|
+
])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/fuzzing/template'
|
3
|
+
|
4
|
+
describe Ronin::Fuzzing::Template do
|
5
|
+
subject { described_class }
|
6
|
+
|
7
|
+
it "should generate Strings from CharSets" do
|
8
|
+
strings = subject.new([:lowercase_hexadecimal, :numeric]).to_a
|
9
|
+
|
10
|
+
expect(strings.grep(/^[0-9a-f][0-9]$/)).to eq(strings)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should generate Strings from lengths of CharSets" do
|
14
|
+
strings = subject.new([[:numeric, 2]]).to_a
|
15
|
+
|
16
|
+
expect(strings.grep(/^[0-9]{2}$/)).to eq(strings)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should generate Strings from varying lengths of CharSets" do
|
20
|
+
strings = subject.new([[:numeric, 1..2]]).to_a
|
21
|
+
|
22
|
+
expect(strings.grep(/^[0-9]{1,2}$/)).to eq(strings)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should generate Strings from custom CharSets" do
|
26
|
+
strings = subject.new([[%w[a b c], 2]]).to_a
|
27
|
+
|
28
|
+
expect(strings.grep(/^[abc]{2}$/)).to eq(strings)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should generate Strings containing known Strings" do
|
32
|
+
strings = subject.new(['foo', [%w[a b c], 2]]).to_a
|
33
|
+
|
34
|
+
expect(strings.grep(/^foo[abc]{2}$/)).to eq(strings)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise a TypeError for non String, Symbol, Enumerable CharSets" do
|
38
|
+
expect {
|
39
|
+
subject.new([[Object.new, 2]]).to_a
|
40
|
+
}.to raise_error(TypeError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should raise an ArgumentError for unknown CharSets" do
|
44
|
+
expect {
|
45
|
+
subject.new([[:foo_bar, 2]]).to_a
|
46
|
+
}.to raise_error(ArgumentError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should raise a TypeError for non Integer,Array,Range lengths" do
|
50
|
+
expect {
|
51
|
+
subject.new([[:numeric, 'foo']]).to_a
|
52
|
+
}.to raise_error(TypeError)
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ronin-fuzzer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.beta1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Postmodern
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: combinatorics
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ronin-support
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0.beta1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.0.beta1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ronin-core
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.0.beta1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.0.beta1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
description: 'A Ruby library for generating, mutating, and fuzzing data.
|
70
|
+
|
71
|
+
'
|
72
|
+
email: postmodern.mod3@gmail.com
|
73
|
+
executables:
|
74
|
+
- ronin-fuzzer
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files:
|
77
|
+
- COPYING.txt
|
78
|
+
- ChangeLog.md
|
79
|
+
- README.md
|
80
|
+
files:
|
81
|
+
- ".document"
|
82
|
+
- ".github/workflows/ruby.yml"
|
83
|
+
- ".gitignore"
|
84
|
+
- ".rspec"
|
85
|
+
- ".ruby-version"
|
86
|
+
- ".yardopts"
|
87
|
+
- COPYING.txt
|
88
|
+
- ChangeLog.md
|
89
|
+
- Gemfile
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- bin/ronin-fuzzer
|
93
|
+
- gemspec.yml
|
94
|
+
- lib/ronin/fuzzer/cli.rb
|
95
|
+
- lib/ronin/fuzzer/cli/command.rb
|
96
|
+
- lib/ronin/fuzzer/cli/commands/fuzz.rb
|
97
|
+
- lib/ronin/fuzzer/root.rb
|
98
|
+
- lib/ronin/fuzzer/version.rb
|
99
|
+
- lib/ronin/fuzzing.rb
|
100
|
+
- lib/ronin/fuzzing/core_ext.rb
|
101
|
+
- lib/ronin/fuzzing/core_ext/string.rb
|
102
|
+
- lib/ronin/fuzzing/fuzzer.rb
|
103
|
+
- lib/ronin/fuzzing/mutator.rb
|
104
|
+
- lib/ronin/fuzzing/repeater.rb
|
105
|
+
- lib/ronin/fuzzing/template.rb
|
106
|
+
- man/ronin-fuzzer-fuzz.1
|
107
|
+
- man/ronin-fuzzer-fuzz.1.md
|
108
|
+
- ronin-fuzzer.gemspec
|
109
|
+
- spec/core_ext/string_spec.rb
|
110
|
+
- spec/fuzzer_spec.rb
|
111
|
+
- spec/fuzzing_spec.rb
|
112
|
+
- spec/mutator_spec.rb
|
113
|
+
- spec/repeater_spec.rb
|
114
|
+
- spec/spec_helper.rb
|
115
|
+
- spec/template_spec.rb
|
116
|
+
homepage: https://ronin-rb.dev/
|
117
|
+
licenses:
|
118
|
+
- LGPL-3.0
|
119
|
+
metadata:
|
120
|
+
documentation_uri: https://rubydoc.info/gems/ronin-fuzzer
|
121
|
+
source_code_uri: https://github.com/ronin-rb/ronin-fuzzer
|
122
|
+
bug_tracker_uri: https://github.com/ronin-rb/ronin-fuzzer/issues
|
123
|
+
changelog_uri: https://github.com/ronin-rb/ronin-fuzzer/blob/master/ChangeLog.md
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 3.0.0
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubygems_version: 3.3.26
|
140
|
+
signing_key:
|
141
|
+
specification_version: 4
|
142
|
+
summary: generate, mutate, and fuzz data
|
143
|
+
test_files:
|
144
|
+
- spec/core_ext/string_spec.rb
|
145
|
+
- spec/fuzzer_spec.rb
|
146
|
+
- spec/fuzzing_spec.rb
|
147
|
+
- spec/mutator_spec.rb
|
148
|
+
- spec/repeater_spec.rb
|
149
|
+
- spec/template_spec.rb
|