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/files/file_path.h" 13 #include "base/files/file_util.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 failure, return -1. 153 int64 GetObjectSize(IPortableDeviceValues* properties_values) { 154 DCHECK(properties_values); 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 return success ? static_cast<int64>(actual_size) : -1; 160 } 161 162 // Gets the details of the object specified by the |object_id| given the media 163 // transfer protocol |device|. On success, returns true and fills in |name|, 164 // |is_directory|, |size|. |last_modified_time| will be filled in if possible, 165 // but failure to get it doesn't prevent success. 166 bool GetObjectDetails(IPortableDevice* device, 167 const base::string16 object_id, 168 base::string16* name, 169 bool* is_directory, 170 int64* size, 171 base::Time* last_modified_time) { 172 base::ThreadRestrictions::AssertIOAllowed(); 173 DCHECK(device); 174 DCHECK(!object_id.empty()); 175 DCHECK(name); 176 DCHECK(is_directory); 177 DCHECK(size); 178 DCHECK(last_modified_time); 179 base::win::ScopedComPtr<IPortableDeviceContent> content = 180 GetDeviceContent(device); 181 if (!content) 182 return false; 183 184 base::win::ScopedComPtr<IPortableDeviceProperties> properties; 185 HRESULT hr = content->Properties(properties.Receive()); 186 if (FAILED(hr)) 187 return false; 188 189 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read; 190 hr = properties_to_read.CreateInstance(__uuidof(PortableDeviceKeyCollection), 191 NULL, 192 CLSCTX_INPROC_SERVER); 193 if (FAILED(hr)) 194 return false; 195 196 if (FAILED(properties_to_read->Add(WPD_OBJECT_CONTENT_TYPE)) || 197 FAILED(properties_to_read->Add(WPD_OBJECT_FORMAT)) || 198 FAILED(properties_to_read->Add(WPD_OBJECT_ORIGINAL_FILE_NAME)) || 199 FAILED(properties_to_read->Add(WPD_OBJECT_NAME)) || 200 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_MODIFIED)) || 201 FAILED(properties_to_read->Add(WPD_OBJECT_DATE_CREATED)) || 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()); 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 225 // Try to get the last modified time, but don't fail if we can't. 226 GetLastModifiedTime(properties_values.get(), last_modified_time); 227 228 int64 object_size = GetObjectSize(properties_values.get()); 229 if (object_size < 0) 230 return false; 231 *size = object_size; 232 return true; 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 MTPDeviceObjectEntry GetMTPDeviceObjectEntry(IPortableDevice* device, 238 const base::string16& object_id) { 239 base::ThreadRestrictions::AssertIOAllowed(); 240 DCHECK(device); 241 DCHECK(!object_id.empty()); 242 base::string16 name; 243 bool is_directory; 244 int64 size; 245 base::Time last_modified_time; 246 MTPDeviceObjectEntry entry; 247 if (GetObjectDetails(device, object_id, &name, &is_directory, &size, 248 &last_modified_time)) { 249 entry = MTPDeviceObjectEntry(object_id, name, is_directory, size, 250 last_modified_time); 251 } 252 return entry; 253 } 254 255 // Gets the entries of the directory specified by |directory_object_id| from 256 // the given MTP |device|. To request a specific object entry, put the object 257 // name in |object_name|. Leave |object_name| blank to request all entries. On 258 // success returns true and set |object_entries|. 259 bool GetMTPDeviceObjectEntries(IPortableDevice* device, 260 const base::string16& directory_object_id, 261 const base::string16& object_name, 262 MTPDeviceObjectEntries* object_entries) { 263 base::ThreadRestrictions::AssertIOAllowed(); 264 DCHECK(device); 265 DCHECK(!directory_object_id.empty()); 266 DCHECK(object_entries); 267 base::win::ScopedComPtr<IEnumPortableDeviceObjectIDs> enum_object_ids = 268 GetDeviceObjectEnumerator(device, directory_object_id); 269 if (!enum_object_ids) 270 return false; 271 272 // Loop calling Next() while S_OK is being returned. 273 const DWORD num_objects_to_request = 10; 274 const bool get_all_entries = object_name.empty(); 275 for (HRESULT hr = S_OK; hr == S_OK;) { 276 DWORD num_objects_fetched = 0; 277 scoped_ptr<base::char16*[]> object_ids( 278 new base::char16*[num_objects_to_request]); 279 hr = enum_object_ids->Next(num_objects_to_request, 280 object_ids.get(), 281 &num_objects_fetched); 282 for (DWORD i = 0; i < num_objects_fetched; ++i) { 283 MTPDeviceObjectEntry entry = 284 GetMTPDeviceObjectEntry(device, object_ids[i]); 285 if (entry.object_id.empty()) 286 continue; 287 if (get_all_entries) { 288 object_entries->push_back(entry); 289 } else if (entry.name == object_name) { 290 object_entries->push_back(entry); // Object entry found. 291 break; 292 } 293 } 294 for (DWORD i = 0; i < num_objects_fetched; ++i) 295 CoTaskMemFree(object_ids[i]); 296 } 297 return true; 298 } 299 300 } // namespace 301 302 base::win::ScopedComPtr<IPortableDevice> OpenDevice( 303 const base::string16& pnp_device_id) { 304 base::ThreadRestrictions::AssertIOAllowed(); 305 DCHECK(!pnp_device_id.empty()); 306 base::win::ScopedComPtr<IPortableDeviceValues> client_info; 307 if (!GetClientInformation(&client_info)) 308 return base::win::ScopedComPtr<IPortableDevice>(); 309 base::win::ScopedComPtr<IPortableDevice> device; 310 HRESULT hr = device.CreateInstance(__uuidof(PortableDevice), NULL, 311 CLSCTX_INPROC_SERVER); 312 if (FAILED(hr)) 313 return base::win::ScopedComPtr<IPortableDevice>(); 314 315 hr = device->Open(pnp_device_id.c_str(), client_info.get()); 316 if (SUCCEEDED(hr)) 317 return device; 318 if (hr == E_ACCESSDENIED) 319 DPLOG(ERROR) << "Access denied to open the device"; 320 return base::win::ScopedComPtr<IPortableDevice>(); 321 } 322 323 base::File::Error GetFileEntryInfo( 324 IPortableDevice* device, 325 const base::string16& object_id, 326 base::File::Info* file_entry_info) { 327 DCHECK(device); 328 DCHECK(!object_id.empty()); 329 DCHECK(file_entry_info); 330 MTPDeviceObjectEntry entry = GetMTPDeviceObjectEntry(device, object_id); 331 if (entry.object_id.empty()) 332 return base::File::FILE_ERROR_NOT_FOUND; 333 334 file_entry_info->size = entry.size; 335 file_entry_info->is_directory = entry.is_directory; 336 file_entry_info->is_symbolic_link = false; 337 file_entry_info->last_modified = entry.last_modified_time; 338 file_entry_info->last_accessed = entry.last_modified_time; 339 file_entry_info->creation_time = base::Time(); 340 return base::File::FILE_OK; 341 } 342 343 bool GetDirectoryEntries(IPortableDevice* device, 344 const base::string16& directory_object_id, 345 MTPDeviceObjectEntries* object_entries) { 346 return GetMTPDeviceObjectEntries(device, directory_object_id, 347 base::string16(), object_entries); 348 } 349 350 HRESULT GetFileStreamForObject(IPortableDevice* device, 351 const base::string16& file_object_id, 352 IStream** file_stream, 353 DWORD* optimal_transfer_size) { 354 base::ThreadRestrictions::AssertIOAllowed(); 355 DCHECK(device); 356 DCHECK(!file_object_id.empty()); 357 base::win::ScopedComPtr<IPortableDeviceContent> content = 358 GetDeviceContent(device); 359 if (!content) 360 return E_FAIL; 361 362 base::win::ScopedComPtr<IPortableDeviceResources> resources; 363 HRESULT hr = content->Transfer(resources.Receive()); 364 if (FAILED(hr)) 365 return hr; 366 return resources->GetStream(file_object_id.c_str(), WPD_RESOURCE_DEFAULT, 367 STGM_READ, optimal_transfer_size, 368 file_stream); 369 } 370 371 DWORD CopyDataChunkToLocalFile(IStream* stream, 372 const base::FilePath& local_path, 373 size_t optimal_transfer_size) { 374 base::ThreadRestrictions::AssertIOAllowed(); 375 DCHECK(stream); 376 DCHECK(!local_path.empty()); 377 if (optimal_transfer_size == 0U) 378 return 0U; 379 DWORD bytes_read = 0; 380 std::string buffer; 381 HRESULT hr = stream->Read(WriteInto(&buffer, optimal_transfer_size + 1), 382 optimal_transfer_size, &bytes_read); 383 // IStream::Read() returns S_FALSE when the actual number of bytes read from 384 // the stream object is less than the number of bytes requested (aka 385 // |optimal_transfer_size|). This indicates the end of the stream has been 386 // reached. 387 if (FAILED(hr)) 388 return 0U; 389 DCHECK_GT(bytes_read, 0U); 390 CHECK_LE(bytes_read, buffer.length()); 391 int data_len = 392 base::checked_cast<int>( 393 std::min(bytes_read, 394 base::checked_cast<DWORD>(buffer.length()))); 395 if (base::AppendToFile(local_path, buffer.c_str(), data_len) != data_len) 396 return 0U; 397 return data_len; 398 } 399 400 base::string16 GetObjectIdFromName(IPortableDevice* device, 401 const base::string16& parent_id, 402 const base::string16& object_name) { 403 MTPDeviceObjectEntries object_entries; 404 if (!GetMTPDeviceObjectEntries(device, parent_id, object_name, 405 &object_entries) || 406 object_entries.empty()) 407 return base::string16(); 408 // TODO(thestig): This DCHECK can fail. Multiple MTP objects can have 409 // the same name. Handle the situation gracefully. Refer to crbug.com/169930 410 // for more details. 411 DCHECK_EQ(1U, object_entries.size()); 412 return object_entries[0].object_id; 413 } 414 415 } // namespace media_transfer_protocol 416