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