001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
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.PropsKeys;
026    import com.liferay.portal.kernel.util.PropsUtil;
027    import com.liferay.portal.kernel.util.RandomAccessInputStream;
028    import com.liferay.portal.kernel.util.ServerDetector;
029    import com.liferay.portal.kernel.util.StreamUtil;
030    import com.liferay.portal.kernel.util.StringBundler;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    
035    import java.io.ByteArrayInputStream;
036    import java.io.File;
037    import java.io.FileInputStream;
038    import java.io.IOException;
039    import java.io.InputStream;
040    import java.io.OutputStream;
041    
042    import java.net.SocketException;
043    
044    import java.nio.ByteBuffer;
045    import java.nio.channels.Channels;
046    import java.nio.channels.FileChannel;
047    
048    import java.util.ArrayList;
049    import java.util.Collections;
050    import java.util.List;
051    
052    import javax.servlet.ServletOutputStream;
053    import javax.servlet.http.HttpServletRequest;
054    import javax.servlet.http.HttpServletResponse;
055    
056    /**
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(HttpServletResponse response, byte[] bytes)
327                    throws IOException {
328    
329                    write(response, bytes, 0, 0);
330            }
331    
332            public static void write(
333                            HttpServletResponse response, byte[] bytes, int offset,
334                            int contentLength)
335                    throws IOException {
336    
337                    try {
338    
339                            // LEP-3122
340    
341                            if (!response.isCommitted()) {
342    
343                                    // LEP-536
344    
345                                    if (contentLength == 0) {
346                                            contentLength = bytes.length;
347                                    }
348    
349                                    response.setContentLength(contentLength);
350    
351                                    response.flushBuffer();
352    
353                                    if (response instanceof ByteBufferServletResponse) {
354                                            ByteBufferServletResponse byteBufferResponse =
355                                                    (ByteBufferServletResponse)response;
356    
357                                            byteBufferResponse.setByteBuffer(
358                                                    ByteBuffer.wrap(bytes, offset, contentLength));
359                                    }
360                                    else {
361                                            ServletOutputStream servletOutputStream =
362                                                    response.getOutputStream();
363    
364                                            if ((contentLength == 0) && ServerDetector.isJetty()) {
365                                            }
366                                            else {
367                                                    servletOutputStream.write(bytes, offset, contentLength);
368                                            }
369                                    }
370                            }
371                    }
372                    catch (IOException ioe) {
373                            if ((ioe instanceof SocketException) ||
374                                    isClientAbortException(ioe)) {
375    
376                                    if (_log.isWarnEnabled()) {
377                                            _log.warn(ioe);
378                                    }
379                            }
380                            else {
381                                    throw ioe;
382                            }
383                    }
384            }
385    
386            public static void write(HttpServletResponse response, byte[][] bytesArray)
387                    throws IOException {
388    
389                    try {
390    
391                            // LEP-3122
392    
393                            if (!response.isCommitted()) {
394                                    int contentLength = 0;
395    
396                                    for (byte[] bytes : bytesArray) {
397                                            contentLength += bytes.length;
398                                    }
399    
400                                    response.setContentLength(contentLength);
401    
402                                    response.flushBuffer();
403    
404                                    ServletOutputStream servletOutputStream =
405                                            response.getOutputStream();
406    
407                                    for (byte[] bytes : bytesArray) {
408                                            servletOutputStream.write(bytes);
409                                    }
410                            }
411                    }
412                    catch (IOException ioe) {
413                            if ((ioe instanceof SocketException) ||
414                                    isClientAbortException(ioe)) {
415    
416                                    if (_log.isWarnEnabled()) {
417                                            _log.warn(ioe);
418                                    }
419                            }
420                            else {
421                                    throw ioe;
422                            }
423                    }
424            }
425    
426            public static void write(
427                            HttpServletResponse response, ByteBuffer byteBuffer)
428                    throws IOException {
429    
430                    if (response instanceof ByteBufferServletResponse) {
431                            ByteBufferServletResponse byteBufferResponse =
432                                    (ByteBufferServletResponse)response;
433    
434                            byteBufferResponse.setByteBuffer(byteBuffer);
435                    }
436                    else {
437                            write(
438                                    response, byteBuffer.array(), byteBuffer.position(),
439                                    byteBuffer.limit());
440                    }
441            }
442    
443            public static void write(HttpServletResponse response, File file)
444                    throws IOException {
445    
446                    if (response instanceof ByteBufferServletResponse) {
447                            ByteBufferServletResponse byteBufferResponse =
448                                    (ByteBufferServletResponse)response;
449    
450                            ByteBuffer byteBuffer = ByteBuffer.wrap(FileUtil.getBytes(file));
451    
452                            byteBufferResponse.setByteBuffer(byteBuffer);
453                    }
454                    else if (response instanceof StringServletResponse) {
455                            StringServletResponse stringResponse =
456                                    (StringServletResponse)response;
457    
458                            String s = FileUtil.read(file);
459    
460                            stringResponse.setString(s);
461                    }
462                    else {
463                            FileInputStream fileInputStream = new FileInputStream(file);
464    
465                            FileChannel fileChannel = fileInputStream.getChannel();
466    
467                            try {
468                                    int contentLength = (int)fileChannel.size();
469    
470                                    response.setContentLength(contentLength);
471    
472                                    response.flushBuffer();
473    
474                                    fileChannel.transferTo(
475                                            0, contentLength,
476                                            Channels.newChannel(response.getOutputStream()));
477                            }
478                            finally {
479                                    fileChannel.close();
480                            }
481                    }
482            }
483    
484            public static void write(HttpServletResponse response, InputStream is)
485                    throws IOException {
486    
487                    write(response, is, 0);
488            }
489    
490            public static void write(
491                            HttpServletResponse response, InputStream is, long contentLength)
492                    throws IOException {
493    
494                    if (response.isCommitted()) {
495                            return;
496                    }
497    
498                    if (contentLength > 0) {
499                            response.setHeader(
500                                    HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
501                    }
502    
503                    response.flushBuffer();
504    
505                    StreamUtil.transfer(is, response.getOutputStream());
506            }
507    
508            public static void write(HttpServletResponse response, String s)
509                    throws IOException {
510    
511                    if (response instanceof StringServletResponse) {
512                            StringServletResponse stringResponse =
513                                    (StringServletResponse)response;
514    
515                            stringResponse.setString(s);
516                    }
517                    else {
518                            ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
519                                    StringPool.UTF8, s);
520    
521                            write(response, byteBuffer);
522                    }
523            }
524    
525            public static void write(
526                            HttpServletResponse response, StringServletResponse stringResponse)
527                    throws IOException {
528    
529                    if (stringResponse.isCalledGetOutputStream()) {
530                            UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
531                                    stringResponse.getUnsyncByteArrayOutputStream();
532    
533                            ByteBuffer byteBuffer =
534                                    unsyncByteArrayOutputStream.unsafeGetByteBuffer();
535    
536                            write(response, byteBuffer);
537                    }
538                    else {
539                            write(response, stringResponse.getString());
540                    }
541            }
542    
543            protected static void copyRange(
544                            InputStream inputStream, OutputStream outputStream, long start,
545                            long length)
546                    throws IOException {
547    
548                    if (inputStream instanceof FileInputStream) {
549                            FileInputStream fileInputStream = (FileInputStream)inputStream;
550    
551                            FileChannel fileChannel = fileInputStream.getChannel();
552    
553                            fileChannel.transferTo(
554                                    start, length, Channels.newChannel(outputStream));
555                    }
556                    else if (inputStream instanceof ByteArrayInputStream) {
557                            ByteArrayInputStream byteArrayInputStream =
558                                    (ByteArrayInputStream)inputStream;
559    
560                            byteArrayInputStream.skip(start);
561    
562                            StreamUtil.transfer(byteArrayInputStream, outputStream, length);
563                    }
564                    else {
565                            RandomAccessInputStream randomAccessInputStream =
566                                    new RandomAccessInputStream(inputStream);
567    
568                            randomAccessInputStream.seek(start);
569    
570                            StreamUtil.transfer(randomAccessInputStream, outputStream, length);
571                    }
572            }
573    
574            protected static boolean isClientAbortException(IOException ioe) {
575                    Class<?> clazz = ioe.getClass();
576    
577                    String className = clazz.getName();
578    
579                    if (className.equals(_CLIENT_ABORT_EXCEPTION)) {
580                            return true;
581                    }
582                    else {
583                            return false;
584                    }
585            }
586    
587            protected static void setHeaders(
588                    HttpServletRequest request, HttpServletResponse response,
589                    String fileName, String contentType) {
590    
591                    if (_log.isDebugEnabled()) {
592                            _log.debug("Sending file of type " + contentType);
593                    }
594    
595                    // LEP-2201
596    
597                    if (Validator.isNotNull(contentType)) {
598                            response.setContentType(contentType);
599                    }
600    
601                    response.setHeader(
602                            HttpHeaders.CACHE_CONTROL, HttpHeaders.CACHE_CONTROL_PRIVATE_VALUE);
603                    response.setHeader(
604                            HttpHeaders.PRAGMA, HttpHeaders.PRAGMA_NO_CACHE_VALUE);
605    
606                    if (Validator.isNotNull(fileName)) {
607                            String contentDisposition =
608                                    "attachment; filename=\"" + fileName + "\"";
609    
610                            // If necessary for non-ASCII characters, encode based on RFC 2184.
611                            // However, not all browsers support RFC 2184. See LEP-3127.
612    
613                            boolean ascii = true;
614    
615                            for (int i = 0; i < fileName.length(); i++) {
616                                    if (!Validator.isAscii(fileName.charAt(i))) {
617                                            ascii = false;
618    
619                                            break;
620                                    }
621                            }
622    
623                            if (!ascii) {
624                                    String encodedFileName = HttpUtil.encodeURL(fileName, true);
625    
626                                    if (BrowserSnifferUtil.isIe(request)) {
627                                            contentDisposition =
628                                                    "attachment; filename=\"" + encodedFileName + "\"";
629                                    }
630                                    else {
631                                            contentDisposition =
632                                                    "attachment; filename*=UTF-8''" + encodedFileName;
633                                    }
634                            }
635    
636                            String extension = GetterUtil.getString(
637                                    FileUtil.getExtension(fileName)).toLowerCase();
638    
639                            String[] mimeTypesContentDispositionInline = null;
640    
641                            try {
642                                    mimeTypesContentDispositionInline = PropsUtil.getArray(
643                                            PropsKeys.MIME_TYPES_CONTENT_DISPOSITION_INLINE);
644                            }
645                            catch (Exception e) {
646                                    mimeTypesContentDispositionInline = new String[0];
647                            }
648    
649                            if (ArrayUtil.contains(
650                                            mimeTypesContentDispositionInline, extension)) {
651    
652                                    contentDisposition = StringUtil.replace(
653                                            contentDisposition, "attachment; ", "inline; ");
654                            }
655    
656                            if (_log.isDebugEnabled()) {
657                                    _log.debug(
658                                            "Setting content disposition header " + contentDisposition);
659                            }
660    
661                            response.setHeader(
662                                    HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
663                    }
664            }
665    
666            protected static void setHeaders(
667                    HttpServletRequest request, HttpServletResponse response,
668                    String fileName, String contentType, Range range) {
669    
670                    setHeaders(request, response, fileName, contentType);
671    
672                    if (range != null) {
673                            response.setHeader(
674                                    HttpHeaders.CONTENT_RANGE, range.getContentRange());
675    
676                            response.setHeader(
677                                    HttpHeaders.CONTENT_LENGTH, String.valueOf(range.getLength()));
678                    }
679            }
680    
681            private static final String _CLIENT_ABORT_EXCEPTION =
682                    "org.apache.catalina.connector.ClientAbortException";
683    
684            private static final int _MAX_RANGE_FIELDS = GetterUtil.getInteger(
685                    PropsUtil.get(PropsKeys.WEB_SERVER_SERVLET_MAX_RANGE_FIELDS));
686    
687            private static final String _RANGE_REGEX =
688                    "^bytes=\\d*-\\d*(,\\s?\\d*-\\d*)*$";
689    
690            private static Log _log = LogFactoryUtil.getLog(ServletResponseUtil.class);
691    
692    }