/*
 * Copyright (C) 2012-2020 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. AND ITS CONTRIBUTORS ``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 ITS 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.
 */

#import "config.h"
#import "PluginBlocklist.h"

#if PLATFORM(MAC)

#import "BlocklistUpdater.h"
#import <pal/spi/cf/CFUtilitiesSPI.h>
#import <sys/stat.h>
#import <sys/time.h>

namespace WebCore {

PluginBlocklist::LoadPolicy PluginBlocklist::loadPolicyForPluginVersion(NSString *bundleIdentifier, NSString *bundleVersionString)
{
    BlocklistUpdater::initializeQueue();

    __block PluginBlocklist::LoadPolicy loadPolicy = LoadPolicy::LoadNormally;
    dispatch_sync(BlocklistUpdater::queue(), ^{
        BlocklistUpdater::reloadIfNecessary();

        PluginBlocklist* pluginBlocklist = BlocklistUpdater::pluginBlocklist();
        if (pluginBlocklist)
            loadPolicy = pluginBlocklist->loadPolicyForPlugin(bundleIdentifier, bundleVersionString);
    });

    return loadPolicy;
}

bool PluginBlocklist::isPluginUpdateAvailable(NSString *bundleIdentifier)
{
    BlocklistUpdater::initializeQueue();

    __block bool isPluginUpdateAvailable = false;
    dispatch_sync(BlocklistUpdater::queue(), ^{
        BlocklistUpdater::reloadIfNecessary();

        PluginBlocklist* pluginBlocklist = BlocklistUpdater::pluginBlocklist();
        if (pluginBlocklist)
            isPluginUpdateAvailable = pluginBlocklist->isUpdateAvailable(bundleIdentifier);
    });

    return isPluginUpdateAvailable;
}

std::unique_ptr<PluginBlocklist> PluginBlocklist::create(NSDictionary *propertyList)
{
    CFDictionaryRef systemVersionDictionary = _CFCopySystemVersionDictionary();
    CFStringRef osVersion = static_cast<CFStringRef>(CFDictionaryGetValue(systemVersionDictionary, _kCFSystemVersionProductVersionKey));

    NSDictionary *dictionary = [propertyList objectForKey:@"PlugInBlocklist"];

    NSMutableDictionary *bundleIDToMinimumSecureVersion = [NSMutableDictionary dictionary];
    NSMutableDictionary *bundleIDToMinimumCompatibleVersion = [NSMutableDictionary dictionary];
    NSMutableDictionary *bundleIDToBlockedVersions = [NSMutableDictionary dictionary];
    NSMutableSet *bundleIDsWithAvailableUpdates = [NSMutableSet set];
    
    for (NSString *osVersionComponent in splitOSVersion((__bridge NSString *)osVersion)) {
        NSDictionary *bundleIDs = [dictionary objectForKey:osVersionComponent];
        if (!bundleIDs)
            continue;

        for (NSString *bundleID in bundleIDs) {
            NSDictionary *versionInfo = [bundleIDs objectForKey:bundleID];
            assert(versionInfo);

            if (![versionInfo isKindOfClass:[NSDictionary class]])
                continue;

            [bundleIDToMinimumSecureVersion removeObjectForKey:bundleID];
            [bundleIDToMinimumCompatibleVersion removeObjectForKey:bundleID];
            [bundleIDToBlockedVersions removeObjectForKey:bundleID];

            if (NSArray *blockedVersions = [versionInfo objectForKey:@"BlockedPlugInBundleVersions"])
                [bundleIDToBlockedVersions setObject:blockedVersions forKey:bundleID];

            if (NSString *minimumSecureVersion = [versionInfo objectForKey:@"MinimumPlugInBundleVersion"])
                [bundleIDToMinimumSecureVersion setObject:minimumSecureVersion forKey:bundleID];

            if (NSString *minimumCompatibleVersion = [versionInfo objectForKey:@"MinimumCompatiblePlugInBundleVersion"])
                [bundleIDToMinimumCompatibleVersion setObject:minimumCompatibleVersion forKey:bundleID];

            if (NSNumber *updateAvailable = [versionInfo objectForKey:@"PlugInUpdateAvailable"]) {
                // A missing PlugInUpdateAvailable key means that there is a plug-in update available.
                if (!updateAvailable || [updateAvailable boolValue])
                    [bundleIDsWithAvailableUpdates addObject:bundleID];
            }
        }
    }

    CFRelease(systemVersionDictionary);

    return std::unique_ptr<PluginBlocklist>(new PluginBlocklist(bundleIDToMinimumSecureVersion, bundleIDToMinimumCompatibleVersion, bundleIDToBlockedVersions, bundleIDsWithAvailableUpdates));
}

PluginBlocklist::~PluginBlocklist()
{
}

NSArray *PluginBlocklist::splitOSVersion(NSString *osVersion)
{
    NSArray *components = [osVersion componentsSeparatedByString:@"."];

    NSMutableArray *result = [NSMutableArray array];

    for (NSUInteger i = 0; i < [components count]; ++i) {
        NSString *versionString = [[components subarrayWithRange:NSMakeRange(0, i + 1)] componentsJoinedByString:@"."];

        [result addObject:versionString];
    }

    return result;
}


PluginBlocklist::LoadPolicy PluginBlocklist::loadPolicyForPlugin(NSString *bundleIdentifier, NSString *bundleVersionString) const
{
    if (!bundleIdentifier || !bundleVersionString)
        return LoadPolicy::LoadNormally;

    // First, check for explicitly blocked versions.
    for (NSString *blockedVersion in [m_bundleIDToBlockedVersions objectForKey:bundleIdentifier]) {
        if ([blockedVersion isEqualToString:bundleVersionString])
            return LoadPolicy::BlockedForSecurity;
    }

    // Then, check if there's a forced minimum version for security issues.
    if (NSString *minimumSecureVersion = [m_bundleIDToMinimumSecureVersion objectForKey:bundleIdentifier]) {
        if ([bundleVersionString compare:minimumSecureVersion options:NSNumericSearch] == NSOrderedAscending)
            return LoadPolicy::BlockedForSecurity;
    }

    // Then, check if there's a forced minimum version for compatibility issues.
    if (NSString *minimumCompatibleVersion = [m_bundleIDToMinimumCompatibleVersion objectForKey:bundleIdentifier]) {
        if ([bundleVersionString compare:minimumCompatibleVersion options:NSNumericSearch] == NSOrderedAscending)
            return LoadPolicy::BlockedForCompatibility;
    }

    return LoadPolicy::LoadNormally;
}

bool PluginBlocklist::isUpdateAvailable(NSString *bundleIdentifier) const
{
    return [m_bundleIDsWithAvailableUpdates containsObject:bundleIdentifier];
}

PluginBlocklist::PluginBlocklist(NSDictionary *bundleIDToMinimumSecureVersion, NSDictionary *bundleIDToMinimumCompatibleVersion, NSDictionary *bundleIDToBlockedVersions, NSSet *bundleIDsWithAvailableUpdates)
    : m_bundleIDToMinimumSecureVersion { adoptNS([bundleIDToMinimumSecureVersion copy]) }
    , m_bundleIDToMinimumCompatibleVersion { adoptNS([bundleIDToMinimumCompatibleVersion copy]) }
    , m_bundleIDToBlockedVersions { adoptNS([bundleIDToBlockedVersions copy]) }
    , m_bundleIDsWithAvailableUpdates { adoptNS([bundleIDsWithAvailableUpdates copy]) }
{
}

}

#endif
