diff --git a/addresspublisher/build.xml b/addresspublisher/build.xml index 15431dd..f8d299c 100644 --- a/addresspublisher/build.xml +++ b/addresspublisher/build.xml @@ -12,7 +12,7 @@ - + Must set build.dir @@ -26,5 +26,22 @@ + + + + Must set build.dir + + + + + + + + + + + + + diff --git a/addresspublisher/nbproject/build-impl.xml b/addresspublisher/nbproject/build-impl.xml index dcf3b0f..a2ffc77 100644 --- a/addresspublisher/nbproject/build-impl.xml +++ b/addresspublisher/nbproject/build-impl.xml @@ -316,15 +316,18 @@ is divided into following sections: Must set javac.includes - + + + - + + diff --git a/addresspublisher/nbproject/genfiles.properties b/addresspublisher/nbproject/genfiles.properties index d9736b6..529faeb 100644 --- a/addresspublisher/nbproject/genfiles.properties +++ b/addresspublisher/nbproject/genfiles.properties @@ -4,5 +4,5 @@ build.xml.stylesheet.CRC32=28e38971@1.38.2.45 # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. nbproject/build-impl.xml.data.CRC32=00fdd0af -nbproject/build-impl.xml.script.CRC32=967d9836 -nbproject/build-impl.xml.stylesheet.CRC32=f33e10ff@1.38.2.45 +nbproject/build-impl.xml.script.CRC32=b5dc77d5 +nbproject/build-impl.xml.stylesheet.CRC32=229523de@1.38.3.45 diff --git a/addresspublisher/nbproject/private/private.xml b/addresspublisher/nbproject/private/private.xml index a337f2e..c1f155a 100644 --- a/addresspublisher/nbproject/private/private.xml +++ b/addresspublisher/nbproject/private/private.xml @@ -1,11 +1,4 @@ - - file:/mnt/bb/dream/packages/mtn/i2p.scripts/addresspublisher/src/i2p/dream/addresspublisher/Servlet.java - file:/mnt/bb/dream/packages/mtn/i2p.scripts/addresspublisher/src/i2p/dream/addresspublisher/RecordIndex.java - file:/mnt/bb/dream/packages/mtn/i2p.scripts/addresspublisher/src/i2p/dream/addresspublisher/PersistentLong.java - file:/mnt/bb/dream/packages/mtn/i2p.scripts/addresspublisher/src/i2p/dream/addresspublisher/Configuration.java - file:/mnt/bb/dream/packages/mtn/i2p.scripts/addresspublisher/src/i2p/dream/addresspublisher/Record.java - diff --git a/addresspublisher/src/i2p/dream/addresspublisher/BlackList.java b/addresspublisher/src/i2p/dream/addresspublisher/BlackList.java new file mode 100644 index 0000000..e1a0aaa --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/BlackList.java @@ -0,0 +1,123 @@ +package i2p.dream.addresspublisher; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.i2p.data.Destination; +import net.i2p.data.Hash; + +// This just implements a persistent blacklist of addresses that were deleted + +/** + * + * @author dream + */ +class BlackList implements Iterable { + public Iterator iterator() { + return blacklist.iterator(); + } + private static class HashReader implements Iterator, Iterable{ + private final FileInputStream in; + boolean done = false; + Hash queued = null; + public HashReader(FileInputStream in) { + this.in = in; + queueNext(); + } + + final void queueNext() { + try { + byte[] buf = new byte[Hash.HASH_LENGTH]; + int amount = in.read(buf); + if(amount < Hash.HASH_LENGTH) + done = true; + queued = new Hash(buf); + } catch (IOException ex) { + Logger.getLogger(BlackList.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public boolean hasNext() { + return !done; + } + + public Hash next() { + Hash next = queued; + queueNext(); + return next; + } + + public void remove() { + throw new UnsupportedOperationException("Not supported yet."); + } + + public Iterator iterator() { + return this; + } + } + + final Set blacklist = new HashSet(); + + final Timer timer; + + File theList() { + return new File(Configuration.getConfDir(),"blacklist.dat"); + } + + BlackList(Context context) { + timer = context.timer; + load(); + } + + public final void load() { + FileInputStream in = null; + try { + in = new FileInputStream(theList()); + in.getChannel().lock(); + for(Hash hash : new HashReader(in)) { + blacklist.add(hash); + } + } + catch (IOException ex) { + Logger.getLogger(BlackList.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void save() { + FileOutputStream out = null; + try { + out = new FileOutputStream(theList()); + out.getChannel().lock(); + for(Hash hash : blacklist) { + out.write(hash.getData()); + } + } + catch (IOException ex) { + Logger.getLogger(BlackList.class.getName()).log(Level.SEVERE, null, ex); + } + } + + boolean contains(Destination dest) { + return blacklist.contains(dest.calculateHash()); + } + + void add(Destination dest) { + blacklist.add(dest.calculateHash()); + final BlackList that = this; + timer.schedule(new TimerTask() { + @Override + public void run() { + that.save(); + } + }, 100); + } +} diff --git a/addresspublisher/src/i2p/dream/addresspublisher/BookServlet.java b/addresspublisher/src/i2p/dream/addresspublisher/BookServlet.java new file mode 100644 index 0000000..de742ab --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/BookServlet.java @@ -0,0 +1,105 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package i2p.dream.addresspublisher; + +import java.io.IOException; +import java.io.PrintWriter; +import java.text.DateFormat; +import java.util.TimeZone; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.i2p.I2PAppContext; +import net.i2p.data.Base32; +import net.i2p.data.Hash; + +/** + * + * @author dream + */ + +public class BookServlet extends HttpServlet { + static final Context context = new Context("Book Servlet"); + + static final BlackList blacklist = new BlackList(context); + + /** just check if console password option is set, jetty will do auth */ + private boolean validPassphrase() { + String pass = I2PAppContext.getGlobalContext().getProperty("consolePassword"); + return pass != null && pass.trim().length() > 0; + } + + static final DateFormat formatter = DateFormat.getDateTimeInstance(); + static { + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + if(!validPassphrase()) return; + + response.setContentType("text/html"); + PrintWriter out = null; + try { + out = response.getWriter(); + out.println("Addresses"); + out.println("
"); + out.println(""); + for(Record record : RecordIndex.getInstance()) { + out.println(""); + out.println(""); + out.println(" "); + out.println(" "); + out.println(" "); + out.println(""); + } + out.println("
"+record.getName()+""+ + formatter.format(record.getModified()) + "" + record.getAddress().toBase64() + "
"); + + out.println(""); + out.println("
"); + out.println("

Deleted addresses:

"); + out.println("
"); + out.println(""); + for(Hash hash : blacklist) { + String shash = Base32.encode(hash.getData()); + out.println(""); + out.println(" "); + out.println(" "); + out.println(""); + } + out.println("
"+shash+"
"); + + out.println(""); + + out.println("
"); + } finally { + if(out!=null) + out.close(); + } + } + + @Override + protected void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + try { + out.println("Wheeee

Under construction whee

"); + } finally { + out.close(); + } + } +} diff --git a/addresspublisher/src/i2p/dream/addresspublisher/CheckRSS.java b/addresspublisher/src/i2p/dream/addresspublisher/CheckRSS.java new file mode 100644 index 0000000..ad8ac7f --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/CheckRSS.java @@ -0,0 +1,139 @@ +package i2p.dream.addresspublisher; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +/* AUghhhhhh + * + * RSS is a terrible format! + * + * Oh sure we need well structured XML so that we can + * OH HELL LET'S JUST THROW AN XML ESCAPED STRING OF + * UNSTANDARD HTML IN THERE SO THAT PEOPLE CAN BE ON + * THE WEB ON THEIR RSS! + * + * But more importantly, stats.i2p doesn't provide the address in RSS + * easier just to request newHosts.txt from it -_- + * + */ + + +/** + * + * @author dream + */ +public class CheckRSS extends TimerTask { + + final URL url; + + String lastModified = null; + String lastEtag = null; + private final KnownHosts knownHosts; + private DocumentBuilder parser; + + CheckRSS(Context context, URL url) { + this.url = url; + this.knownHosts = context.knownHosts; + context.timer.schedule(this, 1000, 60*60*1000); + } + + public void run() { + try { + URLConnection request = (HttpURLConnection) url.openConnection(); + if (lastModified != null) { + request.setRequestProperty("If-Modified-since", lastModified); + } + if (lastEtag != null) { + request.setRequestProperty("ETag", lastEtag); + } + lastModified = request.getHeaderField("Last-Modified"); + lastEtag = request.getHeaderField("ETag"); + InputStream in = null; + try { + in = request.getInputStream(); + parse(in); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) { + Logger.getLogger(CheckRSS.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + catch (IOException ex) { + Logger.getLogger(CheckRSS.class.getName()).log(Level.SEVERE, null, ex); + } } + + void foundName(String name, String address, Date added) { + try { + if (knownHosts.contains(name)) { + return; + } + + Destination dest = new Destination(); + try { + dest.fromBase64(address); + } catch (DataFormatException ex) { + Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); + return; + } + + Record rec = RecordIndex.getInstance().newRecord(); + rec.setName(name); + + rec.setAddress(dest); + rec.setModified(new Date()); + try { + rec.maybeSave(); + } catch (DataFormatException ex) { + Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); + return; + } + knownHosts.add(name); + Logger.getLogger(InspectHosts.class.getName()).log(Level.INFO, "Created new address record for {0}({1})", new Object[]{name, rec.id}); + } catch (IOException ex) { + Logger.getLogger(CheckRSS.class.getName()).log(Level.SEVERE, null, ex); + } + } + + static DateFormat parseDate = new SimpleDateFormat("yyyy-MM-ddTHH:mm:ss+00:00"); + private void parse(InputStream in) { + try { + CheckRSS that = this; + Document doc = parser.parse(in); + for(Node e : new StupidW3C(doc.getElementsByTagName("link"))) { + String href = e.getAttributes().getNamedItem("href").getTextContent(); + do { + e = e.getNextSibling(); + } while(! e.getNodeName().equals("updated")); + Date updated = parseDate.parse(e.getTextContent()); + String name = href.substring(7); + String address = "This is BULLTHIT"; + } + } catch (ParseException ex) { + Logger.getLogger(CheckRSS.class.getName()).log(Level.SEVERE, null, ex); + } catch (SAXException ex) { + Logger.getLogger(CheckRSS.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + Logger.getLogger(CheckRSS.class.getName()).log(Level.SEVERE, null, ex); + } + } +} diff --git a/addresspublisher/src/i2p/dream/addresspublisher/Configuration.java b/addresspublisher/src/i2p/dream/addresspublisher/Configuration.java index 7e24377..a662496 100644 --- a/addresspublisher/src/i2p/dream/addresspublisher/Configuration.java +++ b/addresspublisher/src/i2p/dream/addresspublisher/Configuration.java @@ -24,4 +24,12 @@ public class Configuration { } static public final Charset charset = Charset.forName("UTF-8"); + + static File getTempDir() { + File conf = getConfDir(); + File temp = new File(conf,"temp"); + if(!temp.isDirectory()) + temp.mkdir(); + return temp; + } } diff --git a/addresspublisher/src/i2p/dream/addresspublisher/Context.java b/addresspublisher/src/i2p/dream/addresspublisher/Context.java new file mode 100644 index 0000000..15285bb --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/Context.java @@ -0,0 +1,21 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package i2p.dream.addresspublisher; + +import java.util.Timer; + +/** + * + * @author dream + */ +public class Context { + final public Timer timer; + final public KnownHosts knownHosts; + Context(String name) { + timer = new Timer(name+" Timer",true); + knownHosts = new KnownHosts(timer); + } +} diff --git a/addresspublisher/src/i2p/dream/addresspublisher/DownloadInspectHosts.java b/addresspublisher/src/i2p/dream/addresspublisher/DownloadInspectHosts.java new file mode 100644 index 0000000..51a0065 --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/DownloadInspectHosts.java @@ -0,0 +1,61 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package i2p.dream.addresspublisher; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.i2p.I2PAppContext; +import net.i2p.data.DataFormatException; +import net.i2p.data.Destination; + +/** + * + * @author dream + */ +public class DownloadInspectHosts extends InspectHosts { + private final URL source; + + + /* Use this if they just have a hosts.txt file. Note you will + * MISS SOME NEW NAMES from this source if they delete anything + * in the file. Probably should try not to use this. + */ + + DownloadInspectHosts(Context context, String id, URL source) { + super(context,id); + this.source = source; + } + + @Override + public void run() { + try { + File temp = File.createTempFile("hosts", "txt", Configuration.getTempDir()); + URLConnection c = source.openConnection(new Proxy( + Proxy.Type.HTTP, + new InetSocketAddress("localhost",4444))); + c.setRequestProperty("Range",Long.toString(lastPos.get())+"-"); + inspect(c.getInputStream()); + } catch (EOFException ex) { + // done reading hosts + } catch (IOException ex) { + Logger.getLogger(DownloadInspectHosts.class.getName()).log(Level.SEVERE, null, ex); + } + } +} \ No newline at end of file diff --git a/addresspublisher/src/i2p/dream/addresspublisher/DownloadRecentHosts.java b/addresspublisher/src/i2p/dream/addresspublisher/DownloadRecentHosts.java new file mode 100644 index 0000000..cfecbae --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/DownloadRecentHosts.java @@ -0,0 +1,56 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package i2p.dream.addresspublisher; + +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DownloadRecentHosts extends InspectHosts { + private final URL source; + + String lastModified = null; + String etag = null; + + /* Use this if they list only the recent hosts and respect last-modified headers + * i.e. if this source is another addresspublisher servlet. + */ + + DownloadRecentHosts(Context context, String id, URL source) { + super(context,id); + this.source = source; + } + + @Override + public void run() { + try { + File temp = File.createTempFile("hosts", "txt", Configuration.getTempDir()); + URLConnection c = source.openConnection(new Proxy( + Proxy.Type.HTTP, + new InetSocketAddress("localhost",4444))); + if(lastModified!=null) { + c.setRequestProperty("Last-Modified",lastModified); + } + if(etag!=null) { + c.setRequestProperty("ETag", etag); + } + + lastModified = c.getRequestProperty("Last-Modified"); + etag = c.getRequestProperty("ETag"); + inspect(c.getInputStream()); + } catch (EOFException ex) { + // done reading hosts + } catch (IOException ex) { + Logger.getLogger(DownloadRecentHosts.class.getName()).log(Level.SEVERE, null, ex); + } + } +} \ No newline at end of file diff --git a/addresspublisher/src/i2p/dream/addresspublisher/InspectHosts.java b/addresspublisher/src/i2p/dream/addresspublisher/InspectHosts.java index fa83ef1..050b89b 100644 --- a/addresspublisher/src/i2p/dream/addresspublisher/InspectHosts.java +++ b/addresspublisher/src/i2p/dream/addresspublisher/InspectHosts.java @@ -10,12 +10,11 @@ import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.util.Date; -import java.util.HashSet; -import java.util.Set; +import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import net.i2p.I2PAppContext; @@ -26,109 +25,19 @@ import net.i2p.data.Destination; * * @author dream */ -public class InspectHosts implements Runnable { - - static final File hosts = new File(I2PAppContext.getGlobalContext().getConfigDir(),"hosts.txt"); - static File getNames() { - return new File(Configuration.getConfDir(),"names.index"); - } - static File getLastPos() { - return new File(Configuration.getConfDir(),"last_position"); - } +public class InspectHosts extends TimerTask { + private final String id; - Set knownHosts = new HashSet(); - - /* The hosts.txt file doesn't change except the end. Hosts are added at the - * end and not reordered or anything. Thus, hax! - */ - PersistentLong lastPos = new PersistentLong(getLastPos(),0); - - /** - * @param args the command line arguments - */ public void run() { FileInputStream in = null; try { - in = new FileInputStream(getNames()); - long lp = lastPos.get(); - if(lp>0) { - in.getChannel().position(lp); - } - BufferedReader r = new BufferedReader( - new InputStreamReader(in,Configuration.charset)); - for(;;) { - String line = r.readLine(); - if(line==null) break; - knownHosts.add(line.hashCode()); - } - } catch(FileNotFoundException e) { - // index doesn't exist yet - } catch (IOException ex) { - Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); - } finally { - if(in!=null) { - try { - lastPos.set(in.getChannel().position()); - lastPos.save(getLastPos()); - in.close(); - } catch (IOException ex) { - Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - FileOutputStream out = null; - try { - in = new FileInputStream(hosts); - BufferedReader b = new BufferedReader( - new InputStreamReader(in,Configuration.charset)); - out = new FileOutputStream(getNames(),true); - for(;;) { - String line = b.readLine(); - if(line==null) break; - int comment = line.indexOf("#"); - if(comment>0) - line = line.substring(0,comment) - .replaceAll(" ", "") - .replaceAll("\t", ""); - - if(line.length()==0) continue; - - int split = line.indexOf("="); - if(split==-1) continue; - String name = line.substring(0,split) - .replaceAll(" ", "") - .replaceAll("\t", ""); - - if(name.length()==0) continue; - - if(knownHosts.contains(name.hashCode())) continue; - - Record rec = RecordIndex.getInstance().newRecord(); - rec.setName(name); - Destination address = new Destination(); - try { - address.fromBase64(line.substring(split + 1)); - } catch (DataFormatException ex) { - Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); - continue; - } - rec.setAddress(address); - rec.setModified(new Date()); - try { - rec.maybeSave(); - } catch (DataFormatException ex) { - Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); - continue; - } - knownHosts.add(name.hashCode()); - out.write((name+"\n") - .getBytes(Configuration.charset)); - - Logger.getLogger(InspectHosts.class.getName()).log(Level.INFO, - "Created new address record for {0}({1})", - new Object[]{name, rec.id}); + in = new FileInputStream(new File(I2PAppContext.getGlobalContext().getConfigDir(),"hosts.txt")); + if(lastPos.get()<0) { + lastPos.set(0); + lastPos.save(); } + in.skip(lastPos.get()); + inspect(in); } catch (FileNotFoundException ex) { Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); } catch(EOFException e) { @@ -140,12 +49,95 @@ public class InspectHosts implements Runnable { if (in != null) { in.close(); } - if (out != null) { - out.close(); - } } catch (IOException ex) { Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); } } } + + static final File positions = new File(Configuration.getConfDir(),"positions"); + static { + if (!positions.isDirectory()) + positions.mkdir(); + } + + private final KnownHosts knownHosts; + protected final PersistentLong lastPos; + + InspectHosts(Context context, String id) { + this.knownHosts = context.knownHosts; + lastPos = new PersistentLong(new File(positions,id),0); + this.id = id; + } + + /* The hosts.txt file doesn't change except the end. Hosts are added at the + * end and not reordered or anything. Thus, hax! + */ + + public void inspect(InputStream in) throws IOException { + BufferedReader b = new BufferedReader( + new InputStreamReader(in, Configuration.charset)); + for (;;) { + String line = b.readLine(); + if (line == null) { + break; + } + synchronized (lastPos) { + lastPos.set(lastPos.get() + line.length()); + lastPos.save(); + } + + int comment = line.indexOf("#"); + if(comment==0) + continue; + if (comment > 0) { + line = line.substring(0, comment).replaceAll(" ", "").replaceAll("\t", ""); + } + + if (line.length() == 0) { + continue; + } + + int split = line.indexOf("="); + if (split == -1) { + continue; + } + String name = line.substring(0, split).replaceAll(" ", "").replaceAll("\t", ""); + + if (name.length() == 0) { + continue; + } + + if (knownHosts.contains(name)) { + continue; + } + + if(split == line.length()-1) + continue; + + Destination address = new Destination(); + try { + address.fromBase64(line.substring(split + 1)); + } catch (DataFormatException ex) { + Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, name+" had a bad address: "+line.substring(split+1), ex); + continue; + } + + Record rec = RecordIndex.getInstance().newRecord(); + rec.setName(name); + + rec.setAddress(address); + rec.setModified(new Date()); + try { + rec.maybeSave(); + } catch (DataFormatException ex) { + Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); + continue; + } + knownHosts.add(name); + Logger.getLogger(InspectHosts.class.getName()).log(Level.INFO, + "Created new address record for {0}({1})", + new Object[]{name, rec.id}); + } + } } diff --git a/addresspublisher/src/i2p/dream/addresspublisher/KnownHosts.java b/addresspublisher/src/i2p/dream/addresspublisher/KnownHosts.java new file mode 100644 index 0000000..6f144cc --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/KnownHosts.java @@ -0,0 +1,125 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package i2p.dream.addresspublisher; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.HashSet; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author dream + */ +public class KnownHosts { + + private final Set knownHosts = new HashSet(); + private final Set needSaving = new HashSet(); + + private static final File index = new File(Configuration.getConfDir(),"names.index"); + + final Timer timer; + KnownHosts(Timer timer) { + this.timer = timer; + + InputStream in = null; + try { + in = new FileInputStream(index); + BufferedReader r = new BufferedReader( + new InputStreamReader(in,Configuration.charset)); + for(;;) { + String line = r.readLine(); + if(line==null) break; + knownHosts.add(line.hashCode()); + } + } catch(FileNotFoundException e) { + // index doesn't exist yet + } catch (IOException ex) { + Logger.getLogger(InspectHosts.class.getName()).log(Level.SEVERE, null, ex); + } finally { + if(in != null) try { + in.close(); + } catch (IOException ex) { + Logger.getLogger(KnownHosts.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + void add(String name) { + synchronized(knownHosts) { + knownHosts.add(name.hashCode()); + needSaving.add(name); + } + saveLater(); + } + + boolean contains(String name) { + synchronized(knownHosts) { + return knownHosts.contains(name.hashCode()); + } + } + + boolean saving = false; + final Object slock = new Object(); + void saveLater() { + final KnownHosts that = this; + synchronized(slock) { + if(saving) return; + saving = true; + timer.schedule(new TimerTask() { + @Override + public void run() { + that.save(); + } + }, 1000); + } + } + + void save() { + synchronized(slock) { + saving = false; + } + + FileOutputStream out = null; + try { + out = new FileOutputStream(index,true); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, + Configuration.charset)); + synchronized(needSaving) { + for(String name : needSaving) { + writer.write(name,0,name.length()); + writer.newLine(); + } + needSaving.clear(); + } + } catch (IOException ex) { + Logger.getLogger(KnownHosts.class.getName()).log(Level.SEVERE, null, ex); + } finally { + if(out!=null) { + try { + out.close(); + } catch (IOException ex) { + Logger.getLogger(KnownHosts.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + } +} diff --git a/addresspublisher/src/i2p/dream/addresspublisher/LineReader.java b/addresspublisher/src/i2p/dream/addresspublisher/LineReader.java new file mode 100644 index 0000000..035282c --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/LineReader.java @@ -0,0 +1,67 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package i2p.dream.addresspublisher; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author dream + */ +class LineReader implements Iterable { + + private static class LineIterator implements Iterator { + private final BufferedReader b; + private boolean done = false; + private String nextLine = null; + + public LineIterator(BufferedReader b) { + this.b = b; + queueLine(); + } + + final void queueLine() { + try { + nextLine = b.readLine(); + } catch (IOException ex) { + done = true; + } + if(nextLine==null) + done = true; + } + + public boolean hasNext() { + return !done; + } + + public String next() { + String line = nextLine; + queueLine(); + return line; + } + + public void remove() { + throw new UnsupportedOperationException("Not supported yet."); + } + } + private final BufferedReader b; + + public LineReader(InputStream in) { + this.b = new BufferedReader( + new InputStreamReader(in, Configuration.charset)); + } + + public Iterator iterator() { + return new LineIterator(b); + } + +} diff --git a/addresspublisher/src/i2p/dream/addresspublisher/PersistentLong.java b/addresspublisher/src/i2p/dream/addresspublisher/PersistentLong.java index f3836bd..6538b90 100644 --- a/addresspublisher/src/i2p/dream/addresspublisher/PersistentLong.java +++ b/addresspublisher/src/i2p/dream/addresspublisher/PersistentLong.java @@ -21,9 +21,11 @@ import java.util.logging.Logger; */ class PersistentLong { long num; + File f; PersistentLong(File f, long def) { + this.f = f; num = def; - InputStream in = null; + FileInputStream in = null; try { try { in = new FileInputStream(f); @@ -31,6 +33,7 @@ class PersistentLong { num = 0; return; } + in.getChannel().lock(); byte[] buf = new byte[8]; in.read(buf); num = buf[7]<<0x38 | @@ -53,7 +56,7 @@ class PersistentLong { } } - public void save(File f) throws IOException { + public void save() throws IOException { byte[] buf = new byte[8]; buf[0] = (byte) (num & 0xff); buf[1] = (byte) ((num >> 8) & 0xff); @@ -63,9 +66,10 @@ class PersistentLong { buf[5] = (byte) ((num >> 0x28) & 0xff); buf[6] = (byte) ((num >> 0x30) & 0xff); buf[7] = (byte) ((num >> 0x38) & 0xff); - OutputStream out = null; + FileOutputStream out = null; try { out = new FileOutputStream(f); + out.getChannel().lock(); out.write(buf); } finally { if(out!=null) diff --git a/addresspublisher/src/i2p/dream/addresspublisher/RecordIndex.java b/addresspublisher/src/i2p/dream/addresspublisher/RecordIndex.java index a0c5626..ff6be1c 100644 --- a/addresspublisher/src/i2p/dream/addresspublisher/RecordIndex.java +++ b/addresspublisher/src/i2p/dream/addresspublisher/RecordIndex.java @@ -39,21 +39,21 @@ class RecordGetter implements Iterator { * @author dream */ class RecordIndex implements Iterable { - PersistentLong top; + final PersistentLong top; RecordIndex() throws IOException { - top = new PersistentLong(indexFile(),0); - } - - private File indexFile() { - return new File(Configuration.getConfDir(), "index"); + top = new PersistentLong( + new File(Configuration.getConfDir(), "top.long"),0); + top.set(0xb10); + top.save(); + System.out.println("***********8 top is "+Long.toHexString(top.get())); } Record newRecord() throws IOException { - synchronized (Record.class) { + synchronized (top) { long id = top.get(); top.set(id+1); - top.save(indexFile()); + top.save(); return Record.get(id); } } diff --git a/addresspublisher/src/i2p/dream/addresspublisher/Servlet.java b/addresspublisher/src/i2p/dream/addresspublisher/Servlet.java index e09aee3..d269eb5 100644 --- a/addresspublisher/src/i2p/dream/addresspublisher/Servlet.java +++ b/addresspublisher/src/i2p/dream/addresspublisher/Servlet.java @@ -7,7 +7,12 @@ package i2p.dream.addresspublisher; import java.io.IOException; import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Date; +import java.util.Map; +import java.util.Timer; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletConfig; @@ -21,50 +26,16 @@ import javax.servlet.http.HttpServletResponse; * @author dream */ -class RepeatedInspect extends Thread { - InspectHosts guy = new InspectHosts(); - private boolean ready = false; - @Override - @SuppressWarnings("SleepWhileHoldingLock") - public void run() { - /* we can't wait/notify because java doesn't have inotify support, so - * polling is the only option. - * jnotify support would be really nice... - * at least it seeks to the lowest last position every time! - */ - for(;;) { - try { - guy.run(); - synchronized(this) { - ready = true; - this.notifyAll(); - } - } catch(Exception ex) { - // we never want this thread to die. - Logger.getLogger(RepeatedInspect.class.getName()).log(Level.SEVERE, null, ex); - } - try { - // poll once an hour. - Thread.sleep(3600000); - } catch (InterruptedException ex) { - Logger.getLogger(RepeatedInspect.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - void waitUntilReady() throws InterruptedException { - synchronized(this) { - if(ready) return; - this.wait(10000); - } - } -} - public class Servlet extends HttpServlet { Date myModified; - private static RepeatedInspect inspect; + + static final Context context = new Context("Servlet"); + + final Map subscriptions = + new TreeMap(); @Override + @SuppressWarnings("ResultOfObjectAllocationIgnored") public void init(ServletConfig config) throws ServletException { try { for (Record r : RecordIndex.getInstance()) { @@ -76,10 +47,20 @@ public class Servlet extends HttpServlet { } if(myModified==null) myModified = new Date(-1); - - if(inspect==null) { - inspect = new RepeatedInspect(); - inspect.start(); + + Timer timer = context.timer; + + //timer.schedule(new InspectHosts(context,"master"),1000,60*60*1000); + for (String uri : new String[] { + "http://stats.i2p/cgi-bin/newhosts.txt", + "http://i2host.i2p/cgi-bin/i2hostetag"}) { + try { + DownloadRecentHosts task = new DownloadRecentHosts(context, uri, new URL(uri)); + subscriptions.put(uri,task); + timer.schedule(task,10*60*1000,60*60*1000); + } catch (MalformedURLException ex) { + Logger.getLogger(Servlet.class.getName()).log(Level.SEVERE, null, ex); + } } } @@ -137,12 +118,6 @@ public class Servlet extends HttpServlet { } private void recentHosts(Date modified, PrintWriter out) throws IOException { - try { - inspect.waitUntilReady(); - } catch (InterruptedException ex) { - out.write("# not ready yet, still loading. Try again in a bit."); - return; - } for(Record r : RecordIndex.getInstance()) { Date rmod = r.getModified(); @@ -151,6 +126,17 @@ public class Servlet extends HttpServlet { if(rmod.after(myModified)) myModified = rmod; + if(r.getModified() == null) { + throw new RuntimeException("Augh! "+r.id); + } + if(r.getAddress() == null) { + throw new RuntimeException("BORG" + r.id + ": "+r.getName()); + } + if(r.getName() == null) { + throw new RuntimeException("GROB"); + } + + out.write("# Modified="+Long.toHexString(r.getModified().getTime())+" "+"ID="+r.id +"\n"+r.getName()+"="+r.getAddress().toBase64() +"\n"); diff --git a/addresspublisher/src/i2p/dream/addresspublisher/StupidW3C.java b/addresspublisher/src/i2p/dream/addresspublisher/StupidW3C.java new file mode 100644 index 0000000..cb830a7 --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/StupidW3C.java @@ -0,0 +1,38 @@ +package i2p.dream.addresspublisher; + +import java.util.Iterator; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * + * @author dream + */ +class StupidW3C implements Iterable { + + int position; + final NodeList list; + + public StupidW3C(NodeList list) { + this.list = list; + this.position = 0; + } + + public Iterator iterator() { + return new Iterator() { + int position = 0; + + public boolean hasNext() { + return (position + 1) < list.getLength(); + } + + public Node next() { + return list.item(position++); + } + + public void remove() { + throw new UnsupportedOperationException("Not supported yet."); + } + }; + } +} diff --git a/addresspublisher/src/i2p/dream/addresspublisher/SubscriptionsServlet.java b/addresspublisher/src/i2p/dream/addresspublisher/SubscriptionsServlet.java new file mode 100644 index 0000000..063aebb --- /dev/null +++ b/addresspublisher/src/i2p/dream/addresspublisher/SubscriptionsServlet.java @@ -0,0 +1,140 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package i2p.dream.addresspublisher; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.channels.FileLock; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.i2p.I2PAppContext; + +/** + * + * @author dream + */ + +public class SubscriptionsServlet extends HttpServlet { + static final Context context = new Context("Subscriptions Servlet"); + + static final File subscriptionsFile = new File(Configuration.getConfDir(),"subscriptions.txt"); + + final Map subscriptions = + new TreeMap(); + + void reloadSubscriptions() throws IOException { + Timer timer = context.timer; + + FileInputStream in = null; + try { + in = new FileInputStream(subscriptionsFile); + FileLock lock = in.getChannel().lock(); + for(String line : new LineReader(in)) { + int hash = line.indexOf('#'); + if(hash==0) continue; + else if(hash > 0) { + line = line.substring(0,hash); + } + if(line.length()==0) continue; + + try { + DownloadRecentHosts task = new DownloadRecentHosts(context, line, new URL(line)); + subscriptions.put(line, task); + timer.schedule(task, 10 * 60 * 1000, 60 * 60 * 1000); + } + catch (MalformedURLException ex) { + Logger.getLogger(SubscriptionsServlet.class.getName()).log(Level.SEVERE, null, ex); + } + } + } catch (FileNotFoundException ex) { + Logger.getLogger(SubscriptionsServlet.class.getName()).log(Level.SEVERE, null, ex); + } finally { + if(in != null) + in.close(); + } + } + + @Override + public void init(ServletConfig config) throws ServletException { + try { + reloadSubscriptions(); + } catch (IOException ex) { + Logger.getLogger(SubscriptionsServlet.class.getName()).log(Level.SEVERE, null, ex); + } + } + + + /** just check if console password option is set, jetty will do auth */ + private boolean validPassphrase() { + String pass = I2PAppContext.getGlobalContext().getProperty("consolePassword"); + return pass != null && pass.trim().length() > 0; + } + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + if(!validPassphrase()) return; + + response.setContentType("text/html"); + PrintWriter out = null; + try { + out = response.getWriter(); + out.println("Addressbook Subscription Configuration"); + out.print("
"); + out.println("
"); + } finally { + if(out!=null) + out.close(); + } + + } + + @Override + protected void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + String newsubs = request.getParameter("subscriptions"); + FileOutputStream out = null; + try { + out = new FileOutputStream(subscriptionsFile); + FileLock lock = out.getChannel().lock(); + out.write(Configuration.charset.encode(newsubs).array()); + } finally { + if(out!=null) + out.close(); + } + + for(TimerTask task : subscriptions.values()) { + task.cancel(); + } + + reloadSubscriptions(); + + response.sendRedirect("/"); + } +} diff --git a/addresspublisher/web-config.xml b/addresspublisher/web-config.xml new file mode 100644 index 0000000..9c004e3 --- /dev/null +++ b/addresspublisher/web-config.xml @@ -0,0 +1,20 @@ + + + + AddressPublisherSubscriptions + i2p.dream.addresspublisher.SubscriptionsServlet + + + AddressPublisherBook + i2p.dream.addresspublisher.BookServlet + + + + AddressPublisherSubscriptions + /subscriptions/* + + + AddressPublisherBook + /* + + diff --git a/lookup/shortcut/src/i2p/dream/lookup/Shortcut.java b/lookup/shortcut/src/i2p/dream/lookup/Shortcut.java index 126ebd2..ad606a0 100644 --- a/lookup/shortcut/src/i2p/dream/lookup/Shortcut.java +++ b/lookup/shortcut/src/i2p/dream/lookup/Shortcut.java @@ -6,9 +6,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.net.URLEncoder; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.data.Base32; +import net.i2p.data.Base64; import net.i2p.data.Destination; import net.i2p.data.DataFormatException; @@ -41,17 +43,32 @@ public class Shortcut extends HttpServlet { if(name==null) { askForName(out); } else { + if (name.startsWith("http://")) { + name = name.substring(7); + } + int i = name.indexOf('/'); + if(i > 0) { + name = name.substring(0,i); + } + boolean isb32 = false; Destination address = null; try { - if(name.length()==Hash.HASH_LENGTH*5/4) { - name = i2p.dream.BaseConvert.toBase32(name)+".b32.i2p"; + int length = name.length(); + if(length==Hash.HASH_LENGTH*5/4) { + name = Base32.encode(Base64.decode(name))+".b32.i2p"; + isb32 = true; + } else if(length==Hash.HASH_LENGTH*13/8) { + name = name + ".b32.i2p"; + isb32 = true; + } else if(name.endsWith(".b32.i2p")) { + isb32 = true; } address = I2PTunnel.destFromName(name); } catch (DataFormatException ex) { Logger.getLogger(Shortcut.class.getName()).log(Level.SEVERE, null, ex); } - showShortcut(name,address,out); + showShortcut(name,address,out,isb32); } } finally { if(out!=null) out.close(); @@ -69,21 +86,40 @@ public class Shortcut extends HttpServlet { void showShortcut(String name, Destination address, - PrintWriter out) + PrintWriter out, + boolean isb32) throws IOException { if(address==null) { out.println("Could not find '"+name+"'"); return; } - String b32 = Base32.encode(address.calculateHash().getData()); - String b64 = address.toBase64(); + final String b32 = Base32.encode(address.calculateHash().getData()); + final String b64 = address.toBase64(); - out.println(""+b32+""); - b32 = "http://" + b32 + ".b32.i2p"; + boolean showName = true; + if(name.equals(b32)) { + showName = false; + } + + out.println(""); + if(showName) { + out.print(name+" - "); + } + out.println(b32); + out.println(""); + final String b32uri = "http://" + b32 + ".b32.i2p"; + if(showName) { + out.println("

Information for: "+name+"

"); + } out.println("

Base64:

"+b64+"

"); - out.println("

Base32:

"+b32+"

"); - out.println("

Click here and fill in a name at the bottom to add this site to your addressbook!

"); + out.println("

Base32:

"+b32+"

"); + out.println("

Click here and " + + (isb32 ? "fill in a name at" : "page down to") + + " the bottom to add this site to your addressbook!

"); try { Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection s = new StringSelection(b32); @@ -100,4 +136,4 @@ public class Shortcut extends HttpServlet { } out.println(""); } -}; \ No newline at end of file +};