2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #import <Foundation/Foundation.h>
25 #import "CKKSSQLDatabaseObject.h"
26 #include <securityd/SecItemServer.h>
28 #import "CKKSKeychainView.h"
30 @implementation CKKSSQLDatabaseObject
32 + (bool) saveToDatabaseTable: (NSString*) table row: (NSDictionary*) row connection: (SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error {
33 __block CFErrorRef cferror = NULL;
35 bool (^doWithConnection)(SecDbConnectionRef) = ^bool (SecDbConnectionRef dbconn) {
36 NSString * columns = [row.allKeys componentsJoinedByString:@", "];
37 NSMutableString * values = [[NSMutableString alloc] init];
38 for(NSUInteger i = 0; i < [row.allKeys count]; i++) {
40 [values appendString: @",?"];
42 [values appendString: @"?"];
46 NSString *sql = [[NSString alloc] initWithFormat: @"INSERT OR REPLACE into %@ (%@) VALUES (%@);", table, columns, values];
48 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
49 [row.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) {
50 SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) row[key], &cferror);
53 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
54 // don't do anything, I guess?
62 doWithConnection(dbconn);
64 kc_with_dbt(true, &cferror, doWithConnection);
67 bool ret = cferror == NULL;
69 SecTranslateError(error, cferror);
74 + (NSString*) makeWhereClause: (NSDictionary*) whereDict {
78 NSMutableString * whereClause = [[NSMutableString alloc] init];
79 __block bool conjunction = false;
80 [whereDict enumerateKeysAndObjectsUsingBlock: ^(NSString* key, NSNumber* value, BOOL* stop) {
82 [whereClause appendFormat: @" WHERE "];
84 [whereClause appendFormat: @" AND "];
87 if([value class] == [CKKSSQLWhereObject class]) {
88 // Use this string verbatim
89 CKKSSQLWhereObject* whereob = (CKKSSQLWhereObject*) value;
90 [whereClause appendFormat: @"%@%@%@", key, whereob.sqlOp, whereob.contents];
92 [whereClause appendFormat: @"%@=(?)", key];
100 + (NSString*) groupByClause: (NSArray*) columns {
104 NSMutableString * groupByClause = [[NSMutableString alloc] init];
105 __block bool conjunction = false;
106 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) {
108 [groupByClause appendFormat: @" GROUP BY "];
110 [groupByClause appendFormat: @", "];
113 [groupByClause appendFormat: @"%@", column];
117 return groupByClause;
120 + (NSString*)orderByClause: (NSArray*) columns {
121 if(!columns || columns.count == 0u) {
124 NSMutableString * orderByClause = [[NSMutableString alloc] init];
125 __block bool conjunction = false;
126 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) {
128 [orderByClause appendFormat: @" ORDER BY "];
130 [orderByClause appendFormat: @", "];
133 [orderByClause appendFormat: @"%@", column];
137 return orderByClause;
140 + (bool) deleteFromTable: (NSString*) table where: (NSDictionary*) whereDict connection:(SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error {
141 __block CFErrorRef cferror = NULL;
143 bool (^doWithConnection)(SecDbConnectionRef) = ^bool (SecDbConnectionRef dbconn) {
144 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict];
146 NSString * sql = [[NSString alloc] initWithFormat: @"DELETE FROM %@%@;", table, whereClause];
147 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
149 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) {
150 if([whereDict[key] class] != [CKKSSQLWhereObject class]) {
151 SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) whereDict[key], &cferror);
155 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
162 doWithConnection(dbconn);
164 kc_with_dbt(true, &cferror, doWithConnection);
167 // Deletes finish in a single step, so if we didn't get an error, the delete 'happened'
168 bool status = (cferror == nil);
171 *error = (NSError*) CFBridgingRelease(cferror);
173 CFReleaseNull(cferror);
179 + (bool) queryDatabaseTable:(NSString*) table
180 where:(NSDictionary*) whereDict
181 columns:(NSArray*) names
182 groupBy:(NSArray*) groupColumns
183 orderBy:(NSArray*) orderColumns
185 processRow:(void (^)(NSDictionary*)) processRow
186 error:(NSError * __autoreleasing *) error {
187 __block CFErrorRef cferror = NULL;
189 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbconn) {
190 NSString * columns = [names componentsJoinedByString:@", "];
191 NSString * whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict];
192 NSString * groupByClause = [CKKSSQLDatabaseObject groupByClause: groupColumns];
193 NSString * orderByClause = [CKKSSQLDatabaseObject orderByClause: orderColumns];
194 NSString * limitClause = (limit > 0 ? [NSString stringWithFormat:@" LIMIT %lu", limit] : @"");
196 NSString * sql = [[NSString alloc] initWithFormat: @"SELECT %@ FROM %@%@%@%@%@;", columns, table, whereClause, groupByClause, orderByClause, limitClause];
197 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) {
198 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) {
199 if([whereDict[key] class] != [CKKSSQLWhereObject class]) {
200 SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) whereDict[key], &cferror);
204 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) {
205 __block NSMutableDictionary* row = [[NSMutableDictionary alloc] init];
207 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) {
208 const char * col = (const char *) sqlite3_column_text(stmt, (int)i);
209 row[name] = col ? [NSString stringWithUTF8String:col] : [NSNull null];
218 bool ret = (cferror == NULL);
219 SecTranslateError(error, cferror);
223 + (bool)queryMaxValueForField:(NSString*)maxField inTable:(NSString*)table where:(NSDictionary*)whereDict columns:(NSArray*)names processRow:(void (^)(NSDictionary*))processRow
225 __block CFErrorRef cferror = NULL;
227 kc_with_dbt(false, &cferror, ^bool(SecDbConnectionRef dbconn) {
228 NSString* columns = [[names componentsJoinedByString:@", "] stringByAppendingFormat:@", %@", maxField];
229 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause:whereDict];
231 NSString* sql = [[NSString alloc] initWithFormat:@"SELECT %@ FROM %@%@", columns, table, whereClause];
232 SecDbPrepare(dbconn, (__bridge CFStringRef)sql, &cferror, ^(sqlite3_stmt* stmt) {
233 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL* _Nonnull stop) {
234 if ([whereDict[key] class] != [CKKSSQLWhereObject class]) {
235 SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) whereDict[key], &cferror);
239 SecDbStep(dbconn, stmt, &cferror, ^(bool*stop) {
240 __block NSMutableDictionary* row = [[NSMutableDictionary alloc] init];
242 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) {
243 const char * col = (const char *) sqlite3_column_text(stmt, (int)i);
244 row[name] = col ? [NSString stringWithUTF8String:col] : [NSNull null];
254 bool ret = (cferror == NULL);
258 #pragma mark - Instance methods
260 - (bool) saveToDatabase: (NSError * __autoreleasing *) error {
261 return [self saveToDatabaseWithConnection:nil error: error];
264 - (bool) saveToDatabaseWithConnection: (SecDbConnectionRef) conn error: (NSError * __autoreleasing *) error {
265 // Todo: turn this into a transaction
267 NSDictionary* currentWhereClause = [self whereClauseToFindSelf];
269 // First, if we were loaded from the database and the where clause has changed, delete the old record.
270 if(self.originalSelfWhereClause && ![self.originalSelfWhereClause isEqualToDictionary: currentWhereClause]) {
271 secdebug("ckkssql", "Primary key changed; removing old row at %@", self.originalSelfWhereClause);
272 if(![CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: self.originalSelfWhereClause connection:conn error: error]) {
277 bool ok = [CKKSSQLDatabaseObject saveToDatabaseTable: [[self class] sqlTable]
278 row: [self sqlValues]
283 secdebug("ckkssql", "Saved %@", self);
285 secdebug("ckkssql", "Couldn't save %@: %@", self, error ? *error : @"unknown");
290 - (bool) deleteFromDatabase: (NSError * __autoreleasing *) error {
291 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: [self whereClauseToFindSelf] connection:nil error: error];
294 secdebug("ckkssql", "Deleted %@", self);
296 secdebug("ckkssql", "Couldn't delete %@: %@", self, error ? *error : @"unknown");
301 + (bool) deleteAll: (NSError * __autoreleasing *) error {
302 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[self sqlTable] where: nil connection:nil error: error];
305 secdebug("ckkssql", "Deleted all %@", self);
307 secdebug("ckkssql", "Couldn't delete all %@: %@", self, error ? *error : @"unknown");
312 + (instancetype) fromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
313 id ret = [self tryFromDatabaseWhere: whereDict error:error];
316 *error = [NSError errorWithDomain:@"securityd"
317 code:errSecItemNotFound
318 userInfo:@{NSLocalizedDescriptionKey:
319 [NSString stringWithFormat: @"%@ does not exist in database where %@", [self class], whereDict]}];
325 + (instancetype) tryFromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
326 __block id ret = nil;
328 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
330 columns: [self sqlColumns]
334 processRow: ^(NSDictionary* row) {
335 ret = [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause];
342 + (NSArray*) all: (NSError * __autoreleasing *) error {
343 return [self allWhere: nil error:error];
346 + (NSArray*) allWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
347 __block NSMutableArray* items = [[NSMutableArray alloc] init];
349 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
351 columns: [self sqlColumns]
355 processRow: ^(NSDictionary* row) {
356 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]];
363 + (NSArray*)fetch: (size_t)count error: (NSError * __autoreleasing *) error {
364 return [self fetch: count where:nil orderBy:nil error:error];
367 + (NSArray*)fetch: (size_t)count where:(NSDictionary*)whereDict error: (NSError * __autoreleasing *) error {
368 return [self fetch: count where:whereDict orderBy:nil error:error];
371 + (NSArray*)fetch:(size_t)count
372 where:(NSDictionary*)whereDict
373 orderBy:(NSArray*) orderColumns
374 error:(NSError * __autoreleasing *) error {
375 __block NSMutableArray* items = [[NSMutableArray alloc] init];
377 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
379 columns: [self sqlColumns]
382 limit: (ssize_t) count
383 processRow: ^(NSDictionary* row) {
384 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]];
391 - (instancetype) memoizeOriginalSelfWhereClause {
392 _originalSelfWhereClause = [self whereClauseToFindSelf];
396 #pragma mark - Subclass methods
398 + (instancetype) fromDatabaseRow:(NSDictionary *)row {
399 @throw [NSException exceptionWithName:NSInternalInconsistencyException
400 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
404 + (NSString*) sqlTable {
405 @throw [NSException exceptionWithName:NSInternalInconsistencyException
406 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
410 + (NSArray<NSString*>*) sqlColumns {
411 @throw [NSException exceptionWithName:NSInternalInconsistencyException
412 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
416 - (NSDictionary<NSString*,NSString*>*) sqlValues {
417 @throw [NSException exceptionWithName:NSInternalInconsistencyException
418 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
422 - (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf {
423 @throw [NSException exceptionWithName:NSInternalInconsistencyException
424 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)]
428 - (instancetype)copyWithZone:(NSZone *)zone {
429 CKKSSQLDatabaseObject *dbCopy = [[[self class] allocWithZone:zone] init];
430 dbCopy->_originalSelfWhereClause = _originalSelfWhereClause;
435 #pragma mark - CKKSSQLWhereObject
437 @implementation CKKSSQLWhereObject
438 - (instancetype)initWithOperation:(NSString*)op string: (NSString*) str {
439 if(self = [super init]) {
446 + (instancetype)op:(NSString*) op string: (NSString*) str {
447 return [[CKKSSQLWhereObject alloc] initWithOperation:op string: str];
450 + (instancetype)op:(NSString*) op stringValue: (NSString*) str {
451 return [[CKKSSQLWhereObject alloc] initWithOperation:op string:[NSString stringWithFormat:@"'%@'", str]];