Skip to content

Instantly share code, notes, and snippets.

@sj26
Created November 29, 2023 02:56
Show Gist options
  • Save sj26/7306583bb79097fd678723b6d9fb9e52 to your computer and use it in GitHub Desktop.
Save sj26/7306583bb79097fd678723b6d9fb9e52 to your computer and use it in GitHub Desktop.
require "uri"
# Parses an S3 URI for region, bucket and key
#
# Based on the java sdk:
#
# https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-s3/src/main/java/com/amazonaws/services/s3/AmazonS3URI.java
#
# Reference:
#
# https://docs.aws.amazon.com/general/latest/gr/s3.html
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html
#
class AwsS3URI
class InvalidURI < ArgumentError; end
ENDPOINT_PATTERN = /\A(?:(?<bucket>.+)\.)?s3(?:-[a-z0-9-]+)?\.(?:dualstack\.)?(?:(?<region>[a-z0-9-]+?)\.)?amazonaws\.com\z/i
attr_reader :uri, :region, :bucket, :key
def initialize(url)
@uri = URI.parse(url)
if @uri.scheme == "s3"
unless @uri.host
raise InvalidURI.new("Missing bucket")
end
@region = nil
@bucket = @uri.host
unless @uri.path.empty?
@key = @uri.path.delete_prefix("/")
end
elsif @uri.scheme == "http" || @uri.scheme == "https"
if @uri.host.nil? || @uri.host.empty?
raise InvalidURI.new("Missing hostname")
end
if match = ENDPOINT_PATTERN.match(@uri.host)
@region = match[:region] || "us-east-1"
if match[:bucket]
# Virtual host bucket name
@bucket = match[:bucket]
unless @uri.path.empty?
@key = @uri.path.delete_prefix("/")
end
else
# Path-style bucket name
prefix, rest = @uri.path.delete_prefix("/").split("/", 2)
if prefix.nil? || prefix.empty?
raise InvalidURI.new("Missing bucket")
end
@bucket = prefix
unless rest.nil? || rest.empty?
@key = rest
end
end
else
raise InvalidURI.new("Not a recognized S3 endpoint")
end
else
raise InvalidURI.new("Unknown scheme")
end
end
end
require "spec_helper"
require "aws_s3_uri"
RSpec.describe AwsS3URI do
it "parses s3://BUCKET" do
expect(AwsS3URI.new("s3://foo")).to have_attributes(region: nil, bucket: "foo", key: nil)
end
it "parses s3://BUCKET/" do
expect(AwsS3URI.new("s3://foo")).to have_attributes(region: nil, bucket: "foo", key: nil)
end
it "parses s3://BUCKET/KEY" do
expect(AwsS3URI.new("s3://foo/bar/baz")).to have_attributes(region: nil, bucket: "foo", key: "bar/baz")
end
it "parses http://BUCKET.s3.amazonaws.com" do
expect(AwsS3URI.new("http://foo.s3.amazonaws.com")).to have_attributes(region: "us-east-1", bucket: "foo", key: nil)
end
it "parses http://BUCKET.s3.amazonaws.com/KEY" do
expect(AwsS3URI.new("http://foo.s3.amazonaws.com/bar/baz")).to have_attributes(region: "us-east-1", bucket: "foo", key: "bar/baz")
end
it "parses http://BUCKET.s3-fips.amazonaws.com" do
expect(AwsS3URI.new("http://foo.s3-fips.amazonaws.com")).to have_attributes(region: "us-east-1", bucket: "foo", key: nil)
end
it "parses http://BUCKET.s3-fips.amazonaws.com/KEY" do
expect(AwsS3URI.new("http://foo.s3-fips.amazonaws.com/bar/baz")).to have_attributes(region: "us-east-1", bucket: "foo", key: "bar/baz")
end
it "parses http://BUCKET.s3.dualstack.amazonaws.com" do
expect(AwsS3URI.new("http://foo.s3.dualstack.amazonaws.com")).to have_attributes(region: "us-east-1", bucket: "foo", key: nil)
end
it "parses http://BUCKET.s3.dualstack.amazonaws.com/KEY" do
expect(AwsS3URI.new("http://foo.s3.dualstack.amazonaws.com/bar/baz")).to have_attributes(region: "us-east-1", bucket: "foo", key: "bar/baz")
end
it "parses http://BUCKET.s3.REGION.amazonaws.com" do
expect(AwsS3URI.new("http://foo.s3.us-west-1.amazonaws.com")).to have_attributes(region: "us-west-1", bucket: "foo", key: nil)
end
it "parses http://BUCKET.s3.REGION.amazonaws.com/KEY" do
expect(AwsS3URI.new("http://foo.s3.us-west-1.amazonaws.com/bar/baz")).to have_attributes(region: "us-west-1", bucket: "foo", key: "bar/baz")
end
it "parses http://BUCKET.s3-fips.REGION.amazonaws.com" do
expect(AwsS3URI.new("http://foo.s3-fips.us-west-1.amazonaws.com")).to have_attributes(region: "us-west-1", bucket: "foo", key: nil)
end
it "parses http://BUCKET.s3-fips.REGION.amazonaws.com/KEY" do
expect(AwsS3URI.new("http://foo.s3-fips.us-west-1.amazonaws.com/bar/baz")).to have_attributes(region: "us-west-1", bucket: "foo", key: "bar/baz")
end
it "parses http://BUCKET.s3.dualstack.REGION.amazonaws.com" do
expect(AwsS3URI.new("http://foo.s3.dualstack.us-west-1.amazonaws.com")).to have_attributes(region: "us-west-1", bucket: "foo", key: nil)
end
it "parses http://BUCKET.s3.dualstack.REGION.amazonaws.com/KEY" do
expect(AwsS3URI.new("http://foo.s3.dualstack.us-west-1.amazonaws.com/bar/baz")).to have_attributes(region: "us-west-1", bucket: "foo", key: "bar/baz")
end
it "parses http://s3.amazonaws.com/BUCKET" do
expect(AwsS3URI.new("http://s3.amazonaws.com/foo")).to have_attributes(region: "us-east-1", bucket: "foo", key: nil)
end
it "parses http://s3.amazonaws.com/BUCKET/KEY" do
expect(AwsS3URI.new("http://s3.amazonaws.com/foo/bar/baz")).to have_attributes(region: "us-east-1", bucket: "foo", key: "bar/baz")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment