1 /*
2  * Copyright (C) 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.inject.servlet;
17 
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20 import java.util.regex.PatternSyntaxException;
21 
22 /**
23  * An enumeration of the available URI-pattern matching styles
24  *
25  * @since 3.0
26  */
27 public enum UriPatternType {
28   SERVLET,
29   REGEX;
30 
get(UriPatternType type, String pattern)31   static UriPatternMatcher get(UriPatternType type, String pattern) {
32     switch (type) {
33       case SERVLET:
34         return new ServletStyleUriPatternMatcher(pattern);
35       case REGEX:
36         return new RegexUriPatternMatcher(pattern);
37       default:
38         return null;
39     }
40   }
41 
getUri(String uri)42   private static String getUri(String uri) {
43     // Strip out the query, if it existed in the URI.  See issue 379.
44     int queryIdx = uri.indexOf('?');
45     if (queryIdx != -1) {
46       uri = uri.substring(0, queryIdx);
47     }
48     return uri;
49   }
50 
51   /**
52    * Matches URIs using the pattern grammar of the Servlet API and web.xml.
53    *
54    * @author dhanji@gmail.com (Dhanji R. Prasanna)
55    */
56   private static class ServletStyleUriPatternMatcher implements UriPatternMatcher {
57     private final String literal;
58     private final String originalPattern;
59     private final Kind patternKind;
60 
61     private static enum Kind {
62       PREFIX,
63       SUFFIX,
64       LITERAL,
65     }
66 
ServletStyleUriPatternMatcher(String pattern)67     public ServletStyleUriPatternMatcher(String pattern) {
68       this.originalPattern = pattern;
69       if (pattern.startsWith("*")) {
70         this.literal = pattern.substring(1);
71         this.patternKind = Kind.PREFIX;
72       } else if (pattern.endsWith("*")) {
73         this.literal = pattern.substring(0, pattern.length() - 1);
74         this.patternKind = Kind.SUFFIX;
75       } else {
76         this.literal = pattern;
77         this.patternKind = Kind.LITERAL;
78       }
79       String normalized = ServletUtils.normalizePath(literal);
80       if (patternKind == Kind.PREFIX) {
81         normalized = "*" + normalized;
82       } else if (patternKind == Kind.SUFFIX) {
83         normalized = normalized + "*";
84       }
85       if (!pattern.equals(normalized)) {
86         throw new IllegalArgumentException(
87             "Servlet patterns cannot contain escape patterns. Registered pattern: '"
88                 + pattern
89                 + "' normalizes to: '"
90                 + normalized
91                 + "'");
92       }
93     }
94 
95     @Override
matches(String uri)96     public boolean matches(String uri) {
97       if (null == uri) {
98         return false;
99       }
100 
101       uri = getUri(uri);
102       if (patternKind == Kind.PREFIX) {
103         return uri.endsWith(literal);
104       } else if (patternKind == Kind.SUFFIX) {
105         return uri.startsWith(literal);
106       }
107 
108       //else we need a complete match
109       return literal.equals(uri);
110     }
111 
112     @Override
extractPath(String path)113     public String extractPath(String path) {
114       if (patternKind == Kind.PREFIX) {
115         return null;
116       } else if (patternKind == Kind.SUFFIX) {
117         String extract = literal;
118 
119         //trim the trailing '/'
120         if (extract.endsWith("/")) {
121           extract = extract.substring(0, extract.length() - 1);
122         }
123 
124         return extract;
125       }
126 
127       //else treat as literal
128       return path;
129     }
130 
131     @Override
getPatternType()132     public UriPatternType getPatternType() {
133       return UriPatternType.SERVLET;
134     }
135 
136     @Override
getOriginalPattern()137     public String getOriginalPattern() {
138       return originalPattern;
139     }
140   }
141 
142   /**
143    * Matches URIs using a regular expression.
144    *
145    * @author dhanji@gmail.com (Dhanji R. Prasanna)
146    */
147   private static class RegexUriPatternMatcher implements UriPatternMatcher {
148     private final Pattern pattern;
149     private final String originalPattern;
150 
RegexUriPatternMatcher(String pattern)151     public RegexUriPatternMatcher(String pattern) {
152       this.originalPattern = pattern;
153       try {
154         this.pattern = Pattern.compile(pattern);
155       } catch (PatternSyntaxException pse) {
156         throw new IllegalArgumentException("Invalid regex pattern: " + pse.getMessage());
157       }
158     }
159 
160     @Override
matches(String uri)161     public boolean matches(String uri) {
162       return null != uri && this.pattern.matcher(getUri(uri)).matches();
163     }
164 
165     @Override
extractPath(String path)166     public String extractPath(String path) {
167       Matcher matcher = pattern.matcher(path);
168       if (matcher.matches() && matcher.groupCount() >= 1) {
169 
170         // Try to capture the everything before the regex begins to match
171         // the path. This is a rough approximation to try and get parity
172         // with the servlet style mapping where the path is a capture of
173         // the URI before the wildcard.
174         int end = matcher.start(1);
175         if (end < path.length()) {
176           return path.substring(0, end);
177         }
178       }
179       return null;
180     }
181 
182     @Override
getPatternType()183     public UriPatternType getPatternType() {
184       return UriPatternType.REGEX;
185     }
186 
187     @Override
getOriginalPattern()188     public String getOriginalPattern() {
189       return originalPattern;
190     }
191   }
192 }
193