dolzenko 0.0.23 → 0.0.24
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.
@@ -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
|