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