diff --git a/Package.resolved b/Package.resolved index 04619ad..8b9264d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -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" } }, { @@ -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" } }, { diff --git a/Sources/S3/Extensions/S3+Bucket.swift b/Sources/S3/Extensions/S3+Bucket.swift index f09c7cb..ac78654 100644 --- a/Sources/S3/Extensions/S3+Bucket.swift +++ b/Sources/S3/Extensions/S3+Bucket.swift @@ -74,7 +74,7 @@ public extension S3 { let content = """ - \(region.rawValue) + \(region.name.rawValue) """ diff --git a/Sources/S3/Extensions/S3+List.swift b/Sources/S3/Extensions/S3+List.swift index 7386a81..51be8e8 100644 --- a/Sources/S3/Extensions/S3+List.swift +++ b/Sources/S3/Extensions/S3+List.swift @@ -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 { 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 } diff --git a/Sources/S3/URLBuilder/URLBuilder.swift b/Sources/S3/URLBuilder/URLBuilder.swift index 49a6390..67dfea5 100644 --- a/Sources/S3/URLBuilder/URLBuilder.swift +++ b/Sources/S3/URLBuilder/URLBuilder.swift @@ -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://" + } + } diff --git a/Sources/S3Signer/Region.swift b/Sources/S3Signer/Region.swift index 46a3aff..a32ac1f 100755 --- a/Sources/S3Signer/Region.swift +++ b/Sources/S3Signer/Region.swift @@ -2,56 +2,74 @@ 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 + } } @@ -59,7 +77,125 @@ 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) + } } diff --git a/Sources/S3Signer/S3Signer+Private.swift b/Sources/S3Signer/S3Signer+Private.swift index dd696a9..67a3798 100755 --- a/Sources/S3Signer/S3Signer+Private.swift +++ b/Sources/S3Signer/S3Signer+Private.swift @@ -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) @@ -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: "/")