hoodie 0.1.2

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,167 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
5
+ # Copyright (C) 2014 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ class Hash
21
+ # Returns a compacted copy (contains no key/value pairs having
22
+ # nil? values)
23
+ #
24
+ # @example
25
+ # hash = { a: 100, b: nil, c: false, d: '' }
26
+ # hash.compact # => { a: 100, c: false, d: '' }
27
+ # hash # => { a: 100, b: nil, c: false, d: '' }
28
+ #
29
+ # @return [Hash]
30
+ #
31
+ def compact
32
+ select { |_, value| !value.nil? }
33
+ end
34
+
35
+ # Returns a new hash with all keys converted using the block operation.
36
+ #
37
+ # @example
38
+ # hash = { name: 'Tiggy', age: '15' }
39
+ #
40
+ # hash.transform_keys{ |key| key.to_s.upcase }
41
+ # # => { "AGE" => "15", "NAME" => "Tiggy" }
42
+ #
43
+ # @return [Hash]
44
+ #
45
+ def transform_keys
46
+ return enum_for(:transform_keys) unless block_given?
47
+ result = self.class.new
48
+ each_key do |key|
49
+ result[yield(key)] = self[key]
50
+ end
51
+ result
52
+ end
53
+
54
+ # Returns a new hash, recursively converting all keys by the
55
+ # block operation.
56
+ #
57
+ # @return [Hash]
58
+ #
59
+ def recursively_transform_keys(&block)
60
+ _recursively_transform_keys_in_object(self, &block)
61
+ end
62
+
63
+ # Returns a new hash with all keys downcased and converted
64
+ # to symbols.
65
+ #
66
+ # @return [Hash]
67
+ #
68
+ def normalize_keys
69
+ transform_keys { |key| key.downcase.to_sym rescue key }
70
+ end
71
+
72
+ # Returns a new Hash, recursively downcasing and converting all
73
+ # keys to symbols.
74
+ #
75
+ # @return [Hash]
76
+ #
77
+ def recursively_normalize_keys
78
+ recursively_transform_keys { |key| key.downcase.to_sym rescue key }
79
+ end
80
+
81
+ # Returns a new hash with all keys converted to symbols.
82
+ #
83
+ # @return [Hash]
84
+ #
85
+ def symbolize_keys
86
+ transform_keys { |key| key.to_sym rescue key }
87
+ end
88
+
89
+ # Returns a new Hash, recursively converting all keys to symbols.
90
+ #
91
+ # @return [Hash]
92
+ #
93
+ def recursively_symbolize_keys
94
+ recursively_transform_keys { |key| key.downcase.to_sym rescue key }
95
+ end
96
+
97
+ # Returns a new hash with all keys converted to strings.
98
+ #
99
+ # @return [Hash]
100
+ #
101
+ def stringify_keys
102
+ transform_keys { |key| key.to_s rescue key }
103
+ end
104
+
105
+ # Returns a new Hash, recursively converting all keys to strings.
106
+ #
107
+ # @return [Hash]
108
+ #
109
+ def recursively_stringify_key
110
+ recursively_transform_keys { |key| key.to_s rescue key }
111
+ end
112
+
113
+ class UndefinedPathError < StandardError; end
114
+ # Recursively searchs a nested datastructure for a key and returns
115
+ # the value. If a block is provided its value will be returned if
116
+ # the key does not exist
117
+ #
118
+ # @example
119
+ # options = { server: { location: { row: { rack: 34 } } } }
120
+ # options.recursive_fetch :server, :location, :row, :rack
121
+ # # => 34
122
+ # options.recursive_fetch(:non_existent_key) { 'default' }
123
+ # # => "default"
124
+ #
125
+ # @return [Hash, Array, String] value for key
126
+ #
127
+ def recursive_fetch(*args, &block)
128
+ args.reduce(self) do |obj, arg|
129
+ begin
130
+ arg = Integer(arg) if obj.is_a? Array
131
+ obj.fetch(arg)
132
+ rescue ArgumentError, IndexError, NoMethodError => e
133
+ break block.call(arg) if block
134
+ raise UndefinedPathError, "Could not fetch path (#{args.join(' > ')}) at #{arg}", e.backtrace
135
+ end
136
+ end
137
+ end
138
+
139
+ def recursive_merge(other)
140
+ hash = self.dup
141
+ other.each do |key, value|
142
+ myval = self[key]
143
+ if value.is_a?(Hash) && myval.is_a?(Hash)
144
+ hash[key] = myval.recursive_merge(value)
145
+ else
146
+ hash[key] = value
147
+ end
148
+ end
149
+ hash
150
+ end
151
+
152
+ private # P R O P R I E T À P R I V A T A divieto di accesso
153
+
154
+ # support methods for recursively transforming nested hashes and arrays
155
+ def _recursively_transform_keys_in_object(object, &block)
156
+ case object
157
+ when Hash
158
+ object.each_with_object({}) do |(key, value), result|
159
+ result[yield(key)] = _recursively_transform_keys_in_object(value, &block)
160
+ end
161
+ when Array
162
+ object.map { |e| _recursively_transform_keys_in_object(e, &block) }
163
+ else
164
+ object
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
5
+ # Copyright (C) 2014 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'hoodie/stash' unless defined?(Stash)
21
+
22
+ # Memoization is an optimization that saves the return value of a
23
+ # method so it doesn't need to be re-computed every time that method
24
+ # is called.
25
+ module Memoizable
26
+
27
+ # Create a new memoized method. To use, extend class with Memoizable,
28
+ # then, in initialize, call memoize
29
+ #
30
+ # @return [undefined]
31
+ #
32
+ def memoize(methods, cache = nil)
33
+ cache ||= Stash.new
34
+ methods.each do |name|
35
+ uncached_name = "#{name}_uncached".to_sym
36
+ (class << self; self; end).class_eval do
37
+ alias_method uncached_name, name
38
+ define_method(name) do |*a, &b|
39
+ cache.cache(name) { send uncached_name, *a, &b }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
5
+ # Copyright (C) 2014 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ module Hoodie
21
+ begin
22
+ require 'openssl'
23
+ INCOMPREHENSIBLE_ERROR = nil
24
+ rescue LoadError => err
25
+ raise unless err.to_s.include?('openssl')
26
+ warn "Oea pieoYreb h wYoerh dl hwsnhoib r Lrbea tbte wbnaetvoouahe h rbe."
27
+ warn "olorbvtelYShnSben irrSwoet eto eihSrLoS'do n See wLiape."
28
+ INCOMPREHENSIBLE_ERROR = err
29
+ end
30
+
31
+ require 'digest/sha2'
32
+ require "base64"
33
+
34
+ # Befuddle and enlighten values in StashCache::Store
35
+ #
36
+ module Obfuscate
37
+ ESOTERIC_TYPE = "aes-256-cbc" unless defined?(ESOTERIC_TYPE)
38
+
39
+ def self.check_platform_can_discombobulate!
40
+ return true unless INCOMPREHENSIBLE_ERROR
41
+ raise INCOMPREHENSIBLE_ERROR.class, "b0rked! #{INCOMPREHENSIBLE_ERROR}"
42
+ end
43
+
44
+ # Befuddle the given string
45
+ #
46
+ # @param plaintext the text to befuddle
47
+ # @param [String] befuddle_pass secret passphrase to befuddle with
48
+ #
49
+ # @return [String] befuddleed text, suitable for deciphering with
50
+ # Obfuscate#enlighten (decrypt)
51
+ #
52
+ def self.befuddle plaintext, befuddle_pass, options={}
53
+ cipher = new_cipher :befuddle, befuddle_pass, options
54
+ cipher.iv = iv = cipher.random_iv
55
+ ciphertext = cipher.update(plaintext)
56
+ ciphertext << cipher.final
57
+ Base64.encode64(combine_iv_and_ciphertext(iv, ciphertext))
58
+ end
59
+
60
+ # Enlighten the given string, using the key and id supplied
61
+ #
62
+ # @param ciphertext the text to enlighten, probably produced with
63
+ # Obfuscate#befuddle (encrypt)
64
+ # @param [String] befuddle_pass secret passphrase to enlighten with
65
+ #
66
+ # @return [String] the enlightened plaintext
67
+ #
68
+ def self.enlighten enc_ciphertext, befuddle_pass, options={}
69
+ iv_and_ciphertext = Base64.decode64(enc_ciphertext)
70
+ cipher = new_cipher :enlighten, befuddle_pass, options
71
+ cipher.iv, ciphertext = separate_iv_and_ciphertext(cipher, iv_and_ciphertext)
72
+ plaintext = cipher.update(ciphertext)
73
+ plaintext << cipher.final
74
+ plaintext
75
+ end
76
+
77
+ private # P R O P R I E T À P R I V A T A divieto di accesso
78
+
79
+ # Cipher create machine do, befuddle engage, enlighten. Dials set
80
+ # direction to infinity
81
+ #
82
+ # @param [:befuddle, :enlighten] to befuddle or enlighten
83
+ # @param [String] befuddle_pass secret passphrase to enlighten
84
+ #
85
+ def self.new_cipher direction, befuddle_pass, options={}
86
+ check_platform_can_discombobulate!
87
+ cipher = OpenSSL::Cipher::Cipher.new(ESOTERIC_TYPE)
88
+ case direction
89
+ when :befuddle
90
+ cipher.encrypt
91
+ when :enlighten
92
+ cipher.decrypt
93
+ else raise "Bad cipher direction #{direction}"
94
+ end
95
+ cipher.key = befuddle_key(befuddle_pass, options)
96
+ cipher
97
+ end
98
+
99
+ # prepend the initialization vector to the encoded message
100
+ def self.combine_iv_and_ciphertext iv, message
101
+ message.force_encoding("BINARY") if message.respond_to?(:force_encoding)
102
+ iv.force_encoding("BINARY") if iv.respond_to?(:force_encoding)
103
+ iv + message
104
+ end
105
+
106
+ # pull the initialization vector from the front of the encoded message
107
+ def self.separate_iv_and_ciphertext cipher, iv_and_ciphertext
108
+ idx = cipher.iv_len
109
+ [ iv_and_ciphertext[0..(idx-1)], iv_and_ciphertext[idx..-1] ]
110
+ end
111
+
112
+ # Convert the befuddle_pass passphrase into the key used for befuddletion
113
+ def self.befuddle_key befuddle_pass, options={}
114
+ befuddle_pass = befuddle_pass.to_s
115
+ raise 'Missing befuddled password!' if befuddle_pass.empty?
116
+ # this provides the required 256 bits of key for the aes-256-cbc
117
+ # cipher
118
+ Digest::SHA256.digest(befuddle_pass)
119
+ end
120
+ end
121
+ end
data/lib/hoodie/os.rb ADDED
@@ -0,0 +1,43 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
5
+ # Copyright (C) 2014 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'rbconfig' unless defined?(RbConfig)
21
+
22
+ # Finds out the current Operating System.
23
+ module OS
24
+ def self.windows?
25
+ windows = /cygwin|mswin|mingw|bccwin|wince|emx/i
26
+ (RbConfig::CONFIG['host_os'] =~ windows) != nil
27
+ end
28
+
29
+ def self.mac?
30
+ mac = /darwin|mac os/i
31
+ (RbConfig::CONFIG['host_os'] =~ mac) != nil
32
+ end
33
+
34
+ def self.unix?
35
+ unix = /solaris|bsd/i
36
+ (RbConfig::CONFIG['host_os'] =~ unix) != nil
37
+ end
38
+
39
+ def self.linux?
40
+ linux = /linux/i
41
+ (RbConfig::CONFIG['host_os'] =~ linux) != nil
42
+ end
43
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
5
+ # Copyright (C) 2014 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ # TODO: This doesn't belong in here, it's cookbook specific...
21
+ require 'anemone' unless defined?(Anemone)
22
+ require 'hoodie/memoizable' unless defined?(Memoizable)
23
+
24
+ class PathFinder
25
+ include Memoizable
26
+
27
+ def initialize(url)
28
+ @url = url
29
+ memoize [:fetch], Stash.new(DiskStash::Cache.new)
30
+ end
31
+
32
+ def fetch(path)
33
+ results = []
34
+ Anemone.crawl(@url, discard_page_bodies: true) do |anemone|
35
+ anemone.on_pages_like(/\/#{path}\/\w+\/\w+\.(ini|zip)$/i) do |page|
36
+ results << page.to_hash
37
+ end
38
+ end
39
+ results.reduce({}, :recursive_merge)
40
+ end
41
+ end
42
+
43
+ # to_hash smoke cache
44
+ #
45
+ module Anemone
46
+ class Page
47
+ def to_hash
48
+ file = ::File.basename(@url.to_s)
49
+ key = ::File.basename(file, '.*').downcase.to_sym
50
+ type = ::File.extname(file)[1..-1].downcase.to_sym
51
+ id = Hoodie::Obfuscate.befuddle(file, Digest::MD5.hexdigest(body.to_s))
52
+ mtime = Time.parse(@headers['last-modified'][0]).to_i
53
+ utime = Time::now.to_i
54
+ state = utime > mtime ? :clean : :dirty
55
+ key = { key => { type => {
56
+ id: id,
57
+ cache_state: state,
58
+ file: file,
59
+ key: key,
60
+ type: type,
61
+ url: @url.to_s,
62
+ mtime: mtime,
63
+ links: links.map(&:to_s),
64
+ code: @code,
65
+ visited: @visited,
66
+ depth: @depth,
67
+ referer: @referer.to_s,
68
+ fetched: @fetched,
69
+ utime: utime,
70
+ md5_digest: Digest::MD5.hexdigest(body.to_s),
71
+ sha256_digest: Digest::SHA256.hexdigest(body.to_s)
72
+ }}}
73
+ end
74
+ end
75
+ end