awsraw 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in awsraw.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/awsraw.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "awsraw/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "awsraw"
7
+ s.version = Awsraw::VERSION
8
+ s.authors = ["Pete Yandell"]
9
+ s.email = ["pete@notahat.com"]
10
+ s.homepage = "http://github.com/envato/flickraw"
11
+ s.summary = %q{Minimal AWS client}
12
+ s.description = %q{A client for Amazon Web Services in the style of FlickRaw}
13
+
14
+ s.rubyforge_project = "awsraw"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rake"
23
+ s.add_development_dependency "rspec"
24
+ # s.add_runtime_dependency "rest-client"
25
+ end
@@ -0,0 +1,78 @@
1
+ require 'net/http'
2
+ require 'awsraw/s3/request'
3
+ require 'awsraw/s3/response'
4
+ require 'awsraw/s3/signer'
5
+
6
+ module AWSRaw
7
+ module S3
8
+
9
+ # A client for the AWS S3 rest API.
10
+ #
11
+ # http://docs.amazonwebservices.com/AmazonS3/latest/API/APIRest.html
12
+ class Client
13
+
14
+ def initialize(access_key_id, secret_access_key)
15
+ @access_key_id = access_key_id
16
+ @secret_access_key = secret_access_key
17
+ end
18
+
19
+ def request(params = {})
20
+ request = Request.new(params, signer)
21
+
22
+ http_request = construct_http_request(request)
23
+
24
+ http_response = Net::HTTP.start(request.uri.host, request.uri.port) do |http|
25
+ http.request(http_request)
26
+ end
27
+
28
+ construct_response(http_response)
29
+ end
30
+
31
+ def request!(params = {})
32
+ response = request(params)
33
+ raise "Uh oh! Failure from S3." if response.failure?
34
+ end
35
+
36
+ private
37
+
38
+ def construct_http_request(request)
39
+ klass = http_request_class(request)
40
+ path = request.uri.request_uri
41
+
42
+ klass.new(path).tap do |http_request|
43
+ request.headers.each do |name, value|
44
+ http_request[name] = value
45
+ end
46
+ http_request.body = request.content
47
+ end
48
+ end
49
+
50
+ def http_request_class(request)
51
+ case request.method
52
+ when "GET"
53
+ Net::HTTP::Get
54
+ when "HEAD"
55
+ Net::HTTP::Head
56
+ when "PUT"
57
+ Net::HTTP::Put
58
+ else
59
+ raise "Invalid HTTP method!"
60
+ end
61
+ end
62
+
63
+ def construct_response(http_response)
64
+ Response.new(
65
+ :code => http_response.code,
66
+ :headers => http_response.to_hash,
67
+ :content => http_response.body
68
+ )
69
+ end
70
+
71
+ def signer
72
+ @signer ||= Signer.new(@access_key_id, @secret_access_key)
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,58 @@
1
+ require 'digest/md5'
2
+ require 'time'
3
+ require 'uri'
4
+
5
+ module AWSRaw
6
+ module S3
7
+
8
+ # Note that we use path style (rather than virtual hosted style) requests.
9
+ # This is because virtual hosted requests only support lower case bucket
10
+ # names.
11
+ #
12
+ # See http://docs.amazonwebservices.com/AmazonS3/latest/dev/VirtualHosting.html
13
+ class Request
14
+ def initialize(params, signer)
15
+ @method = params[:method]
16
+ @bucket = params[:bucket]
17
+ @key = params[:key]
18
+ @query = params[:query]
19
+ @headers = params[:headers] || {}
20
+ @content = params[:content]
21
+
22
+ raise "Content without Content-Type" if !@content.nil? && @headers["Content-Type"].nil?
23
+
24
+ headers["Content-MD5"] = content_md5 unless content.nil?
25
+ headers["Date"] = Time.now.rfc2822
26
+ headers["Authorization"] = signer.signature(self)
27
+ end
28
+
29
+ attr_reader :method
30
+ attr_reader :bucket
31
+ attr_reader :key
32
+ attr_reader :query
33
+ attr_reader :headers
34
+ attr_reader :content
35
+
36
+ def host
37
+ "s3.amazonaws.com"
38
+ end
39
+
40
+ def path
41
+ @path ||= URI.escape("/#{bucket}#{key}")
42
+ end
43
+
44
+ def uri
45
+ @uri ||= URI::HTTP.build(
46
+ :host => host,
47
+ :path => path,
48
+ :query => query
49
+ )
50
+ end
51
+
52
+ def content_md5
53
+ @content_md5 ||= Base64.encode64(Digest::MD5.digest(content)).strip
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ module AWSRaw
2
+ module S3
3
+
4
+ class Response
5
+ def initialize(params = {})
6
+ @code = params[:code]
7
+ @headers = params[:headers]
8
+ @content = params[:content]
9
+ end
10
+
11
+ attr_accessor :code
12
+ attr_accessor :headers
13
+ attr_accessor :content
14
+
15
+ def success?
16
+ code =~ /^2\d\d$/
17
+ end
18
+
19
+ def failure?
20
+ !success?
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+
@@ -0,0 +1,60 @@
1
+ require 'digest/sha1'
2
+ require 'openssl'
3
+ require 'uri'
4
+ require 'base64'
5
+
6
+ module AWSRaw
7
+ module S3
8
+
9
+ # Generates the Authorization header for a REST request to S3.
10
+ #
11
+ # See http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
12
+ class Signer
13
+
14
+ def initialize(access_key_id, secret_access_key)
15
+ @access_key_id = access_key_id
16
+ @secret_access_key = secret_access_key
17
+ end
18
+
19
+ def signature(request)
20
+ string_to_sign = string_to_sign(request)
21
+
22
+ digest = OpenSSL::Digest::Digest.new("sha1")
23
+ sha = OpenSSL::HMAC.digest(digest, @secret_access_key, string_to_sign)
24
+ signature = Base64.encode64(sha).strip
25
+
26
+ "AWS #{@access_key_id}:#{signature}"
27
+ end
28
+
29
+ def string_to_sign(request)
30
+ [
31
+ request.method,
32
+ request.headers["Content-MD5"] || "",
33
+ request.headers["Content-Type"] || "",
34
+ request.headers["Date"],
35
+ canonicalized_amz_headers(request.headers),
36
+ canonicalized_resource(request)
37
+ ].flatten.join("\n")
38
+ end
39
+
40
+ private
41
+
42
+ def canonicalized_amz_headers(headers)
43
+ header_names = headers.keys.
44
+ select {|name| name =~ /^x-amz-/i }.
45
+ sort_by {|name| name.downcase }
46
+
47
+ header_names.map do |name|
48
+ "#{name.downcase}:#{headers[name]}"
49
+ end
50
+ end
51
+
52
+ def canonicalized_resource(request)
53
+ # TODO: Should also append the sub-resource.
54
+ request.path
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module Awsraw
2
+ VERSION = "0.0.1"
3
+ end
data/lib/awsraw.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "awsraw/version"
2
+
3
+ module Awsraw
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,28 @@
1
+ require 'awsraw/s3/client'
2
+
3
+ describe AWSRaw::S3::Client do
4
+
5
+ subject { AWSRaw::S3::Client.new("dummy_access_key_id", "dummy_secret_access_key") }
6
+
7
+ context "#request!" do
8
+ it "returns if the response indicates success" do
9
+ response = stub(:failure? => false)
10
+ subject.stub(:request => response)
11
+
12
+ expect {
13
+ subject.request!(:method => "PUT")
14
+ }.should_not raise_error
15
+ end
16
+
17
+ it "raises an error if the response indicates failure" do
18
+ response = stub(:failure? => true)
19
+ subject.stub(:request => response)
20
+
21
+ expect {
22
+ subject.request!(:method => "PUT")
23
+ }.should raise_error("Uh oh! Failure from S3.")
24
+ end
25
+ end
26
+
27
+ end
28
+
@@ -0,0 +1,49 @@
1
+ require 'awsraw/s3/signer'
2
+
3
+ describe AWSRaw::S3::Signer do
4
+ let(:access_key_id) { "0PN5J17HBGZHT7JJ3X82" }
5
+ let(:secret_access_key) { "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o" }
6
+
7
+ subject { AWSRaw::S3::Signer.new(access_key_id, secret_access_key) }
8
+
9
+ context "examples from Amazon docs" do
10
+ it "signs a get request correctly" do
11
+ request = stub(
12
+ :method => "GET",
13
+ :path => "/johnsmith/photos/puppy.jpg",
14
+ :headers => { "Date" => "Tue, 27 Mar 2007 19:36:42 +0000" }
15
+ )
16
+
17
+ subject.string_to_sign(request).should ==
18
+ "GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/johnsmith/photos/puppy.jpg"
19
+
20
+ subject.signature(request).should == "AWS #{access_key_id}:xXjDGYUmKxnwqr5KXNPGldn5LbA="
21
+ end
22
+
23
+ it "signs an upload correctly" do
24
+ request = stub(
25
+ :method => "PUT",
26
+ :path => "/static.johnsmith.net/db-backup.dat.gz",
27
+ :headers => {
28
+ "User-Agent" => "curl/7.15.5",
29
+ "Date" => "Tue, 27 Mar 2007 21:06:08 +0000",
30
+ "x-amz-acl" => "public-read",
31
+ "Content-Type" => "application/x-download",
32
+ "Content-MD5" => "4gJE4saaMU4BqNR0kLY+lw==",
33
+ "X-Amz-Meta-ReviewedBy" => "joe@johnsmith.net,jane@johnsmith.net",
34
+ "X-Amz-Meta-FileChecksum" => "0x02661779",
35
+ "X-Amz-Meta-ChecksumAlgorithm" => "crc32",
36
+ "Content-Disposition" => "attachment; filename=database.dat",
37
+ "Content-Encoding" => "gzip",
38
+ "Content-Length" => "5913339"
39
+ }
40
+ )
41
+
42
+ subject.string_to_sign(request).should ==
43
+ "PUT\n4gJE4saaMU4BqNR0kLY+lw==\napplication/x-download\nTue, 27 Mar 2007 21:06:08 +0000\nx-amz-acl:public-read\nx-amz-meta-checksumalgorithm:crc32\nx-amz-meta-filechecksum:0x02661779\nx-amz-meta-reviewedby:joe@johnsmith.net,jane@johnsmith.net\n/static.johnsmith.net/db-backup.dat.gz"
44
+
45
+ subject.signature(request).should == "AWS #{access_key_id}:C0FlOtU8Ylb9KDTpZqYkZPX91iI="
46
+ end
47
+
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: awsraw
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Pete Yandell
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-12 00:00:00 +11:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ requirement: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ requirement: *id002
49
+ description: A client for Amazon Web Services in the style of FlickRaw
50
+ email:
51
+ - pete@notahat.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - Gemfile
61
+ - Rakefile
62
+ - awsraw.gemspec
63
+ - lib/awsraw.rb
64
+ - lib/awsraw/s3/client.rb
65
+ - lib/awsraw/s3/request.rb
66
+ - lib/awsraw/s3/response.rb
67
+ - lib/awsraw/s3/signer.rb
68
+ - lib/awsraw/version.rb
69
+ - spec/s3/client_spec.rb
70
+ - spec/s3/signer_spec.rb
71
+ has_rdoc: true
72
+ homepage: http://github.com/envato/flickraw
73
+ licenses: []
74
+
75
+ post_install_message:
76
+ rdoc_options: []
77
+
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project: awsraw
101
+ rubygems_version: 1.6.2
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Minimal AWS client
105
+ test_files:
106
+ - spec/s3/client_spec.rb
107
+ - spec/s3/signer_spec.rb