debian: fix build-deps for focal
[amule.git] / src / ED2KLinkParser.cpp
blob8a5da3f12663c7099347ab82255ec7411c077e22
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.
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.
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.
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
26 const int versionMajor = 1;
27 const int versionMinor = 5;
28 const int versionRevision = 1;
30 #include <cstdlib>
31 #include <sstream>
32 #include <iostream>
33 #include <fstream>
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
43 #include "FileLock.h"
44 #include "MagnetURI.h"
45 #include "MuleCollection.h"
47 using std::string;
49 static string GetLinksFilePath(const string& configDir)
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;
60 #else
61 string strDir = configDir;
62 if (strDir.at(strDir.length() - 1) != '/') {
63 strDir += '/';
65 return strDir + "ED2KLinks";
66 #endif
70 #ifdef __APPLE__
72 std::string strDir;
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);
82 CFRelease(urlRef) ;
86 return strDir + "/aMule/ED2KLinks";
88 #elif defined(_WIN32)
90 std::string strDir;
91 LPITEMIDLIST pidl;
93 HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pidl);
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);
104 if (pidl) {
105 LPMALLOC pMalloc;
106 SHGetMalloc(&pMalloc);
107 if (pMalloc) {
108 pMalloc->Free(pidl);
109 pMalloc->Release();
113 return strDir;
115 #else
117 return string( getenv("HOME") ) + "/.aMule/ED2KLinks";
119 #endif
123 * Converts a hexadecimal number to a char.
125 * @param hex The hex-number, must be at most 2 digits long.
126 * @return The resulting char or \0 if conversion failed.
128 static char HexToDec( const string& hex )
130 char result = 0;
132 for ( size_t i = 0; i < hex.length(); ++i ) {
133 char cur = toupper( hex.at(i) );
134 result *= 16;
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';
145 return result;
150 * This function converts all valid HTML escape-codes to their corresponding chars.
152 * @param str The string to unescape.
153 * @return The unescaped version of the input string.
155 static string Unescape( const string& str )
157 string result;
158 result.reserve( str.length() );
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 ) );
164 if ( unesc ) {
165 i += 2;
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);
173 } else {
174 result += str.at(i);
178 return result;
183 * Returns the string with whitespace stripped from both ends.
185 static string strip( const string& str )
187 size_t first = 0;
188 size_t last = str.length() - 1;
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;
202 return str.substr( first, 1 + last - first );
207 * Returns true if the string is a valid number.
209 static bool isNumber( const string& str )
211 for ( size_t i = 0; i < str.length(); i++ ) {
212 if ( !isdigit( str.at(i) ) ) {
213 return false;
217 return str.length() > 0;
222 * Returns true if the string is a valid Base16 representation of a MD4 Hash.
224 static bool isMD4Hash( const string& str )
226 for ( size_t i = 0; i < str.length(); i++ ) {
227 const char c = toupper( str.at(i) );
229 if ( !isdigit( c ) && ( c < 'A' || c > 'F' ) ) {
230 return false;
234 return str.length() == 32;
239 * Returns a description of the current version of "ed2k".
241 static string getVersion()
243 std::ostringstream v;
245 v << "aMule ED2k link parser v"
246 << versionMajor << "."
247 << versionMinor << "."
248 << versionRevision;
250 return v.str();
255 * Helper-function for printing link-errors.
257 static void badLink( const string& type, const string& err, const string& uri )
259 std::cout << "Invalid " << type << "-link, " + err << ":\n"
260 << "\t" << uri << std::endl;
265 * Writes a string to the ED2KLinks file.
267 * If errors are detected, it will terminate the program.
269 static void writeLink( const string& uri, const string& config_dir )
271 // Attempt to lock the ED2KLinks file
272 static CFileLock lock(GetLinksFilePath(config_dir));
273 static std::ofstream file;
275 if (!file.is_open()) {
276 string path = GetLinksFilePath(config_dir);
277 file.open( path.c_str(), std::ofstream::out | std::ofstream::app );
279 if (!file.is_open()) {
280 std::cout << "ERROR! Failed to open " << path << " for writing!" << std::endl;
281 exit(1);
285 file << uri << std::endl;
287 std::cout << "Link successfully queued." << std::endl;
292 * Writes the the specified URI to the ED2KLinks file if it is a valid file-link.
294 * @param uri The URI to check.
295 * @return True if the URI was written, false otherwise.
297 static bool checkFileLink( const string& uri )
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 );
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 );
311 if ( !valid ) {
312 badLink( "file", "invalid link format", uri );
313 return false;
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 );
320 if ( name.empty() ) {
321 badLink( "file", "no name specified", uri );
322 return false;
325 if ( !isNumber( size ) ) {
326 badLink( "file", "invalid size", uri );
327 return false;
330 if ( !isMD4Hash( hash ) ) {
331 badLink( "file", "invalid MD4 hash", uri );
332 return false;
335 return true;
338 return false;
343 * Writes the the specified URI to the ED2KLinks file if it is a valid server-link.
345 * @param uri The URI to check.
346 * @return True if the URI was written, false otherwise.
348 static bool checkServerLink( const string& uri )
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 );
355 bool valid = true;
356 valid &= ( base_off < host_off );
357 valid &= ( host_off < port_off );
358 valid &= ( port_off != string::npos );
360 if ( !valid || uri.at( port_off + 1 ) != '/' ) {
361 badLink( "server", "invalid link format", uri );
362 return false;
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 );
368 if ( host.empty() ) {
369 badLink( "server", "no hostname specified", uri );
370 return false;
373 if ( !isNumber( port ) ) {
374 badLink( "server", "invalid port", uri );
375 return false;
378 return true;
381 return false;
386 * Writes the the specified URI to the ED2KLinks file if it is a valid serverlist-link.
388 * @param uri The URI to check.
389 * @return True if the URI was written, false otherwise.
391 static bool checkServerListLink( const string& uri )
393 if ( uri.substr( 0, 19 ) == "ed2k://|serverlist|" ) {
394 size_t base_off = 19;
395 size_t path_off = uri.find( '|', base_off + 1 );
397 bool valid = true;
398 valid &= ( base_off < path_off );
399 valid &= ( path_off != string::npos );
401 if ( !valid ) {
402 badLink( "serverlist", "invalid link format", uri );
403 return false;
406 string path = uri.substr( base_off + 1, path_off - base_off - 1 );
408 if ( path.empty() ) {
409 badLink( "serverlist", "no hostname specified", uri );
410 return false;
413 return true;
416 return false;
420 int main(int argc, char *argv[])
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] ) ) );
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;
439 if ( arg.substr( 0, 8 ) == "ed2k://|" ) {
440 // Ensure the URI is valid
441 if ( arg.at( arg.length() - 1 ) != '/' ) {
442 arg += '/';
445 string type = arg.substr( 8, arg.find( '|', 9 ) - 8 );
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;
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;
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;
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];
494 } else {
495 std::cerr << "Missing mandatory argument for " << arg << std::endl;
496 errors = true;
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] ))
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;
513 } else {
514 std::cerr << "Missing mandatory argument for " << arg << std::endl;
515 errors = true;
517 } else {
518 std::cerr << "Bad parameter value:\n\t" << arg << "\n" << std::endl;
519 errors = true;
523 return ( errors ? 1 : 0 );
526 // File_checked_for_headers