/*
 * Copyright (C) 2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#import <Availability.h>
#import <wtf/Assertions.h>
#import <wtf/RetainPtr.h>

#define USE_SECURE_ARCHIVER_API (PLATFORM(MAC) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110200) || PLATFORM(WATCHOS) || PLATFORM(APPLETV))

#define USE_SECURE_ARCHIVER_FOR_ATTRIBUTED_STRING (PLATFORM(MAC) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 130000) || PLATFORM(WATCHOS) || PLATFORM(APPLETV))

#if USE(SECURE_ARCHIVER_API)
#if USE(APPLE_INTERNAL_SDK)
#import <Foundation/NSKeyedArchiver_Private.h>
#else
#import <Foundation/NSCoder.h>
#import <Foundation/NSKeyedArchiver.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSKeyedArchiver (NSKeyedArchiverSecureCodingInitializers)
- (instancetype)initRequiringSecureCoding:(BOOL)requiresSecureCoding API_AVAILABLE(macos(10.13), ios(11.0));
+ (nullable NSData *)archivedDataWithRootObject:(id)object requiringSecureCoding:(BOOL)requiresSecureCoding error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0));
@end

@interface NSKeyedUnarchiver (NSKeyedUnarchiverSecureCodingInitializer)
- (nullable instancetype)initForReadingFromData:(NSData *)data error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0));
+ (nullable id)unarchivedObjectOfClass:(Class)cls fromData:(NSData *)data error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0));
+ (nullable id)unarchivedObjectOfClasses:(NSSet<Class> *)classes fromData:(NSData *)data error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0));
@end

NS_ASSUME_NONNULL_END

#endif /* USE(APPLE_INTERNAL_SDK) */
#endif /* USE(SECURE_ARCHIVER_API) */

inline NSData *_Nullable securelyArchivedDataWithRootObject(id _Nonnull object)
{
#if USE(SECURE_ARCHIVER_API)
    NSError *error;
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:YES error:&error];
    if (!data)
        LOG_ERROR("Unable to archive data: %@", error);
    return data;
#else
    return [NSKeyedArchiver archivedDataWithRootObject:object];
#endif
}

inline NSData *_Nullable insecurelyArchivedDataWithRootObject(id _Nonnull object)
{
#if USE(SECURE_ARCHIVER_API)
    NSError *error;
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:NO error:&error];
    if (!data)
        LOG_ERROR("Unable to archive data: %@", error);
    return data;
#else
    return [NSKeyedArchiver archivedDataWithRootObject:object];
#endif
}

inline id _Nullable insecurelyUnarchiveObjectFromData(NSData * _Nonnull data)
{
    ALLOW_DEPRECATED_DECLARATIONS_BEGIN
    return [NSKeyedUnarchiver unarchiveObjectWithData:data];
    ALLOW_DEPRECATED_DECLARATIONS_END
}

inline id _Nullable unarchivedObjectOfClassesFromData(NSSet<Class> * _Nonnull classes, NSData * _Nonnull data)
{
#if USE(SECURE_ARCHIVER_API)
#if !USE(SECURE_ARCHIVER_FOR_ATTRIBUTED_STRING)
    // Remove this code when the fix from <rdar://problem/31376830> is deployed to all relevant build targets.
    if ([classes containsObject:[NSAttributedString class]])
        return insecurelyUnarchiveObjectFromData(data);
#endif
    NSError *error;
    id value = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:data error:&error];
    if (!value)
        LOG_ERROR("Unable to unarchive data: %@", error);
    return value;
#else
    UNUSED_PARAM(classes);
    return insecurelyUnarchiveObjectFromData(data);
#endif
}

inline RetainPtr<NSKeyedArchiver> secureArchiver()
{
#if USE(SECURE_ARCHIVER_API)
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
#else
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] init];
    [archiver setRequiresSecureCoding:YES];
#endif
    return adoptNS(archiver);
}

inline RetainPtr<NSKeyedUnarchiver> secureUnarchiverFromData(NSData *_Nonnull data)
{
#if USE(SECURE_ARCHIVER_API)
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
    unarchiver.decodingFailurePolicy = NSDecodingFailurePolicyRaiseException;
#else
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    [unarchiver setRequiresSecureCoding:YES];
#endif
    return adoptNS(unarchiver);
}

inline id _Nullable decodeObjectOfClassForKeyFromCoder(Class _Nonnull cls, NSString * _Nonnull key, NSCoder * _Nonnull coder)
{
#if USE(SECURE_ARCHIVER_API)
    return [coder decodeObjectOfClass:cls forKey:key];
#else
    UNUSED_PARAM(cls);
    return [coder decodeObjectForKey:key];
#endif
}
