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 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(
327 HttpServletResponse response,
328 BufferCacheServletResponse bufferCacheServletResponse)
329 throws IOException {
330
331 if (bufferCacheServletResponse.isByteMode()) {
332 write(response, bufferCacheServletResponse.getByteBuffer());
333 }
334 else if (bufferCacheServletResponse.isCharMode()) {
335 write(response, bufferCacheServletResponse.getCharBuffer());
336 }
337 }
338
339 public static void write(HttpServletResponse response, byte[] bytes)
340 throws IOException {
341
342 write(response, bytes, 0, 0);
343 }
344
345 public static void write(
346 HttpServletResponse response, byte[] bytes, int offset,
347 int contentLength)
348 throws IOException {
349
350 try {
351
352
353
354 if (!response.isCommitted()) {
355
356
357
358 if (contentLength == 0) {
359 contentLength = bytes.length;
360 }
361
362 response.setContentLength(contentLength);
363
364 response.flushBuffer();
365
366 if (response instanceof BufferCacheServletResponse) {
367 BufferCacheServletResponse bufferCacheServletResponse =
368 (BufferCacheServletResponse)response;
369
370 bufferCacheServletResponse.setByteBuffer(
371 ByteBuffer.wrap(bytes, offset, contentLength));
372 }
373 else {
374 ServletOutputStream servletOutputStream =
375 response.getOutputStream();
376
377 if ((contentLength == 0) && ServerDetector.isJetty()) {
378 }
379 else {
380 servletOutputStream.write(bytes, offset, contentLength);
381 }
382 }
383 }
384 }
385 catch (IOException ioe) {
386 if ((ioe instanceof SocketException) ||
387 isClientAbortException(ioe)) {
388
389 if (_log.isWarnEnabled()) {
390 _log.warn(ioe);
391 }
392 }
393 else {
394 throw ioe;
395 }
396 }
397 }
398
399 public static void write(HttpServletResponse response, byte[][] bytesArray)
400 throws IOException {
401
402 try {
403
404
405
406 if (!response.isCommitted()) {
407 int contentLength = 0;
408
409 for (byte[] bytes : bytesArray) {
410 contentLength += bytes.length;
411 }
412
413 response.setContentLength(contentLength);
414
415 response.flushBuffer();
416
417 ServletOutputStream servletOutputStream =
418 response.getOutputStream();
419
420 for (byte[] bytes : bytesArray) {
421 servletOutputStream.write(bytes);
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(
440 HttpServletResponse response, ByteBuffer byteBuffer)
441 throws IOException {
442
443 if (response instanceof BufferCacheServletResponse) {
444 BufferCacheServletResponse bufferCacheServletResponse =
445 (BufferCacheServletResponse)response;
446
447 bufferCacheServletResponse.setByteBuffer(byteBuffer);
448 }
449 else {
450 write(
451 response, byteBuffer.array(), byteBuffer.position(),
452 byteBuffer.limit());
453 }
454 }
455
456 public static void write(
457 HttpServletResponse response, CharBuffer charBuffer)
458 throws IOException {
459
460 if (response instanceof BufferCacheServletResponse) {
461 BufferCacheServletResponse bufferCacheServletResponse =
462 (BufferCacheServletResponse)response;
463
464 bufferCacheServletResponse.setCharBuffer(charBuffer);
465 }
466 else {
467 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
468 StringPool.UTF8, charBuffer);
469
470 write(response, byteBuffer);
471 }
472 }
473
474 public static void write(HttpServletResponse response, File file)
475 throws IOException {
476
477 if (response instanceof BufferCacheServletResponse) {
478 BufferCacheServletResponse bufferCacheServletResponse =
479 (BufferCacheServletResponse)response;
480
481 ByteBuffer byteBuffer = ByteBuffer.wrap(FileUtil.getBytes(file));
482
483 bufferCacheServletResponse.setByteBuffer(byteBuffer);
484 }
485 else {
486 FileInputStream fileInputStream = new FileInputStream(file);
487
488 FileChannel fileChannel = fileInputStream.getChannel();
489
490 try {
491 int contentLength = (int)fileChannel.size();
492
493 response.setContentLength(contentLength);
494
495 response.flushBuffer();
496
497 fileChannel.transferTo(
498 0, contentLength,
499 Channels.newChannel(response.getOutputStream()));
500 }
501 finally {
502 fileChannel.close();
503 }
504 }
505 }
506
507 public static void write(HttpServletResponse response, InputStream is)
508 throws IOException {
509
510 write(response, is, 0);
511 }
512
513 public static void write(
514 HttpServletResponse response, InputStream is, long contentLength)
515 throws IOException {
516
517 if (response.isCommitted()) {
518 return;
519 }
520
521 if (contentLength > 0) {
522 response.setHeader(
523 HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
524 }
525
526 response.flushBuffer();
527
528 StreamUtil.transfer(is, response.getOutputStream());
529 }
530
531 public static void write(HttpServletResponse response, String s)
532 throws IOException {
533
534 if (response instanceof BufferCacheServletResponse) {
535 BufferCacheServletResponse bufferCacheServletResponse =
536 (BufferCacheServletResponse)response;
537
538 bufferCacheServletResponse.setString(s);
539 }
540 else {
541 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
542 StringPool.UTF8, s);
543
544 write(response, byteBuffer);
545 }
546 }
547
548 protected static void copyRange(
549 InputStream inputStream, OutputStream outputStream, long start,
550 long length)
551 throws IOException {
552
553 if (inputStream instanceof FileInputStream) {
554 FileInputStream fileInputStream = (FileInputStream)inputStream;
555
556 FileChannel fileChannel = fileInputStream.getChannel();
557
558 fileChannel.transferTo(
559 start, length, Channels.newChannel(outputStream));
560 }
561 else if (inputStream instanceof ByteArrayInputStream) {
562 ByteArrayInputStream byteArrayInputStream =
563 (ByteArrayInputStream)inputStream;
564
565 byteArrayInputStream.skip(start);
566
567 StreamUtil.transfer(byteArrayInputStream, outputStream, length);
568 }
569 else {
570 RandomAccessInputStream randomAccessInputStream =
571 new RandomAccessInputStream(inputStream);
572
573 randomAccessInputStream.seek(start);
574
575 StreamUtil.transfer(randomAccessInputStream, outputStream, length);
576 }
577 }
578
579 protected static boolean isClientAbortException(IOException ioe) {
580 Class<?> clazz = ioe.getClass();
581
582 String className = clazz.getName();
583
584 if (className.equals(_CLIENT_ABORT_EXCEPTION)) {
585 return true;
586 }
587 else {
588 return false;
589 }
590 }
591
592 protected static void setHeaders(
593 HttpServletRequest request, HttpServletResponse response,
594 String fileName, String contentType) {
595
596 if (_log.isDebugEnabled()) {
597 _log.debug("Sending file of type " + contentType);
598 }
599
600
601
602 if (Validator.isNotNull(contentType)) {
603 response.setContentType(contentType);
604 }
605
606 response.setHeader(
607 HttpHeaders.CACHE_CONTROL, HttpHeaders.CACHE_CONTROL_PRIVATE_VALUE);
608
609 if (Validator.isNotNull(fileName)) {
610 String contentDisposition =
611 "attachment; filename=\"" + fileName + "\"";
612
613
614
615
616 boolean ascii = true;
617
618 for (int i = 0; i < fileName.length(); i++) {
619 if (!Validator.isAscii(fileName.charAt(i))) {
620 ascii = false;
621
622 break;
623 }
624 }
625
626 if (!ascii) {
627 String encodedFileName = HttpUtil.encodeURL(fileName, true);
628
629 if (BrowserSnifferUtil.isIe(request)) {
630 contentDisposition =
631 "attachment; filename=\"" + encodedFileName + "\"";
632 }
633 else {
634 contentDisposition =
635 "attachment; filename*=UTF-8''" + encodedFileName;
636 }
637 }
638
639 String extension = GetterUtil.getString(
640 FileUtil.getExtension(fileName)).toLowerCase();
641
642 String[] mimeTypesContentDispositionInline = null;
643
644 try {
645 mimeTypesContentDispositionInline = PropsUtil.getArray(
646 PropsKeys.MIME_TYPES_CONTENT_DISPOSITION_INLINE);
647 }
648 catch (Exception e) {
649 mimeTypesContentDispositionInline = new String[0];
650 }
651
652 if (ArrayUtil.contains(
653 mimeTypesContentDispositionInline, extension)) {
654
655 contentDisposition = StringUtil.replace(
656 contentDisposition, "attachment; ", "inline; ");
657 }
658
659 if (_log.isDebugEnabled()) {
660 _log.debug(
661 "Setting content disposition header " + contentDisposition);
662 }
663
664 response.setHeader(
665 HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
666 }
667 }
668
669 protected static void setHeaders(
670 HttpServletRequest request, HttpServletResponse response,
671 String fileName, String contentType, Range range) {
672
673 setHeaders(request, response, fileName, contentType);
674
675 if (range != null) {
676 response.setHeader(
677 HttpHeaders.CONTENT_RANGE, range.getContentRange());
678
679 response.setHeader(
680 HttpHeaders.CONTENT_LENGTH, String.valueOf(range.getLength()));
681 }
682 }
683
684 private static final String _CLIENT_ABORT_EXCEPTION =
685 "org.apache.catalina.connector.ClientAbortException";
686
687 private static final int _MAX_RANGE_FIELDS = GetterUtil.getInteger(
688 PropsUtil.get(PropsKeys.WEB_SERVER_SERVLET_MAX_RANGE_FIELDS));
689
690 private static final String _RANGE_REGEX =
691 "^bytes=\\d*-\\d*(,\\s?\\d*-\\d*)*$";
692
693 private static Log _log = LogFactoryUtil.getLog(ServletResponseUtil.class);
694
695 }