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