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 GopherClient(sun.net.www.URLConnection connection)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 */ getUseGopherProxy()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 */ getGopherProxyHost()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 */ getGopherProxyPort()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 */ openStream(URL u)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 */ decodePercent(String s)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 */ encodePercent(String s)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 */ run()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 } 358