001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.workplace.commons;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.file.CmsVfsResourceNotFoundException;
035import org.opencms.file.history.I_CmsHistoryResource;
036import org.opencms.flex.CmsFlexController;
037import org.opencms.i18n.CmsEncoder;
038import org.opencms.jsp.CmsJspActionElement;
039import org.opencms.main.CmsContextInfo;
040import org.opencms.main.CmsException;
041import org.opencms.main.CmsLog;
042import org.opencms.main.OpenCms;
043import org.opencms.util.CmsRequestUtil;
044import org.opencms.util.CmsStringUtil;
045import org.opencms.workplace.CmsDialog;
046import org.opencms.workplace.CmsWorkplaceSettings;
047
048import java.io.IOException;
049
050import org.apache.commons.logging.Log;
051
052import jakarta.servlet.http.HttpServletRequest;
053import jakarta.servlet.http.HttpServletResponse;
054import jakarta.servlet.jsp.PageContext;
055
056/**
057 * Shows a preview of the selected resource in the Explorer view.<p>
058 *
059 * This is required to get correct previews of statically exported pages
060 * in the Online project.<p>
061 *
062 * The following file uses this class:
063 * <ul>
064 * <li>/commons/displayresource.jsp
065 * </ul>
066 * <p>
067 *
068 * @since 6.0.0
069 */
070public class CmsDisplayResource extends CmsDialog {
071
072    /** Request parameter name for versionid. */
073    public static final String PARAM_VERSION = "version";
074
075    /** The log object for this class. */
076    private static final Log LOG = CmsLog.getLog(CmsDisplayResource.class);
077
078    /** The version number parameter. */
079    private String m_paramVersion;
080
081    /**
082     * Public constructor with JSP action element.<p>
083     *
084     * @param jsp an initialized JSP action element
085     */
086    public CmsDisplayResource(CmsJspActionElement jsp) {
087
088        super(jsp);
089    }
090
091    /**
092     * Public constructor with JSP variables.<p>
093     *
094     * @param context the JSP page context
095     * @param req the JSP request
096     * @param res the JSP response
097     */
098    public CmsDisplayResource(PageContext context, HttpServletRequest req, HttpServletResponse res) {
099
100        this(new CmsJspActionElement(context, req, res));
101    }
102
103    /**
104     * Returns the content of an historical resource.<p>
105     *
106     * @param cms a CmsObject
107     * @param resource the name of the historical resource
108     * @param version the version number of the historical resource
109     *
110     * @return the content of an historical resource
111     */
112    protected static byte[] getHistoricalResourceContent(CmsObject cms, String resource, String version) {
113
114        if (CmsStringUtil.isNotEmpty(resource) && CmsStringUtil.isNotEmpty(version)) {
115            // try to load the historical resource
116            I_CmsHistoryResource res = null;
117            String storedSiteRoot = cms.getRequestContext().getSiteRoot();
118            try {
119                cms.getRequestContext().setSiteRoot("/");
120                res = cms.readResource(
121                    cms.readResource(resource, CmsResourceFilter.ALL).getStructureId(),
122                    Integer.parseInt(version));
123            } catch (CmsException e) {
124                // can usually be ignored
125                if (LOG.isInfoEnabled()) {
126                    LOG.info(e.getLocalizedMessage());
127                }
128                return "".getBytes();
129            } finally {
130                cms.getRequestContext().setSiteRoot(storedSiteRoot);
131            }
132            if (res.isFile()) {
133                byte[] historyResourceContent = ((CmsFile)res).getContents();
134                if ((historyResourceContent == null) || (historyResourceContent.length == 0)) {
135                    try {
136                        CmsFile file = cms.readFile((CmsResource)res);
137                        historyResourceContent = file.getContents();
138                    } catch (CmsException e) {
139                        // ignore
140                    }
141                }
142                historyResourceContent = CmsEncoder.changeEncoding(
143                    historyResourceContent,
144                    OpenCms.getSystemInfo().getDefaultEncoding(),
145                    cms.getRequestContext().getEncoding());
146                return historyResourceContent;
147            }
148        }
149        return "".getBytes();
150    }
151
152    /**
153     * Redirects to the specified file or shows an historical resource.<p>
154     *
155     * @throws Exception if redirection fails
156     */
157    public void actionShow() throws Exception {
158
159        // try to load the historical resource
160        if (CmsStringUtil.isNotEmpty(getParamVersion())) {
161            showHistoricVersion();
162        } else {
163            String resourceStr = getParamResource();
164            // trying to read the resource
165            CmsResource resource = readResource(resourceStr);
166            if (isDeleted(resource)) {
167                // resource has been deleted in offline project
168                throw new CmsVfsResourceNotFoundException(
169                    Messages.get().container(
170                        Messages.ERR_RESOURCE_DELETED_2,
171                        resourceStr,
172                        getCms().getRequestContext().getCurrentProject().getName()));
173            }
174
175            // check for release / expiration time window
176            autoTimeWarp(resource);
177
178            // code for display resource after all tests for displayability (exists, not deleted)
179            //            if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
180            //                // if we have a container page look for the first sitemap entry
181            //                // and use that to display the container page
182            //                List<CmsInternalSitemapEntry> entries = OpenCms.getSitemapManager().getEntriesForStructureId(
183            //                    getJsp().getCmsObject(),
184            //                    resource.getStructureId());
185            //                if (!entries.isEmpty()) {
186            //                    CmsInternalSitemapEntry entry = entries.get(0);
187            //                    resourceStr = entry.getRootPath();
188            //                }
189            //            }
190            if (OpenCms.getSiteManager().isSharedFolder(getCms().getRequestContext().getSiteRoot())) {
191                if (!OpenCms.getSiteManager().startsWithShared(resourceStr)) {
192                    resourceStr = CmsStringUtil.joinPaths(OpenCms.getSiteManager().getSharedFolder(), resourceStr);
193                }
194            }
195            String url = getJsp().link(resourceStr);
196            // if in online project
197            if ((url.indexOf("://") < 0) && getCms().getRequestContext().getCurrentProject().isOnlineProject()) {
198                url = prependSiteRoot(url);
199            }
200            getJsp().getResponse().sendRedirect(url);
201        }
202    }
203
204    /**
205     * Returns the version number parameter value.<p>
206     *
207     * @return the version number parameter value
208     */
209    public String getParamVersion() {
210
211        return m_paramVersion;
212    }
213
214    /**
215     * Sets the version number parameter value.<p>
216     *
217     * @param paramVersion the version number parameter value to set
218     */
219    public void setParamVersionid(String paramVersion) {
220
221        m_paramVersion = paramVersion;
222    }
223
224    /**
225     * Performs a timewarp for resources that are expired or not released yet to always allow a
226     * preview of a page out of the workplace.<p>
227     *
228     * If the user has a configured timewarp (preferences dialog) a mandatory timewarp will lead to
229     * an exception. One cannot auto timewarp with configured timewarp time.<p>
230     *
231     * @param resource the resource to show
232     *
233     * @throws CmsVfsResourceNotFoundException if a warp would be needed to show the resource but the user has a configured
234     *      timewarp which disallows auto warping
235     */
236    protected void autoTimeWarp(CmsResource resource) throws CmsVfsResourceNotFoundException {
237
238        long surfTime = getCms().getRequestContext().getRequestTime();
239        if (resource.isReleasedAndNotExpired(surfTime)) {
240            // resource is valid, no modification of time required
241            return;
242        }
243
244        if (getSettings().getUserSettings().getTimeWarp() == CmsContextInfo.CURRENT_TIME) {
245            // no time warp has been set, enable auto time warp
246            long timeWarp;
247            // will also work if ATTRIBUTE_REQUEST_TIME was CmsResource.DATE_RELEASED_EXPIRED_IGNORE
248            if (resource.isExpired(surfTime)) {
249                // do a time warp into the past
250                timeWarp = resource.getDateExpired() - 1;
251            } else if (!resource.isReleased(surfTime)) {
252                // do a time warp into the future
253                timeWarp = resource.getDateReleased() + 1;
254            } else {
255                // do no time warp
256                timeWarp = CmsContextInfo.CURRENT_TIME;
257            }
258            if (timeWarp != CmsContextInfo.CURRENT_TIME) {
259                // let's do the time warp again...
260                getSession().setAttribute(CmsContextInfo.ATTRIBUTE_REQUEST_TIME, Long.valueOf(timeWarp));
261            }
262        } else {
263            // resource is not vaild in the time window set by the user,
264            // report an error message
265            throw new CmsVfsResourceNotFoundException(
266                Messages.get().container(Messages.ERR_RESOURCE_OUTSIDE_TIMEWINDOW_1, getParamResource()));
267        }
268    }
269
270    /**
271     * @see org.opencms.workplace.CmsDialog#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, jakarta.servlet.http.HttpServletRequest)
272     */
273    @Override
274    protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) {
275
276        fillParamValues(settings, request);
277    }
278
279    /**
280     * Returns if the given resource has the state "deleted".<p>
281     *
282     * @param resource the resource
283     *
284     * @return <code>true</code> if the resource is of state "deleted"
285     */
286    private boolean isDeleted(CmsResource resource) {
287
288        return resource.getState().isDeleted();
289    }
290
291    /**
292     * Prepends the site-root to the given URL.<p>
293     *
294     * @param url the URL
295     *
296     * @return the absolute URL
297     */
298    private String prependSiteRoot(String url) {
299
300        String site = getCms().getRequestContext().getSiteRoot();
301        if (CmsStringUtil.isEmptyOrWhitespaceOnly(site) || OpenCms.getSiteManager().isSharedFolder(site)) {
302            site = OpenCms.getSiteManager().getDefaultUri();
303            if (CmsStringUtil.isEmptyOrWhitespaceOnly(site)
304                || (OpenCms.getSiteManager().getSiteForSiteRoot(site) == null)) {
305                return OpenCms.getSiteManager().getWorkplaceServer() + url;
306            } else {
307                return OpenCms.getSiteManager().getSiteForSiteRoot(site).getUrl() + url;
308            }
309        }
310        return OpenCms.getSiteManager().getSiteForSiteRoot(site).getUrl() + url;
311    }
312
313    /**
314     * Reads the resource from the DB.<p>
315     *
316     * @param resourceName the resource name
317     *
318     * @return the resource
319     *
320     * @throws CmsException if the resource can not be read
321     */
322    private CmsResource readResource(String resourceName) throws CmsException {
323
324        CmsResource resource = null;
325        try {
326            resource = getCms().readResource(resourceName, CmsResourceFilter.ALL);
327        } catch (CmsVfsResourceNotFoundException e) {
328            throw new CmsVfsResourceNotFoundException(
329                Messages.get().container(
330                    Messages.ERR_RESOURCE_DOES_NOT_EXIST_3,
331                    resourceName,
332                    getCms().getRequestContext().getCurrentProject().getName(),
333                    getCms().getRequestContext().getSiteRoot()),
334                e);
335        }
336        return resource;
337    }
338
339    /**
340     * Displays the requested historic version of the resource.<p>
341     */
342    private void showHistoricVersion() {
343
344        String resourceStr = getParamResource();
345        byte[] result = getHistoricalResourceContent(getCms(), resourceStr, getParamVersion());
346        if (result != null) {
347            // get the top level response to change the content type
348            String contentType = OpenCms.getResourceManager().getMimeType(
349                resourceStr,
350                getCms().getRequestContext().getEncoding());
351
352            HttpServletResponse res = getJsp().getResponse();
353            HttpServletRequest req = getJsp().getRequest();
354
355            res.setHeader(
356                CmsRequestUtil.HEADER_CONTENT_DISPOSITION,
357                new StringBuffer("attachment; filename=\"").append(resourceStr).append("\"").toString());
358            res.setContentLength(result.length);
359
360            CmsFlexController controller = CmsFlexController.getController(req);
361            res = controller.getTopResponse();
362            res.setContentType(contentType);
363
364            try {
365                res.getOutputStream().write(result);
366                res.getOutputStream().flush();
367            } catch (IOException e) {
368                // can usually be ignored
369                if (LOG.isInfoEnabled()) {
370                    LOG.info(e.getLocalizedMessage());
371                }
372            }
373        }
374    }
375}