dolzenko 0.0.23 → 0.0.24
Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
|
|
1
|
+
<div>
|
2
|
+
<p>
|
3
|
+
Recently I was browsing
|
4
|
+
<a href="http://docs.djangoproject.com/en/1.2/">
|
5
|
+
Django</a>
|
6
|
+
documentation deliberately looking for
|
7
|
+
some ideas to cross-pollinate to Rails. F() and Q() objects looked like
|
8
|
+
nice little hacks so I decided to implement them for Rails.
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<!--more-->
|
12
|
+
|
13
|
+
<h2>Q() Object</h2>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
Even though it's not nearly as powerful as his Star Trek namesake
|
17
|
+
it's still pretty interesting to look at (original docs available
|
18
|
+
<a href="http://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects">
|
19
|
+
here</a>).
|
20
|
+
It tackles the problem of complex SQL conditions building.
|
21
|
+
Usually most issues arise from the fact that simple hash based conditions
|
22
|
+
don't allow for negated or <code>OR</code>ed conditions. There are plenty of
|
23
|
+
other projects aimed to mitigate these restrictions among others (a while
|
24
|
+
ago I assembled the collection of these):
|
25
|
+
<a href="http://github.com/thoughtbot/squirrel">Squirrel</a>,
|
26
|
+
<a href="http://github.com/defunkt/ambition">Ambition</a>,
|
27
|
+
<a href="http://github.com/knowtheory/dm-sugar-glider">dm-sugar-glider</a>,
|
28
|
+
<a href="http://github.com/ezmobius/ez-where">ez_where</a>,
|
29
|
+
<a href="http://github.com/johnbender/rquery">RQuery</a>,
|
30
|
+
<a href="http://github.com/binarylogic/searchlogic">Searchlogic</a>,
|
31
|
+
<a href="http://github.com/jeremyevans/sequel">Sequel</a>,
|
32
|
+
probably most recent one is
|
33
|
+
<a href="http://github.com/ernie/meta_where">MetaWhere</a>.
|
34
|
+
</p>
|
35
|
+
|
36
|
+
<p style="clear: left;">
|
37
|
+
There are different tricks above libraries employ
|
38
|
+
<ol>
|
39
|
+
<li>
|
40
|
+
<code>instance_eval</code> and/or <code>method_missing</code> letting you
|
41
|
+
write
|
42
|
+
[ruby]
|
43
|
+
# Squirrel
|
44
|
+
Playlist.find(:all) do
|
45
|
+
any do
|
46
|
+
name == "Party Mix"
|
47
|
+
total_length > 3600
|
48
|
+
end
|
49
|
+
end
|
50
|
+
[/ruby]
|
51
|
+
or
|
52
|
+
[ruby]
|
53
|
+
# RQuery
|
54
|
+
User.where do |user|
|
55
|
+
(user.age > 20) | (user.age.in 16,18)
|
56
|
+
end
|
57
|
+
[/ruby]
|
58
|
+
</li>
|
59
|
+
<li>core objects extensions
|
60
|
+
(extending <code>Hash</code> and/or <code>Symbol</code> objects)
|
61
|
+
[ruby]
|
62
|
+
# Sequel
|
63
|
+
Artist.filter(:name.like('Y%') & ({:b=>1} | ~{:c=>3}))
|
64
|
+
# SELECT * FROM artists WHERE name LIKE 'Y%' AND (b = 1 OR c != 3)
|
65
|
+
[/ruby]
|
66
|
+
</li>
|
67
|
+
<li>
|
68
|
+
hooking the interpreter (accessing syntax tree of the condition
|
69
|
+
specifying block)
|
70
|
+
[ruby]
|
71
|
+
# Ambition
|
72
|
+
>> SQL::User.select { |m| m.name == 'jon' && m.age == 21 }.to_s
|
73
|
+
=> "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 21"
|
74
|
+
[/ruby]
|
75
|
+
</li>
|
76
|
+
</ol>
|
77
|
+
</p>
|
78
|
+
|
79
|
+
<p>
|
80
|
+
While there is nothing technically wrong with the approaches listed above,
|
81
|
+
it's interesting to see what can be done without resorting to any of these
|
82
|
+
tricks. The Q() object fits there very well. It let's you build ORed,
|
83
|
+
ANDed, and negated conditions without dropping to writing SQL (drawing
|
84
|
+
on simple and well known hash-based syntax). Below is the illustration
|
85
|
+
of Q() based syntax and the corresponding SQL fragments generated by it
|
86
|
+
|
87
|
+
[ruby]
|
88
|
+
~Q(:user_id => nil) # => user_id IS NOT NULL
|
89
|
+
|
90
|
+
Q(:user_id => nil) | Q(:id => nil) # => user_id IS NULL OR id IS NULL
|
91
|
+
|
92
|
+
~(Q(:user_id => nil) & Q(:id => nil)) # => user_id IS NOT NULL AND id IS NOT NULL
|
93
|
+
[/ruby]
|
94
|
+
|
95
|
+
Ruby 1.9 allows <code>!</code> operator overloading leading to more
|
96
|
+
natural syntax for negated conditions
|
97
|
+
|
98
|
+
[ruby]
|
99
|
+
!Q(:user_id => nil) # => user_id IS NOT NULL
|
100
|
+
[/ruby]
|
101
|
+
</p>
|
102
|
+
|
103
|
+
<h2>F() Object</h2>
|
104
|
+
|
105
|
+
<p>
|
106
|
+
<a href="http://docs.djangoproject.com/en/dev/topics/db/queries/#query-expressions">
|
107
|
+
Original docs</a>. It let's you reference your database columns and do
|
108
|
+
simple calculations on them from your conditions and update statements.
|
109
|
+
The concept is pretty simple and can be illustrated with a few
|
110
|
+
examples
|
111
|
+
[ruby]
|
112
|
+
# Simple column reference
|
113
|
+
User.where(:updated_at => F(:created_at))
|
114
|
+
# instead of writing it in SQL like: User.where("updated_at = created_at")
|
115
|
+
|
116
|
+
# Column reference with calculation
|
117
|
+
User.where(:updated_at => F(:created_at) + 1)
|
118
|
+
# instead of User.where("updated_at = created_at + 1")
|
119
|
+
|
120
|
+
# Calculation with references also work
|
121
|
+
User.where(:updated_at => F(:created_at) + F(:updated_at))
|
122
|
+
# instead of User.where("updated_at = created_at + updated_at")
|
123
|
+
|
124
|
+
# Use in update statement
|
125
|
+
User.update_all(:id_copy => F(:id))
|
126
|
+
# instead of User.update_all("id_copy = id")
|
127
|
+
[/ruby]
|
128
|
+
|
129
|
+
Nice side effect of using this is that you get atomic operations for free
|
130
|
+
[ruby]
|
131
|
+
user = User.find(1)
|
132
|
+
user.views = F(:views) + 1 #
|
133
|
+
user.save
|
134
|
+
[/ruby]
|
135
|
+
|
136
|
+
Whenever the <code>save</code> is called it's the current `views`
|
137
|
+
that will be incremented (not the value that that was loaded with
|
138
|
+
<code>User.find</code>).
|
139
|
+
</p>
|
140
|
+
|
141
|
+
<h2>Installation</h2>
|
142
|
+
|
143
|
+
<p>
|
144
|
+
You can get the code from my eponymous gem like this
|
145
|
+
[ruby]
|
146
|
+
> gem install dolzenko
|
147
|
+
> irb
|
148
|
+
|
149
|
+
[/ruby]
|
150
|
+
</p>
|
151
|
+
|
152
|
+
</div>
|
153
|
+
|
@@ -28,7 +28,7 @@
|
|
28
28
|
#
|
29
29
|
#
|
30
30
|
|
31
|
-
require "
|
31
|
+
require "dolzenko/alias_method_chain_once.rb"
|
32
32
|
|
33
33
|
class F
|
34
34
|
attr_accessor :attr_name
|
@@ -21,7 +21,7 @@
|
|
21
21
|
# User.where(!Q(:user_id => nil))
|
22
22
|
#
|
23
23
|
|
24
|
-
require "
|
24
|
+
require "dolzenko/alias_method_chain_once"
|
25
25
|
|
26
26
|
class Q
|
27
27
|
def |(other)
|
@@ -0,0 +1,121 @@
|
|
1
|
+
<div>
|
2
|
+
<h2>Age-Old Problem</h2>
|
3
|
+
|
4
|
+
<p>
|
5
|
+
Suppose you want to authenticate user from the login/password entered
|
6
|
+
on the web from
|
7
|
+
[ruby]
|
8
|
+
user = User.first(:conditions => "login = '#{ login }' AND password = '#{ password }'")
|
9
|
+
[/ruby]
|
10
|
+
Such authentication code as we know can be easily fooled with the
|
11
|
+
<a href="http://en.wikipedia.org/wiki/SQL_injection">SQL injection</a>.
|
12
|
+
|
13
|
+
Thankfully in Rails there are parameterized queries and the above can be
|
14
|
+
rewritten in at least 3 injection safe ways
|
15
|
+
[ruby]
|
16
|
+
User.find_by_login_and_password(login, password)
|
17
|
+
# or
|
18
|
+
User.first(:conditions => ["login = ? AND password = ?", login, password])
|
19
|
+
# or
|
20
|
+
User.first(:conditions => ["login = :login AND password = :password", { :login => login, :password => password }])
|
21
|
+
[/ruby]
|
22
|
+
</p>
|
23
|
+
|
24
|
+
<h2>Many Solutions (Haskell Included)</h2>
|
25
|
+
|
26
|
+
<p>
|
27
|
+
Parameterized queries cover most of the cases but what if you have large
|
28
|
+
query with multiple parameters?
|
29
|
+
You can quickly lose track of all the question marks and order
|
30
|
+
in which parameters appear. Most likely you will prefer the latter,
|
31
|
+
hash-based notation.
|
32
|
+
</p>
|
33
|
+
|
34
|
+
<!--more-->
|
35
|
+
|
36
|
+
<p>
|
37
|
+
Side note: This stuff is actually analyzed in depth by
|
38
|
+
<a href="http://onestepback.org/">
|
39
|
+
Jim Weirich</a>
|
40
|
+
in his
|
41
|
+
<a href="http://mwrc2009.confreaks.com/14-mar-2009-18-10-the-building-blocks-of-modularity-jim-weirich.html">
|
42
|
+
The Building Blocks of Modularity</a>
|
43
|
+
presentation where he talks about the connascence in software.
|
44
|
+
</p>
|
45
|
+
|
46
|
+
<p>
|
47
|
+
Back to our topic what we really want is just to write SQL queries
|
48
|
+
injecting properly quoted values where needed. In other words we would like
|
49
|
+
to be able to customize the way string interpolation works. In cool
|
50
|
+
languages like Haskell this concept is natively supported and called
|
51
|
+
<a href="http://www.haskell.org/haskellwiki/Quasiquotation">
|
52
|
+
Quasiquotation</a>.
|
53
|
+
While not really on par with such rigorous achievements it can
|
54
|
+
be simulated everywhere where <code>eval</code> is available.
|
55
|
+
Here is for example
|
56
|
+
<a href="http://google-caja.googlecode.com/svn/changes/mikesamuel/string-interpolation-29-Jan-2008/trunk/src/js/com/google/caja/interp/index.html">
|
57
|
+
the solution</a>
|
58
|
+
from the
|
59
|
+
<a href="http://en.wikipedia.org/wiki/Caja_(programming_language)">
|
60
|
+
Google Caja</a>
|
61
|
+
team.
|
62
|
+
</p>
|
63
|
+
|
64
|
+
<h2>Hack For Better Future</h2>
|
65
|
+
|
66
|
+
<p>
|
67
|
+
There is <code>eval</code> in Ruby and there is also one peculiar feature
|
68
|
+
about it. You can pass <code>binding</code> to it, and when the
|
69
|
+
block is created it's <code>binding</code> is captured and therefore can be
|
70
|
+
used from anywhere (providing access to the lexical scope captured by the
|
71
|
+
block). Let's illustrate that
|
72
|
+
[ruby]
|
73
|
+
ruby-head > def m
|
74
|
+
local = 42
|
75
|
+
proc {} # return empty block to get binding from it later
|
76
|
+
end
|
77
|
+
ruby-head > eval("local", m.binding) # binding gives access to the scope where block was created
|
78
|
+
=> 42
|
79
|
+
[/ruby]
|
80
|
+
</p>
|
81
|
+
|
82
|
+
<p>
|
83
|
+
Using that feature we can hide <code>eval</code> and implement our own
|
84
|
+
safe interpolation routines like I did in
|
85
|
+
<a href="http://github.com/dolzenko/dolzenko-gem/blob/master/lib/dolzenko/safe_interpolate.rb">
|
86
|
+
safe_interpolate.rb</a>.
|
87
|
+
Use it like
|
88
|
+
|
89
|
+
[ruby]
|
90
|
+
ruby-head > require 'dolzenko/safe_interpolate'
|
91
|
+
ruby-head > include SafeInterpolate
|
92
|
+
ruby-head > login = "' or 1=1"
|
93
|
+
ruby-head > sql_interpolate { 'login = #{ login }' }
|
94
|
+
=> "login = ''' or 1=1'" # single quote got turned into double and the whole expression is quoted itself
|
95
|
+
[/ruby]
|
96
|
+
|
97
|
+
HTML and URI escaping are also provided
|
98
|
+
[ruby]
|
99
|
+
ruby-head > content = "<script>alert(1)</script>"
|
100
|
+
ruby-head > html_interpolate { '<p>#{ content }</p>' }
|
101
|
+
=> "<p><script>alert(1)</script></p>" # injection prevented!
|
102
|
+
|
103
|
+
ruby-head > query = "&admin=1"
|
104
|
+
ruby-head > uri_interpolate { 'http://example.com?q=#{ query }' }
|
105
|
+
=> "http://example.com?q=%26admin%3D1"
|
106
|
+
[/ruby]
|
107
|
+
</p>
|
108
|
+
|
109
|
+
<h2>Further Reading</h2>
|
110
|
+
|
111
|
+
<p>
|
112
|
+
<a href="http://blog.moertel.com/articles/2006/10/18/a-type-based-solution-to-the-strings-problem">
|
113
|
+
A type-based solution to the "strings problem": a fitting end to XSS and SQL-injection holes?</a>
|
114
|
+
by Tom Moertel. This article got me interested in the subject. <br>
|
115
|
+
|
116
|
+
<a href="http://intoverflow.wordpress.com/2010/06/30/haskell-features-id-like-to-see-in-other-languages/">
|
117
|
+
Haskell features I'd like to see in other languages</a>
|
118
|
+
by Tim Carstens. Check the «Quasi-quoting» section for collection of Haskell
|
119
|
+
quoting related links.
|
120
|
+
</p>
|
121
|
+
</div>
|
@@ -2,8 +2,10 @@ require "active_support/all"
|
|
2
2
|
require "active_record"
|
3
3
|
require "cgi"
|
4
4
|
|
5
|
+
# http://dolzhenko.org/blog/2010/07/safe-string-interpolation-in-ruby/
|
5
6
|
module SafeInterpolate
|
6
7
|
def generic_interpolate(string_block, interpolator)
|
8
|
+
raise ArgumentError, "block returning string to interpolate must be provided" unless string_block
|
7
9
|
string_with_interpolations = string_block.call
|
8
10
|
string_with_interpolations.gsub(/\#\{([^}]*)\}/) do
|
9
11
|
result = eval($1, string_block.binding)
|
@@ -11,6 +13,11 @@ module SafeInterpolate
|
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
16
|
+
# Examples
|
17
|
+
#
|
18
|
+
# include SafeInterpolate
|
19
|
+
# ...
|
20
|
+
# sql_interpolate { 'name = #{ name }' } # => "name = 'Bob'"
|
14
21
|
def sql_interpolate(&string_block)
|
15
22
|
generic_interpolate(string_block, ActiveRecord::Base.connection.method(:quote))
|
16
23
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 24
|
9
|
+
version: 0.0.24
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Evgeniy Dolzhenko
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-07-
|
17
|
+
date: 2010-07-03 00:00:00 +04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- lib/dolzenko/core_ext/kernel/in.rb
|
64
64
|
- lib/dolzenko/core_ext/kernel/r.rb
|
65
65
|
- lib/dolzenko/core_ext/object/is_an.rb
|
66
|
+
- lib/dolzenko/django_f_object.html
|
66
67
|
- lib/dolzenko/django_f_object.rb
|
67
68
|
- lib/dolzenko/django_q_object.rb
|
68
69
|
- lib/dolzenko/error_print.rb
|
@@ -72,6 +73,7 @@ files:
|
|
72
73
|
- lib/dolzenko/io_interceptor.rb
|
73
74
|
- lib/dolzenko/light.rb
|
74
75
|
- lib/dolzenko/remote_download.rb
|
76
|
+
- lib/dolzenko/safe_interpolate.html
|
75
77
|
- lib/dolzenko/safe_interpolate.rb
|
76
78
|
- lib/dolzenko/shell_out.rb
|
77
79
|
- lib/dolzenko/try_block.rb
|