dotted_hash 0.8 → 0.9
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.
- 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: []
|