001/*
002 * (C) Copyright 2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Kevin Leturc <kleturc@nuxeo.com>
018 */
019package org.nuxeo.ecm.webengine.app;
020
021import java.io.IOException;
022import java.util.Collections;
023import java.util.Enumeration;
024import java.util.Hashtable;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.NoSuchElementException;
028
029import javax.servlet.FilterChain;
030import javax.servlet.FilterConfig;
031import javax.servlet.ServletException;
032import javax.servlet.http.HttpServletRequest;
033import javax.servlet.http.HttpServletRequestWrapper;
034import javax.servlet.http.HttpServletResponse;
035import javax.ws.rs.core.HttpHeaders;
036
037import org.nuxeo.ecm.webengine.jaxrs.HttpFilter;
038
039/**
040 * This filter is used to fix the http headers sent to Nuxeo. Sometimes Nuxeo Drive send content type or mime type
041 * equals to 'pdf' instead of 'application/pdf', this filter is used to fix this value before giving it to appropriate
042 * webengine object.
043 *
044 * @since 9.2
045 */
046public class HeaderFixFilter extends HttpFilter {
047
048    protected final String MIME_TYPE = "X-File-Type";
049
050    @Override
051    protected void run(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
052            throws IOException, ServletException {
053        HttpServletRequest newRequest = request;
054
055        String ctype = request.getHeader(HttpHeaders.CONTENT_TYPE);
056        String mtype = request.getHeader(MIME_TYPE);
057        boolean patchCType = ctype == null || ctype.length() == 0 || !ctype.contains("/");
058        boolean patchMMType = mtype != null && !mtype.contains("/");
059        if (patchCType || patchMMType) {
060            newRequest = new DefaultContentTypeRequestWrapper(request, patchCType, patchMMType);
061        }
062        chain.doFilter(newRequest, response);
063    }
064
065    @Override
066    public void init(FilterConfig filterConfig) throws ServletException {
067
068    }
069
070    @Override
071    public void destroy() {
072
073    }
074
075    protected class DefaultContentTypeRequestWrapper extends HttpServletRequestWrapper {
076
077        protected final Hashtable<String, String[]> headers;
078
079        protected final String lCONTENT_TYPE = HttpHeaders.CONTENT_TYPE.toLowerCase();
080
081        protected final String lFILE_TYPE = MIME_TYPE.toLowerCase();
082
083        protected DefaultContentTypeRequestWrapper(HttpServletRequest request, boolean patchCType, boolean patchMType) {
084            super(request);
085            headers = patchHeaders(request, patchCType, patchMType);
086        }
087
088        protected Hashtable<String, String[]> patchHeaders(HttpServletRequest request, boolean patchCType,
089                boolean patchMType) {
090            Hashtable<String, String[]> headers = new Hashtable<>();
091            // collect headers from request
092            Enumeration<String> eachNames = request.getHeaderNames();
093            while (eachNames.hasMoreElements()) {
094                String name = eachNames.nextElement().toLowerCase();
095                List<String> values = new LinkedList<>();
096                Enumeration<String> eachValues = request.getHeaders(name);
097                while (eachValues.hasMoreElements()) {
098                    values.add(eachValues.nextElement());
099                }
100                headers.put(name, values.toArray(new String[values.size()]));
101            }
102            if (patchCType) {
103                // patch content type
104                String ctype = request.getContentType();
105                if (ctype == null || ctype.isEmpty()) {
106                    String[] ctypes = new String[] { "application/octet-stream" };
107                    headers.put(lCONTENT_TYPE, ctypes);
108                } else {
109                    patchContentTypes(headers.get(lCONTENT_TYPE));
110                }
111            }
112            if (patchMType) {
113                patchContentTypes(headers.get(lFILE_TYPE));
114            }
115            return headers;
116        }
117
118        protected void patchContentTypes(String[] ctypes) {
119            for (int index = 0; index < ctypes.length; ++index) {
120                String value = ctypes[index];
121                if (value.isEmpty()) {
122                    ctypes[index] = "application/octet-stream";
123                } else if (!value.contains("/")) {
124                    ctypes[index] = "application/".concat(value);
125                }
126            }
127        }
128
129        @Override
130        public Enumeration<String> getHeaderNames() {
131            return headers.keys();
132        }
133
134        @Override
135        public String getHeader(String name) {
136            String lname = name.toLowerCase();
137            if (!headers.containsKey(lname)) {
138                return null;
139            }
140            return headers.get(lname)[0];
141        }
142
143        @Override
144        public Enumeration<String> getHeaders(final String name) {
145            final String lname = name.toLowerCase();
146            if (!headers.containsKey(lname)) {
147                return Collections.emptyEnumeration();
148            }
149            return new Enumeration<String>() {
150                String[] values = headers.get(lname);
151
152                int index = 0;
153
154                @Override
155                public boolean hasMoreElements() {
156                    return index < values.length;
157                }
158
159                @Override
160                public String nextElement() {
161                    if (index >= values.length) {
162                        throw new NoSuchElementException(index + " is higher than " + values.length);
163                    }
164                    return values[index++];
165                }
166            };
167        }
168
169    }
170
171}