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