Home | History | Annotate | Download | only in win
      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