1 /*--------------------------------------------------------------------
2 *
3 * Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4 * See LICENSE.TXT file for copying and redistribution conditions.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; version 3 or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * Contact info: www.generic-mapping-tools.org
16 *--------------------------------------------------------------------*/
17 /*
18 * gmt_common_string.c contains code shared between GMT and PSL
19 *
20 * Author: Florian Wobbe
21 * Date: 3-MAR-2012
22 * Version: 5
23 *
24 * Modules in this file:
25 *
26 * gmt_chop Chops off any CR or LF at end of string
27 * gmt_chop_ext Chops off the trailing .xxx (file extension)
28 * gmt_get_ext Returns a pointer to the tailing .xxx (file extension)
29 * gmt_get_word Return the specified word entry from a list
30 * gmt_strdup_noquote Duplicates a string but removes any surrounding single or double quotes
31 * gmt_strstrip Strip leading and trailing whitespace from string
32 * gmt_strlshift Left shift a string by n characters
33 * gmt_strrepc Replaces all occurrences of a char in the string
34 * gmt_strrep Replaces all occurrences of a string s2 in the string s1 by s3
35 * gmt_strlcmp Compares strings (ignoring case) until first reaches null character
36 * gmt_strtok Reiterant replacement of strtok
37 * gmt_strtok_m A Matlab style strtok
38 * gmt_strrstr A strstr but for last occurrence
39 * gmt_dos_path_fix Turn /c/dir/... paths into c:/dir/...
40 * str(n)casecmp Case-insensitive string comparison functions
41 * strtok_r Reentrant string tokenizer from Gnulib (LGPL)
42 * strsep Reentrant string tokenizer that handles empty fields
43 * strsepz Like strsep but ignores empty fields
44 * stresep Like strsep but takes an additional argument esc in order
45 * to ignore escaped chars (from NetBSD)
46 * basename Extract the base portion of a pathname
47 */
48
49 /* CMake definitions: This must be first! */
50 #include "gmt_config.h"
51
52 #include <stdlib.h>
53 #include <stdio.h>
54 #include <string.h>
55 #include <assert.h>
56
57 #include <limits.h>
58 #include <errno.h>
59
60 #include "gmt_notposix.h"
61 #include "gmt_common_string.h"
62
63 #define BUF_SIZE 4096
64
gmt_strdup_noquote(const char * file)65 char *gmt_strdup_noquote (const char *file) {
66 size_t last;
67 if (file == NULL) return NULL; /* No string given */
68 if (file[0] == '\0') return strdup (file); /* Return empty string */
69 last = strlen (file) - 1; /* We know here that the string is at least 1 character long, so len is >= 0 */
70 if ((file[0] == '\'' || file[0] == '\"') && (file[last] == '\'' || file[last] == '\"')) /* Quoted file name */
71 return (strndup (&file[1], last-1));
72 else
73 return (strdup (file));
74 }
75
gmt_chop_ext(char * string)76 char *gmt_chop_ext (char *string) {
77 /* Chops off the filename extension (e.g., .ps) in the string by replacing the last
78 * '.' with '\0' and returns a pointer to the extension or NULL if not found. */
79 char *p;
80 assert (string != NULL); /* NULL pointer */
81 if ((p = strrchr(string, '.')) == NULL) return NULL; /* No extension found */
82 if (strchr (p, '/')) return NULL; /* Found a directory with a period */
83 #ifdef WIN32
84 if (strchr (p, '\\')) return NULL; /* Found a directory with a period */
85 #endif
86 *p = '\0';
87 return (p + 1);
88 }
89
gmt_chop(char * string)90 void gmt_chop (char *string) {
91 /* Chops off any CR or LF and terminates string */
92 char *p;
93 assert (string != NULL); /* NULL pointer */
94 /* if (string == NULL) return; / NULL pointer */
95 if ((p = strpbrk (string, "\r\n")))
96 /* Overwrite 1st CR or LF with terminate string */
97 *p = '\0';
98 }
99
gmt_get_ext(const char * string)100 char *gmt_get_ext (const char *string) {
101 /* Returns a pointer to the filename extension (e.g., .ps) or NULL if not found. */
102 char *p;
103 assert (string != NULL); /* NULL pointer */
104 if ((p = strrchr(string, '.'))) {
105 return (p + 1);
106 }
107 return NULL;
108 }
109
gmt_strstrip(char * string,bool strip_leading)110 void gmt_strstrip(char *string, bool strip_leading) {
111 /* Strip leading and trailing whitespace from string */
112 char *start = string;
113 char *end;
114
115 assert (string != NULL); /* NULL pointer */
116
117 if (strip_leading) {
118 /* Skip over leading whitespace */
119 while ((*start) && isspace(*start))
120 ++start;
121 /* Is string just whitespace? */
122 if (!(*start)) {
123 *string = '\0'; /* Truncate entire string */
124 return;
125 }
126 }
127
128 /* Find end of string */
129 end = start;
130 while (*end)
131 ++end;
132
133 /* Step backward until first non-whitespace */
134 while ((--end != start) && isspace(*end));
135
136 /* Chop off trailing whitespace */
137 *(end + 1) = '\0';
138
139 /* If leading whitespace, then move entire string back */
140 if (string != start)
141 memmove(string, start, end-start+2);
142 }
143
gmt_strlshift(char * string,size_t n)144 void gmt_strlshift (char *string, size_t n) {
145 /* Left shift a string by n characters */
146 size_t len;
147 assert (string != NULL); /* NULL pointer */
148
149 if ((len = strlen(string)) <= n ) {
150 /* String shorter than shift width */
151 *string = '\0'; /* Truncate entire string */
152 return;
153 }
154
155 /* Move entire string back */
156 memmove(string, string + n, len + 1);
157 }
158
gmt_strrepc(char * string,int c,int r)159 void gmt_strrepc (char *string, int c, int r) {
160 /* Replaces all occurrences of c in the string with r */
161 assert (string != NULL); /* NULL pointer */
162 do {
163 if (*string == c)
164 *string = (char)r;
165 } while (*(++string)); /* repeat until \0 reached */
166 }
167
gmt_strlcmp(char * str1,char * str2)168 size_t gmt_strlcmp (char *str1, char *str2) {
169 /* Compares str1 with str2 but only until str1 reaches the
170 * null-terminator character while case is ignored.
171 * When the strings match until that point, the routine returns the
172 * length of str1, otherwise it returns 0.
173 */
174 size_t i = 0;
175 while (str1[i] && tolower((unsigned char) str1[i]) == tolower((unsigned char) str2[i])) ++i;
176 if (str1[i]) return 0;
177 return i;
178 }
179
gmt_strtok(const char * string,const char * sep,unsigned int * pos,char * token)180 unsigned int gmt_strtok (const char *string, const char *sep, unsigned int *pos, char *token) {
181 /* Reentrant replacement for strtok that uses no static variables.
182 * Breaks string into tokens separated by one of more separator
183 * characters (in sep). Set *pos to 0 before first call. Unlike
184 * strtok, always pass the original string as first argument.
185 * Returns 1 if it finds a token and 0 if no more tokens left.
186 * pos is updated and token is returned. char *token must point
187 * to memory of length >= strlen (string).
188 * string is not changed by gmt_strtok.
189 */
190
191 size_t i, j, string_len;
192
193 string_len = strlen (string);
194
195 /* Wind up *pos to first non-separating character: */
196 while (string[*pos] && strchr (sep, (int)string[*pos])) (*pos)++;
197
198 token[0] = 0; /* Initialize token to NULL in case we are at end */
199
200 if (*pos >= string_len || string_len == 0) return 0; /* Got NULL string or no more string left to search */
201
202 /* Search for next non-separating character */
203 i = *pos; j = 0;
204 while (string[i] && !strchr (sep, (int)string[i])) token[j++] = string[i++];
205 token[j] = 0; /* Add terminating \0 */
206
207 /* Wind up *pos to next non-separating character */
208 while (string[i] && strchr (sep, (int)string[i])) i++;
209 *pos = (unsigned int)i;
210
211 return 1;
212 }
213
gmt_strtok_m(char * in,char ** token,char ** remain,char * sep)214 void gmt_strtok_m (char *in, char **token, char **remain, char *sep) {
215 /* A Matlab style strtok. Note that 'token' and 'remain' must be virgin pointers,
216 otherwise the memory they point to will be leaked because they are allocated here
217 with strdup. For that reason the caller is responsible to free them after being consumed.
218 */
219 unsigned int pos = 0;
220 char *p, *s;
221
222 if (sep == NULL)
223 s = " \t";
224 else
225 s = sep;
226
227 token[0] = NULL; remain[0] = NULL;
228
229 p = calloc(strlen(in)+1, sizeof (char));
230 if (gmt_strtok (in, s, &pos, p)) {
231 token[0] = strdup(p);
232 if (gmt_strtok (in, s, &pos, p))
233 remain[0] = strdup(p);
234 }
235 free(p);
236 }
237
gmt_strrstr(const char * s,const char * m)238 char *gmt_strrstr (const char *s, const char *m) {
239 /* Find last occurrence of m in s */
240 char *last = NULL;
241 size_t n = strlen(m);
242
243 while ((s = strchr(s, *m)) != NULL) {
244 if (!strncmp(s, m, n))
245 last = (char *)s;
246 if (*s++ == '\0')
247 break;
248 }
249 return last;
250 }
251
gmt_get_word(char * list,char * sep,unsigned int col)252 char *gmt_get_word (char *list, char *sep, unsigned int col) {
253 /* Return word number col in the list with separator sep */
254 char *word, *trail, *orig, *retval;
255 unsigned int k = 0;
256 if (list == NULL || sep == NULL) return (NULL);
257 orig = strdup (list);
258 trail = orig;
259 while ((word = strsep (&trail, sep)) != NULL && k < col) k++;
260 retval = (k == col) ? strdup (word) : NULL;
261 free (orig);
262 return (retval);
263 }
264
gmt_get_modifier(const char * string,char modifier,char * token)265 unsigned int gmt_get_modifier (const char *string, char modifier, char *token) {
266 /* Looks for modifier string in the form +<modifier>[arg] and if found
267 returns 1 and places arg in token, else return 0. Must ignore any
268 +<modifier> found inside quotes as part of text. If token is NULL
269 then we only return 1 or 0 and no string.
270 */
271 bool quoted = false;
272 size_t k, len, start = 0;
273
274 if (!string || string[0] == 0) return 0; /* No hope */
275 len = strlen (string);
276 for (k = 0; start == 0 && k < (len-1); k++) {
277 if (string[k] == '\"' || string[k] == '\'') quoted = !quoted; /* Initially false, becomes true at start of quote, then false when exit quote */
278 if (quoted) continue; /* Not look inside quoted strings */
279 if (string[k] == '+' && string[k+1] == modifier) /* Found the start */
280 start = k+2;
281 }
282 if (start == 0) return 0; /* Not found */
283 for (k = start; k < len; k++) {
284 if (string[k] == '\"' || string[k] == '\'') quoted = !quoted; /* Initially false, becomes true at start of quote, then false when exit quote */
285 if (quoted) continue; /* Not look inside quoted strings */
286 if (string[k] == '+') /* Found the end */
287 break;
288 }
289 len = k - start;
290 if (token) { /* Only pass back when token is not NULL */
291 if (len) strncpy (token, &string[start], len);
292 token[len] = '\0';
293 }
294 return 1;
295 }
296
297 #ifdef WIN32
298 /* Turn '/c/dir/...' paths into 'c:/dir/...'
299 * Must do it in a loop since dir may be several ';'-separated dirs */
gmt_dos_path_fix(char * dir)300 void gmt_dos_path_fix (char *dir) {
301 size_t n, k;
302
303 if (!dir || (n = strlen (dir)) < 2U)
304 /* Given NULL or too short dir to work */
305 return;
306
307 if (!strncmp (dir, "/cygdrive/", 10U))
308 /* May happen for example when Cygwin sets GMT_SHAREDIR */
309 gmt_strlshift (dir, 9); /* Chop '/cygdrive' */
310
311 /* Replace dumb backslashes with slashes */
312 gmt_strrepc (dir, '\\', '/');
313
314 /* If dir begins with '/' and is 2 long, as in '/c', replace with 'c:' */
315 if (n == 2U && dir[0] == '/') {
316 dir[0] = dir[1];
317 dir[1] = ':';
318 return;
319 }
320
321 /* If dir is longer than 2 and, e.g., '/c/', replace with 'c:/' */
322 if (n > 2U && dir[0] == '/' && dir[2] == '/' && isalpha ((int)dir[1])) {
323 dir[0] = dir[1];
324 dir[1] = ':';
325 }
326
327 /* Do the same with dirs separated by ';' but do not replace '/c/j/...' with 'c:j:/...' */
328 for (k = 4; k < n-2; k++) {
329 if ( (dir[k-1] == ';' && dir[k] == '/' && dir[k+2] == '/' && isalpha ((int)dir[k+1])) ) {
330 dir[k] = dir[k+1];
331 dir[k+1] = ':';
332 }
333 }
334
335 /* Replace ...:C:/... by ...;C:/... as that was a multi-path set by an e.g. bash shell (msys or cygwin) */
336 for (k = 4; k < n-2; k++) {
337 if ((dir[k-1] == ':' && dir[k+1] == ':' && dir[k+2] == '/' && isalpha ((int)dir[k])) )
338 dir[k-1] = ';';
339 else if ((dir[k-1] == ':' && dir[k] == '/' && dir[k+2] == '/' && isalpha ((int)dir[k+1])) ) {
340 /* The form ...:/C/... will become ...;C:/... */
341 dir[k-1] = ';';
342 dir[k] = dir[k+1];
343 dir[k+1] = ':';
344 }
345 }
346 }
347 #endif
348
349 #if !defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)
350 /* Case-insensitive string comparison function.
351 Copyright (C) 1998-1999, 2005-2007, 2009-2012 Free Software Foundation, Inc.
352
353 This program is free software; you can redistribute it and/or modify
354 it under the terms of the GNU General Public License as published by
355 the Free Software Foundation; either version 2, or (at your option)
356 any later version.
357
358 This program is distributed in the hope that it will be useful,
359 but WITHOUT ANY WARRANTY; without even the implied warranty of
360 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
361 GNU General Public License for more details.
362
363 You should have received a copy of the GNU General Public License
364 along with this program; if not, see <http://www.gnu.org/licenses/>. */
365
366 #ifndef TOLOWER
367 # define TOLOWER(Ch) (isupper (Ch) ? tolower (Ch) : (Ch))
368 #endif
369
370 /* Compare strings S1 and S2, ignoring case, returning less than, equal to or
371 greater than zero if S1 is lexicographically less than, equal to or greater
372 than S2.
373 Note: This function does not work with multibyte strings! */
374
strcasecmp(const char * s1,const char * s2)375 int strcasecmp (const char *s1, const char *s2) {
376 const unsigned char *p1 = (const unsigned char *) s1;
377 const unsigned char *p2 = (const unsigned char *) s2;
378 unsigned char c1, c2;
379
380 if (p1 == p2) return 0;
381
382 do {
383 c1 = TOLOWER (*p1);
384 c2 = TOLOWER (*p2);
385
386 if (c1 == '\0')
387 break;
388
389 ++p1;
390 ++p2;
391 }
392 while (c1 == c2);
393
394 if (UCHAR_MAX <= INT_MAX)
395 return c1 - c2;
396 else
397 /* On machines where 'char' and 'int' are types of the same size, the
398 difference of two 'unsigned char' values - including the sign bit -
399 doesn't fit in an 'int'. */
400 return (c1 > c2 ? 1 : c1 < c2 ? -1 : 0);
401 }
402 #endif /* !defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP) */
403
404 #if !defined(HAVE_STRNCASECMP) && !defined(HAVE_STRNICMP)
405 /* strncasecmp.c -- case insensitive string comparator
406 Copyright (C) 1998-1999, 2005-2007, 2009-2012 Free Software Foundation, Inc.
407
408 This program is free software; you can redistribute it and/or modify
409 it under the terms of the GNU General Public License as published by
410 the Free Software Foundation; either version 2, or (at your option)
411 any later version.
412
413 This program is distributed in the hope that it will be useful,
414 but WITHOUT ANY WARRANTY; without even the implied warranty of
415 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
416 GNU General Public License for more details.
417
418 You should have received a copy of the GNU General Public License
419 along with this program; if not, see <http://www.gnu.org/licenses/>. */
420
421 #ifndef TOLOWER
422 # define TOLOWER(Ch) (isupper (Ch) ? tolower (Ch) : (Ch))
423 #endif
424
425 /* Compare no more than N bytes of strings S1 and S2, ignoring case,
426 returning less than, equal to or greater than zero if S1 is
427 lexicographically less than, equal to or greater than S2.
428 Note: This function cannot work correctly in multibyte locales. */
429
strncasecmp(const char * s1,const char * s2,size_t n)430 int strncasecmp (const char *s1, const char *s2, size_t n) {
431 register const unsigned char *p1 = (const unsigned char *) s1;
432 register const unsigned char *p2 = (const unsigned char *) s2;
433 unsigned char c1, c2;
434
435 if (p1 == p2 || n == 0)
436 return 0;
437
438 do
439 {
440 c1 = TOLOWER (*p1);
441 c2 = TOLOWER (*p2);
442
443 if (--n == 0 || c1 == '\0')
444 break;
445
446 ++p1;
447 ++p2;
448 }
449 while (c1 == c2);
450
451 if (UCHAR_MAX <= INT_MAX)
452 return c1 - c2;
453 else
454 /* On machines where 'char' and 'int' are types of the same size, the
455 difference of two 'unsigned char' values - including the sign bit -
456 doesn't fit in an 'int'. */
457 return (c1 > c2 ? 1 : c1 < c2 ? -1 : 0);
458 }
459 #endif /* !defined(HAVE_STRNCASECMP) && !defined(HAVE_STRNICMP) */
460
461 #if !defined(HAVE_STRTOK_R) && !defined(HAVE_STRTOK_S)
462 /* Reentrant string tokenizer. Generic version.
463 Copyright (C) 1991, 1996-1999, 2001, 2004, 2007, 2009-2012 Free Software
464 Foundation, Inc.
465 This file is part of the GNU C Library.
466
467 This program is free software: you can redistribute it and/or modify
468 it under the terms of the GNU Lesser General Public License as published by
469 the Free Software Foundation; either version 3 of the License, or
470 (at your option) any later version.
471
472 This program is distributed in the hope that it will be useful,
473 but WITHOUT ANY WARRANTY; without even the implied warranty of
474 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
475 GNU Lesser General Public License for more details.
476
477 You should have received a copy of the GNU Lesser General Public License
478 along with this program. If not, see <http://www.gnu.org/licenses/>. */
479
480 #if 0
481 /* Parse S into tokens separated by characters in DELIM. */
482 If S is NULL, the saved pointer in SAVE_PTR is used as
483 the next starting point. For example:
484 char s[] = "-abc-=-def";
485 char *sp;
486 x = strtok_r(s, "-", &sp); /* x = "abc", sp = "=-def" */
487 x = strtok_r(NULL, "-=", &sp); /* x = "def", sp = NULL */
488 x = strtok_r(NULL, "=", &sp); /* x = NULL */
489 /* s = "abc\0-def\0" */
490 #endif
strtok_r(char * s,const char * delim,char ** save_ptr)491 char *strtok_r (char *s, const char *delim, char **save_ptr) {
492 char *token;
493
494 if (s == NULL)
495 s = *save_ptr;
496
497 /* Scan leading delimiters. */
498 s += strspn (s, delim);
499 if (*s == '\0')
500 {
501 *save_ptr = s;
502 return NULL;
503 }
504
505 /* Find the end of the token. */
506 token = s;
507 s = strpbrk (token, delim);
508 if (s == NULL)
509 /* This token finishes the string. */
510 *save_ptr = strchr (token, '\0');
511 else
512 {
513 /* Terminate the token and make *SAVE_PTR point past it. */
514 *s = '\0';
515 *save_ptr = s + 1;
516 }
517 return token;
518 }
519 #endif /* !defined(HAVE_STRTOK_R) && !defined(HAVE_STRTOK_S) */
520
521 #ifndef HAVE_STRSEP
522 /* Copyright (C) 2004, 2007, 2009-2012 Free Software Foundation, Inc.
523
524 Written by Yoann Vandoorselaere <yoann@prelude-ids.org>.
525
526 This program is free software: you can redistribute it and/or modify
527 it under the terms of the GNU Lesser General Public License as published by
528 the Free Software Foundation; either version 3 of the License, or
529 (at your option) any later version.
530
531 This program is distributed in the hope that it will be useful,
532 but WITHOUT ANY WARRANTY; without even the implied warranty of
533 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
534 GNU Lesser General Public License for more details.
535
536 You should have received a copy of the GNU Lesser General Public License
537 along with this program. If not, see <http://www.gnu.org/licenses/>. */
538
strsep(char ** stringp,const char * delim)539 char * strsep (char **stringp, const char *delim) {
540 char *start = *stringp;
541 char *ptr;
542
543 if (start == NULL)
544 return NULL;
545
546 /* Optimize the case of no delimiters. */
547 if (delim[0] == '\0')
548 {
549 *stringp = NULL;
550 return start;
551 }
552
553 /* Optimize the case of one delimiter. */
554 if (delim[1] == '\0')
555 ptr = strchr (start, delim[0]);
556 else
557 /* The general case. */
558 ptr = strpbrk (start, delim);
559 if (ptr == NULL)
560 {
561 *stringp = NULL;
562 return start;
563 }
564
565 *ptr = '\0';
566 *stringp = ptr + 1;
567
568 return start;
569 }
570 #endif /* ifndef HAVE_STRSEP */
571
572 /* Like strsep but ignores empty fields.
573 * PW: Made two near-identical version of strsepz:
574 * strsepz: As it was, but with an unused 3rd argument.
575 * strsepzp: 3rd argument returns position in original stringp.
576 */
577
strsepz(char ** stringp,const char * delim,size_t * pos)578 char *strsepz (char **stringp, const char *delim, size_t *pos) {
579 char *c;
580 (void)(pos);
581 while ( (c = strsep(stringp, delim)) != NULL && *c == '\0' );
582 return c;
583 }
584
strsepzp(char ** stringp,const char * delim,size_t * pos)585 char *strsepzp (char **stringp, const char *delim, size_t *pos) {
586 char *c;
587 while ( (c = strsep(stringp, delim)) != NULL && *c == '\0' )
588 (*pos)++;
589 if (c) (*pos) += strlen (c) + 1;
590 return c;
591 }
592
593 /* $NetBSD: stresep.c,v 1.2 2007/12/06 22:07:07 seb Exp $
594 *
595 * Copyright (c) 1990, 1993
596 * The Regents of the University of California. All rights reserved.
597 *
598 * Redistribution and use in source and binary forms, with or without
599 * modification, are permitted provided that the following conditions
600 * are met:
601 * 1. Redistributions of source code must retain the above copyright
602 * notice, this list of conditions and the following disclaimer.
603 * 2. Redistributions in binary form must reproduce the above copyright
604 * notice, this list of conditions and the following disclaimer in the
605 * documentation and/or other materials provided with the distribution.
606 * 3. Neither the name of the University nor the names of its contributors
607 * may be used to endorse or promote products derived from this software
608 * without specific prior written permission.
609 *
610 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
611 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
612 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
613 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
614 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
615 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
616 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
617 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
618 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
619 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
620 * SUCH DAMAGE.
621 */
622
623 /*
624 * Get next token from string *stringp, where tokens are possibly-empty
625 * strings separated by characters from delim. If esc is not NUL, then
626 * the characters followed by esc are ignored and are not taken into account
627 * when splitting the string.
628 *
629 * Writes NULs into the string at *stringp to end tokens.
630 * delim need not remain constant from call to call.
631 * On return, *stringp points past the last NUL written (if there might
632 * be further tokens), or is NULL (if there are definitely no more tokens).
633 *
634 * If *stringp is NULL, stresep returns NULL.
635 */
636
stresep(char ** stringp,const char * delim,int esc)637 char *stresep(char **stringp, const char *delim, int esc) {
638 char *s;
639 const char *spanp;
640 int c, sc;
641 char *tok;
642
643 assert(delim != NULL);
644
645 if ((s = *stringp) == NULL)
646 return NULL;
647 for (tok = s;;) {
648 c = *s++;
649 while (esc != '\0' && c == esc) {
650 (void)strcpy(s - 1, s);
651 c = *s++;
652 }
653 spanp = delim;
654 do {
655 if ((sc = *spanp++) == c) {
656 if (c == 0)
657 s = NULL;
658 else
659 s[-1] = 0;
660 *stringp = s;
661 return tok;
662 }
663 } while (sc != 0);
664 }
665 }
666
667 /* $OpenBSD: basename.c,v 1.14 2005/08/08 08:05:33 espie Exp $
668 *
669 * Copyright (c) 1997, 2004 Todd C. Miller <Todd.Miller@courtesan.com>
670 *
671 * Permission to use, copy, modify, and distribute this software for any
672 * purpose with or without fee is hereby granted, provided that the above
673 * copyright notice and this permission notice appear in all copies.
674 *
675 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
676 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
677 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
678 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
679 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
680 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
681 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
682 */
683
684 /* The basename() function returns the last component from the pathname
685 * pointed to by path, deleting any trailing `/' characters. If path consists
686 * entirely of `/' characters, a pointer to the string "/" is returned. If
687 * path is a null pointer or the empty string, a pointer to the string "." is
688 * returned.
689 *
690 * On successful completion, basename() returns a pointer to the last
691 * component of path.
692 *
693 * If basename() fails, a null pointer is returned and the global variable
694 * errno is set to indicate the error.
695 *
696 * The basename() function returns a pointer to internal static storage
697 * space that will be overwritten by subsequent calls (not thread safe). The
698 * function does not modify the string pointed to by path. */
699
700 #ifndef HAVE_BASENAME
basename(char * path)701 char *basename(char *path) {
702 #ifdef WIN32
703 static char path_fixed[PATH_MAX];
704 #endif
705 static char bname[PATH_MAX];
706 size_t len;
707 const char *endp, *startp;
708
709 /* Empty or NULL string gets treated as "." */
710 if (path == NULL || *path == '\0') {
711 bname[0] = '.';
712 bname[1] = '\0';
713 return (bname);
714 }
715
716 #ifdef WIN32
717 if (strchr (path, '\\')) {
718 char *fixedp = path_fixed;
719 if (strlen (path) >= PATH_MAX) {
720 errno = ENAMETOOLONG;
721 return (NULL);
722 }
723 /* Replace backslashes with slashes */
724 while (*path) { /* repeat until \0 reached */
725 if (*path == '\\')
726 *fixedp = '/';
727 else
728 *fixedp = *path;
729 ++path, ++fixedp;
730 }
731 *fixedp = '\0'; /* terminate string */
732 path = path_fixed;
733 }
734 #endif
735
736 /* Strip any trailing slashes */
737 endp = path + strlen(path) - 1;
738 while (endp > path && *endp == '/')
739 endp--;
740
741 /* All slashes becomes "/" */
742 if (endp == path && *endp == '/') {
743 bname[0] = '/';
744 bname[1] = '\0';
745 return (bname);
746 }
747
748 /* Find the start of the base */
749 startp = endp;
750 while (startp > path && *(startp - 1) != '/')
751 startp--;
752
753 len = endp - startp + 1;
754 if (len >= PATH_MAX) {
755 errno = ENAMETOOLONG;
756 return (NULL);
757 }
758 memcpy(bname, startp, len);
759 bname[len] = '\0';
760 return (bname);
761 }
762 #endif
763
764 /*
765 * strrep.c - C substring replacement.
766 *
767 * Written in 2011 by Drew Hess <dhess-src@bothan.net>.
768 * https://gist.github.com/dhess/975639
769 *
770 * To the extent possible under law, the author(s) have dedicated all
771 * copyright and related and neighboring rights to this software to
772 * the public domain worldwide. This software is distributed without
773 * any warranty.
774 *
775 * For the full statement of the dedication, see the Creative Commons
776 * CC0 Public Domain Dedication at
777 * <http://creativecommons.org/publicdomain/zero/1.0/>.
778 */
779
780 /*
781 * This file includes a main() function so that the file can be
782 * compiled into an executable, in order to run some simple test cases
783 * on the included strrep() function. If you want to use strrep in
784 * your own project, make sure you cut or comment out the main()
785 * function below.
786 *
787 * This function returns string s1 if string s2 is an empty string, or
788 * if s2 is not found in s1. If s2 is found in s1, the function
789 * returns a new null-terminated string whose contents are identical
790 * to s1, except that all occurrences of s2 in the original string s1
791 * are, in the new string, replaced by the string s3. The caller owns
792 * the new string.
793 *
794 * Strings s1, s2, and s3 must all be null-terminated strings. If any
795 * of s1, s2, or s3 are NULL, the function returns NULL, indicating an
796 * error condition. If any other error occurs, the function returns NULL.
797 *
798 * This code is written pedantically, primarily so that asserts can be
799 * used liberally. The code could certainly be optimized and/or made
800 * less verbose, and I encourage you to do that if you use strstr in
801 * your production code, once you're comfortable that it functions as
802 * intended. Each assert makes plain an invariant condition that is
803 * assumed to be true by the statement(s) that immediately follow the
804 * assert. Some of the asserts are trivially true, as written, but
805 * they're included, nonetheless, in case you, in the process of
806 * optimizing or adapting the code for your own purposes, make a
807 * change that breaks an assumption made downstream by the original code.
808 */
809
gmt_strrep(const char * s1,const char * s2,const char * s3)810 char *gmt_strrep(const char *s1, const char *s2, const char *s3) {
811 size_t s1_len, s2_len, s3_len, count, s1_without_s2_len, newstr_len, i, substr_len, remains;
812 const char *p, *start_substr, *end_substr;
813 char *newstr, *dst;
814 if (!s1 || !s2 || !s3)
815 return 0;
816 s1_len = strlen(s1);
817 if (!s1_len)
818 return (char *)s1;
819 s2_len = strlen(s2);
820 if (!s2_len)
821 return (char *)s1;
822
823 /*
824 * Two-pass approach: figure out how much space to allocate for
825 * the new string, pre-allocate it, then perform replacement(s).
826 */
827 count = 0;
828 p = s1;
829 assert(s2_len); /* otherwise, strstr(s1,s2) will return s1. */
830 do {
831 p = strstr(p, s2);
832 if (p) {
833 p += s2_len;
834 count++;
835 }
836 } while (p);
837
838 if (!count)
839 return (char *)s1;
840
841 /*
842 * The following size arithmetic is extremely cautious, to guard against size_t overflows.
843 */
844 assert(s1_len >= count * s2_len);
845 assert(count);
846 s1_without_s2_len = s1_len - count * s2_len;
847 s3_len = strlen(s3);
848 newstr_len = s1_without_s2_len + count * s3_len;
849 if (s3_len && ((newstr_len <= s1_without_s2_len) || (newstr_len + 1 == 0))) /* Overflow. */
850 return 0;
851
852 newstr = (char *)calloc(newstr_len + 1, sizeof(char)); /* w/ terminator */
853 if (!newstr) /* ENOMEM, but no good way to signal it. */
854 return 0;
855
856 dst = newstr;
857 start_substr = s1;
858 for (i = 0; i != count; ++i) {
859 end_substr = strstr(start_substr, s2);
860 assert(end_substr);
861 substr_len = end_substr - start_substr;
862 memcpy(dst, start_substr, substr_len);
863 dst += substr_len;
864 memcpy(dst, s3, s3_len);
865 dst += s3_len;
866 start_substr = end_substr + s2_len;
867 }
868
869 /* copy remainder of s1, including trailing '\0' */
870 remains = s1_len - (start_substr - s1) + 1;
871 assert(dst + remains == newstr + newstr_len + 1);
872 memcpy(dst, start_substr, remains);
873 assert(strlen(newstr) == newstr_len);
874 return newstr;
875 }
876
877 #ifndef HAVE_CHARCAT /* Do not think this is a standard function but just in case */
chrcat(char * dest,const char add)878 char *chrcat (char *dest, const char add) {
879 /* Simple function to add a single character to a string. No check if there is room
880 * only that it is not NULL, and we explicitly terminate the updated string */
881 if (dest) {
882 dest[strlen(dest)] = add;
883 dest[strlen(dest)] = '\0';
884 }
885 return (dest);
886 }
887 #endif
888