1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2003-2011 Alo Sarv ( madcat_@users.sourceforge.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
10 //
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
15 //
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
24 //
25 
26 const int versionMajor		= 1;
27 const int versionMinor		= 5;
28 const int versionRevision	= 1;
29 
30 #include <cstdlib>
31 #include <sstream>
32 #include <iostream>
33 #include <fstream>
34 
35 #ifdef __APPLE__
36 	#include <CoreServices/CoreServices.h>
37 #elif defined(_WIN32)
38 	#include <winerror.h>
39 	#include <shlobj.h>
40 	#include <shlwapi.h>
41 #endif
42 
43 #include "FileLock.h"
44 #include "MagnetURI.h"
45 #include "MuleCollection.h"
46 
47 using std::string;
48 
GetLinksFilePath(const string & configDir)49 static string GetLinksFilePath(const string& configDir)
50 {
51 	if (!configDir.empty()) {
52 #ifdef _WIN32
53 		char buffer[MAX_PATH + 1];
54 		configDir.copy(buffer, MAX_PATH);
55 		if (PathAppendA(buffer, "ED2KLinks")) {
56 			string strDir;
57 			strDir.assign(buffer);
58 			return strDir;
59 		}
60 #else
61 		string strDir = configDir;
62 		if (strDir.at(strDir.length() - 1) != '/') {
63 			strDir += '/';
64 		}
65 		return strDir + "ED2KLinks";
66 #endif
67 	}
68 
69 
70 #ifdef __APPLE__
71 
72 	std::string strDir;
73 
74 	FSRef fsRef;
75 	if (FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &fsRef) == noErr) {
76 		CFURLRef urlRef = CFURLCreateFromFSRef(NULL, &fsRef);
77 		if (urlRef != NULL) {
78 			UInt8 buffer[PATH_MAX + 1];
79 			if (CFURLGetFileSystemRepresentation(urlRef, true, buffer, sizeof(buffer))) {
80 				strDir.assign((char*) buffer);
81 			}
82 			CFRelease(urlRef) ;
83 		}
84 	}
85 
86 	return strDir + "/aMule/ED2KLinks";
87 
88 #elif defined(_WIN32)
89 
90 	std::string strDir;
91 	LPITEMIDLIST pidl;
92 
93 	HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pidl);
94 
95 	if (SUCCEEDED(hr)) {
96 		char buffer[MAX_PATH + 1];
97 		if (SHGetPathFromIDListA(pidl, buffer)) {
98 			if (PathAppendA(buffer, "aMule\\ED2KLinks")) {
99 				strDir.assign(buffer);
100 			}
101 		}
102 	}
103 
104 	if (pidl) {
105 		LPMALLOC pMalloc;
106 		SHGetMalloc(&pMalloc);
107 		if (pMalloc) {
108 			pMalloc->Free(pidl);
109 			pMalloc->Release();
110 		}
111 	}
112 
113 	return strDir;
114 
115 #else
116 
117 	return string( getenv("HOME") ) + "/.aMule/ED2KLinks";
118 
119 #endif
120 }
121 
122 /**
123  * Converts a hexadecimal number to a char.
124  *
125  * @param hex The hex-number, must be at most 2 digits long.
126  * @return The resulting char or \0 if conversion failed.
127  */
HexToDec(const string & hex)128 static char HexToDec( const string& hex )
129 {
130 	char result = 0;
131 
132 	for ( size_t i = 0; i < hex.length(); ++i ) {
133 		char cur = toupper( hex.at(i) );
134 		result *= 16;
135 
136 		if ( isdigit( cur ) ) {
137 			result += cur - '0';
138 		} else if ( cur >= 'A' && cur <= 'F' ) {
139 			result += cur - 'A' + 10;
140 		} else {
141 			return '\0';
142 		}
143 	}
144 
145 	return result;
146 }
147 
148 
149 /**
150  * This function converts all valid HTML escape-codes to their corresponding chars.
151  *
152  * @param str The string to unescape.
153  * @return The unescaped version of the input string.
154  */
Unescape(const string & str)155 static string Unescape( const string& str )
156 {
157 	string result;
158 	result.reserve( str.length() );
159 
160 	for ( size_t i = 0; i < str.length(); ++i ) {
161 		if ( str.at(i) == '%' && ( i + 2 < str.length() ) ) {
162 			char unesc = HexToDec( str.substr( i + 1, 2 ) );
163 
164 			if ( unesc ) {
165 				i += 2;
166 
167 				result += unesc;
168 			} else {
169 				// If conversion failed, then we just add the escape-code
170 				// and continue past it like nothing happened.
171 				result += str.at(i);
172 			}
173 		} else {
174 			result += str.at(i);
175 		}
176 	}
177 
178 	return result;
179 }
180 
181 
182 /**
183  * Returns the string with whitespace stripped from both ends.
184  */
strip(const string & str)185 static string strip( const string& str )
186 {
187 	size_t first = 0;
188 	size_t last  = str.length() - 1;
189 
190 	// A simple but no very optimized way to narrow down the
191 	// usable text within the string.
192 	while ( first <= last ) {
193 		if ( isspace( str.at(first) ) ) {
194 			first++;
195 		} else if ( isspace( str.at(last) ) ) {
196 			last--;
197 		} else {
198 			break;
199 		}
200 	};
201 
202 	return str.substr( first, 1 + last - first );
203 }
204 
205 
206 /**
207  * Returns true if the string is a valid number.
208  */
isNumber(const string & str)209 static bool isNumber( const string& str )
210 {
211 	for ( size_t i = 0; i < str.length(); i++ ) {
212 		if ( !isdigit( str.at(i) ) ) {
213 			return false;
214 		}
215 	}
216 
217 	return str.length() > 0;
218 }
219 
220 
221 /**
222  * Returns true if the string is a valid Base16 representation of a MD4 Hash.
223  */
isMD4Hash(const string & str)224 static bool isMD4Hash( const string& str )
225 {
226 	for ( size_t i = 0; i < str.length(); i++ ) {
227 		const char c = toupper( str.at(i) );
228 
229 		if ( !isdigit( c ) && ( c < 'A' || c > 'F' ) ) {
230 			return false;
231 		}
232 	}
233 
234 	return str.length() == 32;
235 }
236 
237 
238 /**
239  * Returns a description of the current version of "ed2k".
240  */
getVersion()241 static string getVersion()
242 {
243 	std::ostringstream v;
244 
245 	v << "aMule ED2k link parser v"
246 		<< versionMajor << "."
247 		<< versionMinor << "."
248 		<< versionRevision;
249 
250 	return v.str();
251 }
252 
253 
254 /**
255  * Helper-function for printing link-errors.
256  */
badLink(const string & type,const string & err,const string & uri)257 static void badLink( const string& type, const string& err, const string& uri )
258 {
259 	std::cout << "Invalid " << type << "-link, " + err << ":\n"
260 		<< "\t" << uri << std::endl;
261 }
262 
263 
264 /**
265  * Writes a string to the ED2KLinks file.
266  *
267  * If errors are detected, it will terminate the program.
268  */
writeLink(const string & uri,const string & config_dir)269 static void writeLink( const string& uri, const string& config_dir )
270 {
271 	// Attempt to lock the ED2KLinks file
272 	static CFileLock lock(GetLinksFilePath(config_dir));
273 	static std::ofstream file;
274 
275 	if (!file.is_open()) {
276 		string path = GetLinksFilePath(config_dir);
277 		file.open( path.c_str(), std::ofstream::out | std::ofstream::app );
278 
279 		if (!file.is_open()) {
280 			std::cout << "ERROR! Failed to open " << path << " for writing!" << std::endl;
281 			exit(1);
282 		}
283 	}
284 
285 	file << uri << std::endl;
286 
287 	std::cout << "Link successfully queued." << std::endl;
288 }
289 
290 
291 /**
292  * Writes the the specified URI to the ED2KLinks file if it is a valid file-link.
293  *
294  * @param uri The URI to check.
295  * @return True if the URI was written, false otherwise.
296  */
checkFileLink(const string & uri)297 static bool checkFileLink( const string& uri )
298 {
299 	if ( uri.substr( 0, 13 ) == "ed2k://|file|" ) {
300 		size_t base_off = 12;
301 		size_t name_off = uri.find( '|', base_off + 1 );
302 		size_t size_off = uri.find( '|', name_off + 1 );
303 		size_t hash_off = uri.find( '|', size_off + 1 );
304 
305 		bool valid = true;
306 		valid &= ( base_off < name_off );
307 		valid &= ( name_off < size_off );
308 		valid &= ( size_off < hash_off );
309 		valid &= ( hash_off != string::npos );
310 
311 		if ( !valid ) {
312 			badLink( "file", "invalid link format", uri );
313 			return false;
314 		}
315 
316 		string name = uri.substr( base_off + 1, name_off - base_off - 1 );
317 		string size = uri.substr( name_off + 1, size_off - name_off - 1 );
318 		string hash = uri.substr( size_off + 1, hash_off - size_off - 1 );
319 
320 		if ( name.empty() ) {
321 			badLink( "file", "no name specified", uri );
322 			return false;
323 		}
324 
325 		if ( !isNumber( size ) ) {
326 			badLink( "file", "invalid size", uri );
327 			return false;
328 		}
329 
330 		if ( !isMD4Hash( hash ) ) {
331 			badLink( "file", "invalid MD4 hash", uri );
332 			return false;
333 		}
334 
335 		return true;
336 	}
337 
338 	return false;
339 }
340 
341 
342 /**
343  * Writes the the specified URI to the ED2KLinks file if it is a valid server-link.
344  *
345  * @param uri The URI to check.
346  * @return True if the URI was written, false otherwise.
347  */
checkServerLink(const string & uri)348 static bool checkServerLink( const string& uri )
349 {
350 	if ( uri.substr( 0, 15 ) == "ed2k://|server|" ) {
351 		size_t base_off = 14;
352 		size_t host_off = uri.find( '|', base_off + 1 );
353 		size_t port_off = uri.find( '|', host_off + 1 );
354 
355 		bool valid = true;
356 		valid &= ( base_off < host_off );
357 		valid &= ( host_off < port_off );
358 		valid &= ( port_off != string::npos );
359 
360 		if ( !valid || uri.at( port_off + 1 ) != '/' ) {
361 			badLink( "server", "invalid link format", uri );
362 			return false;
363 		}
364 
365 		string host = uri.substr( base_off + 1, host_off - base_off - 1 );
366 		string port = uri.substr( host_off + 1, port_off - host_off - 1 );
367 
368 		if ( host.empty() ) {
369 			badLink( "server", "no hostname specified", uri );
370 			return false;
371 		}
372 
373 		if ( !isNumber( port ) ) {
374 			badLink( "server", "invalid port", uri );
375 			return false;
376 		}
377 
378 		return true;
379 	}
380 
381 	return false;
382 }
383 
384 
385 /**
386  * Writes the the specified URI to the ED2KLinks file if it is a valid serverlist-link.
387  *
388  * @param uri The URI to check.
389  * @return True if the URI was written, false otherwise.
390  */
checkServerListLink(const string & uri)391 static bool checkServerListLink( const string& uri )
392 {
393 	if ( uri.substr( 0, 19 ) == "ed2k://|serverlist|" ) {
394 		size_t base_off = 19;
395 		size_t path_off = uri.find( '|', base_off + 1 );
396 
397 		bool valid = true;
398 		valid &= ( base_off < path_off );
399 		valid &= ( path_off != string::npos );
400 
401 		if ( !valid ) {
402 			badLink( "serverlist", "invalid link format", uri );
403 			return false;
404 		}
405 
406 		string path = uri.substr( base_off + 1, path_off - base_off - 1 );
407 
408 		if ( path.empty() ) {
409 			badLink( "serverlist", "no hostname specified", uri );
410 			return false;
411 		}
412 
413 		return true;
414 	}
415 
416 	return false;
417 }
418 
419 
main(int argc,char * argv[])420 int main(int argc, char *argv[])
421 {
422 	bool errors = false;
423 	string config_path;
424 	string category = "";
425 	for ( int i = 1; i < argc; i++ ) {
426 		string arg = strip( Unescape( string( argv[i] ) ) );
427 
428 		if ( arg.compare(0, 7, "magnet:" ) == 0 ) {
429 			string ed2k = CMagnetED2KConverter(arg);
430 			if ( ed2k.empty() ) {
431 				std::cerr << "Cannot convert magnet URI to ed2k:\n\t" << arg << std::endl;
432 				errors = true;
433 				continue;
434 			} else {
435 				arg = ed2k;
436 			}
437 		}
438 
439 		if ( arg.substr( 0, 8 ) == "ed2k://|" ) {
440 			// Ensure the URI is valid
441 			if ( arg.at( arg.length() - 1 ) != '/' ) {
442 				arg += '/';
443 			}
444 
445 			string type = arg.substr( 8, arg.find( '|', 9 ) - 8 );
446 
447 			if ( (type == "file") && checkFileLink( arg ) ) {
448 				arg += category;
449 				writeLink( arg, config_path );
450 			} else if ( (type == "server") && checkServerLink( arg ) ) {
451 				writeLink( arg, config_path );
452 			} else if ( (type == "serverlist") && checkServerListLink( arg ) ) {
453 				writeLink( arg, config_path );
454 			} else {
455 				std::cout << "Unknown or invalid link-type:\n\t" << arg << std::endl;
456 				errors = true;
457 			}
458 		} else if (arg == "-c" || arg == "--config-dir") {
459 			if (i < argc - 1) {
460 				config_path = argv[++i];
461 			} else {
462 				std::cerr << "Missing mandatory argument for " << arg << std::endl;
463 				errors = true;
464 			}
465 		} else if (arg.substr(0, 2) == "-c") {
466 			config_path = arg.substr(2);
467 		} else if (arg.substr(0, 13) == "--config-dir=") {
468 			config_path = arg.substr(13);
469 		} else if (arg == "-h" || arg == "--help") {
470 			std::cout << getVersion()
471 				<< "\n\n"
472 				<< "Usage:\n"
473 				<< "    --help, -h              Prints this help.\n"
474 				<< "    --config-dir, -c        Specifies the aMule configuration directory.\n"
475 				<< "    --version, -v           Displays version info.\n\n"
476 				<< "    --category, -t          Add Link to category number.\n"
477 				<< "    magnet:?                Causes the file to be queued for download.\n"
478 				<< "    ed2k://|file|           Causes the file to be queued for download.\n"
479 				<< "    ed2k://|server|         Causes the server to be listed or updated.\n"
480 				<< "    ed2k://|serverlist|     Causes aMule to update the current serverlist.\n\n"
481 				<< "    --list, -l              Show all links of an emulecollection\n"
482 				<< "    --emulecollection, -e   Loads all links of an emulecollection\n\n"
483 				<< "*** NOTE: Option order is important! ***\n"
484 				<< std::endl;
485 
486 		} else if (arg == "-v" || arg == "--version") {
487 			std::cout << getVersion() << std::endl;
488 		} else if (arg == "-t" || arg == "--category") {
489 			if (i < argc - 1) {
490 				if ((category == "" ) && (0 != atoi(argv[++i]))) {
491 					category = ':';
492 					category += argv[i];
493 				}
494 			} else {
495 				std::cerr << "Missing mandatory argument for " << arg << std::endl;
496 				errors = true;
497 			}
498 		} else if (arg == "-e" || arg == "--emulecollection" || arg == "-l" || arg == "--list") {
499 			bool listOnly = (arg == "-l" || arg == "--list");
500 			if (i < argc - 1) {
501 				CMuleCollection my_collection;
502 				if (my_collection.Open( /* emulecollection file */ argv[++i] ))
503 				{
504 					for(size_t e = 0; e < my_collection.size(); e++)
505 						if (listOnly)
506 							std::cout << my_collection[e] << std::endl;
507 						else
508 							writeLink( my_collection[e], config_path );
509 				} else {
510 					std::cerr << "Invalid emulecollection file: " << argv[i] << std::endl;
511 					errors = true;
512 				}
513 			} else {
514 				std::cerr << "Missing mandatory argument for " << arg << std::endl;
515 				errors = true;
516 			}
517 		} else {
518 			std::cerr << "Bad parameter value:\n\t" << arg << "\n" << std::endl;
519 			errors = true;
520 		}
521 	}
522 
523 	return ( errors ? 1 : 0 );
524 }
525 
526 // File_checked_for_headers
527