1 /**
2  * vimb - a webkit based vim like browser.
3  *
4  * Copyright (C) 2012-2018 Daniel Carl
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://www.gnu.org/licenses/.
18  */
19 
20 #include <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <glib.h>
24 #include <glib/gstdio.h>
25 #include <JavaScriptCore/JavaScript.h>
26 #include <pwd.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/file.h>
30 #include <unistd.h>
31 
32 #include "ascii.h"
33 #include "completion.h"
34 #include "util.h"
35 
36 static struct {
37     char    *config_dir;
38 } util;
39 
40 extern struct Vimb vb;
41 
42 static void create_dir_if_not_exists(const char *dirpath);
43 static gboolean match(const char *pattern, int patlen, const char *subject);
44 static gboolean match_list(const char *pattern, int patlen, const char *subject);
45 
46 /**
47  * Build the absolute file path of given path and possible given directory.
48  *
49  * Returned path must be freed.
50  */
util_build_path(State state,const char * path,const char * dir)51 char *util_build_path(State state, const char *path, const char *dir)
52 {
53     char *fullPath = NULL, *fexp, *dexp, *p;
54     int expflags   = UTIL_EXP_TILDE|UTIL_EXP_DOLLAR;
55 
56     /* if the path could be expanded */
57     if ((fexp = util_expand(state, path, expflags))) {
58         if (*fexp == '/') {
59             /* path is already absolute, no need to use given dir - there is
60              * no need to free fexp, because this should be done by the caller
61              * on fullPath later */
62             fullPath = fexp;
63         } else if (dir && *dir) {
64             /* try to expand also the dir given - this may be ~/path */
65             if ((dexp = util_expand(state, dir, expflags))) {
66                 /* use expanded dir and append expanded path */
67                 fullPath = g_build_filename(dexp, fexp, NULL);
68                 g_free(dexp);
69             }
70             g_free(fexp);
71         }
72     }
73 
74     /* if full path not found use current dir */
75     if (!fullPath) {
76         fullPath = g_build_filename(g_get_current_dir(), path, NULL);
77     }
78 
79     /* Create the directory part of the path if it does not exists. */
80     if ((p = strrchr(fullPath, '/'))) {
81         gboolean res;
82         *p = '\0';
83         res = util_create_dir_if_not_exists(fullPath);
84         *p = '/';
85 
86         if (!res) {
87             g_free(fullPath);
88 
89             return NULL;
90         }
91     }
92 
93     return fullPath;
94 }
95 
96 /**
97  * Free memory for allocated path strings.
98  */
util_cleanup(void)99 void util_cleanup(void)
100 {
101     if (util.config_dir) {
102         g_free(util.config_dir);
103     }
104 }
105 
util_create_dir_if_not_exists(const char * dirpath)106 gboolean util_create_dir_if_not_exists(const char *dirpath)
107 {
108     if (g_mkdir_with_parents(dirpath, 0755) == -1) {
109         g_critical("Could not create directory '%s': %s", dirpath, g_strerror(errno));
110 
111         return FALSE;
112     }
113 
114     return TRUE;
115 }
116 
117 /**
118  * Creates a temporary file with given content.
119  *
120  * Upon success, and if file is non-NULL, the actual file path used is
121  * returned in file. This string should be freed with g_free() when not
122  * needed any longer.
123  */
util_create_tmp_file(const char * content,char ** file)124 gboolean util_create_tmp_file(const char *content, char **file)
125 {
126     int fp;
127     ssize_t bytes, len;
128 
129     fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL);
130     if (fp == -1) {
131         g_critical("Could not create temp file %s", *file);
132         g_free(*file);
133         return FALSE;
134     }
135 
136     if (content == NULL) {
137         close(fp);
138         return TRUE;
139     }
140 
141     len = strlen(content);
142 
143     /* write content into temporary file */
144     bytes = write(fp, content, len);
145     if (bytes < len) {
146         close(fp);
147         unlink(*file);
148         g_critical("Could not write temp file %s", *file);
149         g_free(*file);
150 
151         return FALSE;
152     }
153     close(fp);
154 
155     return TRUE;
156 }
157 
158 /**
159  * Expand ~user, ~/, $ENV and ${ENV} for given string into new allocated
160  * string.
161  *
162  * Returned path must be g_freed.
163  */
util_expand(State state,const char * src,int expflags)164 char *util_expand(State state, const char *src, int expflags)
165 {
166     const char **input = &src;
167     char *result;
168     GString *dst = g_string_new("");
169     int flags    = expflags;
170 
171     while (**input) {
172         util_parse_expansion(state, input, dst, flags, "\\");
173         if (VB_IS_SEPARATOR(**input)) {
174             /* after space the tilde expansion is allowed */
175             flags = expflags;
176         } else {
177             /* remove tile expansion for next loop */
178             flags &= ~UTIL_EXP_TILDE;
179         }
180         /* move pointer to the next char */
181         (*input)++;
182     }
183 
184     result = dst->str;
185     g_string_free(dst, FALSE);
186 
187     return result;
188 }
189 
190 /**
191  * Append new data to file.
192  *
193  * @file:   File to append the data
194  * @format: Format string used to process va_list
195  */
util_file_append(const char * file,const char * format,...)196 gboolean util_file_append(const char *file, const char *format, ...)
197 {
198     va_list args;
199     FILE *f;
200 
201     if (file && (f = fopen(file, "a+"))) {
202         flock(fileno(f), LOCK_EX);
203 
204         va_start(args, format);
205         vfprintf(f, format, args);
206         va_end(args);
207 
208         flock(fileno(f), LOCK_UN);
209         fclose(f);
210 
211         return TRUE;
212     }
213     return FALSE;
214 }
215 
216 /**
217  * Prepend new data to file.
218  *
219  * @file:   File to prepend the data
220  * @format: Format string used to process va_list
221  */
util_file_prepend(const char * file,const char * format,...)222 gboolean util_file_prepend(const char *file, const char *format, ...)
223 {
224     gboolean res = FALSE;
225     va_list args;
226     char *content;
227     FILE *f;
228     if (!file) {
229         return FALSE;
230     }
231 
232     content = util_get_file_contents(file, NULL);
233     if ((f = fopen(file, "w"))) {
234         flock(fileno(f), LOCK_EX);
235 
236         va_start(args, format);
237         /* write new content to the file */
238         vfprintf(f, format, args);
239         va_end(args);
240 
241         /* append previous file content */
242         fputs(content, f);
243 
244         flock(fileno(f), LOCK_UN);
245         fclose(f);
246 
247         res = TRUE;
248     }
249     g_free(content);
250 
251     return res;
252 }
253 
254 /**
255  * Prepend a new line to the file and make sure there are not more than
256  * max_lines in the file.
257  *
258  * @file:       File to prepend the data
259  * @line:       Line to be written as new first line into the file.
260  *              The line ending is inserted automatic.
261  * @max_lines   Maximum number of lines in file after the operation.
262  */
util_file_prepend_line(const char * file,const char * line,unsigned int max_lines)263 void util_file_prepend_line(const char *file, const char *line,
264         unsigned int max_lines)
265 {
266     char **lines;
267     GString *new_content;
268 
269     g_assert(file);
270     g_assert(line);
271 
272     lines = util_get_lines(file);
273     /* Write first the new line into the string and append the new line. */
274     new_content = g_string_new(line);
275     g_string_append(new_content, "\n");
276     if (lines) {
277         int len, i;
278 
279         len = g_strv_length(lines);
280         for (i = 0; i < len - 1 && i < max_lines - 1; i++) {
281             g_string_append_printf(new_content, "%s\n", lines[i]);
282         }
283         g_strfreev(lines);
284     }
285     util_file_set_content(file, new_content->str);
286     g_string_free(new_content, TRUE);
287 }
288 
289 /**
290  * Retrieves the first line from file and delete it from file.
291  *
292  * @file:       file to read from
293  * @item_count: will be filled with the number of remaining lines in file if it
294  *              is not NULL.
295  *
296  * Returned string must be freed with g_free.
297  */
util_file_pop_line(const char * file,int * item_count)298 char *util_file_pop_line(const char *file, int *item_count)
299 {
300     char **lines;
301     char *new,
302          *line = NULL;
303     int len,
304         count = 0;
305 
306     if (!file) {
307         return NULL;
308     }
309     lines = util_get_lines(file);
310 
311     if (lines) {
312         len = g_strv_length(lines);
313         if (len) {
314             line = g_strdup(lines[0]);
315             /* minus one for last empty item and one for popped item */
316             count = len - 2;
317             new   = g_strjoinv("\n", lines + 1);
318             util_file_set_content(file, new);
319             g_free(new);
320         }
321         g_strfreev(lines);
322     }
323 
324     if (item_count) {
325         *item_count = count;
326     }
327 
328     return line;
329 }
330 
331 /**
332  * Retrieves the config directory path according to current used profile.
333  * Returned string must be freed.
334  */
util_get_config_dir(void)335 char *util_get_config_dir(void)
336 {
337     char *path = g_build_filename(g_get_user_config_dir(), PROJECT, vb.profile, NULL);
338     create_dir_if_not_exists(path);
339 
340     return path;
341 }
342 
343 /**
344  * Retrieves the length bytes from given file.
345  *
346  * The memory of returned string have to be freed with g_free().
347  */
util_get_file_contents(const char * filename,gsize * length)348 char *util_get_file_contents(const char *filename, gsize *length)
349 {
350     GError *error = NULL;
351     char *content = NULL;
352 
353     if (filename && !g_file_get_contents(filename, &content, length, &error)) {
354         g_warning("Cannot open %s: %s", filename, error->message);
355         g_error_free(error);
356     }
357     return content;
358 }
359 
360 /**
361  * Atomicly writes contents to given file.
362  * Returns TRUE on success, FALSE otherwise.
363  */
util_file_set_content(const char * file,const char * contents)364 gboolean util_file_set_content(const char *file, const char *contents)
365 {
366     gboolean retval = FALSE;
367     char *tmp_name;
368     int fd, mode;
369     gsize length;
370     struct stat st;
371 
372     mode = 0600;
373     if (stat(file, &st) == 0) {
374         mode = st.st_mode;
375     }
376 
377     /* Create a temporary file. */
378     tmp_name = g_strconcat(file, ".XXXXXX", NULL);
379     errno    = 0;
380     fd       = g_mkstemp_full(tmp_name, O_RDWR, mode);
381     length   = strlen(contents);
382 
383     if (fd == -1) {
384         g_error("Failed to create file %s: %s", tmp_name, g_strerror(errno));
385 
386         goto out;
387     }
388 
389     /* Write the contents to the temporary file. */
390     while (length > 0) {
391         gssize s;
392         s = write(fd, contents, length);
393         if (s < 0) {
394             if (errno == EINTR) {
395                 continue;
396             }
397             g_error("Failed to write to file %s: write() failed: %s",
398                     tmp_name, g_strerror(errno));
399             close(fd);
400             g_unlink(tmp_name);
401 
402             goto out;
403         }
404 
405         g_assert (s <= length);
406 
407         contents += s;
408         length   -= s;
409     }
410 
411     if (!g_close(fd, NULL)) {
412         g_unlink(tmp_name);
413         goto out;
414     }
415 
416     /* Atomic rename the temporary file into the destination file. */
417     if (g_rename(tmp_name, file) == -1) {
418         g_error("Failed to rename file %s to %s: g_rename() failed: %s",
419                 tmp_name, file, g_strerror(errno));
420         g_unlink(tmp_name);
421         goto out;
422     }
423 
424     retval = TRUE;
425 
426 out:
427     g_free(tmp_name);
428 
429     return retval;
430 }
431 
432 /**
433  * Retrieves the file content as lines.
434  *
435  * The result have to be freed by g_strfreev().
436  */
util_get_lines(const char * filename)437 char **util_get_lines(const char *filename)
438 {
439     char *content;
440     char **lines  = NULL;
441 
442     if (!filename) {
443         return NULL;
444     }
445 
446     if (g_file_get_contents(filename, &content, NULL, NULL)) {
447         /* split the file content into lines */
448         lines = g_strsplit(content, "\n", -1);
449         g_free(content);
450     }
451     return lines;
452 }
453 
454 /**
455  * Retrieves a list with unique items from file. The uniqueness is calculated
456  * based on the lines comparing all chars until the next <tab> char or end of
457  * line.
458  *
459  * @func:        Function to parse a single line to item.
460  * @max_items:   maximum number of items that are returned, use 0 for
461  *               unlimited items
462  */
util_strv_to_unique_list(char ** lines,Util_Content_Func func,guint max_items)463 GList *util_strv_to_unique_list(char **lines, Util_Content_Func func,
464         guint max_items)
465 {
466     char *line;
467     int i, len;
468     GList *gl = NULL;
469     GHashTable *ht;
470 
471     if (!lines) {
472         return NULL;
473     }
474 
475     /* Use the hashtable to check for duplicates in a faster way than by
476      * iterating over the generated list itself. So it's enough to store the
477      * the keys only. */
478     ht = g_hash_table_new(g_str_hash, g_str_equal);
479 
480     /* Begin with the last line of the file to make unique check easier -
481      * every already existing item in the table is the latest, so we don't need
482      * to do anything if an item already exists in the hash table. */
483     len = g_strv_length(lines);
484     for (i = len - 1; i >= 0; i--) {
485         char *key, *data;
486         void *item;
487 
488         line = lines[i];
489         g_strstrip(line);
490         if (!*line) {
491             continue;
492         }
493 
494         /* if line contains tab char - separate the line at this */
495         if ((data = strchr(line, '\t'))) {
496             *data = '\0';
497             key   = line;
498             data++;
499         } else {
500             key  = line;
501             data = NULL;
502         }
503 
504         /* Check if the entry is already in the result list. */
505         if (g_hash_table_lookup_extended(ht, key, NULL, NULL)) {
506             continue;
507         }
508 
509         /* Item is new - prepend it to the list. Because the record are read
510          * in reverse order the prepend generates a list in the right order. */
511         if ((item = func(key, data))) {
512             g_hash_table_insert(ht, key, NULL);
513             gl = g_list_prepend(gl, item);
514 
515             /* Don't put more entries into the list than requested. */
516             if (max_items && g_hash_table_size(ht) >= max_items) {
517                 break;
518             }
519         }
520     }
521 
522     g_hash_table_destroy(ht);
523 
524     return gl;
525 }
526 
527 /**
528  * Fills the given list store by matching data of also given src list.
529  */
util_fill_completion(GtkListStore * store,const char * input,GList * src)530 gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src)
531 {
532     gboolean found = FALSE;
533     GtkTreeIter iter;
534 
535     if (!input || !*input) {
536         for (GList *l = src; l; l = l->next) {
537             gtk_list_store_append(store, &iter);
538             gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
539             found = TRUE;
540         }
541     } else {
542         for (GList *l = src; l; l = l->next) {
543             char *value = (char*)l->data;
544             if (g_str_has_prefix(value, input)) {
545                 gtk_list_store_append(store, &iter);
546                 gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
547                 found = TRUE;
548             }
549         }
550     }
551 
552     return found;
553 }
554 
555 /**
556  * Fills file path completion entries into given list store for also given
557  * input.
558  */
util_filename_fill_completion(State state,GtkListStore * store,const char * input)559 gboolean util_filename_fill_completion(State state, GtkListStore *store, const char *input)
560 {
561     gboolean found = FALSE;
562     GError *error  = NULL;
563     char *input_dirname, *real_dirname;
564     const char *last_slash, *input_basename;
565     GDir *dir;
566 
567     last_slash     = strrchr(input, '/');
568     input_basename = last_slash ? last_slash + 1 : input;
569     input_dirname  = g_strndup(input, input_basename - input);
570     real_dirname   = util_expand(
571         state,
572         *input_dirname ? input_dirname : ".",
573         UTIL_EXP_TILDE|UTIL_EXP_DOLLAR|UTIL_EXP_SPECIAL
574     );
575 
576     dir = g_dir_open(real_dirname, 0, &error);
577     if (error) {
578         /* Can't open directory, likely bad user input */
579         g_error_free(error);
580     } else {
581         GtkTreeIter iter;
582         const char *filename;
583         char *fullpath, *result;
584 
585         while ((filename = g_dir_read_name(dir))) {
586             if (g_str_has_prefix(filename, input_basename)) {
587                 fullpath = g_build_filename(real_dirname, filename, NULL);
588                 if (g_file_test(fullpath, G_FILE_TEST_IS_DIR)) {
589                     result = g_strconcat(input_dirname, filename, "/", NULL);
590                 } else {
591                     result = g_strconcat(input_dirname, filename, NULL);
592                 }
593                 g_free(fullpath);
594                 gtk_list_store_append(store, &iter);
595                 gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, result, -1);
596                 g_free(result);
597                 found = TRUE;
598             }
599         }
600         g_dir_close(dir);
601     }
602 
603     g_free(input_dirname);
604     g_free(real_dirname);
605 
606     return found;
607 }
608 
609 /**
610  * Returns the script result as string.
611  * Returned string must be freed by g_free.
612  */
util_js_result_as_string(WebKitJavascriptResult * result)613 char *util_js_result_as_string(WebKitJavascriptResult *result)
614 {
615     JSValueRef value;
616     JSStringRef string;
617     size_t len;
618     char *retval = NULL;
619 
620     value  = webkit_javascript_result_get_value(result);
621     string = JSValueToStringCopy(webkit_javascript_result_get_global_context(result),
622                 value, NULL);
623 
624     len = JSStringGetMaximumUTF8CStringSize(string);
625     if (len) {
626         retval = g_malloc(len);
627         JSStringGetUTF8CString(string, retval, len);
628     }
629     JSStringRelease(string);
630 
631     return retval;
632 }
633 
util_js_result_as_number(WebKitJavascriptResult * result)634 double util_js_result_as_number(WebKitJavascriptResult *result)
635 {
636     JSValueRef value = webkit_javascript_result_get_value(result);
637 
638     return JSValueToNumber(webkit_javascript_result_get_global_context(result), value,
639         NULL);
640 }
641 
642 /**
643  * Reads given input and try to parse ~/, ~user, $VAR or ${VAR} expansion
644  * from the start of the input and moves the input pointer to the first
645  * not expanded char. If no expansion pattern was found, the first char is
646  * appended to given GString.
647  *
648  * Please note that for a single ~, g_get_home_dir() is used and if a valid
649  * HOME environment variable is set it is preferred than passwd file.
650  * However, for ~user expansion the passwd file is always used.
651  *
652  * @input:     String pointer with the content to be parsed.
653  * @str:       GString that will be filled with expanded content.
654  * @flags      Flags that determine which expansion are processed.
655  * @quoteable: String of chars that are additionally escapable by \.
656  * Returns TRUE if input started with expandable pattern.
657  */
util_parse_expansion(State state,const char ** input,GString * str,int flags,const char * quoteable)658 gboolean util_parse_expansion(State state, const char **input, GString *str,
659         int flags, const char *quoteable)
660 {
661     GString *name;
662     const char *env, *prev, quote = '\\';
663     struct passwd *pwd;
664     gboolean expanded = FALSE;
665 
666     prev = *input;
667     if (flags & UTIL_EXP_TILDE && **input == '~') {
668         /* skip ~ */
669         (*input)++;
670 
671         if (**input == '/') {
672             g_string_append(str, g_get_home_dir());
673             expanded = TRUE;
674             /* if there is no char or space after ~/ skip the / to get
675              * /home/user instead of /home/user/ */
676             if (!*(*input + 1) || VB_IS_SPACE(*(*input + 1))) {
677                 (*input)++;
678             }
679         } else {
680             /* look ahead to / space or end of string to get a possible
681              * username for ~user pattern */
682             name = g_string_new("");
683             /* current char is ~ that is skipped to get the user name */
684             while (VB_IS_IDENT(**input)) {
685                 g_string_append_c(name, **input);
686                 (*input)++;
687             }
688             /* append the name to the destination string */
689             if ((pwd = getpwnam(name->str))) {
690                 g_string_append(str, pwd->pw_dir);
691                 expanded = TRUE;
692             }
693             g_string_free(name, TRUE);
694         }
695         /* move pointer back to last expanded char */
696         (*input)--;
697     } else if (flags & UTIL_EXP_DOLLAR && **input == '$') {
698         /* skip the $ */
699         (*input)++;
700 
701         name = g_string_new("");
702         /* look for ${VAR}*/
703         if (**input == '{') {
704             /* skip { */
705             (*input)++;
706 
707             /* look ahead to } or end of string */
708             while (**input && **input != '}') {
709                 g_string_append_c(name, **input);
710                 (*input)++;
711             }
712             /* if the } was reached - skip this */
713             if (**input == '}') {
714                 (*input)++;
715             }
716         } else { /* process $VAR */
717             /* look ahead to /, space or end of string */
718             while (VB_IS_IDENT(**input)) {
719                 g_string_append_c(name, **input);
720                 (*input)++;
721             }
722         }
723         /* append the variable to the destination string */
724         if ((env = g_getenv(name->str))) {
725             g_string_append(str, env);
726         }
727         /* move pointer back to last expanded char */
728         (*input)--;
729         /* variable are expanded even if they do not exists */
730         expanded = TRUE;
731         g_string_free(name, TRUE);
732     } else if (flags & UTIL_EXP_SPECIAL && **input == '%') {
733         if (state.uri) {
734             /* TODO check for modifiers like :h:t:r:e */
735             g_string_append(str, state.uri);
736             expanded = TRUE;
737         }
738     }
739 
740     if (!expanded) {
741         /* restore the pointer position if no expansion was found */
742         *input = prev;
743 
744         /* handle escaping of quoteable chars */
745         if (**input == quote) {
746             /* move pointer to the next char */
747             (*input)++;
748             if (!**input) {
749                 /* if input ends here - use only the quote char */
750                 g_string_append_c(str, quote);
751                 (*input)--;
752             } else if (strchr(quoteable, **input)
753                 || (flags & UTIL_EXP_TILDE && **input == '~')
754                 || (flags & UTIL_EXP_DOLLAR && **input == '$')
755                 || (flags & UTIL_EXP_SPECIAL && **input == '%')
756             ) {
757                 /* escaped char becomes only char */
758                 g_string_append_c(str, **input);
759             } else {
760                 /* put escape char and next char into the result string */
761                 g_string_append_c(str, quote);
762                 g_string_append_c(str, **input);
763             }
764         } else {
765             /* take the char like it is */
766             g_string_append_c(str, **input);
767         }
768     }
769 
770     return expanded;
771 }
772 
773 /**
774  * Sanituze filename by removeing directory separator by underscore.
775  *
776  * The string is modified in place.
777  */
util_sanitize_filename(char * filename)778 char *util_sanitize_filename(char *filename)
779 {
780     return g_strdelimit(filename, G_DIR_SEPARATOR_S, '_');
781 }
782 
783 /**
784  * Strips password from a uri.
785  *
786  * Return newly allocated string or NULL.
787  */
util_sanitize_uri(const char * uri_str)788 char *util_sanitize_uri(const char *uri_str)
789 {
790     SoupURI *uri;
791     char *sanitized_uri;
792     char *for_display;
793 
794     if (!uri_str) {
795         return NULL;
796     }
797 #if WEBKIT_CHECK_VERSION(2, 24, 0)
798     for_display = webkit_uri_for_display(uri_str);
799     if (!for_display) {
800         for_display = g_strdup(uri_str);
801     }
802 #else
803     for_display = g_strdup(uri_str);
804 #endif
805 
806     /* Sanitize the uri only in case there is a @ which might be the indicator
807      * for credentials used in uri. */
808     if (!strchr(for_display, '@')) {
809         return for_display;
810     }
811 
812     uri           = soup_uri_new(for_display);
813     sanitized_uri = soup_uri_to_string(uri, FALSE);
814     soup_uri_free(uri);
815     g_free(for_display);
816 
817     return sanitized_uri;
818 }
819 
820 
util_strcasestr(const char * haystack,const char * needle)821 char *util_strcasestr(const char *haystack, const char *needle)
822 {
823     guchar c1, c2;
824     int i, j;
825     int nlen = strlen(needle);
826     int hlen = strlen(haystack) - nlen + 1;
827 
828     for (i = 0; i < hlen; i++) {
829         for (j = 0; j < nlen; j++) {
830             c1 = haystack[i + j];
831             c2 = needle[j];
832             if (toupper(c1) != toupper(c2)) {
833                 goto next;
834             }
835         }
836         return (char*)haystack + i;
837 next:
838         ;
839     }
840     return NULL;
841 }
842 
843 /**
844  * Replaces appearances of search in string by given replace.
845  * Returns a new allocated string if search was found.
846  */
util_str_replace(const char * search,const char * replace,const char * string)847 char *util_str_replace(const char *search, const char *replace, const char *string)
848 {
849     if (!string) {
850         return NULL;
851     }
852 
853     char **buf = g_strsplit(string, search, -1);
854     char *ret  = g_strjoinv(replace, buf);
855     g_strfreev(buf);
856 
857     return ret;
858 }
859 
860 /**
861  * Escapes some special characters in the source string by inserting a '\'
862  * before them. Acts like g_strescape() but does not demage utf8 chars.
863  * Returns a newly allocated string.
864  */
util_strescape(const char * source,const char * exceptions)865 char *util_strescape(const char *source, const char *exceptions)
866 {
867     GString *result = g_string_new(NULL);
868     while (TRUE) {
869         char c = *source++;
870         if ('\0' == c) {
871             goto done;
872         }
873         if (exceptions && !strchr(exceptions, c)) {
874             continue;
875         }
876         switch (c) {
877             case '\n':
878                 g_string_append(result, "\\n");
879                 break;
880             case '\"':
881                 g_string_append(result, "\\\"");
882                 break;
883             case '\\':
884                 g_string_append(result, "\\\\");
885                 break;
886             case '\b':
887                 g_string_append(result, "\\b");
888                 break;
889             case '\f':
890                 g_string_append(result, "\\f");
891                 break;
892             case '\r':
893                 g_string_append(result, "\\r");
894                 break;
895             case '\t':
896                 g_string_append(result, "\\t");
897                 break;
898             default:
899                 g_string_append_c(result, c);
900         }
901     }
902 done:
903     return g_string_free(result, FALSE);
904 }
905 
create_dir_if_not_exists(const char * dirpath)906 static void create_dir_if_not_exists(const char *dirpath)
907 {
908     if (!g_file_test(dirpath, G_FILE_TEST_IS_DIR)) {
909         g_mkdir_with_parents(dirpath, 0755);
910     }
911 }
912 
913 /**
914  * Compares given string against also given list of patterns.
915  *
916  * *         Matches any sequence of characters.
917  * ?         Matches any single character except of '/'.
918  * {foo,bar} Matches foo or bar - '{', ',' and '}' within this pattern must be
919  *           escaped by '\'. '*' and '?' have no special meaning within the
920  *           curly braces.
921  * *?{}      these chars must always be escaped by '\' to match them literally
922  */
util_wildmatch(const char * pattern,const char * subject)923 gboolean util_wildmatch(const char *pattern, const char *subject)
924 {
925     const char *end;
926     int braces, patlen, count;
927 
928     /* loop through all pattens */
929     for (count = 0; *pattern; pattern = (*end == ',' ? end + 1 : end), count++) {
930         /* find end of the pattern - but be careful with comma in curly braces */
931         braces = 0;
932         for (end = pattern; *end && (*end != ',' || braces || *(end - 1) == '\\'); ++end) {
933             if (*end == '{') {
934                 braces++;
935             } else if (*end == '}') {
936                 braces--;
937             }
938         }
939         /* ignore single comma */
940         if (*pattern == *end) {
941             continue;
942         }
943         /* calculate the length of the pattern */
944         patlen = end - pattern;
945 
946         /* if this pattern matches - return */
947         if (match(pattern, patlen, subject)) {
948             return true;
949         }
950     }
951 
952     if (!count) {
953         /* empty pattern matches only on empty subject */
954         return !*subject;
955     }
956     /* there where one or more patterns but none of them matched */
957     return false;
958 }
959 
960 
961 /**
962  * Compares given subject string against the given pattern.
963  * The pattern needs not to bee NUL terminated.
964  */
match(const char * pattern,int patlen,const char * subject)965 static gboolean match(const char *pattern, int patlen, const char *subject)
966 {
967     int i;
968     char sl, pl;
969 
970     while (patlen > 0) {
971         switch (*pattern) {
972             case '?':
973                 /* '?' matches a single char except of / and subject end */
974                 if (*subject == '/' || !*subject) {
975                     return false;
976                 }
977                 break;
978 
979             case '*':
980                 /* easiest case - the '*' ist the last char in pattern - this
981                  * will always match */
982                 if (patlen == 1) {
983                     return true;
984                 }
985                 /* Try to match as much as possible. Try to match the complete
986                  * uri, if that fails move forward in uri and check for a
987                  * match. */
988                 i = strlen(subject);
989                 while (i >= 0 && !match(pattern + 1, patlen - 1, subject + i)) {
990                     i--;
991                 }
992                 return i >= 0;
993 
994             case '}':
995                 /* spurious '}' in pattern */
996                 return false;
997 
998             case '{':
999                 /* possible {foo,bar} pattern */
1000                 return match_list(pattern, patlen, subject);
1001 
1002             case '\\':
1003                 /* '\' escapes next special char */
1004                 if (strchr("*?{}", pattern[1])) {
1005                     pattern++;
1006                     patlen--;
1007                     if (*pattern != *subject) {
1008                         return false;
1009                     }
1010                 }
1011                 break;
1012 
1013             default:
1014                 /* compare case insensitive */
1015                 sl = *subject;
1016                 if (VB_IS_UPPER(sl)) {
1017                     sl += 'a' - 'A';
1018                 }
1019                 pl = *pattern;
1020                 if (VB_IS_UPPER(pl)) {
1021                     pl += 'a' - 'A';
1022                 }
1023                 if (sl != pl) {
1024                     return false;
1025                 }
1026                 break;
1027         }
1028         /* do another loop run with next pattern and subject char */
1029         pattern++;
1030         patlen--;
1031         subject++;
1032     }
1033 
1034     /* on end of pattern only a also ended subject is a match */
1035     return !*subject;
1036 }
1037 
1038 /**
1039  * Matches pattern starting with '{'.
1040  * This function can process also on none null terminated pattern.
1041  */
match_list(const char * pattern,int patlen,const char * subject)1042 static gboolean match_list(const char *pattern, int patlen, const char *subject)
1043 {
1044     int endlen;
1045     const char *end, *s;
1046 
1047     /* finde the next none escaped '}' */
1048     for (end = pattern, endlen = patlen; endlen > 0 && *end != '}'; end++, endlen--) {
1049         /* if escape char - move pointer one additional step */
1050         if (*end == '\\') {
1051             end++;
1052             endlen--;
1053         }
1054     }
1055 
1056     if (!*end) {
1057         /* unterminated '{' in pattern */
1058         return false;
1059     }
1060 
1061     s = subject;
1062     end++;      /* skip over } */
1063     endlen--;
1064     pattern++;  /* skip over { */
1065     patlen--;
1066     while (true) {
1067         switch (*pattern) {
1068             case ',':
1069                 if (match(end, endlen, s)) {
1070                     return true;
1071                 }
1072                 s = subject;
1073                 pattern++;
1074                 patlen--;
1075                 break;
1076 
1077             case '}':
1078                 return match(end, endlen, s);
1079 
1080             case '\\':
1081                 if (pattern[1] == ',' || pattern[1] == '}' || pattern[1] == '{') {
1082                     pattern++;
1083                     patlen--;
1084                 }
1085                 /* fall through */
1086 
1087             default:
1088                 if (*pattern == *s) {
1089                     pattern++;
1090                     patlen--;
1091                     s++;
1092                 } else {
1093                     /* this item of the list does not match - move forward to
1094                      * the next none escaped ',' or '}' */
1095                     s = subject;
1096                     for (s = subject; *pattern != ',' && *pattern != '}'; pattern++, patlen--) {
1097                         /* if escape char is found - skip next char */
1098                         if (*pattern == '\\') {
1099                             pattern++;
1100                             patlen--;
1101                         }
1102                     }
1103                     /* found ',' skip over it to check the next list item */
1104                     if (*pattern == ',') {
1105                         pattern++;
1106                         patlen--;
1107                     }
1108                 }
1109         }
1110     }
1111 }
1112 
1113 /**
1114  * Get the time span to given string like '1y5dh' (one year and five days and
1115  * one hour).
1116  */
util_string_to_timespan(const char * input)1117 GTimeSpan util_string_to_timespan(const char *input)
1118 {
1119     unsigned int multiplier = 0;
1120     GTimeSpan sum           = 0;
1121     gboolean hasmultiplier  = FALSE;
1122 
1123     while (*input) {
1124         if (VB_IS_DIGIT(*input)) {
1125             multiplier    = multiplier * 10 + (*input - '0');
1126             /* Use separate variable to differentiate between no multiplier
1127              * set (is same as 1) or ultiplier of '0'. */
1128             hasmultiplier = TRUE;
1129         } else {
1130             GTimeSpan s = 0;
1131             switch (*input) {
1132                 case 'y': s = 365 * G_TIME_SPAN_DAY; break;
1133                 case 'w': s = 7 * G_TIME_SPAN_DAY; break;
1134                 case 'd': s = G_TIME_SPAN_DAY; break;
1135                 case 'h': s = G_TIME_SPAN_HOUR; break;
1136                 case 'm': s = G_TIME_SPAN_MINUTE; break;
1137                 case 's': s = G_TIME_SPAN_SECOND; break;
1138             }
1139             sum += s * (hasmultiplier ? multiplier : 1);
1140             /* Unset multiplier for th epossible next multiplier in input */
1141             multiplier    = 0;
1142             hasmultiplier = FALSE;
1143         }
1144         input++;
1145     }
1146 
1147     return sum;
1148 }
1149