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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 442525c1060bd5bbfcbd29eab42eacd400bcd21a
4
- data.tar.gz: 64330ffcb55a2db31f48dad429ecc329bd086a74
3
+ metadata.gz: 13450c3abde2a7100248385cefbafd3a41260c76
4
+ data.tar.gz: 21ccd452b4ca350acecbf2209dcdc8723ccac4ac
5
5
  SHA512:
6
- metadata.gz: eb8fdb956dcbfc3b5687e50250554a037ea9228803e4068bb20d174d3704e511eab959aa986f00c4b4fc6937c54bcfa77a777fc96601541e4ba8ec1b1e1dc8a7
7
- data.tar.gz: 37d877e03ad53c404f82b5d5f7fe0fdb432ad8d7d4da174bb37a86a70a05c567d3a34d7c0d73dc6018906051f4745c2e8c37aa2dad6abf89bd4d61e8a7461b76
6
+ metadata.gz: 163655b8c528a56443d47af75813d1e15aca9617154707bcff3ce6262f95ec78d32a6372cc8c5c7a8c9d5a54031360e6ae2126f046c1ab461490d411bb7fcfba
7
+ data.tar.gz: 5889f79bf47c38db461b32ef119662ba7695cef4c0d016073d472dfd1fd1dfbc150c4e9d85ced5abf41d084ebd7e54f6a9811efad578b1bf8996f2585de8dc7d
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  Gemfile.lock
2
+ pkg/
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"
@@ -1,3 +1,3 @@
1
1
  class DottedHash
2
- VERSION = "0.8"
2
+ VERSION = "0.9"
3
3
  end
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
- # TODO: track depth
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 !self.send(key)
61
- self.send("#{key}=", DottedHash.new)
94
+ if !@attributes[key]
95
+ assign_value(key, DottedHash.new({}, @depth+1))
62
96
  end
63
- sub = self.send(key)
97
+ sub = @attributes[key]
64
98
  sub.send(:recursive_assign, keys.join('.'), value)
65
99
  elsif keys.size == 1
66
- self.send("#{keys.shift}=", value)
100
+ assign_value(keys.shift, value)
67
101
  end
68
102
  end
69
103
 
@@ -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.8'
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-01 00:00:00.000000000 Z
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: []