1
17 package org.apache.catalina.servlets;
18
19 import java.io.BufferedInputStream;
20 import java.io.ByteArrayInputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.OutputStreamWriter;
29 import java.io.PrintWriter;
30 import java.io.RandomAccessFile;
31 import java.io.Reader;
32 import java.io.Serializable;
33 import java.io.StringReader;
34 import java.io.StringWriter;
35 import java.io.UnsupportedEncodingException;
36 import java.nio.charset.Charset;
37 import java.nio.charset.StandardCharsets;
38 import java.security.AccessController;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collections;
42 import java.util.Comparator;
43 import java.util.Enumeration;
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.StringTokenizer;
48
49 import javax.servlet.DispatcherType;
50 import javax.servlet.RequestDispatcher;
51 import javax.servlet.ServletContext;
52 import javax.servlet.ServletException;
53 import javax.servlet.ServletOutputStream;
54 import javax.servlet.ServletResponse;
55 import javax.servlet.ServletResponseWrapper;
56 import javax.servlet.UnavailableException;
57 import javax.servlet.http.HttpServlet;
58 import javax.servlet.http.HttpServletRequest;
59 import javax.servlet.http.HttpServletResponse;
60 import javax.xml.parsers.DocumentBuilder;
61 import javax.xml.parsers.DocumentBuilderFactory;
62 import javax.xml.parsers.ParserConfigurationException;
63 import javax.xml.transform.Source;
64 import javax.xml.transform.Transformer;
65 import javax.xml.transform.TransformerException;
66 import javax.xml.transform.TransformerFactory;
67 import javax.xml.transform.dom.DOMSource;
68 import javax.xml.transform.stream.StreamResult;
69 import javax.xml.transform.stream.StreamSource;
70
71 import org.apache.catalina.Context;
72 import org.apache.catalina.Globals;
73 import org.apache.catalina.WebResource;
74 import org.apache.catalina.WebResourceRoot;
75 import org.apache.catalina.connector.RequestFacade;
76 import org.apache.catalina.connector.ResponseFacade;
77 import org.apache.catalina.util.IOTools;
78 import org.apache.catalina.util.ServerInfo;
79 import org.apache.catalina.util.URLEncoder;
80 import org.apache.catalina.webresources.CachedResource;
81 import org.apache.tomcat.util.buf.B2CConverter;
82 import org.apache.tomcat.util.http.ResponseUtil;
83 import org.apache.tomcat.util.http.parser.ContentRange;
84 import org.apache.tomcat.util.http.parser.Ranges;
85 import org.apache.tomcat.util.res.StringManager;
86 import org.apache.tomcat.util.security.Escape;
87 import org.apache.tomcat.util.security.PrivilegedGetTccl;
88 import org.apache.tomcat.util.security.PrivilegedSetTccl;
89 import org.w3c.dom.Document;
90 import org.xml.sax.InputSource;
91 import org.xml.sax.SAXException;
92 import org.xml.sax.ext.EntityResolver2;
93
94
95
137 public class DefaultServlet extends HttpServlet {
138
139 private static final long serialVersionUID = 1L;
140
141
144 protected static final StringManager sm = StringManager.getManager(Constants.Package);
145
146 private static final DocumentBuilderFactory factory;
147
148 private static final SecureEntityResolver secureEntityResolver;
149
150
153 protected static final ArrayList<Range> FULL = new ArrayList<>();
154
155 private static final Range IGNORE = new Range();
156
157
160 protected static final String mimeSeparation = "CATALINA_MIME_BOUNDARY";
161
162
165 protected static final int BUFFER_SIZE = 4096;
166
167
168
169
170 static {
171 if (Globals.IS_SECURITY_ENABLED) {
172 factory = DocumentBuilderFactory.newInstance();
173 factory.setNamespaceAware(true);
174 factory.setValidating(false);
175 secureEntityResolver = new SecureEntityResolver();
176 } else {
177 factory = null;
178 secureEntityResolver = null;
179 }
180 }
181
182
183
184
185
188 protected int debug = 0;
189
190
193 protected int input = 2048;
194
195
198 protected boolean listings = false;
199
200
203 protected boolean readOnly = true;
204
205
208 protected CompressionFormat[] compressionFormats;
209
210
213 protected int output = 2048;
214
215
218 protected String localXsltFile = null;
219
220
223 protected String contextXsltFile = null;
224
225
228 protected String globalXsltFile = null;
229
230
233 protected String readmeFile = null;
234
235
238 protected transient WebResourceRoot resources = null;
239
240
244 protected String fileEncoding = null;
245 private transient Charset fileEncodingCharset = null;
246
247
250 private boolean useBomIfPresent = true;
251
252
255 protected int sendfileSize = 48 * 1024;
256
257
260 protected boolean useAcceptRanges = true;
261
262
265 protected boolean showServerInfo = true;
266
267
270 protected boolean sortListings = false;
271
272
275 protected transient SortManager sortManager;
276
277
280 private boolean allowPartialPut = true;
281
282
283
284
285
288 @Override
289 public void destroy() {
290
291 }
292
293
294
297 @Override
298 public void init() throws ServletException {
299
300 if (getServletConfig().getInitParameter("debug") != null)
301 debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
302
303 if (getServletConfig().getInitParameter("input") != null)
304 input = Integer.parseInt(getServletConfig().getInitParameter("input"));
305
306 if (getServletConfig().getInitParameter("output") != null)
307 output = Integer.parseInt(getServletConfig().getInitParameter("output"));
308
309 listings = Boolean.parseBoolean(getServletConfig().getInitParameter("listings"));
310
311 if (getServletConfig().getInitParameter("readonly") != null)
312 readOnly = Boolean.parseBoolean(getServletConfig().getInitParameter("readonly"));
313
314 compressionFormats = parseCompressionFormats(
315 getServletConfig().getInitParameter("precompressed"),
316 getServletConfig().getInitParameter("gzip"));
317
318 if (getServletConfig().getInitParameter("sendfileSize") != null)
319 sendfileSize =
320 Integer.parseInt(getServletConfig().getInitParameter("sendfileSize")) * 1024;
321
322 fileEncoding = getServletConfig().getInitParameter("fileEncoding");
323 if (fileEncoding == null) {
324 fileEncodingCharset = Charset.defaultCharset();
325 fileEncoding = fileEncodingCharset.name();
326 } else {
327 try {
328 fileEncodingCharset = B2CConverter.getCharset(fileEncoding);
329 } catch (UnsupportedEncodingException e) {
330 throw new ServletException(e);
331 }
332 }
333
334 if (getServletConfig().getInitParameter("useBomIfPresent") != null)
335 useBomIfPresent = Boolean.parseBoolean(
336 getServletConfig().getInitParameter("useBomIfPresent"));
337
338 globalXsltFile = getServletConfig().getInitParameter("globalXsltFile");
339 contextXsltFile = getServletConfig().getInitParameter("contextXsltFile");
340 localXsltFile = getServletConfig().getInitParameter("localXsltFile");
341 readmeFile = getServletConfig().getInitParameter("readmeFile");
342
343 if (getServletConfig().getInitParameter("useAcceptRanges") != null)
344 useAcceptRanges = Boolean.parseBoolean(getServletConfig().getInitParameter("useAcceptRanges"));
345
346
347 if (input < 256)
348 input = 256;
349 if (output < 256)
350 output = 256;
351
352 if (debug > 0) {
353 log("DefaultServlet.init: input buffer size=" + input +
354 ", output buffer size=" + output);
355 }
356
357
358 resources = (WebResourceRoot) getServletContext().getAttribute(
359 Globals.RESOURCES_ATTR);
360
361 if (resources == null) {
362 throw new UnavailableException(sm.getString("defaultServlet.noResources"));
363 }
364
365 if (getServletConfig().getInitParameter("showServerInfo") != null) {
366 showServerInfo = Boolean.parseBoolean(getServletConfig().getInitParameter("showServerInfo"));
367 }
368
369 if (getServletConfig().getInitParameter("sortListings") != null) {
370 sortListings = Boolean.parseBoolean(getServletConfig().getInitParameter("sortListings"));
371
372 if(sortListings) {
373 boolean sortDirectoriesFirst;
374 if (getServletConfig().getInitParameter("sortDirectoriesFirst") != null) {
375 sortDirectoriesFirst = Boolean.parseBoolean(getServletConfig().getInitParameter("sortDirectoriesFirst"));
376 } else {
377 sortDirectoriesFirst = false;
378 }
379
380 sortManager = new SortManager(sortDirectoriesFirst);
381 }
382 }
383
384 if (getServletConfig().getInitParameter("allowPartialPut") != null) {
385 allowPartialPut = Boolean.parseBoolean(getServletConfig().getInitParameter("allowPartialPut"));
386 }
387 }
388
389 private CompressionFormat[] parseCompressionFormats(String precompressed, String gzip) {
390 List<CompressionFormat> ret = new ArrayList<>();
391 if (precompressed != null && precompressed.indexOf('=') > 0) {
392 for (String pair : precompressed.split(",")) {
393 String[] setting = pair.split("=");
394 String encoding = setting[0];
395 String extension = setting[1];
396 ret.add(new CompressionFormat(extension, encoding));
397 }
398 } else if (precompressed != null) {
399 if (Boolean.parseBoolean(precompressed)) {
400 ret.add(new CompressionFormat(".br", "br"));
401 ret.add(new CompressionFormat(".gz", "gzip"));
402 }
403 } else if (Boolean.parseBoolean(gzip)) {
404
405 ret.add(new CompressionFormat(".gz", "gzip"));
406 }
407 return ret.toArray(new CompressionFormat[ret.size()]);
408 }
409
410
411
412
413
414
420 protected String getRelativePath(HttpServletRequest request) {
421 return getRelativePath(request, false);
422 }
423
424 protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
425
465 protected String getPathPrefix(final HttpServletRequest request) {
466 return request.getContextPath();
467 }
468
469
470 @Override
471 protected void service(HttpServletRequest req, HttpServletResponse resp)
472 throws ServletException, IOException {
473
474 if (req.getDispatcherType() == DispatcherType.ERROR) {
475 doGet(req, resp);
476 } else {
477 super.service(req, resp);
478 }
479 }
480
481
482
491 @Override
492 protected void doGet(HttpServletRequest request,
493 HttpServletResponse response)
494 throws IOException, ServletException {
495
496
497 serveResource(request, response, true, fileEncoding);
498
499 }
500
501
502
511 @Override
512 protected void doHead(HttpServletRequest request, HttpServletResponse response)
513 throws IOException, ServletException {
514
515
516
517 boolean serveContent = DispatcherType.INCLUDE.equals(request.getDispatcherType());
518 serveResource(request, response, serveContent, fileEncoding);
519 }
520
521
522
541 @Override
542 protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
543 throws ServletException, IOException {
544
545 resp.setHeader("Allow", determineMethodsAllowed(req));
546 }
547
548
549 protected String determineMethodsAllowed(HttpServletRequest req) {
550 StringBuilder allow = new StringBuilder();
551
552
553 allow.append("OPTIONS, GET, HEAD, POST");
554
555
556 if (!readOnly) {
557 allow.append(", PUT, DELETE");
558 }
559
560
561 if (req instanceof RequestFacade &&
562 ((RequestFacade) req).getAllowTrace()) {
563 allow.append(", TRACE");
564 }
565
566 return allow.toString();
567 }
568
569
570 protected void sendNotAllowed(HttpServletRequest req, HttpServletResponse resp)
571 throws IOException {
572 resp.addHeader("Allow", determineMethodsAllowed(req));
573 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
574 }
575
576
577
586 @Override
587 protected void doPost(HttpServletRequest request,
588 HttpServletResponse response)
589 throws IOException, ServletException {
590 doGet(request, response);
591 }
592
593
594
603 @Override
604 protected void doPut(HttpServletRequest req, HttpServletResponse resp)
605 throws ServletException, IOException {
606
607 if (readOnly) {
608 sendNotAllowed(req, resp);
609 return;
610 }
611
612 String path = getRelativePath(req);
613
614 WebResource resource = resources.getResource(path);
615
616 Range range = parseContentRange(req, resp);
617
618 if (range == null) {
619
620 return;
621 }
622
623 InputStream resourceInputStream = null;
624
625 try {
626
627
628
629
630 if (range == IGNORE) {
631 resourceInputStream = req.getInputStream();
632 } else {
633 File contentFile = executePartialPut(req, range, path);
634 resourceInputStream = new FileInputStream(contentFile);
635 }
636
637 if (resources.write(path, resourceInputStream, true)) {
638 if (resource.exists()) {
639 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
640 } else {
641 resp.setStatus(HttpServletResponse.SC_CREATED);
642 }
643 } else {
644 resp.sendError(HttpServletResponse.SC_CONFLICT);
645 }
646 } finally {
647 if (resourceInputStream != null) {
648 try {
649 resourceInputStream.close();
650 } catch (IOException ioe) {
651
652 }
653 }
654 }
655 }
656
657
658
668 protected File executePartialPut(HttpServletRequest req, Range range,
669 String path)
670 throws IOException {
671
672
673
674
675 File tempDir = (File) getServletContext().getAttribute
676 (ServletContext.TEMPDIR);
677
678 String convertedResourcePath = path.replace('/', '.');
679 File contentFile = new File(tempDir, convertedResourcePath);
680 if (contentFile.createNewFile()) {
681
682 contentFile.deleteOnExit();
683 }
684
685 try (RandomAccessFile randAccessContentFile =
686 new RandomAccessFile(contentFile, "rw")) {
687
688 WebResource oldResource = resources.getResource(path);
689
690
691 if (oldResource.isFile()) {
692 try (BufferedInputStream bufOldRevStream =
693 new BufferedInputStream(oldResource.getInputStream(),
694 BUFFER_SIZE)) {
695
696 int numBytesRead;
697 byte[] copyBuffer = new byte[BUFFER_SIZE];
698 while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) {
699 randAccessContentFile.write(copyBuffer, 0, numBytesRead);
700 }
701
702 }
703 }
704
705 randAccessContentFile.setLength(range.length);
706
707
708 randAccessContentFile.seek(range.start);
709 int numBytesRead;
710 byte[] transferBuffer = new byte[BUFFER_SIZE];
711 try (BufferedInputStream requestBufInStream =
712 new BufferedInputStream(req.getInputStream(), BUFFER_SIZE)) {
713 while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) {
714 randAccessContentFile.write(transferBuffer, 0, numBytesRead);
715 }
716 }
717 }
718
719 return contentFile;
720 }
721
722
723
732 @Override
733 protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
734 throws ServletException, IOException {
735
736 if (readOnly) {
737 sendNotAllowed(req, resp);
738 return;
739 }
740
741 String path = getRelativePath(req);
742
743 WebResource resource = resources.getResource(path);
744
745 if (resource.exists()) {
746 if (resource.delete()) {
747 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
748 } else {
749 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
750 }
751 } else {
752 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
753 }
754
755 }
756
757
758
770 protected boolean checkIfHeaders(HttpServletRequest request,
771 HttpServletResponse response,
772 WebResource resource)
773 throws IOException {
774
775 return checkIfMatch(request, response, resource)
776 && checkIfModifiedSince(request, response, resource)
777 && checkIfNoneMatch(request, response, resource)
778 && checkIfUnmodifiedSince(request, response, resource);
779
780 }
781
782
783
789 protected String rewriteUrl(String path) {
790 return URLEncoder.DEFAULT.encode(path, StandardCharsets.UTF_8);
791 }
792
793
794
806 protected void serveResource(HttpServletRequest request,
807 HttpServletResponse response,
808 boolean content,
809 String inputEncoding)
810 throws IOException, ServletException {
811
812 boolean serveContent = content;
813
814
815 String path = getRelativePath(request, true);
816
817 if (debug > 0) {
818 if (serveContent)
819 log("DefaultServlet.serveResource: Serving resource '" +
820 path + "' headers and data");
821 else
822 log("DefaultServlet.serveResource: Serving resource '" +
823 path + "' headers only");
824 }
825
826 if (path.length() == 0) {
827
828 doDirectoryRedirect(request, response);
829 return;
830 }
831
832 WebResource resource = resources.getResource(path);
833 boolean isError = DispatcherType.ERROR == request.getDispatcherType();
834
835 if (!resource.exists()) {
836
837
838 String requestUri = (String) request.getAttribute(
839 RequestDispatcher.INCLUDE_REQUEST_URI);
840 if (requestUri == null) {
841 requestUri = request.getRequestURI();
842 } else {
843
844
845 throw new FileNotFoundException(sm.getString(
846 "defaultServlet.missingResource", requestUri));
847 }
848
849 if (isError) {
850 response.sendError(((Integer) request.getAttribute(
851 RequestDispatcher.ERROR_STATUS_CODE)).intValue());
852 } else {
853 response.sendError(HttpServletResponse.SC_NOT_FOUND, requestUri);
854 }
855 return;
856 }
857
858 if (!resource.canRead()) {
859
860
861 String requestUri = (String) request.getAttribute(
862 RequestDispatcher.INCLUDE_REQUEST_URI);
863 if (requestUri == null) {
864 requestUri = request.getRequestURI();
865 } else {
866
867
868
869 throw new FileNotFoundException(sm.getString(
870 "defaultServlet.missingResource", requestUri));
871 }
872
873 if (isError) {
874 response.sendError(((Integer) request.getAttribute(
875 RequestDispatcher.ERROR_STATUS_CODE)).intValue());
876 } else {
877 response.sendError(HttpServletResponse.SC_FORBIDDEN, requestUri);
878 }
879 return;
880 }
881
882 boolean included = false;
883
884
885 if (resource.isFile()) {
886
887 included = (request.getAttribute(
888 RequestDispatcher.INCLUDE_CONTEXT_PATH) != null);
889 if (!included && !isError && !checkIfHeaders(request, response, resource)) {
890 return;
891 }
892 }
893
894
895 String contentType = resource.getMimeType();
896 if (contentType == null) {
897 contentType = getServletContext().getMimeType(resource.getName());
898 resource.setMimeType(contentType);
899 }
900
901
902
903
904 String eTag = null;
905 String lastModifiedHttp = null;
906 if (resource.isFile() && !isError) {
907 eTag = resource.getETag();
908 lastModifiedHttp = resource.getLastModifiedHttp();
909 }
910
911
912
913 boolean usingPrecompressedVersion = false;
914 if (compressionFormats.length > 0 && !included && resource.isFile() &&
915 !pathEndsWithCompressedExtension(path)) {
916 List<PrecompressedResource> precompressedResources =
917 getAvailablePrecompressedResources(path);
918 if (!precompressedResources.isEmpty()) {
919 ResponseUtil.addVaryFieldName(response, "accept-encoding");
920 PrecompressedResource bestResource =
921 getBestPrecompressedResource(request, precompressedResources);
922 if (bestResource != null) {
923 response.addHeader("Content-Encoding", bestResource.format.encoding);
924 resource = bestResource.resource;
925 usingPrecompressedVersion = true;
926 }
927 }
928 }
929
930 ArrayList<Range> ranges = FULL;
931 long contentLength = -1L;
932
933 if (resource.isDirectory()) {
934 if (!path.endsWith("/")) {
935 doDirectoryRedirect(request, response);
936 return;
937 }
938
939
940
941 if (!listings) {
942 response.sendError(HttpServletResponse.SC_NOT_FOUND,
943 request.getRequestURI());
944 return;
945 }
946 contentType = "text/html;charset=UTF-8";
947 } else {
948 if (!isError) {
949 if (useAcceptRanges) {
950
951 response.setHeader("Accept-Ranges", "bytes");
952 }
953
954
955 ranges = parseRange(request, response, resource);
956 if (ranges == null) {
957 return;
958 }
959
960
961 response.setHeader("ETag", eTag);
962
963
964 response.setHeader("Last-Modified", lastModifiedHttp);
965 }
966
967
968 contentLength = resource.getContentLength();
969
970
971 if (contentLength == 0L) {
972 serveContent = false;
973 }
974 }
975
976 ServletOutputStream ostream = null;
977 PrintWriter writer = null;
978
979 if (serveContent) {
980
981 try {
982 ostream = response.getOutputStream();
983 } catch (IllegalStateException e) {
984
985
986 if (!usingPrecompressedVersion && isText(contentType)) {
987 writer = response.getWriter();
988
989 ranges = FULL;
990 } else {
991 throw e;
992 }
993 }
994 }
995
996
997
998
999 ServletResponse r = response;
1000 long contentWritten = 0;
1001 while (r instanceof ServletResponseWrapper) {
1002 r = ((ServletResponseWrapper) r).getResponse();
1003 }
1004 if (r instanceof ResponseFacade) {
1005 contentWritten = ((ResponseFacade) r).getContentWritten();
1006 }
1007 if (contentWritten > 0) {
1008 ranges = FULL;
1009 }
1010
1011 String outputEncoding = response.getCharacterEncoding();
1012 Charset charset = B2CConverter.getCharset(outputEncoding);
1013 boolean conversionRequired;
1014
1024 boolean outputEncodingSpecified =
1025 outputEncoding != org.apache.coyote.Constants.DEFAULT_BODY_CHARSET.name() &&
1026 outputEncoding != resources.getContext().getResponseCharacterEncoding();
1027 if (!usingPrecompressedVersion && isText(contentType) && outputEncodingSpecified &&
1028 !charset.equals(fileEncodingCharset)) {
1029 conversionRequired = true;
1030
1031
1032 ranges = FULL;
1033 } else {
1034 conversionRequired = false;
1035 }
1036
1037 if (resource.isDirectory() || isError || ranges == FULL ) {
1038
1039 if (contentType != null) {
1040 if (debug > 0)
1041 log("DefaultServlet.serveFile: contentType='" +
1042 contentType + "'");
1043
1044 if (response.getContentType() == null) {
1045 response.setContentType(contentType);
1046 }
1047 }
1048 if (resource.isFile() && contentLength >= 0 &&
1049 (!serveContent || ostream != null)) {
1050 if (debug > 0)
1051 log("DefaultServlet.serveFile: contentLength=" +
1052 contentLength);
1053
1054
1055 if (contentWritten == 0 && !conversionRequired) {
1056 response.setContentLengthLong(contentLength);
1057 }
1058 }
1059
1060 if (serveContent) {
1061 try {
1062 response.setBufferSize(output);
1063 } catch (IllegalStateException e) {
1064
1065 }
1066 InputStream renderResult = null;
1067 if (ostream == null) {
1068
1069
1070 if (resource.isDirectory()) {
1071 renderResult = render(request, getPathPrefix(request), resource, inputEncoding);
1072 } else {
1073 renderResult = resource.getInputStream();
1074 if (included) {
1075
1076 if (!renderResult.markSupported()) {
1077 renderResult = new BufferedInputStream(renderResult);
1078 }
1079 Charset bomCharset = processBom(renderResult);
1080 if (bomCharset != null && useBomIfPresent) {
1081 inputEncoding = bomCharset.name();
1082 }
1083 }
1084 }
1085 copy(renderResult, writer, inputEncoding);
1086 } else {
1087
1088 if (resource.isDirectory()) {
1089 renderResult = render(request, getPathPrefix(request), resource, inputEncoding);
1090 } else {
1091
1092
1093 if (conversionRequired || included) {
1094
1095
1096
1097 InputStream source = resource.getInputStream();
1098 if (!source.markSupported()) {
1099 source = new BufferedInputStream(source);
1100 }
1101 Charset bomCharset = processBom(source);
1102 if (bomCharset != null && useBomIfPresent) {
1103 inputEncoding = bomCharset.name();
1104 }
1105
1106
1107
1108 if (outputEncodingSpecified) {
1109 OutputStreamWriter osw = new OutputStreamWriter(ostream, charset);
1110 PrintWriter pw = new PrintWriter(osw);
1111 copy(source, pw, inputEncoding);
1112 pw.flush();
1113 } else {
1114
1115 renderResult = source;
1116 }
1117 } else {
1118 if (!checkSendfile(request, response, resource, contentLength, null)) {
1119
1120
1121
1122
1123
1124
1125 byte[] resourceBody = null;
1126 if (resource instanceof CachedResource) {
1127 resourceBody = resource.getContent();
1128 }
1129 if (resourceBody == null) {
1130
1131
1132 renderResult = resource.getInputStream();
1133 } else {
1134
1135 ostream.write(resourceBody);
1136 }
1137 }
1138 }
1139 }
1140
1141
1142 if (renderResult != null) {
1143 copy(renderResult, ostream);
1144 }
1145 }
1146 }
1147
1148 } else {
1149
1150 if ((ranges == null) || (ranges.isEmpty()))
1151 return;
1152
1153
1154
1155 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
1156
1157 if (ranges.size() == 1) {
1158
1159 Range range = ranges.get(0);
1160 response.addHeader("Content-Range", "bytes "
1161 + range.start
1162 + "-" + range.end + "/"
1163 + range.length);
1164 long length = range.end - range.start + 1;
1165 response.setContentLengthLong(length);
1166
1167 if (contentType != null) {
1168 if (debug > 0)
1169 log("DefaultServlet.serveFile: contentType='" +
1170 contentType + "'");
1171 response.setContentType(contentType);
1172 }
1173
1174 if (serveContent) {
1175 try {
1176 response.setBufferSize(output);
1177 } catch (IllegalStateException e) {
1178
1179 }
1180 if (ostream != null) {
1181 if (!checkSendfile(request, response, resource,
1182 range.end - range.start + 1, range))
1183 copy(resource, ostream, range);
1184 } else {
1185
1186 throw new IllegalStateException();
1187 }
1188 }
1189 } else {
1190 response.setContentType("multipart/byteranges; boundary="
1191 + mimeSeparation);
1192 if (serveContent) {
1193 try {
1194 response.setBufferSize(output);
1195 } catch (IllegalStateException e) {
1196
1197 }
1198 if (ostream != null) {
1199 copy(resource, ostream, ranges.iterator(), contentType);
1200 } else {
1201
1202 throw new IllegalStateException();
1203 }
1204 }
1205 }
1206 }
1207 }
1208
1209
1210
1213 private static Charset processBom(InputStream is) throws IOException {
1214
1215 byte[] bom = new byte[4];
1216 is.mark(bom.length);
1217
1218 int count = is.read(bom);
1219
1220
1221 if (count < 2) {
1222 skip(is, 0);
1223 return null;
1224 }
1225
1226
1227 int b0 = bom[0] & 0xFF;
1228 int b1 = bom[1] & 0xFF;
1229 if (b0 == 0xFE && b1 == 0xFF) {
1230 skip(is, 2);
1231 return StandardCharsets.UTF_16BE;
1232 }
1233
1234
1235 if (count == 2 && b0 == 0xFF && b1 == 0xFE) {
1236 skip(is, 2);
1237 return StandardCharsets.UTF_16LE;
1238 }
1239
1240
1241 if (count < 3) {
1242 skip(is, 0);
1243 return null;
1244 }
1245
1246
1247 int b2 = bom[2] & 0xFF;
1248 if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
1249 skip(is, 3);
1250 return StandardCharsets.UTF_8;
1251 }
1252
1253 if (count < 4) {
1254 skip(is, 0);
1255 return null;
1256 }
1257
1258
1259 int b3 = bom[3] & 0xFF;
1260 if (b0 == 0x00 && b1 == 0x00 && b2 == 0xFE && b3 == 0xFF) {
1261 return Charset.forName("UTF-32BE");
1262 }
1263 if (b0 == 0xFF && b1 == 0xFE && b2 == 0x00 && b3 == 0x00) {
1264 return Charset.forName("UTF-32LE");
1265 }
1266
1267
1268
1269
1270 if (b0 == 0xFF && b1 == 0xFE) {
1271 skip(is, 2);
1272 return StandardCharsets.UTF_16LE;
1273 }
1274
1275 skip(is, 0);
1276 return null;
1277 }
1278
1279
1280 private static void skip(InputStream is, int skip) throws IOException {
1281 is.reset();
1282 while (skip-- > 0) {
1283 is.read();
1284 }
1285 }
1286
1287
1288 private static boolean isText(String contentType) {
1289 return contentType == null || contentType.startsWith("text") ||
1290 contentType.endsWith("xml") || contentType.contains("/javascript");
1291 }
1292
1293
1294 private boolean pathEndsWithCompressedExtension(String path) {
1295 for (CompressionFormat format : compressionFormats) {
1296 if (path.endsWith(format.extension)) {
1297 return true;
1298 }
1299 }
1300 return false;
1301 }
1302
1303 private List<PrecompressedResource> getAvailablePrecompressedResources(String path) {
1304 List<PrecompressedResource> ret = new ArrayList<>(compressionFormats.length);
1305 for (CompressionFormat format : compressionFormats) {
1306 WebResource precompressedResource = resources.getResource(path + format.extension);
1307 if (precompressedResource.exists() && precompressedResource.isFile()) {
1308 ret.add(new PrecompressedResource(precompressedResource, format));
1309 }
1310 }
1311 return ret;
1312 }
1313
1314
1321 private PrecompressedResource getBestPrecompressedResource(HttpServletRequest request,
1322 List<PrecompressedResource> precompressedResources) {
1323 Enumeration<String> headers = request.getHeaders("Accept-Encoding");
1324 PrecompressedResource bestResource = null;
1325 double bestResourceQuality = 0;
1326 int bestResourcePreference = Integer.MAX_VALUE;
1327 while (headers.hasMoreElements()) {
1328 String header = headers.nextElement();
1329 for (String preference : header.split(",")) {
1330 double quality = 1;
1331 int qualityIdx = preference.indexOf(';');
1332 if (qualityIdx > 0) {
1333 int equalsIdx = preference.indexOf('=', qualityIdx + 1);
1334 if (equalsIdx == -1) {
1335 continue;
1336 }
1337 quality = Double.parseDouble(preference.substring(equalsIdx + 1).trim());
1338 }
1339 if (quality >= bestResourceQuality) {
1340 String encoding = preference;
1341 if (qualityIdx > 0) {
1342 encoding = encoding.substring(0, qualityIdx);
1343 }
1344 encoding = encoding.trim();
1345 if ("identity".equals(encoding)) {
1346 bestResource = null;
1347 bestResourceQuality = quality;
1348 bestResourcePreference = Integer.MAX_VALUE;
1349 continue;
1350 }
1351 if ("*".equals(encoding)) {
1352 bestResource = precompressedResources.get(0);
1353 bestResourceQuality = quality;
1354 bestResourcePreference = 0;
1355 continue;
1356 }
1357 for (int i = 0; i < precompressedResources.size(); ++i) {
1358 PrecompressedResource resource = precompressedResources.get(i);
1359 if (encoding.equals(resource.format.encoding)) {
1360 if (quality > bestResourceQuality || i < bestResourcePreference) {
1361 bestResource = resource;
1362 bestResourceQuality = quality;
1363 bestResourcePreference = i;
1364 }
1365 break;
1366 }
1367 }
1368 }
1369 }
1370 }
1371 return bestResource;
1372 }
1373
1374 private void doDirectoryRedirect(HttpServletRequest request, HttpServletResponse response)
1375 throws IOException {
1376 StringBuilder location = new StringBuilder(request.getRequestURI());
1377 location.append('/');
1378 if (request.getQueryString() != null) {
1379 location.append('?');
1380 location.append(request.getQueryString());
1381 }
1382
1383 while (location.length() > 1 && location.charAt(1) == '/') {
1384 location.deleteCharAt(0);
1385 }
1386 response.sendRedirect(response.encodeRedirectURL(location.toString()));
1387 }
1388
1389
1399 protected Range parseContentRange(HttpServletRequest request,
1400 HttpServletResponse response)
1401 throws IOException {
1402
1403
1404 String contentRangeHeader = request.getHeader("Content-Range");
1405
1406 if (contentRangeHeader == null) {
1407 return IGNORE;
1408 }
1409
1410 if (!allowPartialPut) {
1411 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1412 return null;
1413 }
1414
1415 ContentRange contentRange = ContentRange.parse(new StringReader(contentRangeHeader));
1416
1417 if (contentRange == null) {
1418 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1419 return null;
1420 }
1421
1422
1423
1424 if (!contentRange.getUnits().equals("bytes")) {
1425 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1426 return null;
1427 }
1428
1429
1430
1431 Range range = new Range();
1432 range.start = contentRange.getStart();
1433 range.end = contentRange.getEnd();
1434 range.length = contentRange.getLength();
1435
1436 if (!range.validate()) {
1437 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1438 return null;
1439 }
1440
1441 return range;
1442 }
1443
1444
1445
1455 protected ArrayList<Range> parseRange(HttpServletRequest request,
1456 HttpServletResponse response,
1457 WebResource resource) throws IOException {
1458
1459
1460
1461
1462
1463
1464 String headerValue = request.getHeader("If-Range");
1465
1466 if (headerValue != null) {
1467
1468 long headerValueTime = (-1L);
1469 try {
1470 headerValueTime = request.getDateHeader("If-Range");
1471 } catch (IllegalArgumentException e) {
1472
1473 }
1474
1475 String eTag = resource.getETag();
1476 long lastModified = resource.getLastModified();
1477
1478 if (headerValueTime == (-1L)) {
1479
1480
1481 if (!eTag.equals(headerValue.trim())) {
1482 return FULL;
1483 }
1484 } else {
1485
1486
1487
1488 if (Math.abs(lastModified -headerValueTime) > 1000) {
1489 return FULL;
1490 }
1491 }
1492 }
1493
1494 long fileLength = resource.getContentLength();
1495
1496 if (fileLength == 0) {
1497
1498
1499 return FULL;
1500 }
1501
1502
1503 String rangeHeader = request.getHeader("Range");
1504
1505 if (rangeHeader == null) {
1506
1507 return FULL;
1508 }
1509
1510 Ranges ranges = Ranges.parse(new StringReader(rangeHeader));
1511
1512 if (ranges == null) {
1513
1514
1515
1516
1517
1518 response.addHeader("Content-Range", "bytes */" + fileLength);
1519 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1520 return null;
1521 }
1522
1523
1524
1525 if (!ranges.getUnits().equals("bytes")) {
1526
1527 return FULL;
1528 }
1529
1530
1531
1532 ArrayList<Range> result = new ArrayList<>();
1533
1534 for (Ranges.Entry entry : ranges.getEntries()) {
1535 Range currentRange = new Range();
1536 if (entry.getStart() == -1) {
1537 currentRange.start = fileLength - entry.getEnd();
1538 if (currentRange.start < 0) {
1539 currentRange.start = 0;
1540 }
1541 currentRange.end = fileLength - 1;
1542 } else if (entry.getEnd() == -1) {
1543 currentRange.start = entry.getStart();
1544 currentRange.end = fileLength - 1;
1545 } else {
1546 currentRange.start = entry.getStart();
1547 currentRange.end = entry.getEnd();
1548 }
1549 currentRange.length = fileLength;
1550
1551 if (!currentRange.validate()) {
1552 response.addHeader("Content-Range", "bytes */" + fileLength);
1553 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1554 return null;
1555 }
1556
1557 result.add(currentRange);
1558 }
1559
1560 return result;
1561 }
1562
1563
1564
1578 @Deprecated
1579 protected InputStream render(String contextPath, WebResource resource, String encoding)
1580 throws IOException, ServletException {
1581
1582 return render(null, contextPath, resource, encoding);
1583 }
1584
1585
1598 protected InputStream render(HttpServletRequest request, String contextPath, WebResource resource, String encoding)
1599 throws IOException, ServletException {
1600
1601 Source xsltSource = findXsltSource(resource);
1602
1603 if (xsltSource == null) {
1604 return renderHtml(request, contextPath, resource, encoding);
1605 }
1606 return renderXml(request, contextPath, resource, xsltSource, encoding);
1607 }
1608
1609
1610
1626 @Deprecated
1627 protected InputStream renderXml(String contextPath, WebResource resource, Source xsltSource,
1628 String encoding)
1629 throws ServletException, IOException
1630 {
1631 return renderXml(null, contextPath, resource, xsltSource, encoding);
1632 }
1633
1634
1649 protected InputStream renderXml(HttpServletRequest request, String contextPath, WebResource resource, Source xsltSource,
1650 String encoding)
1651 throws IOException, ServletException {
1652
1653 StringBuilder sb = new StringBuilder();
1654
1655 sb.append("<?xml version=\"1.0\"?>");
1656 sb.append("<listing ");
1657 sb.append(" contextPath='");
1658 sb.append(contextPath);
1659 sb.append("'");
1660 sb.append(" directory='");
1661 sb.append(resource.getName());
1662 sb.append("' ");
1663 sb.append(" hasParent='").append(!resource.getName().equals("/"));
1664 sb.append("'>");
1665
1666 sb.append("<entries>");
1667
1668 String[] entries = resources.list(resource.getWebappPath());
1669
1670
1671 String rewrittenContextPath = rewriteUrl(contextPath);
1672 String directoryWebappPath = resource.getWebappPath();
1673
1674 for (String entry : entries) {
1675
1676 if (entry.equalsIgnoreCase("WEB-INF") ||
1677 entry.equalsIgnoreCase("META-INF") ||
1678 entry.equalsIgnoreCase(localXsltFile))
1679 continue;
1680
1681 if ((directoryWebappPath + entry).equals(contextXsltFile))
1682 continue;
1683
1684 WebResource childResource =
1685 resources.getResource(directoryWebappPath + entry);
1686 if (!childResource.exists()) {
1687 continue;
1688 }
1689
1690 sb.append("<entry");
1691 sb.append(" type='")
1692 .append(childResource.isDirectory()?"dir":"file")
1693 .append("'");
1694 sb.append(" urlPath='")
1695 .append(rewrittenContextPath)
1696 .append(rewriteUrl(directoryWebappPath + entry))
1697 .append(childResource.isDirectory()?"/":"")
1698 .append("'");
1699 if (childResource.isFile()) {
1700 sb.append(" size='")
1701 .append(renderSize(childResource.getContentLength()))
1702 .append("'");
1703 }
1704 sb.append(" date='")
1705 .append(childResource.getLastModifiedHttp())
1706 .append("'");
1707
1708 sb.append(">");
1709 sb.append(Escape.htmlElementContent(entry));
1710 if (childResource.isDirectory())
1711 sb.append("/");
1712 sb.append("</entry>");
1713 }
1714 sb.append("</entries>");
1715
1716 String readme = getReadme(resource, encoding);
1717
1718 if (readme!=null) {
1719 sb.append("<readme><![CDATA[");
1720 sb.append(readme);
1721 sb.append("]]></readme>");
1722 }
1723
1724 sb.append("</listing>");
1725
1726
1727
1728 ClassLoader original;
1729 if (Globals.IS_SECURITY_ENABLED) {
1730 PrivilegedGetTccl pa = new PrivilegedGetTccl();
1731 original = AccessController.doPrivileged(pa);
1732 } else {
1733 original = Thread.currentThread().getContextClassLoader();
1734 }
1735 try {
1736 if (Globals.IS_SECURITY_ENABLED) {
1737 PrivilegedSetTccl pa =
1738 new PrivilegedSetTccl(DefaultServlet.class.getClassLoader());
1739 AccessController.doPrivileged(pa);
1740 } else {
1741 Thread.currentThread().setContextClassLoader(
1742 DefaultServlet.class.getClassLoader());
1743 }
1744
1745 TransformerFactory tFactory = TransformerFactory.newInstance();
1746 Source xmlSource = new StreamSource(new StringReader(sb.toString()));
1747 Transformer transformer = tFactory.newTransformer(xsltSource);
1748
1749 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1750 OutputStreamWriter osWriter = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
1751 StreamResult out = new StreamResult(osWriter);
1752 transformer.transform(xmlSource, out);
1753 osWriter.flush();
1754 return new ByteArrayInputStream(stream.toByteArray());
1755 } catch (TransformerException e) {
1756 throw new ServletException(sm.getString("defaultServlet.xslError"), e);
1757 } finally {
1758 if (Globals.IS_SECURITY_ENABLED) {
1759 PrivilegedSetTccl pa = new PrivilegedSetTccl(original);
1760 AccessController.doPrivileged(pa);
1761 } else {
1762 Thread.currentThread().setContextClassLoader(original);
1763 }
1764 }
1765 }
1766
1767
1782 @Deprecated
1783 protected InputStream renderHtml(String contextPath, WebResource resource, String encoding)
1784 throws IOException {
1785 return renderHtml(null, contextPath, resource, encoding);
1786 }
1787
1788
1801 protected InputStream renderHtml(HttpServletRequest request, String contextPath, WebResource resource, String encoding)
1802 throws IOException {
1803
1804
1805 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1806 OutputStreamWriter osWriter = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
1807 PrintWriter writer = new PrintWriter(osWriter);
1808
1809 StringBuilder sb = new StringBuilder();
1810
1811 String directoryWebappPath = resource.getWebappPath();
1812 WebResource[] entries = resources.listResources(directoryWebappPath);
1813
1814
1815 String rewrittenContextPath = rewriteUrl(contextPath);
1816
1817
1818 sb.append("<!doctype html><html>\r\n");
1819
1823 sb.append("<head>\r\n");
1824 sb.append("<title>");
1825 sb.append(sm.getString("directory.title", directoryWebappPath));
1826 sb.append("</title>\r\n");
1827 sb.append("<style>");
1828 sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
1829 sb.append("</style> ");
1830 sb.append("</head>\r\n");
1831 sb.append("<body>");
1832 sb.append("<h1>");
1833 sb.append(sm.getString("directory.title", directoryWebappPath));
1834
1835
1836 String parentDirectory = directoryWebappPath;
1837 if (parentDirectory.endsWith("/")) {
1838 parentDirectory =
1839 parentDirectory.substring(0, parentDirectory.length() - 1);
1840 }
1841 int slash = parentDirectory.lastIndexOf('/');
1842 if (slash >= 0) {
1843 String parent = directoryWebappPath.substring(0, slash);
1844 sb.append(" - <a href=\"");
1845 sb.append(rewrittenContextPath);
1846 if (parent.equals(""))
1847 parent = "/";
1848 sb.append(rewriteUrl(parent));
1849 if (!parent.endsWith("/"))
1850 sb.append("/");
1851 sb.append("\">");
1852 sb.append("<b>");
1853 sb.append(sm.getString("directory.parent", parent));
1854 sb.append("</b>");
1855 sb.append("</a>");
1856 }
1857
1858 sb.append("</h1>");
1859 sb.append("<hr class=\"line\">");
1860
1861 sb.append("<table width=\"100%\" cellspacing=\"0\"" +
1862 " cellpadding=\"5\" align=\"center\">\r\n");
1863
1864 SortManager.Order order;
1865 if(sortListings && null != request)
1866 order = sortManager.getOrder(request.getQueryString());
1867 else
1868 order = null;
1869
1870 sb.append("<tr>\r\n");
1871 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
1872 if(sortListings && null != request) {
1873 sb.append("<a href=\"?C=N;O=");
1874 sb.append(getOrderChar(order, 'N'));
1875 sb.append("\">");
1876 sb.append(sm.getString("directory.filename"));
1877 sb.append("</a>");
1878 } else {
1879 sb.append(sm.getString("directory.filename"));
1880 }
1881 sb.append("</strong></font></td>\r\n");
1882 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
1883 if(sortListings && null != request) {
1884 sb.append("<a href=\"?C=S;O=");
1885 sb.append(getOrderChar(order, 'S'));
1886 sb.append("\">");
1887 sb.append(sm.getString("directory.size"));
1888 sb.append("</a>");
1889 } else {
1890 sb.append(sm.getString("directory.size"));
1891 }
1892 sb.append("</strong></font></td>\r\n");
1893 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
1894 if(sortListings && null != request) {
1895 sb.append("<a href=\"?C=M;O=");
1896 sb.append(getOrderChar(order, 'M'));
1897 sb.append("\">");
1898 sb.append(sm.getString("directory.lastModified"));
1899 sb.append("</a>");
1900 } else {
1901 sb.append(sm.getString("directory.lastModified"));
1902 }
1903 sb.append("</strong></font></td>\r\n");
1904 sb.append("</tr>");
1905
1906 if(null != sortManager && null != request) {
1907 sortManager.sort(entries, request.getQueryString());
1908 }
1909
1910 boolean shade = false;
1911 for (WebResource childResource : entries) {
1912 String filename = childResource.getName();
1913 if (filename.equalsIgnoreCase("WEB-INF") ||
1914 filename.equalsIgnoreCase("META-INF"))
1915 continue;
1916
1917 if (!childResource.exists()) {
1918 continue;
1919 }
1920
1921 sb.append("<tr");
1922 if (shade)
1923 sb.append(" bgcolor=\"#eeeeee\"");
1924 sb.append(">\r\n");
1925 shade = !shade;
1926
1927 sb.append("<td align=\"left\"> \r\n");
1928 sb.append("<a href=\"");
1929 sb.append(rewrittenContextPath);
1930 sb.append(rewriteUrl(childResource.getWebappPath()));
1931 if (childResource.isDirectory())
1932 sb.append("/");
1933 sb.append("\"><tt>");
1934 sb.append(Escape.htmlElementContent(filename));
1935 if (childResource.isDirectory())
1936 sb.append("/");
1937 sb.append("</tt></a></td>\r\n");
1938
1939 sb.append("<td align=\"right\"><tt>");
1940 if (childResource.isDirectory())
1941 sb.append(" ");
1942 else
1943 sb.append(renderSize(childResource.getContentLength()));
1944 sb.append("</tt></td>\r\n");
1945
1946 sb.append("<td align=\"right\"><tt>");
1947 sb.append(childResource.getLastModifiedHttp());
1948 sb.append("</tt></td>\r\n");
1949
1950 sb.append("</tr>\r\n");
1951 }
1952
1953
1954 sb.append("</table>\r\n");
1955
1956 sb.append("<hr class=\"line\">");
1957
1958 String readme = getReadme(resource, encoding);
1959 if (readme!=null) {
1960 sb.append(readme);
1961 sb.append("<hr class=\"line\">");
1962 }
1963
1964 if (showServerInfo) {
1965 sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
1966 }
1967 sb.append("</body>\r\n");
1968 sb.append("</html>\r\n");
1969
1970
1971 writer.write(sb.toString());
1972 writer.flush();
1973 return new ByteArrayInputStream(stream.toByteArray());
1974
1975 }
1976
1977
1978
1984 protected String renderSize(long size) {
1985
1986 long leftSide = size / 1024;
1987 long rightSide = (size % 1024) / 103;
1988 if ((leftSide == 0) && (rightSide == 0) && (size > 0))
1989 rightSide = 1;
1990
1991 return ("" + leftSide + "." + rightSide + " kb");
1992
1993 }
1994
1995
1996
2002 protected String getReadme(WebResource directory, String encoding) {
2003
2004 if (readmeFile != null) {
2005 WebResource resource = resources.getResource(
2006 directory.getWebappPath() + readmeFile);
2007 if (resource.isFile()) {
2008 StringWriter buffer = new StringWriter();
2009 InputStreamReader reader = null;
2010 try (InputStream is = resource.getInputStream()){
2011 if (encoding != null) {
2012 reader = new InputStreamReader(is, encoding);
2013 } else {
2014 reader = new InputStreamReader(is);
2015 }
2016 copyRange(reader, new PrintWriter(buffer));
2017 } catch (IOException e) {
2018 log(sm.getString("defaultServlet.readerCloseFailed"), e);
2019 } finally {
2020 if (reader != null) {
2021 try {
2022 reader.close();
2023 } catch (IOException e) {
2024 }
2025 }
2026 }
2027 return buffer.toString();
2028 } else {
2029 if (debug > 10)
2030 log("readme '" + readmeFile + "' not found");
2031
2032 return null;
2033 }
2034 }
2035
2036 return null;
2037 }
2038
2039
2040
2046 protected Source findXsltSource(WebResource directory)
2047 throws IOException {
2048
2049 if (localXsltFile != null) {
2050 WebResource resource = resources.getResource(
2051 directory.getWebappPath() + localXsltFile);
2052 if (resource.isFile()) {
2053 InputStream is = resource.getInputStream();
2054 if (is != null) {
2055 if (Globals.IS_SECURITY_ENABLED) {
2056 return secureXslt(is);
2057 } else {
2058 return new StreamSource(is);
2059 }
2060 }
2061 }
2062 if (debug > 10) {
2063 log("localXsltFile '" + localXsltFile + "' not found");
2064 }
2065 }
2066
2067 if (contextXsltFile != null) {
2068 InputStream is =
2069 getServletContext().getResourceAsStream(contextXsltFile);
2070 if (is != null) {
2071 if (Globals.IS_SECURITY_ENABLED) {
2072 return secureXslt(is);
2073 } else {
2074 return new StreamSource(is);
2075 }
2076 }
2077
2078 if (debug > 10)
2079 log("contextXsltFile '" + contextXsltFile + "' not found");
2080 }
2081
2082
2085 if (globalXsltFile != null) {
2086 File f = validateGlobalXsltFile();
2087 if (f != null) {
2088 long globalXsltFileSize = f.length();
2089 if (globalXsltFileSize > Integer.MAX_VALUE) {
2090 log("globalXsltFile [" + f.getAbsolutePath() + "] is too big to buffer");
2091 } else {
2092 try (FileInputStream fis = new FileInputStream(f)){
2093 byte b[] = new byte[(int)f.length()];
2094 IOTools.readFully(fis, b);
2095 return new StreamSource(new ByteArrayInputStream(b));
2096 }
2097 }
2098 }
2099 }
2100
2101 return null;
2102 }
2103
2104
2105 private File validateGlobalXsltFile() {
2106 Context context = resources.getContext();
2107
2108 File baseConf = new File(context.getCatalinaBase(), "conf");
2109 File result = validateGlobalXsltFile(baseConf);
2110 if (result == null) {
2111 File homeConf = new File(context.getCatalinaHome(), "conf");
2112 if (!baseConf.equals(homeConf)) {
2113 result = validateGlobalXsltFile(homeConf);
2114 }
2115 }
2116
2117 return result;
2118 }
2119
2120
2121 private File validateGlobalXsltFile(File base) {
2122 File candidate = new File(globalXsltFile);
2123 if (!candidate.isAbsolute()) {
2124 candidate = new File(base, globalXsltFile);
2125 }
2126
2127 if (!candidate.isFile()) {
2128 return null;
2129 }
2130
2131
2132 try {
2133 if (!candidate.getCanonicalPath().startsWith(base.getCanonicalPath())) {
2134 return null;
2135 }
2136 } catch (IOException ioe) {
2137 return null;
2138 }
2139
2140
2141 String nameLower = candidate.getName().toLowerCase(Locale.ENGLISH);
2142 if (!nameLower.endsWith(".xslt") && !nameLower.endsWith(".xsl")) {
2143 return null;
2144 }
2145
2146 return candidate;
2147 }
2148
2149
2150 private Source secureXslt(InputStream is) {
2151
2152 Source result = null;
2153 try {
2154 DocumentBuilder builder = factory.newDocumentBuilder();
2155 builder.setEntityResolver(secureEntityResolver);
2156 Document document = builder.parse(is);
2157 result = new DOMSource(document);
2158 } catch (ParserConfigurationException | SAXException | IOException e) {
2159 if (debug > 0) {
2160 log(e.getMessage(), e);
2161 }
2162 } finally {
2163 if (is != null) {
2164 try {
2165 is.close();
2166 } catch (IOException e) {
2167
2168 }
2169 }
2170 }
2171 return result;
2172 }
2173
2174
2175
2176
2177
2188 protected boolean checkSendfile(HttpServletRequest request,
2189 HttpServletResponse response,
2190 WebResource resource,
2191 long length, Range range) {
2192 String canonicalPath;
2193 if (sendfileSize > 0
2194 && length > sendfileSize
2195 && (Boolean.TRUE.equals(request.getAttribute(Globals.SENDFILE_SUPPORTED_ATTR)))
2196 && (request.getClass().getName().equals("org.apache.catalina.connector.RequestFacade"))
2197 && (response.getClass().getName().equals("org.apache.catalina.connector.ResponseFacade"))
2198 && resource.isFile()
2199 && ((canonicalPath = resource.getCanonicalPath()) != null)
2200 ) {
2201 request.setAttribute(Globals.SENDFILE_FILENAME_ATTR, canonicalPath);
2202 if (range == null) {
2203 request.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0L));
2204 request.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(length));
2205 } else {
2206 request.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(range.start));
2207 request.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(range.end + 1));
2208 }
2209 return true;
2210 }
2211 return false;
2212 }
2213
2214
2215
2226 protected boolean checkIfMatch(HttpServletRequest request,
2227 HttpServletResponse response, WebResource resource)
2228 throws IOException {
2229
2230 String eTag = resource.getETag();
2231 String headerValue = request.getHeader("If-Match");
2232 if (headerValue != null) {
2233 if (headerValue.indexOf('*') == -1) {
2234
2235 StringTokenizer commaTokenizer = new StringTokenizer
2236 (headerValue, ",");
2237 boolean conditionSatisfied = false;
2238
2239 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2240 String currentToken = commaTokenizer.nextToken();
2241 if (currentToken.trim().equals(eTag))
2242 conditionSatisfied = true;
2243 }
2244
2245
2246
2247 if (!conditionSatisfied) {
2248 response.sendError
2249 (HttpServletResponse.SC_PRECONDITION_FAILED);
2250 return false;
2251 }
2252
2253 }
2254 }
2255 return true;
2256 }
2257
2258
2259
2269 protected boolean checkIfModifiedSince(HttpServletRequest request,
2270 HttpServletResponse response, WebResource resource) {
2271 try {
2272 long headerValue = request.getDateHeader("If-Modified-Since");
2273 long lastModified = resource.getLastModified();
2274 if (headerValue != -1) {
2275
2276
2277
2278 if ((request.getHeader("If-None-Match") == null)
2279 && (lastModified < headerValue + 1000)) {
2280
2281
2282 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2283 response.setHeader("ETag", resource.getETag());
2284
2285 return false;
2286 }
2287 }
2288 } catch (IllegalArgumentException illegalArgument) {
2289 return true;
2290 }
2291 return true;
2292 }
2293
2294
2295
2306 protected boolean checkIfNoneMatch(HttpServletRequest request,
2307 HttpServletResponse response, WebResource resource)
2308 throws IOException {
2309
2310 String eTag = resource.getETag();
2311 String headerValue = request.getHeader("If-None-Match");
2312 if (headerValue != null) {
2313
2314 boolean conditionSatisfied = false;
2315
2316 if (!headerValue.equals("*")) {
2317
2318 StringTokenizer commaTokenizer =
2319 new StringTokenizer(headerValue, ",");
2320
2321 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2322 String currentToken = commaTokenizer.nextToken();
2323 if (currentToken.trim().equals(eTag))
2324 conditionSatisfied = true;
2325 }
2326
2327 } else {
2328 conditionSatisfied = true;
2329 }
2330
2331 if (conditionSatisfied) {
2332
2333
2334
2335
2336
2337 if ( ("GET".equals(request.getMethod()))
2338 || ("HEAD".equals(request.getMethod())) ) {
2339 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2340 response.setHeader("ETag", eTag);
2341
2342 return false;
2343 }
2344 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2345 return false;
2346 }
2347 }
2348 return true;
2349 }
2350
2351
2362 protected boolean checkIfUnmodifiedSince(HttpServletRequest request,
2363 HttpServletResponse response, WebResource resource)
2364 throws IOException {
2365 try {
2366 long lastModified = resource.getLastModified();
2367 long headerValue = request.getDateHeader("If-Unmodified-Since");
2368 if (headerValue != -1) {
2369 if ( lastModified >= (headerValue + 1000)) {
2370
2371
2372 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2373 return false;
2374 }
2375 }
2376 } catch(IllegalArgumentException illegalArgument) {
2377 return true;
2378 }
2379 return true;
2380 }
2381
2382
2383
2393 protected void copy(InputStream is, ServletOutputStream ostream) throws IOException {
2394
2395 IOException exception = null;
2396 InputStream istream = new BufferedInputStream(is, input);
2397
2398
2399 exception = copyRange(istream, ostream);
2400
2401
2402 istream.close();
2403
2404
2405 if (exception != null)
2406 throw exception;
2407 }
2408
2409
2410
2421 protected void copy(InputStream is, PrintWriter writer, String encoding) throws IOException {
2422 IOException exception = null;
2423
2424 Reader reader;
2425 if (encoding == null) {
2426 reader = new InputStreamReader(is);
2427 } else {
2428 reader = new InputStreamReader(is, encoding);
2429 }
2430
2431
2432 exception = copyRange(reader, writer);
2433
2434
2435 reader.close();
2436
2437
2438 if (exception != null) {
2439 throw exception;
2440 }
2441 }
2442
2443
2444
2454 protected void copy(WebResource resource, ServletOutputStream ostream,
2455 Range range)
2456 throws IOException {
2457
2458 IOException exception = null;
2459
2460 InputStream resourceInputStream = resource.getInputStream();
2461 InputStream istream =
2462 new BufferedInputStream(resourceInputStream, input);
2463 exception = copyRange(istream, ostream, range.start, range.end);
2464
2465
2466 istream.close();
2467
2468
2469 if (exception != null)
2470 throw exception;
2471
2472 }
2473
2474
2475
2487 protected void copy(WebResource resource, ServletOutputStream ostream,
2488 Iterator<Range> ranges, String contentType)
2489 throws IOException {
2490
2491 IOException exception = null;
2492
2493 while ( (exception == null) && (ranges.hasNext()) ) {
2494
2495 InputStream resourceInputStream = resource.getInputStream();
2496 try (InputStream istream = new BufferedInputStream(resourceInputStream, input)) {
2497
2498 Range currentRange = ranges.next();
2499
2500
2501 ostream.println();
2502 ostream.println("--" + mimeSeparation);
2503 if (contentType != null)
2504 ostream.println("Content-Type: " + contentType);
2505 ostream.println("Content-Range: bytes " + currentRange.start
2506 + "-" + currentRange.end + "/"
2507 + currentRange.length);
2508 ostream.println();
2509
2510
2511 exception = copyRange(istream, ostream, currentRange.start,
2512 currentRange.end);
2513 }
2514 }
2515
2516 ostream.println();
2517 ostream.print("--" + mimeSeparation + "--");
2518
2519
2520 if (exception != null)
2521 throw exception;
2522
2523 }
2524
2525
2526
2535 protected IOException copyRange(InputStream istream,
2536 ServletOutputStream ostream) {
2537
2538
2539 IOException exception = null;
2540 byte buffer[] = new byte[input];
2541 int len = buffer.length;
2542 while (true) {
2543 try {
2544 len = istream.read(buffer);
2545 if (len == -1)
2546 break;
2547 ostream.write(buffer, 0, len);
2548 } catch (IOException e) {
2549 exception = e;
2550 len = -1;
2551 break;
2552 }
2553 }
2554 return exception;
2555
2556 }
2557
2558
2559
2568 protected IOException copyRange(Reader reader, PrintWriter writer) {
2569
2570
2571 IOException exception = null;
2572 char buffer[] = new char[input];
2573 int len = buffer.length;
2574 while (true) {
2575 try {
2576 len = reader.read(buffer);
2577 if (len == -1)
2578 break;
2579 writer.write(buffer, 0, len);
2580 } catch (IOException e) {
2581 exception = e;
2582 len = -1;
2583 break;
2584 }
2585 }
2586 return exception;
2587
2588 }
2589
2590
2591
2602 protected IOException copyRange(InputStream istream,
2603 ServletOutputStream ostream,
2604 long start, long end) {
2605
2606 if (debug > 10)
2607 log("Serving bytes:" + start + "-" + end);
2608
2609 long skipped = 0;
2610 try {
2611 skipped = istream.skip(start);
2612 } catch (IOException e) {
2613 return e;
2614 }
2615 if (skipped < start) {
2616 return new IOException(sm.getString("defaultServlet.skipfail",
2617 Long.valueOf(skipped), Long.valueOf(start)));
2618 }
2619
2620 IOException exception = null;
2621 long bytesToRead = end - start + 1;
2622
2623 byte buffer[] = new byte[input];
2624 int len = buffer.length;
2625 while ( (bytesToRead > 0) && (len >= buffer.length)) {
2626 try {
2627 len = istream.read(buffer);
2628 if (bytesToRead >= len) {
2629 ostream.write(buffer, 0, len);
2630 bytesToRead -= len;
2631 } else {
2632 ostream.write(buffer, 0, (int) bytesToRead);
2633 bytesToRead = 0;
2634 }
2635 } catch (IOException e) {
2636 exception = e;
2637 len = -1;
2638 }
2639 if (len < buffer.length)
2640 break;
2641 }
2642
2643 return exception;
2644
2645 }
2646
2647
2648 protected static class Range {
2649
2650 public long start;
2651 public long end;
2652 public long length;
2653
2654
2659 public boolean validate() {
2660 if (end >= length)
2661 end = length - 1;
2662 return (start >= 0) && (end >= 0) && (start <= end) && (length > 0);
2663 }
2664 }
2665
2666 protected static class CompressionFormat implements Serializable {
2667 private static final long serialVersionUID = 1L;
2668 public final String extension;
2669 public final String encoding;
2670
2671 public CompressionFormat(String extension, String encoding) {
2672 this.extension = extension;
2673 this.encoding = encoding;
2674 }
2675 }
2676
2677 private static class PrecompressedResource {
2678 public final WebResource resource;
2679 public final CompressionFormat format;
2680
2681 private PrecompressedResource(WebResource resource, CompressionFormat format) {
2682 this.resource = resource;
2683 this.format = format;
2684 }
2685 }
2686
2687
2691 private static class SecureEntityResolver implements EntityResolver2 {
2692
2693 @Override
2694 public InputSource resolveEntity(String publicId, String systemId)
2695 throws SAXException, IOException {
2696 throw new SAXException(sm.getString("defaultServlet.blockExternalEntity",
2697 publicId, systemId));
2698 }
2699
2700 @Override
2701 public InputSource getExternalSubset(String name, String baseURI)
2702 throws SAXException, IOException {
2703 throw new SAXException(sm.getString("defaultServlet.blockExternalSubset",
2704 name, baseURI));
2705 }
2706
2707 @Override
2708 public InputSource resolveEntity(String name, String publicId,
2709 String baseURI, String systemId) throws SAXException,
2710 IOException {
2711 throw new SAXException(sm.getString("defaultServlet.blockExternalEntity2",
2712 name, publicId, baseURI, systemId));
2713 }
2714 }
2715
2716
2725 private char getOrderChar(SortManager.Order order, char column) {
2726 if(column == order.column) {
2727 if(order.ascending) {
2728 return 'D';
2729 } else {
2730 return 'A';
2731 }
2732 } else {
2733 return 'D';
2734 }
2735 }
2736
2737
2740 private static class SortManager
2741 {
2742
2745 protected Comparator<WebResource> defaultResourceComparator;
2746
2747
2750 protected Comparator<WebResource> resourceNameComparator;
2751
2752
2755 protected Comparator<WebResource> resourceNameComparatorAsc;
2756
2757
2760 protected Comparator<WebResource> resourceSizeComparator;
2761
2762
2765 protected Comparator<WebResource> resourceSizeComparatorAsc;
2766
2767
2770 protected Comparator<WebResource> resourceLastModifiedComparator;
2771
2772
2775 protected Comparator<WebResource> resourceLastModifiedComparatorAsc;
2776
2777 public SortManager(boolean directoriesFirst) {
2778 resourceNameComparator = new ResourceNameComparator();
2779 resourceNameComparatorAsc = Collections.reverseOrder(resourceNameComparator);
2780 resourceSizeComparator = new ResourceSizeComparator(resourceNameComparator);
2781 resourceSizeComparatorAsc = Collections.reverseOrder(resourceSizeComparator);
2782 resourceLastModifiedComparator = new ResourceLastModifiedDateComparator(resourceNameComparator);
2783 resourceLastModifiedComparatorAsc = Collections.reverseOrder(resourceLastModifiedComparator);
2784
2785 if(directoriesFirst) {
2786 resourceNameComparator = new DirsFirstComparator(resourceNameComparator);
2787 resourceNameComparatorAsc = new DirsFirstComparator(resourceNameComparatorAsc);
2788 resourceSizeComparator = new DirsFirstComparator(resourceSizeComparator);
2789 resourceSizeComparatorAsc = new DirsFirstComparator(resourceSizeComparatorAsc);
2790 resourceLastModifiedComparator = new DirsFirstComparator(resourceLastModifiedComparator);
2791 resourceLastModifiedComparatorAsc = new DirsFirstComparator(resourceLastModifiedComparatorAsc);
2792 }
2793
2794 defaultResourceComparator = resourceNameComparator;
2795 }
2796
2797
2805 public void sort(WebResource[] resources, String order) {
2806 Comparator<WebResource> comparator = getComparator(order);
2807
2808 if(null != comparator)
2809 Arrays.sort(resources, comparator);
2810 }
2811
2812 public Comparator<WebResource> getComparator(String order) {
2813 return getComparator(getOrder(order));
2814 }
2815
2816 public Comparator<WebResource> getComparator(Order order) {
2817 if(null == order)
2818 return defaultResourceComparator;
2819
2820 if('N' == order.column) {
2821 if(order.ascending) {
2822 return resourceNameComparatorAsc;
2823 } else {
2824 return resourceNameComparator;
2825 }
2826 }
2827
2828 if('S' == order.column) {
2829 if(order.ascending) {
2830 return resourceSizeComparatorAsc;
2831 } else {
2832 return resourceSizeComparator;
2833 }
2834 }
2835
2836 if('M' == order.column) {
2837 if(order.ascending) {
2838 return resourceLastModifiedComparatorAsc;
2839 } else {
2840 return resourceLastModifiedComparator;
2841 }
2842 }
2843
2844 return defaultResourceComparator;
2845 }
2846
2847
2858 public Order getOrder(String order) {
2859 if(null == order || 0 == order.trim().length())
2860 return Order.DEFAULT;
2861
2862 String[] options = order.split(";");
2863
2864 if(0 == options.length)
2865 return Order.DEFAULT;
2866
2867 char column = '\0';
2868 boolean ascending = false;
2869
2870 for(String option : options) {
2871 option = option.trim();
2872
2873 if(2 < option.length()) {
2874 char opt = option.charAt(0);
2875 if('C' == opt)
2876 column = option.charAt(2);
2877 else if('O' == opt)
2878 ascending = ('A' == option.charAt(2));
2879 }
2880 }
2881
2882 if('N' == column) {
2883 if(ascending) {
2884 return Order.NAME_ASC;
2885 } else {
2886 return Order.NAME;
2887 }
2888 }
2889
2890 if('S' == column) {
2891 if(ascending) {
2892 return Order.SIZE_ASC;
2893 } else {
2894 return Order.SIZE;
2895 }
2896 }
2897
2898 if('M' == column) {
2899 if(ascending) {
2900 return Order.LAST_MODIFIED_ASC;
2901 } else {
2902 return Order.LAST_MODIFIED;
2903 }
2904 }
2905
2906 return Order.DEFAULT;
2907 }
2908
2909 public static class Order {
2910 final char column;
2911 final boolean ascending;
2912
2913 public Order(char column, boolean ascending) {
2914 this.column = column;
2915 this.ascending = ascending;
2916 }
2917
2918 public static final Order NAME = new Order('N', false);
2919 public static final Order NAME_ASC = new Order('N', true);
2920 public static final Order SIZE = new Order('S', false);
2921 public static final Order SIZE_ASC = new Order('S', true);
2922 public static final Order LAST_MODIFIED = new Order('M', false);
2923 public static final Order LAST_MODIFIED_ASC = new Order('M', true);
2924
2925 public static final Order DEFAULT = NAME;
2926 }
2927 }
2928
2929 private static class DirsFirstComparator
2930 implements Comparator<WebResource>
2931 {
2932 private final Comparator<WebResource> base;
2933
2934 public DirsFirstComparator(Comparator<WebResource> core) {
2935 this.base = core;
2936 }
2937
2938 @Override
2939 public int compare(WebResource r1, WebResource r2) {
2940 if(r1.isDirectory()) {
2941 if(r2.isDirectory()) {
2942 return base.compare(r1, r2);
2943 } else {
2944 return -1;
2945 }
2946 } else if(r2.isDirectory()) {
2947 return 1;
2948 } else {
2949 return base.compare(r1, r2);
2950 }
2951 }
2952 }
2953
2954 private static class ResourceNameComparator
2955 implements Comparator<WebResource>
2956 {
2957 @Override
2958 public int compare(WebResource r1, WebResource r2) {
2959 return r1.getName().compareTo(r2.getName());
2960 }
2961 }
2962
2963 private static class ResourceSizeComparator
2964 implements Comparator<WebResource>
2965 {
2966 private Comparator<WebResource> base;
2967
2968 public ResourceSizeComparator(Comparator<WebResource> base) {
2969 this.base = base;
2970 }
2971
2972 @Override
2973 public int compare(WebResource r1, WebResource r2) {
2974 int c = Long.compare(r1.getContentLength(), r2.getContentLength());
2975
2976 if(0 == c)
2977 return base.compare(r1, r2);
2978 else
2979 return c;
2980 }
2981 }
2982
2983 private static class ResourceLastModifiedDateComparator
2984 implements Comparator<WebResource>
2985 {
2986 private Comparator<WebResource> base;
2987
2988 public ResourceLastModifiedDateComparator(Comparator<WebResource> base) {
2989 this.base = base;
2990 }
2991
2992 @Override
2993 public int compare(WebResource r1, WebResource r2) {
2994 int c = Long.compare(r1.getLastModified(), r2.getLastModified());
2995
2996 if(0 == c)
2997 return base.compare(r1, r2);
2998 else
2999 return c;
3000 }
3001 }
3002 }
3003