/* * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you 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 the following location: * * 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 org.jasig.cas.authentication.principal; import java.net.URLEncoder; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Encapsulates a Response to send back for a particular service. * * @author Scott Battaglia * @author Arnaud Lesueur * @since 3.1 */ public final class Response { /** Pattern to detect unprintable ASCII characters. */ private static final Pattern NON_PRINTABLE = Pattern.compile("[\\x00-\\x1F\\x7F]+"); /** Log instance. */ protected static final Logger LOGGER = LoggerFactory.getLogger(Response.class); /** An enumeration of different response types. **/ public static enum ResponseType { POST, REDIRECT } private final ResponseType responseType; private final String url; private final Map<String, String> attributes; protected Response(final ResponseType responseType, final String url, final Map<String, String> attributes) { this.responseType = responseType; this.url = url; this.attributes = attributes; } public static Response getPostResponse(final String url, final Map<String, String> attributes) { return new Response(ResponseType.POST, url, attributes); } public static Response getRedirectResponse(final String url, final Map<String, String> parameters) { final StringBuilder builder = new StringBuilder(parameters.size() * 40 + 100); boolean isFirst = true; final String[] fragmentSplit = sanitizeUrl(url).split("#"); builder.append(fragmentSplit[0]); for (final Map.Entry<String, String> entry : parameters.entrySet()) { if (entry.getValue() != null) { if (isFirst) { builder.append(url.contains("?") ? "&" : "?"); isFirst = false; } else { builder.append("&"); } builder.append(entry.getKey()); builder.append("="); try { builder.append(URLEncoder.encode(entry.getValue(), "UTF-8")); } catch (final Exception e) { builder.append(entry.getValue()); } } } if (fragmentSplit.length > 1) { builder.append("#"); builder.append(fragmentSplit[1]); } return new Response(ResponseType.REDIRECT, builder.toString(), parameters); } public Map<String, String> getAttributes() { return this.attributes; } public ResponseType getResponseType() { return this.responseType; } public String getUrl() { return this.url; } /** * Sanitize a URL provided by a relying party by normalizing non-printable * ASCII character sequences into spaces. This functionality protects * against CRLF attacks and other similar attacks using invisible characters * that could be abused to trick user agents. * * @param url URL to sanitize. * * @return Sanitized URL string. */ private static String sanitizeUrl(final String url) { final Matcher m = NON_PRINTABLE.matcher(url); final StringBuffer sb = new StringBuffer(url.length()); boolean hasNonPrintable = false; while (m.find()) { m.appendReplacement(sb, " "); hasNonPrintable = true; } m.appendTail(sb); if (hasNonPrintable) { LOGGER.warn("The following redirect URL has been sanitized and may be sign of attack:\n{}", url); } return sb.toString(); } }