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.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    /**
056     * @author Brian Wing Shun Chan
057     * @author Shuyang Zhou
058     */
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            /**
153             * @deprecated
154             */
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            /**
163             * @deprecated
164             */
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            /**
174             * @deprecated
175             */
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            /**
184             * @deprecated
185             */
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            /**
195             * @deprecated
196             */
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                            // LEP-3122
284    
285                            if (!response.isCommitted()) {
286    
287                                    // LEP-536
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                            // LEP-3122
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                    // LEP-2201
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                            // If necessary for non-ASCII characters, encode based on RFC 2184.
554                            // However, not all browsers support RFC 2184. See LEP-3127.
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    }