Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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/fileapi/itunes_library_parser.h" | |
| 6 | |
| 7 #include <string> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "base/string16.h" | |
| 11 #include "base/strings/string_number_conversions.h" | |
| 12 #include "base/strings/utf_string_conversions.h" | |
| 13 #include "googleurl/src/gurl.h" | |
| 14 #include "googleurl/src/url_canon.h" | |
| 15 #include "googleurl/src/url_util.h" | |
| 16 #include "third_party/libxml/chromium/libxml_utils.h" | |
| 17 | |
| 18 namespace itunes { | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 struct TrackInfo { | |
| 23 uint32_t id; | |
| 24 base::FilePath location; | |
| 25 std::string artist; | |
| 26 std::string album; | |
| 27 }; | |
| 28 | |
| 29 // Traverse |reader| looking for a node named |name| at the current depth | |
| 30 // of |reader|. | |
| 31 bool SeekToNodeAtCurrentDepth(XmlReader* reader, const std::string& name) { | |
| 32 int depth = reader->Depth(); | |
| 33 do { | |
| 34 if (!reader->SkipToElement()) { | |
| 35 // SkipToElement returns false if the current node is an end element, | |
| 36 // try to advance to the next element and then try again. | |
| 37 if (!reader->Read() || !reader->SkipToElement()) | |
| 38 return false; | |
| 39 } | |
| 40 DCHECK_EQ(depth, reader->Depth()); | |
| 41 if (reader->NodeName() == name) | |
| 42 return true; | |
| 43 } while (reader->Next()); | |
| 44 | |
| 45 return false; | |
| 46 } | |
| 47 | |
| 48 // Search within the dict for |key|. | |
| 49 bool SeekInDict(XmlReader* reader, const std::string& key) { | |
| 50 DCHECK_EQ("dict", reader->NodeName()); | |
| 51 | |
| 52 int dict_content_depth = reader->Depth() + 1; | |
| 53 // Advance past the dict node and into the body of the dictionary. | |
| 54 if (!reader->Read()) | |
| 55 return false; | |
| 56 | |
| 57 while (reader->Depth() >= dict_content_depth) { | |
| 58 if (!SeekToNodeAtCurrentDepth(reader, "key")) | |
| 59 return false; | |
| 60 std::string found_key; | |
| 61 if (!reader->ReadElementContent(&found_key)) | |
| 62 return false; | |
| 63 DCHECK_EQ(dict_content_depth, reader->Depth()); | |
| 64 if (found_key == key) | |
| 65 return true; | |
| 66 } | |
| 67 return false; | |
| 68 } | |
| 69 | |
| 70 // Seek to the start of a tag and read the value into |result| if the node's | |
| 71 // name is |node_name|. | |
| 72 bool ReadSimpleValue(XmlReader* reader, const std::string& node_name, | |
| 73 std::string* result) { | |
| 74 if (!reader->SkipToElement()) { | |
| 75 // SkipToElement returns false if the current node is an end element, | |
| 76 // try to advance to the next element and then try again. | |
| 77 if (!reader->Read() || !reader->SkipToElement()) | |
| 78 return false; | |
| 79 } | |
| 80 if (reader->NodeName() != node_name) | |
| 81 return false; | |
| 82 return reader->ReadElementContent(result); | |
| 83 } | |
| 84 | |
| 85 // Get the value out of a string node. | |
| 86 bool ReadString(XmlReader* reader, std::string* result) { | |
| 87 return ReadSimpleValue(reader, "string", result); | |
| 88 } | |
| 89 | |
| 90 // Get the value out of an integer node. | |
| 91 bool ReadInteger(XmlReader* reader, uint32_t* result) { | |
| 92 std::string value; | |
| 93 if (!ReadSimpleValue(reader, "integer", &value)) | |
| 94 return false; | |
| 95 return base::StringToUint(value, result); | |
| 96 } | |
| 97 | |
| 98 // Walk through a dictionary filling in |result| with track information. Return | |
| 99 // true if it was all found, false otherwise. In either case, the curser is | |
|
Lei Zhang
2013/05/31 04:34:35
typo - cursor
vandebo (ex-Chrome)
2013/05/31 21:41:12
Done.
| |
| 100 // advanced out of the dictionary. | |
| 101 bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) { | |
| 102 DCHECK(result != NULL); | |
|
Lei Zhang
2013/05/31 04:34:35
nit: DCHECK(result);
vandebo (ex-Chrome)
2013/05/31 21:41:12
Done.
| |
| 103 DCHECK_EQ("dict", reader->NodeName()); | |
|
Lei Zhang
2013/05/31 04:34:35
Can there be a malformed XML file that fails this
vandebo (ex-Chrome)
2013/05/31 21:41:12
Done.
| |
| 104 | |
| 105 int dict_content_depth = reader->Depth() + 1; | |
|
Lei Zhang
2013/05/31 04:34:35
Can someone create a big, malicious XML file that
vandebo (ex-Chrome)
2013/05/31 21:41:12
Probably, but to what end? If they control the XM
Lei Zhang
2013/05/31 22:06:53
With the way it's used, I just want to make sure w
vandebo (ex-Chrome)
2013/05/31 22:21:12
In infinite loop isn't possible because we'll alwa
| |
| 106 // Advance past the dict node and into the body of the dictionary. | |
| 107 if (!reader->Read()) | |
| 108 return false; | |
| 109 | |
| 110 bool found_id = false; | |
| 111 bool found_location = false; | |
| 112 bool found_artist = false; | |
| 113 bool found_album = false; | |
| 114 while (reader->Depth() >= dict_content_depth && | |
| 115 !(found_id && found_location && found_artist && found_album)) { | |
| 116 if (!SeekToNodeAtCurrentDepth(reader, "key")) | |
| 117 break; | |
| 118 std::string found_key; | |
| 119 if (!reader->ReadElementContent(&found_key)) | |
| 120 break; | |
| 121 DCHECK_EQ(dict_content_depth, reader->Depth()); | |
| 122 | |
| 123 if (found_key == "Track ID") { | |
| 124 if (found_id) | |
| 125 break; | |
| 126 if (!ReadInteger(reader, &result->id)) | |
| 127 break; | |
| 128 found_id = true; | |
| 129 } else if (found_key == "Location") { | |
| 130 // TODO(vandebo): strip file::/localhost/. | |
| 131 if (found_location) | |
| 132 break; | |
|
Lei Zhang
2013/05/31 04:34:35
Are you breaking out of the while loop because it
vandebo (ex-Chrome)
2013/05/31 21:41:12
Yes. We expect one entry for each these fields. If
| |
| 133 std::string value; | |
| 134 if (!ReadString(reader, &value)) | |
| 135 break; | |
| 136 GURL url(value); | |
| 137 if (!url.SchemeIsFile() || url.host() != "localhost") | |
| 138 break; | |
| 139 url_canon::RawCanonOutputW<1024> decoded_location; | |
| 140 url_util::DecodeURLEscapeSequences(url.path().c_str() + 1, // Strip /. | |
| 141 url.path().length() - 1, | |
| 142 &decoded_location); | |
| 143 #if defined(OS_WIN) | |
| 144 string16 location(decoded_location.data(), decoded_location.length()); | |
| 145 #else | |
| 146 string16 location16(decoded_location.data(), decoded_location.length()); | |
| 147 std::string location = UTF16ToUTF8(location16); | |
| 148 #endif | |
| 149 result->location = base::FilePath(location); | |
| 150 found_location = true; | |
| 151 } else if (found_key == "Album Artist") { | |
| 152 if (found_artist) | |
| 153 break; | |
| 154 if (!ReadString(reader, &result->artist)) | |
| 155 break; | |
| 156 found_artist = true; | |
| 157 } else if (found_key == "Album") { | |
| 158 if (found_album) | |
| 159 break; | |
| 160 if (!ReadString(reader, &result->album)) | |
| 161 break; | |
| 162 found_album = true; | |
| 163 } else { | |
| 164 if (!reader->SkipToElement()) { | |
| 165 // SkipToElement returns false if the current node is an end element, | |
| 166 // try to advance to the next element and then try again. | |
| 167 if (!reader->Read() || !reader->SkipToElement()) | |
| 168 break; | |
| 169 } | |
| 170 std::string value; | |
|
Lei Zhang
2013/05/31 04:34:35
Explain why this value is read into and then ignor
vandebo (ex-Chrome)
2013/05/31 21:41:12
Done.
| |
| 171 if (!reader->ReadElementContent(&value)) | |
| 172 break; | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 // Seek to the end of the dictionary | |
| 177 while (reader->Depth() >= dict_content_depth) { | |
| 178 reader->Next(); | |
| 179 } | |
| 180 | |
| 181 return found_id && found_location && found_artist && found_album; | |
| 182 } | |
| 183 | |
| 184 } // namespace | |
| 185 | |
| 186 ITunesLibraryParser::Track::Track(uint32_t id, const base::FilePath& location) | |
| 187 : id(id), | |
| 188 location(location) { | |
| 189 } | |
| 190 | |
| 191 bool ITunesLibraryParser::Track::operator<(const Track& other) const { | |
| 192 return id < other.id; | |
| 193 } | |
| 194 | |
| 195 ITunesLibraryParser::ITunesLibraryParser() {} | |
| 196 | |
| 197 bool ITunesLibraryParser::Parse(const std::string& library_xml) { | |
| 198 XmlReader reader; | |
| 199 | |
| 200 if (!reader.Load(library_xml)) | |
| 201 return false; | |
| 202 | |
| 203 // Find the plist node and then search within that tag. | |
| 204 if (!SeekToNodeAtCurrentDepth(&reader, "plist")) | |
| 205 return false; | |
| 206 if (!reader.Read()) | |
| 207 return false; | |
| 208 | |
| 209 if (!SeekToNodeAtCurrentDepth(&reader, "dict")) | |
| 210 return false; | |
| 211 | |
| 212 if (!SeekInDict(&reader, "Tracks")) | |
| 213 return false; | |
| 214 | |
| 215 // Once inside the Tracks dict, we expect track dictionaries keyed by id. i.e. | |
| 216 // <key>Tracks</key> | |
| 217 // <dict> | |
| 218 // <key>160</key> | |
| 219 // <dict> | |
| 220 // <key>Track ID</key><integer>160</integer> | |
| 221 if (!SeekToNodeAtCurrentDepth(&reader, "dict")) | |
| 222 return false; | |
| 223 int tracks_dict_depth = reader.Depth() + 1; | |
| 224 if (!reader.Read()) | |
| 225 return false; | |
| 226 | |
| 227 // Once parsing has gotten this far, return what ever is found, even if | |
| 228 // some of the data isn't extracted just right. | |
| 229 bool no_errors = true; | |
| 230 bool track_found = false; | |
| 231 while (reader.Depth() >= tracks_dict_depth) { | |
| 232 if (!SeekToNodeAtCurrentDepth(&reader, "key")) { | |
| 233 no_errors = false; | |
|
Lei Zhang
2013/05/31 04:34:35
why not just return track_found at this point?
vandebo (ex-Chrome)
2013/05/31 21:41:12
Done.
| |
| 234 break; | |
| 235 } | |
| 236 std::string key; // Should match track id below. | |
| 237 if (!reader.ReadElementContent(&key)) { | |
| 238 no_errors = false; | |
| 239 break; | |
| 240 } | |
| 241 uint32_t id; | |
| 242 base::StringToUint(key, &id); | |
|
Lei Zhang
2013/05/31 04:34:35
Check return result.
vandebo (ex-Chrome)
2013/05/31 21:41:12
Done.
| |
| 243 if (!reader.Read()) { | |
| 244 no_errors = false; | |
| 245 break; | |
| 246 } | |
| 247 | |
| 248 TrackInfo track_info; | |
| 249 if (GetTrackInfoFromDict(&reader, &track_info) && id == track_info.id) { | |
| 250 Track track(track_info.id, track_info.location); | |
| 251 library_[track_info.artist][track_info.album].insert(track); | |
|
Lei Zhang
2013/05/31 04:34:35
Do you care if there already exists a track with t
vandebo (ex-Chrome)
2013/05/31 21:41:12
ids should be unique. If that's not the case, I'm
| |
| 252 track_found = true; | |
| 253 } else { | |
| 254 no_errors = false; | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 return track_found || no_errors; | |
| 259 } | |
| 260 | |
| 261 } // namespace itunes | |
| OLD | NEW |