/* Headers822.java * * Copyright (c) 2002, Benja Fallenstein * * You may use and distribute under the terms of either the GNU Lesser * General Public License, either version 2 of the license or, * at your choice, any later version. Alternatively, you may use and * distribute under the terms of the XPL. * * See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of * the licenses. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the README * file for more details. * */ /* * Written by Benja Fallenstein */ package gzz.storm.headers; import java.io.*; import java.util.*; /** Utility functions for reading and writing RFC 2822-style headers. * The parsing functions in this util are strict in that they don't * try to handle non-conformant headers (e.g., headers that use * only CR or LF instead of CRLF). They also aren't fully conformant * because they do not currently attempt to parse obsoleted syntax, * as required by RFC 2822; support for this may be added * in the future. *

* So far, there is no support at all for structured field values. */ public class Headers822 { public static boolean dbg = false; private static void p(String s) { System.out.println(s); } /** Read and parse a header. * After this method, in is positioned * at the beginning of the message body, after the empty line * ending the header. */ public static Header822 readHeader(InputStream in) throws IOException { List lines = new ArrayList(); HeaderMap822 map = new DefaultHeaderMap822(); String fieldName = null; String fieldBody = null; while(true) { String line = readLine(in); if(line.equals("")) { // add the previous line before returning if(dbg) p("add last: "+fieldName+", "+fieldBody); if(fieldName != null) map.add(fieldName, fieldBody.trim()); HeaderLines822 hlines = new DefaultHeaderLines822(lines); return new ModularHeader822(hlines, map); } lines.add(line); if(line.trim().equals("")) throw new ParseException("Line containing only whitespace " + "in headers"); if(!Character.isSpace(line.charAt(0))) { // not a continuation of a folded line // first, add the previous line if(dbg) p("add: "+fieldName+", "+fieldBody); if(fieldName != null) map.add(fieldName, fieldBody.trim()); // then, remember name and body of the current line int i = line.indexOf(':'); if(i <= 0) throw new ParseException("No header name in line '" + line + "'"); fieldName = line.substring(0, i).toLowerCase(); fieldBody = line.substring(i+1); } else if(fieldName == null) { throw new ParseException("First line in header is folded"); } else { fieldBody += line; } } } public static String canonicalizeFieldName(String fieldName) { if(fieldName.equals("")) throw new IllegalArgumentException("Empty header field name"); String s = fieldName.toLowerCase(); int i = 0; do { s = s.substring(0, i) + s.substring(i,i+1).toUpperCase() + s.substring(i+1); i = s.indexOf('-', i+1)+1; } while(i > 0); if(dbg) p("Canonicalized header field name: "+s); return s; } // ParseException public static class ParseException extends IOException { public ParseException(String s) { super(s); } } // Private stuff private static int CR = 0xd, LF = 0xa; private static String readLine(InputStream in) throws IOException { StringBuffer s = new StringBuffer(); while(true) { int c = in.read(); if(c < 0) throw new ParseException("EOF while reading headers"); if(c == CR) break; if(c == LF) throw new ParseException("LF without preceding CR"); if(c < 1 || c > 127) throw new ParseException("Character outside US-ASCII " + "1-127: '"+c+"'"); s.append((char)c); } int i = in.read(); if(i < 0) throw new ParseException("EOF while reading headers"); if(i != LF) throw new ParseException("CR not followed by LF"); return s.toString(); } }