shortcut.c

Go to the documentation of this file.
00001 /*
00002  * File Name: shortcut.c
00003  */
00004 
00005 /*
00006  * This file is part of ctb.
00007  *
00008  * ctb is free software: you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation, either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * ctb is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00020  */
00021  
00022 /**
00023  * Copyright (C) 2008 iRex Technologies B.V.
00024  * All rights reserved.
00025  */
00026 
00027 //----------------------------------------------------------------------------
00028 // Include Files
00029 //----------------------------------------------------------------------------
00030 
00031 #include <glib.h>
00032 #include <stdlib.h>
00033 #include <sys/stat.h>
00034 #include <sys/types.h>
00035 #include <unistd.h>
00036 
00037 #include "ctb_log.h"
00038 #include "i18n.h"
00039 #include "filetypes.h"
00040 #include "shortcut.h"
00041 
00042 
00043 static const char   *ILLEGAL_FILEPATH_CHARS = "*?\\:\"<>|";
00044 
00045 static const gchar  *FILE_SHORTCUT   = "[Desktop Entry]\n"
00046                                         "Version=1.0\n"
00047                                         "Type=Link\n"
00048                                         "URL=file:%s\n"
00049                                         "Name=%s\n";
00050 
00051 static const gchar  *FOLDER_SHORTCUT = "[Desktop Entry]\n"
00052                                        "Version=1.0\n"
00053                                        "Type=Directory\n"
00054                                        "Path=%s\n"
00055                                        "Name=%s\n";
00056 
00057 
00058 //----------------------------------------------------------------------------
00059 // Static Variables
00060 //----------------------------------------------------------------------------
00061 
00062 
00063 //============================================================================
00064 // Local Function Definitions
00065 //============================================================================
00066 
00067 static int          parse_file_url              ( const gchar      *directory,
00068                                                   const gchar      *url,
00069                                                         shortcut_t *shortcut );
00070                                                 
00071 static int          parse_folder_path           ( const gchar      *directory,
00072                                                   const gchar      *target_path,
00073                                                         shortcut_t *shortcut    );
00074 
00075 static shortcut_t* shortcut_new()
00076 {
00077     shortcut_t  *thiz = g_new0( shortcut_t, 1 );
00078     g_assert(thiz);
00079 
00080     thiz->type = SHORTCUT_EMPTY;
00081     return thiz;
00082 }
00083 
00084 
00085 static gboolean path_has_invalid_characters ( const gchar *path )
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 }
00096 
00097 
00098 int create_shortcut_file ( const gchar   *target_dir,
00099                            const gchar   *target_file,
00100                            const gchar   *target_display,
00101                            const gchar   *shortcut_dir,
00102                                  GString *shortcut_file  )
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 }
00237 
00238 
00239 int parse_shortcut_file ( const gchar      *directory,
00240                           const gchar      *filename,
00241                                 shortcut_t **p_shortcut )
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 }
00467 
00468 
00469 static int parse_file_url ( const gchar      *directory,
00470                             const gchar      *url,
00471                                   shortcut_t *shortcut )
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 }
00588 
00589 
00590 static int parse_folder_path ( const gchar      *directory,
00591                                const gchar      *target_path,
00592                                      shortcut_t *shortcut)
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 }
00635 
00636 
00637 void shortcut_free_impl ( shortcut_t* thiz )
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 }
00680 
Generated by  doxygen 1.6.2-20100208