From f8c7f9dac9a5f915de27d9236e7d728b5fe5e5ea Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 26 Jan 2020 13:59:04 +0000 Subject: [PATCH] interpret \ as an escape char in break_token So: vips_break_token( "hello\ world", " " ) Sees a single token, `"hello world"`. This means you can now do things like: $ vips arrayjoin "k\ 2.jpg" x.png Where "k 2.jpg" is a filename containing a space. See https://github.com/libvips/libvips/issues/1493 --- ChangeLog | 1 + libvips/iofuncs/util.c | 62 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 156ace5b..6608763a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ - more conformat IIIF output from dzsave [regisrob] - add @id to dzsave to set IIIF id property [regisrob] - add max and min to region shrink [rgluskin] +- allow \ as an escape character in vips_break_token() [akemrir] 20/6/19 started 8.9.1 - don't use the new source loaders for new_from_file or new_from_buffer, it diff --git a/libvips/iofuncs/util.c b/libvips/iofuncs/util.c index 48001ed6..b143ed89 100644 --- a/libvips/iofuncs/util.c +++ b/libvips/iofuncs/util.c @@ -357,10 +357,46 @@ vips_isprefix( const char *a, const char *b ) return( TRUE ); } +/* Exactly like strcspn(), but allow \ as an escape character. + * + * strspne( "hello world", " " ) == 5 + * strspne( "hello\\ world", " " ) == 12 + */ +static size_t +strcspne( const char *s, const char *reject ) +{ + size_t skip; + + /* If \ is one of the reject chars, no need for any looping. + */ + if( strchr( reject, '\\' ) ) + return( strcspn( s, reject ) ); + + skip = 0; + for(;;) { + skip += strcspn( s + skip, reject ); + + /* s[skip] is at the start of the string, or the end, or on a + * break character. + */ + if( skip == 0 || + !s[skip] || + s[skip - 1] != '\\' ) + break; + + /* So skip points at break char and we have a '\' in the char + * before. Step over the break. + */ + skip += 1; + } + + return( skip ); +} + /* Like strtok(). Give a string and a list of break characters. Then: * - skip initial break characters * - EOS? return NULL - * - skip a series of non-break characters + * - skip a series of non-break characters, allow `\` as a break escape * - write a '\0' over the next break character and return a pointer to the * char after that * @@ -388,15 +424,22 @@ vips_isprefix( const char *a, const char *b ) * * for( i = 0; p; p = vips_break_token( p, " " ) ) * v[i] = atoi( p ); + * + * You can use \ to escape breaks, for example: + * + * vips_break_token( "hello\ world", " " ) will see a single token containing + * a space. The \ characters are squashed out. */ char * vips_break_token( char *str, const char *brk ) { char *p; + char *q; /* Is the string empty? If yes, return NULL immediately. */ - if( !str || !*str ) + if( !str || + !*str ) return( NULL ); /* Skip initial break characters. @@ -409,9 +452,9 @@ vips_break_token( char *str, const char *brk ) return( NULL ); /* We have a token ... search for the first break character after the - * token. + * token. strcspne() allows '\' to escape breaks, see above. */ - p += strcspn( p, brk ); + p += strcspne( p, brk ); /* Is there string left? */ @@ -423,6 +466,17 @@ vips_break_token( char *str, const char *brk ) p += strspn( p, brk ); } + /* There may be escaped break characters in str. Loop, squashing them + * out. + */ + for( q = strchr( str, '\\' ); q && *q; q = strchr( q, '\\' ) ) { + memmove( q, q + 1, strlen( q ) ); + + /* If there's \\, we don't want to squash out the second \. + */ + q += 1; + } + return( p ); }