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