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 boolean isClientAbortException(IOException ioe) {
127 Class<?> clazz = ioe.getClass();
128
129 String className = clazz.getName();
130
131 if (className.equals(_CLIENT_ABORT_EXCEPTION)) {
132 return true;
133 }
134 else {
135 return false;
136 }
137 }
138
139 public static void sendFile(
140 HttpServletRequest request, HttpServletResponse response,
141 String fileName, byte[] bytes)
142 throws IOException {
143
144 sendFile(request, response, fileName, bytes, null);
145 }
146
147 public static void sendFile(
148 HttpServletRequest request, HttpServletResponse response,
149 String fileName, byte[] bytes, String contentType)
150 throws IOException {
151
152 sendFile(request, response, fileName, bytes, contentType, null);
153 }
154
155 public static void sendFile(
156 HttpServletRequest request, HttpServletResponse response,
157 String fileName, byte[] bytes, String contentType,
158 String contentDispositionType)
159 throws IOException {
160
161 setHeaders(
162 request, response, fileName, contentType, contentDispositionType);
163
164 write(response, bytes);
165 }
166
167 public static void sendFile(
168 HttpServletRequest request, HttpServletResponse response,
169 String fileName, InputStream is)
170 throws IOException {
171
172 sendFile(request, response, fileName, is, null);
173 }
174
175 public static void sendFile(
176 HttpServletRequest request, HttpServletResponse response,
177 String fileName, InputStream is, long contentLength,
178 String contentType)
179 throws IOException {
180
181 sendFile(request, response, fileName, is, 0, contentType, null);
182 }
183
184 public static void sendFile(
185 HttpServletRequest request, HttpServletResponse response,
186 String fileName, InputStream is, long contentLength,
187 String contentType, String contentDispositionType)
188 throws IOException {
189
190 setHeaders(
191 request, response, fileName, contentType, contentDispositionType);
192
193 write(response, is, contentLength);
194 }
195
196 public static void sendFile(
197 HttpServletRequest request, HttpServletResponse response,
198 String fileName, InputStream is, String contentType)
199 throws IOException {
200
201 sendFile(request, response, fileName, is, 0, contentType);
202 }
203
204
207 public static void sendFile(
208 HttpServletResponse response, String fileName, byte[] bytes)
209 throws IOException {
210
211 sendFile(null, response, fileName, bytes);
212 }
213
214
217 public static void sendFile(
218 HttpServletResponse response, String fileName, byte[] bytes,
219 String contentType)
220 throws IOException {
221
222 sendFile(null, response, fileName, bytes, contentType);
223 }
224
225
228 public static void sendFile(
229 HttpServletResponse response, String fileName, InputStream is)
230 throws IOException {
231
232 sendFile(null, response, fileName, is);
233 }
234
235
238 public static void sendFile(
239 HttpServletResponse response, String fileName, InputStream is,
240 int contentLength, String contentType)
241 throws IOException {
242
243 sendFile(null, response, fileName, is, contentLength, contentType);
244 }
245
246
249 public static void sendFile(
250 HttpServletResponse response, String fileName, InputStream is,
251 String contentType)
252 throws IOException {
253
254 sendFile(null, response, fileName, is, contentType);
255 }
256
257 public static void write(
258 HttpServletRequest request, HttpServletResponse response,
259 String fileName, List<Range> ranges, InputStream inputStream,
260 long fullLength, String contentType)
261 throws IOException {
262
263 OutputStream outputStream = null;
264
265 try {
266 outputStream = response.getOutputStream();
267
268 Range fullRange = new Range(0, fullLength - 1, fullLength);
269
270 Range firstRange = null;
271
272 if (!ranges.isEmpty()) {
273 firstRange = ranges.get(0);
274 }
275
276 if ((firstRange == null) || firstRange.equals(fullRange)) {
277 if (_log.isDebugEnabled()) {
278 _log.debug("Writing full range");
279 }
280
281 response.setContentType(contentType);
282
283 setHeaders(
284 request, response, fileName, contentType, null, fullRange);
285
286 copyRange(
287 inputStream, outputStream, fullRange.getStart(),
288 fullRange.getLength());
289 }
290 else if (ranges.size() == 1) {
291 if (_log.isDebugEnabled()) {
292 _log.debug("Attempting to write a single range");
293 }
294
295 Range range = ranges.get(0);
296
297 response.setContentType(contentType);
298
299 setHeaders(
300 request, response, fileName, contentType, null, range);
301
302 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
303
304 copyRange(
305 inputStream, outputStream, range.getStart(),
306 range.getLength());
307 }
308 else if (ranges.size() > 1 ) {
309 if (_log.isDebugEnabled()) {
310 _log.debug("Attempting to write multiple ranges");
311 }
312
313 ServletOutputStream servletOutputStream =
314 (ServletOutputStream)outputStream;
315
316 String boundary =
317 "liferay-multipart-boundary-" + System.currentTimeMillis();
318
319 String multipartContentType =
320 "multipart/byteranges; boundary=" + boundary;
321
322 response.setContentType(multipartContentType);
323
324 setHeaders(
325 request, response, fileName, multipartContentType, null);
326
327 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
328
329 for (int i = 0; i < ranges.size(); i++) {
330 Range range = ranges.get(i);
331
332 servletOutputStream.println();
333 servletOutputStream.println(
334 StringPool.DOUBLE_DASH + boundary);
335 servletOutputStream.println(
336 HttpHeaders.CONTENT_TYPE + ": " + contentType);
337 servletOutputStream.println(
338 HttpHeaders.CONTENT_RANGE + ": " +
339 range.getContentRange());
340 servletOutputStream.println();
341
342 copyRange(
343 inputStream, outputStream, range.getStart(),
344 range.getLength());
345 }
346
347 servletOutputStream.println();
348 servletOutputStream.println(
349 StringPool.DOUBLE_DASH + boundary + StringPool.DOUBLE_DASH);
350 }
351 }
352 finally {
353 try {
354 inputStream.close();
355 }
356 catch (IOException ioe) {
357 }
358 }
359 }
360
361 public static void write(
362 HttpServletResponse response,
363 BufferCacheServletResponse bufferCacheServletResponse)
364 throws IOException {
365
366 if (bufferCacheServletResponse.isByteMode()) {
367 write(response, bufferCacheServletResponse.getByteBuffer());
368 }
369 else if (bufferCacheServletResponse.isCharMode()) {
370 write(response, bufferCacheServletResponse.getCharBuffer());
371 }
372 }
373
374 public static void write(HttpServletResponse response, byte[] bytes)
375 throws IOException {
376
377 write(response, bytes, 0, 0);
378 }
379
380 public static void write(
381 HttpServletResponse response, byte[] bytes, int offset,
382 int contentLength)
383 throws IOException {
384
385 try {
386
387
388
389 if (!response.isCommitted()) {
390
391
392
393 if (contentLength == 0) {
394 contentLength = bytes.length;
395 }
396
397 response.setContentLength(contentLength);
398
399 response.flushBuffer();
400
401 if (response instanceof BufferCacheServletResponse) {
402 BufferCacheServletResponse bufferCacheServletResponse =
403 (BufferCacheServletResponse)response;
404
405 bufferCacheServletResponse.setByteBuffer(
406 ByteBuffer.wrap(bytes, offset, contentLength));
407 }
408 else {
409 ServletOutputStream servletOutputStream =
410 response.getOutputStream();
411
412 if ((contentLength == 0) && ServerDetector.isJetty()) {
413 }
414 else {
415 servletOutputStream.write(bytes, offset, contentLength);
416 }
417 }
418 }
419 }
420 catch (IOException ioe) {
421 if ((ioe instanceof SocketException) ||
422 isClientAbortException(ioe)) {
423
424 if (_log.isWarnEnabled()) {
425 _log.warn(ioe);
426 }
427 }
428 else {
429 throw ioe;
430 }
431 }
432 }
433
434 public static void write(HttpServletResponse response, byte[][] bytesArray)
435 throws IOException {
436
437 try {
438
439
440
441 if (!response.isCommitted()) {
442 int contentLength = 0;
443
444 for (byte[] bytes : bytesArray) {
445 contentLength += bytes.length;
446 }
447
448 response.setContentLength(contentLength);
449
450 response.flushBuffer();
451
452 ServletOutputStream servletOutputStream =
453 response.getOutputStream();
454
455 for (byte[] bytes : bytesArray) {
456 servletOutputStream.write(bytes);
457 }
458 }
459 }
460 catch (IOException ioe) {
461 if ((ioe instanceof SocketException) ||
462 isClientAbortException(ioe)) {
463
464 if (_log.isWarnEnabled()) {
465 _log.warn(ioe);
466 }
467 }
468 else {
469 throw ioe;
470 }
471 }
472 }
473
474 public static void write(
475 HttpServletResponse response, ByteBuffer byteBuffer)
476 throws IOException {
477
478 if (response instanceof BufferCacheServletResponse) {
479 BufferCacheServletResponse bufferCacheServletResponse =
480 (BufferCacheServletResponse)response;
481
482 bufferCacheServletResponse.setByteBuffer(byteBuffer);
483 }
484 else {
485 write(
486 response, byteBuffer.array(), byteBuffer.position(),
487 byteBuffer.limit());
488 }
489 }
490
491 public static void write(
492 HttpServletResponse response, CharBuffer charBuffer)
493 throws IOException {
494
495 if (response instanceof BufferCacheServletResponse) {
496 BufferCacheServletResponse bufferCacheServletResponse =
497 (BufferCacheServletResponse)response;
498
499 bufferCacheServletResponse.setCharBuffer(charBuffer);
500 }
501 else {
502 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
503 StringPool.UTF8, charBuffer);
504
505 write(response, byteBuffer);
506 }
507 }
508
509 public static void write(HttpServletResponse response, File file)
510 throws IOException {
511
512 if (response instanceof BufferCacheServletResponse) {
513 BufferCacheServletResponse bufferCacheServletResponse =
514 (BufferCacheServletResponse)response;
515
516 ByteBuffer byteBuffer = ByteBuffer.wrap(FileUtil.getBytes(file));
517
518 bufferCacheServletResponse.setByteBuffer(byteBuffer);
519 }
520 else {
521 FileInputStream fileInputStream = new FileInputStream(file);
522
523 FileChannel fileChannel = fileInputStream.getChannel();
524
525 try {
526 int contentLength = (int)fileChannel.size();
527
528 response.setContentLength(contentLength);
529
530 response.flushBuffer();
531
532 fileChannel.transferTo(
533 0, contentLength,
534 Channels.newChannel(response.getOutputStream()));
535 }
536 finally {
537 fileChannel.close();
538 }
539 }
540 }
541
542 public static void write(HttpServletResponse response, InputStream is)
543 throws IOException {
544
545 write(response, is, 0);
546 }
547
548 public static void write(
549 HttpServletResponse response, InputStream is, long contentLength)
550 throws IOException {
551
552 if (response.isCommitted()) {
553 return;
554 }
555
556 if (contentLength > 0) {
557 response.setHeader(
558 HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
559 }
560
561 response.flushBuffer();
562
563 StreamUtil.transfer(is, response.getOutputStream());
564 }
565
566 public static void write(HttpServletResponse response, String s)
567 throws IOException {
568
569 if (response instanceof BufferCacheServletResponse) {
570 BufferCacheServletResponse bufferCacheServletResponse =
571 (BufferCacheServletResponse)response;
572
573 bufferCacheServletResponse.setString(s);
574 }
575 else {
576 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
577 StringPool.UTF8, s);
578
579 write(response, byteBuffer);
580 }
581 }
582
583 protected static void copyRange(
584 InputStream inputStream, OutputStream outputStream, long start,
585 long length)
586 throws IOException {
587
588 if (inputStream instanceof FileInputStream) {
589 FileInputStream fileInputStream = (FileInputStream)inputStream;
590
591 FileChannel fileChannel = fileInputStream.getChannel();
592
593 fileChannel.transferTo(
594 start, length, Channels.newChannel(outputStream));
595 }
596 else if (inputStream instanceof ByteArrayInputStream) {
597 ByteArrayInputStream byteArrayInputStream =
598 (ByteArrayInputStream)inputStream;
599
600 byteArrayInputStream.skip(start);
601
602 StreamUtil.transfer(byteArrayInputStream, outputStream, length);
603 }
604 else {
605 RandomAccessInputStream randomAccessInputStream =
606 new RandomAccessInputStream(inputStream);
607
608 randomAccessInputStream.seek(start);
609
610 StreamUtil.transfer(randomAccessInputStream, outputStream, length);
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 }