1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/media_galleries/win/mtp_device_operations_util.h" 6 7 #include <portabledevice.h> 8 9 #include <algorithm> 10 11 #include "base/basictypes.h" 12 #include "base/file_util.h" 13 #include "base/files/file_path.h" 14 #include "base/logging.h" 15 #include "base/safe_numerics.h" 16 #include "base/strings/string_util.h" 17 #include "base/threading/thread_restrictions.h" 18 #include "base/time/time.h" 19 #include "base/win/scoped_co_mem.h" 20 #include "base/win/scoped_propvariant.h" 21 #include "chrome/browser/storage_monitor/removable_device_constants.h" 22 #include "chrome/common/chrome_constants.h" 23 #include "content/public/browser/browser_thread.h" 24 25 namespace chrome { 26 27 namespace media_transfer_protocol { 28 29 namespace { 30 31 // On success, returns true and updates |client_info| with a reference to an 32 // IPortableDeviceValues interface that holds information about the 33 // application that communicates with the device. 34 bool GetClientInformation( 35 base::win::ScopedComPtr<IPortableDeviceValues>* client_info) { 36 base::ThreadRestrictions::AssertIOAllowed(); 37 DCHECK(client_info); 38 HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues), 39 NULL, CLSCTX_INPROC_SERVER); 40 if (FAILED(hr)) { 41 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues"; 42 return false; 43 } 44 45 (*client_info)->SetStringValue(WPD_CLIENT_NAME, 46 chrome::kBrowserProcessExecutableName); 47 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0); 48 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0); 49 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0); 50 (*client_info)->SetUnsignedIntegerValue( 51 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION); 52 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, 53 GENERIC_READ); 54 return true; 55 } 56 57 // Gets the content interface of the portable |device|. On success, returns 58 // the IPortableDeviceContent interface. On failure, returns NULL. 59 base::win::ScopedComPtr<IPortableDeviceContent> GetDeviceContent( 60 IPortableDevice* device) { 61 base::ThreadRestrictions::AssertIOAllowed(); 62 DCHECK(device); 63 base::win::ScopedComPtr<IPortableDeviceContent> content; 64 if (SUCCEEDED(device->Content(content.Receive()))) 65 return content; 66 return base::win::ScopedComPtr<IPortableDeviceContent>(); 67 } 68 69 // On success, returns IEnumPortableDeviceObjectIDs interface to enumerate 70 // the device objects. On failure, returns NULL. 71 // |parent_id| specifies the parent object identifier. 72 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> GetDeviceObjectEnumerator( 73 IPortableDevice* device, 74 const string16& parent_id) { 75 base::ThreadRestrictions::AssertIOAllowed(); 76 DCHECK(device); 77 DCHECK(!parent_id.empty()); 78 base::win::ScopedComPtr<IPortableDeviceContent> content = 79 GetDeviceContent(device); 80 if (!content) 81 return base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs>(); 82 83 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids; 84 if (SUCCEEDED(content->EnumObjects(0, parent_id.c_str(), NULL, 85 enum_object_ids.Receive()))) 86 return enum_object_ids; 87 return base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs>(); 88 } 89 90 // Returns whether the object is a directory/folder/album. |properties_values| 91 // contains the object property key values. 92 bool IsDirectory(IPortableDeviceValues* properties_values) { 93 DCHECK(properties_values); 94 GUID content_type; 95 HRESULT hr = properties_values->GetGuidValue(WPD_OBJECT_CONTENT_TYPE, 96 &content_type); 97 if (FAILED(hr)) 98 return false; 99 // TODO(kmadhusu): |content_type| can be an image or audio or video or mixed 100 // album. It is not clear whether an album is a collection of physical objects 101 // or virtual objects. Investigate this in detail. 102 103 // The root storage object describes its content type as 104 // WPD_CONTENT_FUNCTIONAL_OBJECT. 105 return (content_type == WPD_CONTENT_TYPE_FOLDER || 106 content_type == WPD_CONTENT_TYPE_FUNCTIONAL_OBJECT); 107 } 108 109 // Returns the friendly name of the object from the property key values 110 // specified by the |properties_values|. 111 string16 GetObjectName(IPortableDeviceValues* properties_values, 112 bool is_directory) { 113 DCHECK(properties_values); 114 base::win::ScopedCoMem<char16> buffer; 115 REFPROPERTYKEY key = 116 is_directory ? WPD_OBJECT_NAME : WPD_OBJECT_ORIGINAL_FILE_NAME; 117 HRESULT hr = properties_values->GetStringValue(key, &buffer); 118 string16 result; 119 if (SUCCEEDED(hr)) 120 result.assign(buffer); 121 return result; 122 } 123 124 // Gets the last modified time of the object from the property key values 125 // specified by the |properties_values|. On success, returns true and fills in 126 // |last_modified_time|. 127 bool GetLastModifiedTime(IPortableDeviceValues* properties_values, 128 base::Time* last_modified_time) { 129 DCHECK(properties_values); 130 DCHECK(last_modified_time); 131 base::win::ScopedPropVariant last_modified_date; 132 HRESULT hr = properties_values->GetValue(WPD_OBJECT_DATE_MODIFIED, 133 last_modified_date.Receive()); 134 if (FAILED(hr)) 135 return false; 136 137 bool last_modified_time_set = (last_modified_date.get().vt == VT_DATE); 138 if (last_modified_time_set) { 139 SYSTEMTIME system_time; 140 FILETIME file_time; 141 if (VariantTimeToSystemTime(last_modified_date.get().date, &system_time) && 142 SystemTimeToFileTime(&system_time, &file_time)) { 143 *last_modified_time = base::Time::FromFileTime(file_time); 144 } else { 145 last_modified_time_set = false; 146 } 147 } 148 return last_modified_time_set; 149 } 150 151 // Gets the size of the file object in bytes from the property key values 152 // specified by the |properties_values|. On success, returns true and fills 153 // in |size|. 154 bool GetObjectSize(IPortableDeviceValues* properties_values, int64* size) { 155 DCHECK(properties_values); 156 DCHECK(size); 157 ULONGLONG actual_size; 158 HRESULT hr = properties_values->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE, 159 &actual_size); 160 bool success = SUCCEEDED(hr) && (actual_size <= kint64max); 161 if (success) 162 *size = static_cast<int64>(actual_size); 163 return success; 164 } 165 166 // Gets the details of the object specified by the |object_id| given the media 167 // transfer protocol |device|. On success, returns true and fills in |name|, 168 // |is_directory|, |size| and |last_modified_time|. 169 bool GetObjectDetails(IPortableDevice* device, 170 const string16 object_id, 171 string16* name, 172 bool* is_directory, 173 int64* size, 174 base::Time* last_modified_time) { 175 base::ThreadRestrictions::AssertIOAllowed(); 176 DCHECK(device); 177 DCHECK(!object_id.empty()); 178 DCHECK(name); 179 DCHECK(is_directory); 180 DCHECK(size); 181 DCHECK(last_modified_time); 182 base::win::ScopedComPtr<IPortableDeviceContent> content = 183 GetDeviceContent(device); 184 if (!content) 185 return false; 186 187 base::win::ScopedComPtr<IPortableDeviceProperties> properties; 188 HRESULT hr = content->Properties(properties.Receive()); 189 if (FAILED(hr)) 190 return false; 191 192 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read; 193 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection), 194 NULL, 195 CLSCTX_INPROC_SERVER); 196 if (FAILED(hr)) 197 return false; 198 199 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) || 200 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) || 201 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) || 202 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) || 203 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) || 204 FAILED(properties_to_read->Add(WPD_OBJECT_SIZE))) 205 return false; 206 207 base::win::ScopedComPtr<IPortableDeviceValues> properties_values; 208 hr = properties->GetValues(object_id.c_str(), 209 properties_to_read.get(), 210 properties_values.Receive()); 211 if (FAILED(hr)) 212 return false; 213 214 *is_directory = IsDirectory(properties_values.get()); 215 *name = GetObjectName(properties_values.get(), *is_directory); 216 if (name->empty()) 217 return false; 218 219 if (*is_directory) { 220 // Directory entry does not have size and last modified date property key 221 // values. 222 *size = 0; 223 *last_modified_time = base::Time(); 224 return true; 225 } 226 return (GetObjectSize(properties_values.get(), size) && 227 GetLastModifiedTime(properties_values.get(), last_modified_time)); 228 } 229 230 // Creates an MTP device object entry for the given |device| and |object_id|. 231 // On success, returns true and fills in |entry|. 232 bool GetMTPDeviceObjectEntry(IPortableDevice* device, 233 const string16& object_id, 234 MTPDeviceObjectEntry* entry) { 235 base::ThreadRestrictions::AssertIOAllowed(); 236 DCHECK(device); 237 DCHECK(!object_id.empty()); 238 DCHECK(entry); 239 string16 name; 240 bool is_directory; 241 int64 size; 242 base::Time last_modified_time; 243 if (!GetObjectDetails(device, object_id, &name, &is_directory, &size, 244 &last_modified_time)) 245 return false; 246 *entry = MTPDeviceObjectEntry(object_id, name, is_directory, size, 247 last_modified_time); 248 return true; 249 } 250 251 // Gets the entries of the directory specified by |directory_object_id| from 252 // the given MTP |device|. Set |get_all_entries| to true to get all the entries 253 // of the directory. To request a specific object entry, put the object name in 254 // |object_name|. Leave |object_name| blank to request all entries. On 255 // success returns true and set |object_entries|. 256 bool GetMTPDeviceObjectEntries(IPortableDevice* device, 257 const string16& directory_object_id, 258 const string16& object_name, 259 MTPDeviceObjectEntries* object_entries) { 260 base::ThreadRestrictions::AssertIOAllowed(); 261 DCHECK(device); 262 DCHECK(!directory_object_id.empty()); 263 DCHECK(object_entries); 264 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids = 265 GetDeviceObjectEnumerator(device, directory_object_id); 266 if (!enum_object_ids) 267 return false; 268 269 // Loop calling Next() while S_OK is being returned. 270 const DWORD num_objects_to_request = 10; 271 const bool get_all_entries = object_name.empty(); 272 for (HRESULT hr = S_OK; hr == S_OK;) { 273 DWORD num_objects_fetched = 0; 274 scoped_ptr<char16*[]> object_ids(new char16*[num_objects_to_request]); 275 hr = enum_object_ids->Next(num_objects_to_request, 276 object_ids.get(), 277 &num_objects_fetched); 278 for (DWORD index = 0; index < num_objects_fetched; ++index) { 279 MTPDeviceObjectEntry entry; 280 if (GetMTPDeviceObjectEntry(device, 281 object_ids[index], 282 &entry)) { 283 if (get_all_entries) { 284 object_entries->push_back(entry); 285 } else if (entry.name == object_name) { 286 object_entries->push_back(entry); // Object entry found. 287 break; 288 } 289 } 290 } 291 for (DWORD index = 0; index < num_objects_fetched; ++index) 292 CoTaskMemFree(object_ids[index]); 293 } 294 return true; 295 } 296 297 } // namespace 298 299 base::win::ScopedComPtr<IPortableDevice> OpenDevice( 300 const string16& pnp_device_id) { 301 base::ThreadRestrictions::AssertIOAllowed(); 302 DCHECK(!pnp_device_id.empty()); 303 base::win::ScopedComPtr<IPortableDeviceValues> client_info; 304 if (!GetClientInformation(&client_info)) 305 return base::win::ScopedComPtr<IPortableDevice>(); 306 base::win::ScopedComPtr<IPortableDevice> device; 307 HRESULT hr = device.CreateInstance(__uuidof(PortableDevice), NULL, 308 CLSCTX_INPROC_SERVER); 309 if (FAILED(hr)) 310 return base::win::ScopedComPtr<IPortableDevice>(); 311 312 hr = device->Open(pnp_device_id.c_str(), client_info.get()); 313 if (SUCCEEDED(hr)) 314 return device; 315 if (hr == E_ACCESSDENIED) 316 DPLOG(ERROR) << "Access denied to open the device"; 317 return base::win::ScopedComPtr<IPortableDevice>(); 318 } 319 320 base::PlatformFileError GetFileEntryInfo( 321 IPortableDevice* device, 322 const string16& object_id, 323 base::PlatformFileInfo* file_entry_info) { 324 DCHECK(device); 325 DCHECK(!object_id.empty()); 326 DCHECK(file_entry_info); 327 MTPDeviceObjectEntry entry; 328 if (!GetMTPDeviceObjectEntry(device, object_id, &entry)) 329 return base::PLATFORM_FILE_ERROR_NOT_FOUND; 330 331 file_entry_info->size = entry.size; 332 file_entry_info->is_directory = entry.is_directory; 333 file_entry_info->is_symbolic_link = false; 334 file_entry_info->last_modified = entry.last_modified_time; 335 file_entry_info->last_accessed = entry.last_modified_time; 336 file_entry_info->creation_time = base::Time(); 337 return base::PLATFORM_FILE_OK; 338 } 339 340 bool GetDirectoryEntries(IPortableDevice* device, 341 const string16& directory_object_id, 342 MTPDeviceObjectEntries* object_entries) { 343 return GetMTPDeviceObjectEntries(device, directory_object_id, string16(), 344 object_entries); 345 } 346 347 HRESULT GetFileStreamForObject(IPortableDevice* device, 348 const string16& file_object_id, 349 IStream** file_stream, 350 DWORD* optimal_transfer_size) { 351 base::ThreadRestrictions::AssertIOAllowed(); 352 DCHECK(device); 353 DCHECK(!file_object_id.empty()); 354 base::win::ScopedComPtr<IPortableDeviceContent> content = 355 GetDeviceContent(device); 356 if (!content) 357 return E_FAIL; 358 359 base::win::ScopedComPtr<IPortableDeviceResources> resources; 360 HRESULT hr = content->Transfer(resources.Receive()); 361 if (FAILED(hr)) 362 return hr; 363 return resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT, 364 STGM_READ, optimal_transfer_size, 365 file_stream); 366 } 367 368 DWORD CopyDataChunkToLocalFile(IStream* stream, 369 const base::FilePath& local_path, 370 size_t optimal_transfer_size) { 371 base::ThreadRestrictions::AssertIOAllowed(); 372 DCHECK(stream); 373 DCHECK(!local_path.empty()); 374 if (optimal_transfer_size == 0U) 375 return 0U; 376 DWORD bytes_read = 0; 377 std::string buffer; 378 HRESULT hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1), 379 optimal_transfer_size, &bytes_read); 380 // IStream::Read() returns S_FALSE when the actual number of bytes read from 381 // the stream object is less than the number of bytes requested (aka 382 // |optimal_transfer_size|). This indicates the end of the stream has been 383 // reached. 384 if (FAILED(hr)) 385 return 0U; 386 DCHECK_GT(bytes_read, 0U); 387 CHECK_LE(bytes_read, buffer.length()); 388 int data_len = 389 base::checked_numeric_cast<int>( 390 std::min(bytes_read, 391 base::checked_numeric_cast<DWORD>(buffer.length()))); 392 if (file_util::AppendToFile(local_path, buffer.c_str(), data_len) != data_len) 393 return 0U; 394 return data_len; 395 } 396 397 string16 GetObjectIdFromName(IPortableDevice* device, 398 const string16& parent_id, 399 const string16& object_name) { 400 MTPDeviceObjectEntries object_entries; 401 if (!GetMTPDeviceObjectEntries(device, parent_id, object_name, 402 &object_entries) || 403 object_entries.empty()) 404 return string16(); 405 // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have 406 // the same name. Handle the situation gracefully. Refer to crbug.com/169930 407 // for more details. 408 DCHECK_EQ(1U, object_entries.size()); 409 return object_entries[0].object_id; 410 } 411 412 } // namespace media_transfer_protocol 413 414 } // namespace chrome 415