| /* |
| * Copyright 2011 gitblit.com. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.gitblit.wicket; |
| |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.wicket.IRequestTarget; |
| import org.apache.wicket.Page; |
| import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy; |
| import org.apache.wicket.request.RequestParameters; |
| import org.apache.wicket.request.target.coding.MixedParamUrlCodingStrategy; |
| import org.apache.wicket.util.string.AppendingStringBuffer; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.gitblit.IStoredSettings; |
| import com.gitblit.Keys; |
| import com.gitblit.utils.XssFilter; |
| |
| /** |
| * Simple subclass of mixed parameter url coding strategy that works around the |
| * encoded forward-slash issue that is present in some servlet containers. |
| * |
| * https://issues.apache.org/jira/browse/WICKET-1303 |
| * http://tomcat.apache.org/security-6.html |
| * |
| * @author James Moger |
| * |
| */ |
| public class GitblitParamUrlCodingStrategy extends MixedParamUrlCodingStrategy { |
| |
| private final String[] parameterNames; |
| |
| private Logger logger = LoggerFactory.getLogger(GitblitParamUrlCodingStrategy.class); |
| |
| private IStoredSettings settings; |
| |
| private XssFilter xssFilter; |
| |
| /** |
| * Construct. |
| * |
| * @param <C> |
| * @param mountPath |
| * mount path (not empty) |
| * @param bookmarkablePageClass |
| * class of mounted page (not null) |
| * @param parameterNames |
| * the parameter names (not null) |
| */ |
| public <C extends Page> GitblitParamUrlCodingStrategy( |
| IStoredSettings settings, |
| XssFilter xssFilter, |
| String mountPath, |
| Class<C> bookmarkablePageClass, String[] parameterNames) { |
| |
| super(mountPath, bookmarkablePageClass, parameterNames); |
| this.parameterNames = parameterNames; |
| this.settings = settings; |
| this.xssFilter = xssFilter; |
| } |
| |
| /** |
| * Url encodes a string that is mean for a URL path (e.g., between slashes) |
| * |
| * @param string |
| * string to be encoded |
| * @return encoded string |
| */ |
| @Override |
| protected String urlEncodePathComponent(String string) { |
| char altChar = settings.getChar(Keys.web.forwardSlashCharacter, '/'); |
| if (altChar != '/') { |
| string = string.replace('/', altChar); |
| } |
| return super.urlEncodePathComponent(string); |
| } |
| |
| /** |
| * Returns a decoded value of the given value (taken from a URL path |
| * section) |
| * |
| * @param value |
| * @return Decodes the value |
| */ |
| @Override |
| protected String urlDecodePathComponent(String value) { |
| char altChar = settings.getChar(Keys.web.forwardSlashCharacter, '/'); |
| if (altChar != '/') { |
| value = value.replace(altChar, '/'); |
| } |
| return super.urlDecodePathComponent(value); |
| } |
| |
| /** |
| * Gets the decoded request target. |
| * |
| * @param requestParameters |
| * the request parameters |
| * @return the decoded request target |
| */ |
| @Override |
| public IRequestTarget decode(RequestParameters requestParameters) { |
| Map<String, Object> parameterMap = (Map<String, Object>) requestParameters.getParameters(); |
| for (Map.Entry<String, Object> entry : parameterMap.entrySet()) { |
| String parameter = entry.getKey(); |
| if (parameter.startsWith(WebRequestCodingStrategy.NAME_SPACE)) { |
| // ignore Wicket parameters |
| continue; |
| } |
| |
| // sanitize Gitblit request parameters |
| Object o = entry.getValue(); |
| if (o instanceof String) { |
| String value = o.toString(); |
| String safeValue = xssFilter.none(value); |
| if (!value.equals(safeValue)) { |
| logger.warn("XSS filter triggered on {} URL parameter: {}={}", |
| getMountPath(), parameter, value); |
| parameterMap.put(parameter, safeValue); |
| } |
| } else if (o instanceof String[]) { |
| String[] values = (String[]) o; |
| for (int i = 0; i < values.length; i++) { |
| String value = values[i].toString(); |
| String safeValue = xssFilter.none(value); |
| if (!value.equals(safeValue)) { |
| logger.warn("XSS filter triggered on {} URL parameter: {}={}", |
| getMountPath(), parameter, value); |
| values[i] = safeValue; |
| } |
| } |
| } |
| } |
| |
| return super.decode(requestParameters); |
| } |
| |
| /** |
| * @see org.apache.wicket.request.target.coding.AbstractRequestTargetUrlCodingStrategy#appendParameters(org.apache.wicket.util.string.AppendingStringBuffer, |
| * java.util.Map) |
| */ |
| @Override |
| protected void appendParameters(AppendingStringBuffer url, Map<String, ?> parameters) |
| { |
| if (!url.endsWith("/")) |
| { |
| url.append("/"); |
| } |
| |
| Set<String> parameterNamesToAdd = new HashSet<String>(parameters.keySet()); |
| |
| // Find index of last specified parameter |
| boolean foundParameter = false; |
| int lastSpecifiedParameter = parameterNames.length; |
| while (lastSpecifiedParameter != 0 && !foundParameter) |
| { |
| foundParameter = parameters.containsKey(parameterNames[--lastSpecifiedParameter]); |
| } |
| |
| if (foundParameter) |
| { |
| for (int i = 0; i <= lastSpecifiedParameter; i++) |
| { |
| String parameterName = parameterNames[i]; |
| final Object param = parameters.get(parameterName); |
| String value = param instanceof String[] ? ((String[])param)[0] : ((param == null) |
| ? null : param.toString()); |
| if (value == null) |
| { |
| value = ""; |
| } |
| if (!url.endsWith("/")) |
| { |
| url.append("/"); |
| } |
| url.append(urlEncodePathComponent(value)); |
| parameterNamesToAdd.remove(parameterName); |
| } |
| } |
| |
| if (!parameterNamesToAdd.isEmpty()) |
| { |
| boolean first = true; |
| for (String parameterName : parameterNamesToAdd) |
| { |
| final Object param = parameters.get(parameterName); |
| if (param instanceof String[]) { |
| String [] values = (String[]) param; |
| for (String value : values) { |
| url.append(first ? '?' : '&'); |
| url.append(urlEncodeQueryComponent(parameterName)).append("=").append( |
| urlEncodeQueryComponent(value)); |
| first = false; |
| } |
| } else { |
| url.append(first ? '?' : '&'); |
| String value = String.valueOf(param); |
| url.append(urlEncodeQueryComponent(parameterName)).append("=").append( |
| urlEncodeQueryComponent(value)); |
| } |
| first = false; |
| } |
| } |
| } |
| } |