fnmatch.c 8.38 KB
Newer Older
Elliot Lee's avatar
Elliot Lee committed
1
/* Copyright (C) 1991, 1992, 1993 Free Software Foundation, Inc.
2 3
 *
 * This library is free software; you can redistribute it and/or
4
 * modify it under the terms of the GNU Lesser General Public
5 6 7 8 9 10
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11
 * Lesser General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU Lesser General Public
14 15 16 17
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
Elliot Lee's avatar
Elliot Lee committed
18

19
/*
20
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
21 22 23 24 25
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

26 27 28 29 30
/*
 * Stripped down, converted to UTF-8 and test cases added
 *
 *                    Owen Taylor, 13 December 2002;
 */
Elliot Lee's avatar
Elliot Lee committed
31

32
#include <config.h>
33
#include <string.h>
34

35 36 37 38 39
#include <glib.h>

/* We need to make sure that all constants are defined
 * to properly compile this file
 */
40
#ifndef _GNU_SOURCE
41
#define _GNU_SOURCE
42 43
#endif

44 45 46 47 48
static gunichar
get_char (const char **str)
{
  gunichar c = g_utf8_get_char (*str);
  *str = g_utf8_next_char (*str);
Elliot Lee's avatar
Elliot Lee committed
49

Tor Lillqvist's avatar
Tor Lillqvist committed
50 51
#ifdef G_PLATFORM_WIN32
  c = g_unichar_tolower (c);
52
#endif
Elliot Lee's avatar
Elliot Lee committed
53

54 55
  return c;
}
Elliot Lee's avatar
Elliot Lee committed
56

57 58 59 60 61
#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
#define DO_ESCAPE 0
#else  
#define DO_ESCAPE 1
#endif  
Elliot Lee's avatar
Elliot Lee committed
62

63 64 65 66 67 68 69 70 71 72 73 74
static gunichar
get_unescaped_char (const char **str,
		    gboolean    *was_escaped)
{
  gunichar c = get_char (str);

  *was_escaped = DO_ESCAPE && c == '\\';
  if (*was_escaped)
    c = get_char (str);
  
  return c;
}
Elliot Lee's avatar
Elliot Lee committed
75 76 77 78

/* Match STRING against the filename pattern PATTERN, returning zero if
   it matches, nonzero if not.  */

79 80 81 82 83 84 85 86
static gboolean
gtk_fnmatch_intern (const char *pattern,
		    const char *string,
		    gboolean   component_start)
{
  const char *p = pattern, *n = string;
  
  while (*p)
Elliot Lee's avatar
Elliot Lee committed
87
    {
88 89 90 91 92
      const char *last_n = n;
      
      gunichar c = get_char (&p);
      gunichar nc = get_char (&n);
      
Elliot Lee's avatar
Elliot Lee committed
93 94
      switch (c)
	{
95 96 97 98 99 100 101
   	case '?':
	  if (nc == '\0')
	    return FALSE;
	  else if (nc == G_DIR_SEPARATOR)
	    return FALSE;
	  else if (nc == '.' && component_start)
	    return FALSE;
Elliot Lee's avatar
Elliot Lee committed
102 103
	  break;
	case '\\':
104 105 106 107
	  if (DO_ESCAPE)
	    c = get_char (&p);
	  if (nc != c)
	    return FALSE;
Elliot Lee's avatar
Elliot Lee committed
108 109
	  break;
	case '*':
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
	  if (nc == '.' && component_start)
	    return FALSE;

	  {
	    const char *last_p = p;

	    for (last_p = p, c = get_char (&p);
		 c == '?' || c == '*';
		 last_p = p, c = get_char (&p))
	      {
		if (c == '?')
		  {
		    if (nc == '\0')
		      return FALSE;
		    else if (nc == G_DIR_SEPARATOR)
		      return FALSE;
		    else
		      {
			last_n = n; nc = get_char (&n);
		      }
		  }
	      }
Elliot Lee's avatar
Elliot Lee committed
132

133 134 135 136 137 138 139 140 141 142 143
	    /* If the pattern ends with wildcards, we have a
	     * guaranteed match unless there is a dir separator
	     * in the remainder of the string.
	     */
	    if (c == '\0')
	      {
		if (strchr (last_n, G_DIR_SEPARATOR) != NULL)
		  return FALSE;
		else
		  return TRUE;
	      }
Elliot Lee's avatar
Elliot Lee committed
144

145 146
	    if (DO_ESCAPE && c == '\\')
	      c = get_char (&p);
Elliot Lee's avatar
Elliot Lee committed
147

148 149 150 151 152 153 154 155 156 157 158 159
	    for (p = last_p; nc != '\0';)
	      {
		if ((c == '[' || nc == c) &&
		    gtk_fnmatch_intern (p, last_n, component_start))
		  return TRUE;
		
		component_start = (nc == G_DIR_SEPARATOR);
		last_n = n;
		nc = get_char (&n);
	      }
		  
	    return FALSE;
Elliot Lee's avatar
Elliot Lee committed
160 161 162 163 164
	  }

	case '[':
	  {
	    /* Nonzero if the sense of the character class is inverted.  */
165 166
	    gboolean not;
	    gboolean was_escaped;
Elliot Lee's avatar
Elliot Lee committed
167

168 169
	    if (nc == '\0' || nc == G_DIR_SEPARATOR)
	      return FALSE;
Elliot Lee's avatar
Elliot Lee committed
170

171 172
	    if (nc == '.' && component_start)
	      return FALSE;
Elliot Lee's avatar
Elliot Lee committed
173 174 175 176 177

	    not = (*p == '!' || *p == '^');
	    if (not)
	      ++p;

178
	    c = get_unescaped_char (&p, &was_escaped);
Elliot Lee's avatar
Elliot Lee committed
179 180
	    for (;;)
	      {
181
		register gunichar cstart = c, cend = c;
Elliot Lee's avatar
Elliot Lee committed
182 183
		if (c == '\0')
		  /* [ (unterminated) loses.  */
184
		  return FALSE;
Elliot Lee's avatar
Elliot Lee committed
185

186 187 188
		c = get_unescaped_char (&p, &was_escaped);
		
		if (!was_escaped && c == '-' && *p != ']')
Elliot Lee's avatar
Elliot Lee committed
189
		  {
190
		    cend = get_unescaped_char (&p, &was_escaped);
Elliot Lee's avatar
Elliot Lee committed
191
		    if (cend == '\0')
192
		      return FALSE;
Elliot Lee's avatar
Elliot Lee committed
193

194
		    c = get_char (&p);
Elliot Lee's avatar
Elliot Lee committed
195 196
		  }

197
		if (nc >= cstart && nc <= cend)
Elliot Lee's avatar
Elliot Lee committed
198 199
		  goto matched;

200
		if (!was_escaped && c == ']')
Elliot Lee's avatar
Elliot Lee committed
201 202 203
		  break;
	      }
	    if (!not)
204
	      return FALSE;
Elliot Lee's avatar
Elliot Lee committed
205 206 207 208
	    break;

	  matched:;
	    /* Skip the rest of the [...] that already matched.  */
209 210
	    /* XXX 1003.2d11 is unclear if was_escaped is right.  */
	    while (was_escaped || c != ']')
Elliot Lee's avatar
Elliot Lee committed
211 212 213
	      {
		if (c == '\0')
		  /* [... (unterminated) loses.  */
214
		  return FALSE;
Elliot Lee's avatar
Elliot Lee committed
215

216
		c = get_unescaped_char (&p, &was_escaped);
Elliot Lee's avatar
Elliot Lee committed
217 218
	      }
	    if (not)
219
	      return FALSE;
Elliot Lee's avatar
Elliot Lee committed
220 221 222 223
	  }
	  break;

	default:
224 225
	  if (c != nc)
	    return FALSE;
Elliot Lee's avatar
Elliot Lee committed
226 227
	}

228
      component_start = (nc == G_DIR_SEPARATOR);
Elliot Lee's avatar
Elliot Lee committed
229 230 231
    }

  if (*n == '\0')
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    return TRUE;

  return FALSE;
}

/* Match STRING against the filename pattern PATTERN, returning zero if
 *  it matches, nonzero if not.
 *
 * GTK+ used to use a old version of GNU fnmatch() that was buggy
 * in various ways and didn't handle UTF-8. The following is
 * converted to UTF-8. To simplify the process of making it
 * correct, this is special-cased to the combinations of flags
 * that gtkfilesel.c uses.
 *
 *   FNM_FILE_NAME   - always set
 *   FNM_LEADING_DIR - never set
 *   FNM_PERIOD      - always set
 *   FNM_NOESCAPE    - set only on windows
 *   FNM_CASEFOLD    - set only on windows
 */
gboolean
_gtk_fnmatch (const char *pattern,
	      const char *string)
{
  return gtk_fnmatch_intern (pattern, string, TRUE);
}

Matthias Clasen's avatar
Matthias Clasen committed
259
#undef FNMATCH_TEST_CASES
260
#ifdef FNMATCH_TEST_CASES
Elliot Lee's avatar
Elliot Lee committed
261

262 263
#define TEST(pat, str, result) \
  g_assert (_gtk_fnmatch ((pat), (str)) == result)
Elliot Lee's avatar
Elliot Lee committed
264

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
int main (int argc, char **argv)
{
  TEST ("[a-]", "-", TRUE);
  
  TEST ("a", "a", TRUE);
  TEST ("a", "b", FALSE);

  /* Test what ? matches */
  TEST ("?", "a", TRUE);
  TEST ("?", ".", FALSE);
  TEST ("a?", "a.", TRUE);
  TEST ("a/?", "a/b", TRUE);
  TEST ("a/?", "a/.", FALSE);
  TEST ("?", "/", FALSE);

  /* Test what * matches */
  TEST ("*", "a", TRUE);
  TEST ("*", ".", FALSE);
  TEST ("a*", "a.", TRUE);
  TEST ("a/*", "a/b", TRUE);
  TEST ("a/*", "a/.", FALSE);
  TEST ("*", "/", FALSE);

  /* Range tests */
  TEST ("[ab]", "a", TRUE);
  TEST ("[ab]", "c", FALSE);
  TEST ("[^ab]", "a", FALSE);
  TEST ("[!ab]", "a", FALSE);
  TEST ("[^ab]", "c", TRUE);
  TEST ("[!ab]", "c", TRUE);
  TEST ("[a-c]", "b", TRUE);
  TEST ("[a-c]", "d", FALSE);
  TEST ("[a-]", "-", TRUE);
  TEST ("[]]", "]", TRUE);
  TEST ("[^]]", "a", TRUE);
  TEST ("[!]]", "a", TRUE);

  /* Various unclosed ranges */
  TEST ("[ab", "a", FALSE);
  TEST ("[a-", "a", FALSE);
  TEST ("[ab", "c", FALSE);
  TEST ("[a-", "c", FALSE);
  TEST ("[^]", "a", FALSE);

  /* Ranges and special no-wildcard matches */
  TEST ("[.]", ".", FALSE);
  TEST ("a[.]", "a.", TRUE);
  TEST ("a/[.]", "a/.", FALSE);
  TEST ("[/]", "/", FALSE);
  TEST ("[^/]", "a", TRUE);
  
  /* Basic tests of * (and combinations of * and ?) */
  TEST ("a*b", "ab", TRUE);
  TEST ("a*b", "axb", TRUE);
  TEST ("a*b", "axxb", TRUE);
  TEST ("a**b", "ab", TRUE);
  TEST ("a**b", "axb", TRUE);
  TEST ("a**b", "axxb", TRUE);
  TEST ("a*?*b", "ab", FALSE);
  TEST ("a*?*b", "axb", TRUE);
  TEST ("a*?*b", "axxb", TRUE);

  /* Test of  *[range] */
  TEST ("a*[cd]", "ac", TRUE);
  TEST ("a*[cd]", "axc", TRUE);
  TEST ("a*[cd]", "axx", FALSE);

  TEST ("a/[.]", "a/.", FALSE);
  TEST ("a*[.]", "a/.", FALSE);

  /* Test of UTF-8 */

  TEST ("ä", "ä", TRUE);      /* TEST ("ä", "ä", TRUE); */
  TEST ("?", "ä", TRUE);       /* TEST ("?", "ä", TRUE); */
  TEST ("*ö", "äö", TRUE);   /* TEST ("*ö", "äö", TRUE); */
  TEST ("*ö", "ääö", TRUE); /* TEST ("*ö", "ääö", TRUE); */
  TEST ("[ä]", "ä", TRUE);    /* TEST ("[ä]", "ä", TRUE); */
  TEST ("[ä-ö]", "é", TRUE); /* TEST ("[ä-ö]", "é", TRUE); */
  TEST ("[ä-ö]", "a", FALSE); /* TEST ("[ä-ö]", "a", FALSE); */

#ifdef DO_ESCAPE
  /* Tests of escaping */
  TEST ("\\\\", "\\", TRUE);
  TEST ("\\?", "?", TRUE);
  TEST ("\\?", "a", FALSE);
  TEST ("\\*", "*", TRUE);
  TEST ("\\*", "a", FALSE);
  TEST ("\\[a-b]", "[a-b]", TRUE);
  TEST ("[\\\\]", "\\", TRUE);
  TEST ("[\\^a]", "a", TRUE);
  TEST ("[a\\-c]", "b", FALSE);
  TEST ("[a\\-c]", "-", TRUE);
  TEST ("[a\\]", "a", FALSE);
#endif /* DO_ESCAPE */
  
  return 0;
Elliot Lee's avatar
Elliot Lee committed
361 362
}

363
#endif /* FNMATCH_TEST_CASES */