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