1 #pragma ident "%Z%%M% %I% %E% SMI" 2 3 /**************************************************************************** 4 Copyright (c) 1999,2000 WU-FTPD Development Group. 5 All rights reserved. 6 7 Portions Copyright (c) 1980, 1985, 1988, 1989, 1990, 1991, 1993, 1994 8 The Regents of the University of California. 9 Portions Copyright (c) 1993, 1994 Washington University in Saint Louis. 10 Portions Copyright (c) 1996, 1998 Berkeley Software Design, Inc. 11 Portions Copyright (c) 1989 Massachusetts Institute of Technology. 12 Portions Copyright (c) 1998 Sendmail, Inc. 13 Portions Copyright (c) 1983, 1995, 1996, 1997 Eric P. Allman. 14 Portions Copyright (c) 1997 by Stan Barber. 15 Portions Copyright (c) 1997 by Kent Landfield. 16 Portions Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997 17 Free Software Foundation, Inc. 18 19 Use and distribution of this software and its source code are governed 20 by the terms and conditions of the WU-FTPD Software License ("LICENSE"). 21 22 If you did not receive a copy of the license, it may be obtained online 23 at http://www.wu-ftpd.org/license.html. 24 25 $Id: wu_fnmatch.c,v 1.7 2000/10/25 20:18:13 wuftpd Exp $ 26 27 ****************************************************************************/ 28 /* 29 * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. 30 * Compares a filename or pathname to a pattern. 31 */ 32 33 #include <ctype.h> 34 #include <stddef.h> 35 #include <stdio.h> 36 #include <string.h> 37 38 typedef int boolean; 39 #define FALSE 0 40 #define TRUE 1 41 42 #include "wu_fnmatch.h" 43 44 #define EOS '\0' 45 46 static const char *rangematch(const char *pattern, const char *string, int flags) 47 { 48 /* 49 * A bracket expression starting with an unquoted circumflex character 50 * produces unspecified results (IEEE 1003.2-1992, 3.13.2). This 51 * implementation treats it like '!', for consistency with the regular 52 * expression syntax. J.T. Conklin (conklin@ngai.kaleida.com) 53 */ 54 char test = *string; 55 boolean negate = ((*pattern == '!') || (*pattern == '^')); 56 boolean ok = FALSE; 57 if (negate) 58 ++pattern; 59 if (flags & FNM_CASEFOLD) 60 test = tolower((unsigned char) test); 61 while (*pattern != ']') { 62 char c = *pattern++; 63 if ((c == '\\') && !(flags & FNM_NOESCAPE)) 64 c = *pattern++; 65 if (c == EOS) 66 return (NULL); 67 if (flags & FNM_CASEFOLD) 68 c = tolower((unsigned char) c); 69 if (*pattern == '-') { 70 char c2 = pattern[1]; 71 if ((c2 != EOS) 72 && (c2 != ']')) { 73 pattern += 2; 74 if ((c2 == '\\') && !(flags & FNM_NOESCAPE)) 75 c2 = *pattern++; 76 if (c2 == EOS) 77 return (NULL); 78 if (flags & FNM_CASEFOLD) 79 c2 = tolower((unsigned char) c2); 80 /* this is a hack */ 81 if ((c <= test) && (test <= c2)) 82 ok = TRUE; 83 } 84 else if (c == test) 85 ok = TRUE; 86 } 87 else if (c == test) 88 ok = TRUE; 89 } 90 return ((ok == negate) ? NULL : pattern+1); 91 } 92 93 int wu_fnmatch(const char *pattern, const char *string, int flags) 94 { 95 const char *stringstart = string; 96 if ((pattern == NULL) || (string == NULL)) 97 return FNM_NOMATCH; 98 while (TRUE) { 99 char test; 100 char c = *pattern++; 101 switch (c) { 102 case EOS: 103 #ifdef FNM_LEADING_DIR 104 if ((flags & FNM_LEADING_DIR) 105 && (*string == '/')) 106 return (0); 107 /* 108 * WU-FTPD extension/correction. 109 * 110 * If the pattern ended with a '/', and we're doing 111 * FNM_PATHNAME matching, consider it a match if the 112 * previous string character was a '/' and the current 113 * is not a '/'. 114 */ 115 if ((flags & FNM_LEADING_DIR) 116 && (string != stringstart) 117 && (flags & FNM_PATHNAME) 118 && (*(string - 1) == '/')) 119 return (0); 120 #endif 121 return ((*string == EOS) ? 0 : FNM_NOMATCH); 122 case '?': 123 if (*string == EOS) 124 return (FNM_NOMATCH); 125 if ((*string == '/') 126 && (flags & FNM_PATHNAME)) 127 return (FNM_NOMATCH); 128 if ((*string == '.') 129 && (flags & FNM_PERIOD) 130 && ((string == stringstart) 131 || ((flags & FNM_PATHNAME) 132 && (*(string - 1) == '/')))) 133 return (FNM_NOMATCH); 134 ++string; 135 break; 136 case '*': 137 c = *pattern; 138 while (c == '*') 139 c = *++pattern; 140 if ((*string == '.') 141 && (flags & FNM_PERIOD) 142 && ((string == stringstart) 143 || ((flags & FNM_PATHNAME) 144 && (*(string - 1) == '/')))) 145 return (FNM_NOMATCH); 146 /* Optimize for pattern with * at end or before /. */ 147 if (c == EOS) 148 if (flags & FNM_PATHNAME) { 149 #ifdef FNM_LEADING_DIR 150 if (flags & FNM_LEADING_DIR) 151 return (0); 152 #endif 153 return ((strchr(string, '/') == NULL) ? 0 : FNM_NOMATCH); 154 } 155 else 156 return (0); 157 else if ((c == '/') 158 && (flags & FNM_PATHNAME)) { 159 string = strchr(string, '/'); 160 if (string == NULL) 161 return (FNM_NOMATCH); 162 break; 163 } 164 /* General case, use recursion. */ 165 for (test = *string; test != EOS; test = *++string) { 166 if (!wu_fnmatch(pattern, string, (flags & ~FNM_PERIOD))) 167 return (0); 168 if ((test == '/') 169 && (flags & FNM_PATHNAME)) 170 break; 171 } 172 return (FNM_NOMATCH); 173 case '[': 174 if (*string == EOS) 175 return (FNM_NOMATCH); 176 if ((*string == '/') 177 && (flags & FNM_PATHNAME)) 178 return (FNM_NOMATCH); 179 pattern = rangematch(pattern, string, flags); 180 if (pattern == NULL) 181 return (FNM_NOMATCH); 182 ++string; 183 break; 184 case '\\': 185 if (!(flags & FNM_NOESCAPE)) { 186 c = *pattern++; 187 if (c == EOS) { 188 c = '\\'; 189 --pattern; 190 } 191 } 192 /* FALLTHROUGH */ 193 default: 194 if (c == *string); 195 #ifdef FNM_CASEFOLD 196 else if ((flags & FNM_CASEFOLD) 197 && (tolower((unsigned char) c) == tolower((unsigned char) *string))); 198 #endif 199 else 200 return (FNM_NOMATCH); 201 string++; 202 break; 203 } 204 } 205 /* NOTREACHED */ 206 }