001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jk.common;
019:
020: import java.io.File;
021: import java.io.FileOutputStream;
022: import java.io.IOException;
023: import java.io.CharConversionException;
024: import java.net.InetAddress;
025: import java.util.Properties;
026:
027: import org.apache.coyote.Request;
028: import org.apache.coyote.RequestInfo;
029: import org.apache.coyote.Response;
030: import org.apache.coyote.Constants;
031: import org.apache.jk.core.JkHandler;
032: import org.apache.jk.core.Msg;
033: import org.apache.jk.core.MsgContext;
034: import org.apache.jk.core.WorkerEnv;
035: import org.apache.jk.core.JkChannel;
036: import org.apache.tomcat.util.buf.ByteChunk;
037: import org.apache.tomcat.util.buf.CharChunk;
038: import org.apache.tomcat.util.buf.HexUtils;
039: import org.apache.tomcat.util.buf.MessageBytes;
040: import org.apache.tomcat.util.http.MimeHeaders;
041: import org.apache.tomcat.util.net.SSLSupport;
042: import org.apache.tomcat.util.threads.ThreadWithAttributes;
043:
044: /**
045: * Handle messages related with basic request information.
046: *
047: * This object can handle the following incoming messages:
048: * - "FORWARD_REQUEST" input message ( sent when a request is passed from the
049: * web server )
050: * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in
051: * response to GET_BODY_CHUNK )
052: *
053: * It can handle the following outgoing messages:
054: * - SEND_HEADERS. Pass the status code and headers.
055: * - SEND_BODY_CHUNK. Send a chunk of body
056: * - GET_BODY_CHUNK. Request a chunk of body data
057: * - END_RESPONSE. Notify the end of a request processing.
058: *
059: * @author Henri Gomez [hgomez@apache.org]
060: * @author Dan Milstein [danmil@shore.net]
061: * @author Keith Wannamaker [Keith@Wannamaker.org]
062: * @author Costin Manolache
063: */
064: public class HandlerRequest extends JkHandler {
065: private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
066: .getLog(HandlerRequest.class);
067:
068: /*
069: * Note for Host parsing.
070: */
071: public static final int HOSTBUFFER = 10;
072:
073: /**
074: * Thread lock.
075: */
076: private static Object lock = new Object();
077:
078: private HandlerDispatch dispatch;
079: private String ajpidDir = "conf";
080:
081: public HandlerRequest() {
082: }
083:
084: public void init() {
085: dispatch = (HandlerDispatch) wEnv.getHandler("dispatch");
086: if (dispatch != null) {
087: // register incoming message handlers
088: dispatch.registerMessageType(
089: AjpConstants.JK_AJP13_FORWARD_REQUEST,
090: "JK_AJP13_FORWARD_REQUEST", this , null); // 2
091:
092: dispatch.registerMessageType(
093: AjpConstants.JK_AJP13_SHUTDOWN,
094: "JK_AJP13_SHUTDOWN", this , null); // 7
095:
096: dispatch.registerMessageType(
097: AjpConstants.JK_AJP13_CPING_REQUEST,
098: "JK_AJP13_CPING_REQUEST", this , null); // 10
099: dispatch.registerMessageType(HANDLE_THREAD_END,
100: "HANDLE_THREAD_END", this , null);
101: // register outgoing messages handler
102: dispatch.registerMessageType(
103: AjpConstants.JK_AJP13_SEND_BODY_CHUNK, // 3
104: "JK_AJP13_SEND_BODY_CHUNK", this , null);
105: }
106:
107: tmpBufNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "tmpBuf");
108: secretNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "secret");
109:
110: if (next == null)
111: next = wEnv.getHandler("container");
112: if (log.isDebugEnabled())
113: log.debug("Container handler " + next + " "
114: + next.getName() + " " + next.getClass().getName());
115:
116: // should happen on start()
117: generateAjp13Id();
118: }
119:
120: public void setSecret(String s) {
121: requiredSecret = s;
122: }
123:
124: public void setUseSecret(boolean b) {
125: if (b) {
126: requiredSecret = Double.toString(Math.random());
127: }
128: }
129:
130: public void setDecodedUri(boolean b) {
131: decoded = b;
132: }
133:
134: public boolean isTomcatAuthentication() {
135: return tomcatAuthentication;
136: }
137:
138: public void setShutdownEnabled(boolean se) {
139: shutdownEnabled = se;
140: }
141:
142: public boolean getShutdownEnabled() {
143: return shutdownEnabled;
144: }
145:
146: public void setTomcatAuthentication(boolean newTomcatAuthentication) {
147: tomcatAuthentication = newTomcatAuthentication;
148: }
149:
150: public void setAjpidDir(String path) {
151: if ("".equals(path))
152: path = null;
153: ajpidDir = path;
154: }
155:
156: /**
157: * Set the flag to tell if we JMX register requests.
158: */
159: public void setRegisterRequests(boolean srr) {
160: registerRequests = srr;
161: }
162:
163: /**
164: * Get the flag to tell if we JMX register requests.
165: */
166: public boolean getRegisterRequests() {
167: return registerRequests;
168: }
169:
170: /**
171: * Set the flag to delay the initial body read
172: */
173: public void setDelayInitialRead(boolean dir) {
174: delayInitialRead = dir;
175: }
176:
177: /**
178: * Get the flag to tell if we delay the initial body read
179: */
180: public boolean getDelayInitialRead() {
181: return delayInitialRead;
182: }
183:
184: // -------------------- Ajp13.id --------------------
185:
186: private void generateAjp13Id() {
187: int portInt = 8009; // tcpCon.getPort();
188: InetAddress address = null; // tcpCon.getAddress();
189:
190: if (requiredSecret == null || !shutdownEnabled)
191: return;
192:
193: File f1 = new File(wEnv.getJkHome());
194: File f2 = new File(f1, "conf");
195:
196: if (!f2.exists()) {
197: log.error("No conf dir for ajp13.id " + f2);
198: return;
199: }
200:
201: File sf = new File(f2, "ajp13.id");
202:
203: if (log.isDebugEnabled())
204: log.debug("Using stop file: " + sf);
205:
206: try {
207: Properties props = new Properties();
208:
209: props.put("port", Integer.toString(portInt));
210: if (address != null) {
211: props.put("address", address.getHostAddress());
212: }
213: if (requiredSecret != null) {
214: props.put("secret", requiredSecret);
215: }
216:
217: FileOutputStream stopF = new FileOutputStream(sf);
218: props.store(stopF, "Automatically generated, don't edit");
219: } catch (IOException ex) {
220: if (log.isDebugEnabled())
221: log.debug("Can't create stop file: " + sf, ex);
222: }
223: }
224:
225: // -------------------- Incoming message --------------------
226: private String requiredSecret = null;
227: private int secretNote;
228: private int tmpBufNote;
229:
230: private boolean decoded = true;
231: private boolean tomcatAuthentication = true;
232: private boolean registerRequests = true;
233: private boolean shutdownEnabled = false;
234: private boolean delayInitialRead = true;
235:
236: public int invoke(Msg msg, MsgContext ep) throws IOException {
237: int type = msg.getByte();
238: ThreadWithAttributes twa = null;
239: if (Thread.currentThread() instanceof ThreadWithAttributes) {
240: twa = (ThreadWithAttributes) Thread.currentThread();
241: }
242: Object control = ep.getControl();
243: MessageBytes tmpMB = (MessageBytes) ep.getNote(tmpBufNote);
244: if (tmpMB == null) {
245: tmpMB = MessageBytes.newInstance();
246: ep.setNote(tmpBufNote, tmpMB);
247: }
248:
249: if (log.isDebugEnabled())
250: log.debug("Handling " + type);
251:
252: switch (type) {
253: case AjpConstants.JK_AJP13_FORWARD_REQUEST:
254: try {
255: if (twa != null) {
256: twa.setCurrentStage(control, "JkDecode");
257: }
258: decodeRequest(msg, ep, tmpMB);
259: if (twa != null) {
260: twa.setCurrentStage(control, "JkService");
261: twa.setParam(control, ((Request) ep.getRequest())
262: .unparsedURI());
263: }
264: } catch (Exception ex) {
265: log.error("Error decoding request ", ex);
266: msg.dump("Incomming message");
267: return ERROR;
268: }
269:
270: if (requiredSecret != null) {
271: String epSecret = (String) ep.getNote(secretNote);
272: if (epSecret == null
273: || !requiredSecret.equals(epSecret))
274: return ERROR;
275: }
276: /* XXX it should be computed from request, by workerEnv */
277: if (log.isDebugEnabled())
278: log.debug("Calling next " + next.getName() + " "
279: + next.getClass().getName());
280:
281: int err = next.invoke(msg, ep);
282: if (twa != null) {
283: twa.setCurrentStage(control, "JkDone");
284: }
285:
286: if (log.isDebugEnabled())
287: log.debug("Invoke returned " + err);
288: return err;
289: case AjpConstants.JK_AJP13_SHUTDOWN:
290: String epSecret = null;
291: if (msg.getLen() > 3) {
292: // we have a secret
293: msg.getBytes(tmpMB);
294: epSecret = tmpMB.toString();
295: }
296:
297: if (requiredSecret != null
298: && requiredSecret.equals(epSecret)) {
299: if (log.isDebugEnabled())
300: log.debug("Received wrong secret, no shutdown ");
301: return ERROR;
302: }
303:
304: // XXX add isSameAddress check
305: JkChannel ch = ep.getSource();
306: if (!ch.isSameAddress(ep)) {
307: log.error("Shutdown request not from 'same address' ");
308: return ERROR;
309: }
310:
311: if (!shutdownEnabled) {
312: log
313: .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
371: .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, new Integer(
440: 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: // 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
497: .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, tmpMB
510: .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, tmpMB
517: .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(), bc.getStart(),
572: bc.getLength());
573: }
574:
575: msg.getBytes(vMB);
576:
577: if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH
578: || (hId == -1 && tmpMB
579: .equalsIgnoreCase("Content-Length"))) {
580: // just read the content-length header, so set it
581: long cl = vMB.getLong();
582: if (cl < Integer.MAX_VALUE)
583: req.setContentLength((int) cl);
584: } else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE
585: || (hId == -1 && tmpMB
586: .equalsIgnoreCase("Content-Type"))) {
587: // just read the content-type header, so set it
588: ByteChunk bchunk = vMB.getByteChunk();
589: req.contentType().setBytes(bchunk.getBytes(),
590: bchunk.getOffset(), bchunk.getLength());
591: }
592: }
593: }
594:
595: /**
596: * Parse host.
597: */
598: private void parseHost(MessageBytes valueMB, Request request)
599: throws IOException {
600:
601: if (valueMB == null || valueMB.isNull()) {
602: // HTTP/1.0
603: // Default is what the socket tells us. Overriden if a host is
604: // found/parsed
605: request.setServerPort(request.getLocalPort());
606: request.serverName().duplicate(request.localName());
607: return;
608: }
609:
610: ByteChunk valueBC = valueMB.getByteChunk();
611: byte[] valueB = valueBC.getBytes();
612: int valueL = valueBC.getLength();
613: int valueS = valueBC.getStart();
614: int colonPos = -1;
615: CharChunk hostNameC = (CharChunk) request.getNote(HOSTBUFFER);
616: if (hostNameC == null) {
617: hostNameC = new CharChunk(valueL);
618: request.setNote(HOSTBUFFER, hostNameC);
619: }
620: hostNameC.recycle();
621:
622: boolean ipv6 = (valueB[valueS] == '[');
623: boolean bracketClosed = false;
624: for (int i = 0; i < valueL; i++) {
625: char b = (char) valueB[i + valueS];
626: hostNameC.append(b);
627: if (b == ']') {
628: bracketClosed = true;
629: } else if (b == ':') {
630: if (!ipv6 || bracketClosed) {
631: colonPos = i;
632: break;
633: }
634: }
635: }
636:
637: if (colonPos < 0) {
638: if (request.scheme().equalsIgnoreCase("https")) {
639: // 80 - Default HTTTP port
640: request.setServerPort(443);
641: } else {
642: // 443 - Default HTTPS port
643: request.setServerPort(80);
644: }
645: request.serverName().setChars(hostNameC.getChars(),
646: hostNameC.getStart(), 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(
659: "Invalid char in port: "
660: + valueB[i + valueS]);
661: }
662: port = port + (charValue * mult);
663: mult = 10 * mult;
664: }
665: request.setServerPort(port);
666:
667: }
668:
669: }
670:
671: }
|