fnmatch.c 8.36 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 <string.h>
33

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

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

43 44 45 46 47
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
48

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

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

56 57 58 59 60
#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
61

62 63 64 65 66 67 68 69 70 71 72 73
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
74 75 76 77

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

78 79 80 81 82 83 84 85
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
86
    {
87 88 89 90 91
      const char *last_n = n;
      
      gunichar c = get_char (&p);
      gunichar nc = get_char (&n);
      
Elliot Lee's avatar
Elliot Lee committed
92 93
      switch (c)
	{
94 95 96 97 98 99 100
   	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
101 102
	  break;
	case '\\':
103 104 105 106
	  if (DO_ESCAPE)
	    c = get_char (&p);
	  if (nc != c)
	    return FALSE;
Elliot Lee's avatar
Elliot Lee committed
107 108
	  break;
	case '*':
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
	  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
131

132 133 134 135 136 137 138 139 140 141 142
	    /* 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
143

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

147 148 149 150 151 152 153 154 155 156 157 158
	    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
159 160 161 162 163
	  }

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

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

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

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

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

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

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

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

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

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

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

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

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

  if (*n == '\0')
231 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
    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
258
#undef FNMATCH_TEST_CASES
259
#ifdef FNMATCH_TEST_CASES
Elliot Lee's avatar
Elliot Lee committed
260

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

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
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
360 361
}

362
#endif /* FNMATCH_TEST_CASES */