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