From 066520ae10f040ed646cbab227a87ce4c82598bf Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 14:41:47 +0100 Subject: [PATCH 01/10] Add SHAHasher This contains the hashing logic within the RemotePost Hashing extension alongside hashing for additional types for use outside of RemotePost --- WordPressKit.xcodeproj/project.pbxproj | 6 ++ WordPressKit/SHAHasher.h | 18 ++++++ WordPressKit/SHAHasher.m | 81 ++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 WordPressKit/SHAHasher.h create mode 100644 WordPressKit/SHAHasher.m diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 4fe06e61..0ae78a4f 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 14233D35253F6B51001C9E2E /* SHAHasher.m in Sources */ = {isa = PBXBuildFile; fileRef = 14233D34253F6B51001C9E2E /* SHAHasher.m */; }; 1769DEAA24729AFF00F42EFC /* HomepageSettingsServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */; }; 17BF9A6C20C7DC3300BF57D2 /* reader-site-search-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 17BF9A6B20C7DC3300BF57D2 /* reader-site-search-success.json */; }; 17BF9A7220C7E18200BF57D2 /* reader-site-search-success-hasmore.json in Resources */ = {isa = PBXBuildFile; fileRef = 17BF9A6D20C7E18100BF57D2 /* reader-site-search-success-hasmore.json */; }; @@ -538,6 +539,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 14233D34253F6B51001C9E2E /* SHAHasher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SHAHasher.m; sourceTree = ""; }; + 14233D39253F6B99001C9E2E /* SHAHasher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SHAHasher.h; sourceTree = ""; }; 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageSettingsServiceRemote.swift; sourceTree = ""; }; 17BF9A6B20C7DC3300BF57D2 /* reader-site-search-success.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "reader-site-search-success.json"; sourceTree = ""; }; 17BF9A6D20C7E18100BF57D2 /* reader-site-search-success-hasmore.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reader-site-search-success-hasmore.json"; sourceTree = ""; }; @@ -1881,6 +1884,8 @@ 93C674EF1EE8351E00BFAF05 /* NSMutableDictionary+Helpers.h */, 93C674F01EE8351E00BFAF05 /* NSMutableDictionary+Helpers.m */, 9F4E51FF2088E38200424676 /* ObjectValidation.swift */, + 14233D39253F6B99001C9E2E /* SHAHasher.h */, + 14233D34253F6B51001C9E2E /* SHAHasher.m */, ); name = Utility; sourceTree = ""; @@ -2576,6 +2581,7 @@ 74650F741F0EA1E200188EDB /* RemoteGravatarProfile.swift in Sources */, 40E7FEB4221063480032834E /* StatsTodayInsight.swift in Sources */, 436D563C2118E18D00CEAA33 /* State.swift in Sources */, + 14233D35253F6B51001C9E2E /* SHAHasher.m in Sources */, 439A44DA2107C93000795ED7 /* RemotePlan_ApiVersion1_3.swift in Sources */, 93BD27811EE73944002BB00B /* WordPressOrgXMLRPCApi.swift in Sources */, 439A44D62107C66A00795ED7 /* JSONDecoderExtension.swift in Sources */, diff --git a/WordPressKit/SHAHasher.h b/WordPressKit/SHAHasher.h new file mode 100644 index 00000000..fe71ba0e --- /dev/null +++ b/WordPressKit/SHAHasher.h @@ -0,0 +1,18 @@ +// +// SHAHasher.h +// WordPressKit +// +// Created by Declan McKenna on 20/10/2020. +// Copyright © 2020 Automattic Inc. All rights reserved. +// +#import + +@interface SHAHasher : NSObject ++ (NSString *)combineHashes:(NSArray*) hashArray; ++ (NSData *)hashForStringArray:(NSArray *) array; ++ (NSData *)hashForString:(NSString *) string; ++ (NSData *)hashForNSInteger:(NSInteger)integer; ++ (NSData *)hashForDouble:(double)dbl; ++ (NSData *)hashForBool:(BOOL)boolean; ++ (NSString *)sha256StringFromData:(NSData *)data; +@end diff --git a/WordPressKit/SHAHasher.m b/WordPressKit/SHAHasher.m new file mode 100644 index 00000000..8cfe4b32 --- /dev/null +++ b/WordPressKit/SHAHasher.m @@ -0,0 +1,81 @@ +// +// SHAHasher.m +// WordPressKit +// +// Created by Declan McKenna on 20/10/2020. +// Copyright © 2020 Automattic Inc. All rights reserved. +// +#import "SHAHasher.h" +#import +#import +@implementation SHAHasher + ++ (NSString *)combineHashes:(NSArray*) hashArray +{ + NSMutableData *mutableData = [NSMutableData data]; + for (NSData *hash in hashArray) { + [mutableData appendData:hash]; + } + + unsigned char finalDigest[CC_SHA256_DIGEST_LENGTH]; + + CC_SHA256(mutableData.bytes, (CC_LONG)mutableData.length, finalDigest); + + return [self sha256StringFromData:[NSData dataWithBytes:finalDigest length:CC_SHA256_DIGEST_LENGTH]]; +} + ++ (NSData *)hashForStringArray:(NSArray *) array { + NSString *joinedArrayString = [array componentsJoinedByString:@""]; + return [self hashForString:joinedArrayString]; +} + ++ (NSData *)hashForString:(NSString *) string { + if (!string) { + return [[NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH] copy]; + } + + NSData *encodedBytes = [string dataUsingEncoding:NSUTF8StringEncoding]; + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + + CC_SHA256(encodedBytes.bytes, (CC_LONG)encodedBytes.length, digest); + + return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; +} + ++ (NSData *)hashForNSInteger:(NSInteger)integer { + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + + CC_SHA256(&integer, sizeof(integer), digest); + + return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; +} + ++ (NSData *)hashForDouble:(double)dbl { + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + + CC_SHA256(&dbl, sizeof(dbl), digest); + + return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; +} + ++ (NSData *)hashForBool:(BOOL)boolean { + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + + CC_SHA256(&boolean, sizeof(boolean), digest); + + return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; +} + ++ (NSString *)sha256StringFromData:(NSData *)data { + NSMutableString *mutableString = [NSMutableString string]; + + const char *hashBytes = [data bytes]; + + for (int i = 0; i < data.length; i++) { + [mutableString appendFormat:@"%02.2hhx", hashBytes[i]]; + } + + return [mutableString copy]; +} + +@end From 7b38d496d093ebfd7d03eca8e002d84533209980 Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 14:55:56 +0100 Subject: [PATCH 02/10] Add contentHash to RemotePost --- WordPressKit/RemotePost.h | 1 + WordPressKit/RemotePost.m | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/WordPressKit/RemotePost.h b/WordPressKit/RemotePost.h index d4fa0ac2..e002ce38 100644 --- a/WordPressKit/RemotePost.h +++ b/WordPressKit/RemotePost.h @@ -11,6 +11,7 @@ extern NSString * const PostStatusDeleted; @interface RemotePost : NSObject - (id)initWithSiteID:(NSNumber *)siteID status:(NSString *)status title:(NSString *)title content:(NSString *)content; +- (NSString *)contentHash; @property (nonatomic, strong) NSNumber *postID; @property (nonatomic, strong) NSNumber *siteID; diff --git a/WordPressKit/RemotePost.m b/WordPressKit/RemotePost.m index ef7960fe..361c38df 100644 --- a/WordPressKit/RemotePost.m +++ b/WordPressKit/RemotePost.m @@ -1,5 +1,6 @@ #import "RemotePost.h" #import +#import "SHAHasher.h" NSString * const PostStatusDraft = @"draft"; NSString * const PostStatusPending = @"pending"; @@ -47,4 +48,41 @@ - (NSDictionary *)debugProperties { return [NSDictionary dictionaryWithDictionary:debugProperties]; } +/// A hash used to determine if the remote content has changed. +/// +/// This hash must remain constant regardless of iOS version, app restarts or instances used +- (NSString *)contentHash +{ + NSString *hashedContents = [NSString stringWithFormat:@"%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@", + self.postID.stringValue, + self.siteID.stringValue, + self.authorAvatarURL, + self.authorDisplayName, + self.authorEmail, + self.authorURL, + self.authorID.stringValue, + self.date.description, + self.title, + self.URL.absoluteString, + self.shortURL.absoluteString, + self.content, + self.excerpt, + self.slug, + self.suggestedSlug, + self.status, + self.parentID.stringValue, + self.postThumbnailID.stringValue, + self.postThumbnailPath, + self.type, + self.format, + self.commentCount.stringValue, + self.likeCount.stringValue, + [self.tags componentsJoinedByString:@""], + self.pathForDisplayImage, + self.isStickyPost.stringValue, + self.isFeaturedImageChanged ? @"1" : @"2"]; + NSData *hashData = [SHAHasher hashForString:hashedContents]; + return [SHAHasher sha256StringFromData:hashData]; +} + @end From 240a52466c6ed6f67e32efdd790a3d549752580c Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 14:57:34 +0100 Subject: [PATCH 03/10] Add RemotePost Hashing tests --- WordPressKit.xcodeproj/project.pbxproj | 4 ++ WordPressKitTests/RemotePostTests.swift | 82 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 WordPressKitTests/RemotePostTests.swift diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 0ae78a4f..52177d64 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 14233D0F253DAEF7001C9E2E /* RemotePostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14233D0E253DAEF7001C9E2E /* RemotePostTests.swift */; }; 14233D35253F6B51001C9E2E /* SHAHasher.m in Sources */ = {isa = PBXBuildFile; fileRef = 14233D34253F6B51001C9E2E /* SHAHasher.m */; }; 1769DEAA24729AFF00F42EFC /* HomepageSettingsServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */; }; 17BF9A6C20C7DC3300BF57D2 /* reader-site-search-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 17BF9A6B20C7DC3300BF57D2 /* reader-site-search-success.json */; }; @@ -539,6 +540,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 14233D0E253DAEF7001C9E2E /* RemotePostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePostTests.swift; sourceTree = ""; }; 14233D34253F6B51001C9E2E /* SHAHasher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SHAHasher.m; sourceTree = ""; }; 14233D39253F6B99001C9E2E /* SHAHasher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SHAHasher.h; sourceTree = ""; }; 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageSettingsServiceRemote.swift; sourceTree = ""; }; @@ -1168,6 +1170,7 @@ 9AB6D649218727D60008F274 /* PostServiceRemoteRESTRevisionsTest.swift */, 740B23D01F17F6BB00067A2A /* PostServiceRemoteRESTTests.m */, 740B23D11F17F6BB00067A2A /* PostServiceRemoteXMLRPCTests.swift */, + 14233D0E253DAEF7001C9E2E /* RemotePostTests.swift */, ); name = Post; sourceTree = ""; @@ -2660,6 +2663,7 @@ 93188D221F2264E60028ED4D /* TaxonomyServiceRemoteRESTTests.m in Sources */, F194E1232417ED9F00874408 /* AtomicAuthenticationServiceRemoteTests.swift in Sources */, 74FC6F3B1F191BB400112505 /* NotificationSyncServiceRemoteTests.swift in Sources */, + 14233D0F253DAEF7001C9E2E /* RemotePostTests.swift in Sources */, 731BA83821DECD97000FDFCD /* SiteCreationResponseDecodingTests.swift in Sources */, 9A2D0B2B225E0E22009E585F /* JetpackServiceRemoteTests.swift in Sources */, 74FA25F71F1FDA200044BC54 /* MediaServiceRemoteRESTTests.swift in Sources */, diff --git a/WordPressKitTests/RemotePostTests.swift b/WordPressKitTests/RemotePostTests.swift new file mode 100644 index 00000000..df7ff606 --- /dev/null +++ b/WordPressKitTests/RemotePostTests.swift @@ -0,0 +1,82 @@ +// +// RemotePostTests.swift +// WordPressKitTests +// +// Created by Declan McKenna on 19/10/2020. +// Copyright © 2020 Automattic Inc. All rights reserved. +// + +import XCTest +@testable import WordPressKit + +class RemotePostTests: XCTestCase { + + func testHashWithNilValues() { + XCTAssertEqual(RemotePost().contentHash(), RemotePost().contentHash()) + } + + func testHashWithNilValuesIsPersistent() { + let expectedHash = "72bcdd41f59ecd51f66ada667342a04765ff8f17997a4d48ea708e6eabbf5580" + XCTAssertEqual(RemotePost().contentHash(), expectedHash) + } + + func testRemotePostHashIsSameForSameContent() { + let post = noNullPropertyPost + let identicalPost = noNullPropertyPost + XCTAssertEqual(post.contentHash(), identicalPost.contentHash()) + } + + func testRemotePostHashDiffersForDifferentContent() { + let post = noNullPropertyPost + let modifiedPost = noNullPropertyPost + modifiedPost.tags.append("new tag") + XCTAssertNotEqual(post.contentHash(), modifiedPost.contentHash()) + } + + func testRemotePostHashIsPersistent() { + let post = noNullPropertyPost + let expectedHash = "729a3df7c916699c5efb548dc4f53f43dec11d5516dd63ff6787c81904d464f1" + XCTAssertEqual(post.contentHash(), expectedHash) + } + + func testHashSpeed() { + measure { + noNullPropertyPost.contentHash() + } + } +} + +private extension RemotePostTests { + /// Remote post with all properties set to non null to ensure hash is consistent for all fields + var noNullPropertyPost: RemotePost { + let remotePost = RemotePost() + remotePost.postID = 90210 + remotePost.siteID = 101 + remotePost.authorAvatarURL = "www.test.com" + remotePost.authorDisplayName = "jk" + remotePost.authorEmail = "omg@somuchtestdata.com" + remotePost.authorURL = "swiftdec.com" + remotePost.authorID = 9001 + remotePost.date = Date(timeIntervalSince1970: 0) + remotePost.title = "Lorem Ipsum" + remotePost.url = URL(string: "lemonparty.com") + remotePost.shortURL = URL(string: "www.why.com") + remotePost.content = "Dolor Sit Amet" + remotePost.excerpt = "...." + remotePost.slug = "lorem-ipsum" + remotePost.suggestedSlug = "~!!" + remotePost.status = "draft" + remotePost.parentID = 42 + remotePost.postThumbnailID = 420 + remotePost.postThumbnailPath = "Arakis" + remotePost.type = "" + remotePost.format = "" + remotePost.commentCount = 666 + remotePost.likeCount = 555 + remotePost.tags = ["lorem,ipsum,test"] + remotePost.pathForDisplayImage = "!.com" + remotePost.isStickyPost = true + remotePost.isFeaturedImageChanged = false + return remotePost + } +} From a3596bb70c1edb6c61e65dfe572314abc10ed1c9 Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 15:05:42 +0100 Subject: [PATCH 04/10] Add more detail to contentHash documentation --- WordPressKit/RemotePost.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPressKit/RemotePost.m b/WordPressKit/RemotePost.m index 361c38df..810fa183 100644 --- a/WordPressKit/RemotePost.m +++ b/WordPressKit/RemotePost.m @@ -50,7 +50,9 @@ - (NSDictionary *)debugProperties { /// A hash used to determine if the remote content has changed. /// -/// This hash must remain constant regardless of iOS version, app restarts or instances used +/// This hash must remain constant regardless of iOS version, app restarts or instances used. `Hasher` or NSObject's `hash` were not used for these reasons. +/// +/// - Note: `dateModified` is not included within the hash as it is prone to change wihout the content having been changed and is the reason this hash is necessary. - (NSString *)contentHash { NSString *hashedContents = [NSString stringWithFormat:@"%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@", From 9269015eb216e689fe334c060d887733ad4c5a3f Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 15:40:07 +0100 Subject: [PATCH 05/10] Remove hash performance test The test is too slow to go in the existing test suite. --- WordPressKitTests/RemotePostTests.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/WordPressKitTests/RemotePostTests.swift b/WordPressKitTests/RemotePostTests.swift index df7ff606..55fa0bea 100644 --- a/WordPressKitTests/RemotePostTests.swift +++ b/WordPressKitTests/RemotePostTests.swift @@ -38,12 +38,6 @@ class RemotePostTests: XCTestCase { let expectedHash = "729a3df7c916699c5efb548dc4f53f43dec11d5516dd63ff6787c81904d464f1" XCTAssertEqual(post.contentHash(), expectedHash) } - - func testHashSpeed() { - measure { - noNullPropertyPost.contentHash() - } - } } private extension RemotePostTests { From cc6ada5a7d39f78fcd8511126b5e666b8bd45f59 Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 19:58:14 +0100 Subject: [PATCH 06/10] Add SHAHasher to WordPressKit framework header --- WordPressKit.xcodeproj/project.pbxproj | 2 ++ WordPressKit/WordPressKit.h | 1 + 2 files changed, 3 insertions(+) diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 52177d64..3fdf04e6 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 14233D0F253DAEF7001C9E2E /* RemotePostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14233D0E253DAEF7001C9E2E /* RemotePostTests.swift */; }; 14233D35253F6B51001C9E2E /* SHAHasher.m in Sources */ = {isa = PBXBuildFile; fileRef = 14233D34253F6B51001C9E2E /* SHAHasher.m */; }; + 14C42DBD2545EE3B0091712B /* SHAHasher.h in Headers */ = {isa = PBXBuildFile; fileRef = 14233D39253F6B99001C9E2E /* SHAHasher.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1769DEAA24729AFF00F42EFC /* HomepageSettingsServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */; }; 17BF9A6C20C7DC3300BF57D2 /* reader-site-search-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 17BF9A6B20C7DC3300BF57D2 /* reader-site-search-success.json */; }; 17BF9A7220C7E18200BF57D2 /* reader-site-search-success-hasmore.json in Resources */ = {isa = PBXBuildFile; fileRef = 17BF9A6D20C7E18100BF57D2 /* reader-site-search-success-hasmore.json */; }; @@ -2026,6 +2027,7 @@ 742362E01F1025B400BD0A7F /* RemoteMenuItem.h in Headers */, 7430C9B51F1927C50051B8E6 /* RemoteReaderSiteInfo.h in Headers */, 7430C9A71F1927180051B8E6 /* ReaderTopicServiceRemote.h in Headers */, + 14C42DBD2545EE3B0091712B /* SHAHasher.h in Headers */, 7430C9B71F1927C50051B8E6 /* RemoteReaderTopic.h in Headers */, 7430C9B31F1927C50051B8E6 /* RemoteReaderSite.h in Headers */, 7430C9B11F1927C50051B8E6 /* RemoteReaderPost.h in Headers */, diff --git a/WordPressKit/WordPressKit.h b/WordPressKit/WordPressKit.h index 1a334e47..d634ca0f 100644 --- a/WordPressKit/WordPressKit.h +++ b/WordPressKit/WordPressKit.h @@ -57,3 +57,4 @@ FOUNDATION_EXPORT const unsigned char WordPressKitVersionString[]; #import #import +#import From f795a2782edd136353ef2c9c0dd019952c8ec443 Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 19:59:19 +0100 Subject: [PATCH 07/10] Test autosaves don't alter hash --- WordPressKitTests/RemotePostTests.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WordPressKitTests/RemotePostTests.swift b/WordPressKitTests/RemotePostTests.swift index 55fa0bea..8f7f2ee7 100644 --- a/WordPressKitTests/RemotePostTests.swift +++ b/WordPressKitTests/RemotePostTests.swift @@ -38,6 +38,14 @@ class RemotePostTests: XCTestCase { let expectedHash = "729a3df7c916699c5efb548dc4f53f43dec11d5516dd63ff6787c81904d464f1" XCTAssertEqual(post.contentHash(), expectedHash) } + + func testAutosaveDoesntAlterHash() { + let post = RemotePost() + let hash = RemotePost().contentHash() + post.autosave = RemotePostAutosave() + let autosavedPostHash = post.contentHash() + XCTAssertEqual(hash, autosavedPostHash) + } } private extension RemotePostTests { From ca291e02dd816e997771d7a888bd7f9d902ab006 Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 20:06:23 +0100 Subject: [PATCH 08/10] Add documentation on hash exclusions & exceptions --- WordPressKit/RemotePost.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPressKit/RemotePost.m b/WordPressKit/RemotePost.m index 810fa183..f29cdcb2 100644 --- a/WordPressKit/RemotePost.m +++ b/WordPressKit/RemotePost.m @@ -52,7 +52,12 @@ - (NSDictionary *)debugProperties { /// /// This hash must remain constant regardless of iOS version, app restarts or instances used. `Hasher` or NSObject's `hash` were not used for these reasons. /// -/// - Note: `dateModified` is not included within the hash as it is prone to change wihout the content having been changed and is the reason this hash is necessary. +/// `dateModified` is not included within the hash as it is prone to change wihout the content having been changed and is the reason this hash is necessary. +/// +/// `autosave` properties are intentionally omitted as remote autosaves are always discarded in favor of local autosaves (aka `revision`s) +/// +/// - Note: At the time of writing the backend will occasionally create updates that neither come from autosaves or user initiated saves, these will modify the hash +/// and at present are treated as genuine updates as they are triggered by the user changing their posts content. - (NSString *)contentHash { NSString *hashedContents = [NSString stringWithFormat:@"%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@%@/%@/%@", From 7dc25bbdbdd22dfe2772ac38da2c058087ce5f9b Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Sun, 25 Oct 2020 20:43:50 +0100 Subject: [PATCH 09/10] Bump pod version number --- WordPressKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPressKit.podspec b/WordPressKit.podspec index 48a0a123..f1cbfadf 100644 --- a/WordPressKit.podspec +++ b/WordPressKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "WordPressKit" - s.version = "4.19-beta.2" + s.version = "4.19-beta.3" s.summary = "WordPressKit offers a clean and simple WordPress.com and WordPress.org API." s.description = <<-DESC From 428ba3cb2a6d2814f27ba5429597aacb42573a15 Mon Sep 17 00:00:00 2001 From: Declan McKenna Date: Fri, 30 Oct 2020 20:51:26 +0100 Subject: [PATCH 10/10] Rename sha256StringFromData This doesn't actually do any sha256 hashing --- WordPressKit/RemotePost.m | 2 +- WordPressKit/SHAHasher.h | 2 +- WordPressKit/SHAHasher.m | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPressKit/RemotePost.m b/WordPressKit/RemotePost.m index f29cdcb2..2a57140d 100644 --- a/WordPressKit/RemotePost.m +++ b/WordPressKit/RemotePost.m @@ -89,7 +89,7 @@ - (NSString *)contentHash self.isStickyPost.stringValue, self.isFeaturedImageChanged ? @"1" : @"2"]; NSData *hashData = [SHAHasher hashForString:hashedContents]; - return [SHAHasher sha256StringFromData:hashData]; + return [SHAHasher hexStringFromData:hashData]; } @end diff --git a/WordPressKit/SHAHasher.h b/WordPressKit/SHAHasher.h index fe71ba0e..f624ee6c 100644 --- a/WordPressKit/SHAHasher.h +++ b/WordPressKit/SHAHasher.h @@ -14,5 +14,5 @@ + (NSData *)hashForNSInteger:(NSInteger)integer; + (NSData *)hashForDouble:(double)dbl; + (NSData *)hashForBool:(BOOL)boolean; -+ (NSString *)sha256StringFromData:(NSData *)data; ++ (NSString *)hexStringFromData:(NSData *)data; @end diff --git a/WordPressKit/SHAHasher.m b/WordPressKit/SHAHasher.m index 8cfe4b32..6a8617b5 100644 --- a/WordPressKit/SHAHasher.m +++ b/WordPressKit/SHAHasher.m @@ -21,7 +21,7 @@ + (NSString *)combineHashes:(NSArray*) hashArray CC_SHA256(mutableData.bytes, (CC_LONG)mutableData.length, finalDigest); - return [self sha256StringFromData:[NSData dataWithBytes:finalDigest length:CC_SHA256_DIGEST_LENGTH]]; + return [self hexStringFromData:[NSData dataWithBytes:finalDigest length:CC_SHA256_DIGEST_LENGTH]]; } + (NSData *)hashForStringArray:(NSArray *) array { @@ -66,7 +66,7 @@ + (NSData *)hashForBool:(BOOL)boolean { return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; } -+ (NSString *)sha256StringFromData:(NSData *)data { ++ (NSString *)hexStringFromData:(NSData *)data { NSMutableString *mutableString = [NSMutableString string]; const char *hashBytes = [data bytes];