package betty16.lec1.httplong.message; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import org.scribble.net.ScribMessage; import org.scribble.net.ScribMessageFormatter; import betty16.lec1.httplong.message.client.Accept; import betty16.lec1.httplong.message.client.AcceptEncoding; import betty16.lec1.httplong.message.client.AcceptLanguage; import betty16.lec1.httplong.message.client.Connection; import betty16.lec1.httplong.message.client.DoNotTrack; import betty16.lec1.httplong.message.client.Host; import betty16.lec1.httplong.message.client.RequestLine; import betty16.lec1.httplong.message.client.UpgradeInsecureRequests; import betty16.lec1.httplong.message.client.UserAgent; import betty16.lec1.httplong.message.server.AcceptRanges; import betty16.lec1.httplong.message.server.ContentLength; import betty16.lec1.httplong.message.server.ContentType; import betty16.lec1.httplong.message.server.Date; import betty16.lec1.httplong.message.server.ETag; import betty16.lec1.httplong.message.server.HttpVersion; import betty16.lec1.httplong.message.server.LastModified; import betty16.lec1.httplong.message.server.Server; import betty16.lec1.httplong.message.server.StrictTransportSecurity; import betty16.lec1.httplong.message.server.Vary; import betty16.lec1.httplong.message.server.Via; import betty16.lec1.httplong.message.server._200; import betty16.lec1.httplong.message.server._404; public class HttpLongMessageFormatter implements ScribMessageFormatter { public static final Charset cs = Charset.forName("UTF8"); //private static CharsetDecoder cd = cs.newDecoder(); private int len = -1; public HttpLongMessageFormatter() { } @Override public byte[] toBytes(ScribMessage m) throws IOException { return ((HttpLongMessage) m).toBytes(); } @Override public ScribMessage fromBytes(ByteBuffer bb) throws IOException, ClassNotFoundException { bb.flip(); int rem = bb.remaining(); if (rem < 2) { bb.compact(); return null; } int pos = bb.position(); String front = new String(new byte[] { bb.get(pos), bb.get(pos + 1) }, HttpLongMessageFormatter.cs); if (front.equals(HttpLongMessage.CRLF)) // not sound? -- actually, due to sess types it is safe (same reason why interpreting any of these messages without context is sound) -- parsing doesn't have to follow the *full protocol* BNF any more to be sound { if (this.len == -1) { return new Body(""); } if (rem < this.len + 2) { bb.compact(); return null; } //String body = readLine(dis) + HttpMessage.CRLF; // HACK: assumes at least 1 CRLF StringBuffer sb = new StringBuffer(); //for (int i = body.length(); i < this.len; i++) for (int i = 0; i < this.len; i++) { sb.append((char) bb.get()); } this.len = -1; //return new Body(body + sb.toString()); return new Body(sb.toString()); } if (rem < pos + 4) { bb.compact(); return null; } //byte[] bs = new byte[2]; //dis.readFully(bs); front += new String(new byte[] { bb.get(pos + 2), bb.get(pos + 3) }, HttpLongMessageFormatter.cs); // FIXME: factor out with HttpMessage op strings int code = isStatusCode(front); if (code > -1) { String reason = readLine(bb, pos + 4).trim(); // Whitespace already built into the message classes bb.compact(); if (reason == null) { return null; } switch (code) { case 200: return new _200(reason); case 404: return new _404(reason); default: throw new RuntimeException("Unknown status code: " + code); } } else if (front.startsWith(HttpLongMessage.GET)) { String reql = readLine(bb, pos + 4).trim(); bb.compact(); if (reql == null) { return null; } String target = reql.substring(0, reql.indexOf(' ')); String vers = reql.substring(reql.indexOf(' ') + 1).trim(); vers = vers.substring(vers.indexOf('/') + 1); return new RequestLine(target, vers); } else if (front.equals(HttpLongMessage.HTTP)) { //dis.read(); // '/' if (rem < pos + 5) { bb.compact(); return null; } String word = readWord(bb, pos + 5); bb.compact(); if (word == null) { return null; } return new HttpVersion(word); } else { String line = readLine(bb, pos + 4); bb.compact(); if (line == null) { return null; } line = front + line; int colon = line.indexOf(':'); if (colon > -1) { String name = line.substring(0, colon); String value = line.substring(colon + 1).trim(); // Whitespace already built into the message classes switch (name) { case HttpLongMessage.HOST: return new Host("value"); case HttpLongMessage.USER_AGENT: return new UserAgent(value); case HttpLongMessage.ACCEPT: return new Accept(value); case HttpLongMessage.ACCEPT_LANGUAGE: return new AcceptLanguage(value); case HttpLongMessage.ACCEPT_ENCODING: return new AcceptEncoding(value); case HttpLongMessage.DO_NOT_TRACK: return new DoNotTrack(Integer.parseInt(value)); case HttpLongMessage.CONNECTION: return new Connection(value); case HttpLongMessage.UPGRADE_INSECURE_REQUESTS: return new UpgradeInsecureRequests(Integer.parseInt(value)); case HttpLongMessage.DATE: return new Date(value); case HttpLongMessage.SERVER: return new Server(value); case HttpLongMessage.STRICT_TRANSPORT_SECURITY: return new StrictTransportSecurity(value); case HttpLongMessage.LAST_MODIFIED: return new LastModified(value); case HttpLongMessage.ETAG: return new ETag(value); case HttpLongMessage.ACCEPT_RANGES: return new AcceptRanges(value); case HttpLongMessage.CONTENT_LENGTH: { len = Integer.parseInt(value.trim()); return new ContentLength(len); } case HttpLongMessage.VARY: return new Vary(value); case HttpLongMessage.CONTENT_TYPE: return new ContentType(value); case HttpLongMessage.VIA: return new Via(value); default: throw new RuntimeException("Cannot parse header field: " + line); } } else { throw new RuntimeException("Cannot parse message: " + line); } } } private static String readLine(ByteBuffer bb, int i) throws IOException { StringBuilder sb = new StringBuilder(); for (int limit = bb.limit(); i <= limit; ) { char c = (char) bb.get(i++); // readChar is not the same sb.append(c); if (c == '\r') { if (i > limit) { return null; } c = (char) bb.get(i++); sb.append(c); if (c == '\n') { bb.position(i); return sb.substring(0, sb.length() - 2).toString(); } } } return null; } private static String readWord(ByteBuffer bb, int i) throws IOException { StringBuilder sb = new StringBuilder(); for (int limit = bb.limit(); i <= limit; ) { char c = (char) bb.get(i++); if (c == ' ') { bb.position(i); return sb.toString(); } sb.append(c); } return null; } //private int isStatusCode(byte[] bs) private int isStatusCode(String front) { String code = ""; for (int i = 0; i < 4; i++) { //char c = (char) bs[i]; char c = front.charAt(i); if (i < 3) { if (c < '0' || c > '9') { return -1; } code += c; } else { if (c != ' ') { return -1; } } } return Integer.parseInt(code); } // FIXME: delete @Deprecated @Override public void writeMessage(DataOutputStream dos, ScribMessage m) throws IOException { dos.write(((HttpLongMessage) m).toBytes()); dos.flush(); } @Deprecated @Override public ScribMessage readMessage(DataInputStream dis) throws IOException { byte[] bs = new byte[2]; dis.readFully(bs); String front = new String(bs, HttpLongMessageFormatter.cs); if (front.equals(HttpLongMessage.CRLF)) // not sound? -- actually, due to sess types it is safe (same reason why interpreting any of these messages without context is sound) -- parsing doesn't have to follow the *full protocol* BNF any more to be sound { if (this.len == -1) { return new Body(""); } //String body = readLine(dis) + HttpMessage.CRLF; // HACK: assumes at least 1 CRLF StringBuffer sb = new StringBuffer(); //for (int i = body.length(); i < this.len; i++) for (int i = 0; i < this.len; i++) { sb.append((char) dis.read()); } this.len = -1; //return new Body(body + sb.toString()); return new Body(sb.toString()); } bs = new byte[2]; dis.readFully(bs); front += new String(bs, HttpLongMessageFormatter.cs); // FIXME: factor out with HttpMessage op strings int code = isStatusCode(front); if (code > -1) { String reason = readLine(dis).trim(); // Whitespace already built into the message classes switch (code) { case 200: return new _200(reason); case 404: return new _404(reason); default: throw new RuntimeException("Unknown status code: " + code); } } else if (front.startsWith(HttpLongMessage.GET)) { String reql = readLine(dis).trim(); String target = reql.substring(0, reql.indexOf(' ')); String vers = reql.substring(reql.indexOf(' ') + 1).trim(); vers = vers.substring(vers.indexOf('/') + 1); return new RequestLine(target, vers); } else if (front.equals(HttpLongMessage.HTTP)) { dis.read(); // '/' String word = readWord(dis); return new HttpVersion(word); } else { String line = front + readLine(dis); int colon = line.indexOf(':'); if (colon > -1) { String name = line.substring(0, colon); String value = line.substring(colon + 1).trim(); // Whitespace already built into the message classes switch (name) { case HttpLongMessage.HOST: return new Host("value"); case HttpLongMessage.USER_AGENT: return new UserAgent(value); case HttpLongMessage.ACCEPT: return new Accept(value); case HttpLongMessage.ACCEPT_LANGUAGE: return new AcceptLanguage(value); case HttpLongMessage.ACCEPT_ENCODING: return new AcceptEncoding(value); case HttpLongMessage.DO_NOT_TRACK: return new DoNotTrack(Integer.parseInt(value)); case HttpLongMessage.CONNECTION: return new Connection(value); case HttpLongMessage.DATE: return new Date(value); case HttpLongMessage.SERVER: return new Server(value); case HttpLongMessage.LAST_MODIFIED: return new LastModified(value); case HttpLongMessage.ETAG: return new ETag(value); case HttpLongMessage.ACCEPT_RANGES: return new AcceptRanges(value); case HttpLongMessage.CONTENT_LENGTH: { len = Integer.parseInt(value.trim()); return new ContentLength(len); } case HttpLongMessage.VARY: return new Vary(value); case HttpLongMessage.CONTENT_TYPE: return new ContentType(value); case HttpLongMessage.VIA: return new Via(value); default: throw new RuntimeException("Cannot parse header field: " + line); } } else { throw new RuntimeException("Cannot parse message: " + line); } } } private static String readLine(DataInputStream dis) throws IOException { StringBuilder sb = new StringBuilder(); for (; true; ) { char c = (char) dis.read(); // readChar is not the same sb.append(c); if (c == '\r') { c = (char) dis.read(); sb.append(c); if (c == '\n') { return sb.substring(0, sb.length() - 2).toString(); } } } } private static String readWord(DataInputStream dis) throws IOException { StringBuilder sb = new StringBuilder(); for (; true; ) { char c = (char) dis.read(); if (c == ' ') { return sb.toString(); } sb.append(c); } } }