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 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: []