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/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