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.file;
029
030import org.opencms.gwt.shared.CmsGwtConstants;
031import org.opencms.main.CmsIllegalArgumentException;
032import org.opencms.main.OpenCms;
033import org.opencms.security.CmsOrganizationalUnit;
034import org.opencms.site.CmsSiteMatcher;
035import org.opencms.util.CmsResourceTranslator;
036import org.opencms.util.CmsUUID;
037import org.opencms.workplace.CmsWorkplace;
038
039import java.util.Hashtable;
040import java.util.Locale;
041import java.util.Map;
042
043/**
044 * Stores the information about the current users OpenCms context,
045 * for example the requested URI, the current project, the selected site and more.<p>
046 *
047 * @since 6.0.0
048 */
049public final class CmsRequestContext {
050
051    /** Request context attribute for the ADE context path (should be a root path). */
052    public static final String ATTRIBUTE_ADE_CONTEXT_PATH = CmsRequestContext.class.getName() + ".ADE_CONTEXT_PATH";
053
054    /** Request context attribute for the optional client label a session created from this context is marked with. */
055    public static final String ATTRIBUTE_CLIENT_LABEL = CmsRequestContext.class.getName() + ".CLIENT_LABEL";
056
057    /** Request context attribute for indicating that an editor is currently open. */
058    public static final String ATTRIBUTE_EDITOR = CmsRequestContext.class.getName() + ".ATTRIBUTE_EDITOR";
059
060    /** Request context attribute for indicating we want full links generated for HTML fields. */
061    public static final String ATTRIBUTE_FULLLINKS = CmsRequestContext.class.getName() + ".ATTRIBUTE_FULLLINKS";
062
063    /** Request context attribute for indicating the model file for a create resource operation. */
064    public static final String ATTRIBUTE_MODEL = CmsRequestContext.class.getName() + ".ATTRIBUTE_MODEL";
065
066    /** Request context attribute for indicating content locale for a create resource operation. */
067    public static final String ATTRIBUTE_NEW_RESOURCE_LOCALE = CmsRequestContext.class.getName()
068        + ".NEW_RESOURCE_LOCALE";
069
070    /** A map for storing (optional) request context attributes. */
071    private Map<String, Object> m_attributeMap;
072
073    /** The current project. */
074    private CmsProject m_currentProject;
075
076    /** The detail content resource (possibly null). */
077    private CmsResource m_detailResource;
078
079    /** Directory name translator. */
080    private CmsResourceTranslator m_directoryTranslator;
081
082    /** Current encoding. */
083    private String m_encoding;
084
085    /** File name translator. */
086    private CmsResourceTranslator m_fileTranslator;
087
088    /** Flag to control whether links should be absolute even if we're linking to the current site. */
089    private boolean m_forceAbsoluteLinks;
090
091    /** The secure request flag. */
092    private boolean m_isSecureRequest;
093
094    /** The locale used by this request context. */
095    private Locale m_locale;
096
097    /** The fully qualified name of the organizational unit for this request. */
098    private String m_ouFqn;
099
100    /** The remote ip address. */
101    private String m_remoteAddr;
102
103    /** the matcher for the current request, that is the host part of the URI from the original http request. */
104    private CmsSiteMatcher m_requestMatcher;
105
106    /** The current request time. */
107    private long m_requestTime;
108
109    /** The name of the root, e.g. /site_a/vfs. */
110    private String m_siteRoot;
111
112    /** Flag to indicate that this context should not update the user session. */
113    private boolean m_updateSession;
114
115    /** The URI for getUri() in case it is "overwritten".  */
116    private String m_uri;
117
118    /** The current user. */
119    private CmsUser m_user;
120
121    /**
122     * Constructs a new request context.<p>
123     *
124     * @param user the current user
125     * @param project the current project
126     * @param requestedUri the requested OpenCms VFS URI
127     * @param requestMatcher the matcher for the current request, that is the host part of the URI from the original http request
128     * @param siteRoot the users current site root
129     * @param isSecureRequest true if this is a secure request
130     * @param locale the users current locale
131     * @param encoding the encoding to use for this request
132     * @param remoteAddr the remote IP address of the user
133     * @param requestTime the time of the request (used for resource publication / expiration date)
134     * @param directoryTranslator the directory translator
135     * @param fileTranslator the file translator
136     * @param ouFqn the fully qualified name of the organizational unit
137     * @param forceAbsoluteLinks if true, links should be generated with a server prefix even if we're linking to the current site
138     */
139    public CmsRequestContext(
140        CmsUser user,
141        CmsProject project,
142        String requestedUri,
143        CmsSiteMatcher requestMatcher,
144        String siteRoot,
145        boolean isSecureRequest,
146        Locale locale,
147        String encoding,
148        String remoteAddr,
149        long requestTime,
150        CmsResourceTranslator directoryTranslator,
151        CmsResourceTranslator fileTranslator,
152        String ouFqn,
153        boolean forceAbsoluteLinks) {
154
155        m_updateSession = true;
156        m_user = user;
157        m_currentProject = project;
158        m_uri = requestedUri;
159        m_requestMatcher = requestMatcher;
160        m_isSecureRequest = isSecureRequest;
161        setSiteRoot(siteRoot);
162        m_locale = locale;
163        m_encoding = encoding;
164        m_remoteAddr = remoteAddr;
165        m_requestTime = requestTime;
166        m_directoryTranslator = directoryTranslator;
167        m_fileTranslator = fileTranslator;
168        setOuFqn(ouFqn);
169        m_forceAbsoluteLinks = forceAbsoluteLinks;
170    }
171
172    /**
173     * Returns the adjusted site root for a resource using the provided site root as a base.<p>
174     *
175     * Usually, this would be the site root for the current site.
176     * However, if a resource from the <code>/system/</code> folder is requested,
177     * this will be the empty String.<p>
178     *
179     * @param siteRoot the site root of the current site
180     * @param resourcename the resource name to get the adjusted site root for
181     *
182     * @return the adjusted site root for the resource
183     */
184    public static String getAdjustedSiteRoot(String siteRoot, String resourcename) {
185
186        if (resourcename.startsWith(CmsWorkplace.VFS_PATH_SYSTEM)
187            || OpenCms.getSiteManager().startsWithShared(resourcename)
188            || (resourcename.startsWith(CmsWorkplace.VFS_PATH_SITES) && !resourcename.startsWith(siteRoot))) {
189            return "";
190        } else {
191            return siteRoot;
192        }
193    }
194
195    /**
196     * Adds the current site root of this context to the given resource name,
197     * and also translates the resource name with the configured the directory translator.<p>
198     *
199     * @param resourcename the resource name
200     * @return the translated resource name including site root
201     * @see #addSiteRoot(String, String)
202     */
203    public String addSiteRoot(String resourcename) {
204
205        return addSiteRoot(m_siteRoot, resourcename);
206    }
207
208    /**
209     * Adds the given site root of this context to the given resource name,
210     * taking into account special folders like "/system" where no site root must be added,
211     * and also translates the resource name with the configured the directory translator.<p>
212     *
213     * @param siteRoot the site root to add
214     * @param resourcename the resource name
215     * @return the translated resource name including site root
216     */
217    public String addSiteRoot(String siteRoot, String resourcename) {
218
219        if ((resourcename == null) || (siteRoot == null)) {
220            return null;
221        }
222        siteRoot = getAdjustedSiteRoot(siteRoot, resourcename);
223        StringBuffer result = new StringBuffer(128);
224        result.append(siteRoot);
225        if (((siteRoot.length() == 0) || (siteRoot.charAt(siteRoot.length() - 1) != '/'))
226            && ((resourcename.length() == 0) || (resourcename.charAt(0) != '/'))) {
227            // add slash between site root and resource if required
228            result.append('/');
229        }
230        result.append(resourcename);
231        return m_directoryTranslator.translateResource(result.toString());
232    }
233
234    /**
235     * Returns the current project of the current user.
236     *
237     * @return the current project of the current user
238     *
239     * @deprecated use {@link #getCurrentProject()} instead
240     */
241    @Deprecated
242    public CmsProject currentProject() {
243
244        return getCurrentProject();
245    }
246
247    /**
248     * Returns the current user object.<p>
249     *
250     * @return the current user object
251     *
252     * @deprecated use {@link #getCurrentUser()} instead
253     */
254    @Deprecated
255    public CmsUser currentUser() {
256
257        return getCurrentUser();
258    }
259
260    /**
261     * Returns the adjusted site root for a resource this context current site root.<p>
262     *
263     * @param resourcename the resource name to get the adjusted site root for
264     *
265     * @return the adjusted site root for the resource
266     *
267     * @see #getAdjustedSiteRoot(String, String)
268     */
269    public String getAdjustedSiteRoot(String resourcename) {
270
271        return getAdjustedSiteRoot(m_siteRoot, resourcename);
272    }
273
274    /**
275     * Gets the value of an attribute from the OpenCms request context attribute list.<p>
276     *
277     * @param attributeName the attribute name
278     * @return Object the attribute value, or <code>null</code> if the attribute was not found
279     */
280    public Object getAttribute(String attributeName) {
281
282        if (m_attributeMap == null) {
283            return null;
284        }
285        return m_attributeMap.get(attributeName);
286    }
287
288    /**
289     * Returns the current project of the current user.
290     *
291     * @return the current project of the current user
292     */
293    public CmsProject getCurrentProject() {
294
295        return m_currentProject;
296    }
297
298    /**
299     * Returns the current user object.<p>
300     *
301     * @return the current user object
302     */
303    public CmsUser getCurrentUser() {
304
305        return m_user;
306    }
307
308    /**
309     * Gets the detail content structure id (or null if no detail content has been loaded).<p>
310     *
311     * @return the detail content id
312     */
313    public CmsUUID getDetailContentId() {
314
315        if (m_detailResource == null) {
316            return null;
317        }
318        return m_detailResource.getStructureId();
319    }
320
321    /**
322     * Gets the detail content resource (or null if no detail content has been loaded).<p>
323     *
324     * @return the detail content resource
325     */
326    public CmsResource getDetailResource() {
327
328        return m_detailResource;
329    }
330
331    /**
332     * Returns the directory name translator this context was initialized with.<p>
333     *
334     * The directory translator is used to translate old VFS path information
335     * to a new location. Example: <code>/bodys/index.html --> /system/bodies/</code>.<p>
336     *
337     * @return the directory name translator this context was initialized with
338     */
339    public CmsResourceTranslator getDirectoryTranslator() {
340
341        return m_directoryTranslator;
342    }
343
344    /**
345     * Returns the current content encoding to be used in HTTP response.<p>
346     *
347     * @return the encoding
348     */
349    public String getEncoding() {
350
351        return m_encoding;
352    }
353
354    /**
355     * Returns the file name translator this context was initialized with.<p>
356     *
357     * The file name translator is used to translate filenames from uploaded files
358     * to valid OpenCms filenames. Example: <code>W&uuml;ste W&ouml;rter.doc --> Wueste_Woerter.doc</code>.<p>
359     *
360     * @return the file name translator this context was initialized with
361     */
362    public CmsResourceTranslator getFileTranslator() {
363
364        return m_fileTranslator;
365    }
366
367    /**
368     * Gets the name of the parent folder of the requested file.<p>
369     *
370     * @return the name of the parent folder of the requested file
371     */
372    public String getFolderUri() {
373
374        return CmsResource.getFolderPath(m_uri);
375    }
376
377    /**
378     * Returns the locale used by this request context.<p>
379     *
380     * In normal operation, the request context locale is initialized using
381     * {@link org.opencms.i18n.I_CmsLocaleHandler#getI18nInfo(jakarta.servlet.http.HttpServletRequest, CmsUser, CmsProject, String)}
382     * depending on the requested resource URI.<p>
383     *
384     * @return the locale used by this request context
385     *
386     * @see org.opencms.i18n.I_CmsLocaleHandler#getI18nInfo(jakarta.servlet.http.HttpServletRequest, CmsUser, CmsProject, String)
387     * @see org.opencms.i18n.CmsLocaleManager#getDefaultLocale(CmsObject, String)
388     */
389    public Locale getLocale() {
390
391        return m_locale;
392    }
393
394    /**
395     * Returns the fully qualified name of the organizational unit.<p>
396     *
397     * @return the fully qualified name of the organizational unit
398     */
399    public String getOuFqn() {
400
401        return m_ouFqn;
402    }
403
404    /**
405     * Returns the remote ip address.<p>
406     *
407     * @return the remote ip address as string
408     */
409    public String getRemoteAddress() {
410
411        return m_remoteAddr;
412    }
413
414    /**
415     * Returns the matcher for the current request, that is the host part of the URI from the original http request.<p>
416     *
417     * @return the matcher for the current request, that is the host part of the URI from the original http request
418     */
419    public CmsSiteMatcher getRequestMatcher() {
420
421        return m_requestMatcher;
422    }
423
424    /**
425     * Returns the current request time.<p>
426     *
427     * @return the current request time
428     */
429    public long getRequestTime() {
430
431        return m_requestTime;
432    }
433
434    /**
435     * Returns this request contexts uri extended with the current site root path.<p>
436     *
437     * @return this request contexts uri extended with the current site root path
438     *
439     * @see #getUri()
440     * @see #addSiteRoot(String)
441     */
442    public String getRootUri() {
443
444        return addSiteRoot(m_siteRoot, m_uri);
445    }
446
447    /**
448     * Adjusts the absolute resource root path for the current site.<p>
449     *
450     * The full root path of a resource is always available using
451     * <code>{@link CmsResource#getRootPath()}</code>. From this name this method cuts
452     * of the current site root using
453     * <code>{@link CmsRequestContext#removeSiteRoot(String)}</code>.<p>
454     *
455     * If the resource root path does not start with the current site root,
456     * it is left untouched.<p>
457     *
458     * @param resource the resource to get the adjusted site root path for
459     *
460     * @return the absolute resource path adjusted for the current site
461     *
462     * @see #removeSiteRoot(String)
463     * @see CmsResource#getRootPath()
464     * @see CmsObject#getSitePath(CmsResource)
465     */
466    public String getSitePath(CmsResource resource) {
467
468        return removeSiteRoot(resource.getRootPath());
469    }
470
471    /**
472     * Returns the current root directory in the virtual file system.<p>
473     *
474     * @return the current root directory in the virtual file system
475     */
476    public String getSiteRoot() {
477
478        return m_siteRoot;
479    }
480
481    /**
482     * Returns the OpenCms VFS URI of the requested resource.<p>
483     *
484     * @return the OpenCms VFS URI of the requested resource
485     */
486    public String getUri() {
487
488        return m_uri;
489    }
490
491    /**
492     * Returns true if links to the current site should be generated as absolute links, i.e. with a server prefix.
493     *
494     * @return true if links to the current site should be absolute
495     */
496    public boolean isForceAbsoluteLinks() {
497
498        return m_forceAbsoluteLinks;
499    }
500
501    /**
502     * Checks if we are currently either in the Online project or the 'direct edit disabled' mode.
503     *
504     * @return true if we are online or in 'direct edit enabled' mode
505     */
506    public boolean isOnlineOrEditDisabled() {
507
508        if (getCurrentProject().isOnlineProject()) {
509            return true;
510        }
511        Object directEdit = getAttribute(CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT);
512        if (Boolean.TRUE.equals(directEdit)) {
513            return true;
514        }
515        return false;
516    }
517
518    /**
519     * Returns true if this is a secure request.<p>
520     *
521     * @return true if this is secure
522     */
523    public boolean isSecureRequest() {
524
525        return m_isSecureRequest;
526    }
527
528    /**
529     * Check if this request context will update the session.<p>
530     *
531     * This is used mainly for CmsReports that continue to use the
532     * users context, even after the http request is already finished.<p>
533     *
534     * @return true if this request context will update the session, false otherwise
535     */
536    public boolean isUpdateSessionEnabled() {
537
538        return m_updateSession;
539    }
540
541    /**
542     * Removes an attribute from the request context.<p>
543     *
544     * @param key the name of the attribute to remove
545     *
546     * @return the removed attribute, or <code>null</code> if no attribute was set with this name
547     */
548    public Object removeAttribute(String key) {
549
550        if (m_attributeMap != null) {
551            return m_attributeMap.remove(key);
552        }
553        return null;
554    }
555
556    /**
557     * Removes the current site root prefix from the absolute path in the resource name,
558     * that is adjusts the resource name for the current site root.<p>
559     *
560     * If the resource name does not start with the current site root,
561     * it is left untouched.<p>
562     *
563     * @param resourcename the resource name
564     *
565     * @return the resource name adjusted for the current site root
566     *
567     * @see #getSitePath(CmsResource)
568     */
569    public String removeSiteRoot(String resourcename) {
570
571        String siteRoot = getAdjustedSiteRoot(m_siteRoot, resourcename);
572        if ((siteRoot == m_siteRoot)
573            && resourcename.startsWith(siteRoot)
574            && ((resourcename.length() == siteRoot.length()) || (resourcename.charAt(siteRoot.length()) == '/'))) {
575            resourcename = resourcename.substring(siteRoot.length());
576        }
577        if (resourcename.length() == 0) {
578            // input was a site root folder without trailing slash
579            resourcename = "/";
580        }
581        return resourcename;
582    }
583
584    /**
585     * Sets an attribute in the request context.<p>
586     *
587     * @param key the attribute name
588     * @param value the attribute value
589     */
590    public void setAttribute(String key, Object value) {
591
592        if (m_attributeMap == null) {
593            // hash table is still the most efficient form of a synchronized Map
594            m_attributeMap = new Hashtable<String, Object>();
595        }
596        m_attributeMap.put(key, value);
597    }
598
599    /**
600     * Sets the current project for the user.<p>
601     *
602     * @param project the project to be set as current project
603     *
604     * @return the CmsProject instance
605     */
606    public CmsProject setCurrentProject(CmsProject project) {
607
608        if (project != null) {
609            m_currentProject = project;
610        }
611        return m_currentProject;
612    }
613
614    /**
615     * Sets the detail content resource.<p>
616     *
617     * @param detailResource the detail content resource
618     */
619    public void setDetailResource(CmsResource detailResource) {
620
621        m_detailResource = detailResource;
622    }
623
624    /**
625     * Sets the current content encoding to be used in HTTP response.<p>
626     *
627     * @param encoding the encoding
628     */
629    public void setEncoding(String encoding) {
630
631        m_encoding = encoding;
632    }
633
634    /**
635     * Enables/disables link generation with full server prefix for the current site.
636     *
637     * @param forceAbsoluteLinks true if links to the current site should be generated with server prefix
638     */
639    public void setForceAbsoluteLinks(boolean forceAbsoluteLinks) {
640
641        m_forceAbsoluteLinks = forceAbsoluteLinks;
642    }
643
644    /**
645     * Sets the locale used by this request context.<p>
646     *
647     * @param locale the locale to set
648     *
649     * @see #getLocale() for more information about how the locale is set in normal operation
650     */
651    public void setLocale(Locale locale) {
652
653        m_locale = locale;
654    }
655
656    /**
657     * Sets the organizational unit fully qualified name.<p>
658     *
659     * @param ouFqn the organizational unit fully qualified name
660     */
661    public void setOuFqn(String ouFqn) {
662
663        String userOu = CmsOrganizationalUnit.getParentFqn(m_user.getName());
664        if (ouFqn != null) {
665            if (ouFqn.startsWith(userOu)
666                || (ouFqn.startsWith(CmsOrganizationalUnit.SEPARATOR) && ouFqn.substring(1).startsWith(userOu))) {
667                m_ouFqn = ouFqn;
668            } else {
669                throw new CmsIllegalArgumentException(
670                    Messages.get().container(Messages.ERR_BAD_ORGUNIT_2, ouFqn, userOu));
671            }
672        } else {
673            m_ouFqn = userOu;
674        }
675        m_ouFqn = CmsOrganizationalUnit.removeLeadingSeparator(m_ouFqn);
676    }
677
678    /**
679     * Sets the current request time.<p>
680     *
681     * @param time the request time
682     */
683    public void setRequestTime(long time) {
684
685        m_requestTime = time;
686    }
687
688    /**
689     * Sets the 'secure request' status.<p>
690     *
691     * @param secureRequest the new value
692     */
693    public void setSecureRequest(boolean secureRequest) {
694
695        m_isSecureRequest = secureRequest;
696    }
697
698    /**
699     * Sets the current root directory in the virtual file system.<p>
700     *
701     * @param root the name of the new root directory
702     */
703    public void setSiteRoot(String root) {
704
705        // site roots must never end with a "/"
706        if (root.endsWith("/")) {
707            m_siteRoot = root.substring(0, root.length() - 1);
708        } else {
709            m_siteRoot = root;
710        }
711    }
712
713    /**
714     * Mark this request context to update the session or not.<p>
715     *
716     * @param value true if this request context will update the session, false otherwise
717     */
718    public void setUpdateSessionEnabled(boolean value) {
719
720        m_updateSession = value;
721    }
722
723    /**
724     * Set the requested resource OpenCms VFS URI, that is the value returned by {@link #getUri()}.<p>
725     *
726     * Use this with caution! Many things (caches etc.) depend on this value.
727     * If you change this value, better make sure that you change it only temporarily
728     * and reset it in a <code>try { // do something // } finally { // reset URI // }</code> statement.<p>
729     *
730     * @param value the value to set the Uri to, must be a complete OpenCms path name like /system/workplace/style.css
731     */
732    public void setUri(String value) {
733
734        m_uri = value;
735    }
736
737    /**
738     * Switches the user in the context, required after a login.<p>
739     *
740     * @param user the new user to use
741     * @param project the new users current project
742     * @param ouFqn the organizational unit
743     */
744    protected void switchUser(CmsUser user, CmsProject project, String ouFqn) {
745
746        m_user = user;
747        m_currentProject = project;
748        setOuFqn(ouFqn);
749    }
750}