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