Class | Line # | Actions | |||||
---|---|---|---|---|---|---|---|
SavedRequestRestorerFilter | 67 | 20 | 0% | 11 | 14 | ||
SavedRequestRestorerFilter.SavedRequestWrapper | 86 | 17 | 0% | 11 | 10 |
1 | /* | |
2 | * See the NOTICE file distributed with this work for additional | |
3 | * information regarding copyright ownership. | |
4 | * | |
5 | * This is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU Lesser General Public License as | |
7 | * published by the Free Software Foundation; either version 2.1 of | |
8 | * the License, or (at your option) any later version. | |
9 | * | |
10 | * This software is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | * Lesser General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU Lesser General Public | |
16 | * License along with this software; if not, write to the Free | |
17 | * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | |
18 | * 02110-1301 USA, or see the FSF site: http://www.fsf.org. | |
19 | */ | |
20 | package org.xwiki.container.servlet.filters.internal; | |
21 | ||
22 | import java.io.IOException; | |
23 | import java.util.Collections; | |
24 | import java.util.Enumeration; | |
25 | import java.util.HashMap; | |
26 | import java.util.Map; | |
27 | import java.util.regex.Matcher; | |
28 | import java.util.regex.Pattern; | |
29 | ||
30 | import javax.servlet.Filter; | |
31 | import javax.servlet.FilterChain; | |
32 | import javax.servlet.FilterConfig; | |
33 | import javax.servlet.ServletException; | |
34 | import javax.servlet.ServletRequest; | |
35 | import javax.servlet.ServletResponse; | |
36 | import javax.servlet.http.HttpServletRequest; | |
37 | import javax.servlet.http.HttpServletRequestWrapper; | |
38 | import javax.servlet.http.HttpSession; | |
39 | ||
40 | import org.apache.commons.lang3.StringUtils; | |
41 | import org.xwiki.container.servlet.filters.SavedRequestManager; | |
42 | import org.xwiki.container.servlet.filters.SavedRequestManager.SavedRequest; | |
43 | ||
44 | /** | |
45 | * <p> | |
46 | * A filter that allows requests to be saved and reused later. For example when the current request contains an expired | |
47 | * authentication token, and the authorization module redirects to the login page, all the information sent by the | |
48 | * client would be lost; this filter allows to save all that information, and after a successful login, injects the | |
49 | * saved data in the new request. | |
50 | * </p> | |
51 | * <p> | |
52 | * The saved data is used as a fallback for the new request, meaning that a parameter value is first searched in the new | |
53 | * request, and only if not found it is searched in the saved request. Only the request parameters are stored, along | |
54 | * with the request URL needed to verify that the request is reused only in a compatible future request. Multiple | |
55 | * requests can be stored, each one identified by a distinct ID. A request is restored only if a valid ID was provided | |
56 | * in the new URL, and if the new URL matches the URL of the saved request (except the query string). A saved session is | |
57 | * deleted after it is restored, so it cannot be reused more than once. | |
58 | * </p> | |
59 | * <p> | |
60 | * Request data is stored in the current HTTP session, in order to provide a safe temporary storage. The data is only as | |
61 | * safe as a session is, and it will not be available after the session is invalidated. Another consequence is that only | |
62 | * HTTP requests are saved. | |
63 | * </p> | |
64 | * | |
65 | * @version $Id: 497f09f8e284d2a251ea7326c89046f1d0bcf61f $ | |
66 | */ | |
67 | public class SavedRequestRestorerFilter implements Filter | |
68 | { | |
69 | /** | |
70 | * Regular expression used for extracting the SRID from the query string. See | |
71 | * {@link #getSavedRequest(HttpServletRequest)}. | |
72 | */ | |
73 | private static final Pattern SAVED_REQUEST_REGEXP = | |
74 | Pattern.compile("(?:^|&)" + SavedRequestManager.getSavedRequestIdentifier() + "=([^&]++)"); | |
75 | ||
76 | /** | |
77 | * The name of the request attribute that specifies if this filter has already been applied to the current request. | |
78 | * This flag is required to prevent prevent processing the same request multiple times. The value of this request | |
79 | * attribute is a string. The associated boolean value is determined using {@link Boolean#valueOf(String)}. | |
80 | */ | |
81 | private static final String ATTRIBUTE_APPLIED = SavedRequestRestorerFilter.class.getName() + ".applied"; | |
82 | ||
83 | /** | |
84 | * Request Wrapper that inserts data from a previous request into the current request. | |
85 | */ | |
86 | public static class SavedRequestWrapper extends HttpServletRequestWrapper | |
87 | { | |
88 | /** The saved request data; may be <code>null</code>, in which case no fallback data is used. */ | |
89 | private SavedRequest savedRequest; | |
90 | ||
91 | /** | |
92 | * Simple constructor that forwards the initialization to the default {@link HttpServletRequestWrapper}. No | |
93 | * saved data is used. | |
94 | * | |
95 | * @param request the new request, the primary object wrapped which contains the actual request data. | |
96 | */ | |
97 | 0 | ![]() |
98 | { | |
99 | 0 | super(request); |
100 | } | |
101 | ||
102 | /** | |
103 | * Constructor that forwards the new request to the {@link HttpServletRequestWrapper}, and stores the saved | |
104 | * request data internally. | |
105 | * | |
106 | * @param newRequest the new request, the primary object wrapped which contains the actual request data. | |
107 | * @param savedRequest the old request, the secondary object wrapped which contains the saved (fallback) request | |
108 | * parameters. | |
109 | */ | |
110 | 12400 | ![]() |
111 | { | |
112 | 12399 | super(newRequest); |
113 | 12391 | this.savedRequest = savedRequest; |
114 | } | |
115 | ||
116 | /** | |
117 | * Retrieves the value for the parameter, either from the new request, or from the saved data. | |
118 | * | |
119 | * @param name the name of the parameter | |
120 | * @return a <code>String</code> representing the first value of the parameter, or <code>null</code> if no value | |
121 | * was set in either of the requests. | |
122 | * @see javax.servlet.ServletRequest#getParameter(java.lang.String) | |
123 | */ | |
124 | 203352 | ![]() |
125 | public String getParameter(String name) | |
126 | { | |
127 | 203345 | String value = super.getParameter(name); |
128 | 203333 | if (value == null && this.savedRequest != null) { |
129 | 0 | value = this.savedRequest.getParameter(name); |
130 | } | |
131 | 203336 | return value; |
132 | } | |
133 | ||
134 | /** | |
135 | * Retrieves all the values for the parameter, either from the new request, or from the saved data (but not | |
136 | * combined). | |
137 | * | |
138 | * @param name the name of the parameter | |
139 | * @return an array of <code>String</code> objects containing the parameter's values, or <code>null</code> if no | |
140 | * value was set in either of the requests. | |
141 | * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) | |
142 | */ | |
143 | 15011 | ![]() |
144 | public String[] getParameterValues(String name) | |
145 | { | |
146 | 15028 | String[] values = super.getParameterValues(name); |
147 | 15048 | if (values == null && this.savedRequest != null) { |
148 | 0 | values = this.savedRequest.getParameterValues(name); |
149 | } | |
150 | 15047 | return values; |
151 | } | |
152 | ||
153 | /** | |
154 | * Retrieves the combined map of parameter names - values, with the new values overriding the old ones. | |
155 | * | |
156 | * @return an immutable Map containing parameter names as keys and parameter values as map values | |
157 | * @see javax.servlet.ServletRequest#getParameterMap() | |
158 | */ | |
159 | 11168 | ![]() |
160 | @Override | |
161 | public Map<String, String[]> getParameterMap() | |
162 | { | |
163 | 11198 | if (this.savedRequest == null) { |
164 | 11195 | return super.getParameterMap(); |
165 | } else { | |
166 | // First put the saved (old) request data in the map, so that the new data overrides it. | |
167 | 0 | Map<String, String[]> map = new HashMap<String, String[]>(this.savedRequest.getParameterMap()); |
168 | 0 | map.putAll(super.getParameterMap()); |
169 | 0 | return Collections.unmodifiableMap(map); |
170 | } | |
171 | } | |
172 | ||
173 | /** | |
174 | * Retrieves the combined list of parameter names, from both the new and saved requests. | |
175 | * | |
176 | * @return an <code>Enumeration</code> of <code>String</code> objects, each <code>String</code> containing the | |
177 | * name of a request parameter; or an empty <code>Enumeration</code> if the request has no parameters | |
178 | * @see javax.servlet.ServletRequest#getParameterNames() | |
179 | */ | |
180 | 9933 | ![]() |
181 | public Enumeration<String> getParameterNames() | |
182 | { | |
183 | 9948 | return Collections.enumeration(getParameterMap().keySet()); |
184 | } | |
185 | } | |
186 | ||
187 | 32 | ![]() |
188 | public void init(FilterConfig filterConfig) | |
189 | { | |
190 | // Don't do anything, as this filter does not need any resources. | |
191 | } | |
192 | ||
193 | 12583 | ![]() |
194 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, | |
195 | ServletException | |
196 | { | |
197 | 12582 | ServletRequest filteredRequest = request; |
198 | // This filter works only for HTTP requests, because they are the only ones with a session. | |
199 | 12541 | if (request instanceof HttpServletRequest |
200 | && !Boolean.valueOf((String) request.getAttribute(ATTRIBUTE_APPLIED))) { | |
201 | // Get the saved request, if any (returns null if not applicable) | |
202 | 12343 | SavedRequest savedRequest = getSavedRequest((HttpServletRequest) request); |
203 | // Merge the new and the saved request | |
204 | 12372 | filteredRequest = new SavedRequestWrapper((HttpServletRequest) request, savedRequest); |
205 | 12366 | filteredRequest.setAttribute(ATTRIBUTE_APPLIED, "true"); |
206 | } | |
207 | // Forward the request | |
208 | 12602 | chain.doFilter(filteredRequest, response); |
209 | // Allow multiple calls to this filter as long as they are not nested. | |
210 | 12607 | filteredRequest.removeAttribute(ATTRIBUTE_APPLIED); |
211 | } | |
212 | ||
213 | 32 | ![]() |
214 | public void destroy() | |
215 | { | |
216 | // Don't do anything, as this filter does not use any resources. | |
217 | } | |
218 | ||
219 | /** | |
220 | * If this request specifies a saved request (using the srid paramter) and the URL matches the one of the saved | |
221 | * request, return the SavedRequest and remove it from the session. | |
222 | * | |
223 | * @param request the current request | |
224 | * @return the saved request, if one exists, or <code>null</code>. | |
225 | */ | |
226 | 12285 | ![]() |
227 | protected SavedRequest getSavedRequest(HttpServletRequest request) | |
228 | { | |
229 | // Only do something if the new request contains a Saved Request IDentifier (srid) | |
230 | 12319 | String savedRequestId = null; |
231 | // Using request.getParameter is not good, since in some containers it prevents using request.getInputStream | |
232 | // and/or request.getReader. A workaround is to manually extract the srid parameter from the query string, but | |
233 | // this means that: | |
234 | // - the srid cannot be used in POST requests, but in all current use cases GET is used anyway; | |
235 | // - the regular expression used for this is pretty basic, so there might be some URLs that fail to be | |
236 | // recognized; so far this wasn't observed. | |
237 | 12359 | Matcher m = SAVED_REQUEST_REGEXP.matcher(StringUtils.defaultString(request.getQueryString())); |
238 | 12373 | if (m.find()) { |
239 | 0 | savedRequestId = m.group(1); |
240 | } | |
241 | ||
242 | 12366 | if (!StringUtils.isEmpty(savedRequestId)) { |
243 | // Saved requests are stored in the request session | |
244 | 0 | HttpSession session = request.getSession(); |
245 | // Get the SavedRequest from the session | |
246 | 0 | Map<String, SavedRequest> savedRequests = |
247 | (Map<String, SavedRequest>) session.getAttribute(SavedRequestManager.getSavedRequestKey()); | |
248 | 0 | if (savedRequests != null) { |
249 | 0 | SavedRequest savedRequest = savedRequests.get(savedRequestId); |
250 | // Only reuse this request if the new request is for the same resource (URL) | |
251 | 0 | if (savedRequest != null |
252 | && StringUtils.equals(savedRequest.getRequestUrl(), request.getRequestURL().toString())) { | |
253 | // Remove the saved request from the session | |
254 | 0 | savedRequests.remove(savedRequestId); |
255 | // Return the SavedRequest | |
256 | 0 | return savedRequest; |
257 | } | |
258 | } | |
259 | } | |
260 | 12366 | return null; |
261 | } | |
262 | } |