File: utils.rb

package info (click to toggle)
ruby-aws 2.10.2-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, buster, forky, sid, trixie
  • size: 748 kB
  • sloc: ruby: 7,748; makefile: 16
file content (255 lines) | stat: -rw-r--r-- 10,440 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
module Aws

  class Utils #:nodoc:
    @@digest1 = OpenSSL::Digest.new("sha1")
    @@digest256 = nil
    if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
      @@digest256 = OpenSSL::Digest.new("sha256") rescue nil # Some installation may not support sha256
    end

    def self.sign(aws_secret_access_key, auth_string)
      Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
    end


    # Set a timestamp and a signature version
    def self.fix_service_params(service_hash, signature)
      service_hash["Timestamp"] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"]
      service_hash["SignatureVersion"] = signature
      service_hash
    end

    # Signature Version 0
    # A deprecated guy (should work till septemper 2009)
    def self.sign_request_v0(aws_secret_access_key, service_hash)
      fix_service_params(service_hash, '0')
      string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}"
      service_hash['Signature'] = Utils::sign(aws_secret_access_key, string_to_sign)
      service_hash.to_a.collect { |key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
    end

    # Signature Version 1
    # Another deprecated guy (should work till septemper 2009)
    def self.sign_request_v1(aws_secret_access_key, service_hash)
      fix_service_params(service_hash, '1')
      string_to_sign = service_hash.sort { |a, b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase) }.to_s
      service_hash['Signature'] = Utils::sign(aws_secret_access_key, string_to_sign)
      service_hash.to_a.collect { |key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
    end

    # Signature Version 2
    # EC2, SQS and SDB requests must be signed by this guy.
    # See:  http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
    #       http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
    def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
      fix_service_params(service_hash, '2')
      # select a signing method (make an old openssl working with sha1)
      # make 'HmacSHA256' to be a default one
      service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
      service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256
      # select a digest
      digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
      # form string to sign
      canonical_string = service_hash.keys.sort.map do |key|
        "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
      end.join('&')
      string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
      # sign the string
      signature = escape_sig(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
      ret = "#{canonical_string}&Signature=#{signature}"
#            puts 'full=' + ret.inspect
      ret
    end

    # New signature for ses
    def self.signature_version3(aws_secret_key, now)
      algorithm =  @@digest256 ? 'HmacSHA256' : 'HmacSHA1'
      # select a digest
      digest = (algorithm == 'HmacSHA256' ? @@digest256 : @@digest1)
      signature = (Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_key, now.httpdate)).strip)
      return signature, algorithm
    end

    HEX = [
        "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
        "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F",
        "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
        "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F",
        "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
        "%28", "%29", "%2A", "%2B", "%2C", "%2D", "%2E", "%2F",
        "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
        "%38", "%39", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F",
        "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
        "%48", "%49", "%4A", "%4B", "%4C", "%4D", "%4E", "%4F",
        "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
        "%58", "%59", "%5A", "%5B", "%5C", "%5D", "%5E", "%5F",
        "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
        "%68", "%69", "%6A", "%6B", "%6C", "%6D", "%6E", "%6F",
        "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
        "%78", "%79", "%7A", "%7B", "%7C", "%7D", "%7E", "%7F",
        "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
        "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F",
        "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
        "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
        "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7",
        "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF",
        "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7",
        "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF",
        "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
        "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF",
        "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7",
        "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF",
        "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7",
        "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
        "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7",
        "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
    ]
    TO_REMEMBER = 'AZaz09 -_.!~*\'()'
    ASCII = {} # {'A'=>65, 'Z'=>90, 'a'=>97, 'z'=>122, '0'=>48, '9'=>57, ' '=>32, '-'=>45, '_'=>95, '.'=>}
    TO_REMEMBER.each_byte do |b|
      ASCII[b.chr] = b.chr.unpack("c")[0]
    end
#        puts 'ascii=' + ASCII.inspect

    # Escape a string accordingly Amazon rulles
    # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
    def self.amz_escape(param)

      param = param.to_s
#            param = param.force_encoding("UTF-8")

#      e = "x" # escape2(param.to_s)
#            puts 'ESCAPED=' + e.inspect


      #return CGI.escape(param.to_s).gsub("%7E", "~").gsub("+", "%20") # from: http://umlaut.rubyforge.org/svn/trunk/lib/aws_product_sign.rb

      #param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
      #  '%' + $1.unpack('H2' * $1.size).join('%').upcase
      #end

#            puts 'e in=' + e.inspect
#            converter = Iconv.new('ASCII', 'UTF-8')
#            e = converter.iconv(e) #.unpack('U*').select{ |cp| cp < 127 }.pack('U*')
#            puts 'e out=' + e.inspect

      e2 = CGI.escape(param)
      e2 = e2.gsub("%7E", "~")
      e2 = e2.gsub("+", "%20")
      e2 = e2.gsub("*", "%2A")

#            puts 'E2=' + e2.inspect
#            puts e == e2.to_s

      e2

    end

    # From: https://github.com/tomandersen/aws/commit/04b494b1ac440db1cd3b4ff3c84d0b2745d25250
    # Not used yet, but worth trying it out.
    def self.perhaps_a_better_escape(key)
      # EG: CGI escape is str.gsub(/[^a-zA-Z0-9_\-.]/n){ sprintf("%%%02X", $&.unpack("C")[0]) }, but we can leave in / and some others for easier reading
      # escape all characters except a-Z, 0-9, and - _ . ! ~ *'  ( )  /
      key.gsub(/[^a-zA-Z0-9\-_.!~*'()\/]/n) { sprintf("%%%02X", $&.unpack("C")[0]) }
    end

    def self.escape2(s)
      # home grown
      ret = ""
      s.unpack("U*") do |ch|
#                puts 'ch=' + ch.inspect
        if ASCII['A'] <= ch && ch <= ASCII['Z'] # A to Z
          ret << ch
        elsif ASCII['a'] <= ch && ch <= ASCII['z'] # a to z
          ret << ch
        elsif ASCII['0'] <= ch && ch <= ASCII['9'] # 0 to 9
          ret << ch
        elsif ch == ASCII[' '] # space
          ret << "%20" # "+"
        elsif ch == ASCII['-'] || ch == ASCII['_'] || ch == ASCII['.'] || ch == ASCII['~']
          ret << ch
        elsif ch <= 0x007f # other ascii
          ret << HEX[ch]
        elsif ch <= 0x07FF # non-ascii
          ret << HEX[0xc0 | (ch >> 6)]
          ret << HEX[0x80 | (ch & 0x3F)]
        else
          ret << HEX[0xe0 | (ch >> 12)]
          ret << HEX[0x80 | ((ch >> 6) & 0x3F)]
          ret << HEX[0x80 | (ch & 0x3F)]
        end

      end
      ret

    end

    def self.escape_sig(raw)
      e = CGI.escape(raw)
    end

    # From Amazon's SQS Dev Guide, a brief description of how to escape:
    # "URL encode the computed signature and other query parameters as specified in
    # RFC1738, section 2.2. In addition, because the + character is interpreted as a blank space
    # by Sun Java classes that perform URL decoding, make sure to encode the + character
    # although it is not required by RFC1738."
    # Avoid using CGI::escape to escape URIs.
    # CGI::escape will escape characters in the protocol, host, and port
    # sections of the URI.  Only target chars in the query
    # string should be escaped.
    def self.URLencode(raw)
      e = URI.escape(raw)
      e.gsub(/\+/, "%2b")
    end


    def self.allow_only(allowed_keys, params)
      bogus_args = []
      params.keys.each { |p| bogus_args.push(p) unless allowed_keys.include?(p) }
      raise AwsError.new("The following arguments were given but are not legal for the function call #{caller_method}: #{bogus_args.inspect}") if bogus_args.length > 0
    end

    def self.mandatory_arguments(required_args, params)
      rargs = required_args.dup
      params.keys.each { |p| rargs.delete(p) }
      raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0
    end

    def self.caller_method
      caller[1]=~/`(.*?)'/
      $1
    end

    def self.blank?(obj)
      case obj
      when NilClass, FalseClass
        true
      when TrueClass, Numeric
        false
      when Array, Hash
        obj.empty?
      when String
        obj.empty? || obj.strip.empty?
      else
        # "", "   ", nil, [], and {} are blank
        if obj.respond_to?(:empty?) && obj.respond_to?(:strip)
          obj.empty? or obj.strip.empty?
        elsif obj.respond_to?(:empty?)
          obj.empty?
        else
          !obj
        end
      end
    end

    def self.underscore(camel_cased_word)
      camel_cased_word.to_s.gsub(/::/, '/').
        gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
        gsub(/([a-z\d])([A-Z])/,'\1_\2').
        tr("-", "_").
        downcase
    end


  end
end