Source code: org/apache/coyote/http11/Http11Processor.java
1 /*
2 * Copyright 1999-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.coyote.http11;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.InterruptedIOException;
22 import java.io.OutputStream;
23 import java.net.InetAddress;
24 import java.net.Socket;
25 import java.util.StringTokenizer;
26 import java.util.regex.Pattern;
27 import java.util.regex.PatternSyntaxException;
28 import java.security.AccessController;
29 import java.security.PrivilegedAction;
30
31 import org.apache.coyote.ActionCode;
32 import org.apache.coyote.ActionHook;
33 import org.apache.coyote.Adapter;
34 import org.apache.coyote.Processor;
35 import org.apache.coyote.Request;
36 import org.apache.coyote.RequestInfo;
37 import org.apache.coyote.Response;
38 import org.apache.coyote.http11.filters.ChunkedInputFilter;
39 import org.apache.coyote.http11.filters.ChunkedOutputFilter;
40 import org.apache.coyote.http11.filters.GzipOutputFilter;
41 import org.apache.coyote.http11.filters.IdentityInputFilter;
42 import org.apache.coyote.http11.filters.IdentityOutputFilter;
43 import org.apache.coyote.http11.filters.SavedRequestInputFilter;
44 import org.apache.coyote.http11.filters.VoidInputFilter;
45 import org.apache.coyote.http11.filters.VoidOutputFilter;
46 import org.apache.coyote.http11.filters.BufferedInputFilter;
47 import org.apache.tomcat.util.buf.Ascii;
48 import org.apache.tomcat.util.buf.ByteChunk;
49 import org.apache.tomcat.util.buf.HexUtils;
50 import org.apache.tomcat.util.buf.MessageBytes;
51 import org.apache.tomcat.util.http.FastHttpDateFormat;
52 import org.apache.tomcat.util.http.MimeHeaders;
53 import org.apache.tomcat.util.net.PoolTcpEndpoint;
54 import org.apache.tomcat.util.net.SSLSupport;
55 import org.apache.tomcat.util.res.StringManager;
56 import org.apache.tomcat.util.threads.ThreadPool;
57 import org.apache.tomcat.util.threads.ThreadWithAttributes;
58
59
60 /**
61 * Processes HTTP requests.
62 *
63 * @author Remy Maucherat
64 */
65 public class Http11Processor implements Processor, ActionHook {
66
67
68 /**
69 * Logger.
70 */
71 protected static org.apache.commons.logging.Log log
72 = org.apache.commons.logging.LogFactory.getLog(Http11Processor.class);
73
74 /**
75 * The string manager for this package.
76 */
77 protected static StringManager sm =
78 StringManager.getManager(Constants.Package);
79
80
81 // ----------------------------------------------------------- Constructors
82
83
84 /**
85 * Default constructor.
86 */
87 public Http11Processor() {
88 this(Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);
89 }
90
91
92 public Http11Processor(int headerBufferSize) {
93
94 request = new Request();
95 inputBuffer = new InternalInputBuffer(request, headerBufferSize);
96 request.setInputBuffer(inputBuffer);
97
98 response = new Response();
99 response.setHook(this);
100 outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
101 response.setOutputBuffer(outputBuffer);
102 request.setResponse(response);
103
104 initializeFilters();
105
106 // Cause loading of HexUtils
107 int foo = HexUtils.DEC[0];
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 InternalInputBuffer inputBuffer = null;
137
138
139 /**
140 * Output.
141 */
142 protected InternalOutputBuffer outputBuffer = null;
143
144
145 /**
146 * State flag.
147 */
148 protected boolean started = false;
149
150
151 /**
152 * Error flag.
153 */
154 protected boolean error = false;
155
156
157 /**
158 * Keep-alive.
159 */
160 protected boolean keepAlive = true;
161
162
163 /**
164 * HTTP/1.1 flag.
165 */
166 protected boolean http11 = true;
167
168
169 /**
170 * HTTP/0.9 flag.
171 */
172 protected boolean http09 = false;
173
174
175 /**
176 * Content delimitator for the request (if false, the connection will
177 * be closed at the end of the request).
178 */
179 protected boolean contentDelimitation = true;
180
181
182 /**
183 * Is there an expectation ?
184 */
185 protected boolean expectation = false;
186
187
188 /**
189 * List of restricted user agents.
190 */
191 protected Pattern[] restrictedUserAgents = null;
192
193
194 /**
195 * Maximum number of Keep-Alive requests to honor.
196 */
197 protected int maxKeepAliveRequests = -1;
198
199
200 /**
201 * SSL information.
202 */
203 protected SSLSupport sslSupport;
204
205
206 /**
207 * Socket associated with the current connection.
208 */
209 protected Socket socket;
210
211
212 /**
213 * Remote Address associated with the current connection.
214 */
215 protected String remoteAddr = null;
216
217
218 /**
219 * Remote Host associated with the current connection.
220 */
221 protected String remoteHost = null;
222
223
224 /**
225 * Local Host associated with the current connection.
226 */
227 protected String localName = null;
228
229
230
231 /**
232 * Local port to which the socket is connected
233 */
234 protected int localPort = -1;
235
236
237 /**
238 * Remote port to which the socket is connected
239 */
240 protected int remotePort = -1;
241
242
243 /**
244 * The local Host address.
245 */
246 protected String localAddr = null;
247
248
249 /**
250 * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server.
251 */
252 protected int timeout = 300000;
253
254
255 /**
256 * Flag to disable setting a different time-out on uploads.
257 */
258 protected boolean disableUploadTimeout = false;
259
260
261 /**
262 * Allowed compression level.
263 */
264 protected int compressionLevel = 0;
265
266
267 /**
268 * Minimum contentsize to make compression.
269 */
270 protected int compressionMinSize = 2048;
271
272
273 /**
274 * Socket buffering.
275 */
276 protected int socketBuffer = -1;
277
278
279 /**
280 * Max saved post size.
281 */
282 protected int maxSavePostSize = 4 * 1024;
283
284
285 /**
286 * List of user agents to not use gzip with
287 */
288 protected Pattern noCompressionUserAgents[] = null;
289
290 /**
291 * List of MIMES which could be gzipped
292 */
293 protected String[] compressableMimeTypes =
294 { "text/html", "text/xml", "text/plain" };
295
296
297 /**
298 * Host name (used to avoid useless B2C conversion on the host name).
299 */
300 protected char[] hostNameC = new char[0];
301
302
303 /**
304 * Associated thread pool.
305 */
306 protected ThreadPool threadPool;
307
308
309 /**
310 * Associated endpoint.
311 */
312 protected PoolTcpEndpoint 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 public void setThreadPool(ThreadPool threadPool) {
371 this.threadPool = threadPool;
372 }
373
374
375 public void setEndpoint(PoolTcpEndpoint endpoint) {
376 this.endpoint = endpoint;
377 }
378
379
380 /**
381 * Add user-agent for which gzip compression didn't works
382 * The user agent String given will be exactly matched
383 * to the user-agent header submitted by the client.
384 *
385 * @param userAgent user-agent string
386 */
387 public void addNoCompressionUserAgent(String userAgent) {
388 try {
389 Pattern nRule = Pattern.compile(userAgent);
390 noCompressionUserAgents =
391 addREArray(noCompressionUserAgents, nRule);
392 } catch (PatternSyntaxException pse) {
393 log.error(sm.getString("http11processor.regexp.error", userAgent), pse);
394 }
395 }
396
397
398 /**
399 * Set no compression user agent list (this method is best when used with
400 * a large number of connectors, where it would be better to have all of
401 * them referenced a single array).
402 */
403 public void setNoCompressionUserAgents(Pattern[] noCompressionUserAgents) {
404 this.noCompressionUserAgents = noCompressionUserAgents;
405 }
406
407
408 /**
409 * Set no compression user agent list.
410 * List contains users agents separated by ',' :
411 *
412 * ie: "gorilla,desesplorer,tigrus"
413 */
414 public void setNoCompressionUserAgents(String noCompressionUserAgents) {
415 if (noCompressionUserAgents != null) {
416 StringTokenizer st = new StringTokenizer(noCompressionUserAgents, ",");
417
418 while (st.hasMoreTokens()) {
419 addNoCompressionUserAgent(st.nextToken().trim());
420 }
421 }
422 }
423
424 /**
425 * Add a mime-type which will be compressable
426 * The mime-type String will be exactly matched
427 * in the response mime-type header .
428 *
429 * @param mimeType mime-type string
430 */
431 public void addCompressableMimeType(String mimeType) {
432 compressableMimeTypes =
433 addStringArray(compressableMimeTypes, mimeType);
434 }
435
436
437 /**
438 * Set compressable mime-type list (this method is best when used with
439 * a large number of connectors, where it would be better to have all of
440 * them referenced a single array).
441 */
442 public void setCompressableMimeTypes(String[] compressableMimeTypes) {
443 this.compressableMimeTypes = compressableMimeTypes;
444 }
445
446
447 /**
448 * Set compressable mime-type list
449 * List contains users agents separated by ',' :
450 *
451 * ie: "text/html,text/xml,text/plain"
452 */
453 public void setCompressableMimeTypes(String compressableMimeTypes) {
454 if (compressableMimeTypes != null) {
455 StringTokenizer st = new StringTokenizer(compressableMimeTypes, ",");
456
457 while (st.hasMoreTokens()) {
458 addCompressableMimeType(st.nextToken().trim());
459 }
460 }
461 }
462
463
464 /**
465 * Return the list of restricted user agents.
466 */
467 public String[] findCompressableMimeTypes() {
468 return (compressableMimeTypes);
469 }
470
471
472
473 // --------------------------------------------------------- Public Methods
474
475
476 /**
477 * Add input or output filter.
478 *
479 * @param className class name of the filter
480 */
481 protected void addFilter(String className) {
482 try {
483 Class clazz = Class.forName(className);
484 Object obj = clazz.newInstance();
485 if (obj instanceof InputFilter) {
486 inputBuffer.addFilter((InputFilter) obj);
487 } else if (obj instanceof OutputFilter) {
488 outputBuffer.addFilter((OutputFilter) obj);
489 } else {
490 log.warn(sm.getString("http11processor.filter.unknown", className));
491 }
492 } catch (Exception e) {
493 log.error(sm.getString("http11processor.filter.error", className), e);
494 }
495 }
496
497
498 /**
499 * General use method
500 *
501 * @param sArray the StringArray
502 * @param value string
503 */
504 private String[] addStringArray(String sArray[], String value) {
505 String[] result = null;
506 if (sArray == null) {
507 result = new String[1];
508 result[0] = value;
509 }
510 else {
511 result = new String[sArray.length + 1];
512 for (int i = 0; i < sArray.length; i++)
513 result[i] = sArray[i];
514 result[sArray.length] = value;
515 }
516 return result;
517 }
518
519
520 /**
521 * General use method
522 *
523 * @param rArray the REArray
524 * @param value Obj
525 */
526 private Pattern[] addREArray(Pattern rArray[], Pattern value) {
527 Pattern[] result = null;
528 if (rArray == null) {
529 result = new Pattern[1];
530 result[0] = value;
531 }
532 else {
533 result = new Pattern[rArray.length + 1];
534 for (int i = 0; i < rArray.length; i++)
535 result[i] = rArray[i];
536 result[rArray.length] = value;
537 }
538 return result;
539 }
540
541
542 /**
543 * General use method
544 *
545 * @param sArray the StringArray
546 * @param value string
547 */
548 private boolean inStringArray(String sArray[], String value) {
549 for (int i = 0; i < sArray.length; i++) {
550 if (sArray[i].equals(value)) {
551 return true;
552 }
553 }
554 return false;
555 }
556
557
558 /**
559 * Checks if any entry in the string array starts with the specified value
560 *
561 * @param sArray the StringArray
562 * @param value string
563 */
564 private boolean startsWithStringArray(String sArray[], String value) {
565 if (value == null)
566 return false;
567 for (int i = 0; i < sArray.length; i++) {
568 if (value.startsWith(sArray[i])) {
569 return true;
570 }
571 }
572 return false;
573 }
574
575
576 /**
577 * Add restricted user-agent (which will downgrade the connector
578 * to HTTP/1.0 mode). The user agent String given will be matched
579 * via regexp to the user-agent header submitted by the client.
580 *
581 * @param userAgent user-agent string
582 */
583 public void addRestrictedUserAgent(String userAgent) {
584 try {
585 Pattern nRule = Pattern.compile(userAgent);
586 restrictedUserAgents = addREArray(restrictedUserAgents, nRule);
587 } catch (PatternSyntaxException pse) {
588 log.error(sm.getString("http11processor.regexp.error", userAgent), pse);
589 }
590 }
591
592
593 /**
594 * Set restricted user agent list (this method is best when used with
595 * a large number of connectors, where it would be better to have all of
596 * them referenced a single array).
597 */
598 public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) {
599 this.restrictedUserAgents = restrictedUserAgents;
600 }
601
602
603 /**
604 * Set restricted user agent list (which will downgrade the connector
605 * to HTTP/1.0 mode). List contains users agents separated by ',' :
606 *
607 * ie: "gorilla,desesplorer,tigrus"
608 */
609 public void setRestrictedUserAgents(String restrictedUserAgents) {
610 if (restrictedUserAgents != null) {
611 StringTokenizer st =
612 new StringTokenizer(restrictedUserAgents, ",");
613 while (st.hasMoreTokens()) {
614 addRestrictedUserAgent(st.nextToken().trim());
615 }
616 }
617 }
618
619
620 /**
621 * Return the list of restricted user agents.
622 */
623 public String[] findRestrictedUserAgents() {
624 String[] sarr = new String [restrictedUserAgents.length];
625
626 for (int i = 0; i < restrictedUserAgents.length; i++)
627 sarr[i] = restrictedUserAgents[i].toString();
628
629 return (sarr);
630 }
631
632
633 /**
634 * Set the maximum number of Keep-Alive requests to honor.
635 * This is to safeguard from DoS attacks. Setting to a negative
636 * value disables the check.
637 */
638 public void setMaxKeepAliveRequests(int mkar) {
639 maxKeepAliveRequests = mkar;
640 }
641
642
643 /**
644 * Return the number of Keep-Alive requests that we will honor.
645 */
646 public int getMaxKeepAliveRequests() {
647 return maxKeepAliveRequests;
648 }
649
650
651 /**
652 * Set the maximum size of a POST which will be buffered in SSL mode.
653 */
654 public void setMaxSavePostSize(int msps) {
655 maxSavePostSize = msps;
656 }
657
658
659 /**
660 * Return the maximum size of a POST which will be buffered in SSL mode.
661 */
662 public int getMaxSavePostSize() {
663 return maxSavePostSize;
664 }
665
666
667 /**
668 * Set the SSL information for this HTTP connection.
669 */
670 public void setSSLSupport(SSLSupport sslSupport) {
671 this.sslSupport = sslSupport;
672 }
673
674
675 /**
676 * Set the socket associated with this HTTP connection.
677 */
678 public void setSocket(Socket socket)
679 throws IOException {
680 this.socket = socket;
681 }
682
683 /**
684 * Set the flag to control upload time-outs.
685 */
686 public void setDisableUploadTimeout(boolean isDisabled) {
687 disableUploadTimeout = isDisabled;
688 }
689
690 /**
691 * Get the flag that controls upload time-outs.
692 */
693 public boolean getDisableUploadTimeout() {
694 return disableUploadTimeout;
695 }
696
697 /**
698 * Set the socket buffer flag.
699 */
700 public void setSocketBuffer(int socketBuffer) {
701 this.socketBuffer = socketBuffer;
702 outputBuffer.setSocketBuffer(socketBuffer);
703 }
704
705 /**
706 * Get the socket buffer flag.
707 */
708 public int getSocketBuffer() {
709 return socketBuffer;
710 }
711
712 /**
713 * Set the upload timeout.
714 */
715 public void setTimeout( int timeouts ) {
716 timeout = timeouts ;
717 }
718
719 /**
720 * Get the upload timeout.
721 */
722 public int getTimeout() {
723 return timeout;
724 }
725
726
727 /**
728 * Set the server header name.
729 */
730 public void setServer( String server ) {
731 if (server==null || server.equals("")) {
732 this.server = null;
733 } else {
734 this.server = server;
735 }
736 }
737
738 /**
739 * Get the server header name.
740 */
741 public String getServer() {
742 return server;
743 }
744
745
746 /** Get the request associated with this processor.
747 *
748 * @return The request
749 */
750 public Request getRequest() {
751 return request;
752 }
753
754 /**
755 * Process pipelined HTTP requests using the specified input and output
756 * streams.
757 *
758 * @param input stream from which the HTTP requests will be read
759 * @param output stream which will be used to output the HTTP
760 * responses
761 * @throws IOException error during an I/O operation
762 */
763 public void process(InputStream input, OutputStream output)
764 throws IOException {
765 ThreadWithAttributes thrA=
766 (ThreadWithAttributes)Thread.currentThread();
767 RequestInfo rp = request.getRequestProcessor();
768 thrA.setCurrentStage(threadPool, "parsing http request");
769 rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
770
771 // Set the remote address
772 remoteAddr = null;
773 remoteHost = null;
774 localAddr = null;
775 localName = null;
776 remotePort = -1;
777 localPort = -1;
778
779 // Setting up the I/O
780 inputBuffer.setInputStream(input);
781 outputBuffer.setOutputStream(output);
782
783 // Error flag
784 error = false;
785 keepAlive = true;
786
787 int keepAliveLeft = maxKeepAliveRequests;
788 int soTimeout = socket.getSoTimeout();
789 int oldSoTimeout = soTimeout;
790
791 int threadRatio = 0;
792 if (threadPool.getCurrentThreadsBusy() > 0) {
793 threadRatio = (threadPool.getCurrentThreadsBusy() * 100)
794 / threadPool.getMaxThreads();
795 } else {
796 threadRatio = (endpoint.getCurrentThreadsBusy() * 100)
797 / endpoint.getMaxThreads();
798 }
799 if ((threadRatio > 33) && (threadRatio <= 66)) {
800 soTimeout = soTimeout / 2;
801 } else if ((threadRatio > 66) && (threadRatio <= 90)) {
802 soTimeout = soTimeout / 3;
803 keepAliveLeft = 1;
804 } else if (threadRatio > 90) {
805 soTimeout = soTimeout / 20;
806 keepAliveLeft = 1;
807 }
808
809 if (soTimeout != oldSoTimeout) {
810 try {
811 socket.setSoTimeout(soTimeout);
812 } catch (Throwable t) {
813 log.debug(sm.getString("http11processor.socket.timeout"), t);
814 error = true;
815 }
816 }
817
818 boolean keptAlive = false;
819
820 while (started && !error && keepAlive) {
821
822 // Parsing the request header
823 try {
824 if( !disableUploadTimeout && keptAlive && soTimeout > 0 ) {
825 socket.setSoTimeout(soTimeout);
826 }
827 inputBuffer.parseRequestLine();
828 request.setStartTime(System.currentTimeMillis());
829 thrA.setParam( threadPool, request.requestURI() );
830 keptAlive = true;
831 if (!disableUploadTimeout) {
832 socket.setSoTimeout(timeout);
833 }
834 inputBuffer.parseHeaders();
835 } catch (IOException e) {
836 error = true;
837 break;
838 } catch (Throwable t) {
839 if (log.isDebugEnabled()) {
840 log.debug(sm.getString("http11processor.header.parse"), t);
841 }
842 // 400 - Bad Request
843 response.setStatus(400);
844 error = true;
845 }
846
847 // Setting up filters, and parse some request headers
848 thrA.setCurrentStage(threadPool, "prepareRequest");
849 rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
850 try {
851 prepareRequest();
852 } catch (Throwable t) {
853 if (log.isDebugEnabled()) {
854 log.debug(sm.getString("http11processor.request.prepare"), t);
855 }
856 // 400 - Internal Server Error
857 response.setStatus(400);
858 error = true;
859 }
860
861 if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)
862 keepAlive = false;
863
864 // Process the request in the adapter
865 if (!error) {
866 try {
867 thrA.setCurrentStage(threadPool, "service");
868 rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
869 adapter.service(request, response);
870 // Handle when the response was committed before a serious
871 // error occurred. Throwing a ServletException should both
872 // set the status to 500 and set the errorException.
873 // If we fail here, then the response is likely already
874 // committed, so we can't try and set headers.
875 if(keepAlive && !error) { // Avoid checking twice.
876 error = response.getErrorException() != null ||
877 statusDropsConnection(response.getStatus());
878 }
879
880 } catch (InterruptedIOException e) {
881 error = true;
882 } catch (Throwable t) {
883 log.error(sm.getString("http11processor.request.process"), t);
884 // 500 - Internal Server Error
885 response.setStatus(500);
886 error = true;
887 }
888 }
889
890 // Finish the handling of the request
891 try {
892 thrA.setCurrentStage(threadPool, "endRequestIB");
893 rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
894 inputBuffer.endRequest();
895 } catch (IOException e) {
896 error = true;
897 } catch (Throwable t) {
898 log.error(sm.getString("http11processor.request.finish"), t);
899 // 500 - Internal Server Error
900 response.setStatus(500);
901 error = true;
902 }
903 try {
904 thrA.setCurrentStage(threadPool, "endRequestOB");
905 rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
906 outputBuffer.endRequest();
907 } catch (IOException e) {
908 error = true;
909 } catch (Throwable t) {
910 log.error(sm.getString("http11processor.response.finish"), t);
911 error = true;
912 }
913
914 // If there was an error, make sure the request is counted as
915 // and error, and update the statistics counter
916 if (error) {
917 response.setStatus(500);
918 }
919 request.updateCounters();
920
921 thrA.setCurrentStage(threadPool, "ended");
922 rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
923
924 // Don't reset the param - we'll see it as ended. Next request
925 // will reset it
926 // thrA.setParam(null);
927 // Next request
928 inputBuffer.nextRequest();
929 outputBuffer.nextRequest();
930
931 }
932
933 rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
934
935 // Recycle
936 inputBuffer.recycle();
937 outputBuffer.recycle();
938
939 // Recycle ssl info
940 sslSupport = null;
941 }
942
943
944 // ----------------------------------------------------- ActionHook Methods
945
946
947 /**
948 * Send an action to the connector.
949 *
950 * @param actionCode Type of the action
951 * @param param Action parameter
952 */
953 public void action(ActionCode actionCode, Object param) {
954
955 if (actionCode == ActionCode.ACTION_COMMIT) {
956 // Commit current response
957
958 if (response.isCommitted())
959 return;
960
961 // Validate and write response headers
962 prepareResponse();
963 try {
964 outputBuffer.commit();
965 } catch (IOException e) {
966 // Set error flag
967 error = true;
968 }
969
970 } else if (actionCode == ActionCode.ACTION_ACK) {
971
972 // Acknowlege request
973
974 // Send a 100 status back if it makes sense (response not committed
975 // yet, and client specified an expectation for 100-continue)
976
977 if ((response.isCommitted()) || !expectation)
978 return;
979
980 inputBuffer.setSwallowInput(true);
981 try {
982 outputBuffer.sendAck();
983 } catch (IOException e) {
984 // Set error flag
985 error = true;
986 }
987
988 } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
989
990 try {
991 outputBuffer.flush();
992 } catch (IOException e) {
993 // Set error flag
994 error = true;
995 response.setErrorException(e);
996 }
997
998 } else if (actionCode == ActionCode.ACTION_CLOSE) {
999 // Close
1000
1001 // End the processing of the current request, and stop any further
1002 // transactions with the client
1003
1004 try {
1005 outputBuffer.endRequest();
1006 } catch (IOException e) {
1007 // Set error flag
1008 error = true;
1009 }
1010
1011 } else if (actionCode == ActionCode.ACTION_RESET) {
1012
1013 // Reset response
1014
1015 // Note: This must be called before the response is committed
1016
1017 outputBuffer.reset();
1018
1019 } else if (actionCode == ActionCode.ACTION_CUSTOM) {
1020
1021 // Do nothing
1022
1023 } else if (actionCode == ActionCode.ACTION_START) {
1024
1025 started = true;
1026
1027 } else if (actionCode == ActionCode.ACTION_STOP) {
1028
1029 started = false;
1030
1031 } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
1032
1033 try {
1034 if (sslSupport != null) {
1035 Object sslO = sslSupport.getCipherSuite();
1036 if (sslO != null)
1037 request.setAttribute
1038 (SSLSupport.CIPHER_SUITE_KEY, sslO);
1039 sslO = sslSupport.getPeerCertificateChain(false);
1040 if (sslO != null)
1041 request.setAttribute
1042 (SSLSupport.CERTIFICATE_KEY, sslO);
1043 sslO = sslSupport.getKeySize();
1044 if (sslO != null)
1045 request.setAttribute
1046 (SSLSupport.KEY_SIZE_KEY, sslO);
1047 sslO = sslSupport.getSessionId();
1048 if (sslO != null)
1049 request.setAttribute
1050 (SSLSupport.SESSION_ID_KEY, sslO);
1051 }
1052 } catch (Exception e) {
1053 log.warn(sm.getString("http11processor.socket.ssl"), e);
1054 }
1055
1056 } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {
1057
1058 if ((remoteAddr == null) && (socket != null)) {
1059 InetAddress inetAddr = socket.getInetAddress();
1060 if (inetAddr != null) {
1061 remoteAddr = inetAddr.getHostAddress();
1062 }
1063 }
1064 request.remoteAddr().setString(remoteAddr);
1065
1066 } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {
1067
1068 if ((localName == null) && (socket != null)) {
1069 InetAddress inetAddr = socket.getLocalAddress();
1070 if (inetAddr != null) {
1071 localName = inetAddr.getHostName();
1072 }
1073 }
1074 request.localName().setString(localName);
1075
1076 } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
1077
1078 if ((remoteHost == null) && (socket != null)) {
1079 InetAddress inetAddr = socket.getInetAddress();
1080 if (inetAddr != null) {
1081 remoteHost = inetAddr.getHostName();
1082 }
1083 }
1084 request.remoteHost().setString(remoteHost);
1085
1086 } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
1087
1088 if (localAddr == null)
1089 localAddr = socket.getLocalAddress().getHostAddress();
1090
1091 request.localAddr().setString(localAddr);
1092
1093 } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {
1094
1095 if ((remotePort == -1 ) && (socket !=null)) {
1096 remotePort = socket.getPort();
1097 }
1098 request.setRemotePort(remotePort);
1099
1100 } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {
1101
1102 if ((localPort == -1 ) && (socket !=null)) {
1103 localPort = socket.getLocalPort();
1104 }
1105 request.setLocalPort(localPort);
1106
1107 } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {
1108 if( sslSupport != null) {
1109 /*
1110 * Consume and buffer the request body, so that it does not
1111 * interfere with the client's handshake messages
1112 */
1113 InputFilter[] inputFilters = inputBuffer.getFilters();
1114 ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
1115 .setLimit(maxSavePostSize);
1116 inputBuffer.addActiveFilter
1117 (inputFilters[Constants.BUFFERED_FILTER]);
1118 try {
1119 Object sslO = sslSupport.getPeerCertificateChain(true);
1120 if( sslO != null) {
1121 request.setAttribute
1122 (SSLSupport.CERTIFICATE_KEY, sslO);
1123 }
1124 } catch (Exception e) {
1125 log.warn(sm.getString("http11processor.socket.ssl"), e);
1126 }
1127 }
1128 } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
1129 ByteChunk body = (ByteChunk) param;
1130
1131 InputFilter savedBody = new SavedRequestInputFilter(body);
1132 savedBody.setRequest(request);
1133
1134 InternalInputBuffer internalBuffer = (InternalInputBuffer)
1135 request.getInputBuffer();
1136 internalBuffer.addActiveFilter(savedBody);
1137 }
1138
1139 }
1140
1141
1142 // ------------------------------------------------------ Connector Methods
1143
1144
1145 /**
1146 * Set the associated adapter.
1147 *
1148 * @param adapter the new adapter
1149 */
1150 public void setAdapter(Adapter adapter) {
1151 this.adapter = adapter;
1152 }
1153
1154
1155 /**
1156 * Get the associated adapter.
1157 *
1158 * @return the associated adapter
1159 */
1160 public Adapter getAdapter() {
1161 return adapter;
1162 }
1163
1164
1165 // ------------------------------------------------------ Protected Methods
1166
1167
1168 /**
1169 * After reading the request headers, we have to setup the request filters.
1170 */
1171 protected void prepareRequest() {
1172
1173 http11 = true;
1174 http09 = false;
1175 contentDelimitation = false;
1176 expectation = false;
1177 if (sslSupport != null) {
1178 request.scheme().setString("https");
1179 }
1180 MessageBytes protocolMB = request.protocol();
1181 if (protocolMB.equals(Constants.HTTP_11)) {
1182 http11 = true;
1183 protocolMB.setString(Constants.HTTP_11);
1184 } else if (protocolMB.equals(Constants.HTTP_10)) {
1185 http11 = false;
1186 keepAlive = false;
1187 protocolMB.setString(Constants.HTTP_10);
1188 } else if (protocolMB.equals("")) {
1189 // HTTP/0.9
1190 http09 = true;
1191 http11 = false;
1192 keepAlive = false;
1193 } else {
1194 // Unsupported protocol
1195 http11 = false;
1196 error = true;
1197 // Send 505; Unsupported HTTP version
1198 response.setStatus(505);
1199 }
1200
1201 MessageBytes methodMB = request.method();
1202 if (methodMB.equals(Constants.GET)) {
1203 methodMB.setString(Constants.GET);
1204 } else if (methodMB.equals(Constants.POST)) {
1205 methodMB.setString(Constants.POST);
1206 }
1207
1208 MimeHeaders headers = request.getMimeHeaders();
1209
1210 // Check connection header
1211 MessageBytes connectionValueMB = headers.getValue("connection");
1212 if (connectionValueMB != null) {
1213 ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
1214 if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
1215 keepAlive = false;
1216 } else if (findBytes(connectionValueBC,
1217 Constants.KEEPALIVE_BYTES) != -1) {
1218 keepAlive = true;
1219 }
1220 }
1221
1222 MessageBytes expectMB = null;
1223 if (http11)
1224 expectMB = headers.getValue("expect");
1225 if ((expectMB != null)
1226 && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
1227 inputBuffer.setSwallowInput(false);
1228 expectation = true;
1229 }
1230
1231 // Check user-agent header
1232 if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
1233 MessageBytes userAgentValueMB = headers.getValue("user-agent");
1234 // Check in the restricted list, and adjust the http11
1235 // and keepAlive flags accordingly
1236 if(userAgentValueMB != null) {
1237 String userAgentValue = userAgentValueMB.toString();
1238 for (int i = 0; i < restrictedUserAgents.length; i++) {
1239 if (restrictedUserAgents[i].matcher(userAgentValue).matches()) {
1240 http11 = false;
1241 keepAlive = false;
1242 break;
1243 }
1244 }
1245 }
1246 }
1247
1248 // Check for a full URI (including protocol://host:port/)
1249 ByteChunk uriBC = request.requestURI().getByteChunk();
1250 if (uriBC.startsWithIgnoreCase("http", 0)) {
1251
1252 int pos = uriBC.indexOf("://", 0, 3, 4);
1253 int uriBCStart = uriBC.getStart();
1254 int slashPos = -1;
1255 if (pos != -1) {
1256 byte[] uriB = uriBC.getBytes();
1257 slashPos = uriBC.indexOf('/', pos + 3);
1258 if (slashPos == -1) {
1259 slashPos = uriBC.getLength();
1260 // Set URI as "/"
1261 request.requestURI().setBytes
1262 (uriB, uriBCStart + pos + 1, 1);
1263 } else {
1264 request.requestURI().setBytes
1265 (uriB, uriBCStart + slashPos,
1266 uriBC.getLength() - slashPos);
1267 }
1268 MessageBytes hostMB = headers.setValue("host");
1269 hostMB.setBytes(uriB, uriBCStart + pos + 3,
1270 slashPos - pos - 3);
1271 }
1272
1273 }
1274
1275 // Input filter setup
1276 InputFilter[] inputFilters = inputBuffer.getFilters();
1277
1278 // Parse transfer-encoding header
1279 MessageBytes transferEncodingValueMB = null;
1280 if (http11)
1281 transferEncodingValueMB = headers.getValue("transfer-encoding");
1282 if (transferEncodingValueMB != null) {
1283 String transferEncodingValue = transferEncodingValueMB.toString();
1284 // Parse the comma separated list. "identity" codings are ignored
1285 int startPos = 0;
1286 int commaPos = transferEncodingValue.indexOf(',');
1287 String encodingName = null;
1288 while (commaPos != -1) {
1289 encodingName = transferEncodingValue.substring
1290 (startPos, commaPos).toLowerCase().trim();
1291 if (!addInputFilter(inputFilters, encodingName)) {
1292 // Unsupported transfer encoding
1293 error = true;
1294 // 501 - Unimplemented
1295 response.setStatus(501);
1296 }
1297 startPos = commaPos + 1;
1298 commaPos = transferEncodingValue.indexOf(',', startPos);
1299 }
1300 encodingName = transferEncodingValue.substring(startPos)
1301 .toLowerCase().trim();
1302 if (!addInputFilter(inputFilters, encodingName)) {
1303 // Unsupported transfer encoding
1304 error = true;
1305 // 501 - Unimplemented
1306 response.setStatus(501);
1307 }
1308 }
1309
1310 // Parse content-length header
1311 long contentLength = request.getContentLengthLong();
1312 if (contentLength >= 0 && !contentDelimitation) {
1313 inputBuffer.addActiveFilter
1314 (inputFilters[Constants.IDENTITY_FILTER]);
1315 contentDelimitation = true;
1316 }
1317
1318 MessageBytes valueMB = headers.getValue("host");
1319
1320 // Check host header
1321 if (http11 && (valueMB == null)) {
1322 error = true;
1323 // 400 - Bad request
1324 response.setStatus(400);
1325 }
1326
1327 parseHost(valueMB);
1328
1329 if (!contentDelimitation) {
1330 // If there's no content length and we're using keep-alive
1331 // (HTTP/1.0 with keep-alive or HTTP/1.1), assume
1332 // the client is not broken and didn't send a body
1333 if (keepAlive) {
1334 inputBuffer.addActiveFilter
1335 (inputFilters[Constants.VOID_FILTER]);
1336 contentDelimitation = true;
1337 }
1338 }
1339
1340 if (!contentDelimitation)
1341 keepAlive = false;
1342
1343 }
1344
1345
1346 /**
1347 * Parse host.
1348 */
1349 public void parseHost(MessageBytes valueMB) {
1350
1351 if (valueMB == null || valueMB.isNull()) {
1352 // HTTP/1.0
1353 // Default is what the socket tells us. Overriden if a host is
1354 // found/parsed
1355 request.setServerPort(socket.getLocalPort());
1356 InetAddress localAddress = socket.getLocalAddress();
1357 // Setting the socket-related fields. The adapter doesn't know
1358 // about socket.
1359 request.serverName().setString(localAddress.getHostName());
1360 return;
1361 }
1362
1363 ByteChunk valueBC = valueMB.getByteChunk();
1364 byte[] valueB = valueBC.getBytes();
1365 int valueL = valueBC.getLength();
1366 int valueS = valueBC.getStart();
1367 int colonPos = -1;
1368 if (hostNameC.length < valueL) {
1369 hostNameC = new char[valueL];
1370 }
1371
1372 boolean ipv6 = (valueB[valueS] == '[');
1373 boolean bracketClosed = false;
1374 for (int i = 0; i < valueL; i++) {
1375 char b = (char) valueB[i + valueS];
1376 hostNameC[i] = b;
1377 if (b == ']') {
1378 bracketClosed = true;
1379 } else if (b == ':') {
1380 if (!ipv6 || bracketClosed) {
1381 colonPos = i;
1382 break;
1383 }
1384 }
1385 }
1386
1387 if (colonPos < 0) {
1388 if (sslSupport == null) {
1389 // 80 - Default HTTP port
1390 request.setServerPort(80);
1391 } else {
1392 // 443 - Default HTTPS port
1393 request.setServerPort(443);
1394 }
1395 request.serverName().setChars(hostNameC, 0, valueL);
1396 } else {
1397
1398 request.serverName().setChars(hostNameC, 0, colonPos);
1399
1400 int port = 0;
1401 int mult = 1;
1402 for (int i = valueL - 1; i > colonPos; i--) {
1403 int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
1404 if (charValue == -1) {
1405 // Invalid character
1406 error = true;
1407 // 400 - Bad request
1408 response.setStatus(400);
1409 break;
1410 }
1411 port = port + (charValue * mult);
1412 mult = 10 * mult;
1413 }
1414 request.setServerPort(port);
1415
1416 }
1417
1418 }
1419
1420
1421 /**
1422 * Check for compression
1423 */
1424 private boolean isCompressable() {
1425
1426 // Nope Compression could works in HTTP 1.0 also
1427 // cf: mod_deflate
1428
1429 // Compression only since HTTP 1.1
1430 // if (! http11)
1431 // return false;
1432
1433 // Check if browser support gzip encoding
1434 MessageBytes acceptEncodingMB =
1435 request.getMimeHeaders().getValue("accept-encoding");
1436
1437 if ((acceptEncodingMB == null)
1438 || (acceptEncodingMB.indexOf("gzip") == -1))
1439 return false;
1440
1441 // Check if content is not allready gzipped
1442 MessageBytes contentEncodingMB =
1443 response.getMimeHeaders().getValue("Content-Encoding");
1444
1445 if ((contentEncodingMB != null)
1446 && (contentEncodingMB.indexOf("gzip") != -1))
1447 return false;
1448
1449 // If force mode, allways compress (test purposes only)
1450 if (compressionLevel == 2)
1451 return true;
1452
1453 // Check for incompatible Browser
1454 if (noCompressionUserAgents != null) {
1455 MessageBytes userAgentValueMB =
1456 request.getMimeHeaders().getValue("user-agent");
1457 if(userAgentValueMB != null) {
1458 String userAgentValue = userAgentValueMB.toString();
1459
1460 // If one Regexp rule match, disable compression
1461 for (int i = 0; i < noCompressionUserAgents.length; i++)
1462 if (noCompressionUserAgents[i].matcher(userAgentValue).matches())
1463 return false;
1464 }
1465 }
1466
1467 // Check if suffisant len to trig the compression
1468 long contentLength = response.getContentLengthLong();
1469 if ((contentLength == -1)
1470 || (contentLength > compressionMinSize)) {
1471 // Check for compatible MIME-TYPE
1472 if (compressableMimeTypes != null) {
1473 return (startsWithStringArray(compressableMimeTypes,
1474 response.getContentType()));
1475 }
1476 }
1477
1478 return false;
1479 }
1480
1481
1482 /**
1483 * When committing the response, we have to validate the set of headers, as
1484 * well as setup the response filters.
1485 */
1486 protected void prepareResponse() {
1487
1488 boolean entityBody = true;
1489 contentDelimitation = false;
1490
1491 OutputFilter[] outputFilters = outputBuffer.getFilters();
1492
1493 if (http09 == true) {
1494 // HTTP/0.9
1495 outputBuffer.addActiveFilter
1496 (outputFilters[Constant