| 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 | } |