dotted_hash 0.8 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +42 -2
- data/dotted_hash.gemspec +1 -1
- data/lib/dotted_hash/version.rb +1 -1
- data/lib/dotted_hash.rb +42 -8
- data/test/dotted_hash_spec.rb +72 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13450c3abde2a7100248385cefbafd3a41260c76
|
4
|
+
data.tar.gz: 21ccd452b4ca350acecbf2209dcdc8723ccac4ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 163655b8c528a56443d47af75813d1e15aca9617154707bcff3ce6262f95ec78d32a6372cc8c5c7a8c9d5a54031360e6ae2126f046c1ab461490d411bb7fcfba
|
7
|
+
data.tar.gz: 5889f79bf47c38db461b32ef119662ba7695cef4c0d016073d472dfd1fd1dfbc150c4e9d85ced5abf41d084ebd7e54f6a9811efad578b1bf8996f2585de8dc7d
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Recursive OpenStruct-like or Hash-like object. Uses ActiveModel.
|
4
4
|
|
5
|
-
Based on *Tire::Result::Item* with addition of writing attributes.
|
5
|
+
Based on *Tire::Result::Item* with addition of writing attributes and security limits.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -70,8 +70,48 @@ recursively (also creates sub-DottedHashes if they don't exist)
|
|
70
70
|
> d.class
|
71
71
|
=> Cannon
|
72
72
|
|
73
|
+
## Set security for structure
|
74
|
+
|
75
|
+
- *MAX_DEPTH* for maximal depth of whole tree (keys_depth+1), counted from 0.
|
76
|
+
While it is not completely bulletproof, because depth can be set to wrong number if careless, it is enough in most of the time.
|
77
|
+
|
78
|
+
- *MAX_ATTRS* to specify maximum count of attributes. Use integer for limit to all levels or use hash like this
|
79
|
+
|
80
|
+
MAX_ATTRS = {1 => 20, 2 => 5, default: 10}
|
81
|
+
|
82
|
+
for limits to be applied to certain levels. *Default* is optional, if not present - limit is not set.
|
83
|
+
|
84
|
+
- *MAX_SIZE* to limit structure size. Size is computed from JSON representation of *DottedHash*.
|
85
|
+
Note that some objects may have much bigger representation in memory than in JSON.
|
86
|
+
|
87
|
+
|
88
|
+
## JSON
|
89
|
+
|
90
|
+
Because security uses JSON representation of DottedHash, use library like *Oj* or *Yajl* when under heavy load.
|
91
|
+
|
92
|
+
DottedHash uses *ActiveSupport* which uses *MultiJson*...
|
93
|
+
|
94
|
+
Which backend is used?
|
95
|
+
|
96
|
+
> MultiJson.adapter
|
97
|
+
|
98
|
+
or
|
99
|
+
|
100
|
+
> ActiveSupport::JSON.backend
|
101
|
+
|
102
|
+
Set backend
|
103
|
+
|
104
|
+
> MultiJson.adapter = :oj
|
105
|
+
|
106
|
+
or
|
107
|
+
|
108
|
+
> ActiveSupport::JSON.backend = :yajl
|
109
|
+
|
110
|
+
|
111
|
+
|
73
112
|
See source and tests for some more details.
|
74
113
|
|
114
|
+
|
75
115
|
## Contributing
|
76
116
|
|
77
117
|
1. Fork it
|
@@ -86,4 +126,4 @@ Original project: [https://github.com/karmi/tire](https://github.com/karmi/tire)
|
|
86
126
|
|
87
127
|
Author of modificated version: *Ivan Stana*
|
88
128
|
|
89
|
-
License: *MIT*
|
129
|
+
License: *MIT*
|
data/dotted_hash.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = DottedHash::VERSION
|
9
9
|
spec.authors = ["Ivan Stana"]
|
10
10
|
spec.email = ["stiipa@centrum.sk"]
|
11
|
-
spec.description = %q{Recursive OpenStruct-like or Hash-like object. Based on Tire::Result::Item with addition of writing attributes.}
|
11
|
+
spec.description = %q{Recursive OpenStruct-like or Hash-like object. Based on Tire::Result::Item with addition of writing attributes and security limits.}
|
12
12
|
spec.summary = %q{Recursive OpenStruct-like object.}
|
13
13
|
spec.homepage = "http://github.com/istana/dotted_hash"
|
14
14
|
spec.license = "MIT"
|
data/lib/dotted_hash/version.rb
CHANGED
data/lib/dotted_hash.rb
CHANGED
@@ -9,12 +9,32 @@ class DottedHash
|
|
9
9
|
extend ActiveModel::Naming
|
10
10
|
include ActiveModel::Conversion
|
11
11
|
#include ActiveModel::Model
|
12
|
+
|
13
|
+
## Basic security
|
14
|
+
|
15
|
+
# Maximum depth of whole tree, not keys (keys depth+1)
|
16
|
+
# Counted from 0
|
17
|
+
# Not fully bulletproof, depth may be set to wrong number if careless
|
18
|
+
MAX_DEPTH = 10
|
19
|
+
|
20
|
+
# Maximum count of attributes
|
21
|
+
# Use hash like this to specify each level
|
22
|
+
# MAX_ATTRS = {1 => 20, 2 => 5, default: 10}
|
23
|
+
MAX_ATTRS = 10
|
24
|
+
|
25
|
+
# Maximum size of document, counted from JSON result of document
|
26
|
+
# It is not bulletproof, but if using simple structures, it is enough
|
27
|
+
# Other structures may have much bigger representation in memory than in JSON
|
28
|
+
MAX_SIZE = 16384
|
29
|
+
|
12
30
|
# Create new instance, recursively converting all Hashes to Item
|
13
31
|
# and leaving everything else alone.
|
14
32
|
#
|
15
|
-
|
16
|
-
def initialize(args={})
|
33
|
+
def initialize(args={}, level=0)
|
17
34
|
raise ArgumentError, "Please pass a Hash-like object" unless args.respond_to?(:each_pair)
|
35
|
+
raise RuntimeError, "Maximal depth reached" if level > MAX_DEPTH
|
36
|
+
|
37
|
+
@depth = level
|
18
38
|
@attributes = {}
|
19
39
|
args.each_pair do |key, value|
|
20
40
|
assign_value(key, value)
|
@@ -22,12 +42,26 @@ class DottedHash
|
|
22
42
|
end
|
23
43
|
|
24
44
|
def assign_value(key, value)
|
45
|
+
max_attrs = if MAX_ATTRS.is_a?(Fixnum)
|
46
|
+
MAX_ATTRS
|
47
|
+
elsif MAX_ATTRS.respond_to?(:[])
|
48
|
+
MAX_ATTRS[@depth] || MAX_ATTRS[:default]
|
49
|
+
end
|
50
|
+
|
51
|
+
if max_attrs
|
52
|
+
attrs = @attributes.size + (@attributes.include?(key.to_sym) ? 0 : 1)
|
53
|
+
raise RuntimeError, "Maximum number of attributes reached" if attrs > max_attrs
|
54
|
+
end
|
55
|
+
|
56
|
+
raise RuntimeError, "Maximal size of document reached" if self.to_json.size+value.to_json.size > MAX_SIZE
|
57
|
+
|
25
58
|
if value.is_a?(Array)
|
26
|
-
@attributes[key.to_sym] = value.map { |item| @attributes[key.to_sym] = item.is_a?(Hash) ? DottedHash.new(item.to_hash) : item }
|
59
|
+
@attributes[key.to_sym] = value.map { |item| @attributes[key.to_sym] = item.is_a?(Hash) ? DottedHash.new(item.to_hash, @depth+1) : item }
|
27
60
|
else
|
28
|
-
@attributes[key.to_sym] = value.is_a?(Hash) ? DottedHash.new(value.to_hash) : value
|
61
|
+
@attributes[key.to_sym] = value.is_a?(Hash) ? DottedHash.new(value.to_hash, @depth+1) : value
|
29
62
|
end
|
30
63
|
end
|
64
|
+
|
31
65
|
private :assign_value
|
32
66
|
|
33
67
|
# Delegate method to a key in underlying hash, if present, otherwise return +nil+.
|
@@ -57,13 +91,13 @@ class DottedHash
|
|
57
91
|
if keys.size > 1
|
58
92
|
key = keys.shift.to_sym
|
59
93
|
|
60
|
-
if
|
61
|
-
|
94
|
+
if !@attributes[key]
|
95
|
+
assign_value(key, DottedHash.new({}, @depth+1))
|
62
96
|
end
|
63
|
-
sub =
|
97
|
+
sub = @attributes[key]
|
64
98
|
sub.send(:recursive_assign, keys.join('.'), value)
|
65
99
|
elsif keys.size == 1
|
66
|
-
|
100
|
+
assign_value(keys.shift, value)
|
67
101
|
end
|
68
102
|
end
|
69
103
|
|
data/test/dotted_hash_spec.rb
CHANGED
@@ -17,7 +17,7 @@ describe DottedHash do
|
|
17
17
|
expect{ DottedHash.new(id: 1) }.not_to raise_error
|
18
18
|
DottedHash.new(id: 1).should_be_instance_of :N
|
19
19
|
expect(DottedHash.new(id: 1)).to be_instance_of(DottedHash)
|
20
|
-
|
20
|
+
|
21
21
|
expect do
|
22
22
|
class AlmostHash < Hash; end
|
23
23
|
DottedHash.new(AlmostHash.new(id: 1))
|
@@ -191,6 +191,77 @@ describe DottedHash do
|
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
194
|
+
context "Security" do
|
195
|
+
describe "MAX_DEPTH" do
|
196
|
+
it "has good depth" do
|
197
|
+
hash = {}
|
198
|
+
ptr = hash
|
199
|
+
for i in (0..DottedHash::MAX_DEPTH-1) do
|
200
|
+
ptr.merge!({i.to_s => {}})
|
201
|
+
ptr = ptr[i.to_s]
|
202
|
+
end
|
203
|
+
|
204
|
+
expect{ DottedHash.new(hash) }.not_to raise_error
|
205
|
+
end
|
206
|
+
|
207
|
+
it "is too deep" do
|
208
|
+
hash = {}
|
209
|
+
ptr = hash
|
210
|
+
for i in (0..DottedHash::MAX_DEPTH) do
|
211
|
+
ptr.merge!({i.to_s => {}})
|
212
|
+
ptr = ptr[i.to_s]
|
213
|
+
end
|
214
|
+
|
215
|
+
expect{ DottedHash.new(hash)}.to raise_error(RuntimeError, /depth/)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe "MAX_ATTRS" do
|
220
|
+
context "Integer value" do
|
221
|
+
it "is in limit" do
|
222
|
+
stub_const("DottedHash::MAX_ATTRS", 3)
|
223
|
+
expect{ DottedHash.new(a: 1, b: 2, c: 3) }.not_to raise_error
|
224
|
+
end
|
225
|
+
|
226
|
+
it "is not in limit" do
|
227
|
+
stub_const("DottedHash::MAX_ATTRS", 3)
|
228
|
+
expect{ DottedHash.new(a: 1, b: 2, c: 3, d: 4) }.to raise_error(RuntimeError, /attribu/)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context "Depths specified" do
|
233
|
+
it "is set with key 'default'" do
|
234
|
+
stub_const("DottedHash::MAX_ATTRS", {1 => 3, default: 2})
|
235
|
+
|
236
|
+
expect{ DottedHash.new(a: 1, b: 2) }.not_to raise_error
|
237
|
+
expect{ DottedHash.new(a: 1, b: 2, c: 3) }.to raise_error(RuntimeError, /attribu/)
|
238
|
+
|
239
|
+
expect{ DottedHash.new(a: 1, b: {one: 1, two: 2, three: 3}) }.not_to raise_error
|
240
|
+
expect{ DottedHash.new(a: 1, b: {one: 1, two: 2, three: 3, four: 4}) }.to raise_error(RuntimeError, /attribu/)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "is not set with key 'default'" do
|
244
|
+
stub_const("DottedHash::MAX_ATTRS", {0 => 3})
|
245
|
+
|
246
|
+
expect{ DottedHash.new(a: 1, b: 2, c: 3) }.not_to raise_error
|
247
|
+
expect{ DottedHash.new(a: 1, b: 2, c: 3, d: 4) }.to raise_error(RuntimeError, /attribu/)
|
248
|
+
|
249
|
+
expect{ DottedHash.new(a: 1, b: 2, c: {one: 1, two: 2, three: 3, four: 4, five: 5}) }.not_to raise_error
|
250
|
+
expect{ DottedHash.new(a: 1, b: 2, c: 3, d: {one: 1, two: 2, three: 3, four: 4, five: 5}) }.to raise_error(RuntimeError, /attribu/)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
it "tests MAX_SIZE" do
|
256
|
+
stub_const("DottedHash::MAX_SIZE", 10)
|
257
|
+
|
258
|
+
expect{ DottedHash.new(a: "short") }.not_to raise_error
|
259
|
+
expect{ DottedHash.new(a: "najnevypocitavatelnejsi") }.to raise_error(/size/)
|
260
|
+
expect{ DottedHash.new(a: "short", b: "short") }.to raise_error(/size/)
|
261
|
+
expect{ DottedHash.new(a: {b: "x", c: {d: "xxx"}}) }.to raise_error(/size/)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
194
265
|
end
|
195
266
|
end
|
196
267
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dotted_hash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.9'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Stana
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-05-
|
11
|
+
date: 2013-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '2.13'
|
97
97
|
description: Recursive OpenStruct-like or Hash-like object. Based on Tire::Result::Item
|
98
|
-
with addition of writing attributes.
|
98
|
+
with addition of writing attributes and security limits.
|
99
99
|
email:
|
100
100
|
- stiipa@centrum.sk
|
101
101
|
executables: []
|