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 }