Home | History | Annotate | Download | only in media_galleries
      1 // Copyright 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/utility/media_galleries/itunes_library_parser.h"
      6 
      7 #include <string>
      8 
      9 #include "base/logging.h"
     10 #include "base/stl_util.h"
     11 #include "base/strings/string16.h"
     12 #include "base/strings/string_number_conversions.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/common/media_galleries/itunes_xml_utils.h"
     15 #include "third_party/libxml/chromium/libxml_utils.h"
     16 #include "url/gurl.h"
     17 #include "url/url_canon.h"
     18 #include "url/url_util.h"
     19 
     20 namespace itunes {
     21 
     22 namespace {
     23 
     24 struct TrackInfo {
     25   uint64 id;
     26   base::FilePath location;
     27   std::string artist;
     28   std::string album;
     29 };
     30 
     31 // Seek to the start of a tag and read the value into |result| if the node's
     32 // name is |node_name|.
     33 bool ReadSimpleValue(XmlReader* reader, const std::string& node_name,
     34                      std::string* result) {
     35   if (!SkipToNextElement(reader))
     36       return false;
     37   if (reader->NodeName() != node_name)
     38     return false;
     39   return reader->ReadElementContent(result);
     40 }
     41 
     42 // Get the value out of a string node.
     43 bool ReadString(XmlReader* reader, std::string* result) {
     44   return ReadSimpleValue(reader, "string", result);
     45 }
     46 
     47 // Get the value out of an integer node.
     48 bool ReadInteger(XmlReader* reader, uint64* result) {
     49   std::string value;
     50   if (!ReadSimpleValue(reader, "integer", &value))
     51     return false;
     52   return base::StringToUint64(value, result);
     53 }
     54 
     55 // Walk through a dictionary filling in |result| with track information. Return
     56 // true if at least the id and location where found (artist and album may be
     57 // empty).  In either case, the cursor is advanced out of the dictionary.
     58 bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) {
     59   DCHECK(result);
     60   if (reader->NodeName() != "dict")
     61     return false;
     62 
     63   int dict_content_depth = reader->Depth() + 1;
     64   // Advance past the dict node and into the body of the dictionary.
     65   if (!reader->Read())
     66     return false;
     67 
     68   bool found_id = false;
     69   bool found_location = false;
     70   bool found_artist = false;
     71   bool found_album_artist = false;
     72   bool found_album = false;
     73   while (reader->Depth() >= dict_content_depth &&
     74          !(found_id && found_location && found_album_artist && found_album)) {
     75     if (!SeekToNodeAtCurrentDepth(reader, "key"))
     76       break;
     77     std::string found_key;
     78     if (!reader->ReadElementContent(&found_key))
     79       break;
     80     DCHECK_EQ(dict_content_depth, reader->Depth());
     81 
     82     if (found_key == "Track ID") {
     83       if (found_id)
     84         break;
     85       if (!ReadInteger(reader, &result->id))
     86         break;
     87       found_id = true;
     88     } else if (found_key == "Location") {
     89       if (found_location)
     90         break;
     91       std::string value;
     92       if (!ReadString(reader, &value))
     93         break;
     94       GURL url(value);
     95       if (!url.SchemeIsFile())
     96         break;
     97       url_canon::RawCanonOutputW<1024> decoded_location;
     98       url_util::DecodeURLEscapeSequences(url.path().c_str() + 1,  // Strip /.
     99                                          url.path().length() - 1,
    100                                          &decoded_location);
    101 #if defined(OS_WIN)
    102       string16 location(decoded_location.data(), decoded_location.length());
    103 #else
    104       string16 location16(decoded_location.data(), decoded_location.length());
    105       std::string location = "/" + UTF16ToUTF8(location16);
    106 #endif
    107       result->location = base::FilePath(location);
    108       found_location = true;
    109     } else if (found_key == "Artist") {
    110       if (found_artist || found_album_artist)
    111         break;
    112       if (!ReadString(reader, &result->artist))
    113         break;
    114       found_artist = true;
    115     } else if (found_key == "Album Artist") {
    116       if (found_album_artist)
    117         break;
    118       result->artist.clear();
    119       if (!ReadString(reader, &result->artist))
    120         break;
    121       found_album_artist = true;
    122     } else if (found_key == "Album") {
    123       if (found_album)
    124         break;
    125       if (!ReadString(reader, &result->album))
    126         break;
    127       found_album = true;
    128     } else {
    129       if (!SkipToNextElement(reader))
    130         break;
    131       if (!reader->Next())
    132         break;
    133     }
    134   }
    135 
    136   // Seek to the end of the dictionary
    137   while (reader->Depth() >= dict_content_depth)
    138     reader->Next();
    139 
    140   return found_id && found_location;
    141 }
    142 
    143 }  // namespace
    144 
    145 ITunesLibraryParser::ITunesLibraryParser() {}
    146 ITunesLibraryParser::~ITunesLibraryParser() {}
    147 
    148 // static
    149 std::string ITunesLibraryParser::ReadITunesLibraryXmlFile(
    150     const base::PlatformFile file) {
    151   std::string result;
    152   if (file == base::kInvalidPlatformFileValue)
    153     return result;
    154 
    155   // A "reasonable" artificial limit.
    156   // TODO(vandebo): Add a UMA to figure out what common values are.
    157   const int64 kMaxLibraryFileSize = 150 * 1024 * 1024;
    158   base::PlatformFileInfo file_info;
    159   if (!base::GetPlatformFileInfo(file, &file_info) ||
    160       file_info.size > kMaxLibraryFileSize) {
    161     base::ClosePlatformFile(file);
    162     return result;
    163   }
    164 
    165   result.resize(file_info.size);
    166   int bytes_read =
    167       base::ReadPlatformFile(file, 0, string_as_array(&result), file_info.size);
    168   if (bytes_read != file_info.size)
    169     result.clear();
    170 
    171   base::ClosePlatformFile(file);
    172   return result;
    173 }
    174 
    175 bool ITunesLibraryParser::Parse(const std::string& library_xml) {
    176   XmlReader reader;
    177 
    178   if (!reader.Load(library_xml))
    179     return false;
    180 
    181   // Find the plist node and then search within that tag.
    182   if (!SeekToNodeAtCurrentDepth(&reader, "plist"))
    183     return false;
    184   if (!reader.Read())
    185     return false;
    186 
    187   if (!SeekToNodeAtCurrentDepth(&reader, "dict"))
    188     return false;
    189 
    190   if (!SeekInDict(&reader, "Tracks"))
    191     return false;
    192 
    193   // Once inside the Tracks dict, we expect track dictionaries keyed by id. i.e.
    194   //   <key>Tracks</key>
    195   //   <dict>
    196   //     <key>160</key>
    197   //     <dict>
    198   //       <key>Track ID</key><integer>160</integer>
    199   if (!SeekToNodeAtCurrentDepth(&reader, "dict"))
    200     return false;
    201   int tracks_dict_depth = reader.Depth() + 1;
    202   if (!reader.Read())
    203     return false;
    204 
    205   // Once parsing has gotten this far, return whatever is found, even if
    206   // some of the data isn't extracted just right.
    207   bool no_errors = true;
    208   bool track_found = false;
    209   while (reader.Depth() >= tracks_dict_depth) {
    210     if (!SeekToNodeAtCurrentDepth(&reader, "key"))
    211       return track_found;
    212     std::string key;  // Should match track id below.
    213     if (!reader.ReadElementContent(&key))
    214       return track_found;
    215     uint64 id;
    216     bool id_valid = base::StringToUint64(key, &id);
    217     if (!reader.SkipToElement())
    218       return track_found;
    219 
    220     TrackInfo track_info;
    221     if (GetTrackInfoFromDict(&reader, &track_info) &&
    222         id_valid &&
    223         id == track_info.id) {
    224       if (track_info.artist.empty())
    225         track_info.artist = "Unknown Artist";
    226       if (track_info.album.empty())
    227         track_info.album = "Unknown Album";
    228       parser::Track track(track_info.id, track_info.location);
    229       library_[track_info.artist][track_info.album].insert(track);
    230       track_found = true;
    231     } else {
    232       no_errors = false;
    233     }
    234   }
    235 
    236   return track_found || no_errors;
    237 }
    238 
    239 }  // namespace itunes
    240