initial commit
[fcgi] / java / FCGIMessage.java
1 /*
2  * @(#)FCGIMessage.java
3  *
4  *
5  *      FastCGi compatibility package Interface
6  *
7  *
8  *  Copyright (c) 1996 Open Market, Inc.
9  *
10  * See the file "LICENSE.TERMS" for information on usage and redistribution
11  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  *
13  * $Id: FCGIMessage.java,v 1.4 2000/10/02 15:09:07 robs Exp $
14  */
15 package com.fastcgi;
16
17 import java.io.*;
18 import java.util.Properties;
19
20 /* This class handles reading and building the fastcgi messages.
21  * For reading incoming mesages, we pass the input
22  * stream as a param to the constructor rather than to each method.
23  * Methods that build messages use and return internal buffers, so they
24  * dont need a stream.
25  */
26
27 public class FCGIMessage
28 {
29     private static final String RCSID = "$Id: FCGIMessage.java,v 1.4 2000/10/02 15:09:07 robs Exp $";
30
31     /*
32      * Instance variables
33      */
34     /*
35      * FCGI Message Records
36      * The logical structures of the FCGI Message Records.
37      * Fields are originally 1 unsigned byte in message
38      * unless otherwise noted.
39      */
40     /*
41      * FCGI Header
42      */
43     private int  h_version;
44     private int  h_type;
45     private int  h_requestID;       // 2 bytes
46     private int  h_contentLength;   // 2 bytes
47     private int  h_paddingLength;
48     /*
49      * FCGI BeginRequest body.
50      */
51     private int  br_role;      // 2 bytes
52     private int  br_flags;
53
54     private FCGIInputStream in;
55
56     /*
57      * constructor - Java would do this implicitly.
58      */
59     public FCGIMessage(){
60         super();
61     }
62     /*
63      * constructor - get the stream.
64      */
65     public FCGIMessage(FCGIInputStream instream){
66         in = instream;
67     }
68
69     /*
70      * Message Reading Methods
71      */
72
73     /*
74      * Interpret the FCGI Message Header. Processes FCGI
75      * BeginRequest and Management messages. Param hdr is the header.
76      * The calling routine has to keep track of the stream reading
77      * management or use FCGIInputStream.fill() which does just that.
78      */
79     public int processHeader(byte[] hdr) throws IOException{
80         processHeaderBytes(hdr);
81         if (h_version != FCGIGlobalDefs.def_FCGIVersion1) {
82             return(FCGIGlobalDefs.def_FCGIUnsupportedVersion);
83         }
84         in.contentLen = h_contentLength;
85         in.paddingLen = h_paddingLength;
86         if (h_type == FCGIGlobalDefs.def_FCGIBeginRequest) {
87             return processBeginRecord(h_requestID);
88         }
89         if (h_requestID == FCGIGlobalDefs.def_FCGINullRequestID) {
90             return processManagementRecord(h_type);
91         }
92         if (h_requestID != in.request.requestID) {
93             return(FCGIGlobalDefs.def_FCGISkip);
94         }
95         if (h_type != in.type) {
96             return(FCGIGlobalDefs.def_FCGIProtocolError);
97         }
98         return(FCGIGlobalDefs.def_FCGIStreamRecord);
99     }
100
101     /* Put the unsigned bytes in the incoming FCGI header into
102      * integer form for Java, concatinating bytes when needed.
103      * Because Java has no unsigned byte type, we have to be careful
104      * about signed numeric promotion to int.
105      */
106     private void processHeaderBytes(byte[] hdrBuf){
107         h_version = hdrBuf[0] & 0xFF;
108         h_type = hdrBuf[1] & 0xFF;
109         h_requestID = ((hdrBuf[2] & 0xFF) << 8) | (hdrBuf[3] & 0xFF);
110         h_contentLength = ((hdrBuf[4] & 0xFF) << 8) | (hdrBuf[5] & 0xFF);
111         h_paddingLength = hdrBuf[6] & 0xFF;
112     }
113
114     /*
115      * Reads FCGI Begin Request Record.
116      */
117     public int processBeginRecord(int requestID) throws IOException {
118         byte beginReqBody[];
119         byte endReqMsg[];
120         if (requestID == 0 || in.contentLen
121             != FCGIGlobalDefs.def_FCGIEndReqBodyLen) {
122             return FCGIGlobalDefs.def_FCGIProtocolError;
123         }
124         /*
125          * If the webserver is multiplexing the connection,
126          * this library can't deal with it, so repond with
127          * FCGIEndReq message with protocolStatus FCGICantMpxConn
128          */
129         if (in.request.isBeginProcessed) {
130             endReqMsg = new byte[FCGIGlobalDefs.def_FCGIHeaderLen
131                 + FCGIGlobalDefs.def_FCGIEndReqBodyLen];
132             System.arraycopy(makeHeader(
133                 FCGIGlobalDefs.def_FCGIEndRequest,
134                 requestID,
135                 FCGIGlobalDefs.def_FCGIEndReqBodyLen,
136                 0), 0,  endReqMsg, 0,
137                 FCGIGlobalDefs.def_FCGIHeaderLen);
138             System.arraycopy(makeEndrequestBody(0,
139                 FCGIGlobalDefs.def_FCGICantMpxConn), 0,
140                 endReqMsg,
141                 FCGIGlobalDefs.def_FCGIHeaderLen,
142                 FCGIGlobalDefs.def_FCGIEndReqBodyLen);
143             /*
144              * since isBeginProcessed is first set below,this
145              * can't be out first call, so request.out is properly set
146              */
147             try {
148                 in.request.outStream.write(endReqMsg, 0,
149                     FCGIGlobalDefs.def_FCGIHeaderLen
150                     + FCGIGlobalDefs.def_FCGIEndReqBodyLen);
151             } catch (IOException e){
152                 in.request.outStream.setException(e);
153                 return -1;
154             }
155         }
156         /*
157          * Accept this  new request. Read the record body
158          */
159         in.request.requestID = requestID;
160         beginReqBody =
161             new byte[FCGIGlobalDefs.def_FCGIBeginReqBodyLen];
162         if (in.read(beginReqBody, 0,
163             FCGIGlobalDefs.def_FCGIBeginReqBodyLen) !=
164             FCGIGlobalDefs.def_FCGIBeginReqBodyLen) {
165             return FCGIGlobalDefs.def_FCGIProtocolError;
166         }
167         br_flags = beginReqBody[2] & 0xFF;
168         in.request.keepConnection
169             = (br_flags & FCGIGlobalDefs.def_FCGIKeepConn) != 0;
170         br_role = ((beginReqBody[0] & 0xFF) << 8) | (beginReqBody[1] & 0xFF);
171         in.request.role = br_role;
172         in.request.isBeginProcessed = true;
173         return FCGIGlobalDefs.def_FCGIBeginRecord;
174     }
175
176     /*
177      * Reads and Responds to a Management Message. The only type of
178      * management message this library understands is FCGIGetValues.
179      * The only variables that this library's FCGIGetValues understands
180      * are def_FCGIMaxConns, def_FCGIMaxReqs, and def_FCGIMpxsConns.
181      * Ignore the other management variables, and repsond to other
182      * management messages with FCGIUnknownType.
183      */
184     public int processManagementRecord(int type) throws IOException {
185
186         byte[] response = new byte[64];
187         int wrndx = response[FCGIGlobalDefs.def_FCGIHeaderLen];
188         int value, len, plen;
189         if (type == FCGIGlobalDefs.def_FCGIGetValues) {
190             Properties tmpProps = new Properties();
191             readParams(tmpProps);
192
193             if (in.getFCGIError() != 0 || in.contentLen != 0) {
194                 return FCGIGlobalDefs.def_FCGIProtocolError;
195             }
196             if (tmpProps.containsKey(
197                 FCGIGlobalDefs.def_FCGIMaxConns)) {
198                 makeNameVal(
199                     FCGIGlobalDefs.def_FCGIMaxConns, "1",
200                     response, wrndx);
201             }
202             else {
203                 if (tmpProps.containsKey(
204                     FCGIGlobalDefs.def_FCGIMaxReqs)) {
205                     makeNameVal(
206                         FCGIGlobalDefs.def_FCGIMaxReqs, "1",
207                         response, wrndx);
208                 }
209                 else {
210                     if (tmpProps.containsKey(
211                         FCGIGlobalDefs.def_FCGIMaxConns)) {
212                         makeNameVal(
213                             FCGIGlobalDefs.def_FCGIMpxsConns, "0",
214                             response, wrndx);
215                     }
216                 }
217             }
218             plen = 64 - wrndx;
219             len = wrndx - FCGIGlobalDefs.def_FCGIHeaderLen;
220             System.arraycopy(makeHeader(
221                 FCGIGlobalDefs.def_FCGIGetValuesResult,
222                 FCGIGlobalDefs.def_FCGINullRequestID,
223                 len, plen), 0,
224                 response, 0,
225                 FCGIGlobalDefs.def_FCGIHeaderLen);
226         }
227         else {
228             plen = len =
229                 FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen;
230             System.arraycopy(makeHeader(
231                 FCGIGlobalDefs.def_FCGIUnknownType,
232                 FCGIGlobalDefs.def_FCGINullRequestID,
233                 len, 0), 0,
234                 response, 0,
235                 FCGIGlobalDefs.def_FCGIHeaderLen);
236             System.arraycopy(makeUnknownTypeBodyBody(h_type), 0,
237                 response,
238                 FCGIGlobalDefs.def_FCGIHeaderLen,
239                 FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen);
240         }
241         /*
242          * No guarantee that we have a request yet, so
243          * dont use fcgi output stream to reference socket, instead
244          * use the FileInputStream that refrences it. Also
245          * nowhere to save exception, since this is not FCGI stream.
246          */
247
248         try {
249             in.request.socket.getOutputStream().write(response, 0,
250                 FCGIGlobalDefs.def_FCGIHeaderLen +
251                 FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen);
252
253         } catch (IOException e){
254             return -1;
255         }
256         return FCGIGlobalDefs.def_FCGIMgmtRecord;
257     }
258
259     /*
260      * Makes a name/value with name = string of some length, and
261      * value a 1 byte integer. Pretty specific to what we are doing
262      * above.
263      */
264     void makeNameVal(String name, String value, byte[] dest, int pos) {
265         int nameLen = name.length();
266         if (nameLen < 0x80) {
267             dest[pos++] = (byte)nameLen;
268         }else {
269             dest[pos++] = (byte)(((nameLen >> 24) | 0x80) & 0xff);
270             dest[pos++] = (byte)((nameLen >> 16) & 0xff);
271             dest[pos++] = (byte)((nameLen >> 8) & 0xff);
272             dest[pos++] = (byte)nameLen;
273         }
274         int valLen = value.length();
275         if (valLen < 0x80) {
276             dest[pos++] =  (byte)valLen;
277         }else {
278             dest[pos++] = (byte)(((valLen >> 24) | 0x80) & 0xff);
279             dest[pos++] = (byte)((valLen >> 16) & 0xff);
280             dest[pos++] = (byte)((valLen >> 8) & 0xff);
281             dest[pos++] = (byte)valLen;
282         }
283
284         try {
285             System.arraycopy(name.getBytes("UTF-8"), 0, dest, pos, nameLen);
286             pos += nameLen;
287
288             System.arraycopy(value.getBytes("UTF-8"), 0, dest, pos, valLen);
289             pos += valLen;
290         }
291         catch (UnsupportedEncodingException x) {}
292     }
293
294     /*
295      * Read FCGI name-value pairs from a stream until EOF. Put them
296      * into a Properties object, storing both as strings.
297      */
298     public int readParams(Properties props) throws IOException{
299         int nameLen, valueLen;
300         byte lenBuff[] = new byte[3];
301         int i = 1;
302
303         while ((nameLen = in.read()) != -1) {
304             i++;
305             if ((nameLen & 0x80) != 0) {
306                 if ((in.read( lenBuff, 0, 3)) != 3) {
307                     in.setFCGIError(
308                         FCGIGlobalDefs.def_FCGIParamsError);
309                     return -1;
310                 }
311                 nameLen = ((nameLen & 0x7f) << 24)
312                     | ((lenBuff[0] & 0xFF) << 16)
313                     | ((lenBuff[1] & 0xFF) << 8)
314                     | (lenBuff[2] & 0xFF);
315             }
316
317             if ((valueLen = in.read()) == -1) {
318                 in.setFCGIError(
319                     FCGIGlobalDefs.def_FCGIParamsError);
320                 return -1;
321             }
322             if ((valueLen & 0x80) != 0) {
323                 if ((in.read( lenBuff, 0, 3)) != 3) {
324                     in.setFCGIError(
325                         FCGIGlobalDefs.def_FCGIParamsError);
326                     return -1;
327                 }
328                 valueLen = ((valueLen & 0x7f) << 24)
329                     | ((lenBuff[0] & 0xFF) << 16)
330                     | ((lenBuff[1] & 0xFF) << 8)
331                     | (lenBuff[2] & 0xFF);
332             }
333
334             /*
335              * nameLen and valueLen are now valid; read the name
336              * and the value from the stream and construct a standard
337              * environmental entity
338              */
339             byte[] name  = new byte[nameLen];
340             byte[] value = new byte[valueLen];
341             if (in.read(name ,0,  nameLen) != nameLen) {
342                 in.setFCGIError(
343                     FCGIGlobalDefs.def_FCGIParamsError);
344                 return -1;
345             }
346
347             if(in.read(value, 0, valueLen) != valueLen) {
348                 in.setFCGIError(
349                     FCGIGlobalDefs.def_FCGIParamsError);
350                 return -1;
351             }
352             String strName  = new String(name);
353             String strValue = new String(value);
354             props.put(strName, strValue);
355         }
356         return 0;
357
358
359     }
360     /*
361      * Message Building Methods
362      */
363
364     /*
365      * Build an FCGI Message Header -
366      */
367     public byte[] makeHeader(int type,
368         int requestId,
369         int contentLength,
370         int paddingLength) {
371         byte[] header = new byte[FCGIGlobalDefs.def_FCGIHeaderLen];
372         header[0]   = (byte)FCGIGlobalDefs.def_FCGIVersion1;
373         header[1]   = (byte)type;
374         header[2]   = (byte)((requestId      >> 8) & 0xff);
375         header[3]   = (byte)((requestId          ) & 0xff);
376         header[4]   = (byte)((contentLength  >> 8) & 0xff);
377         header[5]   = (byte)((contentLength      ) & 0xff);
378         header[6]   = (byte)paddingLength;
379         header[7]   =  0;  //reserved byte
380         return header;
381     }
382     /*
383      * Build an FCGI Message End Request Body
384      */
385     public byte[] makeEndrequestBody(int appStatus,int protocolStatus){
386         byte body[] = new byte[FCGIGlobalDefs.def_FCGIEndReqBodyLen];
387         body[0] = (byte)((appStatus >> 24) & 0xff);
388         body[1] = (byte)((appStatus >> 16) & 0xff);
389         body[2] = (byte)((appStatus >>  8) & 0xff);
390         body[3] = (byte)((appStatus      ) & 0xff);
391         body[4] = (byte)protocolStatus;
392         for (int i = 5; i < 8; i++) {
393             body[i] = 0;
394         }
395         return body;
396     }
397     /*
398      * Build an FCGI Message UnknownTypeBodyBody
399      */
400     public byte[] makeUnknownTypeBodyBody(int type){
401         byte body[] =
402             new byte[FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen];
403         body[0] = (byte)type;
404         for (int i = 1;
405             i < FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen; i++) {
406             body[i] = 0;
407         }
408         return body;
409     }
410
411 } //end class