001    /*
002     * LAPIS lightweight structured text processing system
003     *
004     * Copyright (C) 1998-2002 Carnegie Mellon University,
005     * Copyright (C) 2003 Massachusetts Institute of Technology.
006     * All rights reserved.
007     *
008     * This library is free software; you can redistribute it
009     * and/or modify it under the terms of the GNU General
010     * Public License as published by the Free Software
011     * Foundation, version 2.
012     *
013     * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/
014     */
015    
016    package lapisx.net;
017    
018    import java.net.*;
019    import java.io.*;
020    import java.util.*;
021    import java.lang.reflect.*;
022    
023    // FIX: support multipart/form-data as well
024    public class FormURL {
025        String method;
026        URL action;
027        URL url;
028        String queryString;
029        Vector queryVector;
030    
031        /**
032         * Create a query using a given method (get or post) and query data.
033         * @param method "get" or "post" (case-insensitive).
034         * @param url URL to send query to.  Must be http:
035         * @param query Name/value pairs to simulate submitting a form.  Names and values may be arbitrary objects; this method uses toString() to convert them to strings, then URL-encodes them before sending to the web server.  Pass null if no form data is desired.  
036         */
037        public FormURL (String method, URL url, Vector query) {
038            this (method, url, (query != null) ? encodeQuery (query) : null);
039            queryVector = query;
040        }
041    
042        /**
043         * Open a URL using a given method (get or post) and query data.
044         * @param method "get" or "post" (case-insensitive). 
045         * @param url URL to open.  Must be http:
046         * @param query URL-encoded name/value pairs, or null if no query desired
047         */
048        public FormURL (String method, URL url, String query) {
049            this.method = method;
050            this.action = url;
051            this.url = url;
052            this.queryString = (query != null) ? query : "";
053    
054            if (method == null || "get".equalsIgnoreCase (method)) {
055                try {
056                    this.url = new URL (URLUtil.getServiceURL (url) + "?" + query);
057                } catch (MalformedURLException e) {
058                    throw new RuntimeException (e.toString ());// shouldn't happen
059                }
060            }
061        }
062    
063        // FIX: constructor from a String (assuming GET), or String + method
064    
065        public String getMethod () {
066            return method;
067        }
068    
069        public URL getAction () {
070            return url;
071        }
072    
073        public URL getURL () {
074            return url;
075        }
076    
077        public String getQueryString () {
078            return queryString;
079        }
080    
081        // FIX: write decodeQuery, so that this can work even when
082        // FormURL is constructed from a string, not a hash
083        public Vector getQueryVector () {
084            return queryVector;
085        }
086    
087        public String toString () {
088            return url.toString ();
089        }
090    
091        /**
092         * Open a connection ready to submit query and retrieve its results.
093         * @return URLConnection (set up for connection but not yet connected)
094         */
095        public URLConnection openConnection () throws IOException {
096            if (!"http".equals (url.getProtocol ()))
097                throw new IOException ("query URL must be http:"); 
098    
099            URLConnection conn = url.openConnection ();
100    
101            // handle POST method
102            if ("post".equalsIgnoreCase (method)) {
103                conn.setDoOutput (true);
104                conn.setUseCaches (false);
105                conn.setRequestProperty ("Content-type",
106                                         "application/x-www-form-urlencoded");
107                conn.setRequestProperty ("Content-length", String.valueOf (queryString.length()));
108    
109                    // commence request
110                PrintWriter out = null;
111                try {
112                    out = new PrintWriter (new OutputStreamWriter
113                                           (conn.getOutputStream ()));
114    //                 System.err.println ("Sending by POST:\n" + queryString + "\ndone");
115                    out.print (queryString);
116                    out.flush ();
117                } finally {
118                    if (out != null)
119                        out.close ();
120                }
121            }
122    
123            return conn;
124        }
125    
126        /**
127         * URL-encode a sequence of name-value pairs.
128         * @param query Name/value pairs to encode; even indexes are names, odd indexes are values
129         * @return String of URL-encoded name/value pairs
130         */
131        public static String encodeQuery (Vector query) {
132            StringBuffer result = new StringBuffer ();
133    
134            for (int i = 0, n = query.size (); i < n; i+=2) {
135                String name = (String)query.elementAt (i);
136                String value = (String)query.elementAt (i+1);
137                
138                if (result.length () > 0)
139                    result.append ('&');
140                
141                result.append (ENCODER.encode (name));
142                result.append ('=');
143                result.append (ENCODER.encode (value));
144            }
145            return result.toString ();
146        }
147    
148        //
149        // The following craziness calls the deprecated URLEncoder.encode(string)
150        // only when we have to, in Java 1.2 and 1.3.  In Java 1.4 and later,
151        // we call the preferred URLEncoder.encode(string, character-encoding).
152        //
153        private static Encoder ENCODER;
154    
155        private interface Encoder {
156            public String encode (String s);
157        }
158    
159        /**
160         * Wrapper for deprecated Java 1.2/1.3 URLEncoder.encode().
161         */
162        private static class Encoder2 implements Encoder {
163            Class cls;
164            Method method;
165            public Encoder2 () throws Exception {
166                cls = Class.forName ("java.net.URLEncoder");
167                method = cls.getMethod ("encode", new Class[] {String.class});
168            }            
169            public String encode (String s) {
170                try {
171                    return (String) method.invoke (cls, new Object[] {s});
172                } catch (Exception e) {
173                    throw new RuntimeException (e.toString ());
174                }
175            }
176        } 
177    
178        /**
179         * Wrapper for preferred Java 1.4 & later URLEncoder.encode().
180         */
181        private static class Encoder4 implements Encoder {
182            Class cls;
183            Method method;
184            public Encoder4 () throws Exception {
185                cls = Class.forName ("java.net.URLEncoder");
186                method = cls.getMethod ("encode", 
187                                        new Class[] {String.class, String.class});
188            }            
189            public String encode (String s) {
190                try {
191                    return (String) method.invoke (cls, new Object[] {s, "UTF-8"});
192                } catch (Exception e) {
193                    throw new RuntimeException (e.toString ());
194                }
195            }
196        } 
197    
198        /**
199         * Determine which version of Java is running and
200         * set ENCODER appropriately.
201         */
202        static {
203            String javaVersion = System.getProperty ("java.version");
204            try {
205                if (javaVersion.startsWith ("1.2")
206                    || javaVersion.startsWith ("1.3"))
207                    ENCODER = new Encoder2 ();
208                else
209                    ENCODER = new Encoder4 ();
210            } catch (Exception e) {
211                throw new RuntimeException (e.toString ());
212            }
213        }
214    }