diff --git a/Swifter.xcodeproj/project.pbxproj b/Swifter.xcodeproj/project.pbxproj index ee7cc384..0c792d34 100644 --- a/Swifter.xcodeproj/project.pbxproj +++ b/Swifter.xcodeproj/project.pbxproj @@ -82,6 +82,9 @@ A1C7F0A51C7845FD00955ADB /* SwifterMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F51131944A61D00894629 /* SwifterMac.framework */; }; A1C7F0A61C7845FD00955ADB /* SwifterMac.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F51131944A61D00894629 /* SwifterMac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A1DD65C51B618D7E0070E6FC /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1DD65C41B618D7E0070E6FC /* Launch Screen.storyboard */; }; + F6B4D5781C7BAC4500E06514 /* SwifterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B4D5771C7BAC4500E06514 /* SwifterTests.swift */; }; + F6B4D57A1C7BAC4500E06514 /* SwifteriOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F50EE1944A5AB00894629 /* SwifteriOS.framework */; }; + F6B4D5811C7BAC5E00E06514 /* String+SwifterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B4D5801C7BAC5E00E06514 /* String+SwifterTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -99,6 +102,13 @@ remoteGlobalIDString = 8B9F51121944A61D00894629; remoteInfo = SwifterMac; }; + F6B4D57B1C7BAC4500E06514 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8B9F50E51944A5AB00894629 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8B9F50ED1944A5AB00894629; + remoteInfo = SwifteriOS; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -175,6 +185,10 @@ 8BF1D6871948C11E00E3AA64 /* SwifterCredential.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterCredential.swift; sourceTree = ""; }; A17A694D1C78333400063DFD /* Operator+Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Operator+Swifter.swift"; sourceTree = ""; }; A1DD65C41B618D7E0070E6FC /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "../Swifter/Launch Screen.storyboard"; sourceTree = ""; }; + F6B4D5751C7BAC4500E06514 /* SwifterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwifterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F6B4D5771C7BAC4500E06514 /* SwifterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwifterTests.swift; sourceTree = ""; }; + F6B4D5791C7BAC4500E06514 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F6B4D5801C7BAC5E00E06514 /* String+SwifterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SwifterTests.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -208,6 +222,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F6B4D5721C7BAC4500E06514 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F6B4D57A1C7BAC4500E06514 /* SwifteriOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -268,6 +290,7 @@ 8B9F51461944A8DC00894629 /* Swifter */, 8B300C201944AA1A00993175 /* SwifterDemoiOS */, 8B300C481944AECB00993175 /* SwifterDemoMac */, + F6B4D5761C7BAC4500E06514 /* SwifterTests */, 8B9F50EF1944A5AB00894629 /* Products */, ); sourceTree = ""; @@ -279,6 +302,7 @@ 8B9F51131944A61D00894629 /* SwifterMac.framework */, 8B300C1F1944AA1A00993175 /* SwifterDemoiOS.app */, 8B300C471944AECB00993175 /* SwifterDemoMac.app */, + F6B4D5751C7BAC4500E06514 /* SwifterTests.xctest */, ); name = Products; sourceTree = ""; @@ -329,6 +353,16 @@ name = Extensions; sourceTree = ""; }; + F6B4D5761C7BAC4500E06514 /* SwifterTests */ = { + isa = PBXGroup; + children = ( + F6B4D5801C7BAC5E00E06514 /* String+SwifterTests.swift */, + F6B4D5771C7BAC4500E06514 /* SwifterTests.swift */, + F6B4D5791C7BAC4500E06514 /* Info.plist */, + ); + path = SwifterTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -427,6 +461,24 @@ productReference = 8B9F51131944A61D00894629 /* SwifterMac.framework */; productType = "com.apple.product-type.framework"; }; + F6B4D5741C7BAC4500E06514 /* SwifterTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F6B4D57F1C7BAC4500E06514 /* Build configuration list for PBXNativeTarget "SwifterTests" */; + buildPhases = ( + F6B4D5711C7BAC4500E06514 /* Sources */, + F6B4D5721C7BAC4500E06514 /* Frameworks */, + F6B4D5731C7BAC4500E06514 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F6B4D57C1C7BAC4500E06514 /* PBXTargetDependency */, + ); + name = SwifterTests; + productName = SwifterTests; + productReference = F6B4D5751C7BAC4500E06514 /* SwifterTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -434,7 +486,7 @@ isa = PBXProject; attributes = { LastSwiftMigration = 0700; - LastSwiftUpdateCheck = 0700; + LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Matt Donnelly"; TargetAttributes = { @@ -450,6 +502,9 @@ 8B9F51121944A61D00894629 = { CreatedOnToolsVersion = 6.0; }; + F6B4D5741C7BAC4500E06514 = { + CreatedOnToolsVersion = 7.2; + }; }; }; buildConfigurationList = 8B9F50E81944A5AB00894629 /* Build configuration list for PBXProject "Swifter" */; @@ -469,6 +524,7 @@ 8B9F50ED1944A5AB00894629 /* SwifteriOS */, 8B9F51121944A61D00894629 /* SwifterMac */, 8B300C461944AECB00993175 /* SwifterDemoMac */, + F6B4D5741C7BAC4500E06514 /* SwifterTests */, ); }; /* End PBXProject section */ @@ -507,6 +563,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F6B4D5731C7BAC4500E06514 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -600,6 +663,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F6B4D5711C7BAC4500E06514 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F6B4D5781C7BAC4500E06514 /* SwifterTests.swift in Sources */, + F6B4D5811C7BAC5E00E06514 /* String+SwifterTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -613,6 +685,11 @@ target = 8B9F51121944A61D00894629 /* SwifterMac */; targetProxy = A1C7F0A71C7845FD00955ADB /* PBXContainerItemProxy */; }; + F6B4D57C1C7BAC4500E06514 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8B9F50ED1944A5AB00894629 /* SwifteriOS */; + targetProxy = F6B4D57B1C7BAC4500E06514 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -866,6 +943,35 @@ }; name = Release; }; + F6B4D57D1C7BAC4500E06514 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = SwifterTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mattdonnelly.SwifteriOS.SwifterTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + F6B4D57E1C7BAC4500E06514 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = SwifterTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.mattdonnelly.SwifteriOS.SwifterTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -914,6 +1020,14 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F6B4D57F1C7BAC4500E06514 /* Build configuration list for PBXNativeTarget "SwifterTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F6B4D57D1C7BAC4500E06514 /* Debug */, + F6B4D57E1C7BAC4500E06514 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; /* End XCConfigurationList section */ }; rootObject = 8B9F50E51944A5AB00894629 /* Project object */; diff --git a/Swifter/Dictionary+Swifter.swift b/Swifter/Dictionary+Swifter.swift index 119e8cfd..f7e659aa 100644 --- a/Swifter/Dictionary+Swifter.swift +++ b/Swifter/Dictionary+Swifter.swift @@ -52,8 +52,8 @@ extension Dictionary { var parts = [String]() for (key, value) in self { - let keyString: String = "\(key)".urlEncodedStringWithEncoding(encoding) - let valueString: String = "\(value)".urlEncodedStringWithEncoding(encoding) + let keyString: String = "\(key)".urlEncodedStringWithEncoding() + let valueString: String = "\(value)".urlEncodedStringWithEncoding() let query: String = "\(keyString)=\(valueString)" parts.append(query) } diff --git a/Swifter/String+Swifter.swift b/Swifter/String+Swifter.swift index fd96f4e0..3cd522e8 100644 --- a/Swifter/String+Swifter.swift +++ b/Swifter/String+Swifter.swift @@ -42,15 +42,12 @@ extension String { } } - func urlEncodedStringWithEncoding(encoding: NSStringEncoding) -> String { - let charactersToBeEscaped = ":/?&=;+!@#$()',*" as CFStringRef - let charactersToLeaveUnescaped = "[]." as CFStringRef + func urlEncodedStringWithEncoding() -> String { + let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet + allowedCharacterSet.removeCharactersInString("\n:#/?@!$&'()*+,;=") + allowedCharacterSet.addCharactersInString("[]") + return self.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet)! - let str = self as NSString - - let result = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, str as CFString, charactersToLeaveUnescaped, charactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)) as NSString - - return result as String } func parametersFromQueryString() -> Dictionary { diff --git a/Swifter/SwifterAppOnlyClient.swift b/Swifter/SwifterAppOnlyClient.swift index ee69b8c7..4ca052c4 100644 --- a/Swifter/SwifterAppOnlyClient.swift +++ b/Swifter/SwifterAppOnlyClient.swift @@ -79,8 +79,8 @@ internal class SwifterAppOnlyClient: SwifterClientProtocol { } class func base64EncodedCredentialsWithKey(key: String, secret: String) -> String { - let encodedKey = key.urlEncodedStringWithEncoding(NSUTF8StringEncoding) - let encodedSecret = secret.urlEncodedStringWithEncoding(NSUTF8StringEncoding) + let encodedKey = key.urlEncodedStringWithEncoding() + let encodedSecret = secret.urlEncodedStringWithEncoding() let bearerTokenCredentials = "\(encodedKey):\(encodedSecret)" if let data = bearerTokenCredentials.dataUsingEncoding(NSUTF8StringEncoding) { return data.base64EncodedStringWithOptions([]) diff --git a/Swifter/SwifterOAuthClient.swift b/Swifter/SwifterOAuthClient.swift index 42b6a214..fffa14a2 100644 --- a/Swifter/SwifterOAuthClient.swift +++ b/Swifter/SwifterOAuthClient.swift @@ -146,9 +146,9 @@ internal class SwifterOAuthClient: SwifterClientProtocol { } func oauthSignatureForMethod(method: HTTPMethodType, url: NSURL, parameters: Dictionary, accessToken token: SwifterCredential.OAuthAccessToken?) -> String { - let tokenSecret: NSString = token?.secret.urlEncodedStringWithEncoding(self.dataEncoding) ?? "" + let tokenSecret: NSString = token?.secret.urlEncodedStringWithEncoding() ?? "" - let encodedConsumerSecret = self.consumerSecret.urlEncodedStringWithEncoding(self.dataEncoding) + let encodedConsumerSecret = self.consumerSecret.urlEncodedStringWithEncoding() let signingKey = "\(encodedConsumerSecret)&\(tokenSecret)" @@ -156,9 +156,9 @@ internal class SwifterOAuthClient: SwifterClientProtocol { parameterComponents.sortInPlace { $0 < $1 } let parameterString = parameterComponents.joinWithSeparator("&") - let encodedParameterString = parameterString.urlEncodedStringWithEncoding(self.dataEncoding) + let encodedParameterString = parameterString.urlEncodedStringWithEncoding() - let encodedURL = url.absoluteString.urlEncodedStringWithEncoding(self.dataEncoding) + let encodedURL = url.absoluteString.urlEncodedStringWithEncoding() let signatureBaseString = "\(method)&\(encodedURL)&\(encodedParameterString)" diff --git a/SwifterTests/Info.plist b/SwifterTests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/SwifterTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/SwifterTests/String+SwifterTests.swift b/SwifterTests/String+SwifterTests.swift new file mode 100644 index 00000000..b3ef4dff --- /dev/null +++ b/SwifterTests/String+SwifterTests.swift @@ -0,0 +1,71 @@ +// +// String+SwifterTests.swift +// Swifter +// +// Created by Lewis Smith on 20/02/2016. +// Copyright © 2016 Matt Donnelly. All rights reserved. +// + +import XCTest + +@testable import SwifteriOS + +class String_SwifterTests: XCTestCase { + + + override func setUp() { + + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testEncoding() { + + let shouldBeTheSameAfterEncoding = [ + "a": "a", + "a a": "a%20a", + ".": ".", + "[": "[", + "]": "]", + "'": "%27", + "$": "%24", + "&": "%26", + "<": "%3C", + ">": "%3E", + "?": "%3F", + ";": "%3B", + "#": "%23", + ":": "%3A", + "=": "%3D", + ",": "%2C", + "\"": "%22", + "+": "%2B", + "%": "%25", + ":/?&=;+!@# ()',*": "%3A%2F%3F%26%3D%3B%2B%21%40%23%20%28%29%27%2C%2A", + "http://a?a=[a,b]": "http%3A%2F%2Fa%3Fa%3D[a%2Cb]" + ] + + let shouldBeDifferentAfterEcoding = [ + "a": "b", + "a a": "a a", + "$": "$" + ] + + for (input, output) in shouldBeTheSameAfterEncoding { + XCTAssertEqual(input.urlEncodedStringWithEncoding(), output, input) + } + + + for (input, output) in shouldBeDifferentAfterEcoding { + XCTAssertNotEqual(input.urlEncodedStringWithEncoding(), output) + } + } + + +} diff --git a/SwifterTests/SwifterTests.swift b/SwifterTests/SwifterTests.swift new file mode 100644 index 00000000..8edd1527 --- /dev/null +++ b/SwifterTests/SwifterTests.swift @@ -0,0 +1,35 @@ +// +// SwifterTests.swift +// SwifterTests +// +// Created by Lewis Smith on 22/02/2016. +// Copyright © 2016 Matt Donnelly. All rights reserved. +// + +import XCTest + +class SwifterTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock { + // Put the code you want to measure the time of here. + } + } + +}