<%@ include file="../auth-check.jspf" %>

<% 
    if (request.getAttribute("isMstrNotAuthenticated") != null) {
        response.setStatus(401); // Unauthorized
        out.print("{\"error\": \"User not authenticated.\"}");
        return;
    }

    if (request.getAttribute("isMstrSessionAlive") != null) {
%> 
<%@ page import="java.io.*"%>
<%@ page import="com.google.gson.Gson"%>
<%@ page import="java.net.*"%>
<%@ page import="java.util.*" %>
<%@ page import="java.util.regex.*"%>
<%@ page import="java.nio.file.*" %>
<%@ page import="java.nio.charset.StandardCharsets" %>
<%@ page import="org.owasp.encoder.Encode"%>
<%@ page import="org.apache.commons.codec.binary.Base64"%>
<%@page contentType="application/json; charset=UTF-8"%>
<%@ page import="org.apache.commons.fileupload.*, org.apache.commons.fileupload.disk.*, org.apache.commons.fileupload.servlet.*, org.apache.commons.io.IOUtils" %>

<%!
    String getAbsoluteChartsPath(ServletContext ctx, String filePath) {
        return ctx.getRealPath("/plugins/VitaraCharts/" + filePath);
    }

    String getRelativeChartsPath(String filePath) {
        return "/plugins/VitaraCharts/" + filePath;
    }

    public String getRealPath(ServletContext application,  String path) {
        return application.getRealPath(getRelativeChartsPath(path));
    }

    boolean isAllowedToServe(ServletContext ctx, String filePath) {
        try {
            String targetPath = getAbsoluteChartsPath(ctx, filePath);
            String rootPath = getAbsoluteChartsPath(ctx, "");

            if (targetPath == null || rootPath == null) {
                return false;
            }

            File targetFile = new File(targetPath).getCanonicalFile();
            File chartsRoot = new File(rootPath).getCanonicalFile();

            return targetFile.getPath().startsWith(chartsRoot.getPath());
        } catch (IOException e) {
            return false;
        }
    }

    String[] getDirs(String path){
        File file = new File(path);
        String[] directories = file.list(new FilenameFilter() {
            @Override
            public boolean accept(File current, String name) {
                return new File(current, name).isDirectory();
            }
        });
        return directories;
    }

    String readFolderContent(ServletContext ctx, String filePath) throws FileNotFoundException, IOException {
        String res = "";
        String file = getAbsoluteChartsPath(ctx, filePath);
        String[] directories = getDirs(file);
        StringBuffer sb = new StringBuffer();      
        for (int i = 0; i < directories.length; i++) {
            sb.append(directories[i]+"\n");
        }
        res = sb.toString();
        return res;
    }

    String readFileIS(ServletContext ctx, URLRequest mURLRequest) throws IOException {
        String fullPath = getAbsoluteChartsPath(ctx, mURLRequest.url);
        File file = new File(fullPath);

        if (!file.exists() || !file.isFile()) {
            mURLRequest.status = 404;
            mURLRequest.message = "File not found: " + fullPath;
            return mURLRequest.getResponse();
        }
    
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
    
        mURLRequest.status = 200;
        mURLRequest.message = "success";
        mURLRequest.data = sb.toString();
        return mURLRequest.getResponse();
    }    

    String getResponse(String data){
        return new String(Base64.encodeBase64(data.getBytes()));
    }
    class FileResponse {
        int status;
        String message;
        String data;
        String type;
        String ctx;

        FileResponse(int status, String message, String data, String type, String ctx) {
            this.status = status;
            this.message = message;
            this.data = data;
            this.type = type;
            this.ctx = ctx;
        }
    }

    private class URLRequest {
        int status;
        String callback, url, message, data, fileType, context;

        URLRequest(String callback, String url, String fileType, String context) {
            this.callback = htmlEncode(callback);
            this.url = htmlEncode(url);
            this.fileType = htmlEncode(fileType);
            this.context = htmlEncode(context);
        }

        String htmlEncode(String data) {
            return Encode.forHtml(data);
        }

        String encode(String data) {
            try {
                return new String(Base64.encodeBase64(data.getBytes()));
            } catch(Exception ex) {
                //ex.printStackTrace();
            }
            return "";
        }

        String getResponse() {
            FileResponse res = new FileResponse(
                this.status,
                this.message,
                this.data,
                this.fileType,
                this.context
            );

            Gson gson = new Gson();
            String json = gson.toJson(res);

            if (this.callback == null || this.callback.equals("null") || this.callback.isEmpty()) {
                return json;
            } else {
                return this.callback + "(" + json + ")";
            }
        }

        void println(JspWriter out, String data) throws IOException {
            out.println(data);
        }

        void printError(JspWriter out, int status, String msg) throws IOException {
            this.status = status;
            this.message = msg;
            this.data = "VITARA_FILE_READ_EXCEPTION";
            this.println(out, this.getResponse());
        }
    }

    boolean handleFontDelete(ServletContext ctx, HttpServletRequest request) {
        try {
            String fileName = request.getParameter("fileName");
            String fontPath = getAbsoluteChartsPath(ctx, "/style/fonts/custom-fonts/" + fileName);
            File fontFile = new File(fontPath);
    
            if (fontFile.exists()) {
                return fontFile.delete();
            } else {
                return false;
            }
        } catch (Exception e) {
            //e.printStackTrace();
            return false;
        }
    }

    boolean handleFontUpload(ServletContext ctx, HttpServletRequest request) {
        try {
            String contentType = request.getContentType();
            String fileName = request.getParameter("fileName");
            if (fileName != null) {
                fileName = new File(fileName).getName();
            }

            // Validate file extension
            String fileExtension = "";
            int i = fileName.lastIndexOf('.');
            if (i > 0) {
                fileExtension = fileName.substring(i+1);
            }

            if (!Arrays.asList("ttf", "otf", "woff", "woff2").contains(fileExtension.toLowerCase())) {
                return false; // Invalid file type
            }
            String fontName = request.getParameter("fontName");
            String boundary = "--" + contentType.split("boundary=")[1];
            ServletInputStream input = request.getInputStream();
            ByteArrayOutputStream dataBuffer = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            byte[] fileContent = null;
            int bytesRead;
    
            while ((bytesRead = input.read(buffer)) != -1) {
                dataBuffer.write(buffer, 0, bytesRead);
            }
    
            String fullBody = dataBuffer.toString("ISO-8859-1"); // use raw charset to preserve bytes
            String[] parts = fullBody.split(boundary);
            fileContent = dataBuffer.toByteArray();

            // Validate file size (e.g., max 5MB)
            final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
            if (fileContent.length > MAX_FILE_SIZE) {
                return false; // File too large
            }
    
            for (String part : parts) {
                if (part.contains("name=\"fontFile\"")) {
                    // Find start of file bytes
                    int dataStart = fullBody.indexOf(part) + part.indexOf("\r\n\r\n") + 4;
                    int dataEnd = fullBody.indexOf(boundary, dataStart) - 4;
    
                    if (dataStart > 0 && dataEnd > dataStart) {
                        fileContent = Arrays.copyOfRange(dataBuffer.toByteArray(), dataStart, dataEnd);
                    }
                }
            }
    
            if (fileName != null && fileContent != null) {
                // Generate a random unique file name
                String uniqueFileName = java.util.UUID.randomUUID().toString() + "." + fileExtension;
                String savePath = getAbsoluteChartsPath(ctx, "style/fonts/custom-fonts/" + uniqueFileName);
                FileOutputStream fout = new FileOutputStream(savePath);
                fout.write(fileContent);
                fout.close();
                return true;
            } else {
                return false;
            }
        } catch(IOException e) {
            return false;
        }
    }

    boolean handleFileUpdate(ServletContext ctx, HttpServletRequest request, String fileType, String filePath, String data, String action) {
        try {
            // Special handling for fonts
            if ("font".equalsIgnoreCase(fileType)) {
                if ("upload".equalsIgnoreCase(action)) {
                    return handleFontUpload(ctx, request);
                } else if ("delete".equalsIgnoreCase(action)) {
                    return handleFontDelete(ctx, request);
                } else {
                    throw new IllegalArgumentException("Unsupported font action: " + action);
                }
            }
    
            // For settings, styles, translations paste the entire data into the file
            File file = new File(getAbsoluteChartsPath(ctx, filePath));
            file.getParentFile().mkdirs();
            if (!file.exists()) {
                file.createNewFile();
            }
            Files.write(file.toPath(), data.getBytes(StandardCharsets.UTF_8));
            return true;
    
        } catch (IOException e) {
            return false;
        }
    }
%> 

<% 
    Set<String> allowedFiles = new HashSet<>();
    allowedFiles.add("custom/global.txt");
    allowedFiles.add("custom/customStyles.css");
    allowedFiles.add("custom/IBCSGlobal.txt");
    allowedFiles.add("custom/customIBCSStyles.css");
    allowedFiles.add("custom/MessageBundle_en.txt");
    allowedFiles.add("custom/MessageBundle_en_us.txt");
    allowedFiles.add("custom/MessageBundle_es.txt");
    allowedFiles.add("custom/MessageBundle_de.txt");
    allowedFiles.add("custom/MessageBundle_fr.txt");
    allowedFiles.add("custom/MessageBundle_ko.txt");

    String callbackFun = request.getParameter("callback");
    String fileType = request.getParameter("type");
    String filePath = request.getParameter("file");

    if (!allowedFiles.contains(filePath)) {
        URLRequest mURLRequest = new URLRequest(callbackFun, filePath, fileType, request.getParameter("ctx"));
        mURLRequest.printError(out, 403, "File access not allowed.");
        return;
    }

    String context = request.getParameter("ctx");
    String data = request.getParameter("data");
    String action = request.getParameter("action");

    URLRequest mURLRequest = new URLRequest(callbackFun, filePath, fileType, context);

    try {
        if (isAllowedToServe(application, getRelativeChartsPath(mURLRequest.url))) {
            if (data == null && action == null) {
                if ("folder".equalsIgnoreCase(fileType)) {
                    String content = readFolderContent(application, filePath);
                    Map<String, Object> jsonResponse = new HashMap<>();
                    jsonResponse.put("status", 200);
                    jsonResponse.put("message", "Read successfully");
                    jsonResponse.put("data", getResponse(content));
                    Gson gson = new Gson();
                    String json = gson.toJson(jsonResponse);
                    out.println(json);
                } else {
                    String fileContent = readFileIS(application, mURLRequest);
                    response.setContentType("text/plain;charset=UTF-8");
                    response.setCharacterEncoding("UTF-8");
                    out.print(fileContent);
                }
            } else {
                String requestToken = request.getParameter("csrfToken");
                String sessionToken = (String) session.getAttribute("csrfToken");

                if (requestToken == null || !requestToken.equals(sessionToken)) {
                    mURLRequest.printError(out, 403, "Invalid CSRF token.");
                    return;
                }
                boolean success = handleFileUpdate(application, request, fileType, filePath, data, action);
                if (success) {
                    mURLRequest.println(out, "{\"status\": \"200\", \"message\": \"Updated successfully\"}");
                } else {
                    mURLRequest.printError(out, 400, "Update failed");
                }
            }
        } else {
            mURLRequest.printError(out, 400, "Invalid file path");
        }
    } catch (Exception e) {
        mURLRequest.printError(out, 500, "Exception occurred: " + e.getMessage());
    }
%> 
<% } %>
