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