001/* 002 * 003 * Copyright (c) 2015 The Regents of the University of California. 004 * All rights reserved. 005 * 006 * '$Author: crawl $' 007 * '$Date: 2017-08-23 22:42:39 -0700 (Wed, 23 Aug 2017) $' 008 * '$Revision: 1375 $' 009 * 010 * Permission is hereby granted, without written agreement and without 011 * license or royalty fees, to use, copy, modify, and distribute this 012 * software and its documentation for any purpose, provided that the above 013 * copyright notice and the following two paragraphs appear in all copies 014 * of this software. 015 * 016 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 017 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 018 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 019 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 020 * SUCH DAMAGE. 021 * 022 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 023 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 024 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 025 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 026 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 027 * ENHANCEMENTS, OR MODIFICATIONS. 028 * 029 */ 030package org.kepler.webview.server.handler; 031 032import java.io.File; 033import java.net.HttpURLConnection; 034import java.text.SimpleDateFormat; 035import java.util.Collections; 036import java.util.HashSet; 037import java.util.List; 038import java.util.Set; 039 040import org.kepler.webview.server.WebViewConfiguration; 041import org.kepler.webview.server.WebViewServer; 042 043import io.vertx.core.file.FileProps; 044import io.vertx.core.file.FileSystem; 045import io.vertx.core.http.HttpServerRequest; 046import io.vertx.core.json.JsonArray; 047import io.vertx.core.json.JsonObject; 048import io.vertx.ext.web.RoutingContext; 049 050public class NoMatchHandler extends BaseHandler { 051 052 public NoMatchHandler(WebViewServer server) { 053 super(server); 054 055 _allowWorkflowDownloads = WebViewConfiguration.getHttpServerAllowWorkflowDownloads(); 056 057 _dirsToIndex = WebViewConfiguration.getHttpServerDirectoriesToIndex(); 058 059 for(String dir: new HashSet<String>(_dirsToIndex)) { 060 if(!dir.startsWith(File.separator)) { 061 _dirsToIndex.remove(dir); 062 System.err.println("WARNING: directory to index will be ignored since it is not an absolute path: " + dir); 063 } 064 } 065 066 _fileSystem = WebViewServer.vertx().fileSystem(); 067 068 _rootDir = WebViewConfiguration.getHttpServerRootDir(); 069 if(_rootDir == null) { 070 _rootDir = ""; 071 } 072 073 } 074 075 @Override 076 public void handle(RoutingContext context) { 077 078 long timestamp = System.currentTimeMillis(); 079 080 HttpServerRequest req = context.request(); 081 String normalizedPath = context.normalisedPath(); 082 083 String path = WebViewServer.findFile(normalizedPath); 084 boolean isDir = false; 085 if(path != null) { 086 isDir = new File(path).isDirectory(); 087 if(!isDir && (_allowWorkflowDownloads || !path.endsWith(".kar"))) { 088 context.response().sendFile(path); 089 _server.log(req, context.user(), HttpURLConnection.HTTP_OK, timestamp, new File(path).length()); 090 return; 091 } 092 } 093 094 int error; 095 String message; 096 if(isDir) { 097 File indexFile = new File(path, "index.html"); 098 if(indexFile.exists()) { 099 context.response().sendFile(indexFile.getAbsolutePath()); 100 _server.log(req, context.user(), HttpURLConnection.HTTP_OK, timestamp, new File(path).length()); 101 return; 102 } else { 103 // remove trailing / from path 104 while(path.endsWith("/")) { 105 path.substring(0, path.length()-1); 106 } 107 for(String dir : _dirsToIndex) { 108 if(path.substring(_rootDir.length() + 1).startsWith(dir)) { 109 _sendDirectoryIndex(context, path, timestamp); 110 return; 111 } 112 } 113 } 114 System.err.println("Unhandled http request (directory) for: " + normalizedPath); 115 error = HttpURLConnection.HTTP_FORBIDDEN; 116 message = "File " + normalizedPath + ": permission denied."; 117 } else if(path != null && path.endsWith(".kar") && !_allowWorkflowDownloads) { 118 System.err.println("Unhandled http request (workflow permission denied) for: " + normalizedPath); 119 error = HttpURLConnection.HTTP_FORBIDDEN; 120 message = "File " + normalizedPath + ": permission denied."; 121 } else { 122 System.err.println("Unhandled http request (file not found) for: " + normalizedPath); 123 error = HttpURLConnection.HTTP_NOT_FOUND; 124 message = "File " + normalizedPath + " not found."; 125 } 126 127 context.response().headers().set("Content-Type", "text/html"); 128 // NOTE: always return not found since we do not want to disclose 129 // the presence of directories. 130 context.response().setChunked(true) 131 .write("<html>\n<body>\n<h2>" + message + "</h2>\n</body>\n</html>") 132 .setStatusCode(HttpURLConnection.HTTP_NOT_FOUND) 133 .end(); 134 _server.log(req, context.user(), error, timestamp); 135 } 136 137 /** Send a directory index. 138 * @param context The routing context 139 * @param path The directory index to send 140 * @param timestamp The request timestamp 141 */ 142 private void _sendDirectoryIndex(RoutingContext context, String path, long timestamp) { 143 HttpServerRequest req = context.request(); 144 String normalizedPath = context.normalisedPath(); 145 146 String accept = req.headers().get("accept"); 147 if(accept.contains("application/json") || accept.contains("text/javascript")) { 148 _fileSystem.readDir(path, readResult -> { 149 if(readResult.failed()) { 150 System.err.println("Error reading directory " + path + ": " + readResult.cause()); 151 _sendResponseWithError(req, readResult.cause().toString()); 152 _server.log(req, context.user(), HttpURLConnection.HTTP_INTERNAL_ERROR, timestamp, new File(path).length()); 153 return; 154 } 155 156 JsonArray array = new JsonArray(); 157 for(String file: readResult.result()) { 158 JsonObject json = new JsonObject() 159 .put("name", file.substring(_rootDir.length() + 1)); 160 FileProps props = _fileSystem.lpropsBlocking(file); 161 if(!props.isDirectory()) { 162 json.put("lastModified", props.lastModifiedTime()); 163 } 164 array.add(json); 165 } 166 167 _sendResponseWithSuccessJson(req, new JsonObject().put(normalizedPath.substring(1), array)); 168 _server.log(req, context.user(), HttpURLConnection.HTTP_OK, timestamp, new File(path).length()); 169 }); 170 } else { 171 _fileSystem.readDir(path, readResult -> { 172 if(readResult.failed()) { 173 System.err.println("Error reading directory " + path + ": " + readResult.cause()); 174 context.response() 175 .putHeader("Content-Type", "text/html") 176 .write("<html>\n<body>\n<h2>Error reading directory.</h2>\n</body>\n</html>") 177 .setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR) 178 .end(); 179 _server.log(req, context.user(), HttpURLConnection.HTTP_INTERNAL_ERROR, timestamp, new File(path).length()); 180 return; 181 } 182 StringBuilder buf = new StringBuilder(); 183 buf.append("<meta content='text/html;charset=utf-8' http-equiv='Content-Type'>"); 184 buf.append("<html><body><h2>Index of "); 185 buf.append(normalizedPath); 186 buf.append("</h2><ul>"); 187 188 List<String> files = readResult.result(); 189 Collections.sort(files); 190 191 for(String file: files) { 192 String f = file.substring(_rootDir.length() + 1 + normalizedPath.length()); 193 buf.append("<li><a href='"); 194 buf.append(normalizedPath); 195 buf.append("/"); 196 buf.append(f); 197 buf.append("'>"); 198 buf.append(f); 199 FileProps props = _fileSystem.propsBlocking(file); 200 if(props.isDirectory()) { 201 buf.append("/"); 202 } else { 203 buf.append(", "); 204 buf.append(_timestampFormat.format(props.lastModifiedTime())); 205 } 206 buf.append("</a></li>"); 207 } 208 209 buf.append("<li><a href='"); 210 buf.append(normalizedPath.substring(0, normalizedPath.lastIndexOf('/'))); 211 buf.append("'>Parent directory"); 212 buf.append("</a></li>"); 213 214 buf.append("</ul></body></html>"); 215 _sendResponseWithSuccessText(req, "text/html", buf.toString()); 216 _server.log(req, context.user(), HttpURLConnection.HTTP_OK, timestamp, new File(path).length()); 217 }); 218 } 219 220 } 221 222 /** If true, allow downloads for workflow files. */ 223 private boolean _allowWorkflowDownloads; 224 225 /** Set of directories that can be indexed. */ 226 private Set<String> _dirsToIndex; 227 228 /** Vertx file system. */ 229 private FileSystem _fileSystem; 230 231 /** Root directory of http server. */ 232 private String _rootDir; 233 234 /** Timestamp format for last modified times. */ 235 private SimpleDateFormat _timestampFormat = new SimpleDateFormat("HH:mm:ss z yyyy.MM.dd"); 236}