1 /* SPDX-License-Identifier: Zlib */
2
3 #include <ctype.h>
4 #include <glib.h>
5 #include <glib/gstdio.h>
6 #include <glib/gi18n-lib.h>
7 #include <limits.h>
8 #include <pwd.h>
9 #include <stdbool.h>
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/types.h>
15 #include <unistd.h>
16
17 #include "utils.h"
18 #include "datastructures.h"
19 #include "session.h"
20 #include "settings.h"
21 #include "internal.h"
22
23 char*
girara_fix_path(const char * path)24 girara_fix_path(const char* path)
25 {
26 if (path == NULL) {
27 return NULL;
28 }
29
30 char* rpath = NULL;
31 if (path[0] == '~') {
32 const size_t len = strlen(path);
33 char* user = NULL;
34 size_t idx = 1;
35
36 if (len > 1 && path[1] != '/') {
37 while (path[idx] && path[idx] != '/') {
38 ++idx;
39 }
40
41 user = g_strndup(path + 1, idx - 1);
42 }
43
44 char* home_path = girara_get_home_directory(user);
45 g_free(user);
46
47 if (home_path == NULL) {
48 return g_strdup(path);
49 }
50
51 rpath = g_build_filename(home_path, path + idx, NULL);
52 g_free(home_path);
53 } else if (g_path_is_absolute(path) == TRUE) {
54 rpath = g_strdup(path);
55 } else {
56 char* curdir = g_get_current_dir();
57 rpath = g_build_filename(curdir, path, NULL);
58 g_free(curdir);
59 }
60
61 return rpath;
62 }
63
64 bool
girara_xdg_open_with_working_directory(const char * uri,const char * working_directory)65 girara_xdg_open_with_working_directory(const char* uri, const char* working_directory)
66 {
67 if (uri == NULL || strlen(uri) == 0) {
68 return false;
69 }
70
71 /* g_spawn_async expects char** */
72 static char xdg_open[] = "xdg-open";
73 char* argv[] = { xdg_open, g_strdup(uri), NULL };
74
75 GError* error = NULL;
76 bool res = g_spawn_async(working_directory, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
77 NULL, NULL, &error);
78 if (error != NULL) {
79 girara_warning("Failed to execute 'xdg-open %s': %s", uri, error->message);
80 g_error_free(error);
81 error = NULL;
82 }
83
84 if (res == false) {
85 /* fall back to `gio open` */
86 char* current_dir = working_directory != NULL ? g_get_current_dir() : NULL;
87 if (working_directory != NULL) {
88 g_chdir(working_directory);
89 }
90
91 res = g_app_info_launch_default_for_uri(uri, NULL, &error);
92 if (error != NULL) {
93 girara_warning("Failed to open '%s': %s", uri, error->message);
94 g_error_free(error);
95 }
96
97 if (working_directory != NULL) {
98 g_chdir(current_dir);
99 g_free(current_dir);
100 }
101 }
102
103 g_free(argv[1]);
104
105 return res;
106 }
107
108 bool
girara_xdg_open(const char * uri)109 girara_xdg_open(const char* uri)
110 {
111 return girara_xdg_open_with_working_directory(uri, NULL);
112 }
113
114 #if defined(HAVE_GETPWNAM_R)
115 static char*
get_home_directory_getpwnam(const char * user)116 get_home_directory_getpwnam(const char* user)
117 {
118 #ifdef _SC_GETPW_R_SIZE_MAX
119 int bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
120 if (bufsize < 0) {
121 bufsize = 4096;
122 }
123 #else
124 const int bufsize = 4096;
125 #endif
126
127 char* buffer = g_try_malloc0(sizeof(char) * bufsize);
128 if (buffer == NULL) {
129 return NULL;
130 }
131
132 struct passwd pwd;
133 struct passwd* result = NULL;
134 if (getpwnam_r(user, &pwd, buffer, bufsize, &result) != 0) {
135 g_free(buffer);
136 return NULL;
137 }
138
139 char* dir = g_strdup(pwd.pw_dir);
140 g_free(buffer);
141 return dir;
142 }
143 #else
144 static char*
get_home_directory_getpwnam(const char * user)145 get_home_directory_getpwnam(const char* user)
146 {
147 const struct passwd* pwd = getpwnam(user);
148 if (pwd != NULL) {
149 return g_strdup(pwd->pw_dir);
150 }
151
152 return NULL;
153 }
154 #endif
155
156 char*
girara_get_home_directory(const char * user)157 girara_get_home_directory(const char* user)
158 {
159 if (user == NULL || g_strcmp0(user, g_get_user_name()) == 0) {
160 return g_strdup(g_get_home_dir());
161 }
162
163 return get_home_directory_getpwnam(user);
164 }
165
166 char*
girara_get_xdg_path(girara_xdg_path_t path)167 girara_get_xdg_path(girara_xdg_path_t path)
168 {
169 static const char VARS[][16] = {
170 [XDG_CONFIG_DIRS] = "XDG_CONFIG_DIRS",
171 [XDG_DATA_DIRS] = "XDG_DATA_DIRS"
172 };
173
174 static const char DEFAULTS[][29] = {
175 [XDG_CONFIG_DIRS] = "/etc/xdg",
176 [XDG_DATA_DIRS] = "/usr/local/share/:/usr/share"
177 };
178
179 switch (path) {
180 case XDG_DATA:
181 return g_strdup(g_get_user_data_dir());
182 case XDG_CONFIG:
183 return g_strdup(g_get_user_config_dir());
184 case XDG_CONFIG_DIRS:
185 case XDG_DATA_DIRS:
186 {
187 const char* tmp = g_getenv(VARS[path]);
188 if (tmp == NULL || !g_strcmp0(tmp, "")) {
189 return g_strdup(DEFAULTS[path]);
190 }
191 return g_strdup(tmp);
192 }
193 case XDG_CACHE:
194 return g_strdup(g_get_user_cache_dir());
195 }
196
197 return NULL;
198 }
199
200 girara_list_t*
girara_split_path_array(const char * patharray)201 girara_split_path_array(const char* patharray)
202 {
203 if (patharray == NULL || !g_strcmp0(patharray, "")) {
204 return NULL;
205 }
206
207 girara_list_t* res = girara_list_new2(g_free);
208 char** paths = g_strsplit(patharray, ":", 0);
209 for (size_t i = 0; paths[i] != NULL; ++i) {
210 girara_list_append(res, g_strdup(paths[i]));
211 }
212 g_strfreev(paths);
213
214 return res;
215 }
216
217 FILE*
girara_file_open(const char * path,const char * mode)218 girara_file_open(const char* path, const char* mode)
219 {
220 if (path == NULL || mode == NULL) {
221 return NULL;
222 }
223
224 char* fixed_path = girara_fix_path(path);
225 if (fixed_path == NULL) {
226 return NULL;
227 }
228
229 FILE* fp = fopen(fixed_path, mode);
230 g_free(fixed_path);
231 if (fp == NULL) {
232 return NULL;
233 }
234
235 return fp;
236 }
237
238 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
239 char*
girara_file_read_line(FILE * file)240 girara_file_read_line(FILE* file)
241 {
242 if (file == NULL) {
243 return NULL;
244 }
245
246 size_t size = 0;
247 char* line = fgetln(file, &size);
248 if (line == NULL) {
249 return NULL;
250 }
251
252 char* copy = g_strndup(line, size);
253 if (copy == NULL) {
254 return NULL;
255 }
256
257 /* remove the trailing line deliminator */
258 g_strdelimit(copy, "\n\r", '\0');
259
260 return copy;
261 }
262 #else
263 char*
girara_file_read_line(FILE * file)264 girara_file_read_line(FILE* file)
265 {
266 if (file == NULL) {
267 return NULL;
268 }
269
270 size_t size = 0;
271 char* line = NULL;
272 if (getline(&line, &size, file) == -1) {
273 if (line != NULL) {
274 free(line);
275 }
276 return NULL;
277 }
278
279 /* remove the trailing line deliminator */
280 g_strdelimit(line, "\n\r", '\0');
281
282 char* duplicate = g_strdup(line);
283 free(line);
284 return duplicate;
285 }
286 #endif
287
288 char*
girara_file_read(const char * path)289 girara_file_read(const char* path)
290 {
291 if (path == NULL) {
292 return NULL;
293 }
294
295 FILE* file = girara_file_open(path, "r");
296 if (file == NULL) {
297 return NULL;
298 }
299
300 char* content = girara_file_read2(file);
301 fclose(file);
302 return content;
303 }
304
305 char*
girara_file_read2(FILE * file)306 girara_file_read2(FILE* file)
307 {
308 if (file == NULL) {
309 return NULL;
310 }
311
312 const off_t curpos = ftello(file);
313 if (curpos == -1) {
314 return NULL;
315 }
316
317 fseeko(file, 0, SEEK_END);
318 const off_t size = ftello(file) - curpos;
319 fseeko(file, curpos, SEEK_SET);
320
321 if (size == 0) {
322 char* content = malloc(1);
323 content[0] = '\0';
324 return content;
325 }
326 /* this can happen on 32 bit systems */
327 if ((uintmax_t)size >= (uintmax_t)SIZE_MAX) {
328 girara_error("file is too large");
329 return NULL;
330 }
331
332 char* buffer = malloc(size + 1);
333 if (buffer == NULL) {
334 return NULL;
335 }
336
337 size_t read = fread(buffer, size, 1, file);
338 if (read != 1) {
339 free(buffer);
340 return NULL;
341 }
342
343 buffer[size] = '\0';
344 return buffer;
345 }
346
347 void
girara_clean_line(char * line)348 girara_clean_line(char* line)
349 {
350 if (line == NULL) {
351 return;
352 }
353
354 unsigned int i = 0;
355 unsigned int j = 0;
356 bool ws_mode = true;
357
358 for(i = 0; i < strlen(line); i++) {
359 if (isspace(line[i]) != 0) {
360 if (ws_mode == true) {
361 continue;
362 }
363
364 line[j++] = ' ';
365 ws_mode = true;
366 } else {
367 line[j++] = line[i];
368 ws_mode = false;
369 }
370 }
371
372 line[j] = '\0';
373 }
374
375 void*
girara_safe_realloc(void ** ptr,size_t size)376 girara_safe_realloc(void** ptr, size_t size)
377 {
378 if (ptr == NULL) {
379 return NULL;
380 }
381
382 if (size == 0) {
383 goto error_free;
384 }
385
386 void* tmp = realloc(*ptr, size);
387 if (tmp == NULL) {
388 goto error_free;
389 }
390
391 *ptr = tmp;
392 return *ptr;
393
394 error_free:
395
396 free(*ptr);
397 *ptr = NULL;
398
399 return NULL;
400 }
401
402 char*
girara_escape_string(const char * value)403 girara_escape_string(const char* value)
404 {
405 if (value == NULL) {
406 return NULL;
407 }
408
409 GString* str = g_string_new("");
410 while (*value != '\0') {
411 const char c = *value++;
412 if (strchr("\\ \t\"\'", c) != NULL) {
413 g_string_append_c(str, '\\');
414 }
415 g_string_append_c(str, c);
416 }
417
418 return g_string_free(str, FALSE);
419 }
420
421 char*
girara_replace_substring(const char * string,const char * old,const char * new)422 girara_replace_substring(const char* string, const char* old, const char* new)
423 {
424 if (string == NULL || old == NULL || new == NULL) {
425 return NULL;
426 }
427
428 if (*string == '\0' || *old == '\0' || strstr(string, old) == NULL) {
429 return g_strdup(string);
430 }
431
432 gchar** split = g_strsplit(string, old, -1);
433 char* ret = g_strjoinv(new, split);
434 g_strfreev(split);
435
436 return ret;
437 }
438
439 bool
girara_exec_with_argument_list(girara_session_t * session,girara_list_t * argument_list)440 girara_exec_with_argument_list(girara_session_t* session, girara_list_t* argument_list)
441 {
442 if (session == NULL || argument_list == NULL) {
443 return false;
444 }
445
446 char* cmd = NULL;
447 girara_setting_get(session, "exec-command", &cmd);
448 if (cmd == NULL || strlen(cmd) == 0) {
449 girara_debug("exec-command is empty, executing directly.");
450 g_free(cmd);
451 cmd = NULL;
452 }
453
454 bool dont_append_first_space = cmd == NULL;
455 GString* command = g_string_new(cmd ? cmd : "");
456 g_free(cmd);
457
458 GIRARA_LIST_FOREACH_BODY(argument_list, char*, value, {
459 if (dont_append_first_space == false) {
460 g_string_append_c(command, ' ');
461 }
462 dont_append_first_space = false;
463 char* tmp = g_shell_quote(value);
464 g_string_append(command, tmp);
465 g_free(tmp);
466 });
467
468 GError* error = NULL;
469 girara_info("executing: %s", command->str);
470 gboolean ret = g_spawn_command_line_async(command->str, &error);
471 if (error != NULL) {
472 girara_warning("Failed to execute command: %s", error->message);
473 girara_notify(session, GIRARA_ERROR, _("Failed to execute command: %s"), error->message);
474 g_error_free(error);
475 }
476
477 g_string_free(command, TRUE);
478
479 return ret;
480 }
481
482 void
widget_add_class(GtkWidget * widget,const char * styleclass)483 widget_add_class(GtkWidget* widget, const char* styleclass)
484 {
485 if (widget == NULL || styleclass == NULL) {
486 return;
487 }
488
489 GtkStyleContext* context = gtk_widget_get_style_context(widget);
490 if (gtk_style_context_has_class(context, styleclass) == FALSE) {
491 gtk_style_context_add_class(context, styleclass);
492 }
493 }
494
495 void
widget_remove_class(GtkWidget * widget,const char * styleclass)496 widget_remove_class(GtkWidget* widget, const char* styleclass)
497 {
498 if (widget == NULL || styleclass == NULL) {
499 return;
500 }
501
502 GtkStyleContext* context = gtk_widget_get_style_context(widget);
503 if (gtk_style_context_has_class(context, styleclass) == TRUE) {
504 gtk_style_context_remove_class(context, styleclass);
505 }
506 }
507
508 const char*
girara_version(void)509 girara_version(void)
510 {
511 return GIRARA_VERSION;
512 }
513
514 int
list_strcmp(const void * data1,const void * data2)515 list_strcmp(const void* data1, const void* data2)
516 {
517 const char* str1 = data1;
518 const char* str2 = data2;
519
520 return g_strcmp0(str1, str2);
521 }
522