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