| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/browser/download/file_metadata_mac.h" | 5 #include "content/browser/download/quarantine.h" |
| 6 | 6 |
| 7 #include <ApplicationServices/ApplicationServices.h> | 7 #include <ApplicationServices/ApplicationServices.h> |
| 8 #include <Foundation/Foundation.h> | 8 #include <Foundation/Foundation.h> |
| 9 | 9 |
| 10 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/mac/foundation_util.h" | 12 #include "base/mac/foundation_util.h" |
| 13 #include "base/mac/mac_logging.h" | 13 #include "base/mac/mac_logging.h" |
| 14 #include "base/mac/mac_util.h" | 14 #include "base/mac/mac_util.h" |
| 15 #include "base/mac/scoped_cftyperef.h" | 15 #include "base/mac/scoped_cftyperef.h" |
| 16 #include "base/threading/thread_restrictions.h" |
| 16 #include "url/gurl.h" | 17 #include "url/gurl.h" |
| 17 | 18 |
| 18 namespace content { | 19 namespace content { |
| 19 | 20 |
| 21 namespace { |
| 22 |
| 20 // As of Mac OS X 10.4 ("Tiger"), files can be tagged with metadata describing | 23 // As of Mac OS X 10.4 ("Tiger"), files can be tagged with metadata describing |
| 21 // various attributes. Metadata is integrated with the system's Spotlight | 24 // various attributes. Metadata is integrated with the system's Spotlight |
| 22 // feature and is searchable. Ordinarily, metadata can only be set by | 25 // feature and is searchable. Ordinarily, metadata can only be set by |
| 23 // Spotlight importers, which requires that the importer own the target file. | 26 // Spotlight importers, which requires that the importer own the target file. |
| 24 // However, there's an attribute intended to describe the origin of a | 27 // However, there's an attribute intended to describe the origin of a |
| 25 // file, that can store the source URL and referrer of a downloaded file. | 28 // file, that can store the source URL and referrer of a downloaded file. |
| 26 // It's stored as a "com.apple.metadata:kMDItemWhereFroms" extended attribute, | 29 // It's stored as a "com.apple.metadata:kMDItemWhereFroms" extended attribute, |
| 27 // structured as a binary1-format plist containing a list of sources. This | 30 // structured as a binary1-format plist containing a list of sources. This |
| 28 // attribute can only be populated by the downloader, not a Spotlight importer. | 31 // attribute can only be populated by the downloader, not a Spotlight importer. |
| 29 // Safari on 10.4 and later populates this attribute. | 32 // Safari on 10.4 and later populates this attribute. |
| 30 // | 33 // |
| 31 // With this metadata set, you can locate downloads by performing a Spotlight | 34 // With this metadata set, you can locate downloads by performing a Spotlight |
| 32 // search for their source or referrer URLs, either from within the Spotlight | 35 // search for their source or referrer URLs, either from within the Spotlight |
| 33 // UI or from the command line: | 36 // UI or from the command line: |
| 34 // mdfind 'kMDItemWhereFroms == "http://releases.mozilla.org/*"' | 37 // mdfind 'kMDItemWhereFroms == "http://releases.mozilla.org/*"' |
| 35 // | 38 // |
| 36 // There is no documented API to set metadata on a file directly as of the | 39 // There is no documented API to set metadata on a file directly as of the |
| 37 // 10.5 SDK. The MDSetItemAttribute function does exist to perform this task, | 40 // 10.5 SDK. The MDSetItemAttribute function does exist to perform this task, |
| 38 // but it's undocumented. | 41 // but it's undocumented. |
| 39 void AddOriginMetadataToFile(const base::FilePath& file, const GURL& source, | 42 bool AddOriginMetadataToFile(const base::FilePath& file, |
| 43 const GURL& source, |
| 40 const GURL& referrer) { | 44 const GURL& referrer) { |
| 45 base::ThreadRestrictions::AssertIOAllowed(); |
| 41 // There's no declaration for MDItemSetAttribute in any known public SDK. | 46 // There's no declaration for MDItemSetAttribute in any known public SDK. |
| 42 // It exists in the 10.4 and 10.5 runtimes. To play it safe, do the lookup | 47 // It exists in the 10.4 and 10.5 runtimes. To play it safe, do the lookup |
| 43 // at runtime instead of declaring it ourselves and linking against what's | 48 // at runtime instead of declaring it ourselves and linking against what's |
| 44 // provided. This has two benefits: | 49 // provided. This has two benefits: |
| 45 // - If Apple relents and declares the function in a future SDK (it's | 50 // - If Apple relents and declares the function in a future SDK (it's |
| 46 // happened before), our build won't break. | 51 // happened before), our build won't break. |
| 47 // - If Apple removes or renames the function in a future runtime, the | 52 // - If Apple removes or renames the function in a future runtime, the |
| 48 // loader won't refuse to let the application launch. Instead, we'll | 53 // loader won't refuse to let the application launch. Instead, we'll |
| 49 // silently fail to set any metadata. | 54 // silently fail to set any metadata. |
| 50 typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, | 55 typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, |
| 51 CFTypeRef); | 56 CFTypeRef); |
| 52 static MDItemSetAttribute_type md_item_set_attribute_func = NULL; | 57 static MDItemSetAttribute_type md_item_set_attribute_func = NULL; |
| 53 | 58 |
| 54 static bool did_symbol_lookup = false; | 59 static bool did_symbol_lookup = false; |
| 55 if (!did_symbol_lookup) { | 60 if (!did_symbol_lookup) { |
| 56 did_symbol_lookup = true; | 61 did_symbol_lookup = true; |
| 57 CFBundleRef metadata_bundle = | 62 CFBundleRef metadata_bundle = |
| 58 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata")); | 63 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata")); |
| 59 if (!metadata_bundle) | 64 if (!metadata_bundle) |
| 60 return; | 65 return false; |
| 61 | 66 |
| 62 md_item_set_attribute_func = (MDItemSetAttribute_type) | 67 md_item_set_attribute_func = |
| 63 CFBundleGetFunctionPointerForName(metadata_bundle, | 68 (MDItemSetAttribute_type)CFBundleGetFunctionPointerForName( |
| 64 CFSTR("MDItemSetAttribute")); | 69 metadata_bundle, CFSTR("MDItemSetAttribute")); |
| 65 } | 70 } |
| 66 if (!md_item_set_attribute_func) | 71 if (!md_item_set_attribute_func) |
| 67 return; | 72 return false; |
| 68 | 73 |
| 69 NSString* file_path = | 74 NSString* file_path = [NSString stringWithUTF8String:file.value().c_str()]; |
| 70 [NSString stringWithUTF8String:file.value().c_str()]; | |
| 71 if (!file_path) | 75 if (!file_path) |
| 72 return; | 76 return false; |
| 73 | 77 |
| 74 base::ScopedCFTypeRef<MDItemRef> md_item( | 78 base::ScopedCFTypeRef<MDItemRef> md_item( |
| 75 MDItemCreate(NULL, base::mac::NSToCFCast(file_path))); | 79 MDItemCreate(NULL, base::mac::NSToCFCast(file_path))); |
| 76 if (!md_item) | 80 if (!md_item) |
| 77 return; | 81 return false; |
| 78 | 82 |
| 79 // We won't put any more than 2 items into the attribute. | 83 // We won't put any more than 2 items into the attribute. |
| 80 NSMutableArray* list = [NSMutableArray arrayWithCapacity:2]; | 84 NSMutableArray* list = [NSMutableArray arrayWithCapacity:2]; |
| 81 | 85 |
| 82 // Follow Safari's lead: the first item in the list is the source URL of | 86 // Follow Safari's lead: the first item in the list is the source URL of |
| 83 // the downloaded file. If the referrer is known, store that, too. | 87 // the downloaded file. If the referrer is known, store that, too. |
| 84 NSString* origin_url = [NSString stringWithUTF8String:source.spec().c_str()]; | 88 NSString* origin_url = [NSString stringWithUTF8String:source.spec().c_str()]; |
| 85 if (origin_url) | 89 if (origin_url) |
| 86 [list addObject:origin_url]; | 90 [list addObject:origin_url]; |
| 87 NSString* referrer_url = | 91 NSString* referrer_url = |
| 88 [NSString stringWithUTF8String:referrer.spec().c_str()]; | 92 [NSString stringWithUTF8String:referrer.spec().c_str()]; |
| 89 if (referrer_url) | 93 if (referrer_url) |
| 90 [list addObject:referrer_url]; | 94 [list addObject:referrer_url]; |
| 91 | 95 |
| 92 md_item_set_attribute_func(md_item, kMDItemWhereFroms, | 96 md_item_set_attribute_func(md_item, kMDItemWhereFroms, |
| 93 base::mac::NSToCFCast(list)); | 97 base::mac::NSToCFCast(list)); |
| 98 return true; |
| 94 } | 99 } |
| 95 | 100 |
| 101 // Adds quarantine metadata to the file, assuming it has already been |
| 102 // quarantined by the OS. |
| 103 // |source| should be the source URL for the download, and |referrer| should be |
| 104 // the URL the user initiated the download from. |
| 105 |
| 96 // The OS will automatically quarantine files due to the | 106 // The OS will automatically quarantine files due to the |
| 97 // LSFileQuarantineEnabled entry in our Info.plist, but it knows relatively | 107 // LSFileQuarantineEnabled entry in our Info.plist, but it knows relatively |
| 98 // little about the files. We add more information about the download to | 108 // little about the files. We add more information about the download to |
| 99 // improve the UI shown by the OS when the users tries to open the file. | 109 // improve the UI shown by the OS when the users tries to open the file. |
| 100 void AddQuarantineMetadataToFile(const base::FilePath& file, const GURL& source, | 110 bool AddQuarantineMetadataToFile(const base::FilePath& file, |
| 111 const GURL& source, |
| 101 const GURL& referrer) { | 112 const GURL& referrer) { |
| 113 base::ThreadRestrictions::AssertIOAllowed(); |
| 102 FSRef file_ref; | 114 FSRef file_ref; |
| 103 if (!base::mac::FSRefFromPath(file.value(), &file_ref)) | 115 if (!base::mac::FSRefFromPath(file.value(), &file_ref)) |
| 104 return; | 116 return false; |
| 105 | 117 |
| 106 NSMutableDictionary* quarantine_properties = nil; | 118 NSMutableDictionary* quarantine_properties = nil; |
| 107 CFTypeRef quarantine_properties_base = NULL; | 119 CFTypeRef quarantine_properties_base = NULL; |
| 108 if (LSCopyItemAttribute(&file_ref, kLSRolesAll, kLSItemQuarantineProperties, | 120 if (LSCopyItemAttribute(&file_ref, kLSRolesAll, kLSItemQuarantineProperties, |
| 109 &quarantine_properties_base) == noErr) { | 121 &quarantine_properties_base) == noErr) { |
| 110 if (CFGetTypeID(quarantine_properties_base) == | 122 if (CFGetTypeID(quarantine_properties_base) == CFDictionaryGetTypeID()) { |
| 111 CFDictionaryGetTypeID()) { | |
| 112 // Quarantine properties will already exist if LSFileQuarantineEnabled | 123 // Quarantine properties will already exist if LSFileQuarantineEnabled |
| 113 // is on and the file doesn't match an exclusion. | 124 // is on and the file doesn't match an exclusion. |
| 114 quarantine_properties = | 125 quarantine_properties = |
| 115 [[(NSDictionary*)quarantine_properties_base mutableCopy] autorelease]; | 126 [[(NSDictionary*)quarantine_properties_base mutableCopy] autorelease]; |
| 116 } else { | 127 } else { |
| 117 LOG(WARNING) << "kLSItemQuarantineProperties is not a dictionary on file " | 128 LOG(WARNING) << "kLSItemQuarantineProperties is not a dictionary on file " |
| 118 << file.value(); | 129 << file.value(); |
| 119 } | 130 } |
| 120 CFRelease(quarantine_properties_base); | 131 CFRelease(quarantine_properties_base); |
| 121 } | 132 } |
| 122 | 133 |
| 123 if (!quarantine_properties) { | 134 if (!quarantine_properties) { |
| 124 // If there are no quarantine properties, then the file isn't quarantined | 135 // If there are no quarantine properties, then the file isn't quarantined |
| 125 // (e.g., because the user has set up exclusions for certain file types). | 136 // (e.g., because the user has set up exclusions for certain file types). |
| 126 // We don't want to add any metadata, because that will cause the file to | 137 // We don't want to add any metadata, because that will cause the file to |
| 127 // be quarantined against the user's wishes. | 138 // be quarantined against the user's wishes. |
| 128 return; | 139 return true; |
| 129 } | 140 } |
| 130 | 141 |
| 131 // kLSQuarantineAgentNameKey, kLSQuarantineAgentBundleIdentifierKey, and | 142 // kLSQuarantineAgentNameKey, kLSQuarantineAgentBundleIdentifierKey, and |
| 132 // kLSQuarantineTimeStampKey are set for us (see LSQuarantine.h), so we only | 143 // kLSQuarantineTimeStampKey are set for us (see LSQuarantine.h), so we only |
| 133 // need to set the values that the OS can't infer. | 144 // need to set the values that the OS can't infer. |
| 134 | 145 |
| 135 if (![quarantine_properties valueForKey:(NSString*)kLSQuarantineTypeKey]) { | 146 if (![quarantine_properties valueForKey:(NSString*)kLSQuarantineTypeKey]) { |
| 136 CFStringRef type = source.SchemeIsHTTPOrHTTPS() | 147 CFStringRef type = source.SchemeIsHTTPOrHTTPS() |
| 137 ? kLSQuarantineTypeWebDownload | 148 ? kLSQuarantineTypeWebDownload |
| 138 : kLSQuarantineTypeOtherDownload; | 149 : kLSQuarantineTypeOtherDownload; |
| 139 [quarantine_properties setValue:(NSString*)type | 150 [quarantine_properties setValue:(NSString*)type |
| 140 forKey:(NSString*)kLSQuarantineTypeKey]; | 151 forKey:(NSString*)kLSQuarantineTypeKey]; |
| 141 } | 152 } |
| 142 | 153 |
| 143 if (![quarantine_properties | 154 if (![quarantine_properties |
| 144 valueForKey:(NSString*)kLSQuarantineOriginURLKey] && | 155 valueForKey:(NSString*)kLSQuarantineOriginURLKey] && |
| 145 referrer.is_valid()) { | 156 referrer.is_valid()) { |
| 146 NSString* referrer_url = | 157 NSString* referrer_url = |
| 147 [NSString stringWithUTF8String:referrer.spec().c_str()]; | 158 [NSString stringWithUTF8String:referrer.spec().c_str()]; |
| 148 [quarantine_properties setValue:referrer_url | 159 [quarantine_properties setValue:referrer_url |
| 149 forKey:(NSString*)kLSQuarantineOriginURLKey]; | 160 forKey:(NSString*)kLSQuarantineOriginURLKey]; |
| 150 } | 161 } |
| 151 | 162 |
| 152 if (![quarantine_properties valueForKey:(NSString*)kLSQuarantineDataURLKey] && | 163 if (![quarantine_properties valueForKey:(NSString*)kLSQuarantineDataURLKey] && |
| 153 source.is_valid()) { | 164 source.is_valid()) { |
| 154 NSString* origin_url = | 165 NSString* origin_url = |
| 155 [NSString stringWithUTF8String:source.spec().c_str()]; | 166 [NSString stringWithUTF8String:source.spec().c_str()]; |
| 156 [quarantine_properties setValue:origin_url | 167 [quarantine_properties setValue:origin_url |
| 157 forKey:(NSString*)kLSQuarantineDataURLKey]; | 168 forKey:(NSString*)kLSQuarantineDataURLKey]; |
| 158 } | 169 } |
| 159 | 170 |
| 160 OSStatus os_error = LSSetItemAttribute(&file_ref, kLSRolesAll, | 171 OSStatus os_error = |
| 161 kLSItemQuarantineProperties, | 172 LSSetItemAttribute(&file_ref, kLSRolesAll, kLSItemQuarantineProperties, |
| 162 quarantine_properties); | 173 quarantine_properties); |
| 163 if (os_error != noErr) { | 174 if (os_error != noErr) { |
| 164 OSSTATUS_LOG(WARNING, os_error) | 175 OSSTATUS_LOG(WARNING, os_error) |
| 165 << "Unable to set quarantine attributes on file " << file.value(); | 176 << "Unable to set quarantine attributes on file " << file.value(); |
| 177 return false; |
| 166 } | 178 } |
| 179 return true; |
| 180 } |
| 181 |
| 182 } // namespace |
| 183 |
| 184 QuarantineFileResult QuarantineFile(const base::FilePath& file, |
| 185 const GURL& source_url, |
| 186 const GURL& referrer_url, |
| 187 const std::string& client_guid) { |
| 188 bool quarantine_succeeded = |
| 189 AddQuarantineMetadataToFile(file, source_url, referrer_url); |
| 190 bool origin_succeeded = |
| 191 AddOriginMetadataToFile(file, source_url, referrer_url); |
| 192 return quarantine_succeeded && origin_succeeded |
| 193 ? QuarantineFileResult::OK |
| 194 : QuarantineFileResult::ANNOTATION_FAILED; |
| 167 } | 195 } |
| 168 | 196 |
| 169 } // namespace content | 197 } // namespace content |
| OLD | NEW |