001
014
015 package com.liferay.portal.kernel.servlet;
016
017 import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
018 import com.liferay.portal.kernel.log.Log;
019 import com.liferay.portal.kernel.log.LogFactoryUtil;
020 import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil;
021 import com.liferay.portal.kernel.util.ArrayUtil;
022 import com.liferay.portal.kernel.util.FileUtil;
023 import com.liferay.portal.kernel.util.GetterUtil;
024 import com.liferay.portal.kernel.util.HttpUtil;
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.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(HttpServletResponse response, byte[] bytes)
349 throws IOException {
350
351 write(response, bytes, 0, 0);
352 }
353
354 public static void write(
355 HttpServletResponse response, byte[] bytes, int offset,
356 int contentLength)
357 throws IOException {
358
359 try {
360
361
362
363 if (!response.isCommitted()) {
364
365
366
367 if (contentLength == 0) {
368 contentLength = bytes.length;
369 }
370
371 response.setContentLength(contentLength);
372
373 response.flushBuffer();
374
375 if (response instanceof ByteBufferServletResponse) {
376 ByteBufferServletResponse byteBufferResponse =
377 (ByteBufferServletResponse)response;
378
379 byteBufferResponse.setByteBuffer(
380 ByteBuffer.wrap(bytes, offset, contentLength));
381 }
382 else {
383 ServletOutputStream servletOutputStream =
384 response.getOutputStream();
385
386 if ((contentLength == 0) && ServerDetector.isJetty()) {
387 }
388 else {
389 servletOutputStream.write(bytes, offset, contentLength);
390 }
391 }
392 }
393 }
394 catch (IOException ioe) {
395 if ((ioe instanceof SocketException) ||
396 isClientAbortException(ioe)) {
397
398 if (_log.isWarnEnabled()) {
399 _log.warn(ioe);
400 }
401 }
402 else {
403 throw ioe;
404 }
405 }
406 }
407
408 public static void write(HttpServletResponse response, byte[][] bytesArray)
409 throws IOException {
410
411 try {
412
413
414
415 if (!response.isCommitted()) {
416 int contentLength = 0;
417
418 for (byte[] bytes : bytesArray) {
419 contentLength += bytes.length;
420 }
421
422 response.setContentLength(contentLength);
423
424 response.flushBuffer();
425
426 ServletOutputStream servletOutputStream =
427 response.getOutputStream();
428
429 for (byte[] bytes : bytesArray) {
430 servletOutputStream.write(bytes);
431 }
432 }
433 }
434 catch (IOException ioe) {
435 if ((ioe instanceof SocketException) ||
436 isClientAbortException(ioe)) {
437
438 if (_log.isWarnEnabled()) {
439 _log.warn(ioe);
440 }
441 }
442 else {
443 throw ioe;
444 }
445 }
446 }
447
448 public static void write(
449 HttpServletResponse response, ByteBuffer byteBuffer)
450 throws IOException {
451
452 if (response instanceof ByteBufferServletResponse) {
453 ByteBufferServletResponse byteBufferResponse =
454 (ByteBufferServletResponse)response;
455
456 byteBufferResponse.setByteBuffer(byteBuffer);
457 }
458 else {
459 write(
460 response, byteBuffer.array(), byteBuffer.position(),
461 byteBuffer.limit());
462 }
463 }
464
465 public static void write(HttpServletResponse response, File file)
466 throws IOException {
467
468 if (response instanceof ByteBufferServletResponse) {
469 ByteBufferServletResponse byteBufferResponse =
470 (ByteBufferServletResponse)response;
471
472 ByteBuffer byteBuffer = ByteBuffer.wrap(FileUtil.getBytes(file));
473
474 byteBufferResponse.setByteBuffer(byteBuffer);
475 }
476 else if (response instanceof StringServletResponse) {
477 StringServletResponse stringResponse =
478 (StringServletResponse)response;
479
480 String s = FileUtil.read(file);
481
482 stringResponse.setString(s);
483 }
484 else {
485 FileInputStream fileInputStream = new FileInputStream(file);
486
487 FileChannel fileChannel = fileInputStream.getChannel();
488
489 try {
490 int contentLength = (int)fileChannel.size();
491
492 response.setContentLength(contentLength);
493
494 response.flushBuffer();
495
496 fileChannel.transferTo(
497 0, contentLength,
498 Channels.newChannel(response.getOutputStream()));
499 }
500 finally {
501 fileChannel.close();
502 }
503 }
504 }
505
506 public static void write(HttpServletResponse response, InputStream is)
507 throws IOException {
508
509 write(response, is, 0);
510 }
511
512 public static void write(
513 HttpServletResponse response, InputStream is, long contentLength)
514 throws IOException {
515
516 if (response.isCommitted()) {
517 return;
518 }
519
520 if (contentLength > 0) {
521 response.setHeader(
522 HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
523 }
524
525 response.flushBuffer();
526
527 StreamUtil.transfer(is, response.getOutputStream());
528 }
529
530 public static void write(HttpServletResponse response, String s)
531 throws IOException {
532
533 if (response instanceof StringServletResponse) {
534 StringServletResponse stringResponse =
535 (StringServletResponse)response;
536
537 stringResponse.setString(s);
538 }
539 else {
540 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
541 StringPool.UTF8, s);
542
543 write(response, byteBuffer);
544 }
545 }
546
547 public static void write(
548 HttpServletResponse response, StringServletResponse stringResponse)
549 throws IOException {
550
551 if (stringResponse.isCalledGetOutputStream()) {
552 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
553 stringResponse.getUnsyncByteArrayOutputStream();
554
555 ByteBuffer byteBuffer =
556 unsyncByteArrayOutputStream.unsafeGetByteBuffer();
557
558 write(response, byteBuffer);
559 }
560 else {
561 write(response, stringResponse.getString());
562 }
563 }
564
565 protected static void copyRange(
566 InputStream inputStream, OutputStream outputStream, long start,
567 long length)
568 throws IOException {
569
570 if (inputStream instanceof FileInputStream) {
571 FileInputStream fileInputStream = (FileInputStream)inputStream;
572
573 FileChannel fileChannel = fileInputStream.getChannel();
574
575 fileChannel.transferTo(
576 start, length, Channels.newChannel(outputStream));
577 }
578 else if (inputStream instanceof ByteArrayInputStream) {
579 ByteArrayInputStream byteArrayInputStream =
580 (ByteArrayInputStream)inputStream;
581
582 byteArrayInputStream.skip(start);
583
584 StreamUtil.transfer(byteArrayInputStream, outputStream, length);
585 }
586 else {
587 RandomAccessInputStream randomAccessInputStream =
588 new RandomAccessInputStream(inputStream);
589
590 randomAccessInputStream.seek(start);
591
592 StreamUtil.transfer(randomAccessInputStream, outputStream, length);
593 }
594 }
595
596 protected static boolean isClientAbortException(IOException ioe) {
597 Class<?> clazz = ioe.getClass();
598
599 String className = clazz.getName();
600
601 if (className.equals(_CLIENT_ABORT_EXCEPTION)) {
602 return true;
603 }
604 else {
605 return false;
606 }
607 }
608
609 protected static void setHeaders(
610 HttpServletRequest request, HttpServletResponse response,
611 String fileName, String contentType, String contentDispositionType) {
612
613 if (_log.isDebugEnabled()) {
614 _log.debug("Sending file of type " + contentType);
615 }
616
617
618
619 if (Validator.isNotNull(contentType)) {
620 response.setContentType(contentType);
621 }
622
623 if (!response.containsHeader(HttpHeaders.CACHE_CONTROL)) {
624 response.setHeader(
625 HttpHeaders.CACHE_CONTROL,
626 HttpHeaders.CACHE_CONTROL_PRIVATE_VALUE);
627 }
628
629 if (Validator.isNull(fileName)) {
630 return;
631 }
632
633 String contentDispositionFileName = "filename=\"" + fileName + "\"";
634
635
636
637
638 boolean ascii = true;
639
640 for (int i = 0; i < fileName.length(); i++) {
641 if (!Validator.isAscii(fileName.charAt(i))) {
642 ascii = false;
643
644 break;
645 }
646 }
647
648 if (!ascii) {
649 String encodedFileName = HttpUtil.encodeURL(fileName, true);
650
651 if (BrowserSnifferUtil.isIe(request)) {
652 contentDispositionFileName =
653 "filename=\"" + encodedFileName + "\"";
654 }
655 else {
656 contentDispositionFileName =
657 "filename*=UTF-8''" + encodedFileName;
658 }
659 }
660
661 if (Validator.isNull(contentDispositionType)) {
662 String extension = GetterUtil.getString(
663 FileUtil.getExtension(fileName)).toLowerCase();
664
665 String[] mimeTypesContentDispositionInline = null;
666
667 try {
668 mimeTypesContentDispositionInline = PropsUtil.getArray(
669 PropsKeys.MIME_TYPES_CONTENT_DISPOSITION_INLINE);
670 }
671 catch (Exception e) {
672 mimeTypesContentDispositionInline = new String[0];
673 }
674
675 if (ArrayUtil.contains(
676 mimeTypesContentDispositionInline, extension)) {
677
678 contentDispositionType = HttpHeaders.CONTENT_DISPOSITION_INLINE;
679 }
680 else {
681 contentDispositionType =
682 HttpHeaders.CONTENT_DISPOSITION_ATTACHMENT;
683 }
684 }
685
686 StringBundler sb = new StringBundler(4);
687
688 sb.append(contentDispositionType);
689 sb.append(StringPool.SEMICOLON);
690 sb.append(StringPool.SPACE);
691 sb.append(contentDispositionFileName);
692
693 if (_log.isDebugEnabled()) {
694 _log.debug("Setting content disposition header " + sb.toString());
695 }
696
697 response.setHeader(HttpHeaders.CONTENT_DISPOSITION, sb.toString());
698 }
699
700 protected static void setHeaders(
701 HttpServletRequest request, HttpServletResponse response,
702 String fileName, String contentType, String contentDispositionType,
703 Range range) {
704
705 setHeaders(
706 request, response, fileName, contentType, contentDispositionType);
707
708 if (range != null) {
709 response.setHeader(
710 HttpHeaders.CONTENT_RANGE, range.getContentRange());
711
712 response.setHeader(
713 HttpHeaders.CONTENT_LENGTH, String.valueOf(range.getLength()));
714 }
715 }
716
717 private static final String _CLIENT_ABORT_EXCEPTION =
718 "org.apache.catalina.connector.ClientAbortException";
719
720 private static final int _MAX_RANGE_FIELDS = GetterUtil.getInteger(
721 PropsUtil.get(PropsKeys.WEB_SERVER_SERVLET_MAX_RANGE_FIELDS));
722
723 private static final String _RANGE_REGEX =
724 "^bytes=\\d*-\\d*(,\\s?\\d*-\\d*)*$";
725
726 private static Log _log = LogFactoryUtil.getLog(ServletResponseUtil.class);
727
728 }