1//
2// This file is part of the aMule Project.
3//
4// Copyright (c) 2007-2011 Johannes Krampf ( wuischke@amule.org )
5//
6// Other code by:
7//
8// Angel Vidal Veiga aka Kry <kry@amule.org>
9// * changed class names
10//
11// Marcelo Malheiros <mgmalheiros@gmail.com>
12// * fixed error with FT_FILEHASH
13// * added inital 5 tag/file support
14//
15// Any parts of this program derived from the xMule, lMule or eMule project,
16// or contributed by third-party developers are copyrighted by their
17// respective authors.
18//
19// This program is free software; you can redistribute it and/or modify
20// it under the terms of the GNU General Public License as published by
21// the Free Software Foundation; either version 2 of the License, or
22// (at your option) any later version.
23//
24// This program is distributed in the hope that it will be useful,
25// but WITHOUT ANY WARRANTY; without even the implied warranty of
26// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27// GNU General Public License for more details.
28//
29// You should have received a copy of the GNU General Public License
30// along with this program; if not, write to the Free Software
31// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
32//
33
34
35#include "MuleCollection.h"
36
37
38#include <fstream>
39#include <iostream>
40#include <sstream>
41#include <string>
42#include <vector>
43
44
45CollectionFile::CollectionFile(
46	const std::string &fileName,
47	uint64_t fileSize,
48	const std::string &fileHash)
49:
50m_fileName(fileName),
51m_fileSize(fileSize),
52m_fileHash(fileHash)
53{
54}
55
56
57CMuleCollection::CMuleCollection()
58:
59vCollection(0)
60{
61}
62
63
64CMuleCollection::~CMuleCollection()
65{
66}
67
68
69bool CMuleCollection::Open(const std::string &File)
70{
71	return OpenBinary(File) || OpenText(File);
72}
73
74
75std::string CMuleCollection::GetEd2kLink(size_t index) const
76{
77	if (index >= GetFileCount()) {
78		return "Invalid Index!";
79	}
80
81	std::stringstream retvalue;
82	// ed2k://|file|fileName|fileSize|fileHash|/
83	retvalue
84		<< "ed2k://|file|" << GetFileName(index)
85		<< "|" << GetFileSize(index)
86		<< "|" << GetFileHash(index)
87		<< "|/";
88
89	return retvalue.str();
90}
91
92
93std::string CMuleCollection::GetFileName(size_t index) const
94{
95	if (index >= GetFileCount()) {
96		return "Invalid Index!";
97	}
98
99	std::string retvalue = vCollection[index].m_fileName;
100	if (retvalue.empty()) {
101		return "Empty String!";
102	}
103
104	return retvalue;
105}
106
107
108uint64_t CMuleCollection::GetFileSize(size_t index) const
109{
110	if (index >= GetFileCount()) {
111		return 0;
112	}
113
114	return vCollection[index].m_fileSize;
115}
116
117
118std::string CMuleCollection::GetFileHash(size_t index) const
119{
120	if (index >= GetFileCount()) {
121		return "Invalid Index!";
122	}
123	std::string retvalue = vCollection[index].m_fileHash;
124	if (retvalue.empty()) {
125		return "Empty String!";
126	}
127
128	return retvalue;
129}
130
131template <typename intType>
132intType CMuleCollection::ReadInt(std::ifstream& infile)
133{
134	intType integer = 0;
135	infile.read(reinterpret_cast<char *>(&integer),sizeof(intType));
136	return integer;
137}
138
139std::string CMuleCollection::ReadString(std::ifstream& infile, int TagType = 0x02)
140{
141	if (TagType >= 0x11 && TagType <= 0x20) {
142		std::vector<char> buffer(TagType - 0x10);
143		infile.read(&buffer[0], TagType - 0x10);
144		return buffer.empty() ?
145			std::string() :
146			std::string (buffer.begin(), buffer.end());
147	}
148	if (TagType == 0x02) {
149		uint16_t TagStringSize = ReadInt<uint16_t>(infile);
150		std::vector<char> buffer (TagStringSize);
151		infile.read(&buffer[0], TagStringSize);
152		return buffer.empty() ?
153			std::string() :
154			std::string (buffer.begin(), buffer.end());
155	}
156	return std::string();
157}
158
159bool CMuleCollection::OpenBinary(const std::string &File)
160{
161	std::ifstream infile;
162
163	infile.open(File.c_str(), std::ifstream::in|std::ifstream::binary);
164	if(!infile.is_open()) {
165		return false;
166	}
167
168	uint32_t cVersion = ReadInt<uint32_t>(infile);
169
170	if (!infile.good() ||
171	    ( cVersion != 0x01 && cVersion != 0x02)) {
172			infile.close();
173			return false;
174	}
175
176	uint32_t hTagCount = ReadInt<uint32_t>(infile);
177	if (!infile.good() ||
178	    hTagCount > 3) {
179		infile.close();
180		return false;
181	}
182
183	for (size_t hTi = 0; hTi < hTagCount;hTi++) {
184		 int hTagType = infile.get();
185
186		// hTagFormat == 1 -> FT-value is given
187		uint16_t hTagFormat = ReadInt<uint16_t>(infile);
188		if (hTagFormat != 0x0001) {
189			infile.close();
190			return false;
191		}
192
193		int hTag = infile.get();
194		if (!infile.good()) {
195			infile.close();
196			return false;
197		}
198		switch (hTag) {
199		// FT_FILENAME
200		case 0x01: {
201			std::string fileName = ReadString(infile, hTagType);
202			break;
203		}
204		// FT_COLLECTIONAUTHOR
205		case 0x31: {
206			std::string CollectionAuthor = ReadString(infile, hTagType);
207			break;
208		}
209		// FT_COLLECTIONAUTHORKEY
210		case 0x32: {
211			uint32_t hTagBlobSize = ReadInt<uint32_t>(infile);
212			if (!infile.good()) {
213				infile.close();
214				return false;
215			}
216			std::vector<char> CollectionAuthorKey(hTagBlobSize);
217			infile.read(&CollectionAuthorKey[0], hTagBlobSize);
218			break;
219		}
220		// UNDEFINED TAG
221		default:
222			if (!infile.good()) {
223				infile.close();
224				return false;
225			}
226			break;
227		}
228	}
229
230	uint32_t cFileCount = ReadInt<uint32_t>(infile);
231
232	/*
233	softlimit is set to 1024 to avoid problems with big uint32_t values
234	I don't believe anyone would want to use an emulecollection file
235	to store more than 1024 files, but just raise below value in case
236	you know someone who does.
237	*/
238
239	if(!infile.good() ||
240	   cFileCount > 1024) {
241		infile.close();
242		return false;
243	}
244
245	for (size_t cFi = 0; cFi < cFileCount; ++cFi) {
246		uint32_t fTagCount = ReadInt<uint32_t>(infile);
247
248		if (!infile.good() ||
249		    fTagCount > 5) {
250			infile.close();
251			return false;
252		}
253
254		std::string fileHash = std::string(32, '0');
255		uint64_t fileSize = 0;
256		std::string fileName;
257		std::string FileComment;
258		for(size_t fTi = 0; fTi < fTagCount; ++fTi) {
259			int fTagType = infile.get();
260			if (!infile.good()) {
261				infile.close();
262				return false;
263			}
264
265			int fTag = infile.get();
266			if (!infile.good()) {
267				infile.close();
268				return false;
269			}
270
271			switch (fTag) {
272			// FT_FILEHASH
273			case 0x28: {
274				std::vector<char> bFileHash(16);
275				infile.read(&bFileHash[0], 16);
276				std::string hex = "0123456789abcdef";
277				for (int pos = 0; pos < 16; pos++) {
278					fileHash[pos*2] = hex[((bFileHash[pos] >> 4) & 0xF)];
279					fileHash[(pos*2) + 1] = hex[(bFileHash[pos]) & 0x0F];
280				}
281				break;
282			}
283			// FT_FILESIZE
284			case 0x02: {
285				switch(fTagType) {
286				case 0x83: {
287					fileSize = ReadInt<uint32_t>(infile);
288					break;
289				}
290				case 0x88: {
291					fileSize = ReadInt<uint16_t>(infile);
292					break;
293				}
294				case 0x89: {
295					fileSize = infile.get();
296					break;
297				}
298				case 0x8b: {
299					fileSize = ReadInt<uint64_t>(infile);
300					break;
301				}
302				default: // Invalid file structure
303					infile.close();
304					return false;
305					break;
306				}
307				break;
308			}
309			// FT_FILENAME
310			case 0x01: {
311				fileName = ReadString(infile, fTagType^0x80);
312				break;
313			}
314			// FT_FILECOMMENT
315			case 0xF6: {
316				FileComment = ReadString(infile, fTagType^0x80);
317				break;
318			}
319			// FT_FILERATING
320			case 0xF7: {
321				if (fTagType == 0x89) { // TAGTYPE_UINT8
322					// uint8_t FileRating =
323						infile.get();
324
325				} else {
326					infile.close();
327					return false;
328				}
329				break;
330			}
331			// UNDEFINED TAG
332			default:
333				infile.close();
334				return false;
335				break;
336			}
337			if( !infile.good() ) {
338				infile.close();
339				return false;
340			}
341		}
342		AddFile(fileName, fileSize, fileHash);
343	}
344	infile.close();
345
346	return true;
347}
348
349
350bool CMuleCollection::OpenText(const std::string &File)
351{
352	int numLinks = 0;
353	std::string line;
354	std::ifstream infile;
355
356	infile.open(File.c_str(), std::ifstream::in|std::ifstream::binary);
357	if (!infile.is_open()) {
358		return false;
359	}
360
361	while (getline(infile, line, (char)10 /* LF */)) {
362		int last = line.size()-1;
363		if ((1 < last) && ((char)13 /* CR */ == line.at(last))) {
364			line.erase(last);
365		}
366		if (AddLink(line)) {
367			numLinks++;
368		}
369	}
370	infile.close();
371
372	if(numLinks == 0) {
373		return false;
374	}
375
376	return true;
377}
378
379
380bool CMuleCollection::AddLink(const std::string &Link)
381{
382	// 12345678901234       56       7 + 32 + 89 = 19+32=51
383	// ed2k://|file|fileName|fileSize|fileHash|/
384	if (Link.size() < 51 ||
385	    Link.substr(0,13) != "ed2k://|file|" ||
386	    Link.substr(Link.size()-2) != "|/") {
387		return false;
388	}
389
390	size_t iName = Link.find("|",13);
391	if (iName == std::string::npos) {
392		return false;
393	}
394	std::string fileName = Link.substr(13,iName-13);
395
396	size_t iSize = Link.find("|",iName+1);
397	if (iSize == std::string::npos) {
398		return false;
399	}
400	std::stringstream sFileSize;
401	sFileSize << Link.substr(iName+1,iSize-iName-1);
402	uint64_t fileSize;
403	if ((sFileSize >> std::dec >> fileSize).fail()) {
404		return false;
405	}
406
407	size_t iHash = Link.find("|",iSize+1);
408	if (iHash == std::string::npos) {
409		return false;
410	}
411	std::string fileHash = Link.substr(iSize+1,32);
412
413	return AddFile(fileName, fileSize, fileHash);
414}
415
416
417bool CMuleCollection::AddFile(
418	const std::string &fileName,
419	uint64_t fileSize,
420	const std::string &fileHash)
421{
422	if (fileName == "" ||
423	    fileSize == 0 ||
424	    fileSize > 0xffffffffLL ||
425	    !IsValidHash(fileHash)) {
426		return false;
427	}
428
429	vCollection.push_back(
430		CollectionFile(fileName, fileSize, fileHash));
431	return true;
432}
433
434
435bool CMuleCollection::IsValidHash(const std::string &fileHash)
436{
437	if (fileHash.size() != 32 || fileHash == "") {
438		return false;
439	}
440
441	// fileHash needs to be a valid MD4Hash
442	std::string hex = "0123456789abcdefABCDEF";
443	for(size_t i = 0; i < fileHash.size(); ++i) {
444		if (hex.find(fileHash[i]) == std::string::npos) {
445			return false;
446		}
447	}
448	return true;
449}
450
451