Skip to content

Commit

Permalink
Merge pull request #13 from lluuaapp/master
Browse files Browse the repository at this point in the history
Added Support for Custom Hosts/Regions
  • Loading branch information
rafiki270 authored Sep 7, 2018
2 parents 1faabf2 + 5bc36d9 commit 82a9c6c
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 61 deletions.
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@
"repositoryURL": "https://github.com/vapor/url-encoded-form.git",
"state": {
"branch": null,
"revision": "57cf7fb9c1a1014c50bc05123684a9139ad44127",
"version": "1.0.3"
"revision": "cbfe7ef6301557d3f2d0807a98165232ae06e1c6",
"version": "1.0.4"
}
},
{
Expand All @@ -149,9 +149,9 @@
"package": "VaporTestTools",
"repositoryURL": "https://github.com/LiveUI/VaporTestTools.git",
"state": {
"branch": "master",
"branch": null,
"revision": "e07ce263257463f2f2af9e640f81eb95a72760e4",
"version": null
"version": "0.1.5"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Sources/S3/Extensions/S3+Bucket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public extension S3 {

let content = """
<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<LocationConstraint>\(region.rawValue)</LocationConstraint>
<LocationConstraint>\(region.name.rawValue)</LocationConstraint>
</CreateBucketConfiguration>
"""

Expand Down
2 changes: 1 addition & 1 deletion Sources/S3/Extensions/S3+List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension S3 {
/// Get list of objects
public func list(bucket: String, region: Region? = nil, headers: [String: String], on container: Container) throws -> Future<BucketResults> {
let region = region ?? signer.config.region
guard let baseUrl = URL(string: "https://\(bucket).s3.\(region.rawValue).amazonaws.com/"), let host = baseUrl.host,
guard let baseUrl = URL(string: region.hostUrlString(bucket: bucket)), let host = baseUrl.host,
var components = URLComponents(string: baseUrl.absoluteString) else {
throw S3.Error.invalidUrl
}
Expand Down
13 changes: 11 additions & 2 deletions Sources/S3/URLBuilder/URLBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,22 @@ extension Region {

/// Host URL including scheme
public func hostUrlString(bucket: String? = nil) -> String {
if hostName != nil {
return urlProtocol + host.finished(with: "/") + (bucket ?? "")
}

var bucket = bucket
if let b = bucket {
if let b = bucket, !b.isEmpty {
bucket = b + "."
}
return "https://" + (bucket ?? "") + host.finished(with: "/")

return urlProtocol + (bucket ?? "") + host.finished(with: "/")
}

private var urlProtocol: String {
return useTLS ? "https://" : "http://"
}

}


Expand Down
236 changes: 186 additions & 50 deletions Sources/S3Signer/Region.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,200 @@ import Foundation


/// AWS Region
public enum Region: String, Codable {

/// US East (N. Virginia)
case usEast1 = "us-east-1"

/// US East (Ohio)
case usEast2 = "us-east-2"

/// US West (N. California)
case usWest1 = "us-west-1"

/// US West (Oregon)
case usWest2 = "us-west-2"

/// Canada (Central)
case caCentral1 = "ca-central-1"

/// EU (Frankfurt)
case euCentral1 = "eu-central-1"

/// EU (Ireland)
case euWest1 = "eu-west-1"

/// EU (London)
case euWest2 = "eu-west-2"

/// EU (Paris)
case euWest3 = "eu-west-3"

/// Asia Pacific (Tokyo)
case apNortheast1 = "ap-northeast-1"

/// Asia Pacific (Seoul)
case apNortheast2 = "ap-northeast-2"

/// Asia Pacific (Osaka-Local)
case apNortheast3 = "ap-northeast-3"

/// Asia Pacific (Singapore)
case apSoutheast1 = "ap-southeast-1"

/// Asia Pacific (Sydney)
case apSoutheast2 = "ap-southeast-2"

/// Asia Pacific (Mumbai)
case apSouth1 = "ap-south-1"

/// South America (São Paulo)
case saEast1 = "sa-east-1"
public struct Region {

/// name of the region, see RegionName
public let name: RegionName

/// name of the custom host, can contain IP and/or port (e.g. 127.0.0.1:9000)
public let hostName: String?

/// use TLS/https (defaults to true)
public let useTLS: Bool

public enum RegionName : String, Codable {
/// US East (N. Virginia)
case usEast1 = "us-east-1"

/// US East (Ohio)
case usEast2 = "us-east-2"

/// US West (N. California)
case usWest1 = "us-west-1"

/// US West (Oregon)
case usWest2 = "us-west-2"

/// Canada (Central)
case caCentral1 = "ca-central-1"

/// EU (Frankfurt)
case euCentral1 = "eu-central-1"

/// EU (Ireland)
case euWest1 = "eu-west-1"

/// EU (London)
case euWest2 = "eu-west-2"

/// EU (Paris)
case euWest3 = "eu-west-3"

/// Asia Pacific (Tokyo)
case apNortheast1 = "ap-northeast-1"

/// Asia Pacific (Seoul)
case apNortheast2 = "ap-northeast-2"

/// Asia Pacific (Osaka-Local)
case apNortheast3 = "ap-northeast-3"

/// Asia Pacific (Singapore)
case apSoutheast1 = "ap-southeast-1"

/// Asia Pacific (Sydney)
case apSoutheast2 = "ap-southeast-2"

/// Asia Pacific (Mumbai)
case apSouth1 = "ap-south-1"

/// South America (São Paulo)
case saEast1 = "sa-east-1"
}

/// initializer for a (custom) region. If you use a custom hostName, you
/// still need a region (e.g. use usEast1 for Minio)
public init(name: RegionName, hostName: String? = nil, useTLS: Bool = true) {
self.name = name
self.hostName = hostName
self.useTLS = useTLS
}
}


extension Region {

/// Base URL / Host
public var host: String {
return "s3.\(rawValue).amazonaws.com"
if let host = hostName {
return host
}
return "s3.\(name.rawValue).amazonaws.com"
}
}

extension Region {
public init?(rawValue: String) {
guard let name = RegionName(rawValue: rawValue) else {
return nil
}

self.init(name: name)
}

/// convenience var for US East (N. Virginia)
public static var usEast1: Region {
return Region(name: RegionName.usEast1)
}

/// convenience var for US East (Ohio)
public static var usEast2: Region {
return Region(name: RegionName.usEast2)
}

/// convenience var for US West (N. California)
public static var usWest1: Region {
return Region(name: RegionName.usWest1)
}

/// convenience var for US West (Oregon)
public static var usWest2: Region {
return Region(name: RegionName.usWest2)
}

/// convenience var for Canada (Central)
public static var caCentral1: Region {
return Region(name: RegionName.caCentral1)
}

/// convenience var for EU (Frankfurt)
public static var euCentral1: Region {
return Region(name: RegionName.euCentral1)
}

/// convenience var for EU (Ireland)
public static var euWest1: Region {
return Region(name: RegionName.euWest1)
}

/// convenience var for EU (London)
public static var euWest2: Region {
return Region(name: RegionName.euWest2)
}

/// convenience var for EU (Paris)
public static var euWest3: Region {
return Region(name: RegionName.euWest3)
}

/// convenience var for Asia Pacific (Tokyo)
public static var apNortheast1: Region {
return Region(name: RegionName.apNortheast1)
}

/// convenience var for Asia Pacific (Seoul)
public static var apNortheast2: Region {
return Region(name: RegionName.apNortheast2)
}

/// convenience var for Asia Pacific (Osaka-Local)
public static var apNortheast3: Region {
return Region(name: RegionName.apNortheast3)
}

/// convenience var for Asia Pacific (Singapore)
public static var apSoutheast1: Region {
return Region(name: RegionName.apSoutheast1)
}

/// convenience var for Asia Pacific (Sydney)
public static var apSoutheast2: Region {
return Region(name: RegionName.apSoutheast2)
}

/// convenience var for Asia Pacific (Mumbai)
public static var apSouth1: Region {
return Region(name: RegionName.apSouth1)
}

/// convenience var for South America (São Paulo)
public static var saEast1: Region {
return Region(name: RegionName.saEast1)
}
}

/// Codable support for Region
extension Region: Codable {

/// decodes a string (see RegionName) to a Region (does not support custom hosts)
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

let name = try container.decode(String.self)

guard let regionName = RegionName(rawValue: name) else {
throw DecodingError.typeMismatch(String.self, DecodingError.Context(codingPath: [],
debugDescription: "Could not find region for \(name)"))
}

self.name = regionName
self.hostName = nil
self.useTLS = true
}

/// encodes the name (see RegionName, does not support custom hosts)
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.name.rawValue)
}
}
6 changes: 3 additions & 3 deletions Sources/S3Signer/S3Signer+Private.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension S3Signer {

func createSignature(_ stringToSign: String, timeStampShort: String, region: Region) throws -> String {
let dateKey = try HMAC.SHA256.authenticate(timeStampShort.convertToData(), key: "AWS4\(config.secretKey)".convertToData())
let dateRegionKey = try HMAC.SHA256.authenticate(region.rawValue.convertToData(), key: dateKey)
let dateRegionKey = try HMAC.SHA256.authenticate(region.name.rawValue.convertToData(), key: dateKey)
let dateRegionServiceKey = try HMAC.SHA256.authenticate(config.service.convertToData(), key: dateRegionKey)
let signingKey = try HMAC.SHA256.authenticate("aws4_request".convertToData(), key: dateRegionServiceKey)
let signature = try HMAC.SHA256.authenticate(stringToSign.convertToData(), key: signingKey)
Expand All @@ -44,8 +44,8 @@ extension S3Signer {
}

func credentialScope(_ timeStampShort: String, region: Region) -> String {
var arr = [timeStampShort, region.rawValue, config.service, "aws4_request"]
if region == .none {
var arr = [timeStampShort, region.name.rawValue, config.service, "aws4_request"]
if region.name == .none {
arr.remove(at: 1)
}
return arr.joined(separator: "/")
Expand Down

0 comments on commit 82a9c6c

Please sign in to comment.