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