2 // This file is part of the aMule Project.
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 )
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
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;
36 #include <CoreServices/CoreServices.h>
44 #include "MagnetURI.h"
45 #include "MuleCollection.h"
49 static string
GetLinksFilePath(const string
& configDir
)
51 if (!configDir
.empty()) {
53 char buffer
[MAX_PATH
+ 1];
54 configDir
.copy(buffer
, MAX_PATH
);
55 if (PathAppendA(buffer
, "ED2KLinks")) {
57 strDir
.assign(buffer
);
61 string strDir
= configDir
;
62 if (strDir
.at(strDir
.length() - 1) != '/') {
65 return strDir
+ "ED2KLinks";
75 if (FSFindFolder(kUserDomain
, kApplicationSupportFolderType
, kCreateFolder
, &fsRef
) == noErr
) {
76 CFURLRef urlRef
= CFURLCreateFromFSRef(NULL
, &fsRef
);
78 UInt8 buffer
[PATH_MAX
+ 1];
79 if (CFURLGetFileSystemRepresentation(urlRef
, true, buffer
, sizeof(buffer
))) {
80 strDir
.assign((char*) buffer
);
86 return strDir
+ "/aMule/ED2KLinks";
93 HRESULT hr
= SHGetSpecialFolderLocation(NULL
, CSIDL_APPDATA
, &pidl
);
96 char buffer
[MAX_PATH
+ 1];
97 if (SHGetPathFromIDListA(pidl
, buffer
)) {
98 if (PathAppendA(buffer
, "aMule\\ED2KLinks")) {
99 strDir
.assign(buffer
);
106 SHGetMalloc(&pMalloc
);
117 return string( getenv("HOME") ) + "/.aMule/ED2KLinks";
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
)
132 for ( size_t i
= 0; i
< hex
.length(); ++i
) {
133 char cur
= toupper( hex
.at(i
) );
136 if ( isdigit( cur
) ) {
138 } else if ( cur
>= 'A' && cur
<= 'F' ) {
139 result
+= cur
- 'A' + 10;
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
)
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 ) );
169 // If conversion failed, then we just add the escape-code
170 // and continue past it like nothing happened.
183 * Returns the string with whitespace stripped from both ends.
185 static string
strip( const string
& str
)
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
) ) ) {
195 } else if ( isspace( str
.at(last
) ) ) {
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
) ) ) {
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' ) ) {
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
<< "."
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
;
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 );
306 valid
&= ( base_off
< name_off
);
307 valid
&= ( name_off
< size_off
);
308 valid
&= ( size_off
< hash_off
);
309 valid
&= ( hash_off
!= string::npos
);
312 badLink( "file", "invalid link format", uri
);
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
);
325 if ( !isNumber( size
) ) {
326 badLink( "file", "invalid size", uri
);
330 if ( !isMD4Hash( hash
) ) {
331 badLink( "file", "invalid MD4 hash", uri
);
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 );
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
);
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
);
373 if ( !isNumber( port
) ) {
374 badLink( "server", "invalid port", uri
);
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 );
398 valid
&= ( base_off
< path_off
);
399 valid
&= ( path_off
!= string::npos
);
402 badLink( "serverlist", "invalid link format", uri
);
406 string path
= uri
.substr( base_off
+ 1, path_off
- base_off
- 1 );
408 if ( path
.empty() ) {
409 badLink( "serverlist", "no hostname specified", uri
);
420 int main(int argc
, char *argv
[])
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
;
439 if ( arg
.substr( 0, 8 ) == "ed2k://|" ) {
440 // Ensure the URI is valid
441 if ( arg
.at( arg
.length() - 1 ) != '/' ) {
445 string type
= arg
.substr( 8, arg
.find( '|', 9 ) - 8 );
447 if ( (type
== "file") && checkFileLink( arg
) ) {
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
);
455 std::cout
<< "Unknown or invalid link-type:\n\t" << arg
<< std::endl
;
458 } else if (arg
== "-c" || arg
== "--config-dir") {
460 config_path
= argv
[++i
];
462 std::cerr
<< "Missing mandatory argument for " << arg
<< std::endl
;
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()
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"
486 } else if (arg
== "-v" || arg
== "--version") {
487 std::cout
<< getVersion() << std::endl
;
488 } else if (arg
== "-t" || arg
== "--category") {
490 if ((category
== "" ) && (0 != atoi(argv
[++i
]))) {
495 std::cerr
<< "Missing mandatory argument for " << arg
<< std::endl
;
498 } else if (arg
== "-e" || arg
== "--emulecollection" || arg
== "-l" || arg
== "--list") {
499 bool listOnly
= (arg
== "-l" || arg
== "--list");
501 CMuleCollection my_collection
;
502 if (my_collection
.Open( /* emulecollection file */ argv
[++i
] ))
504 for(size_t e
= 0; e
< my_collection
.size(); e
++)
506 std::cout
<< my_collection
[e
] << std::endl
;
508 writeLink( my_collection
[e
], config_path
);
510 std::cerr
<< "Invalid emulecollection file: " << argv
[i
] << std::endl
;
514 std::cerr
<< "Missing mandatory argument for " << arg
<< std::endl
;
518 std::cerr
<< "Bad parameter value:\n\t" << arg
<< "\n" << std::endl
;
523 return ( errors
? 1 : 0 );
526 // File_checked_for_headers