diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java
index 035d0c6be8689ba4dac19544f162c3723c9d2d8d..9f2c373dc99f30a9a766a407f1fac894875685d3 100644
--- a/core/java/src/net/i2p/util/EepGet.java
+++ b/core/java/src/net/i2p/util/EepGet.java
@@ -23,48 +23,50 @@ import net.i2p.data.DataHelper;
  *        [-o outputFile] 
  *        [-m markSize lineLen]
  *        url
+ *
+ * Bug: a malformed url http://example.i2p (no trailing '/') fails cryptically
  */
 public class EepGet {
     private I2PAppContext _context;
-    private Log _log;
-    private boolean _shouldProxy;
+    protected Log _log;
+    protected boolean _shouldProxy;
     private String _proxyHost;
     private int _proxyPort;
-    private int _numRetries;
+    protected int _numRetries;
     private long _minSize; // minimum and maximum acceptable response size, -1 signifies unlimited,
     private long _maxSize; // applied both against whole responses and chunks
     private String _outputFile;
     private OutputStream _outputStream;
     /** url we were asked to fetch */
-    private String _url;
+    protected String _url;
     /** the URL we actually fetch from (may differ from the _url in case of redirect) */
-    private String _actualURL;
+    protected String _actualURL;
     private String _postData;
     private boolean _allowCaching;
-    private List _listeners;
+    protected List _listeners;
     
     private boolean _keepFetching;
     private Socket _proxy;
     private OutputStream _proxyOut;
     private InputStream _proxyIn;
-    private OutputStream _out;
+    protected OutputStream _out;
     private long _alreadyTransferred;
     private long _bytesTransferred;
-    private long _bytesRemaining;
-    private int _currentAttempt;
+    protected long _bytesRemaining;
+    protected int _currentAttempt;
     private String _etag;
     private String _lastModified;
     private boolean _encodingChunked;
     private boolean _notModified;
     private String _contentType;
-    private boolean _transferFailed;
-    private boolean _headersRead;
-    private boolean _aborted;
+    protected boolean _transferFailed;
+    protected boolean _headersRead;
+    protected boolean _aborted;
     private long _fetchHeaderTimeout;
     private long _fetchEndTime;
-    private long _fetchInactivityTimeout;
-    private int _redirects;
-    private String _redirectLocation;
+    protected long _fetchInactivityTimeout;
+    protected int _redirects;
+    protected String _redirectLocation;
     
     public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
         this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url);
@@ -214,7 +216,7 @@ public class EepGet {
         return buf.toString();
     }
     
-    private static void usage() {
+    protected static void usage() {
         System.err.println("EepGet [-p 127.0.0.1:4444] [-n #retries] [-o outputFile] [-m markSize lineLen] [-t timeout] url");
     }
     
@@ -480,7 +482,7 @@ public class EepGet {
     }
 
     /** return true if the URL was completely retrieved */
-    private void doFetch(SocketTimeout timeout) throws IOException {
+    protected void doFetch(SocketTimeout timeout) throws IOException {
         _headersRead = false;
         _aborted = false;
         try {
@@ -625,7 +627,7 @@ public class EepGet {
         }
     }
 
-    private void readHeaders() throws IOException {
+    protected void readHeaders() throws IOException {
         String key = null;
         StringBuilder buf = new StringBuilder(32);
 
@@ -844,7 +846,7 @@ public class EepGet {
     private static final byte NL = '\n';
     private boolean isNL(byte b) { return (b == NL); }
 
-    private void sendRequest(SocketTimeout timeout) throws IOException {
+    protected void sendRequest(SocketTimeout timeout) throws IOException {
         if (_outputStream != null) {
             // We are reading into a stream supplied by a caller,
             // for which we cannot easily determine how much we've written.
@@ -892,7 +894,7 @@ public class EepGet {
             _log.debug("Request flushed");
     }
     
-    private String getRequest() throws IOException {
+    protected String getRequest() throws IOException {
         StringBuilder buf = new StringBuilder(512);
         boolean post = false;
         if ( (_postData != null) && (_postData.length() > 0) )
@@ -963,5 +965,4 @@ public class EepGet {
     public String getContentType() {
         return _contentType;
     }
-
 }
diff --git a/core/java/src/net/i2p/util/EepHead.java b/core/java/src/net/i2p/util/EepHead.java
new file mode 100644
index 0000000000000000000000000000000000000000..5127ed93b0bf695308eef37bfdb1c06bee8468bf
--- /dev/null
+++ b/core/java/src/net/i2p/util/EepHead.java
@@ -0,0 +1,204 @@
+package net.i2p.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import net.i2p.I2PAppContext;
+
+/**
+ * This is a quick hack to get a working EepHead, primarily for the following usage:
+ *
+ * EepHead foo = new EepHead(...);
+ * if (foo.fetch()) {
+ *     String lastmod = foo.getLastModified();
+ *     if (lastmod != null) {
+ *         parse the string...
+ *         ...
+ *     }
+ * }
+ *
+ * Other use cases (command line, listeners, etc...) lightly- or un-tested.
+ *
+ * Writing from scratch rather than extending EepGet would maybe have been less bloated memory-wise.
+ * This way gets us redirect handling, among other benefits.
+ *
+ * @author zzz
+ */
+public class EepHead extends EepGet {
+    /** EepGet needs either a non-null file or a stream... shouldn't actually be written to... */
+    static final OutputStream _dummyStream = new ByteArrayOutputStream(0);
+
+    public EepHead(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String url) {
+        // 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, true, proxyHost, proxyPort, numRetries, -1, -1, null, _dummyStream, url, true, null, null);
+    }
+   
+    /**
+     * EepHead [-p 127.0.0.1:4444] [-n #retries] url
+     *
+     * This doesn't really do much since it doesn't register a listener.
+     * EepGet doesn't have a method to store and return all the headers, so just print
+     * out the ones we have methods for.
+     * Turn on logging to use it for a decent test.
+     */ 
+    public static void main(String args[]) {
+        String proxyHost = "127.0.0.1";
+        int proxyPort = 4444;
+        int numRetries = 0;
+        int inactivityTimeout = 60*1000;
+        String url = null;
+        try {
+            for (int i = 0; i < args.length; i++) {
+                if (args[i].equals("-p")) {
+                    proxyHost = args[i+1].substring(0, args[i+1].indexOf(':'));
+                    String port = args[i+1].substring(args[i+1].indexOf(':')+1);
+                    proxyPort = Integer.parseInt(port);
+                    i++;
+                } else if (args[i].equals("-n")) {
+                    numRetries = Integer.parseInt(args[i+1]);
+                    i++;
+                } else if (args[i].equals("-t")) {
+                    inactivityTimeout = 1000 * Integer.parseInt(args[i+1]);
+                    i++;
+                } else if (args[i].startsWith("-")) {
+                    usage();
+                    return;
+                } else {
+                    url = args[i];
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            usage();
+            return;
+        }
+        
+        if (url == null) {
+            usage();
+            return;
+        }
+
+        EepHead get = new EepHead(I2PAppContext.getGlobalContext(), proxyHost, proxyPort, numRetries, url);
+        if (get.fetch(45*1000, -1, inactivityTimeout)) {
+            System.err.println("Content-Type: " + get.getContentType());
+            System.err.println("Content-Length: " + get.getContentLength());
+            System.err.println("Last-Modified: " + get.getLastModified());
+            System.err.println("Etag: " + get.getETag());
+        } else {
+            System.err.println("Failed " + url);
+        }
+    }
+    
+    protected static void usage() {
+        System.err.println("EepHead [-p 127.0.0.1:4444] [-n #retries] [-t timeout] url");
+    }
+    
+    /** return true if the URL was completely retrieved */
+    @Override
+    protected void doFetch(SocketTimeout timeout) throws IOException {
+        _headersRead = false;
+        _aborted = false;
+        try {
+            readHeaders();
+        } finally {
+            _headersRead = true;
+        }
+        if (_aborted)
+            throw new IOException("Timed out reading the HTTP headers");
+        
+        timeout.resetTimer();
+        if (_fetchInactivityTimeout > 0)
+            timeout.setInactivityTimeout(_fetchInactivityTimeout);
+        else
+            timeout.setInactivityTimeout(60*1000);
+        
+        if (_redirectLocation != null) {
+            try {
+                URL oldURL = new URL(_actualURL);
+                String query = oldURL.getQuery();
+                if (query == null) query = "";
+                if (_redirectLocation.startsWith("http://")) {
+                    if ( (_redirectLocation.indexOf('?') < 0) && (query.length() > 0) )
+                        _actualURL = _redirectLocation + "?" + query;
+                    else
+                        _actualURL = _redirectLocation;
+                } else { 
+                    URL url = new URL(_actualURL);
+		    if (_redirectLocation.startsWith("/"))
+                        _actualURL = "http://" + url.getHost() + ":" + url.getPort() + _redirectLocation;
+                    else
+                        _actualURL = "http://" + url.getHost() + ":" + url.getPort() + "/" + _redirectLocation;
+                    if ( (_actualURL.indexOf('?') < 0) && (query.length() > 0) )
+                        _actualURL = _actualURL + "?" + query;
+                }
+            } catch (MalformedURLException mue) {
+                throw new IOException("Redirected from an invalid URL");
+            }
+            _redirects++;
+            if (_redirects > 5)
+                throw new IOException("Too many redirects: to " + _redirectLocation);
+            if (_log.shouldLog(Log.INFO)) _log.info("Redirecting to " + _redirectLocation);
+            sendRequest(timeout);
+            doFetch(timeout);
+            return;
+        }
+        
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Headers read completely");
+            
+        if (_out != null)
+            _out.close();
+        _out = null;
+        
+        if (_aborted)
+            throw new IOException("Timed out reading the HTTP data");
+        
+        timeout.cancel();
+        
+        if (_transferFailed) {
+            // 404, etc - transferFailed is called after all attempts fail, by fetch() above
+            for (int i = 0; i < _listeners.size(); i++) 
+                ((StatusListener)_listeners.get(i)).attemptFailed(_url, 0, 0, _currentAttempt, _numRetries, new Exception("Attempt failed"));
+        } else {
+            for (int i = 0; i < _listeners.size(); i++) 
+                ((StatusListener)_listeners.get(i)).transferComplete(
+                        0, 0, 0, _url, "dummy", false);
+        }
+    }
+
+    @Override
+    protected String getRequest() throws IOException {
+        StringBuilder buf = new StringBuilder(512);
+        URL url = new URL(_actualURL);
+        String proto = url.getProtocol();
+        String host = url.getHost();
+        int port = url.getPort();
+        String path = url.getPath();
+        String query = url.getQuery();
+        if (query != null)
+            path = path + "?" + query;
+        if (!path.startsWith("/"))
+	    path = "/" + path;
+        if ( (port == 80) || (port == 443) || (port <= 0) ) path = proto + "://" + host + path;
+        else path = proto + "://" + host + ":" + port + path;
+        if (_log.shouldLog(Log.DEBUG)) _log.debug("Requesting " + path);
+        buf.append("HEAD ").append(_actualURL).append(" HTTP/1.1\r\n");
+        buf.append("Host: ").append(url.getHost()).append("\r\n");
+        buf.append("Accept-Encoding: \r\n");
+        if (_shouldProxy)
+            buf.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
+        buf.append("Connection: close\r\n\r\n");
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Request: [" + buf.toString() + "]");
+        return buf.toString();
+    }
+
+    /** We don't decrement the variable (unlike in EepGet), so this is valid */
+    public long getContentLength() {
+        return _bytesRemaining;
+    }
+}