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.csp;
018
019import java.io.IOException;
020import java.io.StringWriter;
021
022import javax.servlet.http.HttpServletRequest;
023
024import org.apache.wicket.WicketRuntimeException;
025import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
026import org.apache.wicket.request.IRequestCycle;
027import org.apache.wicket.request.IRequestHandler;
028import org.apache.wicket.request.IRequestMapper;
029import org.apache.wicket.request.Request;
030import org.apache.wicket.request.Url;
031import org.apache.wicket.request.mapper.AbstractMapper;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * A simple {@link IRequestMapper} that logs the content of a CSP violation.
037 * 
038 * @author papegaaij
039 * @see CSPHeaderConfiguration#reportBack()
040 */
041public class ReportCSPViolationMapper extends AbstractMapper
042{
043        private static final int MAX_LOG_SIZE = 4 * 1024;
044
045        private static final Logger log = LoggerFactory.getLogger(ReportCSPViolationMapper.class);
046
047        private final ContentSecurityPolicySettings settings;
048
049        public ReportCSPViolationMapper(ContentSecurityPolicySettings settings)
050        {
051                this.settings = settings;
052        }
053
054        @Override
055        public IRequestHandler mapRequest(Request request)
056        {
057                if (requestMatches(request))
058                {
059                        return new IRequestHandler()
060                        {
061                                @Override
062                                public void respond(IRequestCycle requestCycle)
063                                {
064                                        try
065                                        {
066                                                HttpServletRequest httpRequest =
067                                                        ((ServletWebRequest) requestCycle.getRequest()).getContainerRequest();
068                                                if (log.isErrorEnabled())
069                                                {
070                                                        log.error(reportToString(httpRequest));
071                                                }
072                                        }
073                                        catch (IOException e)
074                                        {
075                                                throw new WicketRuntimeException(e);
076                                        }
077                                }
078
079                                private String reportToString(HttpServletRequest httpRequest) throws IOException
080                                {
081                                        try (StringWriter sw = new StringWriter())
082                                        {
083                                                char[] buffer = new char[MAX_LOG_SIZE];
084                                                int n;
085                                                if (-1 != (n = httpRequest.getReader().read(buffer)))
086                                                {
087                                                        sw.write(buffer, 0, n);
088                                                }
089                                                return sw.toString();
090                                        }
091                                }
092                        };
093                }
094                return null;
095        }
096
097        @Override
098        public int getCompatibilityScore(Request request)
099        {
100                return requestMatches(request) ? 1000 : 0;
101        }
102
103        private boolean requestMatches(Request request)
104        {
105                if (request instanceof ServletWebRequest)
106                {
107                        if (!((ServletWebRequest) request).getContainerRequest().getMethod().equals("POST"))
108                        {
109                                return false;
110                        }
111                        for (CSPHeaderConfiguration curConfig : settings.getConfiguration().values())
112                        {
113                                String mountPath = curConfig.getReportUriMountPath();
114                                if (mountPath != null
115                                        && urlStartsWith(request.getUrl(), getMountSegments(mountPath)))
116                                {
117                                        return true;
118                                }
119                        }
120                }
121                return false;
122        }
123
124        @Override
125        public Url mapHandler(IRequestHandler requestHandler)
126        {
127                return null;
128        }
129}