/** Check whether the string str matches the wildcard string wc. \param str String to be matched. \param wc The wildcard. \param is_first Whether files beginning with dots should not be matched against wildcards. */ static bool wildcard_match2(const wchar_t *str, const wchar_t *wc, bool is_first) { if (*str == 0 && *wc==0) return true; /* Hackish fix for https://github.com/fish-shell/fish-shell/issues/270. Prevent wildcards from matching . or .., but we must still allow literal matches. */ if (is_first && contains(str, L".", L"..")) { /* The string is '.' or '..'. Return true if the wildcard exactly matches. */ return ! wcscmp(str, wc); } if (*wc == ANY_STRING || *wc == ANY_STRING_RECURSIVE) { /* Ignore hidden file */ if (is_first && *str == L'.') { return false; } /* Try all submatches */ do { if (wildcard_match2(str, wc+1, false)) return true; } while (*(str++) != 0); return false; } else if (*str == 0) { /* End of string, but not end of wildcard, and the next wildcard element is not a '*', so this is not a match. */ return false; } if (*wc == ANY_CHAR) { if (is_first && *str == L'.') { return false; } return wildcard_match2(str+1, wc+1, false); } if (*wc == *str) return wildcard_match2(str+1, wc+1, false); return false; }
/** Check whether the string str matches the wildcard string wc. \param str String to be matched. \param wc The wildcard. \param is_first Whether files beginning with dots should not be matched against wildcards. */ static int wildcard_match2( const wchar_t *str, const wchar_t *wc, int is_first ) { if( *str == 0 && *wc==0 ) return 1; if( *wc == ANY_STRING || *wc == ANY_STRING_RECURSIVE) { /* Ignore hidden file */ if( is_first && *str == L'.' ) { return 0; } /* Try all submatches */ do { if( wildcard_match2( str, wc+1, 0 ) ) return 1; } while( *(str++) != 0 ); return 0; } else if( *str == 0 ) { /* End of string, but not end of wildcard, and the next wildcard element is not a '*', so this is not a match. */ return 0; } if( *wc == ANY_CHAR ) { if( is_first && *str == L'.' ) { return 0; } return wildcard_match2( str+1, wc+1, 0 ); } if( *wc == *str ) return wildcard_match2( str+1, wc+1, 0 ); return 0; }
/** The real implementation of wildcard expansion is in this function. Other functions are just wrappers around this one. This function traverses the relevant directory tree looking for matches, and recurses when needed to handle wildcrards spanning multiple components and recursive wildcards. */ static int wildcard_expand_internal( const wchar_t *wc, const wchar_t *base_dir, expand_flags_t flags, std::vector<completion_t> &out, std::set<wcstring> &completion_set, std::set<file_id_t> &visited_files ) { /* Points to the end of the current wildcard segment */ const wchar_t *wc_end; /* Variables for traversing a directory */ DIR *dir; /* The result returned */ int res = 0; /* Length of the directory to search in */ size_t base_len; /* Variables for testing for presense of recursive wildcards */ const wchar_t *wc_recursive; int is_recursive; /* Slightly mangled version of base_dir */ const wchar_t *dir_string; // debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir ); if( reader_interrupted() ) { return -1; } if( !wc || !base_dir ) { debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ ); return 0; } if( flags & ACCEPT_INCOMPLETE ) { /* Avoid excessive number of returned matches for wc ending with a * */ size_t len = wcslen(wc); if( len && (wc[len-1]==ANY_STRING) ) { wchar_t * foo = wcsdup( wc ); foo[len-1]=0; int res = wildcard_expand_internal( foo, base_dir, flags, out, completion_set, visited_files ); free( foo ); return res; } } /* Initialize various variables */ dir_string = base_dir[0]==L'\0'?L".":base_dir; if( !(dir = wopendir( dir_string ))) { return 0; } wc_end = wcschr(wc,L'/'); base_len = wcslen( base_dir ); /* Test for recursive match string in current segment */ wc_recursive = wcschr( wc, ANY_STRING_RECURSIVE ); is_recursive = ( wc_recursive && (!wc_end || wc_recursive < wc_end)); /* Is this segment of the wildcard the last? */ if( !wc_end ) { /* Wildcard segment is the last segment, Insert all matching files/directories */ if( wc[0]=='\0' ) { /* The last wildcard segment is empty. Insert everything if completing, the directory itself otherwise. */ if( flags & ACCEPT_INCOMPLETE ) { wcstring next; while(wreaddir(dir, next)) { if( next[0] != L'.' ) { wcstring long_name = make_path( base_dir, next ); if( test_flags( long_name.c_str(), flags ) ) { wildcard_completion_allocate( out, long_name, next, L"", flags); } } } } else { res = 1; insert_completion_if_missing(base_dir, out, completion_set); } } else { /* This is the last wildcard segment, and it is not empty. Match files/directories. */ wcstring next; while (wreaddir(dir, next)) { const wchar_t * const name = next.c_str(); if( flags & ACCEPT_INCOMPLETE ) { const wcstring long_name = make_path( base_dir, next ); /* Test for matches before stating file, so as to minimize the number of calls to the much slower stat function */ std::vector<completion_t> test; if( wildcard_complete( name, wc, L"", 0, test, 0 ) ) { if( test_flags( long_name.c_str(), flags ) ) { wildcard_completion_allocate( out, long_name, name, wc, flags); } } } else { if( wildcard_match2( name, wc, 1 ) ) { const wcstring long_name = make_path(base_dir, next); int skip = 0; if( is_recursive ) { /* In recursive mode, we are only interested in adding files -directories will be added in the next pass. */ struct stat buf; if( !wstat( long_name, &buf ) ) { skip = S_ISDIR(buf.st_mode); } } if (! skip) { insert_completion_if_missing(long_name, out, completion_set); } res = 1; } } } } } if( wc_end || is_recursive ) { /* Wilcard segment is not the last segment. Recursively call wildcard_expand for all matching subdirectories. */ /* wc_str is the part of the wildcarded string from the beginning to the first slash */ wchar_t *wc_str; /* new_dir is a scratch area containing the full path to a file/directory we are iterating over */ wchar_t *new_dir; /* The maximum length of a file element */ long ln=MAX_FILE_LENGTH; char * narrow_dir_string = wcs2str( dir_string ); /* In recursive mode, we look through the directory twice. If so, this rewind is needed. */ rewinddir( dir ); if( narrow_dir_string ) { /* Find out how long the filename can be in a worst case scenario */ ln = pathconf( narrow_dir_string, _PC_NAME_MAX ); /* If not specified, use som large number as fallback */ if( ln < 0 ) ln = MAX_FILE_LENGTH; free( narrow_dir_string ); } new_dir= (wchar_t *)malloc( sizeof(wchar_t)*(base_len+ln+2) ); wc_str = wc_end?wcsndup(wc, wc_end-wc):wcsdup(wc); if( (!new_dir) || (!wc_str) ) { DIE_MEM(); } wcscpy( new_dir, base_dir ); wcstring next; while (wreaddir(dir, next)) { const wchar_t *name = next.c_str(); /* Test if the file/directory name matches the whole wildcard element, i.e. regular matching. */ int whole_match = wildcard_match2( name, wc_str, 1 ); int partial_match = 0; /* If we are doing recursive matching, also check if this directory matches the part up to the recusrive wildcard, if so, then we can search all subdirectories for matches. */ if( is_recursive ) { const wchar_t *end = wcschr( wc, ANY_STRING_RECURSIVE ); wchar_t *wc_sub = wcsndup( wc, end-wc+1); partial_match = wildcard_match2( name, wc_sub, 1 ); free( wc_sub ); } if( whole_match || partial_match ) { struct stat buf; char *dir_str; int stat_res; int new_res; wcscpy(&new_dir[base_len], name ); dir_str = wcs2str( new_dir ); if( dir_str ) { stat_res = stat( dir_str, &buf ); free( dir_str ); if( !stat_res ) { // Insert a "file ID" into visited_files // If the insertion fails, we've already visited this file (i.e. a symlink loop) const file_id_t file_id(buf.st_dev, buf.st_ino); if( S_ISDIR(buf.st_mode) && visited_files.insert(file_id).second) { size_t new_len = wcslen( new_dir ); new_dir[new_len] = L'/'; new_dir[new_len+1] = L'\0'; /* Regular matching */ if( whole_match ) { const wchar_t *new_wc = L""; if( wc_end ) { new_wc=wc_end+1; /* Accept multiple '/' as a single direcotry separator */ while(*new_wc==L'/') { new_wc++; } } new_res = wildcard_expand_internal( new_wc, new_dir, flags, out, completion_set, visited_files ); if( new_res == -1 ) { res = -1; break; } res |= new_res; } /* Recursive matching */ if( partial_match ) { new_res = wildcard_expand_internal( wcschr( wc, ANY_STRING_RECURSIVE ), new_dir, flags | WILDCARD_RECURSIVE, out, completion_set, visited_files); if( new_res == -1 ) { res = -1; break; } res |= new_res; } } } } } } free( wc_str ); free( new_dir ); } closedir( dir ); return res; }
int wildcard_match( const wcstring &str, const wcstring &wc ) { return wildcard_match2( str.c_str(), wc.c_str(), 1 ); }
bool wildcard_match(const wcstring &str, const wcstring &wc) { return wildcard_match2(str.c_str(), wc.c_str(), true); }