package net.i2p.util; import java.io.BufferedReader; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.Locale; import gnu.getopt.Getopt; import net.i2p.I2PAppContext; /** * Fetch exactly the first 'size' bytes into a stream * Anything less or more will throw an IOException * No retries, no min and max size options, no timeout option * If the server does not return a Content-Length header of the correct size, * the fetch will fail. * * Useful for checking .sud versions * * @since 0.7.12 * @author zzz */ public class PartialEepGet extends EepGet { long _fetchSize; /** * Instantiate an EepGet that will fetch exactly size bytes when fetch() is called. * * @param proxyHost use null or "" for no proxy * @param proxyPort use 0 for no proxy * @param size fetch exactly this many bytes */ public PartialEepGet(I2PAppContext ctx, String proxyHost, int proxyPort, OutputStream outputStream, String url, long size) { // we're using this constructor: // public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, // long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) { super(ctx, proxyHost != null && proxyPort > 0, proxyHost, proxyPort, 0, size, size, null, outputStream, url, true, null, null); _fetchSize = size; } /** * PartialEepGet [-p 127.0.0.1:4444] [-l #bytes] url * */ public static void main(String args[]) { String proxyHost = "127.0.0.1"; int proxyPort = 4444; // 40 sig + 16 version for .suds long size = 56; String saveAs = null; String username = null; String password = null; boolean error = false; Getopt g = new Getopt("partialeepget", args, "p:cl:o:u:x:"); try { int c; while ((c = g.getopt()) != -1) { switch (c) { case 'p': String s = g.getOptarg(); int colon = s.indexOf(':'); if (colon >= 0) { // Todo IPv6 [a:b:c]:4444 proxyHost = s.substring(0, colon); String port = s.substring(colon + 1); proxyPort = Integer.parseInt(port); } else { proxyHost = s; // proxyPort remains default } break; case 'c': // no proxy, same as -p :0 proxyHost = ""; proxyPort = 0; break; case 'l': size = Long.parseLong(g.getOptarg()); break; case 'o': saveAs = g.getOptarg(); break; case 'u': username = g.getOptarg(); break; case 'x': password = g.getOptarg(); break; case '?': case ':': default: error = true; break; } // switch } // while } catch (RuntimeException e) { e.printStackTrace(); error = true; } if (error || args.length - g.getOptind() != 1) { usage(); System.exit(1); } String url = args[g.getOptind()]; if (saveAs == null) saveAs = suggestName(url); OutputStream out; try { // resume from a previous eepget won't work right doing it this way out = new FileOutputStream(saveAs); } catch (IOException ioe) { System.err.println("Failed to create output file " + saveAs); out = null; // dummy for compiler System.exit(1); } EepGet get = new PartialEepGet(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, out, url, size); if (username != null) { if (password == null) { try { BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); do { System.err.print("Proxy password: "); password = r.readLine(); if (password == null) throw new IOException(); password = password.trim(); } while (password.length() <= 0); } catch (IOException ioe) { System.exit(1); } } get.addAuthorization(username, password); } get.addStatusListener(get.new CLIStatusListener(1024, 40)); if (get.fetch(45*1000, -1, 60*1000)) { System.err.println("Last-Modified: " + get.getLastModified()); System.err.println("Etag: " + get.getETag()); } else { System.err.println("Failed " + url); System.exit(1); } } private static void usage() { System.err.println("PartialEepGet [-p 127.0.0.1[:4444]] [-c] [-o outputFile]\n" + " [-l #bytes] (default 56)\n" + " [-u username] [-x password] url\n" + " (use -c or -p :0 for no proxy)"); } @Override protected String getRequest() throws IOException { StringBuilder buf = new StringBuilder(2048); URI url; try { url = new URI(_actualURL); } catch (URISyntaxException use) { IOException ioe = new MalformedURLException("Bad URL"); ioe.initCause(use); throw ioe; } String host = url.getHost(); if (host == null || host.length() <= 0) throw new MalformedURLException("Bad URL, no host"); int port = url.getPort(); String path = url.getRawPath(); String query = url.getRawQuery(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Requesting " + _actualURL); // RFC 2616 sec 5.1.2 - full URL if proxied, absolute path only if not proxied String urlToSend; if (_shouldProxy) { urlToSend = _actualURL; if ((path == null || path.length()<= 0) && (query == null || query.length()<= 0)) urlToSend += "/"; } else { urlToSend = path; if (urlToSend == null || urlToSend.length()<= 0) urlToSend = "/"; if (query != null) urlToSend += '?' + query; } buf.append("GET ").append(urlToSend).append(" HTTP/1.1\r\n"); // RFC 2616 sec 5.1.2 - host + port (NOT authority, which includes userinfo) buf.append("Host: ").append(host); if (port >= 0) buf.append(':').append(port); buf.append("\r\n"); buf.append("Range: bytes="); buf.append(_alreadyTransferred); buf.append('-'); buf.append(_fetchSize - 1); buf.append("\r\n"); buf.append("Cache-control: no-cache\r\n" + "Pragma: no-cache\r\n" + "Accept-Encoding: \r\n" + "Connection: close\r\n"); boolean uaOverridden = false; if (_extraHeaders != null) { for (String hdr : _extraHeaders) { if (hdr.toLowerCase(Locale.US).startsWith("user-agent: ")) uaOverridden = true; buf.append(hdr).append("\r\n"); } } // This will be replaced if we are going through I2PTunnelHTTPClient if(!uaOverridden) buf.append("User-Agent: " + USER_AGENT + "\r\n"); if (_authState != null && _shouldProxy && _authState.authMode != AUTH_MODE.NONE) { buf.append("Proxy-Authorization: "); buf.append(_authState.getAuthHeader("GET", urlToSend)); buf.append("\r\n"); } buf.append("\r\n"); if (_log.shouldLog(Log.DEBUG)) _log.debug("Request: [" + buf.toString() + "]"); return buf.toString(); } }