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
18 package org.apache.coyote.http11;
19
20 import java.io.ByteArrayInputStream;
21 import java.io.IOException;
22 import java.io.InterruptedIOException;
23 import java.util.StringTokenizer;
24 import java.util.regex.Pattern;
25 import java.util.regex.PatternSyntaxException;
26 import java.security.cert.CertificateFactory;
27 import java.security.cert.X509Certificate;
28
29 import org.apache.coyote.ActionCode;
30 import org.apache.coyote.ActionHook;
31 import org.apache.coyote.Adapter;
32 import org.apache.coyote.Request;
33 import org.apache.coyote.RequestInfo;
34 import org.apache.coyote.Response;
35 import org.apache.coyote.http11.filters.ChunkedInputFilter;
36 import org.apache.coyote.http11.filters.ChunkedOutputFilter;
37 import org.apache.coyote.http11.filters.GzipOutputFilter;
38 import org.apache.coyote.http11.filters.IdentityInputFilter;
39 import org.apache.coyote.http11.filters.IdentityOutputFilter;
40 import org.apache.coyote.http11.filters.SavedRequestInputFilter;
41 import org.apache.coyote.http11.filters.VoidInputFilter;
42 import org.apache.coyote.http11.filters.VoidOutputFilter;
43 import org.apache.coyote.http11.filters.BufferedInputFilter;
44 import org.apache.tomcat.jni.Address;
45 import org.apache.tomcat.jni.SSL;
46 import org.apache.tomcat.jni.SSLSocket;
47 import org.apache.tomcat.jni.Sockaddr;
48 import org.apache.tomcat.jni.Socket;
49 import org.apache.tomcat.util.buf.Ascii;
50 import org.apache.tomcat.util.buf.ByteChunk;
51 import org.apache.tomcat.util.buf.HexUtils;
52 import org.apache.tomcat.util.buf.MessageBytes;
53 import org.apache.tomcat.util.http.FastHttpDateFormat;
54 import org.apache.tomcat.util.http.MimeHeaders;
55 import org.apache.tomcat.util.net.AprEndpoint;
56 import org.apache.tomcat.util.net.SocketStatus;
57 import org.apache.tomcat.util.net.AprEndpoint.Handler.SocketState;
58 import org.apache.tomcat.util.res.StringManager;
59
60
61 /**
62 * Processes HTTP requests.
63 *
64 * @author Remy Maucherat
65 */
66 public class Http11AprProcessor implements ActionHook {
67
68
69 /**
70 * Logger.
71 */
72 protected static org.apache.juli.logging.Log log
73 = org.apache.juli.logging.LogFactory.getLog(Http11AprProcessor.class);
74
75 /**
76 * The string manager for this package.
77 */
78 protected static StringManager sm =
79 StringManager.getManager(Constants.Package);
80
81
82 // ----------------------------------------------------------- Constructors
83
84
85 public Http11AprProcessor(int headerBufferSize, AprEndpoint endpoint) {
86
87 this.endpoint = endpoint;
88
89 request = new Request();
90 inputBuffer = new InternalAprInputBuffer(request, headerBufferSize);
91 request.setInputBuffer(inputBuffer);
92
93 response = new Response();
94 response.setHook(this);
95 outputBuffer = new InternalAprOutputBuffer(response, headerBufferSize);
96 response.setOutputBuffer(outputBuffer);
97 request.setResponse(response);
98
99 ssl = endpoint.isSSLEnabled();
100
101 initializeFilters();
102
103 // Cause loading of HexUtils
104 int foo = HexUtils.DEC[0];
105
106 // Cause loading of FastHttpDateFormat
107 FastHttpDateFormat.getCurrentDate();
108
109 }
110
111
112 // ----------------------------------------------------- Instance Variables
113
114
115 /**
116 * Associated adapter.
117 */
118 protected Adapter adapter = null;
119
120
121 /**
122 * Request object.
123 */
124 protected Request request = null;
125
126
127 /**
128 * Response object.
129 */
130 protected Response response = null;
131
132
133 /**
134 * Input.
135 */
136 protected InternalAprInputBuffer inputBuffer = null;
137
138
139 /**
140 * Output.
141 */
142 protected InternalAprOutputBuffer outputBuffer = null;
143
144
145 /**
146 * Error flag.
147 */
148 protected boolean error = false;
149
150
151 /**
152 * Keep-alive.
153 */
154 protected boolean keepAlive = true;
155
156
157 /**
158 * HTTP/1.1 flag.
159 */
160 protected boolean http11 = true;
161
162
163 /**
164 * HTTP/0.9 flag.
165 */
166 protected boolean http09 = false;
167
168
169 /**
170 * Sendfile data.
171 */
172 protected AprEndpoint.SendfileData sendfileData = null;
173
174
175 /**
176 * Comet used.
177 */
178 protected boolean comet = false;
179
180
181 /**
182 * Content delimitator for the request (if false, the connection will
183 * be closed at the end of the request).
184 */
185 protected boolean contentDelimitation = true;
186
187
188 /**
189 * Is there an expectation ?
190 */
191 protected boolean expectation = false;
192
193
194 /**
195 * List of restricted user agents.
196 */
197 protected Pattern[] restrictedUserAgents = null;
198
199
200 /**
201 * Maximum number of Keep-Alive requests to honor.
202 */
203 protected int maxKeepAliveRequests = -1;
204
205
206 /**
207 * SSL enabled ?
208 */
209 protected boolean ssl = false;
210
211
212 /**
213 * Socket associated with the current connection.
214 */
215 protected long socket = 0;
216
217
218 /**
219 * Remote Address associated with the current connection.
220 */
221 protected String remoteAddr = null;
222
223
224 /**
225 * Remote Host associated with the current connection.
226 */
227 protected String remoteHost = null;
228
229
230 /**
231 * Local Host associated with the current connection.
232 */
233 protected String localName = null;
234
235
236
237 /**
238 * Local port to which the socket is connected
239 */
240 protected int localPort = -1;
241
242
243 /**
244 * Remote port to which the socket is connected
245 */
246 protected int remotePort = -1;
247
248
249 /**
250 * The local Host address.
251 */
252 protected String localAddr = null;
253
254
255 /**
256 * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server.
257 */
258 protected int timeout = 300000;
259
260
261 /**
262 * Flag to disable setting a different time-out on uploads.
263 */
264 protected boolean disableUploadTimeout = false;
265
266
267 /**
268 * Allowed compression level.
269 */
270 protected int compressionLevel = 0;
271
272
273 /**
274 * Minimum contentsize to make compression.
275 */
276 protected int compressionMinSize = 2048;
277
278
279 /**
280 * Socket buffering.
281 */
282 protected int socketBuffer = -1;
283
284
285 /**
286 * Max save post size.
287 */
288 protected int maxSavePostSize = 4 * 1024;
289
290
291 /**
292 * List of user agents to not use gzip with
293 */
294 protected Pattern noCompressionUserAgents[] = null;
295
296 /**
297 * List of MIMES which could be gzipped
298 */
299 protected String[] compressableMimeTypes =
300 { "text/html", "text/xml", "text/plain" };
301
302
303 /**
304 * Host name (used to avoid useless B2C conversion on the host name).
305 */
306 protected char[] hostNameC = new char[0];
307
308
309 /**
310 * Associated endpoint.
311 */
312 protected AprEndpoint endpoint;
313
314
315 /**
316 * Allow a customized the server header for the tin-foil hat folks.
317 */
318 protected String server = null;
319
320
321 // ------------------------------------------------------------- Properties
322
323
324 /**
325 * Return compression level.
326 */
327 public String getCompression() {
328 switch (compressionLevel) {
329 case 0:
330 return "off";
331 case 1:
332 return "on";
333 case 2:
334 return "force";
335 }
336 return "off";
337 }
338
339
340 /**
341 * Set compression level.
342 */
343 public void setCompression(String compression) {
344 if (compression.equals("on")) {
345 this.compressionLevel = 1;
346 } else if (compression.equals("force")) {
347 this.compressionLevel = 2;
348 } else if (compression.equals("off")) {
349 this.compressionLevel = 0;
350 } else {
351 try {
352 // Try to parse compression as an int, which would give the
353 // minimum compression size
354 compressionMinSize = Integer.parseInt(compression);
355 this.compressionLevel = 1;
356 } catch (Exception e) {
357 this.compressionLevel = 0;
358 }
359 }
360 }
361
362 /**
363 * Set Minimum size to trigger compression.
364 */
365 public void setCompressionMinSize(int compressionMinSize) {
366 this.compressionMinSize = compressionMinSize;
367 }
368
369
370 /**
371 * Add user-agent for which gzip compression didn't works
372 * The user agent String given will be exactly matched
373 * to the user-agent header submitted by the client.
374 *
375 * @param userAgent user-agent string
376 */
377 public void addNoCompressionUserAgent(String userAgent) {
378 try {
379 Pattern nRule = Pattern.compile(userAgent);
380 noCompressionUserAgents =
381 addREArray(noCompressionUserAgents, nRule);
382 } catch (PatternSyntaxException pse) {
383 log.error(sm.getString("http11processor.regexp.error", userAgent), pse);
384 }
385 }
386
387
388 /**
389 * Set no compression user agent list (this method is best when used with
390 * a large number of connectors, where it would be better to have all of
391 * them referenced a single array).
392 */
393 public void setNoCompressionUserAgents(Pattern[] noCompressionUserAgents) {
394 this.noCompressionUserAgents = noCompressionUserAgents;
395 }
396
397
398 /**
399 * Set no compression user agent list.
400 * List contains users agents separated by ',' :
401 *
402 * ie: "gorilla,desesplorer,tigrus"
403 */
404 public void setNoCompressionUserAgents(String noCompressionUserAgents) {
405 if (noCompressionUserAgents != null) {
406 StringTokenizer st = new StringTokenizer(noCompressionUserAgents, ",");
407
408 while (st.hasMoreTokens()) {
409 addNoCompressionUserAgent(st.nextToken().trim());
410 }
411 }
412 }
413
414 /**
415 * Add a mime-type which will be compressable
416 * The mime-type String will be exactly matched
417 * in the response mime-type header .
418 *
419 * @param mimeType mime-type string
420 */
421 public void addCompressableMimeType(String mimeType) {
422 compressableMimeTypes =
423 addStringArray(compressableMimeTypes, mimeType);
424 }
425
426
427 /**
428 * Set compressable mime-type list (this method is best when used with
429 * a large number of connectors, where it would be better to have all of
430 * them referenced a single array).
431 */
432 public void setCompressableMimeTypes(String[] compressableMimeTypes) {
433 this.compressableMimeTypes = compressableMimeTypes;
434 }
435
436
437 /**
438 * Set compressable mime-type list
439 * List contains users agents separated by ',' :
440 *
441 * ie: "text/html,text/xml,text/plain"
442 */
443 public void setCompressableMimeTypes(String compressableMimeTypes) {
444 if (compressableMimeTypes != null) {
445 StringTokenizer st = new StringTokenizer(compressableMimeTypes, ",");
446
447 while (st.hasMoreTokens()) {
448 addCompressableMimeType(st.nextToken().trim());
449 }
450 }
451 }
452
453
454 /**
455 * Return the list of restricted user agents.
456 */
457 public String[] findCompressableMimeTypes() {
458 return (compressableMimeTypes);
459 }
460
461
462
463 // --------------------------------------------------------- Public Methods
464
465
466 /**
467 * Add input or output filter.
468 *
469 * @param className class name of the filter
470 */
471 protected void addFilter(String className) {
472 try {
473 Class clazz = Class.forName(className);
474 Object obj = clazz.newInstance();
475 if (obj instanceof InputFilter) {
476 inputBuffer.addFilter((InputFilter) obj);
477 } else if (obj instanceof OutputFilter) {
478 outputBuffer.addFilter((OutputFilter) obj);
479 } else {
480 log.warn(sm.getString("http11processor.filter.unknown", className));
481 }
482 } catch (Exception e) {
483 log.error(sm.getString("http11processor.filter.error", className), e);
484 }
485 }
486
487
488 /**
489 * General use method
490 *
491 * @param sArray the StringArray
492 * @param value string
493 */
494 private String[] addStringArray(String sArray[], String value) {
495 String[] result = null;
496 if (sArray == null) {
497 result = new String[1];
498 result[0] = value;
499 }
500 else {
501 result = new String[sArray.length + 1];
502 for (int i = 0; i < sArray.length; i++)
503 result[i] = sArray[i];
504 result[sArray.length] = value;
505 }
506 return result;
507 }
508
509
510 /**
511 * General use method
512 *
513 * @param rArray the REArray
514 * @param value Obj
515 */
516 private Pattern[] addREArray(Pattern rArray[], Pattern value) {
517 Pattern[] result = null;
518 if (rArray == null) {
519 result = new Pattern[1];
520 result[0] = value;
521 }
522 else {
523 result = new Pattern[rArray.length + 1];
524 for (int i = 0; i < rArray.length; i++)
525 result[i] = rArray[i];
526 result[rArray.length] = value;
527 }
528 return result;
529 }
530
531
532 /**
533 * General use method
534 *
535 * @param sArray the StringArray
536 * @param value string
537 */
538 private boolean inStringArray(String sArray[], String value) {
539 for (int i = 0; i < sArray.length; i++) {
540 if (sArray[i].equals(value)) {
541 return true;
542 }
543 }
544 return false;
545 }
546
547
548 /**
549 * Checks if any entry in the string array starts with the specified value
550 *
551 * @param sArray the StringArray
552 * @param value string
553 */
554 private boolean startsWithStringArray(String sArray[], String value) {
555 if (value == null)
556 return false;
557 for (int i = 0; i < sArray.length; i++) {
558 if (value.startsWith(sArray[i])) {
559 return true;
560 }
561 }
562 return false;
563 }
564
565
566 /**
567 * Add restricted user-agent (which will downgrade the connector
568 * to HTTP/1.0 mode). The user agent String given will be matched
569 * via regexp to the user-agent header submitted by the client.
570 *
571 * @param userAgent user-agent string
572 */
573 public void addRestrictedUserAgent(String userAgent) {
574 try {
575 Pattern nRule = Pattern.compile(userAgent);
576 restrictedUserAgents = addREArray(restrictedUserAgents, nRule);
577 } catch (PatternSyntaxException pse) {
578 log.error(sm.getString("http11processor.regexp.error", userAgent), pse);
579 }
580 }
581
582
583 /**
584 * Set restricted user agent list (this method is best when used with
585 * a large number of connectors, where it would be better to have all of
586 * them referenced a single array).
587 */
588 public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) {
589 this.restrictedUserAgents = restrictedUserAgents;
590 }
591
592
593 /**
594 * Set restricted user agent list (which will downgrade the connector
595 * to HTTP/1.0 mode). List contains users agents separated by ',' :
596 *
597 * ie: "gorilla,desesplorer,tigrus"
598 */
599 public void setRestrictedUserAgents(String restrictedUserAgents) {
600 if (restrictedUserAgents != null) {
601 StringTokenizer st =
602 new StringTokenizer(restrictedUserAgents, ",");
603 while (st.hasMoreTokens()) {
604 addRestrictedUserAgent(st.nextToken().trim());
605 }
606 }
607 }
608
609
610 /**
611 * Return the list of restricted user agents.
612 */
613 public String[] findRestrictedUserAgents() {
614 String[] sarr = new String [restrictedUserAgents.length];
615
616 for (int i = 0; i < restrictedUserAgents.length; i++)
617 sarr[i] = restrictedUserAgents[i].toString();
618
619 return (sarr);
620 }
621
622
623 /**
624 * Set the maximum number of Keep-Alive requests to honor.
625 * This is to safeguard from DoS attacks. Setting to a negative
626 * value disables the check.
627 */
628 public void setMaxKeepAliveRequests(int mkar) {
629 maxKeepAliveRequests = mkar;
630 }
631
632
633 /**
634 * Return the number of Keep-Alive requests that we will honor.
635 */
636 public int getMaxKeepAliveRequests() {
637 return maxKeepAliveRequests;
638 }
639
640
641 /**
642 * Set the maximum size of a POST which will be buffered in SSL mode.
643 */
644 public void setMaxSavePostSize(int msps) {
645 maxSavePostSize = msps;
646 }
647
648
649 /**
650 * Return the maximum size of a POST which will be buffered in SSL mode.
651 */
652 public int getMaxSavePostSize() {
653 return maxSavePostSize;
654 }
655
656
657 /**
658 * Set the flag to control upload time-outs.
659 */
660 public void setDisableUploadTimeout(boolean isDisabled) {
661 disableUploadTimeout = isDisabled;
662 }
663
664 /**
665 * Get the flag that controls upload time-outs.
666 */
667 public boolean getDisableUploadTimeout() {
668 return disableUploadTimeout;
669 }
670
671 /**
672 * Set the socket buffer flag.
673 */
674 public void setSocketBuffer(int socketBuffer) {
675 this.socketBuffer = socketBuffer;
676 outputBuffer.setSocketBuffer(socketBuffer);
677 }
678
679 /**
680 * Get the socket buffer flag.
681 */
682 public int getSocketBuffer() {
683 return socketBuffer;
684 }
685
686 /**
687 * Set the upload timeout.
688 */
689 public void setTimeout( int timeouts ) {
690 timeout = timeouts ;
691 }
692
693 /**
694 * Get the upload timeout.
695 */
696 public int getTimeout() {
697 return timeout;
698 }
699
700 /**
701 * Set the server header name.
702 */
703 public void setServer( String server ) {
704 if (server==null || server.equals("")) {
705 this.server = null;
706 } else {
707 this.server = server;
708 }
709 }
710
711 /**
712 * Get the server header name.
713 */
714 public String getServer() {
715 return server;
716 }
717
718
719 /** Get the request associated with this processor.
720 *
721 * @return The request
722 */
723 public Request getRequest() {
724 return request;
725 }
726
727 /**
728 * Process pipelined HTTP requests using the specified input and output
729 * streams.
730 *
731 * @throws IOException error during an I/O operation
732 */
733 public SocketState event(SocketStatus status)
734 throws IOException {
735
736 RequestInfo rp = request.getRequestProcessor();
737
738 try {
739 rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
740 error = !adapter.event(request, response, status);
741 } catch (InterruptedIOException e) {
742 error = true;
743 } catch (Throwable t) {
744 log.error(sm.getString("http11processor.request.process"), t);
745 // 500 - Internal Server Error
746 response.setStatus(500);
747 error = true;
748 }
749
750 rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
751
752 if (error) {
753 inputBuffer.nextRequest();
754 outputBuffer.nextRequest();
755 recycle();
756 return SocketState.CLOSED;
757 } else if (!comet) {
758 inputBuffer.nextRequest();
759 outputBuffer.nextRequest();
760 recycle();
761 return SocketState.OPEN;
762 } else {
763 return SocketState.LONG;
764 }
765 }
766
767 /**
768 * Process pipelined HTTP requests using the specified input and output
769 * streams.
770 *
771 * @throws IOException error during an I/O operation
772 */
773 public SocketState process(long socket)
774 throws IOException {
775 RequestInfo rp = request.getRequestProcessor();
776 rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
777
778 // Set the remote address
779 remoteAddr = null;
780 remoteHost = null;
781 localAddr = null;
782 localName = null;
783 remotePort = -1;
784 localPort = -1;
785
786 // Setting up the socket
787 this.socket = socket;
788 inputBuffer.setSocket(socket);
789 outputBuffer.setSocket(socket);
790
791 // Error flag
792 error = false;
793 comet = false;
794 keepAlive = true;
795
796 int keepAliveLeft = maxKeepAliveRequests;
797 long soTimeout = endpoint.getSoTimeout();
798
799 boolean keptAlive = false;
800 boolean openSocket = false;
801
802 while (!error && keepAlive && !comet) {
803
804 // Parsing the request header
805 try {
806 if( !disableUploadTimeout && keptAlive && soTimeout > 0 ) {
807 Socket.timeoutSet(socket, soTimeout * 1000);
808 }
809 if (!inputBuffer.parseRequestLine(keptAlive)) {
810 // This means that no data is available right now
811 // (long keepalive), so that the processor should be recycled
812 // and the method should return true
813 openSocket = true;
814 // Add the socket to the poller
815 endpoint.getPoller().add(socket);
816 break;
817 }
818 request.setStartTime(System.currentTimeMillis());
819 keptAlive = true;
820 if (!disableUploadTimeout) {
821 Socket.timeoutSet(socket, timeout * 1000);
822 }
823 inputBuffer.parseHeaders();
824 } catch (IOException e) {
825 error = true;
826 break;
827 } catch (Throwable t) {
828 if (log.isDebugEnabled()) {
829 log.debug(sm.getString("http11processor.header.parse"), t);
830 }
831 // 400 - Bad Request
832 response.setStatus(400);
833 error = true;
834 }
835
836 // Setting up filters, and parse some request headers
837 rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
838 try {
839 prepareRequest();
840 } catch (Throwable t) {
841 if (log.isDebugEnabled()) {
842 log.debug(sm.getString("http11processor.request.prepare"), t);
843 }
844 // 400 - Internal Server Error
845 response.setStatus(400);
846 error = true;
847 }
848
849 if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)
850 keepAlive = false;
851
852 // Process the request in the adapter
853 if (!error) {
854 try {
855 rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
856 adapter.service(request, response);
857 // Handle when the response was committed before a serious
858 // error occurred. Throwing a ServletException should both
859 // set the status to 500 and set the errorException.
860 // If we fail here, then the response is likely already
861 // committed, so we can't try and set headers.
862 if(keepAlive && !error) { // Avoid checking twice.
863 error = response.getErrorException() != null ||
864 statusDropsConnection(response.getStatus());
865 }
866 } catch (InterruptedIOException e) {
867 error = true;
868 } catch (Throwable t) {
869 log.error(sm.getString("http11processor.request.process"), t);
870 // 500 - Internal Server Error
871 response.setStatus(500);
872 error = true;
873 }
874 }
875
876 // Finish the handling of the request
877 if (!comet) {
878 endRequest();
879 }
880
881 // If there was an error, make sure the request is counted as
882 // and error, and update the statistics counter
883 if (error) {
884 response.setStatus(500);
885 }
886 request.updateCounters();
887
888 if (!comet) {
889 // Next request
890 inputBuffer.nextRequest();
891 outputBuffer.nextRequest();
892 }
893
894 // Do sendfile as needed: add socket to sendfile and end
895 if (sendfileData != null && !error) {
896 sendfileData.socket = socket;
897 sendfileData.keepAlive = keepAlive;
898 if (!endpoint.getSendfile().add(sendfileData)) {
899 openSocket = true;
900 break;
901 }
902 }
903
904 rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
905
906 }
907
908 rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
909
910 if (comet) {
911 if (error) {
912 inputBuffer.nextRequest();
913 outputBuffer.nextRequest();
914 recycle();
915 return SocketState.CLOSED;
916 } else {
917 return SocketState.LONG;
918 }
919 } else {
920 recycle();
921 return (openSocket) ? SocketState.OPEN : SocketState.CLOSED;
922 }
923
924 }
925
926
927 public void endRequest() {
928
929 // Finish the handling of the request
930 try {
931 inputBuffer.endRequest();
932 } catch (IOException e) {
933 error = true;
934 } catch (Throwable t) {
935 log.error(sm.getString("http11processor.request.finish"), t);
936 // 500 - Internal Server Error
937 response.setStatus(500);
938 error = true;
939 }
940 try {
941 outputBuffer.endRequest();
942 } catch (IOException e) {
943 error = true;
944 } catch (Throwable t) {
945 log.error(sm.getString("http11processor.response.finish"), t);
946 error = true;
947 }
948
949 }
950
951
952 public void recycle() {
953 inputBuffer.recycle();
954 outputBuffer.recycle();
955 this.socket = 0;
956 }
957
958
959 // ----------------------------------------------------- ActionHook Methods
960
961
962 /**
963 * Send an action to the connector.
964 *
965 * @param actionCode Type of the action
966 * @param param Action parameter
967 */
968 public void action(ActionCode actionCode, Object param) {
969
970 if (actionCode == ActionCode.ACTION_COMMIT) {
971 // Commit current response
972
973 if (response.isCommitted())
974 return;
975
976 // Validate and write response headers
977 prepareResponse();
978 try {
979 outputBuffer.commit();
980 } catch (IOException e) {
981 // Set error flag
982 error = true;
983 }
984
985 } else if (actionCode == ActionCode.ACTION_ACK) {
986
987 // Acknowlege request
988
989 // Send a 100 status back if it makes sense (response not committed
990 // yet, and client specified an expectation for 100-continue)
991
992 if ((response.isCommitted()) || !expectation)
993 return;
994
995 inputBuffer.setSwallowInput(true);
996 try {
997 outputBuffer.sendAck();
998 } catch (IOException e) {
999 // Set error flag
1000 error = true;
1001 }
1002
1003 } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
1004
1005 try {
1006 outputBuffer.flush();
1007 } catch (IOException e) {
1008 // Set error flag
1009 error = true;
1010 response.setErrorException(e);
1011 }
1012
1013 } else if (actionCode == ActionCode.ACTION_CLOSE) {
1014 // Close
1015
1016 // End the processing of the current request, and stop any further
1017 // transactions with the client
1018
1019 comet = false;
1020 try {
1021 outputBuffer.endRequest();
1022 } catch (IOException e) {
1023 // Set error flag
1024 error = true;
1025 }
1026
1027 } else if (actionCode == ActionCode.ACTION_RESET) {
1028
1029 // Reset response
1030
1031 // Note: This must be called before the response is committed
1032
1033 outputBuffer.reset();
1034
1035 } else if (actionCode == ActionCode.ACTION_CUSTOM) {
1036
1037 // Do nothing
1038
1039 } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {
1040
1041 // Get remote host address
1042 if (remoteAddr == null && (socket != 0)) {
1043 try {
1044 long sa = Address.get(Socket.APR_REMOTE, socket);
1045 remoteAddr = Address.getip(sa);
1046 } catch (Exception e) {
1047 log.warn(sm.getString("http11processor.socket.info"), e);
1048 }
1049 }
1050 request.remoteAddr().setString(remoteAddr);
1051
1052 } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {
1053
1054 // Get local host name
1055 if (localName == null && (socket != 0)) {
1056 try {
1057 long sa = Address.get(Socket.APR_LOCAL, socket);
1058 localName = Address.getnameinfo(sa, 0);
1059 } catch (Exception e) {
1060 log.warn(sm.getString("http11processor.socket.info"), e);
1061 }
1062 }
1063 request.localName().setString(localName);
1064
1065 } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
1066
1067 // Get remote host name
1068 if (remoteHost == null && (socket != 0)) {
1069 try {
1070 long sa = Address.get(Socket.APR_REMOTE, socket);
1071 remoteHost = Address.getnameinfo(sa, 0);
1072 } catch (Exception e) {
1073 log.warn(sm.getString("http11processor.socket.info"), e);
1074 }
1075 }
1076 request.remoteHost().setString(remoteHost);
1077
1078 } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
1079
1080 // Get local host address
1081 if (localAddr == null && (socket != 0)) {
1082 try {
1083 long sa = Address.get(Socket.APR_LOCAL, socket);
1084 localAddr = Address.getip(sa);
1085 } catch (Exception e) {
1086 log.warn(sm.getString("http11processor.socket.info"), e);
1087 }
1088 }
1089
1090 request.localAddr().setString(localAddr);
1091
1092 } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {
1093
1094 // Get remote port
1095 if (remotePort == -1 && (socket != 0)) {
1096 try {
1097 long sa = Address.get(Socket.APR_REMOTE, socket);
1098 Sockaddr addr = Address.getInfo(sa);
1099 remotePort = addr.port;
1100 } catch (Exception e) {
1101 log.warn(sm.getString("http11processor.socket.info"), e);
1102 }
1103 }
1104 request.setRemotePort(remotePort);
1105
1106 } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {
1107
1108 // Get local port
1109 if (localPort == -1 && (socket != 0)) {
1110 try {
1111 long sa = Address.get(Socket.APR_LOCAL, socket);
1112 Sockaddr addr = Address.getInfo(sa);
1113 localPort = addr.port;
1114 } catch (Exception e) {
1115 log.warn(sm.getString("http11processor.socket.info"), e);
1116 }
1117 }
1118 request.setLocalPort(localPort);
1119
1120 } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
1121
1122 if (ssl && (socket != 0)) {
1123 try {
1124 // Cipher suite
1125 Object sslO = SSLSocket.getInfoS(socket, SSL.SSL_INFO_CIPHER);
1126 if (sslO != null) {
1127 request.setAttribute(AprEndpoint.CIPHER_SUITE_KEY, sslO);
1128 }
1129 // Get client certificate and the certificate chain if present
1130 int certLength = SSLSocket.getInfoI(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN);
1131 byte[] clientCert = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT);
1132 X509Certificate[] certs = null;
1133 if (clientCert != null) {
1134 certs = new X509Certificate[certLength + 1];
1135 CertificateFactory cf = CertificateFactory.getInstance("X.509");
1136 certs[0] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(clientCert));
1137 for (int i = 0; i < certLength; i++) {
1138 byte[] data = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);
1139 certs[i+1] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data));
1140 }
1141 }
1142 if (certs != null) {
1143 request.setAttribute(AprEndpoint.CERTIFICATE_KEY, certs);
1144 }
1145 // User key size
1146 sslO = new Integer(SSLSocket.getInfoI(socket, SSL.SSL_INFO_CIPHER_USEKEYSIZE));
1147 if (sslO != null) {
1148 request.setAttribute(AprEndpoint.KEY_SIZE_KEY, sslO);
1149 }
1150 // SSL session ID
1151 sslO = SSLSocket.getInfoS(socket, SSL.SSL_INFO_SESSION_ID);
1152 if (sslO != null) {
1153 request.setAttribute(AprEndpoint.SESSION_ID_KEY, sslO);
1154 }
1155 } catch (Exception e) {
1156 log.warn(sm.getString("http11processor.socket.ssl"), e);
1157 }
1158 }
1159
1160 } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {
1161
1162 if (ssl && (socket != 0)) {
1163 // Consume and buffer the request body, so that it does not
1164 // interfere with the client's handshake messages
1165 InputFilter[] inputFilters = inputBuffer.getFilters();
1166 ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER]).setLimit(maxSavePostSize);
1167 inputBuffer.addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]);
1168 try {
1169 // Renegociate certificates
1170 SSLSocket.renegotiate(socket);
1171 // Get client certificate and the certificate chain if present
1172 int certLength = SSLSocket.getInfoI(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN);
1173 byte[] clientCert = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT);
1174 X509Certificate[] certs = null;
1175 if (clientCert != null) {
1176 certs = new X509Certificate[certLength + 1];
1177 CertificateFactory cf = CertificateFactory.getInstance("X.509");
1178 certs[0] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(clientCert));
1179 for (int i = 0; i < certLength; i++) {
1180 byte[] data = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);
1181 certs[i+1] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data));
1182 }
1183 }
1184 if (certs != null) {
1185 request.setAttribute(AprEndpoint.CERTIFICATE_KEY, certs);
1186 }
1187 } catch (Exception e) {
1188 log.warn(sm.getString("http11processor.socket.ssl"), e);
1189 }
1190 }
1191
1192 } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
1193 ByteChunk body = (ByteChunk) param;
1194
1195 InputFilter savedBody = new SavedRequestInputFilter(body);
1196 savedBody.setRequest(request);
1197
1198 InternalAprInputBuffer internalBuffer = (InternalAprInputBuffer)
1199 request.getInputBuffer();
1200 internalBuffer.addActiveFilter(savedBody);
1201
1202 } else if (actionCode == ActionCode.ACTION_AVAILABLE) {
1203 request.setAvailable(inputBuffer.available());
1204 } else if (actionCode == ActionCode.ACTION_COMET_BEGIN) {
1205 comet = true;
1206 } else if (actionCode == ActionCode.ACTION_COMET_END) {
1207 comet = false;
1208 }
1209
1210 }
1211
1212
1213 // ------------------------------------------------------ Connector Methods
1214
1215
1216 /**
1217 * Set the associated adapter.
1218 *
1219 * @param adapter the new adapter
1220 */
1221 public void setAdapter(Adapter adapter) {
1222 this.adapter = adapter;
1223 }
1224
1225
1226 /**
1227 * Get the associated adapter.
1228 *
1229 * @return the associated adapter
1230 */
1231 public Adapter getAdapter() {
1232 return adapter;
1233 }
1234
1235
1236 // ------------------------------------------------------ Protected Methods
1237
1238
1239 /**
1240 * After reading the request headers, we have to setup the request filters.
1241 */
1242 protected void prepareRequest() {
1243
1244 http11 = true;
1245 http09 = false;
1246 contentDelimitation = false;
1247 expectation = false;
1248 sendfileData = null;
1249 if (ssl) {
1250 request.scheme().setString("https");
1251 }
1252 MessageBytes protocolMB = request.protocol();
1253 if (protocolMB.equals(Constants.HTTP_11)) {
1254 http11 = true;
1255 protocolMB.setString(Constants.HTTP_11);
1256 } else if (protocolMB.equals(Constants.HTTP_10)) {
1257 http11 = false;
1258 keepAlive = false;
1259 protocolMB.setString(Constants.HTTP_10);
1260 } else if (protocolMB.equals("")) {
1261 // HTTP/0.9
1262 http09 = true;
1263 http11 = false;
1264 keepAlive = false;
1265 } else {
1266 // Unsupported protocol
1267 http11 = false;
1268 error = true;
1269 // Send 505; Unsupported HTTP version
1270 response.setStatus(505);
1271 }
1272
1273 MessageBytes methodMB = request.method();
1274 if (methodMB.equals(Constants.GET)) {
1275 methodMB.setString(Constants.GET);
1276 } else if (methodMB.equals(Constants.POST)) {
1277 methodMB.setString(Constants.POST);
1278 }
1279
1280 MimeHeaders headers = request.getMimeHeaders();
1281
1282 // Check connection header
1283 MessageBytes connectionValueMB = headers.getValue("connection");
1284 if (connectionValueMB != null) {
1285 ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
1286 if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
1287 keepAlive = false;
1288 } else if (findBytes(connectionValueBC,
1289 Constants.KEEPALIVE_BYTES) != -1) {
1290 keepAlive = true;
1291 }
1292 }
1293
1294 MessageBytes expectMB = null;
1295 if (http11)
1296 expectMB = headers.getValue("expect");
1297 if ((expectMB != null)
1298 && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
1299 inputBuffer.setSwallowInput(false);
1300 expectation = true;
1301 }
1302
1303 // Check user-agent header
1304 if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
1305 MessageBytes userAgentValueMB = headers.getValue("user-agent");
1306 // Check in the restricted list, and adjust the http11
1307 // and keepAlive flags accordingly
1308 if(userAgentValueMB != null) {
1309 String userAgentValue = userAgentValueMB.toString();
1310 for (int i = 0; i < restrictedUserAgents.length; i++) {
1311 if (restrictedUserAgents[i].matcher(userAgentValue).matches()) {
1312 http11 = false;
1313 keepAlive = false;
1314 break;
1315 }
1316 }
1317 }
1318 }
1319
1320 // Check for a full URI (including protocol://host:port/)
1321 ByteChunk uriBC = request.requestURI().getByteChunk();
1322 if (uriBC.startsWithIgnoreCase("http", 0)) {
1323
1324 int pos = uriBC.indexOf("://", 0, 3, 4);
1325 int uriBCStart = uriBC.getStart();
1326 int slashPos = -1;
1327 if (pos != -1) {
1328 byte[] uriB = uriBC.getBytes();
1329 slashPos = uriBC.indexOf('/', pos + 3);
1330 if (slashPos == -1) {
1331 slashPos = uriBC.getLength();
1332 // Set URI as "/"
1333 request.requestURI().setBytes
1334 (uriB, uriBCStart + pos + 1, 1);
1335 } else {
1336 request.requestURI().setBytes
1337 (uriB, uriBCStart + slashPos,
1338 uriBC.getLength() - slashPos);
1339 }
1340 MessageBytes hostMB = headers.setValue("host");
1341 hostMB.setBytes(uriB, uriBCStart + pos + 3,
1342 slashPos - pos - 3);
1343 }
1344
1345 }
1346
1347 // Input filter setup
1348 InputFilter[] inputFilters = inputBuffer.getFilters();
1349
1350 // Parse transfer-encoding header
1351 MessageBytes transferEncodingValueMB = null;
1352 if (http11)
1353 transferEncodingValueMB = headers.getValue("transfer-encoding");
1354 if (transferEncodingValueMB != null) {
1355 String transferEncodingValue = transferEncodingValueMB.toString();
1356 // Parse the comma separated list. "identity" codings are ignored
1357 int startPos = 0;
1358 int commaPos = transferEncodingValue.indexOf(',');
1359 String encodingName = null;
1360 while (commaPos != -1) {
1361 encodingName = transferEncodingValue.substring
1362 (startPos, commaPos).toLowerCase().trim();
1363 if (!addInputFilter(inputFilters, encodingName)) {
1364 // Unsupported transfer encoding
1365 error = true;
1366 // 501 - Unimplemented
1367 response.setStatus(501);
1368 }
1369 startPos = commaPos + 1;
1370 commaPos = transferEncodingValue.indexOf(',', startPos);
1371 }
1372 encodingName = transferEncodingValue.substring(startPos)
1373 .toLowerCase().trim();
1374 if (!addInputFilter(inputFilters, encodingName)) {
1375 // Unsupported transfer encoding
1376 error = true;
1377 // 501 - Unimplemented
1378 response.setStatus(501);
1379 }
1380 }
1381
1382 // Parse content-length header
1383 long contentLength = request.getContentLengthLong();
1384 if (contentLength >= 0 && !contentDelimitation) {
1385 inputBuffer.addActiveFilter
1386 (inputFilters[Constants.IDENTITY_FILTER]);
1387 contentDelimitation = true;
1388 }
1389
1390 MessageBytes valueMB = headers.getValue("host");
1391
1392 // Check host header
1393 if (http11 && (valueMB == null)) {
1394 error = true;
1395 // 400 - Bad request
1396 response.setStatus(400);
1397 }
1398
1399 parseHost(valueMB);
1400
1401 if (!contentDelimitation) {
1402 // If there's no content length
1403 // (broken HTTP/1.0 or HTTP/1.1), assume
1404 // the client is not broken and didn't send a body
1405 inputBuffer.addActiveFilter
1406 (inputFilters[Constants.VOID_FILTER]);
1407 contentDelimitation = true;
1408 }
1409
1410 // Advertise sendfile support through a request attribute
1411 if (endpoint.getUseSendfile()) {
1412 request.setAttribute("org.apache.tomcat.sendfile.support", Boolean.TRUE);
1413 }
1414 // Advertise comet support through a request attribute
1415 request.setAttribute("org.apache.tomcat.comet.support", Boolean.TRUE);
1416
1417 }
1418
1419
1420 /**
1421 * Parse host.
1422 */
1423 public void parseHost(MessageBytes valueMB) {
1424
1425 if (valueMB == null || valueMB.isNull()) {
1426 // HTTP/1.0
1427 // Default is what the socket tells us. Overriden if a host is
1428 // found/parsed
1429 request.setServerPort(endpoint.getPort());
1430 return;
1431 }
1432
1433 ByteChunk valueBC = valueMB.getByteChunk();
1434 byte[] valueB = valueBC.getBytes();
1435 int valueL = valueBC.getLength();
1436 int valueS = valueBC.getStart();
1437 int colonPos = -1;
1438 if (hostNameC.length < valueL) {
1439 hostNameC = new char[valueL];
1440 }
1441
1442 boolean ipv6 = (valueB[valueS] == '[');
1443 boolean bracketClosed = false;
1444 for (int i = 0; i < valueL; i++) {
1445 char b = (char) valueB[i + valueS];
1446 hostNameC[i] = b;
1447 if (b == ']') {
1448 bracketClosed = true;
1449 } else if (b == ':') {
1450 if (!ipv6 || bracketClosed) {
1451 colonPos = i;
1452 break;
1453 }
1454 }
1455 }
1456
1457 if (colonPos < 0) {
1458 if (!ssl) {
1459 // 80 - Default HTTP port
1460 request.setServerPort(80);
1461 } else {
1462 // 443 - Default HTTPS port
1463 request.setServerPort(443);
1464 }
1465 request.serverName().setChars(hostNameC, 0, valueL);
1466 } else {
1467
1468 request.serverName().setChars(hostNameC, 0, colonPos);
1469
1470 int port = 0;
1471 int mult = 1;
1472 for (int i = valueL - 1; i > colonPos; i--) {
1473 int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
1474 if (charValue == -1) {
1475 // Invalid character
1476 error = true;
1477 // 400 - Bad request
1478 response.setStatus(400);
1479 break;
1480 }
1481 port = port + (charValue * mult);
1482 mult = 10 * mult;
1483 }
1484 request.setServerPort(port);
1485
1486 }
1487
1488 }
1489
1490
1491 /**
1492 * Check for compression
1493 */
1494 private boolean isCompressable() {
1495
1496 // Nope Compression could works in HTTP 1.0 also
1497 // cf: mod_deflate
1498
1499 // Compression only since HTTP 1.1
1500 // if (! http11)
1501 // return false;
1502
1503 // Check if browser support gzip encoding
1504 MessageBytes acceptEncodingMB =
1505 request.getMimeHeaders().getValue("accept-encoding");
1506
1507 if ((acceptEncodingMB == null)
1508 || (acceptEncodingMB.indexOf("gzip") == -1))
1509 return false;
1510
1511 // Check if content is not allready gzipped
1512 MessageBytes contentEncodingMB =
1513 response.getMimeHeaders().getValue("Content-Encoding");
1514
1515 if ((contentEncodingMB != null)
1516 && (contentEncodingMB.indexOf("gzip") != -1))
1517 return false;
1518
1519 // If force mode, allways compress (test purposes only)
1520 if (compressionLevel == 2)
1521 return true;
1522
1523 // Check for incompatible Browser
1524 if (noCompressionUserAgents != null) {
1525 MessageBytes userAgentValueMB =
1526 request.getMimeHeaders().getValue("user-agent");
1527 if(userAgentValueMB != null) {
1528 String userAgentValue = userAgentValueMB.toString();
1529
1530 // If one Regexp rule match, disable compression
1531 for (int i = 0; i < noCompressionUserAgents.length; i++)
1532 if (noCompressionUserAgents[i].matcher(userAgentValue).matches())
1533 return false;
1534 }
1535 }
1536
1537 // Check if suffisant len to trig the compression
1538 long contentLength = response.getContentLengthLong();
1539 if ((contentLength == -1)
1540 || (contentLength > compressionMinSize)) {
1541 // Check for compatible MIME-TYPE
1542 if (compressableMimeTypes != null) {
1543 return (startsWithStringArray(compressableMimeTypes,
1544 response.getContentType()));
1545 }
1546 }
1547
1548 return false;
1549 }
1550
1551
1552 /**
1553 * When committing the response, we have to validate the set of headers, as
1554 * well as setup the response filters.
1555 */
1556 protected void prepareResponse() {
1557
1558 boolean entityBody = true;
1559 contentDelimitation = false;
1560
1561 OutputFilter[] outputFilters = outputBuffer.getFilters();
1562
1563 if (http09 == true) {
1564 // HTTP/0.9
1565 outputBuffer.addActiveFilter
1566 (outputFilters[Constants.IDENTITY_FILTER]);
1567 return;
1568 }
1569
1570 int statusCode = response.getStatus();
1571 if ((statusCode == 204) || (statusCode == 205)
1572 || (statusCode == 304)) {
1573 // No entity body
1574 outputBuffer.addActiveFilter
1575 (outputFilters[Constants.VOID_FILTER]);
1576 entityBody = false;
1577 contentDelimitation = true;
1578 }
1579
1580 MessageBytes methodMB = request.method();
1581 if (methodMB.equals("HEAD")) {
1582 // No entity body
1583 outputBuffer.addActiveFilter
1584 (outputFilters[Constants.VOID_FILTER]);
1585 contentDelimitation = true;
1586 }
1587
1588 // Sendfile support
1589 if (endpoint.getUseSendfile()) {
1590 String fileName = (String) request.getAttribute("org.apache.tomcat.sendfile.filename");
1591 if (fileName != null) {
1592 // No entity body sent here
1593 outputBuffer.addActiveFilter
1594 (outputFilters[Constants.VOID_FILTER]);
1595 contentDelimitation = true;
1596 sendfileData = new AprEndpoint.SendfileData();
1597 sendfileData.fileName = fileName;
1598 sendfileData.start =
1599 ((Long) request.getAttribute("org.apache.tomcat.sendfile.start")).longValue();
1600 sendfileData.end =
1601 ((Long) request.getAttribute("org.apache.tomcat.sendfile.end")).longValue();
1602 }
1603 }
1604
1605 // Check for compression
1606 boolean useCompression = false;
1607 if (entityBody && (compressionLevel > 0) && (sendfileData == null)) {
1608 useCompression = isCompressable();
1609 // Change content-length to -1 to force chunking
1610 if (useCompression) {
1611 response.setContentLength(-1);
1612 }
1613 }
1614
1615 MimeHeaders headers = response.getMimeHeaders();
1616 if (!entityBody) {
1617 response.setContentLength(-1);
1618 } else {
1619 String contentType = response.getContentType();
1620 if (contentType != null) {
1621 headers.setValue("Content-Type").setString(contentType);
1622 }
1623 String contentLanguage = response.getContentLanguage();
1624 if (contentLanguage != null) {
1625 headers.setValue("Content-Language")
1626 .setString(contentLanguage);
1627 }
1628 }
1629
1630 long contentLength = response.getContentLengthLong();
1631 if (contentLength != -1) {
1632 headers.setValue("Content-Length").setLong(contentLength);
1633 outputBuffer.addActiveFilter
1634 (outputFilters[Constants.IDENTITY_FILTER]);
1635 contentDelimitation = true;
1636 } else {
1637 if (entityBody && http11 && keepAlive) {
1638 outputBuffer.addActiveFilter
1639 (outputFilters[Constants.CHUNKED_FILTER]);
1640 contentDelimitation = true;
1641 headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
1642 } else {
1643 outputBuffer.addActiveFilter
1644 (outputFilters[Constants.IDENTITY_FILTER]);
1645 }
1646 }
1647
1648 if (useCompression) {
1649 outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
1650 headers.setValue("Content-Encoding").setString("gzip");
1651 // Make Proxies happy via Vary (from mod_deflate)
1652 headers.setValue("Vary").setString("Accept-Encoding");
1653 }
1654
1655 // Add date header
1656 headers.setValue("Date").setString(FastHttpDateFormat.getCurrentDate());
1657
1658 // FIXME: Add transfer encoding header
1659
1660 if ((entityBody) && (!contentDelimitation)) {
1661 // Mark as close the connection after the request, and add the
1662 // connection: close header
1663 keepAlive = false;
1664 }
1665
1666 // If we know that the request is bad this early, add the
1667 // Connection: close header.
1668 keepAlive = keepAlive && !statusDropsConnection(statusCode);
1669 if (!keepAlive) {
1670 headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
1671 } else if (!http11 && !error) {
1672 headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);
1673 }
1674
1675 // Build the response header
1676 outputBuffer.sendStatus();
1677
1678 // Add server header
1679 if (server != null) {
1680 headers.setValue("Server").setString(server);
1681 } else {
1682 outputBuffer.write(Constants.SERVER_BYTES);
1683 }
1684
1685 int size = headers.size();
1686 for (int i = 0; i < size; i++) {
1687 outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
1688 }
1689 outputBuffer.endHeaders();
1690
1691 }
1692
1693
1694 /**
1695 * Initialize standard input and output filters.
1696 */
1697 protected void initializeFilters() {
1698
1699 // Create and add the identity filters.
1700 inputBuffer.addFilter(new IdentityInputFilter());
1701 outputBuffer.addFilter(new IdentityOutputFilter());
1702
1703 // Create and add the chunked filters.
1704 inputBuffer.addFilter(new ChunkedInputFilter());
1705 outputBuffer.addFilter(new ChunkedOutputFilter());
1706
1707 // Create and add the void filters.
1708 inputBuffer.addFilter(new VoidInputFilter());
1709 outputBuffer.addFilter(new VoidOutputFilter());
1710
1711 // Create and add buffered input filter
1712 inputBuffer.addFilter(new BufferedInputFilter());
1713
1714 // Create and add the chunked filters.
1715 //inputBuffer.addFilter(new GzipInputFilter());
1716 outputBuffer.addFilter(new GzipOutputFilter());
1717
1718 }
1719
1720
1721 /**
1722 * Add an input filter to the current request.
1723 *
1724 * @return false if the encoding was not found (which would mean it is
1725 * unsupported)
1726 */
1727 protected boolean addInputFilter(InputFilter[] inputFilters,
1728 String encodingName) {
1729 if (encodingName.equals("identity")) {
1730 // Skip
1731 } else if (encodingName.equals("chunked")) {
1732 inputBuffer.addActiveFilter
1733 (inputFilters[Constants.CHUNKED_FILTER]);
1734 contentDelimitation = true;
1735 } else {
1736 for (int i = 2; i < inputFilters.length; i++) {
1737 if (inputFilters[i].getEncodingName()
1738 .toString().equals(encodingName)) {
1739 inputBuffer.addActiveFilter(inputFilters[i]);
1740 return true;
1741 }
1742 }
1743 return false;
1744 }
1745 return true;
1746 }
1747
1748
1749 /**
1750 * Specialized utility method: find a sequence of lower case bytes inside
1751 * a ByteChunk.
1752 */
1753 protected int findBytes(ByteChunk bc, byte[] b) {
1754
1755 byte first = b[0];
1756 byte[] buff = bc.getBuffer();
1757 int start = bc.getStart();
1758 int end = bc.getEnd();
1759
1760 // Look for first char
1761 int srcEnd = b.length;
1762
1763 for (int i = start; i <= (end - srcEnd); i++) {
1764 if (Ascii.toLower(buff[i]) != first) continue;
1765 // found first char, now look for a match
1766 int myPos = i+1;
1767 for (int srcPos = 1; srcPos < srcEnd; ) {
1768 if (Ascii.toLower(buff[myPos++]) != b[srcPos++])
1769 break;
1770 if (srcPos == srcEnd) return i - start; // found it
1771 }
1772 }
1773 return -1;
1774
1775 }
1776
1777 /**
1778 * Determine if we must drop the connection because of the HTTP status
1779 * code. Use the same list of codes as Apache/httpd.
1780 */
1781 protected boolean statusDropsConnection(int status) {
1782 return status == 400 /* SC_BAD_REQUEST */ ||
1783 status == 408 /* SC_REQUEST_TIMEOUT */ ||
1784 status == 411 /* SC_LENGTH_REQUIRED */ ||
1785 status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||
1786 status == 414 /* SC_REQUEST_URI_TOO_LARGE */ ||
1787 status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||
1788 status == 503 /* SC_SERVICE_UNAVAILABLE */ ||
1789 status == 501 /* SC_NOT_IMPLEMENTED */;
1790 }
1791
1792 }