Canonicalize HTTP method in ScgiRequest.
[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         {
59             String tmp = environ.get("REQUEST_METHOD");
60             if(tmp == null)
61                 throw(new InvalidRequestException("Missing REQUEST_METHOD"));
62             method = tmp.toUpperCase().intern();
63         }
64         {
65             /* Ewwww, this is disgusting! */
66             String scheme = "http";
67             if(environ.get("HTTPS") != null)
68                 scheme = "https";
69             int port = -1;
70             String host = environ.get("HTTP_HOST");
71             if((host == null) || (host.length() < 1)) {
72                 if((host = environ.get("SERVER_NAME")) == null)
73                     throw(new InvalidRequestException("Both HTTP_HOST and SERVER name are missing"));
74                 String portnum = environ.get("SERVER_PORT");
75                 if(portnum == null)
76                     throw(new InvalidRequestException("Missing SERVER_PORT"));
77                 try {
78                     port = Integer.parseInt(portnum);
79                 } catch(NumberFormatException e) {
80                     throw(new InvalidRequestException("Bad SERVER_PORT: " + portnum));
81                 }
82                 if((port == 80) && scheme.equals("http"))
83                     port = -1;
84                 else if((port == 443) && scheme.equals("https"))
85                     port = -1;
86             } else {
87                 int p;
88                 if((host.charAt(0) == '[') && ((p = host.indexOf(']', 1)) > 1)) {
89                     String newhost = host.substring(1, p);
90                     if((p = host.indexOf(':', p + 1)) >= 0) {
91                         try {
92                             port = Integer.parseInt(host.substring(p + 1));
93                         } catch(NumberFormatException e) {}
94                     }
95                     host = newhost;
96                 } else if((p = host.indexOf(':')) >= 0) {
97                     try {
98                         port = Integer.parseInt(host.substring(p + 1));
99                         host = host.substring(0, p);
100                     } catch(NumberFormatException e) {}
101                 }
102             }
103             String nm = environ.get("SCRIPT_NAME");
104             if(nm == null)
105                 throw(new InvalidRequestException("Missing SCRIPT_NAME"));
106             String q = environ.get("QUERY_STRING");
107             if(q != null)
108                 q = "?" + q;
109             else
110                 q = "";
111             try {
112                 url = new URL(scheme, host, port, nm + path + q);
113                 if(nm.charAt(nm.length() - 1) != '/')
114                     nm += "/";          /* XXX? */
115                 context = new URL(scheme, host, port, nm);
116             } catch(MalformedURLException e) {
117                 throw(new Error(e));
118             }
119         }
120     }
121     
122     public MultiMap<String, String> inheaders() {
123         return(inhead);
124     }
125
126     public ServerContext ctx() {
127         return(ThreadContext.current().server());
128     }
129     
130     public InputStream input() {
131         return(in);
132     }
133     
134     public URL url() {
135         return(url);
136     }
137     
138     public URL rooturl() {
139         return(context);
140     }
141     
142     public String path() {
143         return(path);
144     }
145     
146     public String method() {
147         return(method);
148     }
149     
150     public MultiMap<String, String> params() {
151         if(params == null)
152             params = Params.stdparams(this);
153         return(params);
154     }
155
156     public SocketAddress localaddr() {
157         String portnum = environ.get("SERVER_PORT");
158         int port = -1;
159         try {
160             if(portnum != null)
161                 port = Integer.parseInt(portnum);
162         } catch(NumberFormatException e) {}
163         if(port < 0)
164             return(null);       /* XXX? */
165         String addr;
166         addr = environ.get("X_ASH_SERVER_ADDRESS");
167         if(addr == null)
168             return(new InetSocketAddress(port)); /* XXX? */
169         else
170             return(new InetSocketAddress(addr, port));
171     }
172
173     public SocketAddress remoteaddr() {
174         String addr;
175         String portnum;
176         addr = environ.get("REMOTE_ADDR");
177         portnum = environ.get("X_ASH_PORT");
178         int port = -1;
179         try {
180             if(portnum != null)
181                 port = Integer.parseInt(portnum);
182         } catch(NumberFormatException e) {}
183         if((addr != null) && (port >= 0))
184             return(new InetSocketAddress(addr, port));
185         return(null);                   /* XXX? */
186     }
187     
188     private void checkstring(String s) {
189         for(int i = 0; i < s.length(); i++) {
190             char c = s.charAt(i);
191             if((c < 32) || (c >= 128))
192                 throw(new RuntimeException("Invalid header string: " + s));
193         }
194     }
195
196     protected void backflush() throws IOException {
197         Writer out = new OutputStreamWriter(realoutput(), Misc.ascii);
198         out.write(String.format("Status: %d %s\n", respcode, resptext));
199         for(Map.Entry<String, String> e : outheaders().entrySet()) {
200             String k = e.getKey();
201             String v = e.getValue();
202             checkstring(k);
203             checkstring(v);
204             out.write(String.format("%s: %s\n", k, v));
205         }
206         out.write("\n");
207         out.flush();
208     }
209     
210     protected OutputStream realoutput() {
211         try {
212             return(sk.getOutputStream());
213         } catch(IOException e) {
214             /* It is not obvious why this would happen, so I'll wait
215              * until I know whatever might happen to try and implement
216              * meaningful behavior. */
217             throw(new RuntimeException(e));
218         }
219     }
220 }