From 60e2c6842cc89327293e16ea1fa18bf544a4a498 Mon Sep 17 00:00:00 2001 From: Nikita Ermolenko Date: Sat, 9 Mar 2019 14:40:39 +0300 Subject: [PATCH] Fix crash related to an invalid nested json (by keypath) --- CodableAlamofire.xcodeproj/project.pbxproj | 24 +++++++++++++ .../AlamofireDecodableError.swift | 3 ++ .../DataRequest+Decodable.swift | 3 ++ Tests/CodableAlamofireTests/MainTests.swift | 34 +++++++++++++++---- array.json => Tests/Mock/array.json | 0 Tests/Mock/emptyKeypath.json | 3 ++ .../Mock/keypathArray.json | 0 .../Mock/keypathObject.json | 0 object.json => Tests/Mock/object.json | 0 9 files changed, 60 insertions(+), 7 deletions(-) rename array.json => Tests/Mock/array.json (100%) create mode 100644 Tests/Mock/emptyKeypath.json rename keypathArray.json => Tests/Mock/keypathArray.json (100%) rename keypathObject.json => Tests/Mock/keypathObject.json (100%) rename object.json => Tests/Mock/object.json (100%) diff --git a/CodableAlamofire.xcodeproj/project.pbxproj b/CodableAlamofire.xcodeproj/project.pbxproj index bde1fac..f4d8462 100644 --- a/CodableAlamofire.xcodeproj/project.pbxproj +++ b/CodableAlamofire.xcodeproj/project.pbxproj @@ -25,6 +25,10 @@ 8472D2F91EED3C5F00E81232 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8472D2F71EED3C5400E81232 /* Alamofire.framework */; }; 8472D2FA1EED3C6300E81232 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8472D2F31EED3C5400E81232 /* Alamofire.framework */; }; 8472D2FB1EED3C6A00E81232 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8472D2EF1EED3C5400E81232 /* Alamofire.framework */; }; + 84A389C52233CCC200A0A66A /* keypathArray.json in Resources */ = {isa = PBXBuildFile; fileRef = 84A389BA2233CCC200A0A66A /* keypathArray.json */; }; + 84A389C62233CCC200A0A66A /* keypathObject.json in Resources */ = {isa = PBXBuildFile; fileRef = 84A389BB2233CCC200A0A66A /* keypathObject.json */; }; + 84A389C72233CCC200A0A66A /* object.json in Resources */ = {isa = PBXBuildFile; fileRef = 84A389BC2233CCC200A0A66A /* object.json */; }; + 84A389CB2233D9F500A0A66A /* emptyKeypath.json in Resources */ = {isa = PBXBuildFile; fileRef = 84A389CA2233D9F500A0A66A /* emptyKeypath.json */; }; 84A8C8501EED2F2700AB31BE /* CodableAlamofire.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8C84D1EED2F0C00AB31BE /* CodableAlamofire.h */; settings = {ATTRIBUTES = (Public, ); }; }; 84A8C8511EED2F2700AB31BE /* CodableAlamofire.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8C84D1EED2F0C00AB31BE /* CodableAlamofire.h */; settings = {ATTRIBUTES = (Public, ); }; }; 84A8C8521EED2F2800AB31BE /* CodableAlamofire.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8C84D1EED2F0C00AB31BE /* CodableAlamofire.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -119,6 +123,10 @@ 8472D2BB1EED3B8C00E81232 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "Carthage/Checkouts/Alamofire/build/Debug-appletvos/Alamofire.framework"; sourceTree = ""; }; 8472D2DE1EED3C4E00E81232 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "Carthage/Checkouts/Alamofire/build/Debug-appletvos/Alamofire.framework"; sourceTree = ""; }; 8472D2DF1EED3C5400E81232 /* Alamofire.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Alamofire.xcodeproj; path = Carthage/Checkouts/Alamofire/Alamofire.xcodeproj; sourceTree = ""; }; + 84A389BA2233CCC200A0A66A /* keypathArray.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = keypathArray.json; sourceTree = ""; }; + 84A389BB2233CCC200A0A66A /* keypathObject.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = keypathObject.json; sourceTree = ""; }; + 84A389BC2233CCC200A0A66A /* object.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = object.json; sourceTree = ""; }; + 84A389CA2233D9F500A0A66A /* emptyKeypath.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = emptyKeypath.json; sourceTree = ""; }; 84A8C7FB1EED2E8600AB31BE /* CodableAlamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CodableAlamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84A8C81A1EED2EB800AB31BE /* CodableAlamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CodableAlamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84A8C8271EED2EC300AB31BE /* CodableAlamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CodableAlamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -200,6 +208,7 @@ children = ( 8447D5CA1F77D54E003B32F3 /* LinuxMain.swift */, 8447D5D21F77D5BC003B32F3 /* MainTests.swift */, + 84A389B92233CCC200A0A66A /* Mock */, 8447D5D31F77D5E2003B32F3 /* Supporting Files */, ); path = Tests; @@ -231,6 +240,17 @@ name = Products; sourceTree = ""; }; + 84A389B92233CCC200A0A66A /* Mock */ = { + isa = PBXGroup; + children = ( + 84A389BA2233CCC200A0A66A /* keypathArray.json */, + 84A389BB2233CCC200A0A66A /* keypathObject.json */, + 84A389BC2233CCC200A0A66A /* object.json */, + 84A389CA2233D9F500A0A66A /* emptyKeypath.json */, + ); + path = Mock; + sourceTree = ""; + }; 84A8C7EF1EED2E4D00AB31BE = { isa = PBXGroup; children = ( @@ -588,6 +608,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 84A389C72233CCC200A0A66A /* object.json in Resources */, + 84A389C52233CCC200A0A66A /* keypathArray.json in Resources */, + 84A389CB2233D9F500A0A66A /* emptyKeypath.json in Resources */, + 84A389C62233CCC200A0A66A /* keypathObject.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/CodableAlamofire/AlamofireDecodableError.swift b/Sources/CodableAlamofire/AlamofireDecodableError.swift index 994d5c5..b0eee2c 100644 --- a/Sources/CodableAlamofire/AlamofireDecodableError.swift +++ b/Sources/CodableAlamofire/AlamofireDecodableError.swift @@ -11,10 +11,12 @@ import Foundation /// /// - invalidKeyPath: Returned when a nested dictionary object doesn't exist by special keyPath. /// - emptyKeyPath: Returned when a keyPath is empty. +/// - invalidJSON: Returned when a nested json is invalid. public enum AlamofireDecodableError: Error { case invalidKeyPath case emptyKeyPath + case invalidJSON } extension AlamofireDecodableError: LocalizedError { @@ -23,6 +25,7 @@ extension AlamofireDecodableError: LocalizedError { switch self { case .invalidKeyPath: return "Nested object doesn't exist by this keyPath." case .emptyKeyPath: return "KeyPath can not be empty." + case .invalidJSON: return "Invalid nested json." } } } diff --git a/Sources/CodableAlamofire/DataRequest+Decodable.swift b/Sources/CodableAlamofire/DataRequest+Decodable.swift index 17b3df5..ead6862 100644 --- a/Sources/CodableAlamofire/DataRequest+Decodable.swift +++ b/Sources/CodableAlamofire/DataRequest+Decodable.swift @@ -49,6 +49,9 @@ extension DataRequest { case .success(let json): if let nestedJson = (json as AnyObject).value(forKeyPath: keyPath) { do { + guard JSONSerialization.isValidJSONObject(nestedJson) else { + return .failure(AlamofireDecodableError.invalidJSON) + } let data = try JSONSerialization.data(withJSONObject: nestedJson) let object = try decoder.decode(T.self, from: data) return .success(object) diff --git a/Tests/CodableAlamofireTests/MainTests.swift b/Tests/CodableAlamofireTests/MainTests.swift index f889093..4566bce 100644 --- a/Tests/CodableAlamofireTests/MainTests.swift +++ b/Tests/CodableAlamofireTests/MainTests.swift @@ -40,7 +40,7 @@ final class MainTests: XCTestCase { ] func testResponseSimpleObject() { - let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/object.json")! + let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/Tests/Mock/object.json")! let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") let decoder = JSONDecoder() @@ -64,7 +64,7 @@ final class MainTests: XCTestCase { } func testResponseArrayOfObjects() { - let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/array.json")! + let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/Tests/Mock/array.json")! let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") let decoder = JSONDecoder() @@ -94,7 +94,7 @@ final class MainTests: XCTestCase { } func testResponseArrayOfObjectsByKeyPath() { - let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/keypathArray.json")! + let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/Tests/Mock/keypathArray.json")! let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") let decoder = JSONDecoder() @@ -124,7 +124,7 @@ final class MainTests: XCTestCase { } func testResponseWithEmptyKeyPath() { - let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/keypathArray.json")! + let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/Tests/Mock/keypathArray.json")! let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") Alamofire.request(url).responseDecodableObject(keyPath: "") { (response: DataResponse<[Repo]>) in @@ -144,7 +144,7 @@ final class MainTests: XCTestCase { } func testResponseWithInvalidKeyPath() { - let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/keypathArray.json")! + let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/Tests/Mock/keypathArray.json")! let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") Alamofire.request(url).responseDecodableObject(keyPath: "keypath") { (response: DataResponse<[Repo]>) in @@ -164,7 +164,7 @@ final class MainTests: XCTestCase { } func testResponseWithWrongPropertyKeyObject() { - let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/object.json")! + let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/Tests/Mock/object.json")! let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") Alamofire.request(url).responseDecodableObject { (response: DataResponse) in @@ -184,7 +184,7 @@ final class MainTests: XCTestCase { } func testResponseWithWrongPropertyKeyObjectByKeyPath() { - let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/keypathObject.json")! + let url = URL(string: "https://raw.githubusercontent.com/otbivnoe/CodableAlamofire/master/Tests/Mock/keypathObject.json")! let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") Alamofire.request(url).responseDecodableObject(keyPath: "result.library") { (response: DataResponse) in @@ -202,4 +202,24 @@ final class MainTests: XCTestCase { XCTAssertNil(error) } } + + func testResponseWithInvalidNestedJSONByKeypath() { + let url = URL(string: "https://raw.githubusercontent.com/Otbivnoe/CodableAlamofire/master/Tests/Mock/emptyKeypath.json")! + let expectation = self.expectation(description: "Reponse from \(url.absoluteString)") + + Alamofire.request(url).responseDecodableObject(keyPath: "data") { (response: DataResponse) in + XCTAssertNotNil(response.error) + if case AlamofireDecodableError.invalidJSON = response.error! { + XCTAssertTrue(true) + } + else { + XCTAssertTrue(false) + } + expectation.fulfill() + } + + waitForExpectations(timeout: 10) { error in + XCTAssertNil(error) + } + } } diff --git a/array.json b/Tests/Mock/array.json similarity index 100% rename from array.json rename to Tests/Mock/array.json diff --git a/Tests/Mock/emptyKeypath.json b/Tests/Mock/emptyKeypath.json new file mode 100644 index 0000000..04ba24b --- /dev/null +++ b/Tests/Mock/emptyKeypath.json @@ -0,0 +1,3 @@ +{ + "data": null +} diff --git a/keypathArray.json b/Tests/Mock/keypathArray.json similarity index 100% rename from keypathArray.json rename to Tests/Mock/keypathArray.json diff --git a/keypathObject.json b/Tests/Mock/keypathObject.json similarity index 100% rename from keypathObject.json rename to Tests/Mock/keypathObject.json diff --git a/object.json b/Tests/Mock/object.json similarity index 100% rename from object.json rename to Tests/Mock/object.json