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.coyote.http11;
18
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.util.Arrays;
22
23 import org.apache.coyote.ActionCode;
24 import org.apache.coyote.CloseNowException;
25 import org.apache.coyote.Response;
26 import org.apache.tomcat.util.buf.ByteChunk;
27 import org.apache.tomcat.util.buf.MessageBytes;
28 import org.apache.tomcat.util.net.SocketWrapperBase;
29 import org.apache.tomcat.util.res.StringManager;
30
31 /**
32  * Provides buffering for the HTTP headers (allowing responses to be reset
33  * before they have been committed) and the link to the Socket for writing the
34  * headers (once committed) and the response body. Note that buffering of the
35  * response body happens at a higher level.
36  */

37 public class Http11OutputBuffer implements HttpOutputBuffer {
38
39     // -------------------------------------------------------------- Variables
40
41     /**
42      * The string manager for this package.
43      */

44     protected static final StringManager sm = StringManager.getManager(Http11OutputBuffer.class);
45
46
47     // ----------------------------------------------------- Instance Variables
48
49     /**
50      * Associated Coyote response.
51      */

52     protected final Response response;
53
54
55     /**
56      * Finished flag.
57      */

58     protected boolean responseFinished;
59
60
61     /**
62      * The buffer used for header composition.
63      */

64     protected final ByteBuffer headerBuffer;
65
66
67     /**
68      * Filter library for processing the response body.
69      */

70     protected OutputFilter[] filterLibrary;
71
72
73     /**
74      * Active filters for the current request.
75      */

76     protected OutputFilter[] activeFilters;
77
78
79     /**
80      * Index of the last active filter.
81      */

82     protected int lastActiveFilter;
83
84
85     /**
86      * Underlying output buffer.
87      */

88     protected HttpOutputBuffer outputStreamOutputBuffer;
89
90
91     /**
92      * Wrapper for socket where data will be written to.
93      */

94     protected SocketWrapperBase<?> socketWrapper;
95
96
97     /**
98      * Bytes written to client for the current request
99      */

100     protected long byteCount = 0;
101
102
103     protected Http11OutputBuffer(Response response, int headerBufferSize) {
104
105         this.response = response;
106
107         headerBuffer = ByteBuffer.allocate(headerBufferSize);
108
109         filterLibrary = new OutputFilter[0];
110         activeFilters = new OutputFilter[0];
111         lastActiveFilter = -1;
112
113         responseFinished = false;
114
115         outputStreamOutputBuffer = new SocketOutputBuffer();
116     }
117
118
119     // ------------------------------------------------------------- Properties
120
121     /**
122      * Add an output filter to the filter library. Note that calling this method
123      * resets the currently active filters to none.
124      *
125      * @param filter The filter to add
126      */

127     public void addFilter(OutputFilter filter) {
128
129         OutputFilter[] newFilterLibrary = Arrays.copyOf(filterLibrary, filterLibrary.length + 1);
130         newFilterLibrary[filterLibrary.length] = filter;
131         filterLibrary = newFilterLibrary;
132
133         activeFilters = new OutputFilter[filterLibrary.length];
134     }
135
136
137     /**
138      * Get filters.
139      *
140      * @return The current filter library containing all possible filters
141      */

142     public OutputFilter[] getFilters() {
143         return filterLibrary;
144     }
145
146
147     /**
148      * Add an output filter to the active filters for the current response.
149      * <p>
150      * The filter does not have to be present in {@link #getFilters()}.
151      * <p>
152      * A filter can only be added to a response once. If the filter has already
153      * been added to this response then this method will be a NO-OP.
154      *
155      * @param filter The filter to add
156      */

157     public void addActiveFilter(OutputFilter filter) {
158
159         if (lastActiveFilter == -1) {
160             filter.setBuffer(outputStreamOutputBuffer);
161         } else {
162             for (int i = 0; i <= lastActiveFilter; i++) {
163                 if (activeFilters[i] == filter)
164                     return;
165             }
166             filter.setBuffer(activeFilters[lastActiveFilter]);
167         }
168
169         activeFilters[++lastActiveFilter] = filter;
170
171         filter.setResponse(response);
172     }
173
174
175     // --------------------------------------------------- OutputBuffer Methods
176
177     @Override
178     public int doWrite(ByteBuffer chunk) throws IOException {
179
180         if (!response.isCommitted()) {
181             // Send the connector a request for commit. The connector should
182             // then validate the headers, send them (using sendHeaders) and
183             // set the filters accordingly.
184             response.action(ActionCode.COMMIT, null);
185         }
186
187         if (lastActiveFilter == -1) {
188             return outputStreamOutputBuffer.doWrite(chunk);
189         } else {
190             return activeFilters[lastActiveFilter].doWrite(chunk);
191         }
192     }
193
194
195     @Override
196     public long getBytesWritten() {
197         if (lastActiveFilter == -1) {
198             return outputStreamOutputBuffer.getBytesWritten();
199         } else {
200             return activeFilters[lastActiveFilter].getBytesWritten();
201         }
202     }
203
204
205     // ----------------------------------------------- HttpOutputBuffer Methods
206
207     /**
208      * Flush the response.
209      *
210      * @throws IOException an underlying I/O error occurred
211      */

212     @Override
213     public void flush() throws IOException {
214         if (lastActiveFilter == -1) {
215             outputStreamOutputBuffer.flush();
216         } else {
217             activeFilters[lastActiveFilter].flush();
218         }
219     }
220
221
222     @Override
223     public void end() throws IOException {
224         if (responseFinished) {
225             return;
226         }
227
228         if (lastActiveFilter == -1) {
229             outputStreamOutputBuffer.end();
230         } else {
231             activeFilters[lastActiveFilter].end();
232         }
233
234         responseFinished = true;
235     }
236
237
238     // --------------------------------------------------------- Public Methods
239
240     /**
241      * Reset the header buffer if an error occurs during the writing of the
242      * headers so the error response can be written.
243      */

244     void resetHeaderBuffer() {
245         headerBuffer.position(0).limit(headerBuffer.capacity());
246     }
247
248
249     /**
250      * Recycle the output buffer. This should be called when closing the
251      * connection.
252      */

253     public void recycle() {
254         nextRequest();
255         socketWrapper = null;
256     }
257
258
259     /**
260      * End processing of current HTTP request.
261      * Note: All bytes of the current request should have been already
262      * consumed. This method only resets all the pointers so that we are ready
263      * to parse the next HTTP request.
264      */

265     public void nextRequest() {
266         // Recycle filters
267         for (int i = 0; i <= lastActiveFilter; i++) {
268             activeFilters[i].recycle();
269         }
270         // Recycle response object
271         response.recycle();
272         // Reset pointers
273         headerBuffer.position(0).limit(headerBuffer.capacity());
274         lastActiveFilter = -1;
275         responseFinished = false;
276         byteCount = 0;
277     }
278
279
280     public void init(SocketWrapperBase<?> socketWrapper) {
281         this.socketWrapper = socketWrapper;
282     }
283
284
285     public void sendAck() throws IOException {
286         if (!response.isCommitted()) {
287             socketWrapper.write(isBlocking(), Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length);
288             if (flushBuffer(true)) {
289                 throw new IOException(sm.getString("iob.failedwrite.ack"));
290             }
291         }
292     }
293
294
295     /**
296      * Commit the response.
297      *
298      * @throws IOException an underlying I/O error occurred
299      */

300     protected void commit() throws IOException {
301         response.setCommitted(true);
302
303         if (headerBuffer.position() > 0) {
304             // Sending the response header buffer
305             headerBuffer.flip();
306             try {
307                 SocketWrapperBase<?> socketWrapper = this.socketWrapper;
308                 if (socketWrapper != null) {
309                     socketWrapper.write(isBlocking(), headerBuffer);
310                 } else {
311                     throw new CloseNowException(sm.getString("iob.failedwrite"));
312                 }
313             } finally {
314                 headerBuffer.position(0).limit(headerBuffer.capacity());
315             }
316         }
317     }
318
319
320     /**
321      * Send the response status line.
322      */

323     public void sendStatus() {
324         // Write protocol name
325         write(Constants.HTTP_11_BYTES);
326         headerBuffer.put(Constants.SP);
327
328         // Write status code
329         int status = response.getStatus();
330         switch (status) {
331         case 200:
332             write(Constants._200_BYTES);
333             break;
334         case 400:
335             write(Constants._400_BYTES);
336             break;
337         case 404:
338             write(Constants._404_BYTES);
339             break;
340         default:
341             write(status);
342         }
343
344         headerBuffer.put(Constants.SP);
345
346         // The reason phrase is optional but the space before it is not. Skip
347         // sending the reason phrase. Clients should ignore it (RFC 7230) and it
348         // just wastes bytes.
349
350         headerBuffer.put(Constants.CR).put(Constants.LF);
351     }
352
353
354     /**
355      * Send a header.
356      *
357      * @param name Header name
358      * @param value Header value
359      */

360     public void sendHeader(MessageBytes name, MessageBytes value) {
361         write(name);
362         headerBuffer.put(Constants.COLON).put(Constants.SP);
363         write(value);
364         headerBuffer.put(Constants.CR).put(Constants.LF);
365     }
366
367
368     /**
369      * End the header block.
370      */

371     public void endHeaders() {
372         headerBuffer.put(Constants.CR).put(Constants.LF);
373     }
374
375
376     /**
377      * This method will write the contents of the specified message bytes
378      * buffer to the output stream, without filtering. This method is meant to
379      * be used to write the response header.
380      *
381      * @param mb data to be written
382      */

383     private void write(MessageBytes mb) {
384         if (mb.getType() != MessageBytes.T_BYTES) {
385             mb.toBytes();
386             ByteChunk bc = mb.getByteChunk();
387             // Need to filter out CTLs excluding TAB. ISO-8859-1 and UTF-8
388             // values will be OK. Strings using other encodings may be
389             // corrupted.
390             byte[] buffer = bc.getBuffer();
391             for (int i = bc.getOffset(); i < bc.getLength(); i++) {
392                 // byte values are signed i.e. -128 to 127
393                 // The values are used unsigned. 0 to 31 are CTLs so they are
394                 // filtered (apart from TAB which is 9). 127 is a control (DEL).
395                 // The values 128 to 255 are all OK. Converting those to signed
396                 // gives -128 to -1.
397                 if ((buffer[i] > -1 && buffer[i] <= 31 && buffer[i] != 9) ||
398                         buffer[i] == 127) {
399                     buffer[i] = ' ';
400                 }
401             }
402         }
403         write(mb.getByteChunk());
404     }
405
406
407     /**
408      * This method will write the contents of the specified byte chunk to the
409      * output stream, without filtering. This method is meant to be used to
410      * write the response header.
411      *
412      * @param bc data to be written
413      */

414     private void write(ByteChunk bc) {
415         // Writing the byte chunk to the output buffer
416         int length = bc.getLength();
417         checkLengthBeforeWrite(length);
418         headerBuffer.put(bc.getBytes(), bc.getStart(), length);
419     }
420
421
422     /**
423      * This method will write the contents of the specified byte
424      * buffer to the output stream, without filtering. This method is meant to
425      * be used to write the response header.
426      *
427      * @param b data to be written
428      */

429     public void write(byte[] b) {
430         checkLengthBeforeWrite(b.length);
431
432         // Writing the byte chunk to the output buffer
433         headerBuffer.put(b);
434     }
435
436
437     /**
438      * This method will write the specified integer to the output stream. This
439      * method is meant to be used to write the response header.
440      *
441      * @param value data to be written
442      */

443     private void write(int value) {
444         // From the Tomcat 3.3 HTTP/1.0 connector
445         String s = Integer.toString(value);
446         int len = s.length();
447         checkLengthBeforeWrite(len);
448         for (int i = 0; i < len; i++) {
449             char c = s.charAt (i);
450             headerBuffer.put((byte) c);
451         }
452     }
453
454
455     /**
456      * Checks to see if there is enough space in the buffer to write the
457      * requested number of bytes.
458      */

459     private void checkLengthBeforeWrite(int length) {
460         // "+ 4": BZ 57509. Reserve space for CR/LF/COLON/SP characters that
461         // are put directly into the buffer following this write operation.
462         if (headerBuffer.position() + length + 4 > headerBuffer.capacity()) {
463             throw new HeadersTooLargeException(
464                     sm.getString("iob.responseheadertoolarge.error"));
465         }
466     }
467
468
469     //------------------------------------------------------ Non-blocking writes
470
471     /**
472      * Writes any remaining buffered data.
473      *
474      * @param block     Should this method block until the buffer is empty
475      * @return  <code>true</code> if data remains in the buffer (which can only
476      *          happen in non-blocking mode) else <code>false</code>.
477      * @throws IOException Error writing data
478      */

479     protected boolean flushBuffer(boolean block) throws IOException  {
480         return socketWrapper.flush(block);
481     }
482
483
484     /**
485      * Is standard Servlet blocking IO being used for output?
486      * @return <code>true</code> if this is blocking IO
487      */

488     protected final boolean isBlocking() {
489         return response.getWriteListener() == null;
490     }
491
492
493     protected final boolean isReady() {
494         boolean result = !hasDataToWrite();
495         if (!result) {
496             socketWrapper.registerWriteInterest();
497         }
498         return result;
499     }
500
501
502     public boolean hasDataToWrite() {
503         return socketWrapper.hasDataToWrite();
504     }
505
506
507     public void registerWriteInterest() {
508         socketWrapper.registerWriteInterest();
509     }
510
511
512     boolean isChunking() {
513         for (int i = 0; i < lastActiveFilter; i++) {
514             if (activeFilters[i] == filterLibrary[Constants.CHUNKED_FILTER]) {
515                 return true;
516             }
517         }
518         return false;
519     }
520
521
522     // ------------------------------------------ SocketOutputBuffer Inner Class
523
524     /**
525      * This class is an output buffer which will write data to a socket.
526      */

527     protected class SocketOutputBuffer implements HttpOutputBuffer {
528
529         /**
530          * Write chunk.
531          */

532         @Override
533         public int doWrite(ByteBuffer chunk) throws IOException {
534             try {
535                 int len = chunk.remaining();
536                 SocketWrapperBase<?> socketWrapper = Http11OutputBuffer.this.socketWrapper;
537                 if (socketWrapper != null) {
538                     socketWrapper.write(isBlocking(), chunk);
539                 } else {
540                     throw new CloseNowException(sm.getString("iob.failedwrite"));
541                 }
542                 len -= chunk.remaining();
543                 byteCount += len;
544                 return len;
545             } catch (IOException ioe) {
546                 response.action(ActionCode.CLOSE_NOW, ioe);
547                 // Re-throw
548                 throw ioe;
549             }
550         }
551
552         @Override
553         public long getBytesWritten() {
554             return byteCount;
555         }
556
557         @Override
558         public void end() throws IOException {
559             socketWrapper.flush(true);
560         }
561
562         @Override
563         public void flush() throws IOException {
564             socketWrapper.flush(isBlocking());
565         }
566     }
567 }
568