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