1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.catalina.connector;
18
19 import java.io.IOException;
20 import java.io.Writer;
21 import java.nio.Buffer;
22 import java.nio.ByteBuffer;
23 import java.nio.CharBuffer;
24 import java.nio.charset.Charset;
25 import java.security.AccessController;
26 import java.security.PrivilegedActionException;
27 import java.security.PrivilegedExceptionAction;
28 import java.util.HashMap;
29 import java.util.Map;
30
31 import javax.servlet.WriteListener;
32 import javax.servlet.http.HttpServletResponse;
33
34 import org.apache.catalina.Globals;
35 import org.apache.coyote.ActionCode;
36 import org.apache.coyote.CloseNowException;
37 import org.apache.coyote.Response;
38 import org.apache.tomcat.util.buf.C2BConverter;
39 import org.apache.tomcat.util.res.StringManager;
40
41 /**
42  * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3
43  * OutputBuffer, with the removal of some of the state handling (which in
44  * Coyote is mostly the Processor's responsibility).
45  *
46  * @author Costin Manolache
47  * @author Remy Maucherat
48  */

49 public class OutputBuffer extends Writer {
50
51     private static final StringManager sm = StringManager.getManager(OutputBuffer.class);
52
53     public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
54
55     /**
56      * Encoder cache.
57      */

58     private final Map<Charset, C2BConverter> encoders = new HashMap<>();
59
60
61     /**
62      * Default buffer size.
63      */

64     private final int defaultBufferSize;
65
66     // ----------------------------------------------------- Instance Variables
67
68     /**
69      * The byte buffer.
70      */

71     private ByteBuffer bb;
72
73
74     /**
75      * The char buffer.
76      */

77     private final CharBuffer cb;
78
79
80     /**
81      * State of the output buffer.
82      */

83     private boolean initial = true;
84
85
86     /**
87      * Number of bytes written.
88      */

89     private long bytesWritten = 0;
90
91
92     /**
93      * Number of chars written.
94      */

95     private long charsWritten = 0;
96
97
98     /**
99      * Flag which indicates if the output buffer is closed.
100      */

101     private volatile boolean closed = false;
102
103
104     /**
105      * Do a flush on the next operation.
106      */

107     private boolean doFlush = false;
108
109
110     /**
111      * Current char to byte converter.
112      */

113     protected C2BConverter conv;
114
115
116     /**
117      * Associated Coyote response.
118      */

119     private Response coyoteResponse;
120
121
122     /**
123      * Suspended flag. All output bytes will be swallowed if this is true.
124      */

125     private volatile boolean suspended = false;
126
127
128     // ----------------------------------------------------------- Constructors
129
130     /**
131      * Create the buffer with the specified initial size.
132      *
133      * @param size Buffer size to use
134      */

135     public OutputBuffer(int size) {
136         defaultBufferSize = size;
137         bb = ByteBuffer.allocate(size);
138         clear(bb);
139         cb = CharBuffer.allocate(size);
140         clear(cb);
141     }
142
143
144     // ------------------------------------------------------------- Properties
145
146     /**
147      * Associated Coyote response.
148      *
149      * @param coyoteResponse Associated Coyote response
150      */

151     public void setResponse(Response coyoteResponse) {
152         this.coyoteResponse = coyoteResponse;
153     }
154
155
156     /**
157      * Is the response output suspended ?
158      *
159      * @return suspended flag value
160      */

161     public boolean isSuspended() {
162         return this.suspended;
163     }
164
165
166     /**
167      * Set the suspended flag.
168      *
169      * @param suspended New suspended flag value
170      */

171     public void setSuspended(boolean suspended) {
172         this.suspended = suspended;
173     }
174
175
176     /**
177      * Is the response output closed ?
178      *
179      * @return closed flag value
180      */

181     public boolean isClosed() {
182         return this.closed;
183     }
184
185
186     // --------------------------------------------------------- Public Methods
187
188     /**
189      * Recycle the output buffer.
190      */

191     public void recycle() {
192
193         initial = true;
194         bytesWritten = 0;
195         charsWritten = 0;
196
197         if (bb.capacity() > 16 * defaultBufferSize) {
198             // Discard buffers which are too large
199             bb = ByteBuffer.allocate(defaultBufferSize);
200         }
201         clear(bb);
202         clear(cb);
203         closed = false;
204         suspended = false;
205         doFlush = false;
206
207         if (conv != null) {
208             conv.recycle();
209             conv = null;
210         }
211     }
212
213
214     /**
215      * Close the output buffer. This tries to calculate the response size if
216      * the response has not been committed yet.
217      *
218      * @throws IOException An underlying IOException occurred
219      */

220     @Override
221     public void close() throws IOException {
222
223         if (closed) {
224             return;
225         }
226         if (suspended) {
227             return;
228         }
229
230         // If there are chars, flush all of them to the byte buffer now as bytes are used to
231         // calculate the content-length (if everything fits into the byte buffer, of course).
232         if (cb.remaining() > 0) {
233             flushCharBuffer();
234         }
235
236         if ((!coyoteResponse.isCommitted()) && (coyoteResponse.getContentLengthLong() == -1)
237                 && !coyoteResponse.getRequest().method().equals("HEAD")) {
238             // If this didn't cause a commit of the response, the final content
239             // length can be calculated. Only do this if this is not a HEAD
240             // request since in that case no body should have been written and
241             // setting a value of zero here will result in an explicit content
242             // length of zero being set on the response.
243             if (!coyoteResponse.isCommitted()) {
244                 coyoteResponse.setContentLength(bb.remaining());
245             }
246         }
247
248         if (coyoteResponse.getStatus() == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
249             doFlush(true);
250         } else {
251             doFlush(false);
252         }
253         closed = true;
254
255         // The request should have been completely read by the time the response
256         // is closed. Further reads of the input a) are pointless and b) really
257         // confuse AJP (bug 50189) so close the input buffer to prevent them.
258         Request req = (Request) coyoteResponse.getRequest().getNote(CoyoteAdapter.ADAPTER_NOTES);
259         req.inputBuffer.close();
260
261         coyoteResponse.action(ActionCode.CLOSE, null);
262     }
263
264
265     /**
266      * Flush bytes or chars contained in the buffer.
267      *
268      * @throws IOException An underlying IOException occurred
269      */

270     @Override
271     public void flush() throws IOException {
272         doFlush(true);
273     }
274
275
276     /**
277      * Flush bytes or chars contained in the buffer.
278      *
279      * @param realFlush <code>true</code> if this should also cause a real network flush
280      * @throws IOException An underlying IOException occurred
281      */

282     protected void doFlush(boolean realFlush) throws IOException {
283
284         if (suspended) {
285             return;
286         }
287
288         try {
289             doFlush = true;
290             if (initial) {
291                 coyoteResponse.sendHeaders();
292                 initial = false;
293             }
294             if (cb.remaining() > 0) {
295                 flushCharBuffer();
296             }
297             if (bb.remaining() > 0) {
298                 flushByteBuffer();
299             }
300         } finally {
301             doFlush = false;
302         }
303
304         if (realFlush) {
305             coyoteResponse.action(ActionCode.CLIENT_FLUSH, null);
306             // If some exception occurred earlier, or if some IOE occurred
307             // here, notify the servlet with an IOE
308             if (coyoteResponse.isExceptionPresent()) {
309                 throw new ClientAbortException(coyoteResponse.getErrorException());
310             }
311         }
312
313     }
314
315
316     // ------------------------------------------------- Bytes Handling Methods
317
318     /**
319      * Sends the buffer data to the client output, checking the
320      * state of Response and calling the right interceptors.
321      *
322      * @param buf the ByteBuffer to be written to the response
323      *
324      * @throws IOException An underlying IOException occurred
325      */

326     public void realWriteBytes(ByteBuffer buf) throws IOException {
327
328         if (closed) {
329             return;
330         }
331         if (coyoteResponse == null) {
332             return;
333         }
334
335         // If we really have something to write
336         if (buf.remaining() > 0) {
337             // real write to the adapter
338             try {
339                 coyoteResponse.doWrite(buf);
340             } catch (CloseNowException e) {
341                 // Catch this sub-class as it requires specific handling.
342                 // Examples where this exception is thrown:
343                 // - HTTP/2 stream timeout
344                 // Prevent further output for this response
345                 closed = true;
346                 throw e;
347             } catch (IOException e) {
348                 // An IOException on a write is almost always due to
349                 // the remote client aborting the request. Wrap this
350                 // so that it can be handled better by the error dispatcher.
351                 throw new ClientAbortException(e);
352             }
353         }
354
355     }
356
357
358     public void write(byte b[], int off, int len) throws IOException {
359
360         if (suspended) {
361             return;
362         }
363
364         writeBytes(b, off, len);
365
366     }
367
368
369     public void write(ByteBuffer from) throws IOException {
370
371         if (suspended) {
372             return;
373         }
374
375         writeBytes(from);
376
377     }
378
379
380     private void writeBytes(byte b[], int off, int len) throws IOException {
381
382         if (closed) {
383             return;
384         }
385
386         append(b, off, len);
387         bytesWritten += len;
388
389         // if called from within flush(), then immediately flush
390         // remaining bytes
391         if (doFlush) {
392             flushByteBuffer();
393         }
394
395     }
396
397
398     private void writeBytes(ByteBuffer from) throws IOException {
399
400         if (closed) {
401             return;
402         }
403
404         append(from);
405         bytesWritten += from.remaining();
406
407         // if called from within flush(), then immediately flush
408         // remaining bytes
409         if (doFlush) {
410             flushByteBuffer();
411         }
412
413     }
414
415
416     public void writeByte(int b) throws IOException {
417
418         if (suspended) {
419             return;
420         }
421
422         if (isFull(bb)) {
423             flushByteBuffer();
424         }
425
426         transfer((byte) b, bb);
427         bytesWritten++;
428
429     }
430
431
432     // ------------------------------------------------- Chars Handling Methods
433
434
435     /**
436      * Convert the chars to bytes, then send the data to the client.
437      *
438      * @param from Char buffer to be written to the response
439      *
440      * @throws IOException An underlying IOException occurred
441      */

442     public void realWriteChars(CharBuffer from) throws IOException {
443
444         while (from.remaining() > 0) {
445             conv.convert(from, bb);
446             if (bb.remaining() == 0) {
447                 // Break out of the loop if more chars are needed to produce any output
448                 break;
449             }
450             if (from.remaining() > 0) {
451                 flushByteBuffer();
452             } else if (conv.isUndeflow() && bb.limit() > bb.capacity() - 4) {
453                 // Handle an edge case. There are no more chars to write at the
454                 // moment but there is a leftover character in the converter
455                 // which must be part of a surrogate pair. The byte buffer does
456                 // not have enough space left to output the bytes for this pair
457                 // once it is complete )it will require 4 bytes) so flush now to
458                 // prevent the bytes for the leftover char and the rest of the
459                 // surrogate pair yet to be written from being lost.
460                 // See TestOutputBuffer#testUtf8SurrogateBody()
461                 flushByteBuffer();
462             }
463         }
464
465     }
466
467     @Override
468     public void write(int c) throws IOException {
469
470         if (suspended) {
471             return;
472         }
473
474         if (isFull(cb)) {
475             flushCharBuffer();
476         }
477
478         transfer((char) c, cb);
479         charsWritten++;
480
481     }
482
483
484     @Override
485     public void write(char c[]) throws IOException {
486
487         if (suspended) {
488             return;
489         }
490
491         write(c, 0, c.length);
492
493     }
494
495
496     @Override
497     public void write(char c[], int off, int len) throws IOException {
498
499         if (suspended) {
500             return;
501         }
502
503         append(c, off, len);
504         charsWritten += len;
505
506     }
507
508
509     /**
510      * Append a string to the buffer
511      */

512     @Override
513     public void write(String s, int off, int len) throws IOException {
514
515         if (suspended) {
516             return;
517         }
518
519         if (s == null) {
520             throw new NullPointerException(sm.getString("outputBuffer.writeNull"));
521         }
522
523         int sOff = off;
524         int sEnd = off + len;
525         while (sOff < sEnd) {
526             int n = transfer(s, sOff, sEnd - sOff, cb);
527             sOff += n;
528             if (isFull(cb)) {
529                 flushCharBuffer();
530             }
531         }
532
533         charsWritten += len;
534     }
535
536
537     @Override
538     public void write(String s) throws IOException {
539
540         if (suspended) {
541             return;
542         }
543
544         if (s == null) {
545             s = "null";
546         }
547         write(s, 0, s.length());
548     }
549
550
551     public void checkConverter() throws IOException {
552         if (conv != null) {
553             return;
554         }
555
556         Charset charset = null;
557
558         if (coyoteResponse != null) {
559             charset = coyoteResponse.getCharset();
560         }
561
562         if (charset == null) {
563             charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
564         }
565
566         conv = encoders.get(charset);
567
568         if (conv == null) {
569             conv = createConverter(charset);
570             encoders.put(charset, conv);
571         }
572     }
573
574
575     private static C2BConverter createConverter(final Charset charset) throws IOException {
576         if (Globals.IS_SECURITY_ENABLED) {
577             try {
578                 return AccessController.doPrivileged(new PrivilegedCreateConverter(charset));
579             } catch (PrivilegedActionException ex) {
580                 Exception e = ex.getException();
581                 if (e instanceof IOException) {
582                     throw (IOException) e;
583                 } else {
584                     throw new IOException(ex);
585                 }
586             }
587         } else {
588             return new C2BConverter(charset);
589         }
590     }
591
592
593     // --------------------  BufferedOutputStream compatibility
594
595     public long getContentWritten() {
596         return bytesWritten + charsWritten;
597     }
598
599     /**
600      * Has this buffer been used at all?
601      *
602      * @return true if no chars or bytes have been added to the buffer since the
603      *         last call to {@link #recycle()}
604      */

605     public boolean isNew() {
606         return (bytesWritten == 0) && (charsWritten == 0);
607     }
608
609
610     public void setBufferSize(int size) {
611         if (size > bb.capacity()) {
612             bb = ByteBuffer.allocate(size);
613             clear(bb);
614         }
615     }
616
617
618     public void reset() {
619         reset(false);
620     }
621
622     public void reset(boolean resetWriterStreamFlags) {
623         clear(bb);
624         clear(cb);
625         bytesWritten = 0;
626         charsWritten = 0;
627         if (resetWriterStreamFlags) {
628             if (conv != null) {
629                 conv.recycle();
630             }
631             conv = null;
632         }
633         initial = true;
634     }
635
636
637     public int getBufferSize() {
638         return bb.capacity();
639     }
640
641
642     /*
643      * All the non-blocking write state information is held in the Response so
644      * it is visible / accessible to all the code that needs it.
645      */

646
647     public boolean isReady() {
648         return coyoteResponse.isReady();
649     }
650
651
652     public void setWriteListener(WriteListener listener) {
653         coyoteResponse.setWriteListener(listener);
654     }
655
656
657     public boolean isBlocking() {
658         return coyoteResponse.getWriteListener() == null;
659     }
660
661     public void checkRegisterForWrite() {
662         coyoteResponse.checkRegisterForWrite();
663     }
664
665     /**
666      * Add data to the buffer.
667      *
668      * @param src Bytes array
669      * @param off Offset
670      * @param len Length
671      * @throws IOException Writing overflow data to the output channel failed
672      */

673     public void append(byte src[], int off, int len) throws IOException {
674         if (bb.remaining() == 0) {
675             appendByteArray(src, off, len);
676         } else {
677             int n = transfer(src, off, len, bb);
678             len = len - n;
679             off = off + n;
680             if (isFull(bb)) {
681                 flushByteBuffer();
682                 appendByteArray(src, off, len);
683             }
684         }
685     }
686
687     /**
688      * Add data to the buffer.
689      * @param src Char array
690      * @param off Offset
691      * @param len Length
692      * @throws IOException Writing overflow data to the output channel failed
693      */

694     public void append(char src[], int off, int len) throws IOException {
695         // if we have limit and we're below
696         if(len <= cb.capacity() - cb.limit()) {
697             transfer(src, off, len, cb);
698             return;
699         }
700
701         // Optimization:
702         // If len-avail < length ( i.e. after we fill the buffer with
703         // what we can, the remaining will fit in the buffer ) we'll just
704         // copy the first part, flush, then copy the second part - 1 write
705         // and still have some space for more. We'll still have 2 writes, but
706         // we write more on the first.
707         if(len + cb.limit() < 2 * cb.capacity()) {
708             /* If the request length exceeds the size of the output buffer,
709                flush the output buffer and then write the data directly.
710                We can't avoid 2 writes, but we can write more on the second
711             */

712             int n = transfer(src, off, len, cb);
713
714             flushCharBuffer();
715
716             transfer(src, off + n, len - n, cb);
717         } else {
718             // long write - flush the buffer and write the rest
719             // directly from source
720             flushCharBuffer();
721
722             realWriteChars(CharBuffer.wrap(src, off, len));
723         }
724     }
725
726
727     public void append(ByteBuffer from) throws IOException {
728         if (bb.remaining() == 0) {
729             appendByteBuffer(from);
730         } else {
731             transfer(from, bb);
732             if (isFull(bb)) {
733                 flushByteBuffer();
734                 appendByteBuffer(from);
735             }
736         }
737     }
738
739     private void appendByteArray(byte src[], int off, int len) throws IOException {
740         if (len == 0) {
741             return;
742         }
743
744         int limit = bb.capacity();
745         while (len >= limit) {
746             realWriteBytes(ByteBuffer.wrap(src, off, limit));
747             len = len - limit;
748             off = off + limit;
749         }
750
751         if (len > 0) {
752             transfer(src, off, len, bb);
753         }
754     }
755
756     private void appendByteBuffer(ByteBuffer from) throws IOException {
757         if (from.remaining() == 0) {
758             return;
759         }
760
761         int limit = bb.capacity();
762         int fromLimit = from.limit();
763         while (from.remaining() >= limit) {
764             from.limit(from.position() + limit);
765             realWriteBytes(from.slice());
766             from.position(from.limit());
767             from.limit(fromLimit);
768         }
769
770         if (from.remaining() > 0) {
771             transfer(from, bb);
772         }
773     }
774
775     private void flushByteBuffer() throws IOException {
776         realWriteBytes(bb.slice());
777         clear(bb);
778     }
779
780     private void flushCharBuffer() throws IOException {
781         realWriteChars(cb.slice());
782         clear(cb);
783     }
784
785     private void transfer(byte b, ByteBuffer to) {
786         toWriteMode(to);
787         to.put(b);
788         toReadMode(to);
789     }
790
791     private void transfer(char b, CharBuffer to) {
792         toWriteMode(to);
793         to.put(b);
794         toReadMode(to);
795     }
796
797     private int transfer(byte[] buf, int off, int len, ByteBuffer to) {
798         toWriteMode(to);
799         int max = Math.min(len, to.remaining());
800         if (max > 0) {
801             to.put(buf, off, max);
802         }
803         toReadMode(to);
804         return max;
805     }
806
807     private int transfer(char[] buf, int off, int len, CharBuffer to) {
808         toWriteMode(to);
809         int max = Math.min(len, to.remaining());
810         if (max > 0) {
811             to.put(buf, off, max);
812         }
813         toReadMode(to);
814         return max;
815     }
816
817     private int transfer(String s, int off, int len, CharBuffer to) {
818         toWriteMode(to);
819         int max = Math.min(len, to.remaining());
820         if (max > 0) {
821             to.put(s, off, off + max);
822         }
823         toReadMode(to);
824         return max;
825     }
826
827     private void transfer(ByteBuffer from, ByteBuffer to) {
828         toWriteMode(to);
829         int max = Math.min(from.remaining(), to.remaining());
830         if (max > 0) {
831             int fromLimit = from.limit();
832             from.limit(from.position() + max);
833             to.put(from);
834             from.limit(fromLimit);
835         }
836         toReadMode(to);
837     }
838
839     private void clear(Buffer buffer) {
840         buffer.rewind().limit(0);
841     }
842
843     private boolean isFull(Buffer buffer) {
844         return buffer.limit() == buffer.capacity();
845     }
846
847     private void toReadMode(Buffer buffer) {
848         buffer.limit(buffer.position())
849               .reset();
850     }
851
852     private void toWriteMode(Buffer buffer) {
853         buffer.mark()
854               .position(buffer.limit())
855               .limit(buffer.capacity());
856     }
857
858
859     private static class PrivilegedCreateConverter
860             implements PrivilegedExceptionAction<C2BConverter> {
861
862         private final Charset charset;
863
864         public PrivilegedCreateConverter(Charset charset) {
865             this.charset = charset;
866         }
867
868         @Override
869         public C2BConverter run() throws IOException {
870             return new C2BConverter(charset);
871         }
872     }
873 }
874