Java tutorial
/* * This file is part of Awake FILE. * Awake file: Easy file upload & download over HTTP with Java. * Copyright (C) 2015, KawanSoft SAS * (http://www.kawansoft.com). All rights reserved. * * Awake FILE is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Awake FILE 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * * Any modifications to this file must keep this entire header * intact. */ package org.kawanfw.file.servlet; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.logging.Level; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.kawanfw.commons.api.server.CommonsConfigurator; import org.kawanfw.commons.json.ListOfStringTransport; import org.kawanfw.commons.server.util.ServerLogger; import org.kawanfw.commons.util.FrameworkDebug; import org.kawanfw.commons.util.HtmlConverter; import org.kawanfw.commons.util.StringUtil; import org.kawanfw.commons.util.Tag; import org.kawanfw.commons.util.TransferStatus; import org.kawanfw.file.api.server.FileConfigurator; import org.kawanfw.file.reflection.ClassPathUtil; import org.kawanfw.file.servlet.nio.FileListAction; import org.kawanfw.file.servlet.nio.FileListFilesAction; import org.kawanfw.file.servlet.nio.FileMethodOneReturnAction; import org.kawanfw.file.servlet.nio.KawanfwSecurityManager; import org.kawanfw.file.servlet.util.CallUtil; import org.kawanfw.file.servlet.util.FileTransferManager; import org.kawanfw.file.servlet.util.HttpConfigurationUtil; import org.kawanfw.file.util.parms.Action; import org.kawanfw.file.util.parms.Parameter; import org.kawanfw.file.util.parms.ReturnCode; import org.kawanfw.file.version.FileVersionValues; /** * @author Nicolas de Pomereu * * The method executeRequest() is to to be called from the * ServerCallerRecv Servlet and Class. <br> * It will execute a client side request with a ServerCaller.call() * instance. * */ public class ServerFileDispatch { private static boolean DEBUG = FrameworkDebug.isSet(ServerFileDispatch.class); // A space public static final String SPACE = " "; public static String CR_LF = System.getProperty("line.separator"); public static KawanfwSecurityManager securityManager = null; /** * Constructor */ public ServerFileDispatch() { } /** * * Execute the dispatched request * * @param request * the http request * @param response * the http response * @param servletContextTempDir * The temp dir used by Servlets * @param commonsConfigurator * the client commons configurator * @param fileConfigurator * the client configurator for files * @throws IOException * if any Servlet Exception occurs */ public void executeRequest(HttpServletRequest request, HttpServletResponse response, File servletContextTempDir, CommonsConfigurator commonsConfigurator, FileConfigurator fileConfigurator) throws IOException { OutputStream out = null; try { // Immediate catch if we are asking a file upload, because // parameters are // in unknown sequence. We know it's a upload action if it's mime // multipart if (ServletFileUpload.isMultipartContent(request)) { ServerFileUploadAction serverFileUploadAction = new ServerFileUploadAction(); serverFileUploadAction.executeAction(request, response, servletContextTempDir, commonsConfigurator, fileConfigurator); return; } debug("ServerFileDispatch begin 2"); // The action & filename (for file size ask) String action = null; // We must trap the IllegalArgumentException to rethrow properly to // client // This happens if there is an encryption problem try { action = request.getParameter(Parameter.ACTION); } catch (IllegalArgumentException e) { out = response.getOutputStream(); throw e; } action = StringUtil.getTrimValue(action); debug("ACTION : " + action); // Special action for Login, because Token does not exists and must // be built if (action.equals(Action.LOGIN_ACTION) || action.equals(Action.BEFORE_LOGIN_ACTION)) { ServerLoginAction serverLoginAction = new ServerLoginAction(); serverLoginAction.executeAction(request, response, commonsConfigurator, action); return; } out = response.getOutputStream(); // Only if there is a call action, we may execute authorized classes // without authentication/login if (action.equals(Action.CALL_ACTION) || action.equals(Action.CALL_ACTION_HTML_ENCODED)) { // The class name String methodName = request.getParameter(Parameter.METHOD_NAME); methodName = StringUtil.getTrimValue(methodName); String className = StringUtils.substringBeforeLast(methodName, "."); Class<?> c = Class.forName(className); CallUtil callUtil = new CallUtil(c, fileConfigurator); boolean callAllowed = callUtil.isCallableNotAuthenticated(); if (callAllowed) { if (action.equals(Action.CALL_ACTION) || action.equals(Action.CALL_ACTION_HTML_ENCODED)) { ServerCallAction serverCallAction = new ServerCallAction(); serverCallAction.call(request, commonsConfigurator, fileConfigurator, out, null); } return; } } // For all other actions, we check the parameters // The username (used for token re-compilation) String username = request.getParameter(Parameter.USERNAME); username = StringUtil.getTrimValue(username); debug("username : " + username); // // For old call(): // try { // username = StringUtil.fromBase64(username); // } catch (Exception e) { // } // The login may be in clear // Authentication Token with SHA-1(login + secret value) String token = request.getParameter(Parameter.TOKEN); token = StringUtil.getTrimValue(token); if (!ServerFileDispatch.isTokenValid(username, token, commonsConfigurator)) { debug("invalid token!"); debug("username: " + username); debug("token : " + token); writeLine(out, TransferStatus.SEND_OK); writeLine(out, ReturnCode.INVALID_LOGIN_OR_PASSWORD); return; } // Notify to Kawan in async mode using a secured Thread that // the user has successfully logged (done once in JVM session per // username). // No notification is done if user.home/.kawansoft/no_notify.txt // exists // or web server name is localhost or 127.0.0.1 if (!KawanNotifier.existsNoNotifyTxt() && !KawanNotifier.usernameAlreadyLogged(username) && !KawanNotifier.serverNameIsLocalhost()) { KawanNotifier kawanNotifier = new KawanNotifier(username, "AwakeFile_" + FileVersionValues.VERSION); kawanNotifier.start(); } // Ok, install our security manager //installSecurityManager(fileConfigurator); // Displays class path if (DEBUG) ClassPathUtil.displayClasspath(); // The filename String filename = request.getParameter(Parameter.FILENAME); filename = StringUtil.getTrimValue(filename); // Call to a File method that returns one result (no list return) if (action.equals(Action.FILE_METHOD_ONE_RETURN_ACTION)) { FileMethodOneReturnAction fileMethodOneReturnAction = new FileMethodOneReturnAction(); fileMethodOneReturnAction.call(request, commonsConfigurator, fileConfigurator, out, username, filename); return; } // Call to a File.list() or File.list(FilenameFilter) else if (action.equals(Action.FILE_LIST_ACTION)) { FileListAction fileListAction = new FileListAction(); fileListAction.list(request, commonsConfigurator, fileConfigurator, out, username, filename); return; } // Call to a File.listFiles() or File.listFiles(FileFilter) // or File.listFiles(FilenameFilter) else if (action.equals(Action.FILE_LIST_FILES_ACTION)) { FileListFilesAction fileListFilesAction = new FileListFilesAction(); fileListFilesAction.listFiles(request, commonsConfigurator, fileConfigurator, out, username, filename); return; } else if (action.equals(Action.CALL_ACTION) || action.equals(Action.CALL_ACTION_HTML_ENCODED)) { ServerCallAction serverCallAction = new ServerCallAction(); serverCallAction.call(request, commonsConfigurator, fileConfigurator, out, username); return; } else if (action.equals(Action.GET_FILE_LENGTH_ACTION)) { long result = actionGetListFileLength(fileConfigurator, username, filename); writeLine(out, TransferStatus.SEND_OK); writeLine(out, Long.toString(result)); } else if (action.equals(Action.GET_JAVA_VERSION)) { String javaVersion = System.getProperty("java.version"); writeLine(out, TransferStatus.SEND_OK); writeLine(out, javaVersion); } else if (action.equals(Action.DOWNLOAD_FILE_ACTION)) { String chunkLengtgStr = request.getParameter(Parameter.CHUNKLENGTH); long chunkLength = Long.parseLong(chunkLengtgStr); boolean result = new FileTransferManager().download(out, fileConfigurator, username, filename, chunkLength); if (!result) { // Impossible to find the file on server writeLine(out, TransferStatus.SEND_OK); writeLine(out, Tag.FileNotFoundException); // throw new FileNotFoundException( // "File not found on remote server: " + filename); } } else { throw new IllegalArgumentException("Invalid Client Action: " + action); } return; } catch (Throwable throwable) { if (DEBUG) throwable.printStackTrace(System.out); Throwable finalThrowable = getFinalThrowable(throwable); writeLine(out, TransferStatus.SEND_FAILED); writeLine(out, finalThrowable.getClass().getName()); // Exception class name writeLine(out, ServerUserThrowable.getMessage(finalThrowable)); // Exception // message writeLine(out, ExceptionUtils.getStackTrace(finalThrowable)); // stack trace try { ServerLogger.getLogger().log(Level.WARNING, Tag.PRODUCT_EXCEPTION_RAISED + " " + ServerUserThrowable.getMessage(finalThrowable)); ServerLogger.getLogger().log(Level.WARNING, Tag.PRODUCT_EXCEPTION_RAISED + " " + ExceptionUtils.getStackTrace(finalThrowable)); } catch (Exception e1) { e1.printStackTrace(); e1.printStackTrace(System.out); } } } /** * Analyse the throwable and build the final Exception/Throwable * @param throwable the input throwable thrown * @return the new rewritten Throwable */ public static Throwable getFinalThrowable(Throwable throwable) { Throwable finalThrowable = null; Throwable cause = throwable.getCause(); if (cause != null && cause instanceof ClassNotFoundException || cause != null && cause instanceof NoClassDefFoundError) { finalThrowable = new ClassNotFoundException(throwable.getMessage()); } else if (cause != null && cause instanceof UnsupportedClassVersionError) { finalThrowable = new UnsupportedClassVersionError(throwable.getMessage()); } else { if (cause != null) { finalThrowable = cause; } else { finalThrowable = throwable; } } return finalThrowable; } /** * NOT USED ANYMORE * Install the Security Manager that restricts FileFilter and FilenameFilter * to write/delete files. * @param fileConfigurator the file configurator in use */ @SuppressWarnings("unused") private void installSecurityManager(FileConfigurator fileConfigurator) { // Ok, install our security manager if (System.getSecurityManager() == null) { securityManager = new KawanfwSecurityManager(); System.setSecurityManager(securityManager); } } /** * Write a line of string on the servlet output stream. Will add the * necessary CR_LF * * @param out * the servlet output stream * @param s * the string to write * @throws IOException */ private void writeLine(OutputStream out, String s) throws IOException { out.write((s + CR_LF).getBytes()); } /** * Check the validity of the (username, token) pair <br> * Will return false if the operation is now allowed! * * @param username * the username to check * @param token * the associated token with the username * @param commonsConfigurator * the user configuration * * @return true if the pair (username, token) is verified and ok. * @throws Exception */ public static boolean isTokenValid(String username, String token, CommonsConfigurator commonsConfigurator) throws Exception { // OK! Now build a token with SHA-1(username + secretValue) String tokenRecomputed = CommonsConfiguratorCall.computeAuthToken(commonsConfigurator, username); if (token == null || !token.equals(tokenRecomputed)) { debug("username : " + username + ":"); debug("token : " + token + ":"); debug("tokenRecomputed: " + tokenRecomputed + ":"); return false; } return true; } /** * Action: get a file list length * * @param fileConfigurator * @param filename * the filelist * @return the length of file list */ private long actionGetListFileLength(FileConfigurator fileConfigurator, String username, String filename) throws IOException { debug("Action.GET_FILE_LENGTH_ACTION"); long result = 0; // We have in fact a list of files List<String> files = ListOfStringTransport.fromJson(filename); // actionGetListFileLength: We must convert each element of List<String> // files from Html files = HtmlConverter.fromHtml(files); for (String theFilename : files) { // result += fileActionManager.length(fileConfigurator, // username, theFilename); theFilename = HttpConfigurationUtil.addRootPath(fileConfigurator, username, theFilename); File file = new File(theFilename); result += file.length(); } return result; } private static void debug(String s) { if (DEBUG) { ServerLogger.getLogger().log(Level.WARNING, s); } } }