ronin-sql 0.2.4 → 1.0.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.
- data/.document +4 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/COPYING.txt +623 -288
- data/{History.txt → ChangeLog.md} +33 -35
- data/Gemfile +25 -0
- data/README.md +110 -0
- data/Rakefile +30 -20
- data/bin/ronin-sql +18 -5
- data/gemspec.yml +16 -0
- data/lib/ronin/formatting/extensions/sql.rb +4 -3
- data/lib/ronin/formatting/extensions/sql/string.rb +83 -10
- data/lib/ronin/formatting/sql.rb +4 -3
- data/lib/ronin/sql.rb +5 -12
- data/lib/ronin/{code/sql/create_index.rb → sql/binary_expr.rb} +25 -18
- data/lib/ronin/sql/clause.rb +72 -0
- data/lib/ronin/sql/clauses.rb +297 -0
- data/lib/ronin/sql/emittable.rb +84 -0
- data/lib/ronin/sql/emitter.rb +375 -0
- data/lib/ronin/sql/field.rb +106 -0
- data/lib/ronin/{code/sql/as.rb → sql/fields.rb} +36 -17
- data/lib/ronin/{code/sql/binary_expr.rb → sql/function.rb} +27 -27
- data/lib/ronin/sql/functions.rb +989 -0
- data/lib/ronin/sql/injection.rb +125 -157
- data/lib/ronin/{code/sql/default_values_clause.rb → sql/literal.rb} +13 -11
- data/lib/ronin/sql/literals.rb +72 -0
- data/lib/ronin/sql/operators.rb +332 -0
- data/lib/ronin/sql/sql.rb +86 -0
- data/lib/ronin/sql/statement.rb +70 -0
- data/lib/ronin/sql/statement_list.rb +110 -0
- data/lib/ronin/sql/statements.rb +115 -0
- data/lib/ronin/{code/sql/desc.rb → sql/unary_expr.rb} +11 -11
- data/lib/ronin/sql/version.rb +5 -4
- data/ronin-sql.gemspec +61 -0
- data/spec/formatting/sql/string_spec.rb +172 -0
- data/spec/spec_helper.rb +1 -4
- data/spec/sql/binary_expr.rb +5 -0
- data/spec/sql/binary_expr_examples.rb +25 -0
- data/spec/sql/clause_examples.rb +43 -0
- data/spec/sql/clause_spec.rb +31 -0
- data/spec/sql/clauses_spec.rb +43 -0
- data/spec/sql/emittable_spec.rb +41 -0
- data/spec/sql/emitter_spec.rb +472 -0
- data/spec/sql/field_spec.rb +103 -0
- data/spec/sql/fields_spec.rb +40 -0
- data/spec/sql/function_examples.rb +30 -0
- data/spec/sql/function_spec.rb +25 -0
- data/spec/sql/functions_spec.rb +110 -0
- data/spec/sql/injection_spec.rb +233 -0
- data/spec/sql/literal_spec.rb +5 -0
- data/spec/sql/literals_spec.rb +46 -0
- data/spec/sql/operators_spec.rb +44 -0
- data/spec/sql/sql_spec.rb +18 -0
- data/spec/sql/statement_examples.rb +39 -0
- data/spec/sql/statement_list_spec.rb +48 -0
- data/spec/sql/statement_sql.rb +38 -0
- data/spec/sql/statements_spec.rb +22 -0
- data/spec/sql/unary_expr.rb +5 -0
- data/spec/sql/unary_expr_examples.rb +20 -0
- metadata +116 -217
- data.tar.gz.sig +0 -0
- data/Manifest.txt +0 -108
- data/README.txt +0 -112
- data/lib/ronin/code/sql.rb +0 -22
- data/lib/ronin/code/sql/add_column_clause.rb +0 -42
- data/lib/ronin/code/sql/alter_table.rb +0 -52
- data/lib/ronin/code/sql/asc.rb +0 -36
- data/lib/ronin/code/sql/between.rb +0 -66
- data/lib/ronin/code/sql/clause.rb +0 -35
- data/lib/ronin/code/sql/code.rb +0 -35
- data/lib/ronin/code/sql/common_dialect.rb +0 -66
- data/lib/ronin/code/sql/create.rb +0 -74
- data/lib/ronin/code/sql/create_table.rb +0 -44
- data/lib/ronin/code/sql/create_view.rb +0 -41
- data/lib/ronin/code/sql/delete.rb +0 -52
- data/lib/ronin/code/sql/dialect.rb +0 -282
- data/lib/ronin/code/sql/drop.rb +0 -55
- data/lib/ronin/code/sql/drop_index.rb +0 -41
- data/lib/ronin/code/sql/drop_table.rb +0 -41
- data/lib/ronin/code/sql/drop_view.rb +0 -41
- data/lib/ronin/code/sql/emittable.rb +0 -100
- data/lib/ronin/code/sql/exceptions.rb +0 -24
- data/lib/ronin/code/sql/exceptions/unknown_clause.rb +0 -29
- data/lib/ronin/code/sql/exceptions/unknown_dialect.rb +0 -29
- data/lib/ronin/code/sql/exceptions/unknown_statement.rb +0 -29
- data/lib/ronin/code/sql/expr.rb +0 -102
- data/lib/ronin/code/sql/field.rb +0 -101
- data/lib/ronin/code/sql/fields_clause.rb +0 -46
- data/lib/ronin/code/sql/from_clause.rb +0 -42
- data/lib/ronin/code/sql/function.rb +0 -53
- data/lib/ronin/code/sql/group_by_clause.rb +0 -46
- data/lib/ronin/code/sql/having_clause.rb +0 -46
- data/lib/ronin/code/sql/in.rb +0 -47
- data/lib/ronin/code/sql/injected_statement.rb +0 -100
- data/lib/ronin/code/sql/injection.rb +0 -203
- data/lib/ronin/code/sql/insert.rb +0 -54
- data/lib/ronin/code/sql/intersect_clause.rb +0 -42
- data/lib/ronin/code/sql/join_clause.rb +0 -123
- data/lib/ronin/code/sql/like.rb +0 -73
- data/lib/ronin/code/sql/limit_clause.rb +0 -42
- data/lib/ronin/code/sql/modifier.rb +0 -48
- data/lib/ronin/code/sql/offset_clause.rb +0 -42
- data/lib/ronin/code/sql/on_clause.rb +0 -55
- data/lib/ronin/code/sql/order_by_clause.rb +0 -42
- data/lib/ronin/code/sql/program.rb +0 -225
- data/lib/ronin/code/sql/rename_to_clause.rb +0 -42
- data/lib/ronin/code/sql/replace.rb +0 -54
- data/lib/ronin/code/sql/select.rb +0 -103
- data/lib/ronin/code/sql/set_clause.rb +0 -42
- data/lib/ronin/code/sql/statement.rb +0 -180
- data/lib/ronin/code/sql/token.rb +0 -62
- data/lib/ronin/code/sql/unary_expr.rb +0 -47
- data/lib/ronin/code/sql/union_all_clause.rb +0 -42
- data/lib/ronin/code/sql/union_clause.rb +0 -42
- data/lib/ronin/code/sql/update.rb +0 -52
- data/lib/ronin/code/sql/values_clause.rb +0 -46
- data/lib/ronin/code/sql/where_clause.rb +0 -42
- data/lib/ronin/sql/error.rb +0 -26
- data/lib/ronin/sql/error/error.rb +0 -62
- data/lib/ronin/sql/error/extensions.rb +0 -22
- data/lib/ronin/sql/error/extensions/string.rb +0 -77
- data/lib/ronin/sql/error/message.rb +0 -62
- data/lib/ronin/sql/error/pattern.rb +0 -104
- data/lib/ronin/sql/error/patterns.rb +0 -99
- data/lib/ronin/sql/extensions.rb +0 -22
- data/lib/ronin/sql/extensions/uri.rb +0 -22
- data/lib/ronin/sql/extensions/uri/http.rb +0 -107
- data/spec/code/sql/common_dialect_spec.rb +0 -205
- data/spec/code/sql/create_examples.rb +0 -19
- data/spec/code/sql/create_index_spec.rb +0 -25
- data/spec/code/sql/create_table_spec.rb +0 -27
- data/spec/code/sql/create_view_spec.rb +0 -16
- data/spec/code/sql/delete_spec.rb +0 -14
- data/spec/code/sql/drop_examples.rb +0 -10
- data/spec/code/sql/drop_index_spec.rb +0 -16
- data/spec/code/sql/drop_table_spec.rb +0 -16
- data/spec/code/sql/drop_view_spec.rb +0 -16
- data/spec/code/sql/has_default_values_clause_examples.rb +0 -10
- data/spec/code/sql/has_fields_clause_examples.rb +0 -15
- data/spec/code/sql/has_from_clause_examples.rb +0 -13
- data/spec/code/sql/has_values_clause_examples.rb +0 -15
- data/spec/code/sql/has_where_clause_examples.rb +0 -15
- data/spec/code/sql/insert_spec.rb +0 -21
- data/spec/code/sql/replace_spec.rb +0 -21
- data/spec/code/sql/select_spec.rb +0 -105
- data/spec/code/sql/update_spec.rb +0 -26
- data/spec/helpers/code.rb +0 -14
- data/spec/sql/error_spec.rb +0 -24
- data/spec/sql/extensions/uri/http_spec.rb +0 -34
- data/spec/sql_spec.rb +0 -9
- data/tasks/spec.rb +0 -10
- data/tasks/yard.rb +0 -13
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'ronin/formatting/extensions/sql/string'
|
|
3
|
+
|
|
4
|
+
describe String do
|
|
5
|
+
it "should provide the #sql_escape method" do
|
|
6
|
+
subject.should respond_to(:sql_escape)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "should provide the #sql_encode method" do
|
|
10
|
+
subject.should respond_to(:sql_encode)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should provide the #sql_decode method" do
|
|
14
|
+
subject.should respond_to(:sql_decode)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "#sql_escape" do
|
|
18
|
+
subject { "hello" }
|
|
19
|
+
|
|
20
|
+
context "with :single" do
|
|
21
|
+
it "should wrap the String in single-quotes" do
|
|
22
|
+
subject.sql_escape(:single).should == "'hello'"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "when the String already contains single-quotes" do
|
|
26
|
+
subject { "O'Brian" }
|
|
27
|
+
|
|
28
|
+
it "should escape existing single-quotes" do
|
|
29
|
+
subject.sql_escape(:single).should == "'O''Brian'"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "with :double" do
|
|
35
|
+
it "should wrap the String in double-quotes" do
|
|
36
|
+
subject.sql_escape(:double).should == '"hello"'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context "when the String already contains double-quotes" do
|
|
40
|
+
subject { 'the "thing"' }
|
|
41
|
+
|
|
42
|
+
it "should escape existing double-quotes" do
|
|
43
|
+
subject.sql_escape(:double).should == '"the ""thing"""'
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context "with :tick" do
|
|
49
|
+
it "should wrap the String in tick-mark quotes" do
|
|
50
|
+
subject.sql_escape(:tick).should == "`hello`"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context "when the String already contains tick-marks" do
|
|
54
|
+
subject { "the `thing`" }
|
|
55
|
+
|
|
56
|
+
it "should escape existing tick-mark quotes" do
|
|
57
|
+
subject.sql_escape(:tick).should == '`the ``thing```'
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "with no arguments" do
|
|
63
|
+
it "should default quote to :single" do
|
|
64
|
+
subject.sql_escape.should == subject.sql_escape(:single)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "otherwise" do
|
|
69
|
+
it "should raise an ArgumentError" do
|
|
70
|
+
lambda { subject.sql_escape(:foo) }.should raise_error(ArgumentError)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "#sql_unescape" do
|
|
76
|
+
context "when the String is single-quoted" do
|
|
77
|
+
subject { "'hello'" }
|
|
78
|
+
|
|
79
|
+
it "should remove leading and tailing single-quotes" do
|
|
80
|
+
subject.sql_unescape.should == "hello"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context "when the String contains escaped single-quotes" do
|
|
84
|
+
subject { "'O''Brian'" }
|
|
85
|
+
|
|
86
|
+
it "should unescape the single-quotes" do
|
|
87
|
+
subject.sql_unescape.should == "O'Brian"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
context "when the String is double-quoted" do
|
|
93
|
+
subject { '"hello"' }
|
|
94
|
+
|
|
95
|
+
it "should remove leading and tailing double-quotes" do
|
|
96
|
+
subject.sql_unescape.should == 'hello'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context "when the String contains escaped double-quotes" do
|
|
100
|
+
subject { '"the ""thing"""' }
|
|
101
|
+
|
|
102
|
+
it "should unescape the double-quotes" do
|
|
103
|
+
subject.sql_unescape.should == 'the "thing"'
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context "when the String is tick-mark quoted" do
|
|
109
|
+
subject { '`hello`' }
|
|
110
|
+
|
|
111
|
+
it "should remove leading and tailing tick-mark quotes" do
|
|
112
|
+
subject.sql_unescape.should == 'hello'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "when the String contains escaped tick-mark quotes" do
|
|
116
|
+
subject { '`the ``thing```' }
|
|
117
|
+
|
|
118
|
+
it "should unescape the tick-mark quotes" do
|
|
119
|
+
subject.sql_unescape.should == 'the `thing`'
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "when the String is not quoted" do
|
|
125
|
+
subject { "hello" }
|
|
126
|
+
|
|
127
|
+
it "should raise an exception" do
|
|
128
|
+
lambda { subject.sql_unescape }.should raise_error(TypeError)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe "#sql_encode" do
|
|
134
|
+
subject { "/etc/passwd" }
|
|
135
|
+
|
|
136
|
+
let(:encoded_string) { '0x2f6574632f706173737764' }
|
|
137
|
+
|
|
138
|
+
it "should be able to be SQL-hex encoded" do
|
|
139
|
+
subject.sql_encode.should == encoded_string
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "should return an empty String if empty" do
|
|
143
|
+
''.sql_encode.should == ''
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe "#sql_decode" do
|
|
148
|
+
subject { '2f6574632f706173737764' }
|
|
149
|
+
|
|
150
|
+
let(:decoded_string) { '/etc/passwd' }
|
|
151
|
+
|
|
152
|
+
it "should be able to be SQL-hex decoded" do
|
|
153
|
+
subject.sql_decode.should == decoded_string
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context "with upper-case hexadecimal" do
|
|
157
|
+
subject { '2F6574632F706173737764' }
|
|
158
|
+
|
|
159
|
+
it "should be able to be SQL-hex decoded" do
|
|
160
|
+
subject.sql_decode.should == decoded_string
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context "when the String is a SQL escaped string" do
|
|
165
|
+
subject { "'Conan O''Brian'" }
|
|
166
|
+
|
|
167
|
+
it "should unescape the SQL String" do
|
|
168
|
+
subject.sql_decode.should == "Conan O'Brian"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'ronin/sql/binary_expr'
|
|
3
|
+
|
|
4
|
+
shared_examples_for "BinaryExpr" do |method,operator=method|
|
|
5
|
+
describe "##{method}" do
|
|
6
|
+
let(:operand) { 1 }
|
|
7
|
+
let(:expr) { subject.send(method,operand) }
|
|
8
|
+
|
|
9
|
+
it "should be a BinaryExpr" do
|
|
10
|
+
expr.should be_kind_of(SQL::BinaryExpr)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should set the left-hand side operand" do
|
|
14
|
+
expr.left.should == subject
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should have a '#{operator}' operator" do
|
|
18
|
+
expr.operator.should == operator
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should set the right-hand side operand" do
|
|
22
|
+
expr.right.should == operand
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
shared_examples_for "Clause" do |method,keyword,argument_or_block=nil|
|
|
4
|
+
describe "##{method}" do
|
|
5
|
+
case argument_or_block
|
|
6
|
+
when Proc
|
|
7
|
+
before { subject.send(method,&argument_or_block) }
|
|
8
|
+
when Array
|
|
9
|
+
let(:arguments) { argument_or_block }
|
|
10
|
+
|
|
11
|
+
before { subject.send(method,*arguments) }
|
|
12
|
+
when NilClass
|
|
13
|
+
before { subject.send(method) }
|
|
14
|
+
else
|
|
15
|
+
let(:argument) { argument_or_block }
|
|
16
|
+
|
|
17
|
+
before { subject.send(method,argument) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should add a #{keyword} clause" do
|
|
21
|
+
clause.keyword.should == keyword
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
case argument_or_block
|
|
25
|
+
when Proc
|
|
26
|
+
it "should accept a block" do
|
|
27
|
+
clause.argument.should_not be_nil
|
|
28
|
+
end
|
|
29
|
+
when NilClass
|
|
30
|
+
it "should not have an argument" do
|
|
31
|
+
clause.argument.should be_nil
|
|
32
|
+
end
|
|
33
|
+
when Array
|
|
34
|
+
it "should accept an argument" do
|
|
35
|
+
clause.argument.should == arguments
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
it "should accept an argument" do
|
|
39
|
+
clause.argument.should == argument
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'ronin/sql/clause'
|
|
3
|
+
|
|
4
|
+
describe SQL::Clause do
|
|
5
|
+
describe "#initialize" do
|
|
6
|
+
context "when given an argument" do
|
|
7
|
+
let(:argument) { 1 }
|
|
8
|
+
|
|
9
|
+
subject { described_class.new(:CLAUSE,argument) }
|
|
10
|
+
|
|
11
|
+
it "should set the argument" do
|
|
12
|
+
subject.argument.should == argument
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "when given a block" do
|
|
17
|
+
subject do
|
|
18
|
+
described_class.new(:CLAUSE) { 1 }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should use the return value as the argument" do
|
|
22
|
+
subject.argument.should == 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "that accepts an argument" do
|
|
26
|
+
it "should yield itself" do
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'sql/clause_examples'
|
|
3
|
+
require 'ronin/sql/clause'
|
|
4
|
+
require 'ronin/sql/clauses'
|
|
5
|
+
|
|
6
|
+
describe SQL::Clauses do
|
|
7
|
+
subject { Object.new.extend(described_class) }
|
|
8
|
+
|
|
9
|
+
let(:clause) { subject.clauses.last }
|
|
10
|
+
its(:clauses) { should be_empty }
|
|
11
|
+
|
|
12
|
+
describe "#clause" do
|
|
13
|
+
let(:keyword) { :EXEC }
|
|
14
|
+
|
|
15
|
+
before { subject.clause(keyword) }
|
|
16
|
+
|
|
17
|
+
it "should add an arbitrary clause" do
|
|
18
|
+
clause.keyword.should == keyword
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
include_examples "Clause", :from, :FROM, :table
|
|
23
|
+
include_examples "Clause", :into, :INTO, :table
|
|
24
|
+
include_examples "Clause", :where, :WHERE, proc { id == 1 }
|
|
25
|
+
include_examples "Clause", :join, :JOIN, :table
|
|
26
|
+
include_examples "Clause", :inner_join, [:INNER, :JOIN], :table
|
|
27
|
+
include_examples "Clause", :left_join, [:LEFT, :JOIN], :table
|
|
28
|
+
include_examples "Clause", :right_join, [:RIGHT, :JOIN], :table
|
|
29
|
+
include_examples "Clause", :full_join, [:FULL, :JOIN], :table
|
|
30
|
+
include_examples "Clause", :on, :ON, proc { id == 1 }
|
|
31
|
+
include_examples "Clause", :union, :UNION, proc { select(:*).from(:table) }
|
|
32
|
+
include_examples "Clause", :group_by, [:GROUP, :BY], [:column1, :column2]
|
|
33
|
+
include_examples "Clause", :having, :HAVING, proc { max(priv) > 100 }
|
|
34
|
+
include_examples "Clause", :limit, :LIMIT, 100
|
|
35
|
+
include_examples "Clause", :offset, :OFFSET, 20
|
|
36
|
+
include_examples "Clause", :top, :TOP, 50
|
|
37
|
+
include_examples "Clause", :into, :INTO, :table
|
|
38
|
+
include_examples "Clause", :values, :VALUES, [1,2,3,4]
|
|
39
|
+
include_examples "Clause", :default_values, [:DEFAULT, :VALUES]
|
|
40
|
+
include_examples "Clause", :set, :SET, {x: 1, y: 2}
|
|
41
|
+
include_examples "Clause", :indexed_by, [:INDEXED, :BY], :index_name
|
|
42
|
+
include_examples "Clause", :not_indexed, [:NOT, :INDEXED]
|
|
43
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'ronin/sql/emittable'
|
|
3
|
+
require 'ronin/sql/literal'
|
|
4
|
+
|
|
5
|
+
describe SQL::Emittable do
|
|
6
|
+
subject { SQL::Literal.new('hello') }
|
|
7
|
+
|
|
8
|
+
describe "#emitter" do
|
|
9
|
+
it "should return an SQL::Emitter" do
|
|
10
|
+
subject.emitter.should be_kind_of(SQL::Emitter)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should accept Emitter options" do
|
|
14
|
+
subject.emitter(:case => :lower).case.should == :lower
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "#to_sql" do
|
|
19
|
+
it "should emit the object" do
|
|
20
|
+
subject.to_sql.should == "'hello'"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context "when given options" do
|
|
24
|
+
it "should pass them to #emitter" do
|
|
25
|
+
subject.to_sql(quotes: :double).should == '"hello"'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#to_s" do
|
|
31
|
+
it "should call #to_sql with no arguments" do
|
|
32
|
+
subject.to_s.should == subject.to_sql
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "#inspect" do
|
|
37
|
+
it "should call #to_sql with no arguments" do
|
|
38
|
+
subject.inspect.should include(subject.to_sql)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'ronin/sql/literal'
|
|
3
|
+
require 'ronin/sql/unary_expr'
|
|
4
|
+
require 'ronin/sql/binary_expr'
|
|
5
|
+
require 'ronin/sql/field'
|
|
6
|
+
require 'ronin/sql/statement'
|
|
7
|
+
require 'ronin/sql/statement_list'
|
|
8
|
+
require 'ronin/sql/emitter'
|
|
9
|
+
|
|
10
|
+
describe SQL::Emitter do
|
|
11
|
+
describe "#initialize" do
|
|
12
|
+
context "without options" do
|
|
13
|
+
its(:space) { should == ' ' }
|
|
14
|
+
its(:quotes) { should == :single }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "#emit_keyword" do
|
|
19
|
+
context "when passed an Array of Symbols" do
|
|
20
|
+
let(:keywords) { [:DROP, :TABLE] }
|
|
21
|
+
|
|
22
|
+
it "should join the keywords" do
|
|
23
|
+
subject.emit_keyword(keywords).should == "DROP TABLE"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "when :space is set" do
|
|
27
|
+
subject { described_class.new(space: '/**/') }
|
|
28
|
+
|
|
29
|
+
it "should join the keywords" do
|
|
30
|
+
subject.emit_keyword(keywords).should == "DROP/**/TABLE"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context "when case is :upper" do
|
|
36
|
+
let(:keyword) { :select }
|
|
37
|
+
|
|
38
|
+
subject { described_class.new(case: :upper) }
|
|
39
|
+
|
|
40
|
+
it "should upcase the keyword" do
|
|
41
|
+
subject.emit_keyword(keyword).should == 'SELECT'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "when case is :lower" do
|
|
46
|
+
let(:keyword) { :SELECT }
|
|
47
|
+
|
|
48
|
+
subject { described_class.new(case: :lower) }
|
|
49
|
+
|
|
50
|
+
it "should upcase the keyword" do
|
|
51
|
+
subject.emit_keyword(keyword).should == 'select'
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
context "when case is :random" do
|
|
56
|
+
let(:keyword) { :select }
|
|
57
|
+
|
|
58
|
+
subject { described_class.new(case: :random) }
|
|
59
|
+
|
|
60
|
+
it "should contain at least one upper-case character" do
|
|
61
|
+
subject.emit_keyword(keyword).should =~ /[SELECT]/
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "when case is nil" do
|
|
66
|
+
subject { described_class.new(case: nil) }
|
|
67
|
+
|
|
68
|
+
let(:keyword) { 'Select' }
|
|
69
|
+
|
|
70
|
+
it "should emit the keyword as is" do
|
|
71
|
+
subject.emit_keyword(keyword).should == keyword
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "#emit_operator" do
|
|
77
|
+
context "when the operator is upper-case alphabetic text" do
|
|
78
|
+
subject { described_class.new(case: :lower) }
|
|
79
|
+
|
|
80
|
+
it "should emit a keyword" do
|
|
81
|
+
subject.emit_operator(:AS).should == 'as'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context "otherwise" do
|
|
86
|
+
it "should emit a String" do
|
|
87
|
+
subject.emit_operator(:"!=").should == '!='
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "#emit_null" do
|
|
93
|
+
it "should emit the NULL keyword" do
|
|
94
|
+
subject.emit_null.should == 'NULL'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe "#emit_false" do
|
|
99
|
+
it "should emit 1=0" do
|
|
100
|
+
subject.emit_false.should == '1=0'
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe "#emit_true" do
|
|
105
|
+
it "should emit 1=1" do
|
|
106
|
+
subject.emit_true.should == '1=1'
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "#emit_integer" do
|
|
111
|
+
it "should emit a String" do
|
|
112
|
+
subject.emit_integer(10).should == '10'
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe "#emit_decimal" do
|
|
117
|
+
it "should emit a String" do
|
|
118
|
+
subject.emit_decimal(2.5).should == '2.5'
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe "#emit_string" do
|
|
123
|
+
it "should emit a String" do
|
|
124
|
+
subject.emit_string("O'Brian").should == "'O''Brian'"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context "when :quotes is :double" do
|
|
128
|
+
subject { described_class.new(quotes: :double) }
|
|
129
|
+
|
|
130
|
+
it "should double quote Strings" do
|
|
131
|
+
subject.emit_string("O'Brian").should == "\"O'Brian\""
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe "#emit_field" do
|
|
137
|
+
it "should emit a String" do
|
|
138
|
+
subject.emit_field(:id).should == 'id'
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
describe "#emit_list" do
|
|
143
|
+
it "should emit a ',' separated list" do
|
|
144
|
+
subject.emit_list([1,2,3,'foo']).should == "(1,2,3,'foo')"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe "#emit_assignments" do
|
|
149
|
+
let(:values) { {x: 1, y: 2} }
|
|
150
|
+
|
|
151
|
+
it "should emit a list of column names and values" do
|
|
152
|
+
subject.emit_assignments(values).should == 'x=1,y=2'
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe "#emit_expression" do
|
|
157
|
+
context "when the expression is a BinaryExpr" do
|
|
158
|
+
context "when the operator is alphabetic" do
|
|
159
|
+
subject { described_class.new(case: :upper) }
|
|
160
|
+
|
|
161
|
+
let(:expr) { SQL::BinaryExpr.new(:id,:is,1) }
|
|
162
|
+
|
|
163
|
+
it "should emit the operands and operator as a keyword with spaces" do
|
|
164
|
+
subject.emit_expression(expr).should == 'id IS 1'
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context "when the operator is symbolic" do
|
|
169
|
+
let(:expr) { SQL::BinaryExpr.new(:id,:"=",1) }
|
|
170
|
+
|
|
171
|
+
it "should emit the operands and operator without spaces" do
|
|
172
|
+
subject.emit_expression(expr).should == 'id=1'
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "when the left-hand operand is a Statement" do
|
|
177
|
+
let(:expr) do
|
|
178
|
+
SQL::BinaryExpr.new(SQL::Statement.new(:SELECT,1),:"=",1)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it "should wrap the left-hand operand in parenthesis" do
|
|
182
|
+
subject.emit_expression(expr).should == '(SELECT 1)=1'
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
context "when the right-hand operand is a Statement" do
|
|
187
|
+
let(:expr) do
|
|
188
|
+
SQL::BinaryExpr.new(1,:"=",SQL::Statement.new(:SELECT,1))
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "should wrap the left-hand operand in parenthesis" do
|
|
192
|
+
subject.emit_expression(expr).should == '1=(SELECT 1)'
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
context "when the expression is a UnaryExpr" do
|
|
198
|
+
context "when the operator is upper-case alpha" do
|
|
199
|
+
let(:expr) { SQL::UnaryExpr.new(:NOT,:admin) }
|
|
200
|
+
|
|
201
|
+
it "should emit the operand and operator with spaces" do
|
|
202
|
+
subject.emit_expression(expr).should == 'NOT admin'
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
context "when the operator is symbolic" do
|
|
207
|
+
let(:expr) { SQL::UnaryExpr.new(:"-",1) }
|
|
208
|
+
|
|
209
|
+
it "should emit the operand and operator without spaces" do
|
|
210
|
+
subject.emit_expression(expr).should == '-1'
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
context "when the operand is a Statement" do
|
|
215
|
+
let(:expr) do
|
|
216
|
+
SQL::UnaryExpr.new(:NOT,SQL::Statement.new(:SELECT,1))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "should wrap the operand in parenthesis" do
|
|
220
|
+
subject.emit_expression(expr).should == 'NOT (SELECT 1)'
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
describe "#emit_function" do
|
|
227
|
+
let(:func) { SQL::Function.new(:NOW) }
|
|
228
|
+
|
|
229
|
+
it "should emit the function name as a keyword" do
|
|
230
|
+
subject.emit_function(func).should == 'NOW()'
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
context "with arguments" do
|
|
234
|
+
let(:func) { SQL::Function.new(:MAX,1,2) }
|
|
235
|
+
|
|
236
|
+
it "should emit the function arguments" do
|
|
237
|
+
subject.emit_function(func).should == 'MAX(1,2)'
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe "#emit" do
|
|
243
|
+
context "when passed nil" do
|
|
244
|
+
it "should emit the NULL keyword" do
|
|
245
|
+
subject.emit(nil).should == 'NULL'
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
context "when passed true" do
|
|
250
|
+
it "should emit true" do
|
|
251
|
+
subject.emit(true).should == '1=1'
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
context "when passed false" do
|
|
256
|
+
it "should emit false" do
|
|
257
|
+
subject.emit(false).should == '1=0'
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
context "when passed an Integer" do
|
|
262
|
+
it "should emit an integer" do
|
|
263
|
+
subject.emit(10).should == '10'
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
context "when passed a Float" do
|
|
268
|
+
it "should emit a decimal" do
|
|
269
|
+
subject.emit(2.5).should == '2.5'
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
context "when passed a String" do
|
|
274
|
+
it "should emit a string" do
|
|
275
|
+
subject.emit("O'Brian").should == "'O''Brian'"
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
context "when passed a Literal" do
|
|
280
|
+
let(:literal) { SQL::Literal.new(42) }
|
|
281
|
+
|
|
282
|
+
it "should emit the value" do
|
|
283
|
+
subject.emit(literal).should == '42'
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
context "when passed a Field" do
|
|
288
|
+
let(:table) { SQL::Field.new(:users) }
|
|
289
|
+
let(:column) { SQL::Field.new(:id,table) }
|
|
290
|
+
|
|
291
|
+
it "should emit a field" do
|
|
292
|
+
subject.emit(column).should == 'users.id'
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
context "when passed a Symbol" do
|
|
297
|
+
it "should emit a field" do
|
|
298
|
+
subject.emit(:id).should == 'id'
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
context "when passed an Array" do
|
|
303
|
+
it "should emit a list" do
|
|
304
|
+
subject.emit([1,2,3,'foo']).should == "(1,2,3,'foo')"
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
context "when passed a Hash" do
|
|
309
|
+
it "should emit a list of assignments" do
|
|
310
|
+
subject.emit(x: 1, y: 2).should == 'x=1,y=2'
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
context "when passed a BinaryExpr" do
|
|
315
|
+
let(:expr) { SQL::BinaryExpr.new(:id,:"=",1) }
|
|
316
|
+
|
|
317
|
+
it "should emit an expression" do
|
|
318
|
+
subject.emit(expr).should == 'id=1'
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
context "when passed a UnaryExpr" do
|
|
323
|
+
let(:expr) { SQL::UnaryExpr.new(:NOT,:admin) }
|
|
324
|
+
|
|
325
|
+
it "should emit an expression" do
|
|
326
|
+
subject.emit(expr).should == 'NOT admin'
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
context "when passed a Statment" do
|
|
331
|
+
let(:stmt) { SQL::Statement.new(:SELECT,1) }
|
|
332
|
+
|
|
333
|
+
it "should emit a statement" do
|
|
334
|
+
subject.emit(stmt).should == 'SELECT 1'
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
context "when the object responds to #to_sql" do
|
|
339
|
+
let(:object) { double(:sql_object) }
|
|
340
|
+
let(:sql) { "EXEC sp_configure 'xp_cmdshell', 0;" }
|
|
341
|
+
|
|
342
|
+
it "should call #to_sql" do
|
|
343
|
+
object.stub(:to_sql).and_return(sql)
|
|
344
|
+
|
|
345
|
+
subject.emit(object).should == sql
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
context "otherwise" do
|
|
350
|
+
let(:object) { Object.new }
|
|
351
|
+
|
|
352
|
+
it "should raise an ArgumentError" do
|
|
353
|
+
lambda {
|
|
354
|
+
subject.emit(object)
|
|
355
|
+
}.should raise_error(ArgumentError)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
describe "#emit_clause" do
|
|
361
|
+
let(:clause) { SQL::Clause.new(:"NOT INDEXED") }
|
|
362
|
+
|
|
363
|
+
it "should emit the clause keyword" do
|
|
364
|
+
subject.emit_clause(clause).should == "NOT INDEXED"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
context "with an argument" do
|
|
368
|
+
let(:argument) { 100 }
|
|
369
|
+
let(:clause) { SQL::Clause.new(:LIMIT,argument) }
|
|
370
|
+
|
|
371
|
+
it "should also emit the clause argument" do
|
|
372
|
+
subject.emit_clause(clause).should == "LIMIT #{argument}"
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
context "with custom :space" do
|
|
377
|
+
subject { described_class.new(space: '/**/') }
|
|
378
|
+
|
|
379
|
+
let(:clause) { SQL::Clause.new(:LIMIT,100) }
|
|
380
|
+
|
|
381
|
+
it "should emit the custom white-space deliminater" do
|
|
382
|
+
subject.emit_clause(clause).should == 'LIMIT/**/100'
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
describe "#emit_clauses" do
|
|
388
|
+
let(:clauses) do
|
|
389
|
+
[
|
|
390
|
+
SQL::Clause.new(:LIMIT, 100),
|
|
391
|
+
SQL::Clause.new(:OFFSET, 10)
|
|
392
|
+
]
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
it "should emit multiple clauses" do
|
|
396
|
+
subject.emit_clauses(clauses).should == 'LIMIT 100 OFFSET 10'
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
context "with custom :space" do
|
|
400
|
+
subject { described_class.new(space: '/**/') }
|
|
401
|
+
|
|
402
|
+
it "should emit the custom white-space deliminater" do
|
|
403
|
+
subject.emit_clauses(clauses).should == 'LIMIT/**/100/**/OFFSET/**/10'
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
describe "#emit_statement" do
|
|
409
|
+
subject { described_class.new(case: :lower) }
|
|
410
|
+
|
|
411
|
+
context "without an argument" do
|
|
412
|
+
let(:stmt) { SQL::Statement.new(:SELECT) }
|
|
413
|
+
|
|
414
|
+
it "should emit the statment keyword" do
|
|
415
|
+
subject.emit_statement(stmt).should == 'select'
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
context "with an argument" do
|
|
420
|
+
let(:stmt) { SQL::Statement.new(:SELECT,1) }
|
|
421
|
+
|
|
422
|
+
it "should emit the statment argument" do
|
|
423
|
+
subject.emit_statement(stmt).should == 'select 1'
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
context "with custom :space" do
|
|
427
|
+
subject { described_class.new(case: :lower, space: '/**/') }
|
|
428
|
+
|
|
429
|
+
it "should emit the custom white-space deliminater" do
|
|
430
|
+
subject.emit_statement(stmt).should == 'select/**/1'
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
context "with clauses" do
|
|
436
|
+
let(:stmt) { SQL::Statement.new(:SELECT,1).offset(1).limit(100) }
|
|
437
|
+
|
|
438
|
+
it "should emit the statment argument" do
|
|
439
|
+
subject.emit_statement(stmt).should == 'select 1 offset 1 limit 100'
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
context "with custom :space" do
|
|
443
|
+
subject { described_class.new(case: :lower, space: '/**/') }
|
|
444
|
+
|
|
445
|
+
it "should emit the custom white-space deliminater" do
|
|
446
|
+
subject.emit_statement(stmt).should == 'select/**/1/**/offset/**/1/**/limit/**/100'
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
describe "#emit_statement_list" do
|
|
453
|
+
let(:stmts) do
|
|
454
|
+
sql = SQL::StatementList.new
|
|
455
|
+
sql << SQL::Statement.new(:SELECT, 1)
|
|
456
|
+
sql << SQL::Statement.new([:DROP, :TABLE], :users)
|
|
457
|
+
sql
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
it "should emit multiple statements separated by '; '" do
|
|
461
|
+
subject.emit_statement_list(stmts).should == 'SELECT 1; DROP TABLE users'
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
context "with custom :space" do
|
|
465
|
+
subject { described_class.new(space: '/**/') }
|
|
466
|
+
|
|
467
|
+
it "should emit the custom white-space deliminater" do
|
|
468
|
+
subject.emit_statement_list(stmts).should == 'SELECT/**/1;/**/DROP/**/TABLE/**/users'
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|