Skip to content

Commit

Permalink
some stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
Jakub Kuźma committed Jun 26, 2009
1 parent eeeff7b commit 81adaf3
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 35 deletions.
6 changes: 4 additions & 2 deletions Rakefile
@@ -1,3 +1,5 @@
# encoding: UTF-8

require 'rubygems'
require 'rake'

Expand All @@ -6,9 +8,9 @@ begin
Jeweler::Tasks.new do |gem|
gem.name = "s3"
gem.summary = %Q{TODO}
gem.email = "kuba@synergypeople.net"
gem.email = "qoobaa@gmail.com"
gem.homepage = "http://github.com/qoobaa/s3"
gem.authors = ["Jakub Kuźma"]
gem.authors = ["Jakub Kuźma", "Mirosław Boruta"]
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
end

Expand Down
8 changes: 7 additions & 1 deletion lib/s3.rb
@@ -1,9 +1,15 @@
require "time"
require "openssl"
require "net/http"
require "net/https"
require "base64"
require "forwardable"
require "digest/md5"

require "s3/authentication"
require "xmlsimple"

require "s3/base"
require "s3/signature"
require "s3/service"
require "s3/bucket"
require "s3/object"
38 changes: 38 additions & 0 deletions lib/s3/base.rb
@@ -0,0 +1,38 @@
module S3
class Base
HOST = "s3.amazonaws.com"

attr_accessor :access_key_id, :secret_access_key, :host, :use_ssl
alias :use_ssl? :use_ssl

def initialize(options)
@access_key_id = options[:access_key_id]
@secret_access_key = options[:secret_access_key]
@use_ssl = options[:use_ssl] || false
@host = options[:host] || HOST
end

def port
use_ssl? ? 443 : 80
end

def send_request(request)
http = Net::HTTP.new(host, port)
http.set_debug_output(STDOUT)
http.use_ssl = use_ssl?
response = http.start do |http|
host = http.address
request['Date'] ||= Time.now.httpdate
request["Authorization"] = S3::Signature.generate(:host => host,
:request => request,
:access_key_id => access_key_id,
:secret_access_key => secret_access_key)
http.request(request)
end

# TODO: handle 40x responses

response
end
end
end
152 changes: 152 additions & 0 deletions lib/s3/bucket.rb
@@ -0,0 +1,152 @@
module S3
class Bucket < Base
attr_accessor :name

def initialize(options)
@name = options[:name]
options[:host], @path_prefix = self.class.parse_name(options[:name], options[:host] || HOST)
super
end

def self.parse_name(bucket_name, host)
path_prefix = ""
if "#{bucket_name}.#{host}" =~ /\A#{URI::REGEXP::PATTERN::HOSTNAME}\Z/
# VHOST
host = "#{bucket_name}.#{host}"
else
# PATH BASED
path_prefix = "/#{bucket_name}"
end
[host, path_prefix]
end

def location
path = @path_prefix
path << "/?location"

request = Net::HTTP::Get.new(path)
response = send_request(request)
parse_location(response.body)
end

def exists?
path = @path_prefix
path << "/"

request = Net::HTTP::Head.new(path)
send_request(request)
true
end

def delete(options = {})
path = @path_prefix
path << "/"

request = Net::HTTP::Delete.new(path)
send_request(request)
end

def save(options = {})
location = options[:location]

path = @path_prefix
path << "/"

request = Net::HTTP::Put.new(path)

if location
xml = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>"
request["content-type"] = "application/xml"
request.body = xml
end

send_request(request)
end

def objects(options = {})
path = @path_prefix
path << "/"

params = {}
params["max-keys"] = options[:limit] if options.has_key?(:limit)
params["prefix"] = options[:prefix] if options.has_key?(:prefix)
params["marker"] = options[:marker] if options.has_key?(:marker)
params["delimiter"] = options[:delimiter] if options.has_key?(:delimiter)

joined_params = params.map { |key, value| "#{key}=#{value}" }.join("&")

path << "?#{joined_params}" unless joined_params.empty?

request = Net::HTTP::Get.new(path)
response = send_request(request)
parse_objects(response.body)
end

# OBJECT methods

def build_object(options = {})
Object.new(options.merge(:bucket => self))
end

def save_object(object)
content_type = object.content_type
content = object.content
content_type ||= "application/octet-stream"
acl = object.acl || "public-read"
content = content.read if content.kind_of?(IO)

path = @path_prefix
path << "/"
path << object.key

request = Net::HTTP::Put.new(path)

request["content-type"] = content_type
request["x-amz-acl"] = acl
request["content-md5"] = Base64.encode64(Digest::MD5.digest(content)).chomp

request.body = content

response = send_request(request)
end

def delete_object(object)
path = @path_prefix
path << "/"
path << object.key

request = Net::HTTP::Delete.new(path)

response = send_request(request)
end

def get_object(object, options = {})
path = @path_prefix
path << "/"
path << object.key

request = Net::HTTP::Get.new(path)

response = send_request(request)
end

private

def parse_objects(xml_body)
xml = XmlSimple.xml_in(xml_body)
objects_attributes = xml["Contents"]
objects_attributes.map do |object|
Object.new(:key => object["Key"],
:etag => object["ETag"],
:last_modified => object["LastModified"],
:size => object["Size"],
:bucket => self)
end
end

def parse_location(xml_body)
xml = XmlSimple.xml_in(xml_body)
xml["content"]
end
end
end
18 changes: 18 additions & 0 deletions lib/s3/object.rb
@@ -0,0 +1,18 @@
module S3
class Object
attr_accessor :key, :last_modified, :etag, :size, :bucket, :content, :content_type, :acl

protected

def initialize(options)
@acl = options[:acl]
@bucket = options[:bucket]
@key = options[:key]
@last_modified = options[:last_modified]
@etag = options[:etag]
@size = options[:size]
@content = options[:content]
@content_type = options[:content_type]
end
end
end
35 changes: 15 additions & 20 deletions lib/s3/service.rb
@@ -1,29 +1,24 @@
module S3
class Service
HOST = "s3.amazonaws.com"
PORT = 80

def initialize(options)
@access_key_id = options[:access_key_id]
@secret_access_key = options[:secret_access_key]
end

class Service < Base
def buckets
request = Net::HTTP::Get.new("/")
response = send_request(request)
parse_buckets(response.body)
end

private

http = Net::HTTP.new(HOST, PORT)
http.set_debug_output(STDOUT)
http.start do |http|
host = http.address
request['Date'] ||= Time.now.httpdate
request["Authorization"] = S3::Signature.generate(:host => host,
:request => request,
:access_key_id => @access_key_id,
:secret_access_key => @secret_access_key)
http.request(request)
def parse_buckets(xml_body)
xml = XmlSimple.xml_in(xml_body)
buckets_names = xml["Buckets"].first["Bucket"].map { |bucket| bucket["Name"].first }
buckets_names.map do |bucket_name|
Bucket.new(:name => bucket_name,
:access_key_id => access_key_id,
:secret_access_key => secret_access_key,
:host => host,
:use_ssl => use_ssl)
end
end

end
end

11 changes: 2 additions & 9 deletions lib/s3/authentication.rb → lib/s3/signature.rb
Expand Up @@ -27,10 +27,6 @@ def self.generate(options)
string_to_sign << canonicalized_amz_headers
string_to_sign << canonicalized_resource

puts "***"
puts string_to_sign
puts "***"

digest = OpenSSL::Digest::Digest.new('sha1')
hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
base64 = Base64.encode64(hmac)
Expand Down Expand Up @@ -84,7 +80,7 @@ def self.canonicalized_amz_headers(request)
# become 'x-amz-meta-username:fred,barney'
joined_headers = unfolded_headers.map do |header|
key = header.first.strip
value = headers.last.strip
value = header.last.strip
"#{key}:#{value}"
end

Expand All @@ -108,19 +104,16 @@ def self.canonicalized_resource(host, request)
# Hosting of Buckets.
bucket_name = host.sub(/\.?s3\.amazonaws\.com\Z/, "")
string << "/#{bucket_name}" unless bucket_name.empty?
puts "string: #{string}"

# 3. Append the path part of the un-decoded HTTP Request-URI,
# up-to but not including the query string.
uri = URI.parse(request.path)
string << uri.path
puts "string: #{string}"

# 4. If the request addresses a sub-resource, like ?location,
# ?acl, or ?torrent, append the sub-resource including question
# mark.
string << "?#{$1}" if uri.query =~ /[&?](acl|torrent|logging|location)(?:&|=|\Z)/
puts "string: #{string}"
string << "?#{$1}" if uri.query =~ /&?(acl|torrent|logging|location)(?:&|=|\Z)/
string
end
end
Expand Down
15 changes: 15 additions & 0 deletions test/bucket_test.rb
@@ -0,0 +1,15 @@
require 'test_helper'

class BucketTest < Test::Unit::TestCase
def test_parse_name_with_vhost_name
host, prefix = S3::Bucket.parse_name("data.synergypeople.net", "s3.amazonaws.com")
assert_equal "data.synergypeople.net.s3.amazonaws.com", host
assert_equal "", prefix
end

def test_parse_name_with_prefix_based_name
host, prefix = S3::Bucket.parse_name("synergypeople_net", "s3.amazonaws.com")
assert_equal "s3.amazonaws.com", host
assert_equal "/synergypeople_net", prefix
end
end
4 changes: 1 addition & 3 deletions test/s3_test.rb
@@ -1,7 +1,5 @@
require 'test_helper'

class S3Test < Test::Unit::TestCase
def test_something_for_real
flunk "hey buddy, you should probably rename this file and start testing for real"
end

end

0 comments on commit 81adaf3

Please sign in to comment.