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.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.channels.Channels;
045 import java.nio.channels.FileChannel;
046
047 import java.util.ArrayList;
048 import java.util.Collections;
049 import java.util.List;
050
051 import javax.servlet.ServletOutputStream;
052 import javax.servlet.http.HttpServletRequest;
053 import javax.servlet.http.HttpServletResponse;
054
055
059 public class ServletResponseUtil {
060
061 public static List<Range> getRanges(
062 HttpServletRequest request, HttpServletResponse response,
063 long length)
064 throws IOException {
065
066 String rangeString = request.getHeader(HttpHeaders.RANGE);
067
068 if (Validator.isNull(rangeString)) {
069 return Collections.emptyList();
070 }
071
072 if (!rangeString.matches(_RANGE_REGEX)) {
073 throw new IOException(
074 "Range header does not match regular expression");
075 }
076
077 List<Range> ranges = new ArrayList<Range>();
078
079 for (String part : StringUtil.split(rangeString.substring(6))) {
080 int index = part.indexOf(StringPool.DASH);
081
082 long start = GetterUtil.getLong(part.substring(0, index), -1);
083 long end = GetterUtil.getLong(
084 part.substring(index + 1, part.length()), -1);
085
086 if (start == -1) {
087 start = length - end;
088 end = length - 1;
089 }
090 else if ((end == -1) || (end > length - 1)) {
091 end = length - 1;
092 }
093
094 if (start > end) {
095 throw new IOException(
096 "Range start " + start + " is greater than end " + end);
097 }
098
099 Range range = new Range(start, end, length);
100
101 ranges.add(range);
102 }
103
104 return ranges;
105 }
106
107 public static void sendFile(
108 HttpServletRequest request, HttpServletResponse response,
109 String fileName, byte[] bytes)
110 throws IOException {
111
112 sendFile(request, response, fileName, bytes, null);
113 }
114
115 public static void sendFile(
116 HttpServletRequest request, HttpServletResponse response,
117 String fileName, byte[] bytes, String contentType)
118 throws IOException {
119
120 setHeaders(request, response, fileName, contentType);
121
122 write(response, bytes);
123 }
124
125 public static void sendFile(
126 HttpServletRequest request, HttpServletResponse response,
127 String fileName, InputStream is)
128 throws IOException {
129
130 sendFile(request, response, fileName, is, null);
131 }
132
133 public static void sendFile(
134 HttpServletRequest request, HttpServletResponse response,
135 String fileName, InputStream is, long contentLength,
136 String contentType)
137 throws IOException {
138
139 setHeaders(request, response, fileName, contentType);
140
141 write(response, is, contentLength);
142 }
143
144 public static void sendFile(
145 HttpServletRequest request, HttpServletResponse response,
146 String fileName, InputStream is, String contentType)
147 throws IOException {
148
149 sendFile(request, response, fileName, is, 0, contentType);
150 }
151
152
155 public static void sendFile(
156 HttpServletResponse response, String fileName, byte[] bytes)
157 throws IOException {
158
159 sendFile(null, response, fileName, bytes);
160 }
161
162
165 public static void sendFile(
166 HttpServletResponse response, String fileName, byte[] bytes,
167 String contentType)
168 throws IOException {
169
170 sendFile(null, response, fileName, bytes, contentType);
171 }
172
173
176 public static void sendFile(
177 HttpServletResponse response, String fileName, InputStream is)
178 throws IOException {
179
180 sendFile(null, response, fileName, is);
181 }
182
183
186 public static void sendFile(
187 HttpServletResponse response, String fileName, InputStream is,
188 int contentLength, String contentType)
189 throws IOException {
190
191 sendFile(null, response, fileName, is, contentLength, contentType);
192 }
193
194
197 public static void sendFile(
198 HttpServletResponse response, String fileName, InputStream is,
199 String contentType)
200 throws IOException {
201
202 sendFile(null, response, fileName, is, contentType);
203 }
204
205 public static void write(
206 HttpServletRequest request, HttpServletResponse response,
207 String fileName, List<Range> ranges, InputStream inputStream,
208 long fullLength, String contentType)
209 throws IOException {
210
211 Range fullRange = new Range(0, fullLength - 1, fullLength);
212
213 OutputStream outputStream = null;
214
215 try {
216 outputStream = response.getOutputStream();
217
218 Range firstRange = null;
219
220 if (!ranges.isEmpty()) {
221 firstRange = ranges.get(0);
222 }
223
224 if ((firstRange == null) || firstRange.equals(fullRange)) {
225 if (_log.isDebugEnabled()) {
226 _log.debug("Writing full range");
227 }
228
229 response.setContentType(contentType);
230
231 setHeaders(request, response, fileName, contentType, fullRange);
232
233 copyRange(
234 inputStream, outputStream, fullRange.getStart(),
235 fullRange.getLength());
236 }
237 else if (ranges.size() >= 1) {
238 if (_log.isDebugEnabled()) {
239 _log.debug("Attempting to write single or multiple range");
240 }
241
242 if (ranges.size() > 1 ) {
243 if (_log.isWarnEnabled()) {
244 _log.warn("Multiple range is not supported");
245 }
246 }
247
248 Range range = ranges.get(0);
249
250 response.setContentType(contentType);
251
252 setHeaders(request, response, fileName, contentType, range);
253
254 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
255
256 copyRange(
257 inputStream, outputStream, range.getStart(),
258 range.getLength());
259 }
260 }
261 finally {
262 try {
263 inputStream.close();
264 }
265 catch (IOException e) {
266 }
267 }
268 }
269
270 public static void write(HttpServletResponse response, byte[] bytes)
271 throws IOException {
272
273 write(response, bytes, 0, 0);
274 }
275
276 public static void write(
277 HttpServletResponse response, byte[] bytes, int offset,
278 int contentLength)
279 throws IOException {
280
281 try {
282
283
284
285 if (!response.isCommitted()) {
286
287
288
289 if (contentLength == 0) {
290 contentLength = bytes.length;
291 }
292
293 response.setContentLength(contentLength);
294
295 response.flushBuffer();
296
297 if (response instanceof ByteBufferServletResponse) {
298 ByteBufferServletResponse byteBufferResponse =
299 (ByteBufferServletResponse)response;
300
301 byteBufferResponse.setByteBuffer(
302 ByteBuffer.wrap(bytes, offset, contentLength));
303 }
304 else {
305 ServletOutputStream servletOutputStream =
306 response.getOutputStream();
307
308 if ((contentLength == 0) && ServerDetector.isJetty()) {
309 }
310 else {
311 servletOutputStream.write(bytes, offset, contentLength);
312 }
313 }
314 }
315 }
316 catch (IOException ioe) {
317 if ((ioe instanceof SocketException) ||
318 isClientAbortException(ioe)) {
319
320 if (_log.isWarnEnabled()) {
321 _log.warn(ioe);
322 }
323 }
324 else {
325 throw ioe;
326 }
327 }
328 }
329
330 public static void write(HttpServletResponse response, byte[][] bytesArray)
331 throws IOException {
332
333 try {
334
335
336
337 if (!response.isCommitted()) {
338 int contentLength = 0;
339
340 for (byte[] bytes : bytesArray) {
341 contentLength += bytes.length;
342 }
343
344 response.setContentLength(contentLength);
345
346 response.flushBuffer();
347
348 ServletOutputStream servletOutputStream =
349 response.getOutputStream();
350
351 for (byte[] bytes : bytesArray) {
352 servletOutputStream.write(bytes);
353 }
354 }
355 }
356 catch (IOException ioe) {
357 if ((ioe instanceof SocketException) ||
358 isClientAbortException(ioe)) {
359
360 if (_log.isWarnEnabled()) {
361 _log.warn(ioe);
362 }
363 }
364 else {
365 throw ioe;
366 }
367 }
368 }
369
370 public static void write(
371 HttpServletResponse response, ByteBuffer byteBuffer)
372 throws IOException {
373
374 if (response instanceof ByteBufferServletResponse) {
375 ByteBufferServletResponse byteBufferResponse =
376 (ByteBufferServletResponse)response;
377
378 byteBufferResponse.setByteBuffer(byteBuffer);
379 }
380 else {
381 write(
382 response, byteBuffer.array(), byteBuffer.position(),
383 byteBuffer.limit());
384 }
385 }
386
387 public static void write(HttpServletResponse response, File file)
388 throws IOException {
389
390 if (response instanceof ByteBufferServletResponse) {
391 ByteBufferServletResponse byteBufferResponse =
392 (ByteBufferServletResponse)response;
393
394 ByteBuffer byteBuffer = ByteBuffer.wrap(FileUtil.getBytes(file));
395
396 byteBufferResponse.setByteBuffer(byteBuffer);
397 }
398 else if (response instanceof StringServletResponse) {
399 StringServletResponse stringResponse =
400 (StringServletResponse)response;
401
402 String s = FileUtil.read(file);
403
404 stringResponse.setString(s);
405 }
406 else {
407 FileInputStream fileInputStream = new FileInputStream(file);
408
409 FileChannel fileChannel = fileInputStream.getChannel();
410
411 try {
412 int contentLength = (int)fileChannel.size();
413
414 response.setContentLength(contentLength);
415
416 response.flushBuffer();
417
418 fileChannel.transferTo(
419 0, contentLength,
420 Channels.newChannel(response.getOutputStream()));
421 }
422 finally {
423 fileChannel.close();
424 }
425 }
426 }
427
428 public static void write(HttpServletResponse response, InputStream is)
429 throws IOException {
430
431 write(response, is, 0);
432 }
433
434 public static void write(
435 HttpServletResponse response, InputStream is, long contentLength)
436 throws IOException {
437
438 if (response.isCommitted()) {
439 return;
440 }
441
442 if (contentLength > 0) {
443 response.setHeader(
444 HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
445 }
446
447 response.flushBuffer();
448
449 StreamUtil.transfer(is, response.getOutputStream());
450 }
451
452 public static void write(HttpServletResponse response, String s)
453 throws IOException {
454
455 if (response instanceof StringServletResponse) {
456 StringServletResponse stringResponse =
457 (StringServletResponse)response;
458
459 stringResponse.setString(s);
460 }
461 else {
462 ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
463 StringPool.UTF8, s);
464
465 write(response, byteBuffer);
466 }
467 }
468
469 public static void write(
470 HttpServletResponse response, StringServletResponse stringResponse)
471 throws IOException {
472
473 if (stringResponse.isCalledGetOutputStream()) {
474 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
475 stringResponse.getUnsyncByteArrayOutputStream();
476
477 ByteBuffer byteBuffer =
478 unsyncByteArrayOutputStream.unsafeGetByteBuffer();
479
480 write(response, byteBuffer);
481 }
482 else {
483 write(response, stringResponse.getString());
484 }
485 }
486
487 protected static void copyRange(
488 InputStream inputStream, OutputStream outputStream, long start,
489 long length)
490 throws IOException {
491
492 if (inputStream instanceof FileInputStream) {
493 FileInputStream fileInputStream = (FileInputStream)inputStream;
494
495 FileChannel fileChannel = fileInputStream.getChannel();
496
497 fileChannel.transferTo(
498 start, length, Channels.newChannel(outputStream));
499 }
500 else if (inputStream instanceof ByteArrayInputStream) {
501 ByteArrayInputStream byteArrayInputStream =
502 (ByteArrayInputStream)inputStream;
503
504 byteArrayInputStream.skip(start);
505
506 StreamUtil.transfer(byteArrayInputStream, outputStream, length);
507 }
508 else {
509 RandomAccessInputStream randomAccessInputStream =
510 new RandomAccessInputStream(inputStream);
511
512 randomAccessInputStream.seek(start);
513
514 StreamUtil.transfer(randomAccessInputStream, outputStream, length);
515 }
516 }
517
518 protected static boolean isClientAbortException(IOException ioe) {
519 Class<?> clazz = ioe.getClass();
520
521 String className = clazz.getName();
522
523 if (className.equals(_CLIENT_ABORT_EXCEPTION)) {
524 return true;
525 }
526 else {
527 return false;
528 }
529 }
530
531 protected static void setHeaders(
532 HttpServletRequest request, HttpServletResponse response,
533 String fileName, String contentType) {
534
535 if (_log.isDebugEnabled()) {
536 _log.debug("Sending file of type " + contentType);
537 }
538
539
540
541 if (Validator.isNotNull(contentType)) {
542 response.setContentType(contentType);
543 }
544
545 response.setHeader(
546 HttpHeaders.CACHE_CONTROL, HttpHeaders.CACHE_CONTROL_PUBLIC_VALUE);
547 response.setHeader(HttpHeaders.PRAGMA, HttpHeaders.PRAGMA_PUBLIC_VALUE);
548
549 if (Validator.isNotNull(fileName)) {
550 String contentDisposition =
551 "attachment; filename=\"" + fileName + "\"";
552
553
554
555
556 boolean ascii = true;
557
558 for (int i = 0; i < fileName.length(); i++) {
559 if (!Validator.isAscii(fileName.charAt(i))) {
560 ascii = false;
561
562 break;
563 }
564 }
565
566 try {
567 if (!ascii) {
568 String encodedFileName = HttpUtil.encodeURL(fileName, true);
569
570 if (BrowserSnifferUtil.isIe(request)) {
571 contentDisposition =
572 "attachment; filename=\"" + encodedFileName + "\"";
573 }
574 else {
575 contentDisposition =
576 "attachment; filename*=UTF-8''" + encodedFileName;
577 }
578 }
579 }
580 catch (Exception e) {
581 if (_log.isWarnEnabled()) {
582 _log.warn(e);
583 }
584 }
585
586 String extension = GetterUtil.getString(
587 FileUtil.getExtension(fileName)).toLowerCase();
588
589 String[] mimeTypesContentDispositionInline = null;
590
591 try {
592 mimeTypesContentDispositionInline = PropsUtil.getArray(
593 "mime.types.content.disposition.inline");
594 }
595 catch (Exception e) {
596 mimeTypesContentDispositionInline = new String[0];
597 }
598
599 if (ArrayUtil.contains(
600 mimeTypesContentDispositionInline, extension)) {
601
602 contentDisposition = StringUtil.replace(
603 contentDisposition, "attachment; ", "inline; ");
604 }
605
606 response.setHeader(
607 HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
608 }
609 }
610
611 protected static void setHeaders(
612 HttpServletRequest request, HttpServletResponse response,
613 String fileName, String contentType, Range range) {
614
615 setHeaders(request, response, fileName, contentType);
616
617 response.setHeader(
618 HttpHeaders.ACCEPT_RANGES, HttpHeaders.ACCEPT_RANGES_BYTES_VALUE);
619
620 StringBundler sb = new StringBundler(6);
621
622 sb.append("bytes ");
623 sb.append(range.getStart());
624 sb.append(StringPool.DASH);
625 sb.append(range.getEnd());
626 sb.append(StringPool.SLASH);
627 sb.append(range.getTotal());
628
629 response.setHeader(HttpHeaders.CONTENT_RANGE, sb.toString());
630
631 response.setHeader(
632 HttpHeaders.CONTENT_LENGTH, String.valueOf(range.getLength()));
633 }
634
635 private static final String _CLIENT_ABORT_EXCEPTION =
636 "org.apache.catalina.connector.ClientAbortException";
637
638 private static final String _RANGE_REGEX = "^bytes=\\d*-\\d*(,\\d*-\\d*)*$";
639
640 private static Log _log = LogFactoryUtil.getLog(ServletResponseUtil.class);
641
642 }