1 /*  Audacious - Cross-platform multimedia player
2  *  Copyright (C) 2005-2009  Audacious development team
3  *
4  *  Based on BMP:
5  *  Copyright (C) 2003-2004  BMP development team.
6  *
7  *  Based on XMMS:
8  *  Copyright (C) 1998-2003  XMMS development team.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; under version 3 of the License.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program.  If not, see <http://www.gnu.org/licenses>.
21  *
22  *  The Audacious team does not consider modular code linking to
23  *  Audacious or using our public API to be a derived work.
24  */
25 
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 
32 #include <glib/gstdio.h>
33 
34 #include <libaudcore/audstrings.h>
35 #include <libaudcore/i18n.h>
36 #include <libaudcore/hook.h>
37 #include <libaudcore/multihash.h>
38 #include <libaudcore/runtime.h>
39 #include <libaudcore/vfs.h>
40 
41 #include "skins_util.h"
42 
43 #ifdef S_IRGRP
44 #define DIRMODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
45 #else
46 #define DIRMODE (S_IRWXU)
47 #endif
48 
find_file_case_path(const char * folder,const char * basename)49 StringBuf find_file_case_path (const char * folder, const char * basename)
50 {
51     static SimpleHash<String, Index<String>> cache;
52 
53     String key (folder);
54     Index<String> * list = cache.lookup (key);
55 
56     if (! list)
57     {
58         GDir * handle = g_dir_open (folder, 0, nullptr);
59         if (! handle)
60             return StringBuf ();
61 
62         list = cache.add (key, Index<String> ());
63 
64         const char * name;
65         while ((name = g_dir_read_name (handle)))
66             list->append (name);
67 
68         g_dir_close (handle);
69     }
70 
71     for (const String & entry : * list)
72     {
73         if (! strcmp_nocase (entry, basename))
74             return filename_build ({folder, entry});
75     }
76 
77     return StringBuf ();
78 }
79 
open_local_file_nocase(const char * folder,const char * basename)80 VFSFile open_local_file_nocase (const char * folder, const char * basename)
81 {
82     StringBuf path = find_file_case_path (folder, basename);
83     return path ? VFSFile (path, "r") : VFSFile ();
84 }
85 
skin_pixmap_locate(const char * folder,const char * basename,const char * altname)86 StringBuf skin_pixmap_locate (const char * folder, const char * basename, const char * altname)
87 {
88     static const char * const exts[] = {".bmp", ".png", ".xpm"};
89 
90     for (const char * ext : exts)
91     {
92         StringBuf name = find_file_case_path (folder, str_concat ({basename, ext}));
93         if (name)
94             return name.settle ();
95     }
96 
97     return altname ? skin_pixmap_locate (folder, altname) : StringBuf ();
98 }
99 
text_parse_line(char * text)100 char * text_parse_line (char * text)
101 {
102     char * newline = strchr (text, '\n');
103 
104     if (newline == nullptr)
105         return nullptr;
106 
107     * newline = 0;
108     return newline + 1;
109 }
110 
111 enum ArchiveType {
112     ARCHIVE_UNKNOWN = 0,
113     ARCHIVE_TAR,
114     ARCHIVE_TGZ,
115     ARCHIVE_ZIP,
116     ARCHIVE_TBZ2
117 };
118 
119 typedef StringBuf (* ArchiveExtractFunc) (const char *, const char *);
120 
121 struct ArchiveExtensionType {
122     ArchiveType type;
123     const char *ext;
124 };
125 
126 static ArchiveExtensionType archive_extensions[] = {
127     {ARCHIVE_TAR, ".tar"},
128     {ARCHIVE_ZIP, ".wsz"},
129     {ARCHIVE_ZIP, ".zip"},
130     {ARCHIVE_TGZ, ".tar.gz"},
131     {ARCHIVE_TGZ, ".tgz"},
132     {ARCHIVE_TBZ2, ".tar.bz2"},
133     {ARCHIVE_TBZ2, ".bz2"}
134 };
135 
136 static StringBuf archive_extract_tar (const char * archive, const char * dest);
137 static StringBuf archive_extract_zip (const char * archive, const char * dest);
138 static StringBuf archive_extract_tgz (const char * archive, const char * dest);
139 static StringBuf archive_extract_tbz2 (const char * archive, const char * dest);
140 
141 static ArchiveExtractFunc archive_extract_funcs[] = {
142     nullptr,
143     archive_extract_tar,
144     archive_extract_tgz,
145     archive_extract_zip,
146     archive_extract_tbz2
147 };
148 
149 /* FIXME: these functions can be generalised into a function using a
150  * command lookup table */
151 
get_tar_command()152 static const char * get_tar_command ()
153 {
154     static const char * command = nullptr;
155 
156     if (! command)
157     {
158         if (! (command = getenv("TARCMD")))
159             command = "tar";
160     }
161 
162     return command;
163 }
164 
get_unzip_command()165 static const char * get_unzip_command ()
166 {
167     static const char * command = nullptr;
168 
169     if (! command)
170     {
171         if (! (command = getenv("UNZIPCMD")))
172             command = "unzip";
173     }
174 
175     return command;
176 }
177 
archive_extract_tar(const char * archive,const char * dest)178 static StringBuf archive_extract_tar (const char * archive, const char * dest)
179 {
180     return str_printf ("%s >/dev/null xf \"%s\" -C %s", get_tar_command (), archive, dest);
181 }
182 
archive_extract_zip(const char * archive,const char * dest)183 static StringBuf archive_extract_zip (const char * archive, const char * dest)
184 {
185     return str_printf ("%s >/dev/null -o -j \"%s\" -d %s", get_unzip_command (), archive, dest);
186 }
187 
archive_extract_tgz(const char * archive,const char * dest)188 static StringBuf archive_extract_tgz (const char * archive, const char * dest)
189 {
190     return str_printf ("%s >/dev/null xzf \"%s\" -C %s", get_tar_command (), archive, dest);
191 }
192 
archive_extract_tbz2(const char * archive,const char * dest)193 static StringBuf archive_extract_tbz2 (const char * archive, const char * dest)
194 {
195     return str_printf ("bzip2 -dc \"%s\" | %s >/dev/null xf - -C %s", archive,
196      get_tar_command (), dest);
197 }
198 
archive_get_type(const char * filename)199 static ArchiveType archive_get_type (const char * filename)
200 {
201     for (auto & ext : archive_extensions)
202     {
203         if (str_has_suffix_nocase (filename, ext.ext))
204             return ext.type;
205     }
206 
207     return ARCHIVE_UNKNOWN;
208 }
209 
file_is_archive(const char * filename)210 bool file_is_archive (const char * filename)
211 {
212     return (archive_get_type (filename) != ARCHIVE_UNKNOWN);
213 }
214 
archive_basename(const char * str)215 StringBuf archive_basename (const char * str)
216 {
217     for (auto & ext : archive_extensions)
218     {
219         if (str_has_suffix_nocase (str, ext.ext))
220             return str_copy (str, strlen (str) - strlen (ext.ext));
221     }
222 
223     return StringBuf ();
224 }
225 
226 /**
227  * Escapes characters that are special to the shell inside double quotes.
228  *
229  * @param string String to be escaped.
230  * @return Given string with special characters escaped.
231  */
escape_shell_chars(const char * string)232 static StringBuf escape_shell_chars (const char * string)
233 {
234     const char *special = "$`\"\\";    /* Characters to escape */
235 
236     int num = 0;
237     for (const char * in = string; * in; in ++)
238     {
239         if (strchr (special, * in))
240             num ++;
241     }
242 
243     StringBuf escaped (strlen (string) + num);
244 
245     char * out = escaped;
246     for (const char * in = string; * in; in ++)
247     {
248         if (strchr (special, * in))
249             * out ++ = '\\';
250         * out ++ = * in;
251     }
252 
253     return escaped;
254 }
255 
256 /**
257  * Decompresses the archive "filename" to a temporary directory,
258  * returns the path to the temp dir, or nullptr if failed
259  */
archive_decompress(const char * filename)260 StringBuf archive_decompress (const char * filename)
261 {
262     ArchiveType type = archive_get_type (filename);
263     if (type == ARCHIVE_UNKNOWN)
264         return StringBuf ();
265 
266     StringBuf tmpdir = filename_build ({g_get_tmp_dir (), "audacious.XXXXXX"});
267     if (! g_mkdtemp (tmpdir))
268     {
269         AUDWARN ("Error creating %s: %s\n", (const char *) tmpdir, strerror (errno));
270         return StringBuf ();
271     }
272 
273     StringBuf escaped_filename = escape_shell_chars (filename);
274     StringBuf cmd = archive_extract_funcs[type] (escaped_filename, tmpdir);
275 
276     AUDDBG ("Executing \"%s\"\n", (const char *) cmd);
277     int ret = system (cmd);
278     if (ret != 0)
279     {
280         AUDDBG ("Command \"%s\" returned error %d\n", (const char *) cmd, ret);
281         return StringBuf ();
282     }
283 
284     return tmpdir;
285 }
286 
del_directory_func(const char * path,const char *)287 static void del_directory_func (const char * path, const char *)
288 {
289     if (g_file_test (path, G_FILE_TEST_IS_DIR))
290         del_directory (path);
291     else
292         g_unlink (path);
293 }
294 
del_directory(const char * path)295 void del_directory (const char * path)
296 {
297     dir_foreach (path, del_directory_func);
298     g_rmdir (path);
299 }
300 
string_to_int_array(const char * str)301 Index<int> string_to_int_array (const char * str)
302 {
303     Index<int> array;
304     int temp;
305     const char * ptr = str;
306     char * endptr;
307 
308     for (;;)
309     {
310         temp = strtol (ptr, &endptr, 10);
311         if (ptr == endptr)
312             break;
313         array.append (temp);
314         ptr = endptr;
315         while (! g_ascii_isdigit ((int) * ptr) && (* ptr) != '\0')
316             ptr ++;
317         if (* ptr == '\0')
318             break;
319     }
320 
321     return array;
322 }
323 
dir_foreach(const char * path,DirForeachFunc func)324 bool dir_foreach (const char * path, DirForeachFunc func)
325 {
326     GError * error = nullptr;
327     GDir * dir = g_dir_open (path, 0, & error);
328     if (! dir)
329     {
330         AUDWARN ("Error reading %s: %s\n", path, error->message);
331         g_error_free (error);
332         return false;
333     }
334 
335     const char * entry;
336     while ((entry = g_dir_read_name (dir)))
337         func (filename_build ({path, entry}), entry);
338 
339     g_dir_close (dir);
340     return true;
341 }
342 
make_directory(const char * path)343 void make_directory (const char * path)
344 {
345     if (g_mkdir_with_parents (path, DIRMODE) != 0)
346         AUDWARN ("Error creating %s: %s\n", path, strerror (errno));
347 }
348