Source code: org/apache/jk/common/HandlerRequest.java
1 /*
2 * Copyright 1999-2005 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.jk.common;
18
19 import java.io.File;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.CharConversionException;
23 import java.net.InetAddress;
24 import java.util.Properties;
25
26 import org.apache.coyote.Request;
27 import org.apache.coyote.RequestInfo;
28 import org.apache.coyote.Response;
29 import org.apache.coyote.Constants;
30 import org.apache.jk.core.JkHandler;
31 import org.apache.jk.core.Msg;
32 import org.apache.jk.core.MsgContext;
33 import org.apache.jk.core.WorkerEnv;
34 import org.apache.jk.core.JkChannel;
35 import org.apache.tomcat.util.buf.ByteChunk;
36 import org.apache.tomcat.util.buf.CharChunk;
37 import org.apache.tomcat.util.buf.HexUtils;
38 import org.apache.tomcat.util.buf.MessageBytes;
39 import org.apache.tomcat.util.http.MimeHeaders;
40 import org.apache.tomcat.util.net.SSLSupport;
41 import org.apache.tomcat.util.threads.ThreadWithAttributes;
42
43 /**
44 * Handle messages related with basic request information.
45 *
46 * This object can handle the following incoming messages:
47 * - "FORWARD_REQUEST" input message ( sent when a request is passed from the
48 * web server )
49 * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in
50 * response to GET_BODY_CHUNK )
51 *
52 * It can handle the following outgoing messages:
53 * - SEND_HEADERS. Pass the status code and headers.
54 * - SEND_BODY_CHUNK. Send a chunk of body
55 * - GET_BODY_CHUNK. Request a chunk of body data
56 * - END_RESPONSE. Notify the end of a request processing.
57 *
58 * @author Henri Gomez [hgomez@apache.org]
59 * @author Dan Milstein [danmil@shore.net]
60 * @author Keith Wannamaker [Keith@Wannamaker.org]
61 * @author Costin Manolache
62 */
63 public class HandlerRequest extends JkHandler
64 {
65 private static org.apache.commons.logging.Log log=
66 org.apache.commons.logging.LogFactory.getLog( HandlerRequest.class );
67
68 /*
69 * Note for Host parsing.
70 */
71 public static final int HOSTBUFFER = 10;
72
73 /**
74 * Thread lock.
75 */
76 private static Object lock = new Object();
77
78 private HandlerDispatch dispatch;
79 private String ajpidDir="conf";
80
81
82 public HandlerRequest() {
83 }
84
85 public void init() {
86 dispatch=(HandlerDispatch)wEnv.getHandler( "dispatch" );
87 if( dispatch != null ) {
88 // register incoming message handlers
89 dispatch.registerMessageType( AjpConstants.JK_AJP13_FORWARD_REQUEST,
90 "JK_AJP13_FORWARD_REQUEST",
91 this, null); // 2
92
93 dispatch.registerMessageType( AjpConstants.JK_AJP13_SHUTDOWN,
94 "JK_AJP13_SHUTDOWN",
95 this, null); // 7
96
97 dispatch.registerMessageType( AjpConstants.JK_AJP13_CPING_REQUEST,
98 "JK_AJP13_CPING_REQUEST",
99 this, null); // 10
100 dispatch.registerMessageType( HANDLE_THREAD_END,
101 "HANDLE_THREAD_END",
102 this, null);
103 // register outgoing messages handler
104 dispatch.registerMessageType( AjpConstants.JK_AJP13_SEND_BODY_CHUNK, // 3
105 "JK_AJP13_SEND_BODY_CHUNK",
106 this,null );
107 }
108
109 tmpBufNote=wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "tmpBuf" );
110 secretNote=wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "secret" );
111
112 if( next==null )
113 next=wEnv.getHandler( "container" );
114 if( log.isDebugEnabled() )
115 log.debug( "Container handler " + next + " " + next.getName() +
116 " " + next.getClass().getName());
117
118 // should happen on start()
119 generateAjp13Id();
120 }
121
122 public void setSecret( String s ) {
123 requiredSecret=s;
124 }
125
126 public void setUseSecret( boolean b ) {
127 if(b) {
128 requiredSecret=Double.toString(Math.random());
129 }
130 }
131
132 public void setDecodedUri( boolean b ) {
133 decoded=b;
134 }
135
136 public boolean isTomcatAuthentication() {
137 return tomcatAuthentication;
138 }
139
140 public void setShutdownEnabled(boolean se) {
141 shutdownEnabled = se;
142 }
143
144 public boolean getShutdownEnabled() {
145 return shutdownEnabled;
146 }
147
148 public void setTomcatAuthentication(boolean newTomcatAuthentication) {
149 tomcatAuthentication = newTomcatAuthentication;
150 }
151
152 public void setAjpidDir( String path ) {
153 if( "".equals( path ) ) path=null;
154 ajpidDir=path;
155 }
156
157 /**
158 * Set the flag to tell if we JMX register requests.
159 */
160 public void setRegisterRequests(boolean srr) {
161 registerRequests = srr;
162 }
163
164 /**
165 * Get the flag to tell if we JMX register requests.
166 */
167 public boolean getRegisterRequests() {
168 return registerRequests;
169 }
170
171 /**
172 * Set the flag to delay the initial body read
173 */
174 public void setDelayInitialRead(boolean dir) {
175 delayInitialRead = dir;
176 }
177
178 /**
179 * Get the flag to tell if we delay the initial body read
180 */
181 public boolean getDelayInitialRead() {
182 return delayInitialRead;
183 }
184
185 // -------------------- Ajp13.id --------------------
186
187 private void generateAjp13Id() {
188 int portInt=8009; // tcpCon.getPort();
189 InetAddress address=null; // tcpCon.getAddress();
190
191 if( requiredSecret == null )
192 return;
193
194 File f1=new File( wEnv.getJkHome() );
195 File f2=new File( f1, "conf" );
196
197 if( ! f2.exists() ) {
198 log.error( "No conf dir for ajp13.id " + f2 );
199 return;
200 }
201
202 File sf=new File( f2, "ajp13.id");
203
204 if( log.isDebugEnabled())
205 log.debug( "Using stop file: "+sf);
206
207 try {
208 Properties props=new Properties();
209
210 props.put( "port", Integer.toString( portInt ));
211 if( address!=null ) {
212 props.put( "address", address.getHostAddress() );
213 }
214 if( requiredSecret !=null ) {
215 props.put( "secret", requiredSecret );
216 }
217
218 FileOutputStream stopF=new FileOutputStream( sf );
219 props.store( stopF, "Automatically generated, don't edit" );
220 } catch( IOException ex ) {
221 if(log.isDebugEnabled())
222 log.debug( "Can't create stop file: "+sf,ex );
223 }
224 }
225
226 // -------------------- Incoming message --------------------
227 private String requiredSecret=null;
228 private int secretNote;
229 private int tmpBufNote;
230
231 private boolean decoded=true;
232 private boolean tomcatAuthentication=true;
233 private boolean registerRequests=true;
234 private boolean shutdownEnabled=false;
235 private boolean delayInitialRead = true;
236
237 public int invoke(Msg msg, MsgContext ep )
238 throws IOException {
239 int type=msg.getByte();
240 ThreadWithAttributes twa = null;
241 if (Thread.currentThread() instanceof ThreadWithAttributes) {
242 twa = (ThreadWithAttributes) Thread.currentThread();
243 }
244 Object control=ep.getControl();
245 MessageBytes tmpMB=(MessageBytes)ep.getNote( tmpBufNote );
246 if( tmpMB==null ) {
247 tmpMB= MessageBytes.newInstance();
248 ep.setNote( tmpBufNote, tmpMB);
249 }
250
251 if( log.isDebugEnabled() )
252 log.debug( "Handling " + type );
253
254 switch( type ) {
255 case AjpConstants.JK_AJP13_FORWARD_REQUEST:
256 try {
257 if (twa != null) {
258 twa.setCurrentStage(control, "JkDecode");
259 }
260 decodeRequest( msg, ep, tmpMB );
261 if (twa != null) {
262 twa.setCurrentStage(control, "JkService");
263 twa.setParam(control,
264 ((Request)ep.getRequest()).unparsedURI());
265 }
266 } catch( Exception ex ) {
267 log.error( "Error decoding request ", ex );
268 msg.dump( "Incomming message");
269 return ERROR;
270 }
271
272 if( requiredSecret != null ) {
273 String epSecret=(String)ep.getNote( secretNote );
274 if( epSecret==null || ! requiredSecret.equals( epSecret ) )
275 return ERROR;
276 }
277 /* XXX it should be computed from request, by workerEnv */
278 if(log.isDebugEnabled() )
279 log.debug("Calling next " + next.getName() + " " +
280 next.getClass().getName());
281
282 int err= next.invoke( msg, ep );
283 if (twa != null) {
284 twa.setCurrentStage(control, "JkDone");
285 }
286
287 if( log.isDebugEnabled() )
288 log.debug( "Invoke returned " + err );
289 return err;
290 case AjpConstants.JK_AJP13_SHUTDOWN:
291 String epSecret=null;
292 if( msg.getLen() > 3 ) {
293 // we have a secret
294 msg.getBytes( tmpMB );
295 epSecret=tmpMB.toString();
296 }
297
298 if( requiredSecret != null &&
299 requiredSecret.equals( epSecret ) ) {
300 if( log.isDebugEnabled() )
301 log.debug("Received wrong secret, no shutdown ");
302 return ERROR;
303 }
304
305 // XXX add isSameAddress check
306 JkChannel ch=ep.getSource();
307 if( !ch.isSameAddress(ep) ) {
308 log.error("Shutdown request not from 'same address' ");
309 return ERROR;
310 }
311
312 if( !shutdownEnabled ) {
313 log.warn("Ignoring shutdown request: shutdown not enabled");
314 return ERROR;
315 }
316 // forward to the default handler - it'll do the shutdown
317 checkRequest(ep);
318 next.invoke( msg, ep );
319
320 if(log.isInfoEnabled())
321 log.info("Exiting");
322 System.exit(0);
323
324 return OK;
325
326 // We got a PING REQUEST, quickly respond with a PONG
327 case AjpConstants.JK_AJP13_CPING_REQUEST:
328 msg.reset();
329 msg.appendByte(AjpConstants.JK_AJP13_CPONG_REPLY);
330 ep.getSource().send( msg, ep );
331 ep.getSource().flush( msg, ep ); // Server needs to get it
332 return OK;
333
334 case HANDLE_THREAD_END:
335 return OK;
336
337 default:
338 if(log.isInfoEnabled())
339 log.info("Unknown message " + type);
340 }
341
342 return OK;
343 }
344
345 static int count = 0;
346
347 private Request checkRequest(MsgContext ep) {
348 Request req=ep.getRequest();
349 if( req==null ) {
350 req=new Request();
351 Response res=new Response();
352 req.setResponse(res);
353 ep.setRequest( req );
354 if( registerRequests ) {
355 synchronized(lock) {
356 ep.getSource().registerRequest(req, ep, count++);
357 }
358 }
359 }
360 return req;
361 }
362
363 private int decodeRequest( Msg msg, MsgContext ep, MessageBytes tmpMB )
364 throws IOException {
365 // FORWARD_REQUEST handler
366 Request req = checkRequest(ep);
367
368 RequestInfo rp = req.getRequestProcessor();
369 rp.setStage(Constants.STAGE_PARSE);
370 MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
371 if(tmpMB2 != null) {
372 tmpMB2.recycle();
373 }
374 req.setStartTime(System.currentTimeMillis());
375
376 // Translate the HTTP method code to a String.
377 byte methodCode = msg.getByte();
378 if (methodCode != AjpConstants.SC_M_JK_STORED) {
379 String mName=AjpConstants.methodTransArray[(int)methodCode - 1];
380 req.method().setString(mName);
381 }
382
383 msg.getBytes(req.protocol());
384 msg.getBytes(req.requestURI());
385
386 msg.getBytes(req.remoteAddr());
387 msg.getBytes(req.remoteHost());
388 msg.getBytes(req.localName());
389 req.setLocalPort(msg.getInt());
390
391 boolean isSSL = msg.getByte() != 0;
392 if( isSSL ) {
393 // XXX req.setSecure( true );
394 req.scheme().setString("https");
395 }
396
397 decodeHeaders( ep, msg, req, tmpMB );
398
399 decodeAttributes( ep, msg, req, tmpMB );
400
401 rp.setStage(Constants.STAGE_PREPARE);
402 MessageBytes valueMB = req.getMimeHeaders().getValue("host");
403 parseHost(valueMB, req);
404 // set cookies on request now that we have all headers
405 req.getCookies().setHeaders(req.getMimeHeaders());
406
407 // Check to see if there should be a body packet coming along
408 // immediately after
409 int cl=req.getContentLength();
410 if(cl > 0) {
411 JkInputStream jkIS = ep.getInputStream();
412 jkIS.setIsReadRequired(true);
413 if(!delayInitialRead) {
414 jkIS.receive();
415 }
416 }
417
418 if (log.isTraceEnabled()) {
419 log.trace(req.toString());
420 }
421
422 return OK;
423 }
424
425 private int decodeAttributes( MsgContext ep, Msg msg, Request req,
426 MessageBytes tmpMB) {
427 boolean moreAttr=true;
428
429 while( moreAttr ) {
430 byte attributeCode=msg.getByte();
431 if( attributeCode == AjpConstants.SC_A_ARE_DONE )
432 return 200;
433
434 /* Special case ( XXX in future API make it separate type !)
435 */
436 if( attributeCode == AjpConstants.SC_A_SSL_KEY_SIZE ) {
437 // Bug 1326: it's an Integer.
438 req.setAttribute(SSLSupport.KEY_SIZE_KEY,
439 new Integer( msg.getInt()));
440 //Integer.toString(msg.getInt()));
441 }
442
443 if( attributeCode == AjpConstants.SC_A_REQ_ATTRIBUTE ) {
444 // 2 strings ???...
445 msg.getBytes( tmpMB );
446 String n=tmpMB.toString();
447 msg.getBytes( tmpMB );
448 String v=tmpMB.toString();
449 req.setAttribute(n, v );
450 if(log.isTraceEnabled())
451 log.trace("jk Attribute set " + n + "=" + v);
452 }
453
454
455 // 1 string attributes
456 switch(attributeCode) {
457 case AjpConstants.SC_A_CONTEXT :
458 msg.getBytes( tmpMB );
459 // nothing
460 break;
461
462 case AjpConstants.SC_A_SERVLET_PATH :
463 msg.getBytes( tmpMB );
464 // nothing
465 break;
466
467 case AjpConstants.SC_A_REMOTE_USER :
468 if( tomcatAuthentication ) {
469 // ignore server
470 msg.getBytes( tmpMB );
471 } else {
472 msg.getBytes(req.getRemoteUser());
473 }
474 break;
475
476 case AjpConstants.SC_A_AUTH_TYPE :
477 if( tomcatAuthentication ) {
478 // ignore server
479 msg.getBytes( tmpMB );
480 } else {
481 msg.getBytes(req.getAuthType());
482 }
483 break;
484
485 case AjpConstants.SC_A_QUERY_STRING :
486 msg.getBytes(req.queryString());
487 break;
488
489 case AjpConstants.SC_A_JVM_ROUTE :
490 msg.getBytes(req.instanceId());
491 break;
492
493 case AjpConstants.SC_A_SSL_CERT :
494 req.scheme().setString( "https" );
495 // Transform the string into certificate.
496 MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
497 if(tmpMB2 == null) {
498 tmpMB2 = MessageBytes.newInstance();
499 req.setNote(WorkerEnv.SSL_CERT_NOTE, tmpMB2);
500 }
501 // SSL certificate extraction is costy, moved to JkCoyoteHandler
502 msg.getBytes(tmpMB2);
503 break;
504
505 case AjpConstants.SC_A_SSL_CIPHER :
506 req.scheme().setString( "https" );
507 msg.getBytes(tmpMB);
508 req.setAttribute(SSLSupport.CIPHER_SUITE_KEY,
509 tmpMB.toString());
510 break;
511
512 case AjpConstants.SC_A_SSL_SESSION :
513 req.scheme().setString( "https" );
514 msg.getBytes(tmpMB);
515 req.setAttribute(SSLSupport.SESSION_ID_KEY,
516 tmpMB.toString());
517 break;
518
519 case AjpConstants.SC_A_SECRET :
520 msg.getBytes(tmpMB);
521 String secret=tmpMB.toString();
522 if(log.isTraceEnabled())
523 log.trace("Secret: " + secret );
524 // endpoint note
525 ep.setNote( secretNote, secret );
526 break;
527
528 case AjpConstants.SC_A_STORED_METHOD:
529 msg.getBytes(req.method());
530 break;
531
532 default:
533 break; // ignore, we don't know about it - backward compat
534 }
535 }
536 return 200;
537 }
538
539 private void decodeHeaders( MsgContext ep, Msg msg, Request req,
540 MessageBytes tmpMB ) {
541 // Decode headers
542 MimeHeaders headers = req.getMimeHeaders();
543
544 int hCount = msg.getInt();
545 for(int i = 0 ; i < hCount ; i++) {
546 String hName = null;
547
548 // Header names are encoded as either an integer code starting
549 // with 0xA0, or as a normal string (in which case the first
550 // two bytes are the length).
551 int isc = msg.peekInt();
552 int hId = isc & 0xFF;
553
554 MessageBytes vMB=null;
555 isc &= 0xFF00;
556 if(0xA000 == isc) {
557 msg.getInt(); // To advance the read position
558 hName = AjpConstants.headerTransArray[hId - 1];
559 vMB=headers.addValue( hName );
560 } else {
561 // reset hId -- if the header currently being read
562 // happens to be 7 or 8 bytes long, the code below
563 // will think it's the content-type header or the
564 // content-length header - SC_REQ_CONTENT_TYPE=7,
565 // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
566 // behaviour. see bug 5861 for more information.
567 hId = -1;
568 msg.getBytes( tmpMB );
569 ByteChunk bc=tmpMB.getByteChunk();
570 vMB=headers.addValue( bc.getBuffer(),
571 bc.getStart(), bc.getLength() );
572 }
573
574 msg.getBytes(vMB);
575
576 if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH ||
577 (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
578 // just read the content-length header, so set it
579 req.setContentLength( vMB.getInt() );
580 } else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE ||
581 (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
582 // just read the content-type header, so set it
583 ByteChunk bchunk = vMB.getByteChunk();
584 req.contentType().setBytes(bchunk.getBytes(),
585 bchunk.getOffset(),
586 bchunk.getLength());
587 }
588 }
589 }
590
591 /**
592 * Parse host.
593 */
594 private void parseHost(MessageBytes valueMB, Request request)
595 throws IOException {
596
597 if (valueMB == null || valueMB.isNull()) {
598 // HTTP/1.0
599 // Default is what the socket tells us. Overriden if a host is
600 // found/parsed
601 request.setServerPort(request.getLocalPort());
602 request.serverName().duplicate(request.localName());
603 return;
604 }
605
606 ByteChunk valueBC = valueMB.getByteChunk();
607 byte[] valueB = valueBC.getBytes();
608 int valueL = valueBC.getLength();
609 int valueS = valueBC.getStart();
610 int colonPos = -1;
611 CharChunk hostNameC = (CharChunk)request.getNote(HOSTBUFFER);
612 if(hostNameC == null) {
613 hostNameC = new CharChunk(valueL);
614 request.setNote(HOSTBUFFER, hostNameC);
615 }
616 hostNameC.recycle();
617
618 boolean ipv6 = (valueB[valueS] == '[');
619 boolean bracketClosed = false;
620 for (int i = 0; i < valueL; i++) {
621 char b = (char) valueB[i + valueS];
622 hostNameC.append(b);
623 if (b == ']') {
624 bracketClosed = true;
625 } else if (b == ':') {
626 if (!ipv6 || bracketClosed) {
627 colonPos = i;
628 break;
629 }
630 }
631 }
632
633 if (colonPos < 0) {
634 if (request.scheme().equalsIgnoreCase("https")) {
635 // 80 - Default HTTTP port
636 request.setServerPort(443);
637 } else {
638 // 443 - Default HTTPS port
639 request.setServerPort(80);
640 }
641 request.serverName().setChars(hostNameC.getChars(),
642 hostNameC.getStart(),
643 hostNameC.getLength());
644 } else {
645
646 request.serverName().setChars(hostNameC.getChars(),
647 hostNameC.getStart(), colonPos);
648
649 int port = 0;
650 int mult = 1;
651 for (int i = valueL - 1; i > colonPos; i--) {
652 int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
653 if (charValue == -1) {
654 // Invalid character
655 throw new CharConversionException("Invalid char in port: " + valueB[i + valueS]);
656 }
657 port = port + (charValue * mult);
658 mult = 10 * mult;
659 }
660 request.setServerPort(port);
661
662 }
663
664 }
665
666 }