diff --git a/apps/addressbook/README.txt b/apps/addressbook/README.txt
new file mode 100644
index 000000000..dfeec5f52
--- /dev/null
+++ b/apps/addressbook/README.txt
@@ -0,0 +1,32 @@
+addressbook v2.0.2 - A simple name resolution mechanism for I2P
+
+addressbook is a simple implementation of subscribable address books for I2P.
+Addresses are stored in userhosts.txt and a second copy of the address book is
+placed on your eepsite as hosts.txt.
+
+subscriptions.txt contains a list of urls to check for new addresses.
+Since the urls are checked in order, and conflicting addresses are not added,
+addressbook.subscriptions can be considered to be ranked in order of trust.
+
+The system created by addressbook is similar to the early days of DNS,
+when everyone ran a local name server. The major difference is the lack of
+authority. Name cannot be guaranteed to be globally unique, but in practise
+they probably will be, for a variety of social reasons.
+
+Requirements
+************
+
+i2p with a running http proxy
+
+Installation and Usage
+**********************
+
+1. Unzip addressbook-%ver.zip into your i2p directory.
+2. Restart your router.
+
+The addressbook daemon will automatically run while the router is up.
+
+Aside from the daemon itself, the other elements of the addressbook interface
+are the config.txt, myhosts.txt, and subscriptions.txt files found in the addressbook
+directory. Those files are largely self-documenting, so if you want to know what they
+do, just read them.
\ No newline at end of file
diff --git a/apps/addressbook/build.xml b/apps/addressbook/build.xml
new file mode 100644
index 000000000..a8edbbc8f
--- /dev/null
+++ b/apps/addressbook/build.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/addressbook/config.txt b/apps/addressbook/config.txt
new file mode 100644
index 000000000..53e03127e
--- /dev/null
+++ b/apps/addressbook/config.txt
@@ -0,0 +1,43 @@
+# This is the configuration file for addressbook.
+#
+# Options
+# *******
+# All paths are realitive to i2p/addressbook. Default value for
+# each option is given in parentheses.
+#
+# proxy_host The hostname of your I2P http proxy.
+# (localhost)
+#
+# proxy_port The port of your I2P http proxy. (4444)
+#
+# master_addressbook The path to your master address book, used for local
+# changes only. (myhosts.txt)
+#
+# router_addressbook The path to the address book used by the router.
+# Contains the addresses from your master address book
+# and your subscribed address books. (../userhosts.txt)
+#
+# published_addressbook The path to the copy of your address book made
+# available on i2p. (../eepsite/docroot/hosts.txt)
+#
+# log The path to your addressbook log. (log.txt)
+#
+# subscriptions The path to your subscription file. (subscriptions.txt)
+#
+# etags The path to the etags header storage file. (etags)
+#
+# last_modified The path to the last-modified header storage file.
+# (last_modified)
+#
+# update_delay The time (in hours) between each update. (1)
+
+proxy_host=localhost
+proxy_port=4444
+master_addressbook=myhosts.txt
+router_addressbook=../userhosts.txt
+published_addressbook=../eepsite/docroot/hosts.txt
+log=log.txt
+subscriptions=subscriptions.txt
+etags=etags
+last_modified=last_modified
+update_delay=1
diff --git a/apps/addressbook/java/src/addressbook/AddressBook.java b/apps/addressbook/java/src/addressbook/AddressBook.java
new file mode 100644
index 000000000..4261f2d56
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/AddressBook.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * An address book for storing human readable names mapped to base64 i2p
+ * destinations. AddressBooks can be created from local and remote files, merged
+ * together, and written out to local files.
+ *
+ * @author Ragnarok
+ *
+ */
+public class AddressBook {
+
+ private String location;
+
+ private Map addresses;
+
+ private boolean modified;
+
+ /**
+ * Construct an AddressBook from the contents of the Map addresses.
+ *
+ * @param addresses
+ * A Map containing human readable addresses as keys, mapped to
+ * base64 i2p destinations.
+ */
+ public AddressBook(Map addresses) {
+ this.addresses = addresses;
+ }
+
+ /**
+ * Construct an AddressBook from the contents of the file at url. If the
+ * remote file cannot be read, construct an empty AddressBook
+ *
+ * @param url
+ * A URL pointing at a file with lines in the format "key=value",
+ * where key is a human readable name, and value is a base64 i2p
+ * destination.
+ */
+ public AddressBook(URL url) {
+ this.location = url.getHost();
+
+ try {
+ this.addresses = ConfigParser.parse(url);
+ } catch (IOException exp) {
+ this.addresses = new HashMap();
+ }
+ }
+
+ /**
+ * Construct an AddressBook from the Subscription subscription. If the
+ * address book at subscription has not changed since the last time it was
+ * read or cannot be read, return an empty AddressBook.
+ *
+ * @param subscription
+ * A Subscription instance pointing at a remote address book.
+ */
+ public AddressBook(Subscription subscription) {
+ this.location = subscription.getLocation();
+
+ try {
+ URL url = new URL(subscription.getLocation());
+ HttpURLConnection connection = (HttpURLConnection) url
+ .openConnection();
+ if (subscription.getEtag() != null) {
+ connection.addRequestProperty("If-None-Match", subscription
+ .getEtag());
+ }
+ if (subscription.getLastModified() != null) {
+ connection.addRequestProperty("If-Modified-Since", subscription
+ .getLastModified());
+ }
+ connection.connect();
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
+ connection.disconnect();
+ this.addresses = new HashMap();
+ return;
+ }
+ if (connection.getHeaderField("ETag") != null) {
+ subscription.setEtag(connection.getHeaderField("ETag"));
+ }
+ if (connection.getHeaderField("Last-Modified") != null) {
+ subscription.setLastModified(connection
+ .getHeaderField("Last-Modified"));
+ }
+ } catch (IOException exp) {
+ }
+
+ try {
+ this.addresses = ConfigParser.parse(new URL(subscription
+ .getLocation()));
+ } catch (IOException exp) {
+ this.addresses = new HashMap();
+ }
+ }
+
+ /**
+ * Construct an AddressBook from the contents of the file at file. If the
+ * file cannot be read, construct an empty AddressBook
+ *
+ * @param file
+ * A File pointing at a file with lines in the format
+ * "key=value", where key is a human readable name, and value is
+ * a base64 i2p destination.
+ */
+ public AddressBook(File file) {
+ this.location = file.toString();
+ try {
+ this.addresses = ConfigParser.parse(file);
+ } catch (IOException exp) {
+ this.addresses = new HashMap();
+ }
+ }
+
+ /**
+ * Return a Map containing the addresses in the AddressBook.
+ *
+ * @return A Map containing the addresses in the AddressBook, where the key
+ * is a human readable name, and the value is a base64 i2p
+ * destination.
+ */
+ public Map getAddresses() {
+ return this.addresses;
+ }
+
+ /**
+ * Return the location of the file this AddressBook was constructed from.
+ *
+ * @return A String representing either an abstract path, or a url,
+ * depending on how the instance was constructed.
+ */
+ public String getLocation() {
+ return this.location;
+ }
+
+ /**
+ * Return a string representation of the contents of the AddressBook.
+ *
+ * @return A String representing the contents of the AddressBook.
+ */
+ public String toString() {
+ return this.addresses.toString();
+ }
+
+ /**
+ * Merge this AddressBook with AddressBook other, writing messages about new
+ * addresses or conflicts to log. Addresses in AddressBook other that are
+ * not in this AddressBook are added to this AddressBook. In case of a
+ * conflict, addresses in this AddressBook take precedence
+ *
+ * @param other
+ * An AddressBook to merge with.
+ * @param log
+ * The log to write messages about new addresses or conflicts to.
+ */
+ public void merge(AddressBook other, Log log) {
+ Iterator otherIter = other.addresses.keySet().iterator();
+
+ while (otherIter.hasNext()) {
+ String otherKey = (String) otherIter.next();
+ String otherValue = (String) other.addresses.get(otherKey);
+
+ if (otherValue.length() >= 516) {
+ if (this.addresses.containsKey(otherKey)) {
+ if (!this.addresses.get(otherKey).equals(otherValue)
+ && log != null) {
+ log.append("Conflict for " + otherKey + " from "
+ + other.location
+ + ". Destination in remote address book is "
+ + otherValue);
+ }
+ } else {
+ this.addresses.put(otherKey, otherValue);
+ this.modified = true;
+ if (log != null) {
+ log.append("New address " + otherKey
+ + " added to address book.");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Merge this AddressBook with other, without logging.
+ *
+ * @param other
+ * An AddressBook to merge with.
+ */
+ public void merge(AddressBook other) {
+ this.merge(other, null);
+ }
+
+ /**
+ * Write the contents of this AddressBook out to the File file. If the file
+ * cannot be writen to, this method will silently fail.
+ *
+ * @param file
+ * The file to write the contents of this AddressBook too.
+ */
+ public void write(File file) {
+ if (this.modified) {
+ try {
+ ConfigParser.write(this.addresses, file);
+ } catch (IOException exp) {
+ }
+ }
+ }
+
+ /**
+ * Write this AddressBook out to the file it was read from. Requires that
+ * AddressBook was constructed from a file on the local filesystem. If the
+ * file cannot be writen to, this method will silently fail.
+ */
+ public void write() {
+ this.write(new File(this.location));
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/ConfigParser.java b/apps/addressbook/java/src/addressbook/ConfigParser.java
new file mode 100644
index 000000000..7ae0aa132
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/ConfigParser.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.io.*;
+import java.net.URL;
+
+/**
+ * Utility class providing methods to parse and write files in config file
+ * format, and subscription file format.
+ *
+ * @author Ragnarok
+ */
+public class ConfigParser {
+
+ /**
+ * Strip the comments from a String. Lines that begin with '#' and ';' are
+ * considered comments, as well as any part of a line after a '#'.
+ *
+ * @param inputLine
+ * A String to strip comments from.
+ * @return A String without comments, but otherwise identical to inputLine.
+ */
+ public static String stripComments(String inputLine) {
+ if (inputLine.startsWith(";")) {
+ return "";
+ }
+ if (inputLine.split("#").length > 0) {
+ return inputLine.split("#")[0];
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Return a Map using the contents of BufferedReader input. input must have
+ * a single key, value pair on each line, in the format: key=value. Lines
+ * starting with '#' or ';' are considered comments, and ignored. Lines that
+ * are obviously not in the format key=value are also ignored.
+ *
+ * @param input
+ * A BufferedReader with lines in key=value format to parse into
+ * a Map.
+ * @return A Map containing the key, value pairs from input.
+ * @throws IOException
+ * if the BufferedReader cannot be read.
+ *
+ */
+ public static Map parse(BufferedReader input) throws IOException {
+ Map result = new HashMap();
+ String inputLine;
+ inputLine = input.readLine();
+ while (inputLine != null) {
+ inputLine = ConfigParser.stripComments(inputLine);
+ String[] splitLine = inputLine.split("=");
+ if (splitLine.length == 2) {
+ result.put(splitLine[0].trim(), splitLine[1].trim());
+ }
+ inputLine = input.readLine();
+ }
+ input.close();
+ return result;
+ }
+
+ /**
+ * Return a Map using the contents of the file at url. See
+ * parseBufferedReader for details of the input format.
+ *
+ * @param url
+ * A url pointing to a file to parse.
+ * @return A Map containing the key, value pairs from url.
+ * @throws IOException
+ * if url cannot be read.
+ */
+ public static Map parse(URL url) throws IOException {
+ InputStream urlStream;
+ urlStream = url.openConnection().getInputStream();
+ BufferedReader br = new BufferedReader(new InputStreamReader(urlStream));
+ return ConfigParser.parse(br);
+ }
+
+ /**
+ * Return a Map using the contents of the File file. See parseBufferedReader
+ * for details of the input format.
+ *
+ * @param file
+ * A File to parse.
+ * @return A Map containing the key, value pairs from file.
+ * @throws IOException
+ * if file cannot be read.
+ */
+ public static Map parse(File file) throws IOException {
+ FileInputStream fileStream;
+ fileStream = new FileInputStream(file);
+ BufferedReader br = new BufferedReader(
+ new InputStreamReader(fileStream));
+ return ConfigParser.parse(br);
+ }
+
+ /**
+ * Return a List where each element is a line from the File file.
+ *
+ * @param file
+ * A File to parse.
+ * @return A List consisting of one element for each line in file.
+ * @throws IOException
+ * if file cannot be read.
+ */
+ public static List parseSubscriptions(File file) throws IOException {
+ FileInputStream fileStream = new FileInputStream(file);
+ BufferedReader br = new BufferedReader(
+ new InputStreamReader(fileStream));
+ List result = new LinkedList();
+ String inputLine = br.readLine();
+ while (inputLine != null) {
+ inputLine = ConfigParser.stripComments(inputLine);
+ if (inputLine.trim().length() > 0) {
+ result.add(inputLine.trim());
+ }
+ inputLine = br.readLine();
+ }
+ br.close();
+ return result;
+ }
+
+ /**
+ * Write contents of Map hash to BufferedWriter output. Output is written
+ * with one key, value pair on each line, in the format: key=value.
+ *
+ * @param hash
+ * A Map to write to output.
+ * @param output
+ * A BufferedWriter to write the Map to.
+ * @throws IOException
+ * if the BufferedWriter cannot be written to.
+ */
+ public static void write(Map hash, BufferedWriter output)
+ throws IOException {
+ Iterator keyIter = hash.keySet().iterator();
+
+ while (keyIter.hasNext()) {
+ String key = (String) keyIter.next();
+ output.write(key + "=" + (String) hash.get(key));
+ output.newLine();
+ }
+ output.close();
+ }
+
+ /**
+ * Write contents of Map hash to the file at location. Output is written
+ * with one key, value pair on each line, in the format: key=value.
+ *
+ * @param hash
+ * A Map to write to file.
+ * @param file
+ * A File to write the Map to.
+ * @throws IOException
+ * if file cannot be written to.
+ */
+ public static void write(Map hash, File file) throws IOException {
+ ConfigParser.write(hash,
+ new BufferedWriter(new FileWriter(file, false)));
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Daemon.java b/apps/addressbook/java/src/addressbook/Daemon.java
new file mode 100644
index 000000000..ae1d8040f
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Daemon.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Main class of addressbook. Performs updates, and runs the main loop.
+ *
+ * @author Ragnarok
+ *
+ */
+public class Daemon {
+
+ /**
+ * Update the router and published address books using remote data from the
+ * subscribed address books listed in subscriptions.
+ *
+ * @param master
+ * The master AddressBook. This address book is never
+ * overwritten, so it is safe for the user to write to.
+ * @param router
+ * The router AddressBook. This is the address book read by
+ * client applications.
+ * @param published
+ * The published AddressBook. This address book is published on
+ * the user's eepsite so that others may subscribe to it.
+ * @param subscriptions
+ * A SubscriptionList listing the remote address books to update
+ * from.
+ * @param log
+ * The log to write changes and conflicts to.
+ */
+ public static void update(AddressBook master, AddressBook router,
+ File published, SubscriptionList subscriptions, Log log) {
+ String routerLocation = router.getLocation();
+ master.merge(router);
+ Iterator iter = subscriptions.iterator();
+ while (iter.hasNext()) {
+ master.merge((AddressBook) iter.next(), log);
+ }
+ master.write(new File(routerLocation));
+ master.write(published);
+ subscriptions.write();
+ }
+
+ /**
+ * Run an update, using the Map settings to provide the parameters.
+ *
+ * @param settings
+ * A Map containg the parameters needed by update.
+ * @param home
+ * The directory containing addressbook's configuration files.
+ */
+ public static void update(Map settings, String home) {
+ File masterFile = new File(home, (String) settings
+ .get("master_addressbook"));
+ File routerFile = new File(home, (String) settings
+ .get("router_addressbook"));
+ File published = new File(home, (String) settings
+ .get("published_addressbook"));
+ File subscriptionFile = new File(home, (String) settings
+ .get("subscriptions"));
+ File logFile = new File(home, (String) settings.get("log"));
+ File etagsFile = new File(home, (String) settings.get("etags"));
+ File lastModifiedFile = new File(home, (String) settings
+ .get("last_modified"));
+
+ AddressBook master = new AddressBook(masterFile);
+ AddressBook router = new AddressBook(routerFile);
+ SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
+ etagsFile, lastModifiedFile);
+ Log log = new Log(logFile);
+
+ Daemon.update(master, router, published, subscriptions, log);
+ }
+
+ /**
+ * Load the settings, set the proxy, then enter into the main loop. The main
+ * loop performs an immediate update, and then an update every number of
+ * hours, as configured in the settings file.
+ *
+ * @param args
+ * Command line arguments. If there are any arguments provided,
+ * the first is taken as addressbook's home directory, and the
+ * others are ignored.
+ */
+ public static void main(String[] args) {
+ String settingsLocation = "config.txt";
+ Map settings = new HashMap();
+ String home;
+ if (args.length > 0) {
+ home = args[0];
+ } else {
+ home = ".";
+ }
+ try {
+ settings = ConfigParser.parse(new File(home, settingsLocation));
+ } catch (IOException exp) {
+ System.out.println("Could not load " + settingsLocation);
+ }
+
+ System.setProperty("proxySet", "true");
+ System.setProperty("http.proxyHost", (String) settings
+ .get("proxy_host"));
+ System.setProperty("http.proxyPort", (String) settings
+ .get("proxy_port"));
+ long delay = Long.parseLong((String) settings.get("update_delay"));
+ if (delay < 1) {
+ delay = 1;
+ }
+ while (true) {
+ Daemon.update(settings, home);
+ try {
+ Thread.sleep(delay * 60 * 60 * 1000);
+ } catch (InterruptedException exp) {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/DaemonThread.java b/apps/addressbook/java/src/addressbook/DaemonThread.java
new file mode 100644
index 000000000..efc3ab61d
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/DaemonThread.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+/**
+ * A thread that waits five minutes, then runs the addressbook daemon.
+ *
+ * @author Ragnarok
+ *
+ */
+public class DaemonThread extends Thread {
+
+ private String[] args;
+
+ /**
+ * Construct a DaemonThread with the command line arguments args.
+ * @param args
+ * A String array to pass to Daemon.main().
+ */
+ public DaemonThread(String[] args) {
+ this.args = args;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ public void run() {
+ try {
+ Thread.sleep(5 * 60 * 1000);
+ } catch (InterruptedException exp) {
+ }
+ Daemon.main(this.args);
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Log.java b/apps/addressbook/java/src/addressbook/Log.java
new file mode 100644
index 000000000..3f9cfec38
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Log.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * A simple log with automatic time stamping.
+ *
+ * @author Ragnarok
+ *
+ */
+public class Log {
+
+ private File file;
+
+ /**
+ * Construct a Log instance that writes to the File file.
+ *
+ * @param file
+ * A File for the log to write to.
+ */
+ public Log(File file) {
+ this.file = file;
+ }
+
+ /**
+ * Write entry to a new line in the log, with appropriate time stamp.
+ *
+ * @param entry
+ * A String containing a message to append to the log.
+ */
+ public void append(String entry) {
+ try {
+ BufferedWriter bw = new BufferedWriter(new FileWriter(this.file,
+ true));
+ String timestamp = new Date().toString();
+ bw.write(timestamp + " -- " + entry);
+ bw.newLine();
+ bw.close();
+ } catch (IOException exp) {
+ }
+ }
+
+ /**
+ * Return the File that the Log is writing to.
+ *
+ * @return The File that the log is writing to.
+ */
+ public File getFile() {
+ return this.file;
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Servlet.java b/apps/addressbook/java/src/addressbook/Servlet.java
new file mode 100644
index 000000000..4826c9ed9
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Servlet.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+
+/**
+ * A wrapper for addressbook to allow it to be started as a web application.
+ *
+ * @author Ragnarok
+ *
+ */
+public class Servlet extends GenericServlet {
+
+ /* (non-Javadoc)
+ * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ public void service(ServletRequest request, ServletResponse response) {
+ }
+
+ /* (non-Javadoc)
+ * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+ */
+ public void init(ServletConfig config) {
+ try {
+ super.init(config);
+ } catch (ServletException exp) {
+ }
+ String[] args = new String[1];
+ args[0] = config.getInitParameter("home");
+ DaemonThread thread = new DaemonThread(args);
+ thread.start();
+ }
+
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/Subscription.java b/apps/addressbook/java/src/addressbook/Subscription.java
new file mode 100644
index 000000000..1847535a9
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/Subscription.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+/**
+ * A subscription to a remote address book.
+ *
+ * @author Ragnarok
+ *
+ */
+public class Subscription {
+
+ private String location;
+
+ private String etag;
+
+ private String lastModified;
+
+ /**
+ * Construct a Subscription pointing to the address book at location, that
+ * was last read at the time represented by etag and lastModified.
+ *
+ * @param location
+ * A String representing a url to a remote address book.
+ * @param etag
+ * The etag header that we recieved the last time we read this
+ * subscription.
+ * @param lastModified
+ * the last-modified header we recieved the last time we read
+ * this subscription.
+ */
+ public Subscription(String location, String etag, String lastModified) {
+ this.location = location;
+ this.etag = etag;
+ this.lastModified = lastModified;
+ }
+
+ /**
+ * Return the location this Subscription points at.
+ *
+ * @return A String representing a url to a remote address book.
+ */
+ public String getLocation() {
+ return this.location;
+ }
+
+ /**
+ * Return the etag header that we recieved the last time we read this
+ * subscription.
+ *
+ * @return A String containing the etag header.
+ */
+ public String getEtag() {
+ return this.etag;
+ }
+
+ /**
+ * Set the etag header.
+ *
+ * @param etag
+ * A String containing the etag header.
+ */
+ public void setEtag(String etag) {
+ this.etag = etag;
+ }
+
+ /**
+ * Return the last-modified header that we recieved the last time we read
+ * this subscription.
+ *
+ * @return A String containing the last-modified header.
+ */
+ public String getLastModified() {
+ return this.lastModified;
+ }
+
+ /**
+ * Set the last-modified header.
+ *
+ * @param lastModified
+ * A String containing the last-modified header.
+ */
+ public void setLastModified(String lastModified) {
+ this.lastModified = lastModified;
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/SubscriptionIterator.java b/apps/addressbook/java/src/addressbook/SubscriptionIterator.java
new file mode 100644
index 000000000..fecbb5c32
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/SubscriptionIterator.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator over the subscriptions in a SubscriptionList. Note that this iterator
+ * returns AddressBook objects, and not Subscription objects.
+ *
+ * @author Ragnarok
+ */
+public class SubscriptionIterator implements Iterator {
+
+ private Iterator subIterator;
+
+ /**
+ * Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
+ *
+ * @param subscriptions
+ * List of Subscription objects that represent address books.
+ */
+ public SubscriptionIterator(List subscriptions) {
+ this.subIterator = subscriptions.iterator();
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.util.Iterator#hasNext()
+ */
+ public boolean hasNext() {
+ return subIterator.hasNext();
+ }
+
+ /* (non-Javadoc)
+ * @see java.util.Iterator#next()
+ */
+ public Object next() {
+ Subscription sub = (Subscription) subIterator.next();
+ return new AddressBook(sub);
+ }
+
+ /* (non-Javadoc)
+ * @see java.util.Iterator#remove()
+ */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/java/src/addressbook/SubscriptionList.java b/apps/addressbook/java/src/addressbook/SubscriptionList.java
new file mode 100644
index 000000000..95f514832
--- /dev/null
+++ b/apps/addressbook/java/src/addressbook/SubscriptionList.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2004 Ragnarok
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package addressbook;
+
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A list of Subscriptions loaded from a file.
+ *
+ * @author Ragnarok
+ *
+ */
+public class SubscriptionList {
+
+ private List subscriptions;
+
+ private File etagsFile;
+
+ private File lastModifiedFile;
+
+ /**
+ * Construct a SubscriptionList using the urls from locationsFile and, if
+ * available, the etags and last-modified headers loaded from etagsFile and
+ * lastModifiedFile.
+ *
+ * @param locationsFile
+ * A file containing one url on each line.
+ * @param etagsFile
+ * A file containg the etag headers used for conditional GET. The
+ * file is in the format "url=etag".
+ * @param lastModifiedFile
+ * A file containg the last-modified headers used for conditional
+ * GET. The file is in the format "url=leastmodified".
+ */
+ public SubscriptionList(File locationsFile, File etagsFile,
+ File lastModifiedFile) {
+ this.subscriptions = new LinkedList();
+ this.etagsFile = etagsFile;
+ this.lastModifiedFile = lastModifiedFile;
+ List locations;
+ Map etags;
+ Map lastModified;
+ String location;
+ try {
+ locations = ConfigParser.parseSubscriptions(locationsFile);
+ } catch (IOException exp) {
+ locations = new LinkedList();
+ }
+ try {
+ etags = ConfigParser.parse(etagsFile);
+ } catch (IOException exp) {
+ etags = new HashMap();
+ }
+ try {
+ lastModified = ConfigParser.parse(lastModifiedFile);
+ } catch (IOException exp) {
+ lastModified = new HashMap();
+ }
+ Iterator iter = locations.iterator();
+ while (iter.hasNext()) {
+ location = (String) iter.next();
+ subscriptions.add(new Subscription(location, (String) etags
+ .get(location), (String) lastModified.get(location)));
+ }
+
+ iter = this.iterator();
+ }
+
+ /**
+ * Return an iterator over the AddressBooks represented by the Subscriptions
+ * in this SubscriptionList.
+ *
+ * @return A SubscriptionIterator.
+ */
+ public SubscriptionIterator iterator() {
+ return new SubscriptionIterator(this.subscriptions);
+ }
+
+ /**
+ * Write the etag and last-modified headers for each Subscription to files.
+ */
+ public void write() {
+ Iterator iter = this.subscriptions.iterator();
+ Subscription sub;
+ Map etags = new HashMap();
+ Map lastModified = new HashMap();
+ while (iter.hasNext()) {
+ sub = (Subscription) iter.next();
+ if (sub.getEtag() != null) {
+ etags.put(sub.getLocation(), sub.getEtag());
+ }
+ if (sub.getLastModified() != null) {
+ lastModified.put(sub.getLocation(), sub.getLastModified());
+ }
+ }
+ try {
+ ConfigParser.write(etags, this.etagsFile);
+ ConfigParser.write(lastModified, this.lastModifiedFile);
+ } catch (IOException exp) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/addressbook/myhosts.txt b/apps/addressbook/myhosts.txt
new file mode 100644
index 000000000..680d4204b
--- /dev/null
+++ b/apps/addressbook/myhosts.txt
@@ -0,0 +1,10 @@
+# addressbook master address book. Addresses placed in this file take precidence
+# over those in the router address book and in remote address books. If changes
+# are made to this file, they will be reflected in the router address book and
+# published address book after the next update.
+#
+# Do not make changes directly to the router address book, as they could be lost
+# during an update.
+#
+# This file takes addresses in the hosts.txt format, i.e.
+# example.i2p=somereallylongbase64thingAAAA
diff --git a/apps/addressbook/subscriptions.txt b/apps/addressbook/subscriptions.txt
new file mode 100644
index 000000000..62741055b
--- /dev/null
+++ b/apps/addressbook/subscriptions.txt
@@ -0,0 +1,7 @@
+# Subscription list for addressbook
+#
+# Each entry is an absolute url to a file in hosts.txt format.
+# Since the list is checked in order, url's should be listed in order of trust.
+#
+http://dev.i2p/i2p/hosts.txt
+http://duck.i2p/hosts.txt
diff --git a/apps/addressbook/web.xml b/apps/addressbook/web.xml
new file mode 100644
index 000000000..22d0d7d23
--- /dev/null
+++ b/apps/addressbook/web.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ addressbook
+ addressbook.Servlet
+
+ home
+ ./addressbook
+
+ 1
+
+
\ No newline at end of file