/***************************************************************** JADE - Java Agent DEvelopment Framework is a framework to develop multi-agent systems in compliance with the FIPA specifications. Copyright (C) 2000 CSELT S.p.A. The updating of this file to JADE 2.0 has been partially supported by the IST-1999-10211 LEAP Project This file refers to parts of the FIPA 99/00 Agent Message Transport Implementation Copyright (C) 2000, Laboratoire d'Intelligence Artificielle, Ecole Polytechnique Federale de Lausanne GNU Lesser General Public License This library is free software; you can redistribute it sand/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 2.1 of the License. This library 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 GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *****************************************************************/ /** * HTTPIO.java * * * @author Jose Antonio Exposito * @author MARISM-A Development group ( marisma-info@ccd.uab.es ) * @version 0.1 * @author Nicolas Lhuillier (Motorola Labs) * @version 1.0 */ package jade.mtp.http; import jade.domain.FIPAAgentManagement.Envelope; import jade.util.Logger; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.NoSuchElementException; import java.util.StringTokenizer; public class HTTPIO { // Response codes public static final String OK = "200 OK"; private static final String ERROR = "406 Not Acceptable"; //private static final String UNAV = "503 Service Unavailable"; // HTTP constants private static final String HTTP1 = "HTTP/1."; private static final byte[] PROXY = {(byte) 'P',(byte) 'r',(byte) 'o',(byte) 'x',(byte) 'y',(byte) '-',(byte) 'C',(byte) 'o',(byte) 'n',(byte) 'n',(byte) 'e',(byte) 'c',(byte) 't',(byte) 'i',(byte) 'o',(byte) 'n',(byte) ':',(byte) ' '}; private static final String PROXY_STR = "Proxy-Connection: "; private static final byte CR = (byte) '\r'; private static final byte LF = (byte) '\n'; private static final byte[] CRLF = {(byte) CR,(byte) LF}; private static final byte[] POST = {(byte) 'P',(byte) 'O',(byte) 'S',(byte) 'T'}; private static final String POST_STR = "POST"; private static final byte[] CONTENT = {(byte) 'C',(byte) 'o',(byte) 'n',(byte) 't',(byte) 'e',(byte) 'n',(byte) 't',(byte) '-',(byte) 'T',(byte) 'y',(byte) 'p',(byte) 'e',(byte) ':',(byte) ' '}; private static final String CONTENT_STR = "Content-Type: "; private static final byte[] CLENGTH = {(byte) 'C',(byte) 'o',(byte) 'n',(byte) 't',(byte) 'e',(byte) 'n',(byte) 't',(byte) '-',(byte) 'L',(byte) 'e',(byte) 'n',(byte) 'g',(byte) 't',(byte) 'h',(byte) ':',(byte) ' '}; private static final byte[] MM = {(byte) 'm',(byte) 'u',(byte) 'l',(byte) 't',(byte) 'i',(byte) 'p',(byte) 'a',(byte) 'r',(byte) 't',(byte) '/',(byte) 'm',(byte) 'i',(byte) 'x',(byte) 'e',(byte) 'd'}; private static final String MM_STR = "multipart/mixed"; private static final byte[] BND = {(byte) 'b',(byte) 'o',(byte) 'u',(byte) 'n',(byte) 'd',(byte) 'a',(byte) 'r',(byte) 'y'}; private static final String BND_STR = "boundary"; private static final byte[] APPLI = {(byte) 'a',(byte) 'p',(byte) 'p',(byte) 'l',(byte) 'i',(byte) 'c',(byte) 'a',(byte) 't',(byte) 'i',(byte) 'o',(byte) 'n',(byte) '/'}; private static final byte[] CONN = {(byte) 'C',(byte) 'o',(byte) 'n',(byte) 'n',(byte) 'e',(byte) 'c',(byte) 't',(byte) 'i',(byte) 'o',(byte) 'n',(byte) ':',(byte) ' '}; private static final String CONN_STR = "Connection: "; public static final String CLOSE = "close"; public static final String KA = "Keep-Alive"; private static final byte[] HTTP = {(byte) 'H',(byte) 'T',(byte) 'T',(byte) 'P',(byte) '/',(byte) '1',(byte) '.',(byte) '1'}; private static final byte[] CACHE = {(byte) 'C',(byte) 'a',(byte) 'c',(byte) 'h',(byte) 'e',(byte) '-',(byte) 'C',(byte) 'o',(byte) 'n',(byte) 't',(byte) 'r',(byte) 'o',(byte) 'l',(byte) ':',(byte) ' ',(byte) 'n',(byte) 'o',(byte) '-',(byte) 'c',(byte) 'a',(byte) 'c',(byte) 'h',(byte) 'e'}; private static final byte[] MIME = {(byte) 'M',(byte) 'i',(byte) 'm',(byte) 'e',(byte) '-',(byte) 'V',(byte) 'e',(byte) 'r',(byte) 's',(byte) 'i',(byte) 'o',(byte) 'n',(byte) ':',(byte) ' ',(byte) '1',(byte) '.',(byte) '0'}; private static final byte[] HOST = {(byte) 'H',(byte) 'o',(byte) 's',(byte) 't',(byte) ':',(byte) ' '}; private static final String HOST_STR = "Host: "; private static final byte[] DL = {(byte) '-',(byte) '-'}; private static final String DL_STR = "--"; private static final String BLK = ""; private static final byte[] MIME_MULTI_PART_HEADER = {(byte) 'T',(byte) 'h',(byte) 'i',(byte) 's',(byte) ' ',(byte) 'i',(byte) 's',(byte) ' ',(byte) 'n',(byte) 'o',(byte) 't',(byte) ' ',(byte) 'p',(byte) 'a',(byte) 'r',(byte) 't',(byte) ' ',(byte) 'o',(byte) 'f',(byte) ' ',(byte) 't',(byte) 'h',(byte) 'e',(byte) ' ', (byte) 'M',(byte) 'I',(byte) 'M',(byte) 'E',(byte) ' ',(byte) 'm',(byte) 'u',(byte) 'l',(byte) 't',(byte) 'i',(byte) 'p',(byte) 'a',(byte) 'r',(byte) 't',(byte) ' ',(byte) 'e',(byte) 'n',(byte) 'c',(byte) 'o',(byte) 'd',(byte) 'e',(byte) 'd',(byte) ' ', (byte) 'm',(byte) 'e',(byte) 's',(byte) 's',(byte) 'a',(byte) 'g',(byte) 'e',(byte) '.'}; private static final byte[] XML = {(byte) 'x',(byte) 'm',(byte) 'l'}; private static final byte[] CHARSET = {(byte) ';',(byte) ' ',(byte) 'c',(byte) 'h',(byte) 'a',(byte) 'r',(byte) 's',(byte) 'e',(byte) 't',(byte) '='}; private static final byte[] TEXT = {(byte) 't',(byte) 'e',(byte) 'x',(byte) 't'}; private static final byte[] TEXT_HTML = {(byte) 't',(byte) 'e',(byte) 'x',(byte) 't',(byte) '/',(byte) 'h',(byte) 't',(byte) 'm',(byte) 'l'}; private static final byte[] HTML_BEGIN = {(byte) '<',(byte) 'h',(byte) 't',(byte) 'm',(byte) 'l',(byte) '>',(byte) '<',(byte) 'b',(byte) 'o',(byte) 'd',(byte) 'y',(byte) '>',(byte) '<',(byte) 'h',(byte) '1',(byte) '>'}; private static final byte[] HTML_END = {(byte) '<',(byte) '/',(byte) 'h',(byte) '1',(byte) '>',(byte) '<',(byte) '/',(byte) 'b',(byte) 'o',(byte) 'd',(byte) 'y',(byte) '>',(byte) '<',(byte) '/',(byte) 'h',(byte) 't',(byte) 'm',(byte) 'l',(byte) '>'}; private static Logger logger = Logger.getMyLogger(HTTPIO.class.getName()); /* *********************************************** * WRITE METHODS * ***********************************************/ /** * Write the message to the OutputStream associated to the Sender */ public static void writeAll(OutputStream output, byte[] message) throws IOException { output.write(message); output.write(CRLF); output.flush(); } /** * Create a generic message of HTTP with the input msgCode * and type of connection (close or Keep-Alive) */ public static byte[] createHTTPResponse(String msgCode, String type) { ByteArrayOutputStream message = new ByteArrayOutputStream(256); try { message.write(HTTP); message.write(' '); writeLowBytes(message,msgCode); message.write(CRLF); message.write(CONTENT); message.write(TEXT_HTML); message.write(CRLF); message.write(CACHE); message.write(CRLF); message.write(CONN); writeLowBytes(message,type); message.write(CRLF); message.write(CRLF); message.write(HTML_BEGIN); writeLowBytes(message,msgCode); message.write(HTML_END); } catch (IOException exception) { exception.printStackTrace(); } return message.toByteArray(); } /** * Prepare the HTML header */ public static byte[] createHTTPHeader(HTTPAddress host, int length, String policy, byte[] boundary, boolean proxy) { //Put the header ByteArrayOutputStream header = new ByteArrayOutputStream(256); try { header.write(POST); header.write(' '); writeLowBytes(header,host.toString()); header.write(' '); header.write(HTTP); header.write(CRLF); header.write(CACHE); header.write(CRLF); header.write(MIME); header.write(CRLF); header.write(HOST); writeLowBytes(header,host.getHost()); header.write(':'); writeLowBytes(header,host.getPort()); header.write(CRLF); header.write(CONTENT); header.write(MM); header.write(' '); header.write(';'); header.write(' '); header.write(BND); header.write('='); header.write('\"'); header.write(boundary); header.write('\"'); header.write(CRLF); //put the Content-Length header.write(CLENGTH); writeLowBytes(header,Integer.toString(length)); header.write(CRLF); //put the Connection policy if (proxy) { header.write(PROXY); writeLowBytes(header,policy); header.write(CRLF); } else { header.write(CONN); writeLowBytes(header,policy); header.write(CRLF); } header.write(CRLF); header.flush(); } catch (IOException exception) { exception.printStackTrace(); } return header.toByteArray(); } /** * Prepare the HTML body */ public static byte[] createHTTPBody(Envelope env, byte[] boundary, byte[] payload) { ByteArrayOutputStream body = new ByteArrayOutputStream(payload.length + 100); try { //PREPARE BODY body.write(MIME_MULTI_PART_HEADER); body.write(CRLF); body.write(DL); body.write(boundary); body.write(CRLF); //Insert The XML envelope // Put the Content-Type body.write(CONTENT); body.write(APPLI); body.write(XML); body.write(CRLF); body.write(CRLF); //A empty line env.setPayloadLength(new Long(payload.length)); writeLowBytes(body,XMLCodec.encodeXML(env)); body.write(CRLF); //Put the boundary delimit. body.write(DL); body.write(boundary); body.write(CRLF); //Insert the ACL message //Put the Content-Type String payloadEncoding = env.getPayloadEncoding(); if ((payloadEncoding != null) && (payloadEncoding.length() > 0)) { body.write(CONTENT); writeLowBytes(body,env.getAclRepresentation()); body.write(CHARSET); writeLowBytes(body,payloadEncoding); } else { body.write(CONTENT); body.write(APPLI); body.write(TEXT); } body.write(CRLF); body.write(CRLF); //ACL part //Insert the ACL payload body.write(payload); body.write(CRLF); //Put the final boundary body.write(DL); body.write(boundary); body.write(DL); body.write(CRLF); body.flush(); } catch (IOException exception) { exception.printStackTrace(); } return body.toByteArray(); } /* *********************************************** * READS METHODS * ***********************************************/ /** * Blocks on read until something is available on the stream * or the stream is closed */ /* public static String blockOnRead(BufferedReader br) throws IOException { //Skip empty lines String line = null; while(BLK.equals(line=br.readLine())); return line; } */ /** * Parse the input message, this message is received from the master server * @param type return type of connection: close or Keep-Alive */ public static String readAll(InputStream input, StringBuffer xml, OutputStream acl, StringBuffer type) throws IOException { //For the Control of sintaxis String host = null; //boolean foundMime = false; boolean foundBoundary = false; //boolean findContentType = false; String boundary = null; //String line = null; String typeConnection = null; //Reset the Buffers // NL: Not supported on PJava /* if(xml.length()>0) xml.delete(0,xml.length()); if(acl.length()>0) acl.delete(0,acl.length()); if(connection.length()>0) connection.delete(0,connection.length()); */ //try { String line; while(BLK.equals(line=readLineFromInputStream(input))); // skip empty lines if(line==null) throw new IOException(); StringTokenizer st = new StringTokenizer(line); try { if(!(st.nextToken()).equalsIgnoreCase(POST_STR) ) { if(logger.isLoggable(Logger.WARNING)) logger.log(Logger.WARNING,"Malformed POST"); type.append(CLOSE); return ERROR; } st.nextToken(); // Consumme a token if(!(st.nextToken().toUpperCase().startsWith("HTTP/1."))) { if(logger.isLoggable(Logger.WARNING)) logger.log(Logger.WARNING,"Malformed HTTP/1.1 "); type.append(CLOSE); return ERROR; } } catch(NoSuchElementException nsee) { if(logger.isLoggable(Logger.WARNING)) logger.log(Logger.WARNING,"Malformed start line !: "+line); type.append(CLOSE); return ERROR; } //Process rest of header while (!BLK.equals(line=readLineFromInputStream(input))) { String lowerCaseLine = line.toLowerCase(); if (lowerCaseLine.startsWith(HOST_STR.toLowerCase())) { host = processLine(line); //De momento solo controlamos que este } /* // NL do not test MIME version for interoperability with other MTP if (line.toLowerCase().startsWith(MIME.toLowerCase())) { foundMime = true; } */ if (lowerCaseLine.startsWith(CONN_STR.toLowerCase())) { typeConnection= processLine(line); } if (lowerCaseLine.startsWith(CONTENT_STR.toLowerCase())) { //Process the left part if (!(processLine(line).toLowerCase().startsWith(MM_STR))) { if(logger.isLoggable(Logger.WARNING)) logger.log(Logger.WARNING,"MULTIPART/MIXED"); type.append(CLOSE); return ERROR; } //Process the right part int pos = line.indexOf(BND_STR); if (pos == -1) { // Boundary on next line line=readLineFromInputStream(input); if ((pos = line.indexOf(BND_STR)) == -1) { // Bounday not found if(logger.isLoggable(Logger.WARNING)) logger.log(Logger.WARNING,"MIME boundary not found"); type.append(CLOSE); return ERROR; } } line = line.substring(pos+BND_STR.length()); pos = line.indexOf("\"")+1; boundary = DL_STR+line.substring(pos,line.indexOf("\"",pos)); foundBoundary = true; } }//end while //if( !foundBoundary || !foundMime) { if(!foundBoundary) { if(logger.isLoggable(Logger.WARNING)) logger.log(Logger.WARNING,"Mime header error"); type.append(CLOSE); return ERROR; } if (typeConnection == null) { type.append(KA); //Default Connection } else { type.append(typeConnection); //Connection of request } //jump to first "--Boundary" while(BLK.equals(line=readLineFromInputStream(input))); // skip empty lines do { if (line.startsWith(boundary)) { break; } } while(!BLK.equals(line=readLineFromInputStream(input))); while(BLK.equals(line=readLineFromInputStream(input))); // skip empty lines // Skip content-type do { if(line.toLowerCase().startsWith(CONTENT_STR.toLowerCase())) { break; } } while(!BLK.equals(line=readLineFromInputStream(input))); //Capture the XML part //Capture the message envelope while(!boundary.equals(line=readLineFromInputStream(input))) { if (! line.equals(BLK)) { xml.append(line); } } //Capture the ACL part //JMP to ACLMessage while(BLK.equals(line=readLineFromInputStream(input))); // skip empty lines // Skip content-type do { if(line.toLowerCase().startsWith(CONTENT_STR.toLowerCase())) { break; } } while(!BLK.equals(line=readLineFromInputStream(input))); //Create last boundary for capture the ACLMessage ByteArrayOutputStream boundaryPattern = new ByteArrayOutputStream(boundary.length()+6); boundaryPattern.write(CRLF); boundaryPattern.write(boundary.getBytes("ISO-8859-1")); boundaryPattern.write(DL); //Capture the acl part. int character = -1; while(((character = input.read()) == CR ) || (character == LF)) {}; // Dirty hack: Skip leading blank lines. if (character >= 0) { acl.write(character); readBytesUpTo(input,acl,boundaryPattern.toByteArray()); } return OK; /* } catch(NullPointerException npe) { // readLine returns null <--> EOF System.out.println("null pointer in readAll"); //npe.printStackTrace(); type.append(CLOSE); return ERROR; } */ } /** * Capture and return the code of response message, this message is received from client */ public static int getResponseCode(InputStream input, StringBuffer type) throws IOException { int responseCode = -1; try { String line = null; //Capture and process the response message while (!(line=readLineFromInputStream(input)).startsWith(HTTP1)); //capture the response code responseCode= Integer.parseInt(processLine(line)); //Read all message while(((line=readLineFromInputStream(input))!=null)&&(!line.equals(BLK))) { if (line.toLowerCase().startsWith(CONN_STR.toLowerCase())) { type.append(processLine(line)); } else if (line.toLowerCase().startsWith(PROXY_STR.toLowerCase())) { type.append(processLine(line)); } } if (type.length() == 0) { type.append(KA); //Default Connection type } return responseCode; } catch(Exception e) { // Connection has been closed before we receive confirmation. // We do cannot know if message has been received type.append(CLOSE); return responseCode; // NOT OK } } /** * return the next information of search in the line */ private static String processLine(String line) throws IOException { StringTokenizer st = new StringTokenizer(line); try { st.nextToken(); // Consumme first token return st.nextToken(); } catch(NoSuchElementException nsee) { throw new IOException("Malformed line !: "+line); } } /** * Reads byte sequence from specified input stream into specified output stream up to specified * byte sequence pattern is occurred. The output byte sequence does not contains any bytes matched * with pattern. If the specified pattern was not found until the input stream reaches at end, output * all byte sequence up to end of input stream and returns false. * * @param input specified input stream. * @param output specified output stream. * @param pattern specified pattern byte seqence. * @return Whether the specified pattern was found or not. * @throws IOException If an I/O error occurs. * @throws IllegalArgumentException If pattern is null or pattern is empty. * @author mminagawa */ private static boolean readBytesUpTo(InputStream input, OutputStream output, byte[] pattern) throws IOException { if ((pattern == null) || (pattern.length == 0)) { throw new IllegalArgumentException("Specified pattern is null or empty."); } int patternIndex = 0; boolean matched = false; boolean atEnd = false; while ((!matched) && (!atEnd)) { int readByte = input.read(); if (readByte < 0) { atEnd = true; if (patternIndex != 0) { output.write(pattern,0,patternIndex); patternIndex = 0; } } else { if (readByte == pattern[patternIndex]) { patternIndex++; if (patternIndex >= pattern.length) { matched = true; } } else { if (patternIndex != 0) { output.write(pattern,0,patternIndex); patternIndex = 0; } output.write(readByte); } } } return matched; } /** * Read a line of text from specified input stream. A line is considered to be * terminated by a carriage return ('\r') followed immediately by a linefeed ('\n'). * * @param input specified input stream to read from. * @return A String containing the contents of the line, not including any line-termination * characters, or null if the end of the stream has been reached. * @throws IOException If an I/O error occurs. * @author mminagawa */ private static String readLineFromInputStream(InputStream input) throws IOException { StringBuffer buffer = new StringBuffer(256); int characterByte; boolean justBeforeCR = false; boolean terminated = false; boolean entered = false; while ((!terminated) && ((characterByte = input.read()) >= 0)) { entered = true; switch (characterByte) { case CR : if (justBeforeCR) { buffer.append((char)CR); } else { justBeforeCR = true; } break; case LF : if (justBeforeCR) { terminated = true; } else { buffer.append((char)LF); } justBeforeCR = false; break; default : if (justBeforeCR) { buffer.append((char)CR); } buffer.append((char)characterByte); justBeforeCR = false; } } if (!entered) { return null; } if ((!terminated) && (justBeforeCR)) { buffer.append((char)CR); } return buffer.toString(); } /** * Write characters contained specified string to specified output stream.<br /> * These characters must be 7-bit character, and stored only low-byte of each code. * * @param output specified output stream. * @param string specified string to output. * @throws IOException If an I/O error occurs. * @author mminagawa */ private static void writeLowBytes(OutputStream output, String string) throws IOException { for (int i = 0 ; i < string.length() ; i++ ) { output.write(string.charAt(i)); } } } // End of HTTPIO class