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 @Deprecated
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 @Deprecated
222 public static void sendFile(
223 HttpServletResponse response, String fileName, byte[] bytes,
224 String contentType)
225 throws IOException {
226
227 sendFile(null, response, fileName, bytes, contentType);
228 }
229
230
233 @Deprecated
234 public static void sendFile(
235 HttpServletResponse response, String fileName,
236 InputStream inputStream)
237 throws IOException {
238
239 sendFile(null, response, fileName, inputStream);
240 }
241
242
245 @Deprecated
246 public static void sendFile(
247 HttpServletResponse response, String fileName,
248 InputStream inputStream, int contentLength, String contentType)
249 throws IOException {
250
251 sendFile(
252 null, response, fileName, inputStream, contentLength, contentType);
253 }
254
255
258 @Deprecated
259 public static void sendFile(
260 HttpServletResponse response, String fileName,
261 InputStream inputStream, String contentType)
262 throws IOException {
263
264 sendFile(null, response, fileName, inputStream, contentType);
265 }
266
267 public static void write(
268 HttpServletRequest request, HttpServletResponse response,
269 String fileName, List<Range> ranges, InputStream inputStream,
270 long fullLength, String contentType)
271 throws IOException {
272
273 OutputStream outputStream = null;
274
275 try {
276 outputStream = response.getOutputStream();
277
278 Range fullRange = new Range(0, fullLength - 1, fullLength);
279
280 Range firstRange = null;
281
282 if (!ranges.isEmpty()) {
283 firstRange = ranges.get(0);
284 }
285
286 if ((firstRange == null) || firstRange.equals(fullRange)) {
287 if (_log.isDebugEnabled()) {
288 _log.debug("Writing full range");
289 }
290
291 response.setContentType(contentType);
292
293 setHeaders(
294 request, response, fileName, contentType, null, fullRange);
295
296 copyRange(
297 inputStream, outputStream, fullRange.getStart(),
298 fullRange.getLength());
299 }
300 else if (ranges.size() == 1) {
301 if (_log.isDebugEnabled()) {
302 _log.debug("Attempting to write a single range");
303 }
304
305 Range range = ranges.get(0);
306
307 response.setContentType(contentType);
308
309 setHeaders(
310 request, response, fileName, contentType, null, range);
311
312 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
313
314 copyRange(
315 inputStream, outputStream, range.getStart(),
316 range.getLength());
317 }
318 else if (ranges.size() > 1 ) {
319 if (_log.isDebugEnabled()) {
320 _log.debug("Attempting to write multiple ranges");
321 }
322
323 ServletOutputStream servletOutputStream =
324 (ServletOutputStream)outputStream;
325
326 String boundary =
327 "liferay-multipart-boundary-" + System.currentTimeMillis();
328
329 String multipartContentType =
330 "multipart/byteranges; boundary=" + boundary;
331
332 response.setContentType(multipartContentType);
333
334 setHeaders(
335 request, response, fileName, multipartContentType, null);
336
337 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
338
339 for (int i = 0; i < ranges.size(); i++) {
340 Range range = ranges.get(i);
341
342 servletOutputStream.println();
343 servletOutputStream.println(
344 StringPool.DOUBLE_DASH + boundary);
345 servletOutputStream.println(
346 HttpHeaders.CONTENT_TYPE + ": " + contentType);
347 servletOutputStream.println(
348 HttpHeaders.CONTENT_RANGE + ": " +
349 range.getContentRange());
350 servletOutputStream.println();
351
352 copyRange(
353 inputStream, outputStream, range.getStart(),
354 range.getLength());
355 }
356
357 servletOutputStream.println();
358 servletOutputStream.println(
359 StringPool.DOUBLE_DASH + boundary + StringPool.DOUBLE_DASH);
360 }
361 }
362 finally {
363 try {
364 inputStream.close();
365 }
366 catch (IOException ioe) {
367 }
368 }
369 }
370
371 public static void write(
372 HttpServletResponse response,
373 BufferCacheServletResponse bufferCacheServletResponse)
374 throws IOException {
375
376 if (bufferCacheServletResponse.isByteMode()) {
377 write(response, bufferCacheServletResponse.getByteBuffer());
378 }
379 else if (bufferCacheServletResponse.isCharMode()) {
380 write(response, bufferCacheServletResponse.getCharBuffer());
381 }
382 }
383
384 public static void write(HttpServletResponse response, byte[] bytes)
385 throws IOException {
386
387 write(response, bytes, 0, 0);
388 }
389
390 public static void write(
391 HttpServletResponse response, byte[] bytes, int offset,
392 int contentLength)
393 throws IOException {
394
395 try {
396
397
398
399 if (!response.isCommitted()) {
400
401
402
403 if (contentLength == 0) {
404 contentLength = bytes.length;
405 }
406
407 response.setContentLength(contentLength);
408
409 response.flushBuffer();
410
411 if (response instanceof BufferCacheServletResponse) {
412 BufferCacheServletResponse bufferCacheServletResponse =
413 (BufferCacheServletResponse)response;
414
415 bufferCacheServletResponse.setByteBuffer(
416 ByteBuffer.wrap(bytes, offset, contentLength));
417 }
418 else {
419 ServletOutputStream servletOutputStream =
420 response.getOutputStream();
421
422 if ((contentLength == 0) && ServerDetector.isJetty()) {
423 }
424 else {
425 servletOutputStream.write(bytes, offset, contentLength);
426 }
427 }
428 }
429 }
430 catch (IOException ioe) {
431 if ((ioe instanceof SocketException) ||
432 isClientAbortException(ioe)) {
433
434 if (_log.isWarnEnabled()) {
435 _log.warn(ioe);
436 }
437 }
438 else {
439 throw ioe;
440 }
441 }
442 }
443
444 public static void write(HttpServletResponse response, byte[][] bytesArray)
445 throws IOException {
446
447 try {
448
449
450
451 if (!response.isCommitted()) {
452 int contentLength = 0;
453
454 for (byte[] bytes : bytesArray) {
455 contentLength += bytes.length;
456 }
457
458 response.setContentLength(contentLength);
459
460 response.flushBuffer();
461
462 ServletOutputStream servletOutputStream =
463 response.getOutputStream();
464
465 for (byte[] bytes : bytesArray) {
466 servletOutputStream.write(bytes);
467 }
468 }
469 }
470 catch (IOException ioe) {
471 if ((ioe instanceof SocketException) ||
472 isClientAbortException(ioe)) {
473
474 if (_log.isWarnEnabled()) {
475 _log.warn(ioe);
476 }
477 }
478 else {
479 throw ioe;
480 }
481 }
482 }
483
484 public static void write(
485 HttpServletResponse response, ByteBuffer byteBuffer)
486 throws IOException {
487
488 if (response instanceof BufferCacheServletResponse) {
489 BufferCacheServletResponse bufferCacheServletResponse =
490 (BufferCacheServletResponse)response;
491
492 bufferCacheServletResponse.setByteBuffer(byteBuffer);
493 }
494 else {
495 write(
496 response, byteBuffer.array(), byteBuffer.position(),
497 byteBuffer.limit());
498 }
499 }
500
501 public static void write(
502 HttpServletResponse response, CharBuffer charBuffer)
503 throws IOException {
504
505 if (response instanceof BufferCacheServletResponse) {
506 BufferCacheServletResponse bufferCacheServletResponse =
507 (BufferCacheServletResponse)response;
508
509 bufferCacheServletResponse.setCharBuffer(charBuffer);
510 }
511 else {
512 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
513 StringPool.UTF8, charBuffer);
514
515 write(response, byteBuffer);
516 }
517 }
518
519 public static void write(HttpServletResponse response, File file)
520 throws IOException {
521
522 if (response instanceof BufferCacheServletResponse) {
523 BufferCacheServletResponse bufferCacheServletResponse =
524 (BufferCacheServletResponse)response;
525
526 ByteBuffer byteBuffer = ByteBuffer.wrap(FileUtil.getBytes(file));
527
528 bufferCacheServletResponse.setByteBuffer(byteBuffer);
529 }
530 else {
531 FileInputStream fileInputStream = new FileInputStream(file);
532
533 try (FileChannel fileChannel = fileInputStream.getChannel()) {
534 int contentLength = (int)fileChannel.size();
535
536 response.setContentLength(contentLength);
537
538 response.flushBuffer();
539
540 fileChannel.transferTo(
541 0, contentLength,
542 Channels.newChannel(response.getOutputStream()));
543 }
544 }
545 }
546
547 public static void write(
548 HttpServletResponse response, InputStream inputStream)
549 throws IOException {
550
551 write(response, inputStream, 0);
552 }
553
554 public static void write(
555 HttpServletResponse response, InputStream inputStream,
556 long contentLength)
557 throws IOException {
558
559 if (response.isCommitted()) {
560 StreamUtil.cleanUp(inputStream);
561
562 return;
563 }
564
565 if (contentLength > 0) {
566 response.setHeader(
567 HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
568 }
569
570 response.flushBuffer();
571
572 StreamUtil.transfer(inputStream, response.getOutputStream());
573 }
574
575 public static void write(HttpServletResponse response, String s)
576 throws IOException {
577
578 if (response instanceof BufferCacheServletResponse) {
579 BufferCacheServletResponse bufferCacheServletResponse =
580 (BufferCacheServletResponse)response;
581
582 bufferCacheServletResponse.setString(s);
583 }
584 else {
585 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
586 StringPool.UTF8, s);
587
588 write(response, byteBuffer);
589 }
590 }
591
592 protected static void copyRange(
593 InputStream inputStream, OutputStream outputStream, long start,
594 long length)
595 throws IOException {
596
597 if (inputStream instanceof FileInputStream) {
598 FileInputStream fileInputStream = (FileInputStream)inputStream;
599
600 FileChannel fileChannel = fileInputStream.getChannel();
601
602 fileChannel.transferTo(
603 start, length, Channels.newChannel(outputStream));
604 }
605 else if (inputStream instanceof ByteArrayInputStream) {
606 ByteArrayInputStream byteArrayInputStream =
607 (ByteArrayInputStream)inputStream;
608
609 byteArrayInputStream.skip(start);
610
611 StreamUtil.transfer(byteArrayInputStream, outputStream, length);
612 }
613 else {
614 RandomAccessInputStream randomAccessInputStream =
615 new RandomAccessInputStream(inputStream);
616
617 randomAccessInputStream.seek(start);
618
619 StreamUtil.transfer(randomAccessInputStream, outputStream, length);
620 }
621 }
622
623 protected static void setHeaders(
624 HttpServletRequest request, HttpServletResponse response,
625 String fileName, String contentType, String contentDispositionType) {
626
627 if (_log.isDebugEnabled()) {
628 _log.debug("Sending file of type " + contentType);
629 }
630
631
632
633 if (Validator.isNotNull(contentType)) {
634 response.setContentType(contentType);
635 }
636
637 if (!response.containsHeader(HttpHeaders.CACHE_CONTROL)) {
638 response.setHeader(
639 HttpHeaders.CACHE_CONTROL,
640 HttpHeaders.CACHE_CONTROL_PRIVATE_VALUE);
641 }
642
643 if (Validator.isNull(fileName)) {
644 return;
645 }
646
647 String contentDispositionFileName = "filename=\"" + fileName + "\"";
648
649
650
651
652 boolean ascii = true;
653
654 for (int i = 0; i < fileName.length(); i++) {
655 if (!Validator.isAscii(fileName.charAt(i))) {
656 ascii = false;
657
658 break;
659 }
660 }
661
662 if (!ascii) {
663 String encodedFileName = HttpUtil.encodeURL(fileName, true);
664
665 if (BrowserSnifferUtil.isIe(request)) {
666 contentDispositionFileName =
667 "filename=\"" + encodedFileName + "\"";
668 }
669 else {
670 contentDispositionFileName =
671 "filename*=UTF-8''" + encodedFileName;
672 }
673 }
674
675 if (Validator.isNull(contentDispositionType)) {
676 String extension = GetterUtil.getString(
677 FileUtil.getExtension(fileName));
678
679 extension = StringUtil.toLowerCase(extension);
680
681 String[] mimeTypesContentDispositionInline = null;
682
683 try {
684 mimeTypesContentDispositionInline = PropsUtil.getArray(
685 PropsKeys.MIME_TYPES_CONTENT_DISPOSITION_INLINE);
686 }
687 catch (Exception e) {
688 mimeTypesContentDispositionInline = new String[0];
689 }
690
691 if (ArrayUtil.contains(
692 mimeTypesContentDispositionInline, extension)) {
693
694 contentDispositionType = HttpHeaders.CONTENT_DISPOSITION_INLINE;
695
696 contentType = MimeTypesUtil.getContentType(fileName);
697
698 response.setContentType(contentType);
699 }
700 else {
701 contentDispositionType =
702 HttpHeaders.CONTENT_DISPOSITION_ATTACHMENT;
703 }
704 }
705
706 StringBundler sb = new StringBundler(4);
707
708 sb.append(contentDispositionType);
709 sb.append(StringPool.SEMICOLON);
710 sb.append(StringPool.SPACE);
711 sb.append(contentDispositionFileName);
712
713 if (_log.isDebugEnabled()) {
714 _log.debug("Setting content disposition header " + sb.toString());
715 }
716
717 response.setHeader(HttpHeaders.CONTENT_DISPOSITION, sb.toString());
718 }
719
720 protected static void setHeaders(
721 HttpServletRequest request, HttpServletResponse response,
722 String fileName, String contentType, String contentDispositionType,
723 Range range) {
724
725 setHeaders(
726 request, response, fileName, contentType, contentDispositionType);
727
728 if (range != null) {
729 response.setHeader(
730 HttpHeaders.CONTENT_RANGE, range.getContentRange());
731
732 response.setHeader(
733 HttpHeaders.CONTENT_LENGTH, String.valueOf(range.getLength()));
734 }
735 }
736
737 private static final String _CLIENT_ABORT_EXCEPTION =
738 "org.apache.catalina.connector.ClientAbortException";
739
740 private static final int _MAX_RANGE_FIELDS = GetterUtil.getInteger(
741 PropsUtil.get(PropsKeys.WEB_SERVER_SERVLET_MAX_RANGE_FIELDS));
742
743 private static final String _RANGE_REGEX =
744 "^bytes=\\d*-\\d*(,\\s?\\d*-\\d*)*$";
745
746 private static Log _log = LogFactoryUtil.getLog(ServletResponseUtil.class);
747
748 }