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.setContentLength((int)contentLength);
567 }
568
569 response.flushBuffer();
570
571 StreamUtil.transfer(inputStream, response.getOutputStream());
572 }
573
574 public static void write(HttpServletResponse response, String s)
575 throws IOException {
576
577 if (response instanceof BufferCacheServletResponse) {
578 BufferCacheServletResponse bufferCacheServletResponse =
579 (BufferCacheServletResponse)response;
580
581 bufferCacheServletResponse.setString(s);
582 }
583 else {
584 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
585 StringPool.UTF8, s);
586
587 write(response, byteBuffer);
588 }
589 }
590
591 protected static void copyRange(
592 InputStream inputStream, OutputStream outputStream, long start,
593 long length)
594 throws IOException {
595
596 if (inputStream instanceof FileInputStream) {
597 FileInputStream fileInputStream = (FileInputStream)inputStream;
598
599 FileChannel fileChannel = fileInputStream.getChannel();
600
601 fileChannel.transferTo(
602 start, length, Channels.newChannel(outputStream));
603 }
604 else if (inputStream instanceof ByteArrayInputStream) {
605 ByteArrayInputStream byteArrayInputStream =
606 (ByteArrayInputStream)inputStream;
607
608 byteArrayInputStream.skip(start);
609
610 StreamUtil.transfer(byteArrayInputStream, outputStream, length);
611 }
612 else {
613 RandomAccessInputStream randomAccessInputStream =
614 new RandomAccessInputStream(inputStream);
615
616 randomAccessInputStream.seek(start);
617
618 StreamUtil.transfer(randomAccessInputStream, outputStream, length);
619 }
620 }
621
622 protected static void setHeaders(
623 HttpServletRequest request, HttpServletResponse response,
624 String fileName, String contentType, String contentDispositionType) {
625
626 if (_log.isDebugEnabled()) {
627 _log.debug("Sending file of type " + contentType);
628 }
629
630
631
632 if (Validator.isNotNull(contentType)) {
633 response.setContentType(contentType);
634 }
635
636 if (!response.containsHeader(HttpHeaders.CACHE_CONTROL)) {
637 response.setHeader(
638 HttpHeaders.CACHE_CONTROL,
639 HttpHeaders.CACHE_CONTROL_PRIVATE_VALUE);
640 }
641
642 if (Validator.isNull(fileName)) {
643 return;
644 }
645
646 String contentDispositionFileName = "filename=\"" + fileName + "\"";
647
648
649
650
651 boolean ascii = true;
652
653 for (int i = 0; i < fileName.length(); i++) {
654 if (!Validator.isAscii(fileName.charAt(i))) {
655 ascii = false;
656
657 break;
658 }
659 }
660
661 if (!ascii) {
662 String encodedFileName = HttpUtil.encodeURL(fileName, true);
663
664 if (BrowserSnifferUtil.isIe(request)) {
665 contentDispositionFileName =
666 "filename=\"" + encodedFileName + "\"";
667 }
668 else {
669 contentDispositionFileName =
670 "filename*=UTF-8''" + encodedFileName;
671 }
672 }
673
674 if (Validator.isNull(contentDispositionType)) {
675 String extension = GetterUtil.getString(
676 FileUtil.getExtension(fileName));
677
678 extension = StringUtil.toLowerCase(extension);
679
680 String[] mimeTypesContentDispositionInline = null;
681
682 try {
683 mimeTypesContentDispositionInline = PropsUtil.getArray(
684 PropsKeys.MIME_TYPES_CONTENT_DISPOSITION_INLINE);
685 }
686 catch (Exception e) {
687 mimeTypesContentDispositionInline = new String[0];
688 }
689
690 if (ArrayUtil.contains(
691 mimeTypesContentDispositionInline, extension)) {
692
693 contentDispositionType = HttpHeaders.CONTENT_DISPOSITION_INLINE;
694
695 contentType = MimeTypesUtil.getContentType(fileName);
696
697 response.setContentType(contentType);
698 }
699 else {
700 contentDispositionType =
701 HttpHeaders.CONTENT_DISPOSITION_ATTACHMENT;
702 }
703 }
704
705 StringBundler sb = new StringBundler(4);
706
707 sb.append(contentDispositionType);
708 sb.append(StringPool.SEMICOLON);
709 sb.append(StringPool.SPACE);
710 sb.append(contentDispositionFileName);
711
712 if (_log.isDebugEnabled()) {
713 _log.debug("Setting content disposition header " + sb.toString());
714 }
715
716 response.setHeader(HttpHeaders.CONTENT_DISPOSITION, sb.toString());
717 }
718
719 protected static void setHeaders(
720 HttpServletRequest request, HttpServletResponse response,
721 String fileName, String contentType, String contentDispositionType,
722 Range range) {
723
724 setHeaders(
725 request, response, fileName, contentType, contentDispositionType);
726
727 if (range != null) {
728 response.setHeader(
729 HttpHeaders.CONTENT_RANGE, range.getContentRange());
730
731 response.setHeader(
732 HttpHeaders.CONTENT_LENGTH, String.valueOf(range.getLength()));
733 }
734 }
735
736 private static final String _CLIENT_ABORT_EXCEPTION =
737 "org.apache.catalina.connector.ClientAbortException";
738
739 private static final int _MAX_RANGE_FIELDS = GetterUtil.getInteger(
740 PropsUtil.get(PropsKeys.WEB_SERVER_SERVLET_MAX_RANGE_FIELDS));
741
742 private static final String _RANGE_REGEX =
743 "^bytes=\\d*-\\d*(,\\s?\\d*-\\d*)*$";
744
745 private static Log _log = LogFactoryUtil.getLog(ServletResponseUtil.class);
746
747 }