001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
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    /**
057     * @author Brian Wing Shun Chan
058     * @author Shuyang Zhou
059     */
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            /**
172             * @deprecated
173             */
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            /**
182             * @deprecated
183             */
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            /**
193             * @deprecated
194             */
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            /**
203             * @deprecated
204             */
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            /**
214             * @deprecated
215             */
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                            // LEP-3122
353    
354                            if (!response.isCommitted()) {
355    
356                                    // LEP-536
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                            // LEP-3122
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                    // LEP-2201
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                            // If necessary for non-ASCII characters, encode based on RFC 2184.
614                            // However, not all browsers support RFC 2184. See LEP-3127.
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    }