001
014
015 package com.liferay.portal.kernel.servlet;
016
017 import com.liferay.portal.kernel.log.Log;
018 import com.liferay.portal.kernel.log.LogFactoryUtil;
019 import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil;
020 import com.liferay.portal.kernel.util.ArrayUtil;
021 import com.liferay.portal.kernel.util.FileUtil;
022 import com.liferay.portal.kernel.util.GetterUtil;
023 import com.liferay.portal.kernel.util.HttpUtil;
024 import com.liferay.portal.kernel.util.MimeTypesUtil;
025 import com.liferay.portal.kernel.util.PropsKeys;
026 import com.liferay.portal.kernel.util.PropsUtil;
027 import com.liferay.portal.kernel.util.RandomAccessInputStream;
028 import com.liferay.portal.kernel.util.ServerDetector;
029 import com.liferay.portal.kernel.util.StreamUtil;
030 import com.liferay.portal.kernel.util.StringBundler;
031 import com.liferay.portal.kernel.util.StringPool;
032 import com.liferay.portal.kernel.util.StringUtil;
033 import com.liferay.portal.kernel.util.Validator;
034
035 import java.io.ByteArrayInputStream;
036 import java.io.File;
037 import java.io.FileInputStream;
038 import java.io.IOException;
039 import java.io.InputStream;
040 import java.io.OutputStream;
041
042 import java.net.SocketException;
043
044 import java.nio.ByteBuffer;
045 import java.nio.CharBuffer;
046 import java.nio.channels.Channels;
047 import java.nio.channels.FileChannel;
048
049 import java.util.ArrayList;
050 import java.util.Collections;
051 import java.util.List;
052
053 import javax.servlet.ServletOutputStream;
054 import javax.servlet.http.HttpServletRequest;
055 import javax.servlet.http.HttpServletResponse;
056
057
061 public class ServletResponseUtil {
062
063 public static List<Range> getRanges(
064 HttpServletRequest request, HttpServletResponse response,
065 long length)
066 throws IOException {
067
068 String rangeString = request.getHeader(HttpHeaders.RANGE);
069
070 if (Validator.isNull(rangeString)) {
071 return Collections.emptyList();
072 }
073
074 if (!rangeString.matches(_RANGE_REGEX)) {
075 throw new IOException(
076 "Range header does not match regular expression " +
077 rangeString);
078 }
079
080 List<Range> ranges = new ArrayList<Range>();
081
082 String[] rangeFields = StringUtil.split(rangeString.substring(6));
083
084 if (rangeFields.length > _MAX_RANGE_FIELDS) {
085 StringBundler sb = new StringBundler(8);
086
087 sb.append("Request range ");
088 sb.append(rangeString);
089 sb.append(" with ");
090 sb.append(rangeFields.length);
091 sb.append(" range fields has exceeded maximum allowance as ");
092 sb.append("specified by the property \"");
093 sb.append(PropsKeys.WEB_SERVER_SERVLET_MAX_RANGE_FIELDS);
094 sb.append("\"");
095
096 throw new IOException(sb.toString());
097 }
098
099 for (String rangeField : rangeFields) {
100 int index = rangeField.indexOf(StringPool.DASH);
101
102 long start = GetterUtil.getLong(rangeField.substring(0, index), -1);
103 long end = GetterUtil.getLong(
104 rangeField.substring(index + 1, rangeField.length()), -1);
105
106 if (start == -1) {
107 start = length - end;
108 end = length - 1;
109 }
110 else if ((end == -1) || (end > (length - 1))) {
111 end = length - 1;
112 }
113
114 if (start > end) {
115 throw new IOException(
116 "Range start " + start + " is greater than end " + end);
117 }
118
119 Range range = new Range(start, end, length);
120
121 ranges.add(range);
122 }
123
124 return ranges;
125 }
126
127 public static boolean isClientAbortException(IOException ioe) {
128 Class<?> clazz = ioe.getClass();
129
130 String className = clazz.getName();
131
132 if (className.equals(_CLIENT_ABORT_EXCEPTION)) {
133 return true;
134 }
135 else {
136 return false;
137 }
138 }
139
140 public static void sendFile(
141 HttpServletRequest request, HttpServletResponse response,
142 String fileName, byte[] bytes)
143 throws IOException {
144
145 sendFile(request, response, fileName, bytes, null);
146 }
147
148 public static void sendFile(
149 HttpServletRequest request, HttpServletResponse response,
150 String fileName, byte[] bytes, String contentType)
151 throws IOException {
152
153 sendFile(request, response, fileName, bytes, contentType, null);
154 }
155
156 public static void sendFile(
157 HttpServletRequest request, HttpServletResponse response,
158 String fileName, byte[] bytes, String contentType,
159 String contentDispositionType)
160 throws IOException {
161
162 setHeaders(
163 request, response, fileName, contentType, contentDispositionType);
164
165 write(response, bytes);
166 }
167
168 public static void sendFile(
169 HttpServletRequest request, HttpServletResponse response,
170 String fileName, InputStream inputStream)
171 throws IOException {
172
173 sendFile(request, response, fileName, inputStream, null);
174 }
175
176 public static void sendFile(
177 HttpServletRequest request, HttpServletResponse response,
178 String fileName, InputStream inputStream, long contentLength,
179 String contentType)
180 throws IOException {
181
182 sendFile(
183 request, response, fileName, inputStream, contentLength,
184 contentType, null);
185 }
186
187 public static void sendFile(
188 HttpServletRequest request, HttpServletResponse response,
189 String fileName, InputStream inputStream, long contentLength,
190 String contentType, String contentDispositionType)
191 throws IOException {
192
193 setHeaders(
194 request, response, fileName, contentType, contentDispositionType);
195
196 write(response, inputStream, contentLength);
197 }
198
199 public static void sendFile(
200 HttpServletRequest request, HttpServletResponse response,
201 String fileName, InputStream inputStream, String contentType)
202 throws IOException {
203
204 sendFile(request, response, fileName, inputStream, 0, contentType);
205 }
206
207
210 public static void sendFile(
211 HttpServletResponse response, String fileName, byte[] bytes)
212 throws IOException {
213
214 sendFile(null, response, fileName, bytes);
215 }
216
217
220 public static void sendFile(
221 HttpServletResponse response, String fileName, byte[] bytes,
222 String contentType)
223 throws IOException {
224
225 sendFile(null, response, fileName, bytes, contentType);
226 }
227
228
231 public static void sendFile(
232 HttpServletResponse response, String fileName,
233 InputStream inputStream)
234 throws IOException {
235
236 sendFile(null, response, fileName, inputStream);
237 }
238
239
242 public static void sendFile(
243 HttpServletResponse response, String fileName,
244 InputStream inputStream, int contentLength, String contentType)
245 throws IOException {
246
247 sendFile(
248 null, response, fileName, inputStream, contentLength, contentType);
249 }
250
251
254 public static void sendFile(
255 HttpServletResponse response, String fileName,
256 InputStream inputStream, String contentType)
257 throws IOException {
258
259 sendFile(null, response, fileName, inputStream, contentType);
260 }
261
262 public static void sendFileWithRangeHeader(
263 HttpServletRequest request, HttpServletResponse response,
264 String fileName, InputStream inputStream, long contentLength,
265 String contentType)
266 throws IOException {
267
268 if (_log.isDebugEnabled()) {
269 _log.debug("Accepting ranges for the file " + fileName);
270 }
271
272 response.setHeader(
273 HttpHeaders.ACCEPT_RANGES, HttpHeaders.ACCEPT_RANGES_BYTES_VALUE);
274
275 List<Range> ranges = null;
276
277 try {
278 ranges = getRanges(request, response, contentLength);
279 }
280 catch (IOException ioe) {
281 if (_log.isErrorEnabled()) {
282 _log.error(ioe);
283 }
284
285 response.setHeader(
286 HttpHeaders.CONTENT_RANGE, "bytes */" + contentLength);
287
288 response.sendError(
289 HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
290
291 return;
292 }
293
294 if ((ranges == null) || ranges.isEmpty()) {
295 sendFile(
296 request, response, fileName, inputStream, contentLength,
297 contentType);
298 }
299 else {
300 if (_log.isDebugEnabled()) {
301 _log.debug(
302 "Request has range header " +
303 request.getHeader(HttpHeaders.RANGE));
304 }
305
306 write(
307 request, response, fileName, ranges, inputStream, contentLength,
308 contentType);
309 }
310 }
311
312 public static void write(
313 HttpServletRequest request, HttpServletResponse response,
314 String fileName, List<Range> ranges, InputStream inputStream,
315 long fullLength, String contentType)
316 throws IOException {
317
318 OutputStream outputStream = null;
319
320 try {
321 outputStream = response.getOutputStream();
322
323 Range fullRange = new Range(0, fullLength - 1, fullLength);
324
325 Range firstRange = null;
326
327 if (!ranges.isEmpty()) {
328 firstRange = ranges.get(0);
329 }
330
331 if ((firstRange == null) || firstRange.equals(fullRange)) {
332 if (_log.isDebugEnabled()) {
333 _log.debug("Writing full range");
334 }
335
336 response.setContentType(contentType);
337
338 setHeaders(
339 request, response, fileName, contentType, null, fullRange);
340
341 copyRange(
342 inputStream, outputStream, fullRange.getStart(),
343 fullRange.getLength());
344 }
345 else if (ranges.size() == 1) {
346 if (_log.isDebugEnabled()) {
347 _log.debug("Attempting to write a single range");
348 }
349
350 Range range = ranges.get(0);
351
352 response.setContentType(contentType);
353
354 setHeaders(
355 request, response, fileName, contentType, null, range);
356
357 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
358
359 copyRange(
360 inputStream, outputStream, range.getStart(),
361 range.getLength());
362 }
363 else if (ranges.size() > 1 ) {
364 if (_log.isDebugEnabled()) {
365 _log.debug("Attempting to write multiple ranges");
366 }
367
368 ServletOutputStream servletOutputStream =
369 (ServletOutputStream)outputStream;
370
371 String boundary =
372 "liferay-multipart-boundary-" + System.currentTimeMillis();
373
374 String multipartContentType =
375 "multipart/byteranges; boundary=" + boundary;
376
377 response.setContentType(multipartContentType);
378
379 setHeaders(
380 request, response, fileName, multipartContentType, null);
381
382 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
383
384 for (int i = 0; i < ranges.size(); i++) {
385 Range range = ranges.get(i);
386
387 servletOutputStream.println();
388 servletOutputStream.println(
389 StringPool.DOUBLE_DASH + boundary);
390 servletOutputStream.println(
391 HttpHeaders.CONTENT_TYPE + ": " + contentType);
392 servletOutputStream.println(
393 HttpHeaders.CONTENT_RANGE + ": " +
394 range.getContentRange());
395 servletOutputStream.println();
396
397 inputStream = copyRange(
398 inputStream, outputStream, range.getStart(),
399 range.getLength());
400 }
401
402 servletOutputStream.println();
403 servletOutputStream.println(
404 StringPool.DOUBLE_DASH + boundary + StringPool.DOUBLE_DASH);
405 }
406 }
407 finally {
408 try {
409 inputStream.close();
410 }
411 catch (IOException ioe) {
412 }
413 }
414 }
415
416 public static void write(
417 HttpServletResponse response,
418 BufferCacheServletResponse bufferCacheServletResponse)
419 throws IOException {
420
421 if (bufferCacheServletResponse.isByteMode()) {
422 write(response, bufferCacheServletResponse.getByteBuffer());
423 }
424 else if (bufferCacheServletResponse.isCharMode()) {
425 write(response, bufferCacheServletResponse.getCharBuffer());
426 }
427 }
428
429 public static void write(HttpServletResponse response, byte[] bytes)
430 throws IOException {
431
432 write(response, bytes, 0, 0);
433 }
434
435 public static void write(
436 HttpServletResponse response, byte[] bytes, int offset,
437 int contentLength)
438 throws IOException {
439
440 try {
441
442
443
444 if (!response.isCommitted()) {
445
446
447
448 if (contentLength == 0) {
449 contentLength = bytes.length;
450 }
451
452 response.setContentLength(contentLength);
453
454 response.flushBuffer();
455
456 if (response instanceof BufferCacheServletResponse) {
457 BufferCacheServletResponse bufferCacheServletResponse =
458 (BufferCacheServletResponse)response;
459
460 bufferCacheServletResponse.setByteBuffer(
461 ByteBuffer.wrap(bytes, offset, contentLength));
462 }
463 else {
464 ServletOutputStream servletOutputStream =
465 response.getOutputStream();
466
467 if ((contentLength == 0) && ServerDetector.isJetty()) {
468 }
469 else {
470 servletOutputStream.write(bytes, offset, contentLength);
471 }
472 }
473 }
474 }
475 catch (IOException ioe) {
476 if ((ioe instanceof SocketException) ||
477 isClientAbortException(ioe)) {
478
479 if (_log.isWarnEnabled()) {
480 _log.warn(ioe);
481 }
482 }
483 else {
484 throw ioe;
485 }
486 }
487 }
488
489 public static void write(HttpServletResponse response, byte[][] bytesArray)
490 throws IOException {
491
492 try {
493
494
495
496 if (!response.isCommitted()) {
497 long contentLength = 0;
498
499 for (byte[] bytes : bytesArray) {
500 contentLength += bytes.length;
501 }
502
503 setContentLength(response, contentLength);
504
505 response.flushBuffer();
506
507 ServletOutputStream servletOutputStream =
508 response.getOutputStream();
509
510 for (byte[] bytes : bytesArray) {
511 servletOutputStream.write(bytes);
512 }
513 }
514 }
515 catch (IOException ioe) {
516 if ((ioe instanceof SocketException) ||
517 isClientAbortException(ioe)) {
518
519 if (_log.isWarnEnabled()) {
520 _log.warn(ioe);
521 }
522 }
523 else {
524 throw ioe;
525 }
526 }
527 }
528
529 public static void write(
530 HttpServletResponse response, ByteBuffer byteBuffer)
531 throws IOException {
532
533 if (response instanceof BufferCacheServletResponse) {
534 BufferCacheServletResponse bufferCacheServletResponse =
535 (BufferCacheServletResponse)response;
536
537 bufferCacheServletResponse.setByteBuffer(byteBuffer);
538 }
539 else {
540 write(
541 response, byteBuffer.array(),
542 byteBuffer.arrayOffset() + byteBuffer.position(),
543 byteBuffer.arrayOffset() + byteBuffer.limit());
544 }
545 }
546
547 public static void write(
548 HttpServletResponse response, CharBuffer charBuffer)
549 throws IOException {
550
551 if (response instanceof BufferCacheServletResponse) {
552 BufferCacheServletResponse bufferCacheServletResponse =
553 (BufferCacheServletResponse)response;
554
555 bufferCacheServletResponse.setCharBuffer(charBuffer);
556 }
557 else {
558 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
559 StringPool.UTF8, charBuffer);
560
561 write(response, byteBuffer);
562 }
563 }
564
565 public static void write(HttpServletResponse response, File file)
566 throws IOException {
567
568 if (response instanceof BufferCacheServletResponse) {
569 BufferCacheServletResponse bufferCacheServletResponse =
570 (BufferCacheServletResponse)response;
571
572 ByteBuffer byteBuffer = ByteBuffer.wrap(FileUtil.getBytes(file));
573
574 bufferCacheServletResponse.setByteBuffer(byteBuffer);
575 }
576 else {
577 FileInputStream fileInputStream = new FileInputStream(file);
578
579 FileChannel fileChannel = fileInputStream.getChannel();
580
581 try {
582 long contentLength = fileChannel.size();
583
584 setContentLength(response, contentLength);
585
586 response.flushBuffer();
587
588 fileChannel.transferTo(
589 0, contentLength,
590 Channels.newChannel(response.getOutputStream()));
591 }
592 finally {
593 fileChannel.close();
594 }
595 }
596 }
597
598 public static void write(
599 HttpServletResponse response, InputStream inputStream)
600 throws IOException {
601
602 write(response, inputStream, 0);
603 }
604
605 public static void write(
606 HttpServletResponse response, InputStream inputStream,
607 long contentLength)
608 throws IOException {
609
610 OutputStream outputStream = null;
611
612 try {
613 if (response.isCommitted()) {
614 return;
615 }
616
617 if (contentLength > 0) {
618 setContentLength(response, contentLength);
619 }
620
621 response.flushBuffer();
622
623 StreamUtil.transfer(inputStream, response.getOutputStream(), false);
624 }
625 finally {
626 StreamUtil.cleanUp(inputStream, outputStream);
627 }
628 }
629
630 public static void write(HttpServletResponse response, String s)
631 throws IOException {
632
633 if (response instanceof BufferCacheServletResponse) {
634 BufferCacheServletResponse bufferCacheServletResponse =
635 (BufferCacheServletResponse)response;
636
637 bufferCacheServletResponse.setString(s);
638 }
639 else {
640 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
641 StringPool.UTF8, s);
642
643 write(response, byteBuffer);
644 }
645 }
646
647 protected static InputStream copyRange(
648 InputStream inputStream, OutputStream outputStream, long start,
649 long length)
650 throws IOException {
651
652 if (inputStream instanceof FileInputStream) {
653 FileInputStream fileInputStream = (FileInputStream)inputStream;
654
655 FileChannel fileChannel = fileInputStream.getChannel();
656
657 fileChannel.transferTo(
658 start, length, Channels.newChannel(outputStream));
659
660 return fileInputStream;
661 }
662 else if (inputStream instanceof ByteArrayInputStream) {
663 ByteArrayInputStream byteArrayInputStream =
664 (ByteArrayInputStream)inputStream;
665
666 byteArrayInputStream.reset();
667 byteArrayInputStream.skip(start);
668
669 StreamUtil.transfer(byteArrayInputStream, outputStream, length);
670
671 return byteArrayInputStream;
672 }
673 else if (inputStream instanceof RandomAccessInputStream) {
674 RandomAccessInputStream randomAccessInputStream =
675 (RandomAccessInputStream)inputStream;
676
677 randomAccessInputStream.seek(start);
678
679 StreamUtil.transfer(
680 randomAccessInputStream, outputStream, StreamUtil.BUFFER_SIZE,
681 false, length);
682
683 return randomAccessInputStream;
684 }
685
686 return copyRange(
687 new RandomAccessInputStream(inputStream), outputStream, start,
688 length);
689 }
690
691 protected static void setContentLength(
692 HttpServletResponse response, long contentLength) {
693
694 response.setHeader(
695 HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
696 }
697
698 protected static void setHeaders(
699 HttpServletRequest request, HttpServletResponse response,
700 String fileName, String contentType, String contentDispositionType) {
701
702 if (_log.isDebugEnabled()) {
703 _log.debug("Sending file of type " + contentType);
704 }
705
706
707
708 if (Validator.isNotNull(contentType)) {
709 response.setContentType(contentType);
710 }
711
712 if (!response.containsHeader(HttpHeaders.CACHE_CONTROL)) {
713 response.setHeader(
714 HttpHeaders.CACHE_CONTROL,
715 HttpHeaders.CACHE_CONTROL_PRIVATE_VALUE);
716 }
717
718 if (Validator.isNull(fileName)) {
719 return;
720 }
721
722 String contentDispositionFileName = "filename=\"" + fileName + "\"";
723
724
725
726
727 boolean ascii = true;
728
729 for (int i = 0; i < fileName.length(); i++) {
730 if (!Validator.isAscii(fileName.charAt(i))) {
731 ascii = false;
732
733 break;
734 }
735 }
736
737 if (!ascii) {
738 String encodedFileName = HttpUtil.encodeURL(fileName, true);
739
740 if (BrowserSnifferUtil.isIe(request)) {
741 contentDispositionFileName =
742 "filename=\"" + encodedFileName + "\"";
743 }
744 else {
745 contentDispositionFileName =
746 "filename*=UTF-8''" + encodedFileName;
747 }
748 }
749
750 if (Validator.isNull(contentDispositionType)) {
751 String extension = GetterUtil.getString(
752 FileUtil.getExtension(fileName));
753
754 extension = StringUtil.toLowerCase(extension);
755
756 String[] mimeTypesContentDispositionInline = null;
757
758 try {
759 mimeTypesContentDispositionInline = PropsUtil.getArray(
760 PropsKeys.MIME_TYPES_CONTENT_DISPOSITION_INLINE);
761 }
762 catch (Exception e) {
763 mimeTypesContentDispositionInline = new String[0];
764 }
765
766 if (ArrayUtil.contains(
767 mimeTypesContentDispositionInline, extension)) {
768
769 contentDispositionType = HttpHeaders.CONTENT_DISPOSITION_INLINE;
770
771 contentType = MimeTypesUtil.getContentType(fileName);
772
773 response.setContentType(contentType);
774 }
775 else {
776 contentDispositionType =
777 HttpHeaders.CONTENT_DISPOSITION_ATTACHMENT;
778 }
779 }
780
781 StringBundler sb = new StringBundler(4);
782
783 sb.append(contentDispositionType);
784 sb.append(StringPool.SEMICOLON);
785 sb.append(StringPool.SPACE);
786 sb.append(contentDispositionFileName);
787
788 if (_log.isDebugEnabled()) {
789 _log.debug("Setting content disposition header " + sb.toString());
790 }
791
792 response.setHeader(HttpHeaders.CONTENT_DISPOSITION, sb.toString());
793 }
794
795 protected static void setHeaders(
796 HttpServletRequest request, HttpServletResponse response,
797 String fileName, String contentType, String contentDispositionType,
798 Range range) {
799
800 setHeaders(
801 request, response, fileName, contentType, contentDispositionType);
802
803 if (range != null) {
804 response.setHeader(
805 HttpHeaders.CONTENT_RANGE, range.getContentRange());
806
807 response.setHeader(
808 HttpHeaders.CONTENT_LENGTH, String.valueOf(range.getLength()));
809 }
810 }
811
812 private static final String _CLIENT_ABORT_EXCEPTION =
813 "org.apache.catalina.connector.ClientAbortException";
814
815 private static final int _MAX_RANGE_FIELDS = GetterUtil.getInteger(
816 PropsUtil.get(PropsKeys.WEB_SERVER_SERVLET_MAX_RANGE_FIELDS));
817
818 private static final String _RANGE_REGEX =
819 "^bytes=\\d*-\\d*(,\\s?\\d*-\\d*)*$";
820
821 private static Log _log = LogFactoryUtil.getLog(ServletResponseUtil.class);
822
823 }