001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.protocol.http.servlet; 018 019import java.nio.charset.Charset; 020import java.time.Instant; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.Enumeration; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029import java.util.Set; 030import javax.servlet.http.Cookie; 031import javax.servlet.http.HttpServletRequest; 032import javax.servlet.http.HttpServletResponse; 033import org.apache.commons.fileupload.FileItemFactory; 034import org.apache.commons.fileupload.FileUploadException; 035import org.apache.wicket.protocol.http.RequestUtils; 036import org.apache.wicket.request.IRequestParameters; 037import org.apache.wicket.request.IWritableRequestParameters; 038import org.apache.wicket.request.Url; 039import org.apache.wicket.request.UrlUtils; 040import org.apache.wicket.request.http.WebRequest; 041import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeException; 042import org.apache.wicket.util.lang.Args; 043import org.apache.wicket.util.lang.Bytes; 044import org.apache.wicket.util.string.PrependingStringBuffer; 045import org.apache.wicket.util.string.StringValue; 046import org.apache.wicket.util.string.Strings; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050/** 051 * {@link WebRequest} subclass that wraps a {@link HttpServletRequest} object. 052 * 053 * @author Matej Knopp 054 * @author Juergen Donnerstag 055 * @author Igor Vaynberg 056 */ 057public class ServletWebRequest extends WebRequest 058{ 059 private static final Logger LOG = LoggerFactory.getLogger(ServletWebRequest.class); 060 061 private final HttpServletRequest httpServletRequest; 062 063 private final Url url; 064 065 private final String filterPrefix; 066 067 private final ErrorAttributes errorAttributes; 068 069 private final ForwardAttributes forwardAttributes; 070 071 /** 072 * Construct. 073 * 074 * @param httpServletRequest 075 * @param filterPrefix 076 * contentPath + filterPath, used to extract the actual {@link Url} 077 */ 078 public ServletWebRequest(HttpServletRequest httpServletRequest, String filterPrefix) 079 { 080 this(httpServletRequest, filterPrefix, null); 081 } 082 083 /** 084 * Construct. 085 * 086 * @param httpServletRequest 087 * @param filterPrefix 088 * contentPath + filterPath, used to extract the actual {@link Url} 089 * @param url 090 */ 091 public ServletWebRequest(HttpServletRequest httpServletRequest, String filterPrefix, Url url) 092 { 093 Args.notNull(httpServletRequest, "httpServletRequest"); 094 Args.notNull(filterPrefix, "filterPrefix"); 095 096 this.httpServletRequest = httpServletRequest; 097 098 errorAttributes = ErrorAttributes.of(httpServletRequest, filterPrefix); 099 100 forwardAttributes = ForwardAttributes.of(httpServletRequest, filterPrefix); 101 102 this.filterPrefix = filterPrefix; 103 104 if (url != null) 105 { 106 this.url = url; 107 } 108 else 109 { 110 this.url = getContextRelativeUrl(httpServletRequest.getRequestURI(), filterPrefix); 111 } 112 } 113 114 /** 115 * Returns base url without context or filter mapping. 116 * <p> 117 * Example: if current url is 118 * 119 * <pre> 120 * http://localhost:8080/context/filter/mapping/wicket/bookmarkable/com.foo.Page?1&id=2 121 * </pre> 122 * 123 * the base url is <em>wicket/bookmarkable/com.foo.Page</em> 124 * </p> 125 * 126 * @see org.apache.wicket.request.Request#getClientUrl() 127 */ 128 @Override 129 public Url getClientUrl() 130 { 131 if (errorAttributes != null && !Strings.isEmpty(errorAttributes.getRequestUri())) 132 { 133 String problematicURI = Url.parse(errorAttributes.getRequestUri(), getCharset(), false) 134 .toString(); 135 return getContextRelativeUrl(problematicURI, filterPrefix); 136 } 137 else if (forwardAttributes != null && !Strings.isEmpty(forwardAttributes.getRequestUri())) 138 { 139 String forwardURI = Url.parse(forwardAttributes.getRequestUri(), getCharset(), false) 140 .toString(); 141 return getContextRelativeUrl(forwardURI, filterPrefix); 142 } 143 else if (!isAjax()) 144 { 145 return getContextRelativeUrl(httpServletRequest.getRequestURI(), filterPrefix); 146 } 147 else 148 { 149 String base = getHeader(HEADER_AJAX_BASE_URL); 150 151 if (base == null) 152 { 153 base = getRequestParameters().getParameterValue(PARAM_AJAX_BASE_URL).toString(null); 154 } 155 156 if (base == null) 157 { 158 throw new AbortWithHttpErrorCodeException(HttpServletResponse.SC_BAD_REQUEST, 159 "Current ajax request is missing the base url header or parameter"); 160 } 161 162 return setParameters(Url.parse(base, getCharset())); 163 } 164 } 165 166 private Url setParameters(Url url) 167 { 168 url.setPort(httpServletRequest.getServerPort()); 169 url.setHost(httpServletRequest.getServerName()); 170 url.setProtocol(httpServletRequest.getScheme()); 171 url.setContextRelative(true); 172 return url; 173 } 174 175 private Url getContextRelativeUrl(String uri, String filterPrefix) 176 { 177 if (filterPrefix.length() > 0 && !filterPrefix.endsWith("/")) 178 { 179 filterPrefix += "/"; 180 } 181 StringBuilder url = new StringBuilder(); 182 uri = Strings.stripJSessionId(uri); 183 String contextPath = httpServletRequest.getContextPath(); 184 185 if (LOG.isDebugEnabled()) 186 { 187 LOG.debug("Calculating context relative path from: context path '{}', filterPrefix '{}', uri '{}'", 188 new Object[] {contextPath, filterPrefix, uri}); 189 } 190 191 final int start = contextPath.length() + filterPrefix.length() + 1; 192 if (uri.length() > start) 193 { 194 url.append(uri.substring(start)); 195 } 196 197 if (errorAttributes == null) 198 { 199 String query = httpServletRequest.getQueryString(); 200 if (!Strings.isEmpty(query)) 201 { 202 url.append('?'); 203 url.append(query); 204 } 205 } 206 207 return setParameters(Url.parse(url.toString(), getCharset(), false)); 208 } 209 210 /** 211 * Returns the prefix of Wicket filter (without the leading /) 212 * 213 * @return Wicket filter prefix 214 */ 215 public String getFilterPrefix() 216 { 217 return filterPrefix; 218 } 219 220 @Override 221 public List<Cookie> getCookies() 222 { 223 Cookie[] cookies = httpServletRequest.getCookies(); 224 List<Cookie> result = (cookies == null) ? Collections.<Cookie> emptyList() 225 : Arrays.asList(cookies); 226 return Collections.unmodifiableList(result); 227 } 228 229 230 @Override 231 public Locale getLocale() 232 { 233 return httpServletRequest.getLocale(); 234 } 235 236 @Override 237 public Instant getDateHeader(String name) 238 { 239 try 240 { 241 long value = httpServletRequest.getDateHeader(name); 242 243 if (value == -1) 244 { 245 return null; 246 } 247 248 return Instant.ofEpochMilli(value); 249 } 250 catch (IllegalArgumentException e) 251 { 252 // per spec thrown if the header contains a value that cannot be converted to a date 253 return null; 254 } 255 } 256 257 @Override 258 public String getHeader(String name) 259 { 260 return httpServletRequest.getHeader(name); 261 } 262 263 @SuppressWarnings("unchecked") 264 @Override 265 public List<String> getHeaders(String name) 266 { 267 List<String> result = new ArrayList<>(); 268 Enumeration<String> e = httpServletRequest.getHeaders(name); 269 while (e.hasMoreElements()) 270 { 271 result.add(e.nextElement()); 272 } 273 return Collections.unmodifiableList(result); 274 } 275 276 private Map<String, List<StringValue>> postParameters = null; 277 278 protected Map<String, List<StringValue>> generatePostParameters() 279 { 280 Map<String, List<StringValue>> postParameters = new HashMap<>(); 281 282 IRequestParameters queryParams = getQueryParameters(); 283 284 @SuppressWarnings("unchecked") 285 Map<String, String[]> params = getContainerRequest().getParameterMap(); 286 for (Map.Entry<String, String[]> param : params.entrySet()) 287 { 288 final String name = param.getKey(); 289 final String[] values = param.getValue(); 290 291 if (name != null && values != null) 292 { 293 // build a mutable list of query params that have the same name as the post param 294 List<StringValue> queryValues = queryParams.getParameterValues(name); 295 if (queryValues == null) 296 { 297 queryValues = Collections.emptyList(); 298 } 299 else 300 { 301 queryValues = new ArrayList<>(queryValues); 302 } 303 304 // the list that will contain accepted post param values 305 List<StringValue> postValues = new ArrayList<>(); 306 307 for (String value : values) 308 { 309 StringValue val = StringValue.valueOf(value); 310 if (queryValues.contains(val)) 311 { 312 // if a query param with this value exists remove it and continue 313 queryValues.remove(val); 314 } 315 else 316 { 317 // there is no query param with this value, assume post 318 postValues.add(val); 319 } 320 } 321 322 if (!postValues.isEmpty()) 323 { 324 postParameters.put(name, postValues); 325 } 326 } 327 } 328 return postParameters; 329 } 330 331 private Map<String, List<StringValue>> getPostRequestParameters() 332 { 333 if (postParameters == null) 334 { 335 postParameters = generatePostParameters(); 336 } 337 return postParameters; 338 } 339 340 private final IRequestParameters postRequestParameters = new IWritableRequestParameters() 341 { 342 @Override 343 public void reset() 344 { 345 getPostRequestParameters().clear(); 346 } 347 348 @Override 349 public void setParameterValues(String key, List<StringValue> values) 350 { 351 getPostRequestParameters().put(key, values); 352 } 353 354 @Override 355 public Set<String> getParameterNames() 356 { 357 return Collections.unmodifiableSet(getPostRequestParameters().keySet()); 358 } 359 360 @Override 361 public StringValue getParameterValue(String name) 362 { 363 List<StringValue> values = getPostRequestParameters().get(name); 364 if (values == null || values.isEmpty()) 365 { 366 return StringValue.valueOf((String)null); 367 } 368 else 369 { 370 return values.iterator().next(); 371 } 372 } 373 374 @Override 375 public List<StringValue> getParameterValues(String name) 376 { 377 List<StringValue> values = getPostRequestParameters().get(name); 378 if (values != null) 379 { 380 values = Collections.unmodifiableList(values); 381 } 382 return values; 383 } 384 }; 385 386 @Override 387 public IRequestParameters getPostParameters() 388 { 389 return postRequestParameters; 390 } 391 392 @Override 393 public Url getUrl() 394 { 395 return new Url(url); 396 } 397 398 @Override 399 public ServletWebRequest cloneWithUrl(Url url) 400 { 401 return new ServletWebRequest(httpServletRequest, filterPrefix, url) 402 { 403 @Override 404 public Url getOriginalUrl() 405 { 406 return ServletWebRequest.this.getOriginalUrl(); 407 } 408 409 @Override 410 public IRequestParameters getPostParameters() 411 { 412 // don't parse post parameters again 413 return ServletWebRequest.this.getPostParameters(); 414 } 415 }; 416 } 417 418 /** 419 * Creates multipart web request from this request. 420 * 421 * @param maxSize 422 * max allowed size of request 423 * @param upload 424 * upload identifier for {@link UploadInfo} 425 * @return multipart request 426 * @throws FileUploadException 427 */ 428 public MultipartServletWebRequest newMultipartWebRequest(Bytes maxSize, String upload) 429 throws FileUploadException 430 { 431 return new MultipartServletWebRequestImpl(getContainerRequest(), filterPrefix, maxSize, upload); 432 } 433 434 /** 435 * Creates multipart web request from this request. 436 * 437 * @param maxSize 438 * max allowed size of request 439 * @param upload 440 * upload identifier for {@link UploadInfo} 441 * @param factory 442 * @return multipart request 443 * @throws FileUploadException 444 */ 445 public MultipartServletWebRequest newMultipartWebRequest(Bytes maxSize, String upload, 446 FileItemFactory factory) throws FileUploadException 447 { 448 return new MultipartServletWebRequestImpl(getContainerRequest(), filterPrefix, maxSize, upload, factory); 449 } 450 451 @Override 452 public String getPrefixToContextPath() 453 { 454 PrependingStringBuffer buffer = new PrependingStringBuffer(); 455 Url filterPrefixUrl = Url.parse(filterPrefix, getCharset()); 456 for (int i = 0; i < filterPrefixUrl.getSegments().size() - 1; ++i) 457 { 458 buffer.prepend("../"); 459 } 460 return buffer.toString(); 461 } 462 463 @Override 464 public Charset getCharset() 465 { 466 return RequestUtils.getCharset(httpServletRequest); 467 } 468 469 @Override 470 public HttpServletRequest getContainerRequest() 471 { 472 return httpServletRequest; 473 } 474 475 @Override 476 public String getContextPath() 477 { 478 return UrlUtils.normalizePath(httpServletRequest.getContextPath()); 479 } 480 481 @Override 482 public String getFilterPath() 483 { 484 return UrlUtils.normalizePath(filterPrefix); 485 } 486 487 @Override 488 public boolean shouldPreserveClientUrl() 489 { 490 return (errorAttributes != null && !Strings.isEmpty(errorAttributes.getRequestUri()) || forwardAttributes != null && 491 !Strings.isEmpty(forwardAttributes.getRequestUri())); 492 } 493}