#include <glib.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "ctb_log.h"
#include "i18n.h"
#include "filetypes.h"
#include "shortcut.h"
Go to the source code of this file.
Functions | |
static int | parse_file_url (const gchar *directory, const gchar *url, shortcut_t *shortcut) |
static int | parse_folder_path (const gchar *directory, const gchar *target_path, shortcut_t *shortcut) |
static shortcut_t * | shortcut_new () |
static gboolean | path_has_invalid_characters (const gchar *path) |
int | create_shortcut_file (const gchar *target_dir, const gchar *target_file, const gchar *target_display, const gchar *shortcut_dir, GString *shortcut_file) |
create a shortcut file | |
int | parse_shortcut_file (const gchar *directory, const gchar *filename, shortcut_t **p_shortcut) |
get details from shortcut file parses shortcut files recursively when applicable | |
void | shortcut_free_impl (shortcut_t *thiz) |
Variables | |
static const char * | ILLEGAL_FILEPATH_CHARS = "*?\\:\"<>|" |
static const gchar * | FILE_SHORTCUT |
static const gchar * | FOLDER_SHORTCUT |
int create_shortcut_file | ( | const gchar * | target_dir, | |
const gchar * | target_file, | |||
const gchar * | target_display, | |||
const gchar * | shortcut_dir, | |||
GString * | shortcut_file | |||
) |
create a shortcut file
---------------------------------------------------------------------------
Name : create_shortcut_file
[in] | target_dir | - folder in which the target is located specified as an absolute path |
[in] | target_file | - file or folder to which the shortcut must point |
[in] | target_display | - name displayed for target |
[in] | shortcut_dir | - directory in which the shortcut file (xx.desktop) must be created specified as an absolute path |
[out] | shortcut_file | - filename of the shortcut file created in shortcut_dir, or ignored when caller passes a NULL pointer here. Note: caller must allocate and release this GString. |
--------------------------------------------------------------------------
Definition at line 98 of file shortcut.c.
References ER_NOT_FOUND, ER_OK, ER_OPEN_ERROR, ER_WRITE_ERROR, FILE_EXT_SHORTCUT, FILE_EXT_SHORTCUT_TO_DIR, FILE_SHORTCUT, FOLDER_SHORTCUT, SHORTCUT_EMPTY, SHORTCUT_TO_FILE, and SHORTCUT_TO_FOLDER.
Referenced by create_shortcut_item().
00103 { 00104 int ret = ER_OK; // return code 00105 00106 g_assert( target_dir && *target_dir == '/' ); 00107 g_assert( target_file && *target_file ); 00108 g_assert( target_display && *target_display ); 00109 g_assert( shortcut_dir && *shortcut_dir == '/' ); 00110 g_assert( shortcut_file == NULL || shortcut_file->len == 0 ); 00111 g_assert( strlen(target_dir) < 256); 00112 g_assert( strlen(target_file) < 256); 00113 g_assert( strlen(shortcut_dir) < 256); 00114 00115 // determine shortcut type 00116 gchar *target_path = g_strdup_printf("%s/%s", target_dir, target_file); 00117 00118 shortcut_type_t shortcut_type = SHORTCUT_EMPTY; 00119 const gchar *shortcut_ext = ""; 00120 if ( g_file_test(target_path, G_FILE_TEST_IS_REGULAR) ) 00121 { 00122 shortcut_type = SHORTCUT_TO_FILE; 00123 shortcut_ext = FILE_EXT_SHORTCUT; 00124 } 00125 else if ( g_file_test(target_path, G_FILE_TEST_IS_DIR) ) 00126 { 00127 shortcut_type = SHORTCUT_TO_FOLDER; 00128 shortcut_ext = FILE_EXT_SHORTCUT_TO_DIR; 00129 } 00130 else 00131 { 00132 ret = ER_NOT_FOUND; 00133 goto out; 00134 } 00135 00136 // determine shortcut filename 00137 int seq = 0; 00138 char file[256]; 00139 char shortcut_fullname[512]; 00140 while (1) { 00141 seq++; 00142 sprintf(file, "%s_%03d.%s", target_file, seq, shortcut_ext); 00143 sprintf(shortcut_fullname, "%s/%s", shortcut_dir, file); 00144 if (!g_file_test(shortcut_fullname, G_FILE_TEST_EXISTS)) break; 00145 } 00146 00147 // determine relative path shortcut -> target 00148 // skip identical pre-fix 00149 gchar *cp1 = target_path; 00150 gchar *cp2 = shortcut_fullname; 00151 while ( *cp1 != '\0' && *cp1 == *cp2 ) 00152 { 00153 cp1++; 00154 cp2++; 00155 } 00156 while ( *(cp1 - 1) != '/' ) 00157 { 00158 cp1--; 00159 cp2--; 00160 } 00161 // count parent directories to traverse 00162 gboolean done = FALSE; 00163 GString *shortcut_target = g_string_new(""); 00164 while ( !done ) 00165 { 00166 while ( *cp2 == '/' ) 00167 { 00168 cp2++; 00169 } 00170 cp2 = strchr(cp2, '/'); 00171 if (cp2) 00172 { 00173 g_string_append(shortcut_target, "../"); 00174 } 00175 else 00176 { 00177 done = TRUE; 00178 } 00179 } 00180 // append remaining part of shortcut target 00181 g_string_append(shortcut_target, cp1); 00182 00183 FILE *fp = fopen(shortcut_fullname, "w"); 00184 if (fp == NULL) 00185 { 00186 ret = ER_OPEN_ERROR; 00187 goto out2; 00188 } 00189 00190 int rc = 0; 00191 // write shortcut details 00192 switch (shortcut_type) 00193 { 00194 case SHORTCUT_TO_FILE: 00195 { 00196 gchar* temp_uri = g_uri_escape_string(shortcut_target->str, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); 00197 rc = fprintf( fp, 00198 FILE_SHORTCUT, 00199 temp_uri, // URL= 00200 target_display ); // Name= 00201 g_free(temp_uri ); 00202 break; 00203 } 00204 case SHORTCUT_TO_FOLDER: 00205 rc = fprintf( fp, 00206 FOLDER_SHORTCUT, 00207 shortcut_target->str, // Path= 00208 target_display ); // Name= 00209 break; 00210 00211 default: 00212 g_assert_not_reached(); 00213 } 00214 if (rc <= 0) 00215 { 00216 ret = ER_WRITE_ERROR; 00217 } 00218 00219 rc = fclose(fp); 00220 if (rc != 0) 00221 { 00222 ret = ER_WRITE_ERROR; 00223 } 00224 00225 // set output parameters 00226 if (ret == ER_OK && shortcut_file) 00227 { 00228 g_string_assign(shortcut_file, file); 00229 } 00230 00231 out2: 00232 g_string_free(shortcut_target, TRUE); 00233 out: 00234 g_free(target_path); 00235 return ret; 00236 }
static int parse_file_url | ( | const gchar * | directory, | |
const gchar * | url, | |||
shortcut_t * | shortcut | |||
) | [static] |
Definition at line 469 of file shortcut.c.
References cp, shortcut_t::details, ER_INVALID_DATA, ER_NOT_FOUND, ER_OK, shortcut_t::file, LOGPRINTF, path, path_has_invalid_characters(), SHORTCUT_TO_FILE, and shortcut_t::type.
Referenced by parse_shortcut_file().
00472 { 00473 int ret = ER_OK; // return code 00474 00475 LOGPRINTF("entry: dir [%s] url [%s]", directory, url); 00476 g_assert(directory && *directory); 00477 g_assert(url && *url ); 00478 g_assert(shortcut && shortcut->type == SHORTCUT_TO_FILE); 00479 00480 // strip scheme: 00481 char *cp = strchr(url, ':'); 00482 char *url_path = NULL; // path component from url 00483 if (cp == NULL) 00484 { 00485 url_path = g_strdup(url); 00486 } 00487 else if ( g_str_has_prefix(url, "file:") ) 00488 { 00489 url_path = g_strdup(cp + 1); 00490 } 00491 else 00492 { 00493 ret = ER_INVALID_DATA; 00494 } 00495 00496 // strip //authority 00497 if ( url_path[0] == '/' 00498 && url_path[1] == '/' ) 00499 { 00500 if ( url_path[2] == '/' ) 00501 { 00502 // authority is empty: ok 00503 strcpy(url_path, url_path + 2); 00504 } 00505 else 00506 { 00507 // autohority filled in: not implemented 00508 ret = ER_INVALID_DATA; 00509 } 00510 } 00511 00512 // strip ?query 00513 cp = strchr(url_path, '?'); 00514 if (cp) 00515 { 00516 *cp = '\0'; 00517 } 00518 00519 // strip #fragment 00520 cp = strchr(url_path, '#'); 00521 if (cp) 00522 { 00523 *cp = '\0'; 00524 } 00525 00526 // unescape %<hex><hex> strings, without failing on any character 00527 cp = g_uri_unescape_string(url_path, NULL); 00528 if (cp) 00529 { 00530 g_free(url_path); 00531 url_path = cp; 00532 } 00533 00534 // check no illegal characters in path 00535 if ( path_has_invalid_characters(url_path) ) 00536 { 00537 ret = ER_INVALID_DATA; 00538 } 00539 00540 // detemine path on filesystem 00541 char *fs_path = NULL; // path on filesystem 00542 if (ret == ER_OK) 00543 { 00544 if (url_path[0] == '/') 00545 { 00546 fs_path = url_path; 00547 url_path = NULL; 00548 } 00549 else 00550 { 00551 fs_path = g_strdup_printf("%s/%s", directory, url_path); 00552 } 00553 } 00554 00555 // get real path 00556 char path[PATH_MAX]; 00557 if (ret == ER_OK) 00558 { 00559 cp = realpath(fs_path, path); 00560 if (cp != path) 00561 { 00562 ret = ER_NOT_FOUND; 00563 } 00564 } 00565 00566 // check target is a file 00567 if (ret == ER_OK) 00568 { 00569 gboolean ok = g_file_test(path, G_FILE_TEST_IS_REGULAR); 00570 if (!ok) 00571 { 00572 ret = ER_NOT_FOUND; 00573 } 00574 } 00575 00576 // report our findings 00577 if (ret == ER_OK) 00578 { 00579 shortcut->details.file.filename = g_path_get_basename(path); 00580 shortcut->details.file.directory = g_path_get_dirname(path); 00581 LOGPRINTF("file [%s] dir [%s]", shortcut->details.file.filename, shortcut->details.file.directory); 00582 } 00583 00584 g_free(fs_path); 00585 g_free(url_path); 00586 return ret; 00587 }
static int parse_folder_path | ( | const gchar * | directory, | |
const gchar * | target_path, | |||
shortcut_t * | shortcut | |||
) | [static] |
Definition at line 590 of file shortcut.c.
References cp, shortcut_t::details, ER_INVALID_DATA, ER_OK, ERRORPRINTF, shortcut_t::folder, path, path_has_invalid_characters(), SHORTCUT_TO_FOLDER, and shortcut_t::type.
Referenced by parse_shortcut_file().
00593 { 00594 g_assert(directory && *directory ); 00595 g_assert(target_path && *target_path); 00596 g_assert(shortcut && shortcut->type == SHORTCUT_TO_FOLDER); 00597 g_assert(shortcut->details.folder.directory == NULL); 00598 g_assert(shortcut->details.folder.filename == NULL); 00599 00600 // check no illegal characters in path 00601 if ( path_has_invalid_characters(target_path) ) 00602 { 00603 ERRORPRINTF("invalid character in target_path [%s]", target_path); 00604 return ER_INVALID_DATA; 00605 } 00606 00607 // determine path on filesystem 00608 char *fs_path = NULL; 00609 if (target_path[0] == '/') 00610 { 00611 fs_path = g_strdup(target_path); 00612 } 00613 else 00614 { 00615 fs_path = g_strdup_printf("%s/%s", directory, target_path); 00616 } 00617 00618 // get real path, if target present 00619 char path[PATH_MAX] = ""; 00620 gchar *cp = realpath(fs_path, path); 00621 if (cp != path) { 00622 g_free(fs_path); 00623 return ER_INVALID_DATA; 00624 } 00625 00626 gchar *dir = g_path_get_dirname(path); 00627 gchar *file = g_path_get_basename(path); 00628 00629 shortcut->details.folder.directory = dir; 00630 shortcut->details.folder.filename = file; 00631 00632 g_free(fs_path ); 00633 return ER_OK; 00634 }
int parse_shortcut_file | ( | const gchar * | directory, | |
const gchar * | filename, | |||
shortcut_t ** | p_shortcut | |||
) |
get details from shortcut file parses shortcut files recursively when applicable
---------------------------------------------------------------------------
Name : parse_shortcut_file
[in] | directory | - directory in which the shortcut file (xx.desktop) is located |
[in] | filename | - filename of the shortcut file |
[out] | p_shortcut | - structure with details of the requested shortcut, caller must release this with shortcut_free() |
--------------------------------------------------------------------------
Definition at line 239 of file shortcut.c.
References shortcut_t::application, shortcut_t::comment, cp, shortcut_t::details, ER_FAIL, ER_INVALID_DATA, ER_NOT_FOUND, ER_OK, ERRORPRINTF, shortcut_t::filepath, shortcut_t::folder, shortcut_t::keyfile, shortcut_t::name, parse_file_url(), parse_folder_path(), path, path_has_invalid_characters(), shortcut_free, shortcut_new(), SHORTCUT_TO_APPLICATION, SHORTCUT_TO_FILE, SHORTCUT_TO_FOLDER, SHORTCUT_TO_WEB_LOCATION, shortcut_t::type, and shortcut_t::web.
Referenced by activate_shortcut(), check_shortcut(), get_thumbnail(), and load_items_in_model().
00242 { 00243 g_assert(directory && *directory ); 00244 g_assert(filename && *filename ); 00245 g_assert(p_shortcut && *p_shortcut == NULL); 00246 00247 GKeyFile *keyfile = g_key_file_new(); 00248 shortcut_t *shortcut = shortcut_new(); 00249 shortcut->keyfile = keyfile; 00250 gchar *filepath = g_strdup_printf("%s/%s", directory, filename); 00251 shortcut->filepath = filepath; 00252 00253 int ret = ER_OK; 00254 if (!g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) 00255 { 00256 ret = ER_NOT_FOUND; 00257 goto out; 00258 } 00259 00260 if (!g_key_file_load_from_file( keyfile, 00261 filepath, 00262 G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, 00263 NULL)) 00264 { 00265 ret = ER_INVALID_DATA; 00266 goto out; 00267 } 00268 00269 // get name 00270 shortcut->name = g_key_file_get_locale_string( keyfile, 00271 G_KEY_FILE_DESKTOP_GROUP, 00272 G_KEY_FILE_DESKTOP_KEY_NAME, 00273 NULL, 00274 NULL ); 00275 if (shortcut->name == NULL) 00276 { 00277 ret = ER_INVALID_DATA; 00278 goto out; 00279 } 00280 00281 // get comments 00282 shortcut->comment = g_key_file_get_locale_string( keyfile, 00283 G_KEY_FILE_DESKTOP_GROUP, 00284 G_KEY_FILE_DESKTOP_KEY_COMMENT, 00285 NULL, 00286 NULL ); 00287 00288 // get version (optional) 00289 gchar* version = g_key_file_get_string( keyfile, 00290 G_KEY_FILE_DESKTOP_GROUP, 00291 G_KEY_FILE_DESKTOP_KEY_VERSION, 00292 NULL ); 00293 if (version != NULL) 00294 { 00295 int compare = strcmp(version, "1.0"); 00296 g_free(version); 00297 if (compare != 0) { 00298 ret = ER_INVALID_DATA; 00299 goto out; 00300 } 00301 } 00302 00303 // get type 00304 gchar *type = g_key_file_get_string( keyfile, 00305 G_KEY_FILE_DESKTOP_GROUP, 00306 G_KEY_FILE_DESKTOP_KEY_TYPE, 00307 NULL ); 00308 if (type == NULL) 00309 { 00310 // type not found: error 00311 ret = ER_INVALID_DATA; 00312 goto out; 00313 } 00314 00315 // type found: store in shortcut 00316 if ( strcmp(type, G_KEY_FILE_DESKTOP_TYPE_LINK) == 0 ) 00317 { 00318 shortcut->type = SHORTCUT_TO_FILE; 00319 } 00320 else if ( strcmp(type, G_KEY_FILE_DESKTOP_TYPE_DIRECTORY) == 0 ) 00321 { 00322 shortcut->type = SHORTCUT_TO_FOLDER; 00323 } 00324 else if ( strcmp(type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) == 0 ) 00325 { 00326 shortcut->type = SHORTCUT_TO_APPLICATION; 00327 } 00328 else 00329 { 00330 g_free(type); 00331 ret = ER_INVALID_DATA; 00332 goto out; 00333 } 00334 g_free(type); 00335 00336 // get type-dependent keys 00337 switch (shortcut->type) 00338 { 00339 case SHORTCUT_TO_FILE: 00340 { 00341 // get url 00342 gchar *url = g_key_file_get_string( keyfile, 00343 G_KEY_FILE_DESKTOP_GROUP, 00344 G_KEY_FILE_DESKTOP_KEY_URL, 00345 NULL ); 00346 if (url == NULL) 00347 { 00348 ret = ER_INVALID_DATA; 00349 goto out; 00350 } 00351 00352 // url found: convert to path, store in shortcut 00353 00354 // get scheme from url 00355 // note: scheme might indicate url points to a web location 00356 if ( g_str_has_prefix(url, "http:") || g_str_has_prefix(url, "https:") ) 00357 { 00358 // web location: remember details and leave 00359 shortcut->type = SHORTCUT_TO_WEB_LOCATION; 00360 shortcut->details.web.url = url; 00361 ret = ER_OK; 00362 break; // exit switch 00363 } 00364 else if ( g_str_has_prefix(url, "file:") ) 00365 { 00366 // local file location: get real path 00367 ret = parse_file_url( directory, url, shortcut ); 00368 } 00369 else 00370 { 00371 // scheme not implemented 00372 ret = ER_INVALID_DATA; 00373 goto out; 00374 } 00375 00376 // clean up 00377 g_free(url); 00378 break; 00379 } 00380 case SHORTCUT_TO_FOLDER: 00381 { 00382 // get path 00383 gchar *path = g_key_file_get_string( keyfile, 00384 G_KEY_FILE_DESKTOP_GROUP, 00385 G_KEY_FILE_DESKTOP_KEY_PATH, 00386 NULL ); 00387 if (path == NULL) 00388 { 00389 ret = ER_INVALID_DATA; 00390 goto out; 00391 } 00392 00393 // key found: check on invalid characters 00394 if ( path_has_invalid_characters(path) ) 00395 { 00396 ret = ER_INVALID_DATA; 00397 goto out; 00398 } 00399 00400 // path found: convert to realpath, store in shortcut 00401 ret = parse_folder_path( directory, path, shortcut ); 00402 shortcut->details.folder.path = path; 00403 break; 00404 } 00405 // note: no nesting of shortcut to application 00406 case SHORTCUT_TO_APPLICATION: 00407 { 00408 // get exec 00409 gchar *executable = g_key_file_get_string( keyfile, 00410 G_KEY_FILE_DESKTOP_GROUP, 00411 G_KEY_FILE_DESKTOP_KEY_EXEC, 00412 NULL ); 00413 if (executable == NULL) 00414 { 00415 ret = ER_INVALID_DATA; 00416 goto out; 00417 } 00418 00419 // key found: store in shortcut 00420 if (strncmp(executable, "./", 2) == 0 || strncmp(executable, "../", 3) == 0) 00421 { 00422 // relative path: prepend shortcut directory 00423 // shell quote to preserve spaces in directory 00424 gchar *directory_quoted = g_shell_quote(directory); 00425 gchar *cp = g_strdup_printf("%s/%s", directory_quoted, executable); 00426 g_free(directory_quoted); 00427 g_free(executable); 00428 executable = cp; 00429 } 00430 shortcut->details.application.command_line = executable; 00431 00432 // get path (optional) 00433 gchar* path = g_key_file_get_string( keyfile, 00434 G_KEY_FILE_DESKTOP_GROUP, 00435 G_KEY_FILE_DESKTOP_KEY_PATH, 00436 NULL ); 00437 if (path != NULL) 00438 { 00439 // key found: check on invalid characters 00440 if ( path_has_invalid_characters(path) ) 00441 { 00442 g_free(path); 00443 ret = ER_INVALID_DATA; 00444 goto out; 00445 } 00446 // path ok: store in shortcut 00447 shortcut->details.application.work_dir = path; 00448 } 00449 break; 00450 } 00451 default: 00452 ERRORPRINTF("unknown type %d", shortcut->type); 00453 ret = ER_FAIL; 00454 break; 00455 } 00456 00457 if (ret == ER_OK) 00458 { 00459 *p_shortcut = shortcut; 00460 return ER_OK; 00461 } 00462 00463 out: 00464 shortcut_free(shortcut); 00465 return ret; 00466 }
static gboolean path_has_invalid_characters | ( | const gchar * | path | ) | [static] |
Definition at line 85 of file shortcut.c.
References cp, and ILLEGAL_FILEPATH_CHARS.
Referenced by parse_file_url(), parse_folder_path(), and parse_shortcut_file().
00086 { 00087 const gchar *illegal; 00088 for ( illegal = ILLEGAL_FILEPATH_CHARS ; *illegal ; illegal++ ) 00089 { 00090 gchar *cp = strchr(path, *illegal); 00091 if (cp) return TRUE; 00092 } 00093 00094 return FALSE; 00095 }
void shortcut_free_impl | ( | shortcut_t * | thiz | ) |
Definition at line 637 of file shortcut.c.
References shortcut_t::application, shortcut_t::comment, shortcut_t::details, ERRORPRINTF, shortcut_t::file, shortcut_t::filepath, shortcut_t::folder, shortcut_t::keyfile, shortcut_t::name, SHORTCUT_EMPTY, SHORTCUT_TO_APPLICATION, SHORTCUT_TO_FILE, SHORTCUT_TO_FOLDER, SHORTCUT_TO_WEB_LOCATION, shortcut_t::type, and shortcut_t::web.
00638 { 00639 if (thiz == NULL) return; 00640 00641 g_free(thiz->filepath); 00642 if (thiz->keyfile) 00643 { 00644 g_key_file_free(thiz->keyfile); 00645 } 00646 g_free(thiz->name); 00647 g_free(thiz->comment); 00648 switch (thiz->type) 00649 { 00650 case SHORTCUT_EMPTY: 00651 ; //ignore 00652 break; 00653 00654 case SHORTCUT_TO_FILE: 00655 g_free(thiz->details.file.directory); 00656 g_free(thiz->details.file.filename ); 00657 break; 00658 00659 case SHORTCUT_TO_WEB_LOCATION: 00660 g_free(thiz->details.web.url); 00661 break; 00662 00663 case SHORTCUT_TO_FOLDER: 00664 g_free(thiz->details.folder.path); 00665 g_free(thiz->details.folder.directory); 00666 g_free(thiz->details.folder.filename ); 00667 break; 00668 00669 case SHORTCUT_TO_APPLICATION: 00670 g_free(thiz->details.application.command_line); 00671 break; 00672 00673 default: 00674 ERRORPRINTF("illegal shortcut type [%d]", thiz->type); 00675 ; //ignore 00676 } 00677 00678 g_free(thiz); 00679 }
static shortcut_t* shortcut_new | ( | ) | [static] |
Definition at line 75 of file shortcut.c.
References SHORTCUT_EMPTY, and shortcut_t::type.
Referenced by parse_shortcut_file().
00076 { 00077 shortcut_t *thiz = g_new0( shortcut_t, 1 ); 00078 g_assert(thiz); 00079 00080 thiz->type = SHORTCUT_EMPTY; 00081 return thiz; 00082 }
const gchar* FILE_SHORTCUT [static] |
"[Desktop Entry]\n" "Version=1.0\n" "Type=Link\n" "URL=file:%s\n" "Name=%s\n"
Definition at line 45 of file shortcut.c.
Referenced by create_shortcut_file().
const gchar* FOLDER_SHORTCUT [static] |
"[Desktop Entry]\n" "Version=1.0\n" "Type=Directory\n" "Path=%s\n" "Name=%s\n"
Definition at line 51 of file shortcut.c.
Referenced by create_shortcut_file().
const char* ILLEGAL_FILEPATH_CHARS = "*?\\:\"<>|" [static] |
Copyright (C) 2008 iRex Technologies B.V. All rights reserved.
Definition at line 43 of file shortcut.c.
Referenced by path_has_invalid_characters().