1
18
19 package io.undertow.servlet.handlers;
20
21 import static io.undertow.servlet.handlers.ServletPathMatch.Type.REDIRECT;
22 import static io.undertow.servlet.handlers.ServletPathMatch.Type.REWRITE;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.EnumMap;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import javax.servlet.DispatcherType;
34 import javax.servlet.http.MappingMatch;
35
36 import io.undertow.server.HandlerWrapper;
37 import io.undertow.server.HttpHandler;
38 import io.undertow.server.handlers.cache.LRUCache;
39 import io.undertow.server.handlers.resource.Resource;
40 import io.undertow.server.handlers.resource.ResourceManager;
41 import io.undertow.servlet.UndertowServletMessages;
42 import io.undertow.servlet.api.Deployment;
43 import io.undertow.servlet.api.DeploymentInfo;
44 import io.undertow.servlet.api.FilterMappingInfo;
45 import io.undertow.servlet.api.ServletInfo;
46 import io.undertow.servlet.core.ManagedFilter;
47 import io.undertow.servlet.core.ManagedFilters;
48 import io.undertow.servlet.core.ManagedServlet;
49 import io.undertow.servlet.core.ManagedServlets;
50 import io.undertow.servlet.handlers.security.ServletSecurityRoleHandler;
51
52
57 public class ServletPathMatches {
58
59 public static final String DEFAULT_SERVLET_NAME = "default";
60 private final Deployment deployment;
61
62 private volatile String[] welcomePages;
63 private final ResourceManager resourceManager;
64
65 private volatile ServletPathMatchesData data;
66
67 private final LRUCache<String, ServletPathMatch> pathMatchCache = new LRUCache<>(1000, -1, true);
68
69 public ServletPathMatches(final Deployment deployment) {
70 this.deployment = deployment;
71 this.welcomePages = deployment.getDeploymentInfo().getWelcomePages().toArray(new String[deployment.getDeploymentInfo().getWelcomePages().size()]);
72 this.resourceManager = deployment.getDeploymentInfo().getResourceManager();
73 }
74
75 public void initData(){
76 getData();
77 }
78
79 public ServletChain getServletHandlerByName(final String name) {
80 return getData().getServletHandlerByName(name);
81 }
82
83 public ServletPathMatch getServletHandlerByPath(final String path) {
84 ServletPathMatch existing = pathMatchCache.get(path);
85 if(existing != null) {
86 return existing;
87 }
88 ServletPathMatch match = getData().getServletHandlerByPath(path);
89 if (!match.isRequiredWelcomeFileMatch()) {
90 pathMatchCache.add(path, match);
91 return match;
92 }
93 try {
94
95 String remaining = match.getRemaining() == null ? match.getMatched() : match.getRemaining();
96 Resource resource = resourceManager.getResource(remaining);
97 if (resource == null || !resource.isDirectory()) {
98 pathMatchCache.add(path, match);
99 return match;
100 }
101
102 boolean pathEndsWithSlash = remaining.endsWith("/");
103 final String pathWithTrailingSlash = pathEndsWithSlash ? remaining : remaining + "/";
104
105 ServletPathMatch welcomePage = findWelcomeFile(pathWithTrailingSlash, !pathEndsWithSlash);
106
107 if (welcomePage != null) {
108 pathMatchCache.add(path, welcomePage);
109 return welcomePage;
110 } else {
111 welcomePage = findWelcomeServlet(pathWithTrailingSlash, !pathEndsWithSlash);
112 if (welcomePage != null) {
113 pathMatchCache.add(path, welcomePage);
114 return welcomePage;
115 } else if(pathEndsWithSlash) {
116 pathMatchCache.add(path, match);
117 return match;
118 } else {
119 ServletPathMatch redirect = new ServletPathMatch(match.getServletChain(), match.getMatched(), match.getRemaining(), REDIRECT, "/");
120 pathMatchCache.add(path, redirect);
121 return redirect;
122 }
123 }
124
125 } catch (IOException e) {
126 throw new RuntimeException(e);
127 }
128
129 }
130
131 public void invalidate() {
132 this.data = null;
133 this.pathMatchCache.clear();
134 }
135
136 private ServletPathMatchesData getData() {
137 ServletPathMatchesData data = this.data;
138 if (data != null) {
139 return data;
140 }
141 synchronized (this) {
142 if (this.data != null) {
143 return this.data;
144 }
145 return this.data = setupServletChains();
146 }
147 }
148
149 private ServletPathMatch findWelcomeFile(final String path, boolean requiresRedirect) {
150 if(File.separatorChar != '/' && path.contains(File.separator)) {
151 return null;
152 }
153 StringBuilder sb = new StringBuilder();
154 for (String i : welcomePages) {
155 try {
156 sb.append(path);
157 sb.append(i);
158 final String mergedPath = sb.toString();
159 sb.setLength(0);
160 Resource resource = resourceManager.getResource(mergedPath);
161 if (resource != null) {
162 final ServletPathMatch handler = data.getServletHandlerByPath(mergedPath);
163 return new ServletPathMatch(handler.getServletChain(), mergedPath, null, requiresRedirect ? REDIRECT : REWRITE, mergedPath);
164 }
165 } catch (IOException e) {
166 }
167 }
168 return null;
169 }
170
171 private ServletPathMatch findWelcomeServlet(final String path, boolean requiresRedirect) {
172 StringBuilder sb = new StringBuilder();
173 for (String i : welcomePages) {
174 sb.append(path);
175 sb.append(i);
176 final String mergedPath = sb.toString();
177 sb.setLength(0);
178 final ServletPathMatch handler = data.getServletHandlerByPath(mergedPath);
179 if (handler != null && !handler.isRequiredWelcomeFileMatch()) {
180 return new ServletPathMatch(handler.getServletChain(), handler.getMatched(), handler.getRemaining(), requiresRedirect ? REDIRECT : REWRITE, mergedPath);
181 }
182 }
183 return null;
184 }
185
186 public void setWelcomePages(List<String> welcomePages) {
187 this.welcomePages = welcomePages.toArray(new String[welcomePages.size()]);
188 }
189
190
199 private ServletPathMatchesData setupServletChains() {
200
201 ServletHandler defaultServlet = null;
202 final ManagedServlets servlets = deployment.getServlets();
203 final ManagedFilters filters = deployment.getFilters();
204
205 final Map<String, ServletHandler> extensionServlets = new HashMap<>();
206 final Map<String, ServletHandler> pathServlets = new HashMap<>();
207
208 final Set<String> pathMatches = new HashSet<>();
209 final Set<String> extensionMatches = new HashSet<>();
210
211 DeploymentInfo deploymentInfo = deployment.getDeploymentInfo();
212
213
214 for (FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) {
215 if (mapping.getMappingType() == FilterMappingInfo.MappingType.URL) {
216 String path = mapping.getMapping();
217 if (path.equals("*")) {
218
219 path = "/*";
220 }
221 if (!path.startsWith("*.")) {
222 pathMatches.add(path);
223 } else {
224 extensionMatches.add(path.substring(2));
225 }
226 }
227 }
228
229
230 for (Map.Entry<String, ServletHandler> entry : servlets.getServletHandlers().entrySet()) {
231 final ServletHandler handler = entry.getValue();
232
233 for (String path : handler.getManagedServlet().getServletInfo().getMappings()) {
234 if (path.equals("/")) {
235
236 pathMatches.add("/*");
237 if (defaultServlet != null) {
238 throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path);
239 }
240 defaultServlet = handler;
241 } else if (!path.startsWith("*.")) {
242
243 if (path.isEmpty()) {
244 path = "/";
245 }
246 pathMatches.add(path);
247 if (pathServlets.containsKey(path)) {
248 throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path);
249 }
250 pathServlets.put(path, handler);
251 } else {
252
253 String ext = path.substring(2);
254 extensionMatches.add(ext);
255 if(extensionServlets.containsKey(ext)) {
256 throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path);
257 }
258 extensionServlets.put(ext, handler);
259 }
260 }
261 }
262 ServletHandler managedDefaultServlet = servlets.getServletHandler(DEFAULT_SERVLET_NAME);
263 if(managedDefaultServlet == null) {
264
265 managedDefaultServlet = servlets.addServlet(new ServletInfo(DEFAULT_SERVLET_NAME, DefaultServlet.class));
266 }
267
268 if (defaultServlet == null) {
269
270 pathMatches.add("/*");
271 defaultServlet = managedDefaultServlet;
272 }
273
274 final ServletPathMatchesData.Builder builder = ServletPathMatchesData.builder();
275
276
277
278 for (final String path : pathMatches) {
279
280 MatchData targetServletMatch = resolveServletForPath(path, pathServlets, extensionServlets, defaultServlet);
281
282 final Map<DispatcherType, List<ManagedFilter>> noExtension = new EnumMap<>(DispatcherType.class);
283 final Map<String, Map<DispatcherType, List<ManagedFilter>>> extension = new HashMap<>();
284
285
286 for (String ext : extensionMatches) {
287 extension.put(ext, new EnumMap<DispatcherType, List<ManagedFilter>>(DispatcherType.class));
288 }
289
290
291 for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) {
292 ManagedFilter filter = filters.getManagedFilter(filterMapping.getFilterName());
293 if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) {
294 if (targetServletMatch.handler != null) {
295 if (filterMapping.getMapping().equals(targetServletMatch.handler.getManagedServlet().getServletInfo().getName()) || filterMapping.getMapping().equals("*")) {
296 addToListMap(noExtension, filterMapping.getDispatcher(), filter);
297 }
298 }
299 for (Map.Entry<String, Map<DispatcherType, List<ManagedFilter>>> entry : extension.entrySet()) {
300 ServletHandler pathServlet = targetServletMatch.handler;
301 boolean defaultServletMatch = targetServletMatch.defaultServlet;
302 if (defaultServletMatch && extensionServlets.containsKey(entry.getKey())) {
303 pathServlet = extensionServlets.get(entry.getKey());
304 }
305
306 if (filterMapping.getMapping().equals(pathServlet.getManagedServlet().getServletInfo().getName()) || filterMapping.getMapping().equals("*")) {
307 addToListMap(extension.get(entry.getKey()), filterMapping.getDispatcher(), filter);
308 }
309 }
310 } else {
311 if (filterMapping.getMapping().isEmpty() || !filterMapping.getMapping().startsWith("*.")) {
312 if (isFilterApplicable(path, filterMapping.getMapping())) {
313 addToListMap(noExtension, filterMapping.getDispatcher(), filter);
314 for (Map<DispatcherType, List<ManagedFilter>> l : extension.values()) {
315 addToListMap(l, filterMapping.getDispatcher(), filter);
316 }
317 }
318 } else {
319 addToListMap(extension.get(filterMapping.getMapping().substring(2)), filterMapping.getDispatcher(), filter);
320 }
321 }
322 }
323
324 if (path.endsWith("/*")) {
325 String prefix = path.substring(0, path.length() - 2);
326
327 builder.addPrefixMatch(prefix, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath), targetServletMatch.defaultServlet || targetServletMatch.handler.getManagedServlet().getServletInfo().isRequireWelcomeFileMapping());
328
329
330 for (Map.Entry<String, Map<DispatcherType, List<ManagedFilter>>> entry : extension.entrySet()) {
331 ServletHandler pathServlet = targetServletMatch.handler;
332 String pathMatch = targetServletMatch.matchedPath;
333
334 boolean defaultServletMatch = targetServletMatch.defaultServlet;
335 if (defaultServletMatch && extensionServlets.containsKey(entry.getKey())) {
336 defaultServletMatch = false;
337 pathServlet = extensionServlets.get(entry.getKey());
338 }
339 HttpHandler handler = pathServlet;
340 if (!entry.getValue().isEmpty()) {
341 handler = new FilterHandler(entry.getValue(), deploymentInfo.isAllowNonStandardWrappers(), handler);
342 }
343 builder.addExtensionMatch(prefix, entry.getKey(), servletChain(handler, pathServlet.getManagedServlet(), entry.getValue(), pathMatch, deploymentInfo, defaultServletMatch, defaultServletMatch ? MappingMatch.DEFAULT : MappingMatch.EXTENSION, defaultServletMatch ? "/" : "*." + entry.getKey()));
344 }
345 } else if (path.isEmpty()) {
346
347 builder.addExactMatch("/", createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath));
348 } else {
349
350 int lastSegmentIndex = path.lastIndexOf('/');
351 String lastSegment;
352 if(lastSegmentIndex > 0) {
353 lastSegment = path.substring(lastSegmentIndex);
354 } else {
355 lastSegment = path;
356 }
357 if (lastSegment.contains(".")) {
358 String ext = lastSegment.substring(lastSegment.lastIndexOf('.') + 1);
359 if (extension.containsKey(ext)) {
360 Map<DispatcherType, List<ManagedFilter>> extMap = extension.get(ext);
361 builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, extMap, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath));
362 } else {
363 builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath));
364 }
365 } else {
366 builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet, targetServletMatch.mappingMatch, targetServletMatch.userPath));
367 }
368
369 }
370 }
371
372
373
374 for (Map.Entry<String, ServletHandler> entry : servlets.getServletHandlers().entrySet()) {
375 final Map<DispatcherType, List<ManagedFilter>> filtersByDispatcher = new EnumMap<>(DispatcherType.class);
376 for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) {
377 ManagedFilter filter = filters.getManagedFilter(filterMapping.getFilterName());
378 if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) {
379 if (filterMapping.getMapping().equals(entry.getKey())) {
380 addToListMap(filtersByDispatcher, filterMapping.getDispatcher(), filter);
381 }
382 }
383 }
384 if (filtersByDispatcher.isEmpty()) {
385 builder.addNameMatch(entry.getKey(), servletChain(entry.getValue(), entry.getValue().getManagedServlet(), filtersByDispatcher, null, deploymentInfo, false, MappingMatch.EXACT, ""));
386 } else {
387 builder.addNameMatch(entry.getKey(), servletChain(new FilterHandler(filtersByDispatcher, deploymentInfo.isAllowNonStandardWrappers(), entry.getValue()), entry.getValue().getManagedServlet(), filtersByDispatcher, null, deploymentInfo, false, MappingMatch.EXACT, ""));
388 }
389 }
390
391 return builder.build();
392 }
393
394 private ServletChain createHandler(final DeploymentInfo deploymentInfo, final ServletHandler targetServlet, final Map<DispatcherType, List<ManagedFilter>> noExtension, final String servletPath, final boolean defaultServlet, MappingMatch mappingMatch, String pattern) {
395 final ServletChain initialHandler;
396 if (noExtension.isEmpty()) {
397 initialHandler = servletChain(targetServlet, targetServlet.getManagedServlet(), noExtension, servletPath, deploymentInfo, defaultServlet, mappingMatch, pattern);
398 } else {
399 FilterHandler handler = new FilterHandler(noExtension, deploymentInfo.isAllowNonStandardWrappers(), targetServlet);
400 initialHandler = servletChain(handler, targetServlet.getManagedServlet(), noExtension, servletPath, deploymentInfo, defaultServlet, mappingMatch, pattern);
401 }
402 return initialHandler;
403 }
404
405 private static MatchData resolveServletForPath(final String path, final Map<String, ServletHandler> pathServlets, final Map<String, ServletHandler> extensionServlets, ServletHandler defaultServlet) {
406 if (pathServlets.containsKey(path)) {
407 if (path.endsWith("/*")) {
408 final String base = path.substring(0, path.length() - 2);
409 return new MatchData(pathServlets.get(path), base, path, MappingMatch.PATH, false);
410 } else {
411 if(path.equals("/")) {
412 return new MatchData(pathServlets.get(path), path, "", MappingMatch.CONTEXT_ROOT, false);
413 }
414 return new MatchData(pathServlets.get(path), path, path, MappingMatch.EXACT, false);
415 }
416 }
417 String match = null;
418 ServletHandler servlet = null;
419 String userPath = "";
420 for (final Map.Entry<String, ServletHandler> entry : pathServlets.entrySet()) {
421 String key = entry.getKey();
422 if (key.endsWith("/*")) {
423 final String base = key.substring(0, key.length() - 1);
424 if (match == null || base.length() > match.length()) {
425 if (path.startsWith(base) || path.equals(base.substring(0, base.length() - 1))) {
426 match = base.substring(0, base.length() - 1);
427 servlet = entry.getValue();
428 userPath = key;
429 }
430 }
431 }
432 }
433 if (servlet != null) {
434 return new MatchData(servlet, match, userPath, MappingMatch.PATH, false);
435 }
436 int index = path.lastIndexOf('.');
437 if (index != -1) {
438 String ext = path.substring(index + 1);
439 servlet = extensionServlets.get(ext);
440 if (servlet != null) {
441 return new MatchData(servlet, null, "*." + ext, MappingMatch.EXTENSION, false);
442 }
443 }
444
445 return new MatchData(defaultServlet, null, "/", MappingMatch.DEFAULT, true);
446 }
447
448 private static boolean isFilterApplicable(final String path, final String filterPath) {
449 String modifiedPath;
450 if (filterPath.equals("*")) {
451 modifiedPath = "/*";
452 } else {
453 modifiedPath = filterPath;
454 }
455 if (path.isEmpty()) {
456 return modifiedPath.equals("/*") || modifiedPath.equals("/");
457 }
458 if (modifiedPath.endsWith("/*")) {
459 String baseFilterPath = modifiedPath.substring(0, modifiedPath.length() - 1);
460 String exactFilterPath = modifiedPath.substring(0, modifiedPath.length() - 2);
461 return path.startsWith(baseFilterPath) || path.equals(exactFilterPath);
462 } else {
463 return modifiedPath.equals(path);
464 }
465 }
466
467 private static <K, V> void addToListMap(final Map<K, List<V>> map, final K key, final V value) {
468 List<V> list = map.get(key);
469 if (list == null) {
470 map.put(key, list = new ArrayList<>());
471 }
472 list.add(value);
473 }
474
475 private static ServletChain servletChain(HttpHandler next, final ManagedServlet managedServlet, Map<DispatcherType, List<ManagedFilter>> filters, final String servletPath, final DeploymentInfo deploymentInfo, boolean defaultServlet, MappingMatch mappingMatch, String pattern) {
476 HttpHandler servletHandler = next;
477 if(!deploymentInfo.isSecurityDisabled()) {
478 servletHandler = new ServletSecurityRoleHandler(servletHandler, deploymentInfo.getAuthorizationManager());
479 }
480 servletHandler = wrapHandlers(servletHandler, managedServlet.getServletInfo().getHandlerChainWrappers());
481 return new ServletChain(servletHandler, managedServlet, servletPath, defaultServlet, mappingMatch, pattern, filters);
482 }
483
484 private static HttpHandler wrapHandlers(final HttpHandler wrapee, final List<HandlerWrapper> wrappers) {
485 HttpHandler current = wrapee;
486 for (HandlerWrapper wrapper : wrappers) {
487 current = wrapper.wrap(current);
488 }
489 return current;
490 }
491
492 private static class MatchData {
493 final ServletHandler handler;
494 final String matchedPath;
495 final String userPath;
496 final MappingMatch mappingMatch;
497 final boolean defaultServlet;
498
499 private MatchData(final ServletHandler handler, final String matchedPath, String userPath, MappingMatch mappingMatch, boolean defaultServlet) {
500 this.handler = handler;
501 this.matchedPath = matchedPath;
502 this.userPath = userPath;
503 this.mappingMatch = mappingMatch;
504 this.defaultServlet = defaultServlet;
505 }
506 }
507 }
508