chromium/components/services/quarantine/quarantine_mac.mm

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/services/quarantine/quarantine.h"

#import <ApplicationServices/ApplicationServices.h>
#import <Foundation/Foundation.h>

#include "base/apple/bridging.h"
#include "base/apple/foundation_util.h"
#include "base/apple/osstatus_logging.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "components/services/quarantine/common.h"
#include "components/services/quarantine/common_mac.h"
#include "url/gurl.h"

namespace quarantine {

namespace {

extern "C" {
Boolean MDItemSetAttribute(MDItemRef, CFStringRef, CFTypeRef);
}

// As of Mac OS X 10.4 ("Tiger"), files can be tagged with metadata describing
// various attributes. Metadata is integrated with the system's Spotlight
// feature and is searchable. Ordinarily, metadata can only be set by
// Spotlight importers, which requires that the importer own the target file.
// However, there's an attribute intended to describe the origin of a
// file, that can store the source URL and referrer of a downloaded file.
// It's stored as a "com.apple.metadata:kMDItemWhereFroms" extended attribute,
// structured as a binary1-format plist containing a list of sources. This
// attribute can only be populated by the downloader, not a Spotlight importer.
// Safari on 10.4 and later populates this attribute.
//
// With this metadata set, you can locate downloads by performing a Spotlight
// search for their source or referrer URLs, either from within the Spotlight
// UI or from the command line:
//     mdfind 'kMDItemWhereFroms == "http://releases.mozilla.org/*"'
//
// The most modern metadata API is NSMetadata, with this attribute available as
// `NSMetadataItemWhereFromsKey`, but NSMetadata offers a query-only interface.
// The original metadata API is the Metadata.framework in CoreServices, and
// while it too offers a query-only interface, at least it has a private call to
// set the metadata. Therefore, use Metadata.framework.
bool AddOriginMetadataToFile(const base::FilePath& file,
                             const GURL& source,
                             const GURL& referrer) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  NSString* file_path = base::apple::FilePathToNSString(file);
  if (!file_path) {
    return false;
  }

  base::apple::ScopedCFTypeRef<MDItemRef> md_item(
      MDItemCreate(kCFAllocatorDefault, base::apple::NSToCFPtrCast(file_path)));
  if (!md_item) {
    LOG(WARNING) << "MDItemCreate failed for path " << file.value();
    return false;
  }

  // We won't put any more than 2 items into the attribute.
  NSMutableArray* list = [NSMutableArray arrayWithCapacity:2];

  // Follow Safari's lead: the first item in the list is the source URL of the
  // downloaded file. If the referrer is known, store that, too. The URLs may be
  // empty (e.g. files downloaded in Incognito mode); don't add empty URLs.
  NSString* origin_url = base::SysUTF8ToNSString(source.spec());
  if (origin_url && origin_url.length) {
    [list addObject:origin_url];
  }
  NSString* referrer_url = base::SysUTF8ToNSString(referrer.spec());
  if (referrer_url && referrer_url.length) {
    [list addObject:referrer_url];
  }

  if (list.count) {
    return MDItemSetAttribute(md_item.get(), kMDItemWhereFroms,
                              base::apple::NSToCFPtrCast(list));
  }

  return true;
}

}  // namespace

void QuarantineFile(const base::FilePath& file,
                    const GURL& source_url_unsafe,
                    const GURL& referrer_url_unsafe,
                    const std::string& client_guid,
                    mojom::Quarantine::QuarantineFileCallback callback) {
  if (!base::PathExists(file)) {
    std::move(callback).Run(QuarantineFileResult::FILE_MISSING);
    return;
  }

  // By default, all items downloaded from Chromium are quarantined, due to the
  // LSFileQuarantineEnabled in the Info.plist. As of macOS 12.4, additional
  // metadata added to the quarantine database is ignored (see
  // https://crbug.com/1334495#c26), so don't bother including it. Do continue
  // to populate the origin metadata, though, as it's useful to an end-user.

  GURL source_url = SanitizeUrlForQuarantine(source_url_unsafe);
  GURL referrer_url = SanitizeUrlForQuarantine(referrer_url_unsafe);

  // Don't consider it an error if we fail to add origin metadata.
  AddOriginMetadataToFile(file, source_url, referrer_url);

  std::move(callback).Run(QuarantineFileResult::OK);
}

}  // namespace quarantine