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 /* If we are here it is because we have a bad header or something like that */
269 log.error( "Error decoding request ", ex );
270 msg.dump( "Incomming message");
271 Response res=ep.getRequest().getResponse();
272 if ( res==null ) {
273 res=new Response();
274 ep.getRequest().setResponse(res);
275 }
276 res.setMessage("Bad Request");
277 res.setStatus(400);
278 return ERROR;
279 }
280
281 if( requiredSecret != null ) {
282 String epSecret=(String)ep.getNote( secretNote );
283 if( epSecret==null || ! requiredSecret.equals( epSecret ) )
284 return ERROR;
285 }
286 /* XXX it should be computed from request, by workerEnv */
287 if(log.isDebugEnabled() )
288 log.debug("Calling next " + next.getName() + " " +
289 next.getClass().getName());
290
291 int err= next.invoke( msg, ep );
292 if (twa != null) {
293 twa.setCurrentStage(control, "JkDone");
294 }
295
296 if( log.isDebugEnabled() )
297 log.debug( "Invoke returned " + err );
298 return err;
299 case AjpConstants.JK_AJP13_SHUTDOWN:
300 String epSecret=null;
301 if( msg.getLen() > 3 ) {
302 // we have a secret
303 msg.getBytes( tmpMB );
304 epSecret=tmpMB.toString();
305 }
306
307 if( requiredSecret != null &&
308 requiredSecret.equals( epSecret ) ) {
309 if( log.isDebugEnabled() )
310 log.debug("Received wrong secret, no shutdown ");
311 return ERROR;
312 }
313
314 // XXX add isSameAddress check
315 JkChannel ch=ep.getSource();
316 if( !ch.isSameAddress(ep) ) {
317 log.error("Shutdown request not from 'same address' ");
318 return ERROR;
319 }
320
321 if( !shutdownEnabled ) {
322 log.warn("Ignoring shutdown request: shutdown not enabled");
323 return ERROR;
324 }
325 // forward to the default handler - it'll do the shutdown
326 checkRequest(ep);
327 next.invoke( msg, ep );
328
329 if(log.isInfoEnabled())
330 log.info("Exiting");
331 System.exit(0);
332
333 return OK;
334
335 // We got a PING REQUEST, quickly respond with a PONG
336 case AjpConstants.JK_AJP13_CPING_REQUEST:
337 msg.reset();
338 msg.appendByte(AjpConstants.JK_AJP13_CPONG_REPLY);
339 ep.getSource().send( msg, ep );
340 ep.getSource().flush( msg, ep ); // Server needs to get it
341 return OK;
342
343 case HANDLE_THREAD_END:
344 return OK;
345
346 default:
347 if(log.isInfoEnabled())
348 log.info("Unknown message " + type);
349 }
350
351 return OK;
352 }
353
354 static int count = 0;
355
356 private Request checkRequest(MsgContext ep) {
357 Request req=ep.getRequest();
358 if( req==null ) {
359 req=new Request();
360 Response res=new Response();
361 req.setResponse(res);
362 ep.setRequest( req );
363 if( registerRequests ) {
364 synchronized(lock) {
365 ep.getSource().registerRequest(req, ep, count++);
366 }
367 }
368 }
369 return req;
370 }
371
372 private int decodeRequest( Msg msg, MsgContext ep, MessageBytes tmpMB )
373 throws IOException {
374 // FORWARD_REQUEST handler
375 Request req = checkRequest(ep);
376
377 RequestInfo rp = req.getRequestProcessor();
378 rp.setStage(Constants.STAGE_PARSE);
379 MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
380 if(tmpMB2 != null) {
381 tmpMB2.recycle();
382 }
383 req.setStartTime(System.currentTimeMillis());
384
385 // Translate the HTTP method code to a String.
386 byte methodCode = msg.getByte();
387 if (methodCode != AjpConstants.SC_M_JK_STORED) {
388 String mName=AjpConstants.methodTransArray[(int)methodCode - 1];
389 req.method().setString(mName);
390 }
391
392 msg.getBytes(req.protocol());
393 msg.getBytes(req.requestURI());
394
395 msg.getBytes(req.remoteAddr());
396 msg.getBytes(req.remoteHost());
397 msg.getBytes(req.localName());
398 req.setLocalPort(msg.getInt());
399
400 boolean isSSL = msg.getByte() != 0;
401 if( isSSL ) {
402 // XXX req.setSecure( true );
403 req.scheme().setString("https");
404 }
405
406 decodeHeaders( ep, msg, req, tmpMB );
407
408 decodeAttributes( ep, msg, req, tmpMB );
409
410 rp.setStage(Constants.STAGE_PREPARE);
411 MessageBytes valueMB = req.getMimeHeaders().getValue("host");
412 parseHost(valueMB, req);
413 // set cookies on request now that we have all headers
414 req.getCookies().setHeaders(req.getMimeHeaders());
415
416 // Check to see if there should be a body packet coming along
417 // immediately after
418 long cl=req.getContentLengthLong();
419 if(cl > 0) {
420 JkInputStream jkIS = ep.getInputStream();
421 jkIS.setIsReadRequired(true);
422 if(!delayInitialRead) {
423 jkIS.receive();
424 }
425 }
426
427 if (log.isTraceEnabled()) {
428 log.trace(req.toString());
429 }
430
431 return OK;
432 }
433
434 private int decodeAttributes( MsgContext ep, Msg msg, Request req,
435 MessageBytes tmpMB) {
436 boolean moreAttr=true;
437
438 while( moreAttr ) {
439 byte attributeCode=msg.getByte();
440 if( attributeCode == AjpConstants.SC_A_ARE_DONE )
441 return 200;
442
443 /* Special case ( XXX in future API make it separate type !)
444 */
445 if( attributeCode == AjpConstants.SC_A_SSL_KEY_SIZE ) {
446 // Bug 1326: it's an Integer.
447 req.setAttribute(SSLSupport.KEY_SIZE_KEY,
448 new Integer( msg.getInt()));
449 //Integer.toString(msg.getInt()));
450 }
451
452 if( attributeCode == AjpConstants.SC_A_REQ_ATTRIBUTE ) {
453 // 2 strings ???...
454 msg.getBytes( tmpMB );
455 String n=tmpMB.toString();
456 msg.getBytes( tmpMB );
457 String v=tmpMB.toString();
458 /*
459 * AJP13 misses to forward the remotePort.
460 * Allow the AJP connector to add this info via
461 * a private request attribute.
462 * We will accept the forwarded data as the remote port,
463 * and remove it from the public list of request attributes.
464 */
465 if(n.equals(AjpConstants.SC_A_REQ_REMOTE_PORT)) {
466 try {
467 req.setRemotePort(Integer.parseInt(v));
468 } catch (NumberFormatException nfe) {
469 }
470 } else {
471 req.setAttribute(n, v );
472 if(log.isTraceEnabled())
473 log.trace("jk Attribute set " + n + "=" + v);
474 }
475 }
476
477
478 // 1 string attributes
479 switch(attributeCode) {
480 case AjpConstants.SC_A_CONTEXT :
481 msg.getBytes( tmpMB );
482 // nothing
483 break;
484
485 case AjpConstants.SC_A_SERVLET_PATH :
486 msg.getBytes( tmpMB );
487 // nothing
488 break;
489
490 case AjpConstants.SC_A_REMOTE_USER :
491 if( tomcatAuthentication ) {
492 // ignore server
493 msg.getBytes( tmpMB );
494 } else {
495 msg.getBytes(req.getRemoteUser());
496 }
497 break;
498
499 case AjpConstants.SC_A_AUTH_TYPE :
500 if( tomcatAuthentication ) {
501 // ignore server
502 msg.getBytes( tmpMB );
503 } else {
504 msg.getBytes(req.getAuthType());
505 }
506 break;
507
508 case AjpConstants.SC_A_QUERY_STRING :
509 msg.getBytes(req.queryString());
510 break;
511
512 case AjpConstants.SC_A_JVM_ROUTE :
513 msg.getBytes(req.instanceId());
514 break;
515
516 case AjpConstants.SC_A_SSL_CERT :
517 req.scheme().setString( "https" );
518 // Transform the string into certificate.
519 MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
520 if(tmpMB2 == null) {
521 tmpMB2 = MessageBytes.newInstance();
522 req.setNote(WorkerEnv.SSL_CERT_NOTE, tmpMB2);
523 }
524 // SSL certificate extraction is costy, moved to JkCoyoteHandler
525 msg.getBytes(tmpMB2);
526 break;
527
528 case AjpConstants.SC_A_SSL_CIPHER :
529 req.scheme().setString( "https" );
530 msg.getBytes(tmpMB);
531 req.setAttribute(SSLSupport.CIPHER_SUITE_KEY,
532 tmpMB.toString());
533 break;
534
535 case AjpConstants.SC_A_SSL_SESSION :
536 req.scheme().setString( "https" );
537 msg.getBytes(tmpMB);
538 req.setAttribute(SSLSupport.SESSION_ID_KEY,
539 tmpMB.toString());
540 break;
541
542 case AjpConstants.SC_A_SECRET :
543 msg.getBytes(tmpMB);
544 String secret=tmpMB.toString();
545 if(log.isTraceEnabled())
546 log.trace("Secret: " + secret );
547 // endpoint note
548 ep.setNote( secretNote, secret );
549 break;
550
551 case AjpConstants.SC_A_STORED_METHOD:
552 msg.getBytes(req.method());
553 break;
554
555 default:
556 break; // ignore, we don't know about it - backward compat
557 }
558 }
559 return 200;
560 }
561
562 private void decodeHeaders( MsgContext ep, Msg msg, Request req,
563 MessageBytes tmpMB ) {
564 // Decode headers
565 MimeHeaders headers = req.getMimeHeaders();
566
567 int hCount = msg.getInt();
568 for(int i = 0 ; i < hCount ; i++) {
569 String hName = null;
570
571 // Header names are encoded as either an integer code starting
572 // with 0xA0, or as a normal string (in which case the first
573 // two bytes are the length).
574 int isc = msg.peekInt();
575 int hId = isc & 0xFF;
576
577 MessageBytes vMB=null;
578 isc &= 0xFF00;
579 if(0xA000 == isc) {
580 msg.getInt(); // To advance the read position
581 hName = AjpConstants.headerTransArray[hId - 1];
582 vMB=headers.addValue( hName );
583 } else {
584 // reset hId -- if the header currently being read
585 // happens to be 7 or 8 bytes long, the code below
586 // will think it's the content-type header or the
587 // content-length header - SC_REQ_CONTENT_TYPE=7,
588 // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
589 // behaviour. see bug 5861 for more information.
590 hId = -1;
591 msg.getBytes( tmpMB );
592 ByteChunk bc=tmpMB.getByteChunk();
593 vMB=headers.addValue( bc.getBuffer(),
594 bc.getStart(), bc.getLength() );
595 }
596
597 msg.getBytes(vMB);
598
599 if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH ||
600 (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
601 // just read the content-length header, so set it
602 long cl = vMB.getLong();
603 if(cl < Integer.MAX_VALUE)
604 req.setContentLength( (int)cl );
605 } else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE ||
606 (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
607 // just read the content-type header, so set it
608 ByteChunk bchunk = vMB.getByteChunk();
609 req.contentType().setBytes(bchunk.getBytes(),
610 bchunk.getOffset(),
611 bchunk.getLength());
612 }
613 }
614 }
615
616 /**
617 * Parse host.
618 */
619 private void parseHost(MessageBytes valueMB, Request request)
620 throws IOException {
621
622 if (valueMB == null || valueMB.isNull()) {
623 // HTTP/1.0
624 // Default is what the socket tells us. Overriden if a host is
625 // found/parsed
626 request.setServerPort(request.getLocalPort());
627 request.serverName().duplicate(request.localName());
628 return;
629 }
630
631 ByteChunk valueBC = valueMB.getByteChunk();
632 byte[] valueB = valueBC.getBytes();
633 int valueL = valueBC.getLength();
634 int valueS = valueBC.getStart();
635 int colonPos = -1;
636 CharChunk hostNameC = (CharChunk)request.getNote(HOSTBUFFER);
637 if(hostNameC == null) {
638 hostNameC = new CharChunk(valueL);
639 request.setNote(HOSTBUFFER, hostNameC);
640 }
641 hostNameC.recycle();
642
643 boolean ipv6 = (valueB[valueS] == '[');
644 boolean bracketClosed = false;
645 for (int i = 0; i < valueL; i++) {
646 char b = (char) valueB[i + valueS];
647 hostNameC.append(b);
648 if (b == ']') {
649 bracketClosed = true;
650 } else if (b == ':') {
651 if (!ipv6 || bracketClosed) {
652 colonPos = i;
653 break;
654 }
655 }
656 }
657
658 if (colonPos < 0) {
659 if (request.scheme().equalsIgnoreCase("https")) {
660 // 80 - Default HTTTP port
661 request.setServerPort(443);
662 } else {
663 // 443 - Default HTTPS port
664 request.setServerPort(80);
665 }
666 request.serverName().setChars(hostNameC.getChars(),
667 hostNameC.getStart(),
668 hostNameC.getLength());
669 } else {
670
671 request.serverName().setChars(hostNameC.getChars(),
672 hostNameC.getStart(), colonPos);
673
674 int port = 0;
675 int mult = 1;
676 for (int i = valueL - 1; i > colonPos; i--) {
677 int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
678 if (charValue == -1) {
679 // Invalid character
680 throw new CharConversionException("Invalid char in port: " + valueB[i + valueS]);
681 }
682 port = port + (charValue * mult);
683 mult = 10 * mult;
684 }
685 request.setServerPort(port);
686
687 }
688
689 }
690
691 }