1 7 package winstone; 8 9 import java.io.File ; 10 import java.io.FileInputStream ; 11 import java.io.IOException ; 12 import java.io.InputStream ; 13 import java.io.OutputStream ; 14 import java.io.StringWriter ; 15 import java.io.Writer ; 16 import java.text.DateFormat ; 17 import java.text.SimpleDateFormat ; 18 import java.util.ArrayList ; 19 import java.util.Arrays ; 20 import java.util.Date ; 21 import java.util.Iterator ; 22 import java.util.List ; 23 import java.util.StringTokenizer ; 24 25 import javax.servlet.ServletConfig ; 26 import javax.servlet.ServletException ; 27 import javax.servlet.http.HttpServlet ; 28 import javax.servlet.http.HttpServletRequest ; 29 import javax.servlet.http.HttpServletResponse ; 30 31 39 public class StaticResourceServlet extends HttpServlet { 40 final static String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path"; 42 final static String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path"; 43 final static String CACHED_RESOURCE_DATE_HEADER = "If-Modified-Since"; 44 final static String LAST_MODIFIED_DATE_HEADER = "Last-Modified"; 45 final static String RANGE_HEADER = "Range"; 46 final static String ACCEPT_RANGES_HEADER = "Accept-Ranges"; 47 final static String CONTENT_RANGE_HEADER = "Content-Range"; 48 final static String RESOURCE_FILE = "winstone.LocalStrings"; 49 private DateFormat sdfFileDate = new SimpleDateFormat ("dd-MM-yyyy HH:mm"); 50 private File webRoot; 51 private String prefix; 52 private boolean directoryList; 53 54 public void init(ServletConfig config) throws ServletException { 55 super.init(config); 56 this.webRoot = new File (config.getInitParameter("webRoot")); 57 this.prefix = config.getInitParameter("prefix"); 58 String dirList = config.getInitParameter("directoryList"); 59 this.directoryList = (dirList == null) 60 || dirList.equalsIgnoreCase("true") 61 || dirList.equalsIgnoreCase("yes"); 62 } 63 64 public void doPost(HttpServletRequest request, HttpServletResponse response) 65 throws ServletException , IOException { 66 doGet(request, response); 67 } 68 69 public void doGet(HttpServletRequest request, HttpServletResponse response) 70 throws ServletException , IOException { 71 boolean isInclude = (request.getAttribute(INCLUDE_SERVLET_PATH) != null); 72 boolean isForward = (request.getAttribute(FORWARD_SERVLET_PATH) != null); 73 String path = null; 74 75 if (isInclude) 76 path = (String ) request.getAttribute(INCLUDE_SERVLET_PATH); 77 else { 78 path = request.getServletPath(); 79 } 80 81 path = WinstoneRequest.decodeURLToken(path); 83 84 long cachedResDate = request.getDateHeader(CACHED_RESOURCE_DATE_HEADER); 85 Logger.log(Logger.DEBUG, Launcher.RESOURCES, 86 "StaticResourceServlet.PathRequested", new String [] { 87 getServletConfig().getServletName(), path }); 88 89 File res = path.equals("") ? this.webRoot : new File ( 91 this.webRoot, path); 92 93 if (!res.exists()) 95 response.sendError(HttpServletResponse.SC_NOT_FOUND, Launcher.RESOURCES 96 .getString("StaticResourceServlet.PathNotFound", path)); 97 98 else if (!isDescendant(this.webRoot, res, this.webRoot)) { 100 Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "StaticResourceServlet.OutsideWebroot", 101 new String [] {res.getCanonicalPath(), this.webRoot.toString()}); 102 response.sendError(HttpServletResponse.SC_FORBIDDEN, Launcher.RESOURCES 103 .getString("StaticResourceServlet.PathInvalid", path)); 104 } 105 106 else if (!isInclude && !isForward && isDescendant(new File (this.webRoot, "WEB-INF"), res, this.webRoot)) 108 response.sendError(HttpServletResponse.SC_NOT_FOUND, Launcher.RESOURCES 109 .getString("StaticResourceServlet.PathInvalid", path)); 110 111 else if (!isInclude && !isForward && isDescendant(new File (this.webRoot, "META-INF"), res, this.webRoot)) 113 response.sendError(HttpServletResponse.SC_NOT_FOUND, Launcher.RESOURCES 114 .getString("StaticResourceServlet.PathInvalid", path)); 115 116 else if (res.isDirectory()) { 118 if (path.endsWith("/")) { 119 if (this.directoryList) 125 generateDirectoryList(request, response, path); 126 else 127 response.sendError(HttpServletResponse.SC_FORBIDDEN, 128 Launcher.RESOURCES.getString("StaticResourceServlet.AccessDenied")); 129 } else 130 response.sendRedirect(this.prefix + path + "/"); 131 } 132 133 else if (!isInclude && (cachedResDate != -1) 135 && (cachedResDate < (System.currentTimeMillis() / 1000L * 1000L)) 136 && (cachedResDate >= (res.lastModified() / 1000L * 1000L))) { 137 String mimeType = getServletContext().getMimeType( 138 res.getName().toLowerCase()); 139 if (mimeType != null) 140 response.setContentType(mimeType); 141 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 142 response.setContentLength(0); 143 response.flushBuffer(); 144 } 145 146 else if ((request.getHeader(RANGE_HEADER) == null) || isInclude) { 148 String mimeType = getServletContext().getMimeType( 149 res.getName().toLowerCase()); 150 if (mimeType != null) 151 response.setContentType(mimeType); 152 InputStream resStream = new FileInputStream (res); 153 154 response.setStatus(HttpServletResponse.SC_OK); 155 response.setContentLength((int) res.length()); 156 response.addDateHeader(LAST_MODIFIED_DATE_HEADER, res.lastModified()); 158 OutputStream out = null; 159 Writer outWriter = null; 160 try { 161 out = response.getOutputStream(); 162 } catch (IllegalStateException err) { 163 outWriter = response.getWriter(); 164 } catch (IllegalArgumentException err) { 165 outWriter = response.getWriter(); 166 } 167 byte buffer[] = new byte[4096]; 168 int read = resStream.read(buffer); 169 while (read > 0) { 170 if (out != null) { 171 out.write(buffer, 0, read); 172 } else { 173 outWriter.write(new String (buffer, 0, read, 174 response.getCharacterEncoding())); 175 } 176 read = resStream.read(buffer); 177 } 178 resStream.close(); 179 } else if (request.getHeader(RANGE_HEADER).startsWith("bytes=")) { 180 String mimeType = getServletContext().getMimeType( 181 res.getName().toLowerCase()); 182 if (mimeType != null) 183 response.setContentType(mimeType); 184 InputStream resStream = new FileInputStream (res); 185 186 List ranges = new ArrayList (); 187 StringTokenizer st = new StringTokenizer (request.getHeader( 188 RANGE_HEADER).substring(6).trim(), ",", false); 189 int totalSent = 0; 190 String rangeText = ""; 191 while (st.hasMoreTokens()) { 192 String rangeBlock = st.nextToken(); 193 int start = 0; 194 int end = (int) res.length(); 195 int delim = rangeBlock.indexOf('-'); 196 if (delim != 0) 197 start = Integer.parseInt(rangeBlock.substring(0, delim) 198 .trim()); 199 if (delim != rangeBlock.length() - 1) 200 end = Integer.parseInt(rangeBlock.substring(delim + 1) 201 .trim()); 202 totalSent += (end - start); 203 rangeText += "," + start + "-" + end; 204 ranges.add(start + "-" + end); 205 } 206 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); 207 response.addHeader(CONTENT_RANGE_HEADER, "bytes " 208 + rangeText.substring(1) + "/" + res.length()); 209 response.setContentLength(totalSent); 210 211 response.addHeader(ACCEPT_RANGES_HEADER, "bytes"); 212 response.addDateHeader(LAST_MODIFIED_DATE_HEADER, res 213 .lastModified()); 214 OutputStream out = response.getOutputStream(); 215 int bytesRead = 0; 216 for (Iterator i = ranges.iterator(); i.hasNext();) { 217 String rangeBlock = (String ) i.next(); 218 int delim = rangeBlock.indexOf('-'); 219 int start = Integer.parseInt(rangeBlock.substring(0, delim)); 220 int end = Integer.parseInt(rangeBlock.substring(delim + 1)); 221 int read = 0; 222 while ((read != -1) && (bytesRead <= res.length())) { 223 read = resStream.read(); 224 if ((bytesRead >= start) && (bytesRead < end)) 225 out.write(read); 226 bytesRead++; 227 } 228 } 229 resStream.close(); 230 } else 231 response 232 .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); 233 } 234 235 238 private void generateDirectoryList(HttpServletRequest request, 239 HttpServletResponse response, String path) throws ServletException , 240 IOException { 241 File dir = path.equals("") ? this.webRoot : new File ( 243 this.webRoot, path); 244 File children[] = dir.listFiles(); 245 Arrays.sort(children); 246 247 StringWriter rowString = new StringWriter (); 249 String oddColour = Launcher.RESOURCES 250 .getString("StaticResourceServlet.DirectoryList.OddColour"); 251 String evenColour = Launcher.RESOURCES 252 .getString("StaticResourceServlet.DirectoryList.EvenColour"); 253 String rowTextColour = Launcher.RESOURCES 254 .getString("StaticResourceServlet.DirectoryList.RowTextColour"); 255 256 String directoryLabel = Launcher.RESOURCES 257 .getString("StaticResourceServlet.DirectoryList.DirectoryLabel"); 258 String parentDirLabel = Launcher.RESOURCES 259 .getString("StaticResourceServlet.DirectoryList.ParentDirectoryLabel"); 260 String noDateLabel = Launcher.RESOURCES 261 .getString("StaticResourceServlet.DirectoryList.NoDateLabel"); 262 263 int rowCount = 0; 264 265 if (!path.equals("") && !path.equals("/")) { 267 rowString.write(Launcher.RESOURCES.getString( 268 "StaticResourceServlet.DirectoryList.Row", new String [] { 269 rowTextColour, evenColour, parentDirLabel, "..", 270 noDateLabel, directoryLabel })); 271 rowCount++; 272 } 273 274 for (int n = 0; n < children.length; n++) { 276 if (!children[n].getName().equalsIgnoreCase("web-inf") && 277 !children[n].getName().equalsIgnoreCase("meta-inf")) { 278 File file = children[n]; 279 String date = noDateLabel; 280 String size = directoryLabel; 281 if (!file.isDirectory()) { 282 size = "" + file.length(); 283 synchronized (sdfFileDate) { 284 date = sdfFileDate.format(new Date (file.lastModified())); 285 } 286 } 287 rowString.write(Launcher.RESOURCES.getString( 288 "StaticResourceServlet.DirectoryList.Row", 289 new String [] { 290 rowTextColour, 291 rowCount % 2 == 0 ? evenColour : oddColour, 292 file.getName() + (file.isDirectory() ? "/" : ""), 293 "./" + file.getName() + (file.isDirectory() ? "/" : ""), 294 date, size})); 295 rowCount++; 296 } 297 } 298 299 String out = Launcher.RESOURCES.getString("StaticResourceServlet.DirectoryList.Body", 301 new String [] { 302 Launcher.RESOURCES.getString("StaticResourceServlet.DirectoryList.HeaderColour"), 303 Launcher.RESOURCES.getString("StaticResourceServlet.DirectoryList.HeaderTextColour"), 304 Launcher.RESOURCES.getString("StaticResourceServlet.DirectoryList.LabelColour"), 305 Launcher.RESOURCES.getString("StaticResourceServlet.DirectoryList.LabelTextColour"), 306 new Date () + "", 307 Launcher.RESOURCES.getString("ServerVersion"), 308 path.equals("") ? "/" : path, 309 rowString.toString() }); 310 311 response.setContentLength(out.getBytes().length); 312 response.setContentType("text/html"); 313 Writer w = response.getWriter(); 314 w.write(out); 315 w.close(); 316 } 317 318 public static boolean isDescendant(File parent, File child, File commonBase) throws IOException { 319 if (child.equals(parent)) { 320 return true; 321 } else { 322 String canonicalParent = parent.getAbsoluteFile().getCanonicalPath(); 324 String canonicalChild = child.getAbsoluteFile().getCanonicalPath(); 325 if (canonicalChild.startsWith(canonicalParent)) { 326 return true; 327 } 328 329 String childOCValue = constructOurCanonicalVersion(child, commonBase); 332 String parentOCValue = constructOurCanonicalVersion(parent, commonBase); 333 return childOCValue.startsWith(parentOCValue); 334 } 335 } 336 337 public static String constructOurCanonicalVersion(File current, File stopPoint) { 338 int backOnes = 0; 339 StringBuffer ourCanonicalVersion = new StringBuffer (); 340 while ((current != null) && !current.equals(stopPoint)) { 341 if (current.getName().equals("..")) { 342 backOnes++; 343 } else if (current.getName().equals(".")) { 344 } else if (backOnes > 0) { 346 backOnes--; 347 } else { 348 ourCanonicalVersion.insert(0, "/" + current.getName()); 349 } 350 current = current.getParentFile(); 351 } 352 return ourCanonicalVersion.toString(); 353 } 354 } 355
| Popular Tags
|