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