1 /*
2  * Copyright (C) 2001-2012 Jacek Sieka, arnetheduck on gmail point com
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include "stdinc.h"
20 
21 #include "DirectoryListing.h"
22 
23 #include "QueueManager.h"
24 #include "ClientManager.h"
25 
26 #include "StringTokenizer.h"
27 #include "SimpleXML.h"
28 #include "FilteredFile.h"
29 #include "BZUtils.h"
30 #include "CryptoManager.h"
31 #include "ShareManager.h"
32 #include "SimpleXMLReader.h"
33 #include "File.h"
34 
35 #ifdef ff
36 #undef ff
37 #endif
38 
39 namespace dcpp {
40 
DirectoryListing(const HintedUser & aUser)41 DirectoryListing::DirectoryListing(const HintedUser& aUser) :
42 user(aUser),
43 root(new Directory(NULL, Util::emptyString, false, false))
44 {
45 }
46 
~DirectoryListing()47 DirectoryListing::~DirectoryListing() {
48     delete root;
49 }
50 
getUserFromFilename(const string & fileName)51 UserPtr DirectoryListing::getUserFromFilename(const string& fileName) {
52     // General file list name format: [username].[CID].[xml|xml.bz2]
53 
54     string name = Util::getFileName(fileName);
55 
56     // Strip off any extensions
57     if(Util::stricmp(name.c_str() + name.length() - 4, ".bz2") == 0) {
58         name.erase(name.length() - 4);
59     }
60 
61     if(Util::stricmp(name.c_str() + name.length() - 4, ".xml") == 0) {
62         name.erase(name.length() - 4);
63     }
64 
65     // Find CID
66     string::size_type i = name.rfind('.');
67     if(i == string::npos) {
68         return UserPtr();
69     }
70 
71     size_t n = name.length() - (i + 1);
72     // CID's always 39 chars long...
73     if(n != 39)
74         return UserPtr();
75 
76     CID cid(name.substr(i + 1));
77     if(cid.isZero())
78         return UserPtr();
79 
80     return ClientManager::getInstance()->getUser(cid);
81 }
82 
loadFile(const string & name)83 void DirectoryListing::loadFile(const string& name) {
84     string txt;
85 
86     // For now, we detect type by ending...
87     string ext = Util::getFileExt(name);
88 
89     dcpp::File ff(name, dcpp::File::READ, dcpp::File::OPEN);
90     if(Util::stricmp(ext, ".bz2") == 0) {
91         FilteredInputStream<UnBZFilter, false> f(&ff);
92         loadXML(f, false);
93     } else if(Util::stricmp(ext, ".xml") == 0) {
94         loadXML(ff, false);
95     }
96 }
97 
98 class ListLoader : public dcpp::SimpleXMLReader::CallBack {
99 public:
ListLoader(DirectoryListing::Directory * root,bool aUpdating)100     ListLoader(DirectoryListing::Directory* root, bool aUpdating) : cur(root),
101                                                                     base("/"),
102                                                                     inListing(false),
103                                                                     updating(aUpdating),
104                                                                     m_is_mediainfo_list(false),
105                                                                     m_is_first_check_mediainfo_list(false)
106     {
107     }
108 
~ListLoader()109     virtual ~ListLoader() { }
110 
111     virtual void startTag(const string& name, StringPairList& attribs, bool simple);
112     virtual void endTag(const string& name, const string& data);
113 
getBase() const114     const string& getBase() const { return base; }
115 private:
116     DirectoryListing::Directory* cur;
117 
118     StringMap params;
119     string base;
120     bool inListing;
121     bool updating;
122     bool m_is_mediainfo_list;
123     bool m_is_first_check_mediainfo_list;
124 };
125 
updateXML(const string & xml)126 string DirectoryListing::updateXML(const string& xml) {
127     MemoryInputStream mis(xml);
128     return loadXML(mis, true);
129 }
130 
loadXML(InputStream & is,bool updating)131 string DirectoryListing::loadXML(InputStream& is, bool updating) {
132     ListLoader ll(getRoot(), updating);
133 
134     dcpp::SimpleXMLReader(&ll).parse(is, SETTING(MAX_FILELIST_SIZE) ? (size_t)SETTING(MAX_FILELIST_SIZE)*1024*1024 : 0);
135 
136     return ll.getBase();
137 }
138 
139 static const string sFileListing = "FileListing";
140 static const string sBase = "Base";
141 static const string sDirectory = "Directory";
142 static const string sIncomplete = "Incomplete";
143 static const string sFile = "File";
144 static const string sName = "Name";
145 static const string sSize = "Size";
146 static const string sTTH = "TTH";
147 static const string sBR = "BR";
148 static const string sWH = "WH";
149 static const string sMVideo = "MV";
150 static const string sMAudio = "MA";
151 static const string sTS = "TS";
152 static const string sHIT = "HIT";
153 
startTag(const string & name,StringPairList & attribs,bool simple)154 void ListLoader::startTag(const string& name, StringPairList& attribs, bool simple) {
155     if(inListing) {
156         if(name == sFile) {
157             const string& n = getAttrib(attribs, sName, 0);
158             if(n.empty())
159                 return;
160             const string& s = getAttrib(attribs, sSize, 1);
161             if(s.empty())
162                 return;
163             auto size = Util::toInt64(s);
164             const string& h = getAttrib(attribs, sTTH, 2);
165             if(h.empty())
166                 return;
167              TTHValue tth(h); /// @todo verify validity?
168 
169             if(updating) {
170                 // just update the current file if it is already there.
171                 for(auto i = cur->files.cbegin(), iend = cur->files.cend(); i != iend; ++i) {
172                     auto& file = **i;
173                     /// @todo comparisons should be case-insensitive but it takes too long - add a cache
174                     if(file.getTTH() == tth || file.getName() == n) {
175                         file.setName(n);
176                         file.setSize(size);
177                         file.setTTH(tth);
178                         return;
179                     }
180                 }
181             }
182 
183             DirectoryListing::File* f = new DirectoryListing::File(cur, n, size, tth);
184 
185             string l_ts = "";
186 
187             if (!m_is_first_check_mediainfo_list){
188                 m_is_first_check_mediainfo_list = true;
189                 l_ts = getAttrib(attribs, sTS, 3);
190                 m_is_mediainfo_list = !l_ts.empty();
191             }
192             else if (m_is_mediainfo_list) {
193                 l_ts = getAttrib(attribs, sTS, 3);
194             }
195 
196             if (!l_ts.empty()){
197                 f->setTS(atol(l_ts.c_str()));
198                 f->setHit(atol(getAttrib(attribs, sHIT, 3).c_str()));
199                 f->mediaInfo.video_info = getAttrib(attribs, sMVideo, 3);
200                 f->mediaInfo.audio_info = getAttrib(attribs, sMAudio, 3);
201                 f->mediaInfo.resolution = getAttrib(attribs, sWH, 3);
202                 f->mediaInfo.bitrate    = atoi(getAttrib(attribs, sBR, 4).c_str());
203             }
204 
205             cur->files.push_back(f);
206         } else if(name == sDirectory) {
207             const string& n = getAttrib(attribs, sName, 0);
208             if(n.empty()) {
209                 throw SimpleXMLException(_("Directory missing name attribute"));
210             }
211             bool incomp = getAttrib(attribs, sIncomplete, 1) == "1";
212             DirectoryListing::Directory* d = NULL;
213             if(updating) {
214                 for(auto i = cur->directories.begin(); i != cur->directories.end(); ++i) {
215                     /// @todo comparisons should be case-insensitive but it takes too long - add a cache
216                     if((*i)->getName() == n) {
217                         d = *i;
218                         if(!d->getComplete())
219                             d->setComplete(!incomp);
220                         break;
221                     }
222                 }
223             }
224             if(d == NULL) {
225                 d = new DirectoryListing::Directory(cur, n, false, !incomp);
226                 cur->directories.push_back(d);
227             }
228             cur = d;
229 
230             if(simple) {
231                 // To handle <Directory Name="..." />
232                 endTag(name, Util::emptyString);
233             }
234         }
235     } else if(name == sFileListing) {
236         const string& b = getAttrib(attribs, sBase, 2);
237         if(!b.empty() && b[0] == '/' && b[b.size()-1] == '/') {
238             base = b;
239         }
240         StringList sl = StringTokenizer<string>(base.substr(1), '/').getTokens();
241         for(auto i = sl.begin(); i != sl.end(); ++i) {
242             DirectoryListing::Directory* d = NULL;
243             for(auto j = cur->directories.begin(); j != cur->directories.end(); ++j) {
244                 if((*j)->getName() == *i) {
245                     d = *j;
246                     break;
247                 }
248             }
249             if(d == NULL) {
250                 d = new DirectoryListing::Directory(cur, *i, false, false);
251                 cur->directories.push_back(d);
252             }
253             cur = d;
254         }
255         cur->setComplete(true);
256         inListing = true;
257 
258         if(simple) {
259             // To handle <Directory Name="..." />
260             endTag(name, Util::emptyString);
261         }
262     }
263 }
264 
endTag(const string & name,const string &)265 void ListLoader::endTag(const string& name, const string&) {
266     if(inListing) {
267         if(name == sDirectory) {
268             cur = cur->getParent();
269         } else if(name == sFileListing) {
270             // cur should be root now...
271             inListing = false;
272         }
273     }
274 }
275 
getPath(const Directory * d) const276 string DirectoryListing::getPath(const Directory* d) const {
277     if(d == root)
278         return "";
279 
280     string dir;
281     dir.reserve(128);
282     dir.append(d->getName());
283     dir.append(1, '\\');
284 
285     Directory* cur = d->getParent();
286     while(cur!=root) {
287         dir.insert(0, cur->getName() + '\\');
288         cur = cur->getParent();
289     }
290     return dir;
291 }
292 
getLocalPaths(const File * f) const293 StringList DirectoryListing::getLocalPaths(const File* f) const {
294     try {
295         return ShareManager::getInstance()->getRealPaths(Util::toAdcFile(getPath(f) + f->getName()));
296     } catch(const ShareException&) {
297         return StringList();
298     }
299 }
300 
getLocalPaths(const Directory * d) const301 StringList DirectoryListing::getLocalPaths(const Directory* d) const {
302     try {
303         return ShareManager::getInstance()->getRealPaths(Util::toAdcFile(getPath(d)));
304     } catch(const ShareException&) {
305         return StringList();
306     }
307 }
308 
download(Directory * aDir,const string & aTarget,bool highPrio)309 void DirectoryListing::download(Directory* aDir, const string& aTarget, bool highPrio) {
310     string tmp;
311     string target = (aDir == getRoot()) ? aTarget : aTarget + aDir->getName() + PATH_SEPARATOR;
312     // First, recurse over the directories
313     Directory::List& lst = aDir->directories;
314     sort(lst.begin(), lst.end(), Directory::DirSort());
315     for(auto j = lst.begin(); j != lst.end(); ++j) {
316         download(*j, target, highPrio);
317     }
318     // Then add the files
319     File::List& l = aDir->files;
320     sort(l.begin(), l.end(), File::FileSort());
321     for(auto i = aDir->files.begin(); i != aDir->files.end(); ++i) {
322         File* file = *i;
323         try {
324             download(file, target + file->getName(), false, highPrio);
325         } catch(const QueueException&) {
326             // Catch it here to allow parts of directories to be added...
327         } catch(const FileException&) {
328             //..
329         }
330     }
331 }
332 
download(const string & aDir,const string & aTarget,bool highPrio)333 void DirectoryListing::download(const string& aDir, const string& aTarget, bool highPrio) {
334     dcassert(aDir.size() > 2);
335     dcassert(aDir[aDir.size() - 1] == '\\'); // This should not be PATH_SEPARATOR
336     Directory* d = find(aDir, getRoot());
337     if(d)
338         download(d, aTarget, highPrio);
339 }
340 
download(File * aFile,const string & aTarget,bool view,bool highPrio)341 void DirectoryListing::download(File* aFile, const string& aTarget, bool view, bool highPrio) {
342     int flags = (view ? (QueueItem::FLAG_TEXT | QueueItem::FLAG_CLIENT_VIEW) : 0);
343 
344     QueueManager::getInstance()->add(aTarget, aFile->getSize(), aFile->getTTH(), getUser(), flags);
345 
346     if(highPrio)
347         QueueManager::getInstance()->setPriority(aTarget, QueueItem::HIGHEST);
348 }
349 
find(const string & aName,Directory * current)350 DirectoryListing::Directory* DirectoryListing::find(const string& aName, Directory* current) {
351     auto end = aName.find('\\');
352     dcassert(end != string::npos);
353     auto name = aName.substr(0, end);
354 
355     auto i = std::find(current->directories.begin(), current->directories.end(), name);
356     if(i != current->directories.end()) {
357         if(end == (aName.size() - 1))
358             return *i;
359         else
360             return find(aName.substr(end + 1), *i);
361     }
362     return NULL;
363 }
364 
365 struct HashContained {
HashContaineddcpp::HashContained366     HashContained(const DirectoryListing::Directory::TTHSet& l) : tl(l) { }
367     const DirectoryListing::Directory::TTHSet& tl;
operator ()dcpp::HashContained368     bool operator()(const DirectoryListing::File::Ptr i) const {
369         return tl.count((i->getTTH())) && (DeleteFunction()(i), true);
370     }
371 private:
372     HashContained& operator=(HashContained&);
373 };
374 
375 struct DirectoryEmpty {
operator ()dcpp::DirectoryEmpty376     bool operator()(const DirectoryListing::Directory::Ptr i) const {
377         bool r = i->getFileCount() == 0 && i->directories.empty();
378         if (r) DeleteFunction()(i);
379         return r;
380     }
381 };
382 
filterList(DirectoryListing & dirList)383 void DirectoryListing::Directory::filterList(DirectoryListing& dirList) {
384     DirectoryListing::Directory* d = dirList.getRoot();
385 
386     TTHSet l;
387     d->getHashList(l);
388     filterList(l);
389 }
390 
filterList(DirectoryListing::Directory::TTHSet & l)391 void DirectoryListing::Directory::filterList(DirectoryListing::Directory::TTHSet& l) {
392     for(auto i = directories.begin(); i != directories.end(); ++i) (*i)->filterList(l);
393     directories.erase(std::remove_if(directories.begin(),directories.end(),DirectoryEmpty()),directories.end());
394     files.erase(std::remove_if(files.begin(),files.end(),HashContained(l)),files.end());
395 }
396 
getHashList(DirectoryListing::Directory::TTHSet & l)397 void DirectoryListing::Directory::getHashList(DirectoryListing::Directory::TTHSet& l) {
398     for(auto i = directories.begin(); i != directories.end(); ++i) (*i)->getHashList(l);
399     for(auto i = files.begin(); i != files.end(); ++i) l.insert((*i)->getTTH());
400 }
401 
getTotalSize(bool adl)402 int64_t DirectoryListing::Directory::getTotalSize(bool adl) {
403     int64_t x = getSize();
404     for(auto i = directories.begin(); i != directories.end(); ++i) {
405         if(!(adl && (*i)->getAdls()))
406             x += (*i)->getTotalSize(adls);
407     }
408     return x;
409 }
410 
getTotalFileCount(bool adl)411 size_t DirectoryListing::Directory::getTotalFileCount(bool adl) {
412     size_t x = getFileCount();
413     for(auto i = directories.begin(); i != directories.end(); ++i) {
414         if(!(adl && (*i)->getAdls()))
415             x += (*i)->getTotalFileCount(adls);
416     }
417     return x;
418 }
419 } // namespace dcpp
420