# EmbeddedWebHandler.java

    /*
    ** Module   : EmbeddedWebHandler.java
    ** Abstract : Define handlers to run the embedded web app from within FWD.
    **
    ** Copyright (c) 2018-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 CA  20180724 Created initial version
    ** 002 CA  20180815 adm_windows.json will be served from the embedded/adm_windows.json path.
    **                  The DefaultHandler must not be registered here, otherwise the /gui path will
    **                  not be found.
    ** 003 IAS 20210423 Make EmbeddedWebAppHandler.AuthWorker thread daemon.
    **     CA  20210816 Do not set the request.handled to false (and do not handle the request if already handled).
    ** 004 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 005 GBB 20230825 Complete rework of the class. Renamed from EmbeddedWebAppHandler to EmbeddedWebHandler.
    **                  Remote spawn implementation removed.
    ** 006 GBB 20231027 Template placeholder constant for auto-login client url renamed to AUTO_LOGIN_DATA.
    ** 007 GBB 20240517 Resolving web pages template placeholder PLACEHOLDER_PROXY_PATH.
    ** 008 GBB 20240530 Configurable /embedded path.
    ** 009 TJD 20240415 Allow serving JS libraries from webjars
    ** 010 GBB 20240709 DriverType moved to outer class WebDriverType.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.main;

    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.p2j.web.*;

    import javax.servlet.http.*;

    import org.eclipse.jetty.server.*;
    import org.eclipse.jetty.server.handler.*;
    import org.eclipse.jetty.util.resource.*;

    import java.lang.*;
    import java.util.*;

    /**
     * Define handlers to run the embedded web app from within fwd.  The handlers are run on the
     * <code>/embedded</code>, where <code>/embedded/fwd</code> will serve content from the
     * <code>com/goldencode/p2j/ui/client/gui/driver/web/res/</code> package and the
     * <code>/embedded/custom</code> will server content from the <code>embedded/</code> sub-package
     * of the application's package root (as retrieved via {@link SourceNameMapper#getPackageRoot}.
     */
    public class EmbeddedWebHandler
    extends WebDriverHandler
    {
       /** Logger. */
       private static final CentralLogger LOG = CentralLogger.get(EmbeddedWebHandler.class);

       /** The path to for the embedded configs. */
       private static final String TARGET_EMBEDDED_CONFIGS = TARGET_EMBEDDED + "/adm_windows.json";

       /** The path for the embedded custom resources. */
       private static final String TARGET_EMBEDDED_CUSTOM = TARGET_EMBEDDED + "/custom";

       /** The path for the embedded base web resources. */
       private static final String TARGET_EMBEDDED_RES = TARGET_EMBEDDED + "/fwd";

       /** The path for the embedded base web resources. */
       private static final String EMBEDDED_RES_PATH = "com/goldencode/p2j/ui/client/gui/driver/web/res/";

       /** The filename of the resource for the login page partial. */
       private static final String HTML_PARTIAL_HTML_FILENAME = "web_partial_login_embedded.html";

       /** Template placeholder for the embedded context path. See {@link #TARGET_EMBEDDED}. */
       protected static final String PLACEHOLDER_EMBEDDED_PATH = "${embeddedPath}";

       /**
        * Template placeholder for the context path before /webres. Can be the proxy path or empty (root).
        * Different from {@link #PLACEHOLDER_PROXY_PATH} that can be omitted with
        * {@link #REMOVE_PROXY_PATH_IN_WEB_PAGES}.
        */
       protected static final String PLACEHOLDER_WEBRES_PATH = "${webresPath}";

       /**
        * Private constructor for EmbeddedWebHandle. Initializes all the handlers for the embedded web app.
        * Reads directory configs from the server context, because handle() runs without security context.
        */
       EmbeddedWebHandler()
       {
          super("client:cmd-line-option:startup-procedure=emain.p", "client:web:embedded=true");

          addHandler(new HandlerCollection(
             createContextHandler(TARGET_EMBEDDED_RES, EMBEDDED_RES_PATH),
             createContextHandler(TARGET_EMBEDDED_CUSTOM, PKG_ROOT + "embedded/"),
             // in build.gradle, add a WARNING for each zip artifact which is hard-coded here.
             // do not expose the "/" root resource folder - as this will allow access to any class path
             // file, which includes jar'ed classes, configurations, etc
             // this needs to be inline with build.gradle versions
             new DojoToolkitHandler(TARGET_EMBEDDED),
             new WebResourceHandler(TARGET_EMBEDDED, "/bootstrap", "META-INF/resources/webjars/bootstrap/3.4.1"),
             new WebResourceHandler(TARGET_EMBEDDED, "/d3", "META-INF/resources/webjars/d3/4.13.0/build"),
             new WebResourceHandler(TARGET_EMBEDDED, "/fonts", "fonts"),
             new WebResourceHandler(TARGET_EMBEDDED, "/jquery", "META-INF/resources/webjars/jquery/3.7.1"),
             new WebResourceHandler(TARGET_EMBEDDED, "/jquery-ui", "META-INF/resources/webjars/jquery-ui/1.13.2"),
             new WebResourceHandler(TARGET_EMBEDDED, "/material-components-web", "material-components-web"),
             new WebResourceHandler(TARGET_EMBEDDED, "/material-design-icons", "material-design-icons"),
             new WebResourceHandler(TARGET_EMBEDDED, "/tabulator", "tabulator-master-2.12.0")
          ));
       }

       /**
        * Creates a new context handler serving class path resources from the given path.
        *
        * @param    context
        *           The context used to server the resources.
        * @param    resPath
        *           The path were the resources reside.
        *
        * @return   A new handler.
        */
       private static ContextHandler createContextHandler(String context, String resPath)
       {
          ResourceHandler res = new ResourceHandler();
          res.setDirectoriesListed(false);
          ContextHandler ctx = new ContextHandler(context);
          ctx.setBaseResource(Resource.newClassPathResource(resPath));
          ctx.setHandler(res);

          return ctx;
       }

       /**
        * Processes embedded web requests.
        *
        * @param    target
        *           The target path.
        * @param    base
        *           The HTTP request, Jetty style.
        * @param    request
        *           The HTTP request.
        * @param    response
        *           The HTTP response.
        */
       @Override
       public void handle(String target, Request base, HttpServletRequest request, HttpServletResponse response)
       {
          try
          {
             super.handle(target, base, request, response);

             if (base.isHandled())
             {
                return;
             }

             if (!target.equals(TARGET_EMBEDDED) &&
                !target.equals(TARGET_EMBEDDED_CONFIGS))
             {
                // do not set 'handled' to false, as it may have to be processed by another handler.
                return;
             }

             base.setHandled(true);

             String method = request.getMethod().toUpperCase();
             if (target.equals(TARGET_EMBEDDED) && method.equals("POST"))
             {
                handleStartClientRequest(base, request, response, true, WebDriverType.EMBEDDED);
                return;
             }

             if (!method.equals("GET"))
             {
                response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                return;
             }

             String clientUrl = null;
             // TODO: enable auto login (and logout page) when cross document msgs can handle it
             /*if (target.equals(TARGET_EMBEDDED))
             {
                SpawnerResult spawnerResult = handleSsoAutoLogin(base, request, true, DriverType.EMBEDDED);
                if (spawnerResult != null)
                {
                   // on successful auto login, the new client url will be embedded in the login page
                   clientUrl = spawnerResult.uri;
                }
             }*/

             handleStatic(target, request, response, clientUrl);
          }
          catch (Throwable t)
          {
             LOG.warning("Unexpected exception:", t);
             sendError(response, SupportedHttpCode.UNEXPECTED_ERROR, null);
          }
       }

       /**
        * Handles HTTP requests for static resources, i.e. index / SSO login page and configs.
        *
        * @param    target
        *           The target path.
        * @param    request
        *           The HTTP request.
        * @param    response
        *           The HTTP response.
        * @param    clientUrl
        *           The spawned client URL.
        */
       private void handleStatic(String target,
                                 HttpServletRequest request,
                                 HttpServletResponse response,
                                 String clientUrl)
       {
          if (target.equals(TARGET_EMBEDDED_CONFIGS))
          {
             serveStatic(response,
                         "application/json",
                         null,
                         PKG_ROOT + "embedded/adm_windows.json");
          }
          else if (target.equals(TARGET_EMBEDDED))
          {
             String serverProxyPath = getServerProxyPath(resolveForwardedHost(request));
             String htmlContent = loadPartial(HTML_PARTIAL_HTML_FILENAME,
                                              PKG_RES_DIR,
                                              new HashMap<String, String>() {{
                                                 put(PLACEHOLDER_WEBRES_PATH, serverProxyPath);
                                                 put(PLACEHOLDER_PROXY_PATH,
                                                     REMOVE_PROXY_PATH_IN_WEB_PAGES ? "" : serverProxyPath);
                                                 put(PLACEHOLDER_EMBEDDED_PATH, TARGET_EMBEDDED);
                                              }});

             SsoAuthenticator ssoAuthenticator = SecurityManager.getInstance().ssoSm.getSsoAuthenticator();
             if (ssoAuthenticator != null)
             {
                String ssoInflatedTemplate = ssoAuthenticator.getLoginPage(htmlContent);
                if (ssoInflatedTemplate != null)
                {
                   htmlContent = ssoInflatedTemplate;
                }
             }

             if (htmlContent == null)
             {
                LOG.fine("File not found " + HTML_PARTIAL_HTML_FILENAME);
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
             }

             Map<String, String> placeholderValuePairs = new HashMap<>();
             if (clientUrl != null)
             {
                placeholderValuePairs.put(AUTO_LOGIN_DATA, clientUrl);
             }
             placeholderValuePairs.put(PLACEHOLDER_HTML_TEMPLATE, htmlContent);
             placeholderValuePairs.put(PLACEHOLDER_TITLE, PAGE_TITLE_LOGIN == null ?
                "Login (Embedded Gui Web Client)" :
                PAGE_TITLE_LOGIN);
             placeholderValuePairs.put(PLACEHOLDER_PROXY_PATH, getServerProxyPath(request));

             serveHtml(response, placeholderValuePairs, getResPathCurrentDir("web_landing.html"));
          }
          else
          {
             response.setStatus(HttpServletResponse.SC_NOT_FOUND);
          }
       }
    }