View Javadoc

1       /*
2        * The contents of this file are subject to the BT "ZEUS" Open Source
3        * Licence (L77741), Version 1.0 (the "Licence"); you may not use this file
4        * except in compliance with the Licence. You may obtain a copy of the Licence
5        * from $ZEUS_INSTALL/licence.html or alternatively from
6        * http://www.labs.bt.com/projects/agents/zeus/licence.htm
7        *
8        * Except as stated in Clause 7 of the Licence, software distributed under the
9        * Licence is distributed WITHOUT WARRANTY OF ANY KIND, either express or
10       * implied. See the Licence for the specific language governing rights and
11       * limitations under the Licence.
12       *
13       * The Original Code is within the package zeus.*.
14       * The Initial Developer of the Original Code is British Telecommunications
15       * public limited company, whose registered office is at 81 Newgate Street,
16       * London, EC1A 7AJ, England. Portions created by British Telecommunications
17       * public limited company are Copyright 1996-2001. All Rights Reserved.
18       *
19       * THIS NOTICE MUST BE INCLUDED ON ANY COPY OF THIS FILE
20       */
21  
22  package zeus.actors.intrays;
23  import java.net.*;
24  import java.io.*;
25  import zeus.util.*;
26  import FIPA.*;
27  import zeus.concepts.*;
28  import zeus.actors.outtrays.*;
29  import zeus.actors.*;
30  import zeus.actors.factories.*;
31  
32  
33  /***
34   * FIPA_2000_HTTP_Connection handles incomming connections on this host/port socket
35   * and decides
36   * whether or not they are meant for it (ie: is the name on the connection the
37   * same as the name in the init of this class)<p>
38   * If the connection is relevant then the data will be read, a response (as per
39   * the spec ) will be sent and the message will be decoded into a FIPA.FipaMessage
40   * and placed on the registered queue for processing by the relevant server <p>
41   * The class constructor takes a host a port and a name : should this agent only listen
42   * for connections for itself at this port and host? That is what is implemented here...
43   * comments on a postcard please<p>
44   * @author Simon Thompson
45   * @since 1.1
46   */
47  public class FIPA_2000_HTTP_Connection  implements Runnable {
48      protected Queue queue;
49      private String response_ok = new String("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCache-Control: no-cache\r\nConnection: close\n");
50      private String response_not_ok = new String("HTTP/1.1 401 NOT_OK \nContent-Type: text/plain\nCache-Control: no-cache\nConnection: close\n");
51      private String host = null;
52      private String name = null;
53      private String port = null;
54      private int MAX_HTTP_QUEUE = 10;
55      /***
56       * MAX_LINES_TO_READ_WITHOUT_YEILDING is used to control how
57       * many lines will be read in from a connection in one lump. Without this I
58       * think that a DOS attack could be mounted via a http connection just
59       * by sending it an infinitely long message: perhaps another agent could
60       * use this to prevent this agent from bidding in a round of contracts, or
61       * from sending some alert information to another agent! <p>
62       * This is protected to prevent it being reset in Agent specific code
63       */
64      protected int MAX_LINES_TO_READ_WITHOUT_YEILDING = 5000;
65      protected int MAX_LINES_TO_READ_WITHOUT_SHUTTING = 5000;
66      /***
67       * if MAX_CONTENT_LENGHT is greater than 200001 it will be ignored and
68       * max_int length content will be accepted
69       */
70      protected int MAX_CONTENT_LENGTH = 100000;
71      private ServerSocket serverSocket = null;
72      
73      
74      /***
75       * run method that makes sure a thread is trying to pick up inputs from a
76       * connection <p>
77       * The loop blocks on the ServerSocket.accept() method. A count variable
78       * is used to prevent an DOS on this agent by yeilding after a certain number
79       * of lines have been read.
80       */
81      public void run() {
82          while (true) {
83              try {
84                  String currentMessage = new String();
85                  debug("Listening in http");
86                  Socket connection = serverSocket.accept();
87                  //     InetAddress inet = connection.getInetAddress();
88                  //    debug("name " + inet.getHostName() + "  address " + inet.getHostAddress());
89                  
90                  PrintWriter out = new PrintWriter(connection.getOutputStream(), true );
91                  InputStreamReader ins = new InputStreamReader(connection.getInputStream());
92                  BufferedReader in = new BufferedReader(ins);
93                  int count = 0;
94                  String ln;
95                  boolean done = false;
96                  String contentStr = null;
97                  String boundaryVal = null;
98                  int contentLength = 0;
99                  int bigCount = 0;
100                 while(!done) {
101                     ln=in.readLine();
102                     debug(ln);
103                     currentMessage += ln +"\n";
104                     if (boundaryVal == null)
105                         boundaryVal = testAndSetBoundary(ln);
106                     if (contentLength == 0)
107                         contentLength = testAndSetContentLength(ln);
108                     
109                     if (contentLength > MAX_CONTENT_LENGTH) {
110                         respondNotOK(out);
111                         in.close();
112                         out.close();
113                         connection.close(); // reset peer, no DOS!
114                     }
115                     debug("boundary=" + boundaryVal);
116                     // debug (String.valueOf(contentLength));
117                     if ((boundaryVal != null) && (contentLength > 0)) { // start watching for a header end
118                         //  debug("finding boundary");
119                         if (ln.equals("--" + boundaryVal)) {
120                             debug("found boundary");
121                             contentStr = new String();
122                             ln = in.readLine();
123                             String ender = "--" + boundaryVal + "--";
124                             
125                             while (!ender.equals(ln.trim())) {
126                                 debug("content:" + ln);
127                                 contentStr += ln +" "; // added " "
128                                 ln = in.readLine();
129                                 
130                                 
131                             }
132                             contentStr += ender;
133                            /* char [] content = new char [contentLength];
134                             in.read(content,0,contentLength);
135                             contentStr = new String (content); */
136                             //  debug ("content length = " + contentStr.length());
137                             // debug ("\n\ncontent = " + contentStr);
138                             done = true;
139                         }
140                     }
141                     
142                     debug("readIn >> " + ln);
143                     bigCount++;
144                     count++;
145                     //debug("before maxlines");
146                     if (count > MAX_LINES_TO_READ_WITHOUT_YEILDING) {
147                         Thread.yield();
148                         count = 0; }
149                     //debug("before connection clsoser");
150                     if (bigCount > MAX_LINES_TO_READ_WITHOUT_SHUTTING) {
151                         //respondNotOK(out);
152                         in.close();
153                         out.close();
154                         connection.close(); // reset peer, no DOS!
155                     }
156                     //debug("after connection closer");
157                     /*if (done) {
158                         debug ("DONE!!");
159                         }
160                         else {
161                                 debug ("NOT DONE!!!");
162                                 }  */
163                 }
164                 // in.close();
165                 //debug ("1");
166                 debug(contentStr);
167                 FIPAPerformative fmess = process(contentStr,boundaryVal);
168                 //debug("2");
169                 if (fmess!= null) {
170                     respondOK(out);
171                     message(fmess); }
172                 else {
173                     respondNotOK(out);
174                 }
175                 //  debug("3");
176                 //       Thread.yield();
177                 connection.close();
178                 //debug("4");
179             }
180             catch (IOException ioe) {
181                 ioe.printStackTrace();
182                 System.out.println("IO error opening http connection from another agent: recovering");
183             }
184             catch (Exception e) {
185                 e.printStackTrace();}
186             catch (Error er) {
187                 System.out.println("Error trapped and handled in FIPA_2000_HTTP_Connection: recovering");
188                 er.printStackTrace(); }
189             catch (Throwable tr) {
190                 System.out.println("*** Throwable trapped and handled in FIPA_2000_HTTP_Connection: recovering");
191             }
192             
193         }
194         
195     }
196     
197     
198     /***
199      * hitch this connection to a message queue
200      */
201     public void register(Queue q)  {
202         this.queue = q;
203     }
204     
205     
206     /***
207      * pull the boundary val from the header
208      */
209     private String testAndSetBoundary(String ln) {
210         
211         String tmp = ln.toLowerCase();
212         debug(">> test boundary:" + tmp);
213         // works for jade
214         if (tmp.startsWith("content-type: multipart/mixed; boundary=\"")) {
215             //     debug (ln.substring (41,ln.length()-1));
216             debug(">> returning boundary!");
217             return (ln.substring(41,ln.length()-1));
218         }
219         if (tmp.startsWith("content-type: multipart/mixed;  boundary=\"")) {
220             //     debug (ln.substring (41,ln.length()-1));
221             debug(">> returning boundary!");
222             return (ln.substring(42,ln.length()-1));
223         }
224         if (tmp.startsWith("content-type: multipart/mixed ; boundary=\""))    {
225             return (ln.substring(42,ln.length()-1));
226         }
227         else if (tmp.startsWith("\tboundary=\"")) {
228             //         debug (ln.substring (11,ln.length()-1));
229             debug(">> returning boundary!");
230             return (ln.substring(11, ln.length()-1));
231         }
232         else {
233             debug(">> returning null!");
234             return null;
235         }
236     }
237     
238     
239     /***
240      * pull the content length from the header
241      */
242     private int testAndSetContentLength(String ln) {
243         if (ln.startsWith("Content-length") || ln.startsWith("Content-Length")) {
244             String lengthContent = ln.substring(15,ln.length()).trim();
245             int contentLength = Integer.parseInt(lengthContent);
246             //       debug (String.valueOf(contentLength));
247             return contentLength;
248         }
249         else {
250             return 0;
251         }
252     }
253     
254     
255     
256     /***
257      * send the mandated response to a successful message reception
258      * episode (see XC00084C)
259      */
260     public void respondOK(PrintWriter out) {
261         out.println(response_ok);
262         out.flush();
263         out.close();
264     }
265     
266     
267     /***
268      * send a not OK response (hopefully we won't have to do this!)<p>
269      * This is not something that I could see mandated in the FIPA-spec,
270      * but I think that it is a good idea - otherwise the connection remains open
271      * until a timeout. <p>
272      * Perhaps sufficient open connections could be used as some sort of attack
273      * on the agent.
274      */
275     public void respondNotOK(PrintWriter out) {
276         out.println(response_not_ok);
277         out.flush();
278         out.close();
279     }
280     
281     
282     /***
283      * message is used to handle the completely read and parsed message when
284      * it has come off the message queue
285      */
286     public void message(FIPAPerformative aFipaMessage) {
287         //  FIPAPerformative perf = new FIPAPerformative (aFIPAMessage);
288         queue.enqueue(aFipaMessage);
289     }
290     
291     
292     public FIPA_2000_HTTP_Connection(String host, String port, String name) {
293         this.host = host;
294         this.name = name;
295         this.port = port;
296         try {
297             
298             java.io.File file = new java.io.File("http.out");
299             java.io.FileOutputStream outStream = new java.io.FileOutputStream(file);
300             java.io.PrintStream err = new java.io.PrintStream(outStream);
301             // System.setErr(err);
302             
303             serverSocket = new ServerSocket(Integer.parseInt(port),MAX_HTTP_QUEUE); }
304         catch (IOException e) {
305             e.printStackTrace();
306             System.out.println("cannot get a socket to listen for http connections on: recovering (without HTTP connection)");
307         }
308         
309         
310     }
311     
312     
313     /***
314      * used to test the code here!
315      */
316     protected boolean test(String myPort, String testHost, String testPort) {
317         try {
318             TransportFactory tf = new IIOP_Z_HTTP_TransportFactory();
319             InetAddress ip = InetAddress.getLocalHost();
320             String localHost = ip.getHostAddress();
321             String targetAddress = new String("http://"+testHost+":"+testPort+"/test");
322             String sourceAddress = new String("http://"+localHost+":"+myPort+"/test");
323             OutTray transport = tf.getTransport(targetAddress);
324             FIPAPerformative fperf = new FIPAPerformative("inform");
325             javax.agent.Envelope env = fperf.jasEnvelope(new FIPA_AID_Address(sourceAddress),targetAddress);
326             transport.send(env);
327             return true; }
328         catch (Exception e ) {
329             e.printStackTrace();
330             return false;
331         }
332     }
333     
334     
335     /***
336      * main method for testing this module<p>
337      * <p> parameters are <port number for this> <host to test connection to> <port on test host>
338      */
339     public static void main(String argv[]) {
340         String myPort = argv[0];
341         String testHost = argv[1];
342         String testPort = argv[2];
343         FIPA_2000_HTTP_Connection test = new FIPA_2000_HTTP_Connection("tb-toledo.futures.bt.co.uk",myPort,"test");
344         boolean done = false;
345         while (!done) {
346             try {
347                 Thread.sleep(1000);
348             }
349             catch (Exception e) {
350                 e.printStackTrace();
351             }
352             //  done = test.test(myPort,testHost,testPort);
353         }
354         
355     }
356     
357     
358     private FIPAPerformative process(String inMess,String bound) {
359         debug("inMess == \n" + inMess + "\n end inMess");
360         String message = stripEnvelope(inMess, bound);
361         debug ("......\n " + message + " -------"); 
362         FIPAPerformative fperf = ZeusParser.fipaPerformative(message);
363         String content = fperf.getContent();
364         try {
365             fperf.setContent (addEscape(content));}
366         catch (Exception e) { 
367             System.out.println("EXCEPTION"); 
368             e.printStackTrace(); 
369         }
370         debug("outMess == \n" + fperf.getContent() + "\n end inMess");
371         return (fperf);
372         
373     }
374     
375     private String addEscape (String in) { 
376         try { // content can be null 
377      int counter = 0; 
378      int pointer = 0; 
379      StringBuffer buf = new StringBuffer (in);
380      StringBuffer newOne = new StringBuffer(); 
381      while (counter<in.length()-1) {
382          char thisOne = buf.charAt(counter); 
383          if (thisOne != '"') {
384              newOne.append(thisOne); 
385          }
386          else {
387              System.out.println("replacing"); 
388              newOne.append('//'); 
389              newOne.append('//'); 
390              newOne.append('//'); 
391              newOne.append(thisOne); 
392          }
393          counter++; 
394      }
395      String ret = newOne.toString(); 
396      return ret; }
397         catch (Exception e) { 
398             return (new String()); 
399         } 
400     }
401     
402     /***
403      * crude for the moment - this needs redoing so it is less fragile.
404      */
405     private String stripEnvelope(String HTTPmessage,String bound) {
406         //         debug ("boundary == " + bound);
407         String remLastBoundary = HTTPmessage.substring(0, HTTPmessage.lastIndexOf(bound)-2);
408         //  debug (remLastBoundary);
409         //add
410         String message = remLastBoundary.substring(HTTPmessage.indexOf(bound)+bound.length(),remLastBoundary.length());
411         message =  remLastBoundary.substring(remLastBoundary.indexOf('('),remLastBoundary.length());
412         //endadd
413         //           String message = remLastBoundary.substring (HTTPmessage.indexOf(bound)+bound.length()+30, remLastBoundary.length());
414         //      debug ("");
415         //    debug ("");
416         //    debug ("stripped content = "+message);
417         return message.trim();
418     }
419     
420     
421     private void debug(String val) {
422         System.out.println("\nFIPA_2000_HTTP_Connection>>"+val+"\n");
423     }
424 }