File Coverage

src/match_path.c
Criterion Covered Total %
statement 75 115 65.2
branch 50 120 41.6
condition n/a
subroutine n/a
pod n/a
total 125 235 53.1


line stmt bran cond sub pod time code
1             /*-
2             * Copyright (c) 2003-2007 Tim Kientzle
3             * All rights reserved.
4             *
5             * Redistribution and use in source and binary forms, with or without
6             * modification, are permitted provided that the following conditions
7             * are met:
8             * 1. Redistributions of source code must retain the above copyright
9             * notice, this list of conditions and the following disclaimer
10             * in this position and unchanged.
11             * 2. Redistributions in binary form must reproduce the above copyright
12             * notice, this list of conditions and the following disclaimer in the
13             * documentation and/or other materials provided with the distribution.
14             *
15             * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
16             * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17             * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18             * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
19             * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20             * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21             * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22             * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23             * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24             * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25             */
26              
27             /*
28             * Copyright (c) 2012, cPanel, Inc.
29             * All rights reserved.
30             * http://cpanel.net/
31             *
32             * This is free software; you can redistribute it and/or modify it under the
33             * same terms as Perl itself. See the Perl manual section 'perlartistic' for
34             * further information.
35             *
36             * Modified for use in Archive::Tar::Builder.
37             */
38              
39             #include
40              
41             #include "match_path.h"
42              
43             /*
44             * Check whether a character 'c' is matched by a list specification [...]:
45             * * Leading '!' or '^' negates the class.
46             * * - is a range of characters
47             * * \ removes any special meaning for
48             *
49             * Some interesting boundary cases:
50             * a-d-e is one range (a-d) followed by two single characters - and e.
51             * \a-\d is same as a-d
52             * a\-d is three single characters: a, d, -
53             * Trailing - is not special (so [a-] is two characters a and -).
54             * Initial - is not special ([a-] is same as [-a] is same as [\\-a])
55             * This function never sees a trailing \.
56             * [] always fails
57             * [!] always succeeds
58             */
59             static int
60 56           pm_list(const char *start, const char *end, const char c, int flags)
61             {
62 56           const char *p = start;
63 56           char rangeStart = '\0', nextRangeStart;
64 56           int match = 1, nomatch = 0;
65              
66             /* This will be used soon... */
67             (void)flags; /* UNUSED */
68              
69             /* If this is a negated class, return success for nomatch. */
70 56 50         if ((*p == '!' || *p == '^') && p < end) {
    0          
    50          
71 56           match = 0;
72 56           nomatch = 1;
73 56           ++p;
74             }
75              
76 112 100         while (p < end) {
77 56           nextRangeStart = '\0';
78 56           switch (*p) {
79             case '-':
80             /* Trailing or initial '-' is not special. */
81 28 50         if ((rangeStart == '\0') || (p == end - 1)) {
    0          
82 28 50         if (*p == c)
83 0           return (match);
84             } else {
85 0           char rangeEnd = *++p;
86 0 0         if (rangeEnd == '\\')
87 0           rangeEnd = *++p;
88 0 0         if ((rangeStart <= c) && (c <= rangeEnd))
    0          
89 0           return (match);
90             }
91 28           break;
92             case '\\':
93 0           ++p;
94             /* Fall through */
95             default:
96 28 50         if (*p == c)
97 0           return (match);
98 28           nextRangeStart = *p; /* Possible start of range. */
99             }
100 56           rangeStart = nextRangeStart;
101 56           ++p;
102             }
103 56           return (nomatch);
104             }
105              
106             /*
107             * If s is pointing to "./", ".//", "./././" or the like, skip it.
108             */
109             static const char *
110 28           pm_slashskip(const char *s) {
111 56 100         while ((*s == '/')
112 28 50         || (s[0] == '.' && s[1] == '/')
    0          
113 28 50         || (s[0] == '.' && s[1] == '\0'))
    0          
114 28           ++s;
115 28           return (s);
116             }
117              
118             static int
119 1883           pm(const char *p, const char *s, int flags)
120             {
121             const char *end;
122              
123             /*
124             * Ignore leading './', './/', '././', etc.
125             */
126 1883 100         if (s[0] == '.' && s[1] == '/')
    50          
127 0           s = pm_slashskip(s + 1);
128 1883 50         if (p[0] == '.' && p[1] == '/')
    0          
129 0           p = pm_slashskip(p + 1);
130              
131             for (;;) {
132 3080           switch (*p) {
133             case '\0':
134 140 100         if (s[0] == '/') {
135 56 50         if (flags & PATHMATCH_NO_ANCHOR_END)
136 56           return (1);
137             /* "dir" == "dir/" == "dir/." */
138 0           s = pm_slashskip(s);
139             }
140 84           return (*s == '\0');
141             case '?':
142             /* ? always succeeds, unless we hit end of 's' */
143 0 0         if (*s == '\0')
144 0           return (0);
145 0           break;
146             case '*':
147             /* "*" == "**" == "***" ... */
148 168 100         while (*p == '*')
149 84           ++p;
150             /* Trailing '*' always succeeds. */
151 84 100         if (*p == '\0')
152 28           return (1);
153 196 50         while (*s) {
154 196 100         if (lafe_pathmatch(p, s, flags))
155 56           return (1);
156 140           ++s;
157             }
158 0           return (0);
159             case '[':
160             /*
161             * Find the end of the [...] character class,
162             * ignoring \] that might occur within the class.
163             */
164 56           end = p + 1;
165 168 50         while (*end != '\0' && *end != ']') {
    100          
166 112 50         if (*end == '\\' && end[1] != '\0')
    0          
167 0           ++end;
168 112           ++end;
169             }
170 56 50         if (*end == ']') {
171             /* We found [...], try to match it. */
172 56 50         if (!pm_list(p + 1, end, *s, flags))
173 0           return (0);
174 56           p = end; /* Jump to trailing ']' char. */
175 56           break;
176             } else
177             /* No final ']', so just match '['. */
178 0 0         if (*p != *s)
179 0           return (0);
180 0           break;
181             case '\\':
182             /* Trailing '\\' matches itself. */
183 0 0         if (p[1] == '\0') {
184 0 0         if (*s != '\\')
185 0           return (0);
186             } else {
187 0           ++p;
188 0 0         if (*p != *s)
189 0           return (0);
190             }
191 0           break;
192             case '/':
193 14 50         if (*s != '/' && *s != '\0')
    0          
194 0           return (0);
195             /* Note: pattern "/\./" won't match "/";
196             * pm_slashskip() correctly stops at backslash. */
197 14           p = pm_slashskip(p);
198 14           s = pm_slashskip(s);
199 14 50         if (*p == '\0' && (flags & PATHMATCH_NO_ANCHOR_END))
    0          
200 0           return (1);
201 14           --p; /* Counteract the increment below. */
202 14           --s;
203 14           break;
204             case '$':
205             /* '$' is special only at end of pattern and only
206             * if PATHMATCH_NO_ANCHOR_END is specified. */
207 0 0         if (p[1] == '\0' && (flags & PATHMATCH_NO_ANCHOR_END)){
    0          
208             /* "dir" == "dir/" == "dir/." */
209 0           return (*pm_slashskip(s) == '\0');
210             }
211             /* Otherwise, '$' is not special. */
212             /* FALL THROUGH */
213             default:
214 2786 100         if (*p != *s)
215 1659           return (0);
216 1127           break;
217             }
218 1197           ++p;
219 1197           ++s;
220 1197           }
221             }
222              
223             /* Main entry point. */
224             int
225 1043           lafe_pathmatch(const char *p, const char *s, int flags)
226             {
227             /* Empty pattern only matches the empty string. */
228 1043 50         if (p == NULL || *p == '\0')
    50          
229 0 0         return (s == NULL || *s == '\0');
    0          
230              
231             /* Leading '^' anchors the start of the pattern. */
232 1043 50         if (*p == '^') {
233 0           ++p;
234 0           flags &= ~PATHMATCH_NO_ANCHOR_START;
235             }
236              
237 1043 50         if (*p == '/' && *s != '/')
    0          
238 0           return (0);
239              
240             /* Certain patterns and file names anchor implicitly. */
241 1043 50         if (*p == '*' || *p == '/' || *p == '/') {
    50          
    50          
242 0 0         while (*p == '/')
243 0           ++p;
244 0 0         while (*s == '/')
245 0           ++s;
246 0           return (pm(p, s, flags));
247             }
248              
249             /* If start is unanchored, try to match start of each path element. */
250 1043 100         if (flags & PATHMATCH_NO_ANCHOR_START) {
251 2191 100         for ( ; s != NULL; s = strchr(s, '/')) {
252 1603 100         if (*s == '/')
253 910           s++;
254 1603 100         if (pm(p, s, flags))
255 175           return (1);
256             }
257 588           return (0);
258             }
259              
260             /* Default: Match from beginning. */
261 280           return (pm(p, s, flags));
262             }