192d3801029f1c3da1556502b35a3ddf3659d73e
[jsvc.git] / src / dolda / jsvc / scgi / ScgiRequest.java
1 package dolda.jsvc.scgi;
2
3 import java.io.*;
4 import java.net.*;
5 import java.util.*;
6 import dolda.jsvc.*;
7 import dolda.jsvc.util.*;
8
9 public class ScgiRequest extends ResponseBuffer {
10     final Socket sk;
11     private final Map<String, String> environ;
12     private final InputStream in;
13     private final String method, path;
14     private final URL url, context;
15     private MultiMap<String, String> params = null;
16     private MultiMap<String, String> inhead = new HeaderTreeMap();
17     
18     public ScgiRequest(Socket sk, Map<String, String> environ) throws IOException {
19         this.sk = sk;
20         this.environ = environ;
21         for(Map.Entry<String, String> var : environ.entrySet()) {
22             String k = var.getKey();
23             if((k.length() > 5) && k.substring(0, 5).equals("HTTP_")) {
24                 StringBuilder buf = new StringBuilder();
25                 boolean f = true;
26                 for(int i = 5; i < k.length(); i++) {
27                     char c = k.charAt(i);
28                     if(c == '_') {
29                         buf.append('-');
30                         f = true;
31                     } else if(f) {
32                         buf.append(Character.toUpperCase(c));
33                         f = false;
34                     } else {
35                         buf.append(Character.toLowerCase(c));
36                     }
37                 }
38                 inhead.add(buf.toString(), var.getValue());
39             }
40         }
41         long len;
42         {
43             String h = environ.get("CONTENT_LENGTH");
44             if(h == null) {
45                 len = 0;
46             } else {
47                 try {
48                     len = Long.parseLong(h);
49                 } catch(NumberFormatException e) {
50                     throw(new InvalidRequestException("Invalid Content-Length header: " + h));
51                 }
52             }
53         }
54         this.in = new LimitInputStream(sk.getInputStream(), len);
55         path = environ.get("PATH_INFO");
56         if(path == null)
57             throw(new InvalidRequestException("Missing PATH_INFO"));
58         method = environ.get("REQUEST_METHOD");
59         if(method == null)
60             throw(new InvalidRequestException("Missing REQUEST_METHOD"));
61         {
62             /* Ewwww, this is disgusting! */
63             String scheme = "http";
64             if(environ.get("HTTPS") != null)
65                 scheme = "https";
66             int port = -1;
67             String host = environ.get("HTTP_HOST");
68             if((host == null) || (host.length() < 1)) {
69                 if((host = environ.get("SERVER_NAME")) == null)
70                     throw(new InvalidRequestException("Both HTTP_HOST and SERVER name are missing"));
71                 String portnum = environ.get("SERVER_PORT");
72                 if(portnum == null)
73                     throw(new InvalidRequestException("Missing SERVER_PORT"));
74                 try {
75                     port = Integer.parseInt(portnum);
76                 } catch(NumberFormatException e) {
77                     throw(new InvalidRequestException("Bad SERVER_PORT: " + portnum));
78                 }
79                 if((port == 80) && scheme.equals("http"))
80                     port = -1;
81                 else if((port == 443) && scheme.equals("https"))
82                     port = -1;
83             } else {
84                 int p;
85                 if((host.charAt(0) == '[') && ((p = host.indexOf(']', 1)) > 1)) {
86                     String newhost = host.substring(1, p);
87                     if((p = host.indexOf(':', p + 1)) >= 0) {
88                         try {
89                             port = Integer.parseInt(host.substring(p + 1));
90                         } catch(NumberFormatException e) {}
91                     }
92                     host = newhost;
93                 } else if((p = host.indexOf(':')) >= 0) {
94                     try {
95                         port = Integer.parseInt(host.substring(p + 1));
96                         host = host.substring(0, p);
97                     } catch(NumberFormatException e) {}
98                 }
99             }
100             String nm = environ.get("SCRIPT_NAME");
101             if(nm == null)
102                 throw(new InvalidRequestException("Missing SCRIPT_NAME"));
103             String q = environ.get("QUERY_STRING");
104             if(q != null)
105                 q = "?" + q;
106             else
107                 q = "";
108             try {
109                 url = new URL(scheme, host, port, nm + path + q);
110                 if(nm.charAt(nm.length() - 1) != '/')
111                     nm += "/";          /* XXX? */
112                 context = new URL(scheme, host, port, nm);
113             } catch(MalformedURLException e) {
114                 throw(new Error(e));
115             }
116         }
117     }
118     
119     public MultiMap<String, String> inheaders() {
120         return(inhead);
121     }
122
123     public ServerContext ctx() {
124         return(ThreadContext.current().server());
125     }
126     
127     public InputStream input() {
128         return(in);
129     }
130     
131     public URL url() {
132         return(url);
133     }
134     
135     public URL rooturl() {
136         return(context);
137     }
138     
139     public String path() {
140         return(path);
141     }
142     
143     public String method() {
144         return(method);
145     }
146     
147     public MultiMap<String, String> params() {
148         if(params == null)
149             params = Params.stdparams(this);
150         return(params);
151     }
152
153     public SocketAddress localaddr() {
154         String portnum = environ.get("SERVER_PORT");
155         int port = -1;
156         try {
157             if(portnum != null)
158                 port = Integer.parseInt(portnum);
159         } catch(NumberFormatException e) {}
160         if(port < 0)
161             return(null);       /* XXX? */
162         String addr;
163         addr = environ.get("X_ASH_SERVER_ADDRESS");
164         if(addr == null)
165             return(new InetSocketAddress(port)); /* XXX? */
166         else
167             return(new InetSocketAddress(addr, port));
168     }
169
170     public SocketAddress remoteaddr() {
171         String addr;
172         String portnum;
173         addr = environ.get("REMOTE_ADDR");
174         portnum = environ.get("X_ASH_PORT");
175         int port = -1;
176         try {
177             if(portnum != null)
178                 port = Integer.parseInt(portnum);
179         } catch(NumberFormatException e) {}
180         if((addr != null) && (port >= 0))
181             return(new InetSocketAddress(addr, port));
182         return(null);                   /* XXX? */
183     }
184     
185     private void checkstring(String s) {
186         for(int i = 0; i < s.length(); i++) {
187             char c = s.charAt(i);
188             if((c < 32) || (c >= 128))
189                 throw(new RuntimeException("Invalid header string: " + s));
190         }
191     }
192
193     protected void backflush() throws IOException {
194         Writer out = new OutputStreamWriter(realoutput(), Misc.ascii);
195         out.write(String.format("Status: %d %s\n", respcode, resptext));
196         for(Map.Entry<String, String> e : outheaders().entrySet()) {
197             String k = e.getKey();
198             String v = e.getValue();
199             checkstring(k);
200             checkstring(v);
201             out.write(String.format("%s: %s\n", k, v));
202         }
203         out.write("\n");
204         out.flush();
205     }
206     
207     protected OutputStream realoutput() {
208         try {
209             return(sk.getOutputStream());
210         } catch(IOException e) {
211             /* It is not obvious why this would happen, so I'll wait
212              * until I know whatever might happen to try and implement
213              * meaningful behavior. */
214             throw(new RuntimeException(e));
215         }
216     }
217 }