Commit | Line | Data |
61962ef7 |
1 | /* |
2 | * @(#)FCGIMessage.java |
3 | * |
4 | * |
5 | * FastCGi compatibility package Interface |
6 | * |
7 | * |
8 | * Copyright (c) 1996 Open Market, Inc. |
9 | * |
af1b4cad |
10 | * See the file "LICENSE" for information on usage and redistribution |
61962ef7 |
11 | * of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
12 | * |
1671ea1d |
13 | * $Id: FCGIMessage.java,v 1.4 2000/10/02 15:09:07 robs Exp $ |
61962ef7 |
14 | */ |
07c41236 |
15 | package com.fastcgi; |
61962ef7 |
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 | |
1671ea1d |
27 | public class FCGIMessage |
05f58140 |
28 | { |
1671ea1d |
29 | private static final String RCSID = "$Id: FCGIMessage.java,v 1.4 2000/10/02 15:09:07 robs Exp $"; |
61962ef7 |
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 | } |
1671ea1d |
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) {} |
61962ef7 |
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 | } |
1671ea1d |
352 | String strName = new String(name); |
353 | String strValue = new String(value); |
61962ef7 |
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 |