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