Canonicalize HTTP method in ScgiRequest.
[jsvc.git] / src / dolda / jsvc / scgi / ScgiRequest.java
CommitLineData
13e578b1
FT
1package dolda.jsvc.scgi;
2
3import java.io.*;
4import java.net.*;
5import java.util.*;
6import dolda.jsvc.*;
7import dolda.jsvc.util.*;
8
9public 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"));
99c044ee
FT
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 }
13e578b1
FT
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}