1 /*
   2  * Copyright (c) 1996, 2004, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.net.www.protocol.gopher;
  26 
  27 import java.io.*;
  28 import java.util.*;
  29 import java.net.*;
  30 import sun.net.www.*;
  31 import sun.net.NetworkClient;
  32 import java.net.URL;
  33 import java.net.URLStreamHandler;
  34 
  35 import sun.security.action.GetBooleanAction;
  36 
  37 /** Class to maintain the state of a gopher fetch and handle the protocol */
  38 public class GopherClient extends NetworkClient implements Runnable {
  39 
  40     /* The following three data members are left in for binary
  41      * backwards-compatibility.  Unfortunately, HotJava sets them directly
  42      * when it wants to change the settings.  The new design has us not
  43      * cache these, so this is unnecessary, but eliminating the data members
  44      * would break HJB 1.1 under JDK 1.2.
  45      *
  46      * These data members are not used, and their values are meaningless.
  47      * REMIND:  Take them out for JDK 2.0!
  48      */
  49 
  50     /**
  51      * @deprecated
  52      */
  53     @Deprecated
  54     public static boolean       useGopherProxy;
  55 
  56     /**
  57      * @deprecated
  58      */
  59     @Deprecated
  60     public static String        gopherProxyHost;
  61 
  62     /**
  63      * @deprecated
  64      */
  65     @Deprecated
  66     public static int           gopherProxyPort;
  67 
  68 
  69     static {
  70         useGopherProxy = java.security.AccessController.doPrivileged(
  71             new sun.security.action.GetBooleanAction("gopherProxySet"))
  72             .booleanValue();
  73 
  74         gopherProxyHost = java.security.AccessController.doPrivileged(
  75             new sun.security.action.GetPropertyAction("gopherProxyHost"));
  76 
  77         gopherProxyPort = java.security.AccessController.doPrivileged(
  78             new sun.security.action.GetIntegerAction("gopherProxyPort", 80))
  79             .intValue();
  80     }
  81 
  82     PipedOutputStream os;
  83     URL u;
  84     int gtype;
  85     String gkey;
  86     sun.net.www.URLConnection connection;
  87 
  88     GopherClient(sun.net.www.URLConnection connection) {
  89         this.connection = connection;
  90     }
  91 
  92     /**
  93      * @return true if gopher connections should go through a proxy, according
  94      *          to system properties.
  95      */
  96     public static boolean getUseGopherProxy() {
  97         return java.security.AccessController.doPrivileged(
  98             new GetBooleanAction("gopherProxySet")).booleanValue();
  99     }
 100 
 101     /**
 102      * @return the proxy host to use, or null if nothing is set.
 103      */
 104     public static String getGopherProxyHost() {
 105         String host = java.security.AccessController.doPrivileged(
 106                 new sun.security.action.GetPropertyAction("gopherProxyHost"));
 107         if ("".equals(host)) {
 108             host = null;
 109         }
 110         return host;
 111     }
 112 
 113     /**
 114      * @return the proxy port to use.  Will default reasonably.
 115      */
 116     public static int getGopherProxyPort() {
 117         return java.security.AccessController.doPrivileged(
 118             new sun.security.action.GetIntegerAction("gopherProxyPort", 80))
 119             .intValue();
 120     }
 121 
 122     /** Given a url, setup to fetch the gopher document it refers to */
 123     InputStream openStream(URL u) throws IOException {
 124         this.u = u;
 125         this.os = os;
 126         int i = 0;
 127         String s = u.getFile();
 128         int limit = s.length();
 129         int c = '1';
 130         while (i < limit && (c = s.charAt(i)) == '/')
 131             i++;
 132         gtype = c == '/' ? '1' : c;
 133         if (i < limit)
 134             i++;
 135         gkey = s.substring(i);
 136 
 137         openServer(u.getHost(), u.getPort() <= 0 ? 70 : u.getPort());
 138 
 139         MessageHeader msgh = new MessageHeader();
 140 
 141         switch (gtype) {
 142           case '0':
 143           case '7':
 144             msgh.add("content-type", "text/plain");
 145             break;
 146           case '1':
 147             msgh.add("content-type", "text/html");
 148             break;
 149           case 'g':
 150           case 'I':
 151             msgh.add("content-type", "image/gif");
 152             break;
 153           default:
 154             msgh.add("content-type", "content/unknown");
 155             break;
 156         }
 157         if (gtype != '7') {
 158             serverOutput.print(decodePercent(gkey) + "\r\n");
 159             serverOutput.flush();
 160         } else if ((i = gkey.indexOf('?')) >= 0) {
 161             serverOutput.print(decodePercent(gkey.substring(0, i) + "\t" +
 162                                            gkey.substring(i + 1) + "\r\n"));
 163             serverOutput.flush();
 164             msgh.add("content-type", "text/html");
 165         } else {
 166             msgh.add("content-type", "text/html");
 167         }
 168         connection.setProperties(msgh);
 169         if (msgh.findValue("content-type") == "text/html") {
 170             os = new PipedOutputStream();
 171             PipedInputStream ret = new PipedInputStream();
 172             ret.connect(os);
 173             new Thread(this).start();
 174             return ret;
 175         }
 176         return new GopherInputStream(this, serverInput);
 177     }
 178 
 179     /** Translate all the instances of %NN into the character they represent */
 180     private String decodePercent(String s) {
 181         if (s == null || s.indexOf('%') < 0)
 182             return s;
 183         int limit = s.length();
 184         char d[] = new char[limit];
 185         int dp = 0;
 186         for (int sp = 0; sp < limit; sp++) {
 187             int c = s.charAt(sp);
 188             if (c == '%' && sp + 2 < limit) {
 189                 int s1 = s.charAt(sp + 1);
 190                 int s2 = s.charAt(sp + 2);
 191                 if ('0' <= s1 && s1 <= '9')
 192                     s1 = s1 - '0';
 193                 else if ('a' <= s1 && s1 <= 'f')
 194                     s1 = s1 - 'a' + 10;
 195                 else if ('A' <= s1 && s1 <= 'F')
 196                     s1 = s1 - 'A' + 10;
 197                 else
 198                     s1 = -1;
 199                 if ('0' <= s2 && s2 <= '9')
 200                     s2 = s2 - '0';
 201                 else if ('a' <= s2 && s2 <= 'f')
 202                     s2 = s2 - 'a' + 10;
 203                 else if ('A' <= s2 && s2 <= 'F')
 204                     s2 = s2 - 'A' + 10;
 205                 else
 206                     s2 = -1;
 207                 if (s1 >= 0 && s2 >= 0) {
 208                     c = (s1 << 4) | s2;
 209                     sp += 2;
 210                 }
 211             }
 212             d[dp++] = (char) c;
 213         }
 214         return new String(d, 0, dp);
 215     }
 216 
 217     /** Turn special characters into the %NN form */
 218     private String encodePercent(String s) {
 219         if (s == null)
 220             return s;
 221         int limit = s.length();
 222         char d[] = null;
 223         int dp = 0;
 224         for (int sp = 0; sp < limit; sp++) {
 225             int c = s.charAt(sp);
 226             if (c <= ' ' || c == '"' || c == '%') {
 227                 if (d == null)
 228                     d = s.toCharArray();
 229                 if (dp + 3 >= d.length) {
 230                     char nd[] = new char[dp + 10];
 231                     System.arraycopy(d, 0, nd, 0, dp);
 232                     d = nd;
 233                 }
 234                 d[dp] = '%';
 235                 int dig = (c >> 4) & 0xF;
 236                 d[dp + 1] = (char) (dig < 10 ? '0' + dig : 'A' - 10 + dig);
 237                 dig = c & 0xF;
 238                 d[dp + 2] = (char) (dig < 10 ? '0' + dig : 'A' - 10 + dig);
 239                 dp += 3;
 240             } else {
 241                 if (d != null) {
 242                     if (dp >= d.length) {
 243                         char nd[] = new char[dp + 10];
 244                         System.arraycopy(d, 0, nd, 0, dp);
 245                         d = nd;
 246                     }
 247                     d[dp] = (char) c;
 248                 }
 249                 dp++;
 250             }
 251         }
 252         return d == null ? s : new String(d, 0, dp);
 253     }
 254 
 255     /** This method is run as a seperate thread when an incoming gopher
 256         document requires translation to html */
 257     public void run() {
 258         int qpos = -1;
 259         try {
 260             if (gtype == '7' && (qpos = gkey.indexOf('?')) < 0) {
 261                 PrintStream ps = new PrintStream(os, false, encoding);
 262                 ps.print("<html><head><title>Searchable Gopher Index</title></head>\n<body><h1>Searchable Gopher Index</h1><isindex>\n</body></html>\n");
 263             } else if (gtype != '1' && gtype != '7') {
 264                 byte buf[] = new byte[2048];
 265                 try {
 266                     int n;
 267                     while ((n = serverInput.read(buf)) >= 0)
 268                             os.write(buf, 0, n);
 269                 } catch(Exception e) {
 270                 }
 271             } else {
 272                 PrintStream ps = new PrintStream(os, false, encoding);
 273                 String title = null;
 274                 if (gtype == '7')
 275                     title = "Results of searching for \"" + gkey.substring(qpos + 1)
 276                         + "\" on " + u.getHost();
 277                 else
 278                     title = "Gopher directory " + gkey + " from " + u.getHost();
 279                 ps.print("<html><head><title>");
 280                 ps.print(title);
 281                 ps.print("</title></head>\n<body>\n<H1>");
 282                 ps.print(title);
 283                 ps.print("</h1><dl compact>\n");
 284                 DataInputStream ds = new DataInputStream(serverInput);
 285                 String s;
 286                 while ((s = ds.readLine()) != null) {
 287                     int len = s.length();
 288                     while (len > 0 && s.charAt(len - 1) <= ' ')
 289                         len--;
 290                     if (len <= 0)
 291                         continue;
 292                     int key = s.charAt(0);
 293                     int t1 = s.indexOf('\t');
 294                     int t2 = t1 > 0 ? s.indexOf('\t', t1 + 1) : -1;
 295                     int t3 = t2 > 0 ? s.indexOf('\t', t2 + 1) : -1;
 296                     if (t3 < 0) {
 297                         // ps.print("<br><i>"+s+"</i>\n");
 298                         continue;
 299                     }
 300                     String port = t3 + 1 < len ? ":" + s.substring(t3 + 1, len) : "";
 301                     String host = t2 + 1 < t3 ? s.substring(t2 + 1, t3) : u.getHost();
 302                     ps.print("<dt><a href=\"gopher://" + host + port + "/"
 303                              + s.substring(0, 1) + encodePercent(s.substring(t1 + 1, t2)) + "\">\n");
 304                     ps.print("<img align=middle border=0 width=25 height=32 src=");
 305                     switch (key) {
 306                       default:
 307                         ps.print(System.getProperty("java.net.ftp.imagepath.file"));
 308                         break;
 309                       case '0':
 310                         ps.print(System.getProperty("java.net.ftp.imagepath.text"));
 311                         break;
 312                       case '1':
 313                         ps.print(System.getProperty("java.net.ftp.imagepath.directory"));
 314                         break;
 315                       case 'g':
 316                         ps.print(System.getProperty("java.net.ftp.imagepath.gif"));
 317                         break;
 318                     }
 319                     ps.print(".gif align=middle><dd>\n");
 320                     ps.print(s.substring(1, t1) + "</a>\n");
 321                 }
 322                 ps.print("</dl></body>\n");
 323                 ps.close();
 324            }
 325 
 326        } catch (UnsupportedEncodingException e) {
 327             throw new InternalError(encoding+ " encoding not found");
 328        } catch (IOException e) {
 329        } finally {
 330            try {
 331                closeServer();
 332                os.close();
 333            } catch (IOException e2) {
 334            }
 335         }
 336     }
 337 }
 338 
 339 /** An input stream that does nothing more than hold on to the NetworkClient
 340     that created it.  This is used when only the input stream is needed, and
 341     the network client needs to be closed when the input stream is closed. */
 342 class GopherInputStream extends FilterInputStream {
 343     NetworkClient parent;
 344 
 345     GopherInputStream(NetworkClient o, InputStream fd) {
 346         super(fd);
 347         parent = o;
 348     }
 349 
 350     public void close() {
 351         try {
 352             parent.closeServer();
 353             super.close();
 354         } catch (IOException e) {
 355         }
 356     }
 357 }