View Javadoc
1   /**
2    * This file Copyright (c) 2003-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.filters;
35  
36  import info.magnolia.cms.beans.runtime.File;
37  import info.magnolia.cms.core.AggregationState;
38  import info.magnolia.cms.core.version.VersionManager;
39  import info.magnolia.cms.core.version.VersionedNode;
40  import info.magnolia.context.WebContext;
41  import info.magnolia.jcr.util.NodeNameHelper;
42  import info.magnolia.jcr.util.NodeTypes;
43  import info.magnolia.objectfactory.Components;
44  
45  import java.io.IOException;
46  
47  import javax.inject.Inject;
48  import javax.inject.Provider;
49  import javax.jcr.AccessDeniedException;
50  import javax.jcr.Node;
51  import javax.jcr.RepositoryException;
52  import javax.jcr.Session;
53  import javax.jcr.version.VersionException;
54  import javax.jcr.version.VersionHistory;
55  import javax.servlet.FilterChain;
56  import javax.servlet.ServletException;
57  import javax.servlet.http.HttpServletRequest;
58  import javax.servlet.http.HttpServletResponse;
59  
60  import org.apache.commons.lang3.StringUtils;
61  import org.apache.jackrabbit.JcrConstants;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  
66  /**
67   * Reads the accessed content from the repository and puts it into the {@link AggregationState}.
68   */
69  public class AggregatorFilter extends AbstractMgnlFilter {
70      private static final Logger log = LoggerFactory.getLogger(AggregatorFilter.class);
71  
72      private final String VERSION_NUMBER = "mgnlVersion";
73  
74      private final Provider<WebContext> contextProvider;
75  
76      private final VersionManager versionManager;
77  
78      private final NodeNameHelper nodeNameHelper;
79  
80      @Inject
81      public AggregatorFilter(Provider<WebContext> contextProvider, VersionManager versionManager, NodeNameHelper nodeNameHelper) {
82          this.contextProvider = contextProvider;
83          this.versionManager = versionManager;
84          this.nodeNameHelper = nodeNameHelper;
85      }
86  
87      /**
88       * @deprecated since 5.6.5, use {@link #AggregatorFilter(Provider, VersionManager, NodeNameHelper)} instead.
89       */
90      @Deprecated
91      public AggregatorFilter(Provider<WebContext> contextProvider, VersionManager versionManager) {
92          this(contextProvider, versionManager, Components.getComponent(NodeNameHelper.class));
93      }
94  
95      /**
96       * @deprecated since 5.5.5, use {@link #AggregatorFilter(Provider, VersionManager, NodeNameHelper)} instead.
97       */
98      @Deprecated
99      public AggregatorFilter() {
100         this(() -> Components.getComponent(WebContext.class), Components.getComponent(VersionManager.class), Components.getComponent(NodeNameHelper.class));
101     }
102 
103     @Override
104     public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
105 
106         boolean success;
107         try {
108             success = collect();
109         } catch (AccessDeniedException e) {
110             // don't throw further, simply return error and break filter chain
111             log.debug(e.getMessage(), e);
112             if (!response.isCommitted()) {
113                 response.setStatus(HttpServletResponse.SC_FORBIDDEN);
114             }
115             // stop the chain
116             return;
117         } catch (RepositoryException e) {
118             log.error(e.getMessage(), e);
119             throw new ServletException(e.getMessage(), e);
120         }
121 
122         if (!success) {
123             log.debug("Resource not found, redirecting request for [{}] to 404 URI", request.getRequestURI());
124 
125             if (!response.isCommitted()) {
126                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
127             } else {
128                 log.info("Unable to redirect to 404 page, response is already committed. URI was {}", request.getRequestURI());
129             }
130             // stop the chain
131             return;
132         }
133         chain.doFilter(request, response);
134     }
135 
136     /**
137      * Collect content from the pre configured repository and attach it to the HttpServletRequest.
138      */
139     protected boolean collect() throws RepositoryException {
140         final AggregationState aggregationState = contextProvider.get().getAggregationState();
141         final String handle = aggregationState.getHandle();
142         final String repository = aggregationState.getRepository();
143 
144         final Session session = contextProvider.get().getJCRSession(repository);
145 
146         Node requestedPage = null;
147         Node requestedData = null;
148         String templateName;
149 
150         boolean nodeExists = nodeExistsInSession(session, handle);
151 
152         if (nodeExists && !hasBinarySubnode(session, handle)) {
153             requestedPage = session.getNode(handle);
154 
155             // check if its a request for a versioned page
156             final String versionNumber = contextProvider.get().getAttribute(VERSION_NUMBER);
157             if (versionNumber != null) { // get versioned state
158                 try {
159                     VersionHistory versionHistory = versionManager.getVersionHistory(requestedPage);
160                     if (versionHistory != null) {
161                         requestedPage = new VersionedNode(versionHistory.getVersion(versionNumber), requestedPage);
162                     }
163                 } catch (VersionException e) {
164                     log.warn("The version '{}' of the node '{}' doesn't exists, rendering current state.", versionNumber, requestedPage, e);
165                 } catch (RepositoryException re) {
166                     log.debug(re.getMessage(), re);
167                     log.error("Unable to get versioned state {} of {}, rendering current state.", versionNumber, handle);
168                 }
169             }
170 
171             try {
172                 templateName = NodeTypes.Renderable.getTemplate(requestedPage);
173             } catch (RepositoryException e) {
174                 templateName = null;
175             }
176 
177             if (StringUtils.isBlank(templateName)) {
178                 log.error("No template configured for page [{}].", requestedPage);
179             }
180         } else {
181             if (nodeExists) {
182                 requestedData = session.getNode(handle);
183             } else {
184                 // check again, resource might have different name
185                 int lastIndexOfSlash = handle.lastIndexOf("/");
186 
187                 if (lastIndexOfSlash > 0) {
188 
189                     final String handleToUse = StringUtils.substringBeforeLast(handle, "/");
190 
191                     if (nodeExistsInSession(session, handleToUse)) {
192                         requestedData = session.getNode(handleToUse);
193                         aggregationState.setHandle(handleToUse);
194                         // this is needed for binary nodedata, e.g. images are found using the path:
195                         // /features/integration/headerImage instead of /features/integration/headerImage/header30_2
196                     } else {
197                         log.debug("Couldn't access {}", handle);
198                         return false;
199                     }
200                 }
201             }
202 
203             if (requestedData != null) {
204                 templateName = requestedData.hasProperty(File.PROPERTY_TEMPLATE) ? requestedData.getProperty(File.PROPERTY_TEMPLATE).getString() : StringUtils.EMPTY;
205             } else {
206                 return false;
207             }
208         }
209 
210         // Attach all collected information to the HttpServletRequest.
211         if (requestedPage != null) {
212             aggregationState.setMainContentNode(requestedPage);
213             aggregationState.setCurrentContentNode(requestedPage);
214         }
215         if (requestedData != null && isBinary(requestedData)) {
216             Fileruntime/File.html#File">File file = new File(requestedData);
217             aggregationState.setFile(file);
218         }
219 
220         aggregationState.setTemplateName(templateName);
221 
222         return true;
223     }
224 
225     private boolean isBinary(Node requestedData) throws RepositoryException {
226         return requestedData.isNodeType(NodeTypes.Resource.NAME) ||
227                 (requestedData.hasProperty("jcr:frozenPrimaryType") && requestedData.getProperty("jcr:frozenPrimaryType").getValue().getString().equals(NodeTypes.Resource.NAME));
228     }
229 
230     private boolean hasBinarySubnode(Session session, String nodePath) throws RepositoryException {
231         return session.itemExists(nodePath + (nodePath.endsWith("/") ? "" : "/") + JcrConstants.JCR_DATA);
232     }
233 
234     /**
235      * Check if the path *may be* a valid path before calling getItem, in order to avoid annoying logs.
236      *
237      * @param handle node handle
238      * @return true if the path is invalid
239      */
240     private boolean isJcrPathValid(String handle) {
241         if (StringUtils.isBlank(handle) || StringUtils.equals(handle, "/")) {
242             // empty path not allowed
243             return false;
244         }
245 
246         String[] jcrNames = handle.replaceFirst("/", "").split("/");
247 
248         for (String jcrName : jcrNames) {
249             if (!nodeNameHelper.isValidName(jcrName)) {
250                 return false;
251             }
252         }
253         return true;
254     }
255 
256     private boolean nodeExistsInSession(Session session, String handle) throws RepositoryException {
257         return isJcrPathValid(handle) && session.nodeExists(handle);
258     }
259 
260 }