| 1 | package dolda.jsvc.scgi; |
| 2 | |
| 3 | import java.util.logging.*; |
| 4 | import java.io.*; |
| 5 | import java.net.*; |
| 6 | import java.util.*; |
| 7 | |
| 8 | public abstract class Server implements Runnable { |
| 9 | private final ServerSocket sk; |
| 10 | private final Logger logger = Logger.getLogger("dolda.jsvc.scgi"); |
| 11 | public String headcs = "UTF-8"; |
| 12 | |
| 13 | public Server(ServerSocket sk) { |
| 14 | this.sk = sk; |
| 15 | } |
| 16 | |
| 17 | private static int readnslen(InputStream in) throws IOException { |
| 18 | int ret = 0; |
| 19 | while(true) { |
| 20 | int c = in.read(); |
| 21 | if(c == ':') |
| 22 | return(ret); |
| 23 | else if((c >= '0') && (c <= '9')) |
| 24 | ret = (ret * 10) + (c - '0'); |
| 25 | else |
| 26 | throw(new InvalidRequestException("Malformed netstring length")); |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | private static byte[] readns(InputStream in) throws IOException { |
| 31 | byte[] buf = new byte[readnslen(in)]; |
| 32 | int off = 0; |
| 33 | while(off < buf.length) { |
| 34 | int ret = in.read(buf, off, buf.length - off); |
| 35 | if(ret < 0) |
| 36 | throw(new InvalidRequestException("Unexpected EOS in netstring")); |
| 37 | off += ret; |
| 38 | } |
| 39 | if(in.read() != ',') |
| 40 | throw(new InvalidRequestException("Unterminated netstring")); |
| 41 | return(buf); |
| 42 | } |
| 43 | |
| 44 | private Map<String, String> readhead(InputStream in) throws IOException { |
| 45 | byte[] rawhead = readns(in); |
| 46 | String head = new String(rawhead, headcs); |
| 47 | Map<String, String> ret = new HashMap<String, String>(); |
| 48 | int p = 0; |
| 49 | while(true) { |
| 50 | int p2 = head.indexOf(0, p); |
| 51 | if(p2 < 0) { |
| 52 | if(p == head.length()) |
| 53 | return(ret); |
| 54 | throw(new InvalidRequestException("Malformed headers")); |
| 55 | } |
| 56 | String key = head.substring(p, p2); |
| 57 | int p3 = head.indexOf(0, p2 + 1); |
| 58 | if(p3 < 0) |
| 59 | throw(new InvalidRequestException("Malformed headers")); |
| 60 | String val = head.substring(p2 + 1, p3); |
| 61 | ret.put(key, val); |
| 62 | p = p3 + 1; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | private boolean checkhead(Map<String, String> head) { |
| 67 | if(!head.containsKey("SCGI") || !head.get("SCGI").equals("1")) |
| 68 | return(false); |
| 69 | return(true); |
| 70 | } |
| 71 | |
| 72 | protected abstract void handle(Map<String, String> head, Socket sk) throws Exception; |
| 73 | |
| 74 | private void serve(Socket sk) { |
| 75 | try { |
| 76 | try { |
| 77 | InputStream in = sk.getInputStream(); |
| 78 | Map<String, String> head = readhead(in); |
| 79 | if(!checkhead(head)) |
| 80 | return; |
| 81 | try { |
| 82 | handle(head, sk); |
| 83 | } catch(Exception e) { |
| 84 | logger.log(Level.WARNING, "Could not handle request", e); |
| 85 | return; |
| 86 | } |
| 87 | sk = null; |
| 88 | } finally { |
| 89 | if(sk != null) |
| 90 | sk.close(); |
| 91 | } |
| 92 | } catch(IOException e) { |
| 93 | logger.log(Level.WARNING, "I/O error encountered while serving SCGI request", e); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | public void run() { |
| 98 | try { |
| 99 | try { |
| 100 | while(true) { |
| 101 | Socket nsk = sk.accept(); |
| 102 | serve(nsk); |
| 103 | } |
| 104 | } finally { |
| 105 | sk.close(); |
| 106 | } |
| 107 | } catch(IOException e) { |
| 108 | logger.log(Level.SEVERE, "SCGI server encountered I/O error", e); |
| 109 | } |
| 110 | } |
| 111 | } |