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.beans.runtime.FileProperties;
38  import info.magnolia.cms.core.AggregationState;
39  import info.magnolia.cms.core.version.VersionManager;
40  import info.magnolia.cms.core.version.VersionedNode;
41  import info.magnolia.context.WebContext;
42  import info.magnolia.jcr.util.NodeNameHelper;
43  import info.magnolia.jcr.util.NodeTypes;
44  import info.magnolia.objectfactory.Components;
45  
46  import java.io.IOException;
47  
48  import javax.inject.Inject;
49  import javax.inject.Provider;
50  import javax.jcr.AccessDeniedException;
51  import javax.jcr.Node;
52  import javax.jcr.RepositoryException;
53  import javax.jcr.Session;
54  import javax.jcr.version.VersionException;
55  import javax.jcr.version.VersionHistory;
56  import javax.servlet.FilterChain;
57  import javax.servlet.ServletException;
58  import javax.servlet.http.HttpServletRequest;
59  import javax.servlet.http.HttpServletResponse;
60  
61  import org.apache.commons.lang3.StringUtils;
62  import org.apache.jackrabbit.JcrConstants;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  
67  /**
68   * Reads the accessed content from the repository and puts it into the {@link AggregationState}.
69   */
70  public class AggregatorFilter extends AbstractMgnlFilter {
71      private static final Logger log = LoggerFactory.getLogger(AggregatorFilter.class);
72  
73      private final String VERSION_NUMBER = "mgnlVersion";
74  
75      private final Provider<WebContext> contextProvider;
76  
77      private final VersionManager versionManager;
78  
79      private final NodeNameHelper nodeNameHelper;
80  
81      @Inject
82      public AggregatorFilter(Provider<WebContext> contextProvider, VersionManager versionManager, NodeNameHelper nodeNameHelper) {
83          this.contextProvider = contextProvider;
84          this.versionManager = versionManager;
85          this.nodeNameHelper = nodeNameHelper;
86      }
87  
88      /**
89       * @deprecated since 5.6.5, use {@link #AggregatorFilter(Provider, VersionManager, NodeNameHelper)} instead.
90       */
91      @Deprecated
92      public AggregatorFilter(Provider<WebContext> contextProvider, VersionManager versionManager) {
93          this(contextProvider, versionManager, Components.getComponent(NodeNameHelper.class));
94      }
95  
96      /**
97       * @deprecated since 5.5.5, use {@link #AggregatorFilter(Provider, VersionManager, NodeNameHelper)} instead.
98       */
99      @Deprecated
100     public AggregatorFilter() {
101         this(() -> Components.getComponent(WebContext.class), Components.getComponent(VersionManager.class), Components.getComponent(NodeNameHelper.class));
102     }
103 
104     @Override
105     public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
106 
107         boolean success;
108         try {
109             success = collect();
110         } catch (AccessDeniedException e) {
111             // don't throw further, simply return error and break filter chain
112             log.debug(e.getMessage(), e);
113             if (!response.isCommitted()) {
114                 response.setStatus(HttpServletResponse.SC_FORBIDDEN);
115             }
116             // stop the chain
117             return;
118         } catch (RepositoryException e) {
119             log.error(e.getMessage(), e);
120             throw new ServletException(e.getMessage(), e);
121         }
122 
123         if (!success) {
124             log.debug("Resource not found, redirecting request for [{}] to 404 URI", request.getRequestURI());
125 
126             if (!response.isCommitted()) {
127                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
128             } else {
129                 log.info("Unable to redirect to 404 page, response is already committed. URI was {}", request.getRequestURI());
130             }
131             // stop the chain
132             return;
133         }
134         chain.doFilter(request, response);
135     }
136 
137     /**
138      * Collect content from the pre configured repository and attach it to the HttpServletRequest.
139      */
140     protected boolean collect() throws RepositoryException {
141         final AggregationState aggregationState = contextProvider.get().getAggregationState();
142         final String handle = aggregationState.getHandle();
143         final String repository = aggregationState.getRepository();
144 
145         final Session session = contextProvider.get().getJCRSession(repository);
146 
147         Node requestedPage = null;
148         Node requestedData = null;
149         String templateName;
150 
151         boolean nodeExists = nodeExistsInSession(session, handle);
152 
153         if (nodeExists && !hasBinarySubnode(session, handle)) {
154             requestedPage = session.getNode(handle);
155 
156             // check if its a request for a versioned page
157             final String versionNumber = contextProvider.get().getAttribute(VERSION_NUMBER);
158             if (versionNumber != null) { // get versioned state
159                 try {
160                     VersionHistory versionHistory = versionManager.getVersionHistory(requestedPage);
161                     if (versionHistory != null) {
162                         requestedPage = new VersionedNode(versionHistory.getVersion(versionNumber), requestedPage);
163                     }
164                 } catch (VersionException e) {
165                     log.warn("The version '{}' of the node '{}' doesn't exists, rendering current state.", versionNumber, requestedPage, e);
166                 } catch (RepositoryException re) {
167                     log.debug(re.getMessage(), re);
168                     log.error("Unable to get versioned state {} of {}, rendering current state.", versionNumber, handle);
169                 }
170             }
171 
172             try {
173                 templateName = NodeTypes.Renderable.getTemplate(requestedPage);
174             } catch (RepositoryException e) {
175                 templateName = null;
176             }
177 
178             if (StringUtils.isBlank(templateName)) {
179                 log.error("No template configured for page [{}].", requestedPage);
180             }
181         } else {
182             if (nodeExists) {
183                 requestedData = session.getNode(handle);
184             } else {
185                 // check again, resource might have different name
186                 int lastIndexOfSlash = handle.lastIndexOf("/");
187 
188                 if (lastIndexOfSlash > 0) {
189 
190                     final String handleToUse = StringUtils.substringBeforeLast(handle, "/");
191 
192                     if (nodeExistsInSession(session, handleToUse)) {
193                         requestedData = session.getNode(handleToUse);
194                         aggregationState.setHandle(handleToUse);
195                         // this is needed for binary nodedata, e.g. images are found using the path:
196                         // /features/integration/headerImage instead of /features/integration/headerImage/header30_2
197                     } else {
198                         log.debug("Couldn't access {}", handle);
199                         return false;
200                     }
201                 }
202             }
203 
204             if (requestedData != null) {
205                 templateName = requestedData.hasProperty(FileProperties.PROPERTY_TEMPLATE) ? requestedData.getProperty(FileProperties.PROPERTY_TEMPLATE).getString() : StringUtils.EMPTY;
206             } else {
207                 return false;
208             }
209         }
210 
211         // Attach all collected information to the HttpServletRequest.
212         if (requestedPage != null) {
213             aggregationState.setMainContentNode(requestedPage);
214             aggregationState.setCurrentContentNode(requestedPage);
215         }
216         if (requestedData != null && isBinary(requestedData)) {
217             Fileruntime/File.html#File">File file = new File(requestedData);
218             aggregationState.setFile(file);
219         }
220 
221         aggregationState.setTemplateName(templateName);
222 
223         return true;
224     }
225 
226     private boolean isBinary(Node requestedData) throws RepositoryException {
227         return requestedData.isNodeType(NodeTypes.Resource.NAME) ||
228                 (requestedData.hasProperty("jcr:frozenPrimaryType") && requestedData.getProperty("jcr:frozenPrimaryType").getValue().getString().equals(NodeTypes.Resource.NAME));
229     }
230 
231     private boolean hasBinarySubnode(Session session, String nodePath) throws RepositoryException {
232         return session.itemExists(nodePath + (nodePath.endsWith("/") ? "" : "/") + JcrConstants.JCR_DATA);
233     }
234 
235     /**
236      * Check if the path *may be* a valid path before calling getItem, in order to avoid annoying logs.
237      *
238      * @param handle node handle
239      * @return true if the path is invalid
240      */
241     private boolean isJcrPathValid(String handle) {
242         if (StringUtils.isBlank(handle) || StringUtils.equals(handle, "/")) {
243             // empty path not allowed
244             return false;
245         }
246 
247         String[] jcrNames = handle.replaceFirst("/", "").split("/");
248 
249         for (String jcrName : jcrNames) {
250             if (!nodeNameHelper.isValidName(jcrName)) {
251                 return false;
252             }
253         }
254         return true;
255     }
256 
257     private boolean nodeExistsInSession(Session session, String handle) throws RepositoryException {
258         return isJcrPathValid(handle) && session.nodeExists(handle);
259     }
260 
261 }