diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
index a1005f2e124b40802b58776f9b887b04c8b9ac1f..961e43b56fa1b941b9c70c87c2e00120e05a71b5 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
@@ -30,6 +30,7 @@ public class LogsHelper {
         buf.append("<code>\n");
         for (int i = msgs.size(); i > 0; i--) { 
             String msg = (String)msgs.get(i - 1);
+            msg = msg.replaceAll("<","&lt;");
             buf.append("<li>");
             buf.append(msg);
             buf.append("</li>\n");
@@ -46,6 +47,7 @@ public class LogsHelper {
         buf.append("<code>\n");
         for (int i = msgs.size(); i > 0; i--) { 
             String msg = (String)msgs.get(i - 1);
+            msg = msg.replaceAll("<","&lt;");
             buf.append("<li>");
             buf.append(msg);
             buf.append("</li>\n");
@@ -59,8 +61,10 @@ public class LogsHelper {
         String str = FileUtil.readTextFile("wrapper.log", 500, false);
         if (str == null) 
             return "";
-        else
+        else {
+            str = str.replaceAll("<","&lt;");
             return "<pre>" + str + "</pre>";
+        }
     }
     
     public String getConnectionLogs() {
diff --git a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
index 786df04ae6a72cb0a9ea4cacf9394f4135868c17..9c235f987f25d99aad11cbaa8316ba341aa917bb 100644
--- a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
+++ b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
@@ -324,8 +324,10 @@ public class BlogManager {
     }
     
     public boolean isConfigured() {
-        File cfg = getConfigFile();
-        return (cfg.exists());
+        String p = _context.getProperty("syndie.configurationCheck");
+        if(p==null)
+            return false;
+        return true;
     }
     
     /**
@@ -348,6 +350,73 @@ public class BlogManager {
         return delay;
     }
     
+    public List getRssFeeds() {
+        List feedList = new ArrayList();
+        int i=0;
+        while(true) {
+            String url = _context.getProperty("syndie.rssFeed."+i+".url");
+            String blog = _context.getProperty("syndie.rssFeed."+i+".blog");
+            String tagPrefix = _context.getProperty("syndie.rssFeed."+i+".tagPrefix");
+            if(url==null || blog==null || tagPrefix==null)
+                break;
+            String feed[] = new String[3];
+            feed[0]=url.trim();
+            feed[1]=blog.trim();
+            feed[2]=tagPrefix.trim();
+            feedList.add(feed);
+            i++;
+        }
+        return feedList;
+    }
+    public boolean addRssFeed(String url, String blog, String tagPrefix) {
+        
+        List feedList = getRssFeeds();
+        int nextIdx=feedList.size();
+        
+        String baseFeedProp="syndie.rssFeed."+nextIdx;
+        System.setProperty(baseFeedProp+".url",url);
+        System.setProperty(baseFeedProp+".blog",blog);
+        System.setProperty(baseFeedProp+".tagPrefix",tagPrefix);
+        _log.info("addRssFeed("+nextIdx+"): "+url);
+        writeConfig();
+        Updater.wakeup();
+        return true;
+    }
+    public boolean deleteRssFeed(String url, String blog, String tagPrefix) {
+        List feedList = getRssFeeds();
+        Iterator iter = feedList.iterator();
+        int idx=0;
+        while(iter.hasNext()) {
+            String fields[] = (String[])iter.next();
+            if(fields[0].equals(url) &&
+               fields[1].equals(blog) &&
+               fields[2].equals(tagPrefix)) {
+                break;
+            }
+            idx++;
+        }
+        
+        // copy any remaining to idx-1
+        while(iter.hasNext()) {
+            String fields[] = (String[])iter.next();
+            String baseFeedProp="syndie.rssFeed."+idx;
+            System.setProperty(baseFeedProp+".url",fields[0]);
+            System.setProperty(baseFeedProp+".blog",fields[1]);
+            System.setProperty(baseFeedProp+".tagPrefix",fields[2]);
+            idx++;
+        }
+        
+        // Delete last idx from properties
+        String baseFeedProp="syndie.rssFeed."+idx;
+        System.getProperties().remove(baseFeedProp+".url");
+        System.getProperties().remove(baseFeedProp+".blog");
+        System.getProperties().remove(baseFeedProp+".tagPrefix");
+        _log.info("deleteRssFeed("+idx+"): "+url);
+        writeConfig();
+        return true;
+    }
+     
+    
     public boolean authorizeAdmin(String pass) {
         if (isSingleUser()) return true;
         String admin = getAdminPasswordHash();
@@ -388,6 +457,7 @@ public class BlogManager {
             if (defaultProxyPort > 0)
                 out.write("syndie.defaultProxyPort="+defaultProxyPort + "\n");
             out.write("syndie.singleUser=" + isSingleUser + "\n");
+            out.write("syndie.configurationCheck=foo\n");
             if (opts != null) {
                 for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
                     String key = (String)iter.next();
@@ -525,10 +595,13 @@ public class BlogManager {
     }
     
     public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml) {
-        return createBlogEntry(user, subject, tags, entryHeaders, sml, null, null, null);
+        return createBlogEntry(user, true, subject, tags, entryHeaders, sml, null, null, null);
     }
     public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml, List fileNames, List fileStreams, List fileTypes) {
-        if (!user.getAuthenticated()) return null;
+        return createBlogEntry(user, true, subject, tags, entryHeaders, sml, fileNames, fileStreams, fileTypes);        
+    }
+    public BlogURI createBlogEntry(User user, boolean shouldAuthenticate, String subject, String tags, String entryHeaders, String sml, List fileNames, List fileStreams, List fileTypes) {
+        if (shouldAuthenticate && !user.getAuthenticated()) return null;
         BlogInfo info = getArchive().getBlogInfo(user.getBlog());
         if (info == null) return null;
         SigningPrivateKey privkey = getMyPrivateKey(info);
@@ -601,7 +674,10 @@ public class BlogManager {
             if (ok) {
                 getArchive().regenerateIndex();
                 user.setMostRecentEntry(entryId);
-                saveUser(user);
+                if(shouldAuthenticate)
+                    saveUser(user);
+                else
+                    storeUser(user);
                 return uri;
             } else {
                 return null;
diff --git a/apps/syndie/java/src/net/i2p/syndie/Sucker.java b/apps/syndie/java/src/net/i2p/syndie/Sucker.java
index e84e767d81c05b38710300afb9ebab90b1a06bb2..75ebe66c1d083993bf76c9a3d633b229bcb5ceea 100644
--- a/apps/syndie/java/src/net/i2p/syndie/Sucker.java
+++ b/apps/syndie/java/src/net/i2p/syndie/Sucker.java
@@ -6,16 +6,14 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
-import java.util.ListIterator;
-
-//import sun.security.provider.SHA;
+import java.util.Properties;
 
 import com.sun.syndication.feed.synd.SyndCategory;
 import com.sun.syndication.feed.synd.SyndContent;
@@ -27,9 +25,15 @@ import com.sun.syndication.io.XmlReader;
 
 import net.i2p.I2PAppContext;
 import net.i2p.data.Base64;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Hash;
+import net.i2p.syndie.data.BlogURI;
 import net.i2p.util.EepGet;
+import net.i2p.util.Log;
 
 public class Sucker {
+    private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(Sucker.class);
     private String urlToLoad;
     private String outputDir="./sucker_out";
     private String historyPath="./sucker.history";
@@ -46,9 +50,50 @@ public class Sucker {
     private boolean pendingEndLink;
     private boolean shouldProxy;
     private int proxyPortNum;
+    private String blog;
+    private boolean pushToSyndie;
+    private long messageNumber=0;
+    private BlogManager bm;
+    private User user;
+    
+    //
+    private List fileNames;
+    private List fileStreams;
+    private List fileTypes;
     
-    public Sucker() {}
+    public Sucker() {
+    }
     
+    /**
+     * Constructor for BlogManager. 
+     */
+    public Sucker(String[] strings) throws IllegalArgumentException {
+        pushToSyndie=true;
+        urlToLoad = strings[0];
+        blog = strings[1];
+        feedTag = strings[2];
+        outputDir = "blog-"+blog;
+        try {
+            historyPath=BlogManager.instance().getRootDir().getCanonicalPath()+"/rss.history";
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        proxyPort="4444";
+        proxyHost="localhost";
+
+        bm = BlogManager.instance();
+        Hash blogHash = new Hash();
+        try {
+            blogHash.fromBase64(blog);
+        } catch (DataFormatException e1) {
+            throw new IllegalArgumentException("ooh, bad $blog");
+        }
+     
+        user = bm.getUser(blogHash);
+        if(user==null)
+            throw new IllegalArgumentException("wtf, user==null? hash:"+blogHash);
+    }
+
     public boolean parseArgs(String args[]) {
         for (int i = 0; i < args.length; i++) {
             if ("--load".equals(args[i]))
@@ -77,58 +122,57 @@ public class Sucker {
 
         if (urlToLoad == null)
             return false;
-        
-        // Find base url, gah HELP
-        int idx=urlToLoad.length();
-        int x=urlToLoad.indexOf('?');
-        if(x>0)
-            idx=x;
-        while(idx>0)
-        {
-            idx--;
-            if(urlToLoad.charAt(idx)=='/')
-                break;
-        }
-        if(idx==0)
-            idx=x;
-        baseUrl=urlToLoad.substring(0,idx);
-
-	System.out.println("Processing: "+urlToLoad);
 
         return true;
     }
     
+    /**
+     * Fetch urlToLoad and call convertToHtml() on any new entries.
+     */
     public void suck() {
         SyndFeed feed;
+        
+        // Find base url
+        int idx=urlToLoad.lastIndexOf('/');
+        if(idx>0)
+            baseUrl=urlToLoad.substring(0,idx);
+        else
+            baseUrl=urlToLoad;
+
+        debugLog("Processing: "+urlToLoad);
+        debugLog("Base url: "+baseUrl);
 
         //
         try {
-            
-            // Create outputDir if missing
-            File f = new File(outputDir);
-            f.mkdirs();
+            File lastIdFile=null;
+         
+            // Get next message number to use (for messageId in history only)
+            if(!pushToSyndie) {
+                
+                lastIdFile = new File(historyPath + ".lastId");
+                if (!lastIdFile.exists())
+                    lastIdFile.createNewFile();
+                
+                FileInputStream fis = new FileInputStream(lastIdFile);
+                String number = readLine(fis);
+                try {
+                    messageNumber = Integer.parseInt(number);
+                } catch (NumberFormatException e) {
+                    messageNumber = 0;
+                }
+
+                // Create outputDir if missing
+                File f = new File(outputDir);
+                f.mkdirs();
+            } else {
+                messageNumber=bm.getNextBlogEntry(user);
+            }
 
             // Create historyFile if missing
             historyFile = new File(historyPath);
             if (!historyFile.exists())
                 historyFile.createNewFile();
 
-            int messageNumber;
-
-            File lastIdFile = new File(historyPath + ".lastId");
-            if (!lastIdFile.exists())
-                lastIdFile.createNewFile();
-
-            FileInputStream fis = new FileInputStream(lastIdFile);
-            String number = readLine(fis);
-            try {
-                messageNumber = Integer.parseInt(number);
-            } catch (NumberFormatException e) {
-                messageNumber = 0;
-            }
-
-            SyndFeedInput input = new SyndFeedInput();
-
             shouldProxy = false;
             proxyPortNum = -1;
             if ( (proxyHost != null) && (proxyPort != null) ) {
@@ -140,11 +184,11 @@ public class Sucker {
                     nfe.printStackTrace();
                 }
             }
+            
+            // fetch
             int numRetries = 2;
-            // perhaps specify a temp dir?
             File fetched = File.createTempFile("sucker", ".fetch");
             fetched.deleteOnExit();
-            // we use eepGet, since it retries and doesn't leak DNS requests like URL does
             EepGet get = new EepGet(I2PAppContext.getGlobalContext(), shouldProxy, proxyHost, proxyPortNum, 
                                     numRetries, fetched.getAbsolutePath(), urlToLoad);
             SuckerFetchListener lsnr = new SuckerFetchListener();
@@ -155,89 +199,71 @@ public class Sucker {
                 System.err.println("Unable to retrieve the url after " + numRetries + " tries.");
                 return;
             }
-            
+            if(get.getNotModified()) {
+                debugLog("not modified, saving network bytes from useless fetch");
+                return;
+            }
+
+            // Build entry list from fetched rss file
+            SyndFeedInput input = new SyndFeedInput();
             feed = input.build(new XmlReader(fetched));
 
             List entries = feed.getEntries();
 
             FileOutputStream hos = new FileOutputStream(historyFile, true);
 
-            ListIterator iter = entries.listIterator();
-            while (iter.hasNext()) {
+            // Process list backwards to get syndie to display the 
+            // entries in the right order. (most recent at top)
+            for (int i = entries.size()-1; i >= 0; i--) { 
+                SyndEntry e = (SyndEntry) entries.get(i);
                 
                 attachmentCounter=0;
                 
-                SyndEntry e = (SyndEntry) iter.next();
-                // Calculate messageId
-                String feedHash = sha1(urlToLoad);
-                String itemHash = sha1(e.getTitle() + e.getDescription());
-                Date d = e.getPublishedDate();
-                String time;
-                if(d!=null)
-                    time = "" + d.getTime();
-                else
-                    time = "" + new Date().getTime();
-                    
-                String outputFileName = outputDir + "/" + messageNumber;
-
-                /*
-                 * $feedHash:$itemHash:$time:$outputfile. $feedHash would be the
-                 * hash (md5? sha1? sha2?) of the $urlToFeed, $itemHash is some
-                 * hash of the SyndEntry, $time would be the time that the entry
-                 * was posted $outputfile would be the $outputdir/$uniqueid
-                 */
-                String messageId = feedHash + ":" + itemHash + ":" + time + ":"
-                                   + outputFileName;
-
-                // Check if we already have this
-                if (!existsInHistory(messageId)) {
-                    System.out.println("new: " + messageId);
-
-                    if (convertToSml(e, ""+messageNumber)) {
-
-                        if (pushScript != null) {
-                            if (!execPushScript(""+messageNumber, time)) {
-                                System.out.println("################## push failed");
-                            }else {
-                                System.out.println("push success");
-                                hos.write(messageId.getBytes());
-                                hos.write("\n".getBytes());
-                            }
-                        }
-                    }
-                    messageNumber++;
+                String messageId = convertToSml(e);
+                if (messageId!=null) {
+                    hos.write(messageId.getBytes());
+                    hos.write("\n".getBytes());
                 }
             }
-
-            FileOutputStream fos = new FileOutputStream(lastIdFile);
-            fos.write(("" + messageNumber).getBytes());
+            
+            if(!pushToSyndie) {
+                FileOutputStream fos = new FileOutputStream(lastIdFile);
+                fos.write(("" + messageNumber).getBytes());
+            }
         } catch (MalformedURLException e) {
-            // TODO Auto-generated catch block
             e.printStackTrace();
         } catch (IllegalArgumentException e) {
-            // TODO Auto-generated catch block
             e.printStackTrace();
         } catch (FeedException e) {
-            // TODO Auto-generated catch block
             e.printStackTrace();
         } catch (IOException e) {
-            // TODO Auto-generated catch block
             e.printStackTrace();
         }
-        System.out.println("Done.");
+        debugLog("Done.");
     }
 
     public static void main(String[] args) {
         Sucker sucker = new Sucker();
         boolean ok = sucker.parseArgs(args);
         if (!ok) {
-            usage();
-            return;
+            System.out.println("sucker --load $urlToFeed \n"
+                    + "--proxyhost <host> \n" 
+                    + "--proxyport <port> \n"
+                    + "--importenclosures true \n" 
+                    + "--importrefs true \n"
+                    + "--tag feed \n" 
+                    + "--outputdir ./sucker_out \n"
+                    + "--exec pushscript.sh OUTPUTDIR UNIQUEID ENTRYTIMESTAMP \n"
+                    + "--history ./sucker.history");
+            System.exit(1);
         }
         
         sucker.suck();
     }
 
+    /**
+     * Call the specified script with "$outputDir $id and $time". 
+     */
     private boolean execPushScript(String id, String time) {
         try {
             String ls_str;
@@ -251,7 +277,7 @@ public class Sucker {
 
             try {
                 while ((ls_str = ls_in.readLine()) != null) {
-                    System.out.println(pushScript + ": " + ls_str);
+                    debugLog(pushScript + ": " + ls_str);
                 }
             } catch (IOException e) {
                 return false;
@@ -261,7 +287,6 @@ public class Sucker {
                 if(pushScript_proc.exitValue()==0)
                     return true;
             } catch (InterruptedException e) {
-                // TODO Auto-generated catch block
                 e.printStackTrace();
             }
             return false;
@@ -271,40 +296,57 @@ public class Sucker {
         }
     }
 
-    private boolean convertToSml(SyndEntry e, String messageName) {
-
-        // Create message
-        FileOutputStream fos;
-        messagePath=outputDir+"/"+messageName;
+    /** 
+     * Converts the SyndEntry e to sml and fetches any images as attachments 
+     */ 
+    private String convertToSml(SyndEntry e) {
+        String subject;
+        
+        // Calculate messageId, and check if we have got the message already
+        String feedHash = sha1(urlToLoad);
+        String itemHash = sha1(e.getTitle() + e.getDescription());
+        Date d = e.getPublishedDate();
+        String time;
+        if(d!=null)
+            time = "" + d.getTime();
+        else
+            time = "" + new Date().getTime();
+        String outputFileName = outputDir + "/" + messageNumber;
+        String messageId = feedHash + ":" + itemHash + ":" + time + ":" + outputFileName;
+        // Check if we already have this
+        if (existsInHistory(messageId))
+            return null;
+        
+        debugLog("new: " + messageId);
+            
         try {
-            fos = new FileOutputStream(messagePath);
 
-            String sml;
-            sml = "Subject: " + e.getTitle() + "\n";
+            String sml="";
+            subject=e.getTitle();
             List cats = e.getCategories();
             Iterator iter = cats.iterator();
             String tags = feedTag;
             while (iter.hasNext()) {
                 SyndCategory c = (SyndCategory) iter.next();
+                debugLog("Name: "+c.getName());
+                debugLog("uri:"+c.getTaxonomyUri());
                 String tag=c.getName();
                 tag=tag.replaceAll("[^a-zA-z.-_:]","_");
                 tags += "\t" + feedTag + "." + tag;
             }
-            sml += "Tags: " + tags + "\n";
-            sml += "\n";
 
             SyndContent content;
 
             List l = e.getContents();
             if(l!=null)
             {
-            System.out.println("There is content");
+            debugLog("There is content");
                 iter = l.iterator();
                 while(iter.hasNext())
                 {
                     content = (SyndContent)iter.next();
                     String c = content.getValue();
-                    System.out.println("Content: "+c);
+                    debugLog("Content: "+c);
                                   sml += htmlToSml(c);
                                   sml += "\n";
                 }
@@ -314,17 +356,53 @@ public class Sucker {
             source=baseUrl+source;
             sml += "[link schema=\"web\" location=\""+source+"\"]source[/link]\n";
 
-            fos.write(sml.getBytes());
-
-            return true;
+            if(pushToSyndie) {
+                debugLog("user.blog: "+user.getBlogStr());
+                debugLog("user.id: "+bm.getNextBlogEntry(user));
+                debugLog("subject: "+subject);
+                debugLog("tags: "+tags);
+                debugLog("sml: "+sml);
+                debugLog("");
+                BlogURI uri = bm.createBlogEntry(
+                        user, 
+                        false,
+                        subject, 
+                        tags, 
+                        null,
+                        sml, 
+                        fileNames, 
+                        fileStreams, 
+                        fileTypes);
+
+                if(uri==null) {
+                    debugLog("pushToSyndie failure.");
+                    return null;
+                }
+                else
+                    debugLog("pushToSyndie success, uri: "+uri.toString());
+            }
+            else
+            {
+                FileOutputStream fos;
+                fos = new FileOutputStream(messagePath);
+                sml=subject + "\nTags: " + tags + "\n\n" + sml;
+                fos.write(sml.getBytes());
+                if (pushScript != null) {
+                    if (!execPushScript(""+messageNumber, time)) {
+                        debugLog("################## push failed");
+                    } else {
+                        debugLog("push success");
+                    }
+                }
+            }
+            messageNumber++;
+            return messageId;
         } catch (FileNotFoundException e1) {
-            // TODO Auto-generated catch block
             e1.printStackTrace();
         } catch (IOException e2) {
-            // TODO Auto-generated catch block
             e2.printStackTrace();
         }
-        return false;
+        return null;
     }
 
     private String htmlToSml(String html) {
@@ -338,29 +416,32 @@ public class Sucker {
         {
             if(html.charAt(i)=='<')
             {
-                //System.out.println("html: "+html.substring(i));
+                //log("html: "+html.substring(i));
                 
                 int tagLen = findTagLen(html.substring(i));
                 if(tagLen<=0)
                 {
-                        System.out.println("Bad html? ("+html+")");
+                        debugLog("Bad html? ("+html+")");
                         break;
                 }
                 //
                 String htmlTag = html.substring(i,i+tagLen);
                 
-                //System.out.println("htmlTag: "+htmlTag);
+                //log("htmlTag: "+htmlTag);
                 
                 String smlTag = htmlTagToSmlTag(htmlTag);
                 if(smlTag!=null)
                     sml+=smlTag;
                 i+=tagLen;
-                //System.out.println("tagLen: "+tagLen);
+                //log("tagLen: "+tagLen);
                 sml+=" "; 
             }
             else
             {
-                sml+=html.charAt(i++);
+                char c=html.charAt(i++);
+                sml+=c;
+                if(c=='[' || c==']')
+                    sml+=c;
             }
         }
         
@@ -373,7 +454,7 @@ public class Sucker {
 
         if(importEnclosures && htmlTagLowerCase.startsWith("<img"))
         {
-            System.out.println("Found image tag: "+htmlTag);
+            debugLog("Found image tag: "+htmlTag);
             int a,b;
             a=htmlTagLowerCase.indexOf("src=\"")+5;
             b=a+1;
@@ -383,7 +464,7 @@ public class Sucker {
             
             if(pendingEndLink) {
                 ret="[/link]";
-            pendingEndLink=false;
+                pendingEndLink=false;
             }
     
             ret += "[img attachment=\""+""+ attachmentCounter +"\"]";
@@ -391,11 +472,13 @@ public class Sucker {
             a=htmlTagLowerCase.indexOf("alt=\"")+5;
             if(a>=5)
             {
-                b=a+1;
-                while(htmlTagLowerCase.charAt(b)!='\"')
-                    b++;
-                String altText=htmlTag.substring(a,b);
-                ret+=altText;
+                b=a;
+                if(htmlTagLowerCase.charAt(b)!='\"') {
+                    while(htmlTagLowerCase.charAt(b)!='\"')
+                        b++;
+                    String altText=htmlTag.substring(a,b);
+                    ret+=altText;
+                }
             }
             
             ret+="[/img]";
@@ -405,14 +488,14 @@ public class Sucker {
             
             fetchAttachment(imageLink);
 
-            System.out.println("Converted to: "+ret);
+            debugLog("Converted to: "+ret);
             
             return ret;
             
         }
         if(importRefs && htmlTagLowerCase.startsWith("<a "))
         {
-            System.out.println("Found link tag: "+htmlTag);
+            debugLog("Found link tag: "+htmlTag);
             int a,b;
             
             a=htmlTagLowerCase.indexOf("href=\"")+6;
@@ -426,9 +509,12 @@ public class Sucker {
             String schema="web";
             
             ret += "[link schema=\""+schema+"\" location=\""+link+"\"]";
-            pendingEndLink=true;
+            if(htmlTagLowerCase.endsWith("/>"))
+                ret += "[/link]";
+            else
+                pendingEndLink=true;
         
-            System.out.println("Converted to: "+ret);
+            debugLog("Converted to: "+ret);
 
             return ret;
         }
@@ -446,19 +532,36 @@ public class Sucker {
             return "[i]";
         if("</i>".equals(htmlTagLowerCase))
             return "[/i]";
+        if("<em>".equals(htmlTagLowerCase))
+            return "[i]";
+        if("</em>".equals(htmlTagLowerCase))
+            return "[/i]";
         
         return null;
     }
 
     private void fetchAttachment(String link) {
         
-        System.out.println("Fetch attachment from: "+link);
+        link=link.replaceAll("&amp;","&");
         
-        String attachmentPath = messagePath+"."+attachmentCounter;
-            link=link.replaceAll("&amp;","&");
-            
+        debugLog("Fetch attachment from: "+link);
+        
+        File fetched;
+        if(pushToSyndie) {
+            try {
+                // perhaps specify a temp dir?
+                fetched = File.createTempFile("sucker",".attachment");
+                fetched.deleteOnExit();
+            } catch (IOException e) {
+                e.printStackTrace();
+                return;
+            }
+            fetched.deleteOnExit();
+        } else {
+            String attachmentPath = messagePath+"."+attachmentCounter;
+            fetched = new File(attachmentPath);
+        }
         int numRetries = 2;
-        File fetched = new File(attachmentPath);
         // we use eepGet, since it retries and doesn't leak DNS requests like URL does
         EepGet get = new EepGet(I2PAppContext.getGlobalContext(), shouldProxy, proxyHost, proxyPortNum, 
                                 numRetries, fetched.getAbsolutePath(), link);
@@ -471,9 +574,31 @@ public class Sucker {
             fetched.delete();
             return;
         }
+        String filename=EepGet.suggestName(link);
+        String contentType = get.getContentType();
+        if(contentType==null)
+            contentType="text/plain";
+        debugLog("successful fetch of filename "+filename);
+        if(fileNames==null) fileNames = new ArrayList();
+        if(fileTypes==null) fileTypes = new ArrayList();
+        if(fileStreams==null) fileStreams = new ArrayList();
+        fileNames.add(filename);
+        fileTypes.add(contentType);
+        try {
+            fileStreams.add(new FileInputStream(fetched));
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
         attachmentCounter++;
     }
 
+    private void debugLog(String string) {
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug(string);
+        if(!pushToSyndie)
+            System.out.println(string);
+    }
+
     private static int findTagLen(String s) {
         int i;
         for(i=0;i<s.length();i++)
@@ -487,7 +612,7 @@ public class Sucker {
                     i++;
             }   
         }
-        System.out.println("WTF");
+        System.out.println("WTF in Sucker.findTagLen("+s+")");
         return -1;
     }
 
@@ -513,25 +638,11 @@ public class Sucker {
                     return true;
             }
         } catch (FileNotFoundException e) {
-            // TODO Auto-generated catch block
             e.printStackTrace();
         }
         return false;
     }
 
-    private static void usage() {
-        System.out.println("sucker --load $urlToFeed \n"
-                + "--proxyhost <host> \n" 
-                + "--proxyport <port> \n"
-                + "--importenclosures true \n" 
-                + "--importrefs true \n"
-                + "--tag feed \n" 
-                + "--outputdir ./sucker_out \n"
-                + "--exec pushscript.sh OUTPUTDIR UNIQUEID ENTRYTIMESTAMP \n"
-                + "--history ./sucker.history");
-        System.exit(1);
-    }
-
     private static String sha1(String s) {
         try {
             MessageDigest md = MessageDigest.getInstance("SHA");
@@ -552,7 +663,6 @@ public class Sucker {
             try {
                 c = in.read();
             } catch (IOException e) {
-                // TODO Auto-generated catch block
                 e.printStackTrace();
                 break;
             }
@@ -562,7 +672,6 @@ public class Sucker {
                 break;
             sb.append((char) c);
         }
-        // TODO Auto-generated method stub
         return sb.toString();
     }
 }
diff --git a/apps/syndie/java/src/net/i2p/syndie/Updater.java b/apps/syndie/java/src/net/i2p/syndie/Updater.java
index 6e54dfe221bf71be163cbeb99ccbcad8964de790..5f16f0a80d303ca600cd1e729a2459bd793eaf07 100644
--- a/apps/syndie/java/src/net/i2p/syndie/Updater.java
+++ b/apps/syndie/java/src/net/i2p/syndie/Updater.java
@@ -1,6 +1,8 @@
 package net.i2p.syndie;
 
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
 
 import net.i2p.I2PAppContext;
 import net.i2p.util.Log;
@@ -23,6 +25,12 @@ public class Updater {
         for (int i = 0; i < archives.length; i++) {
             fetchArchive(archives[i]);
         }
+        List rssFeeds = bm.getRssFeeds();
+        Iterator iter = rssFeeds.iterator();
+        while(iter.hasNext()) {
+            Sucker sucker = new Sucker((String[])iter.next());
+            sucker.suck();
+        }
     }
     
     public void fetchArchive(String archive) {
diff --git a/apps/syndie/jsp/_topnav.jsp b/apps/syndie/jsp/_topnav.jsp
index 17fcd77b017ea507f2afb08ad461c6c6176038c2..f1850c3022a7c72496037f488e2fe8ad7e8097b2 100644
--- a/apps/syndie/jsp/_topnav.jsp
+++ b/apps/syndie/jsp/_topnav.jsp
@@ -5,6 +5,7 @@
 <span class="b_topnavHome"><a href="index.jsp" class="b_topnavHome">Home</a></span>
 <a href="admin.jsp" class="b_topnavAdmin">Syndie admin</a>
 <a href="remote.jsp" class="b_topnavRemote">Remote archives</a>
+<a href="rssimport.jsp" class="b_topnavRSSImport">RSS imports</a>
 <a href="import.jsp" class="b_topnavImport">Import</a>
 </td><td nowrap="nowrap" height="10" class="b_topnavUser"><%
 if ("true".equals(request.getParameter("logout"))) {
diff --git a/apps/syndie/jsp/rssimport.jsp b/apps/syndie/jsp/rssimport.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..4ac999fc204e9e0444ae4ca692182b00e14474f4
--- /dev/null
+++ b/apps/syndie/jsp/rssimport.jsp
@@ -0,0 +1,143 @@
+<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="net.i2p.data.Base64, net.i2p.syndie.web.*, net.i2p.syndie.sml.*, net.i2p.syndie.data.*, net.i2p.syndie.*, org.mortbay.servlet.MultiPartRequest, java.util.*, java.io.*" %><%
+request.setCharacterEncoding("UTF-8");  %><jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" 
+/><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 TRANSITIONAL//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
+<html>
+<head>
+<title>SyndieMedia rss import configuration</title>
+<link href="style.jsp" rel="stylesheet" type="text/css" >
+</head>
+<body>
+<table border="1" cellpadding="0" cellspacing="0" width="100%">
+<tr class="b_toplogo"><td colspan="5" valign="top" align="left" class="b_toplogo"><jsp:include page="_toplogo.jsp" /></td></tr>
+<tr><td valign="top" align="left" rowspan="2" class="b_leftnav"><jsp:include page="_leftnav.jsp" /></td>
+    <jsp:include page="_topnav.jsp" />
+    <td valign="top" align="left" rowspan="2" class="b_rightnav"><jsp:include page="_rightnav.jsp" /></td></tr>
+<tr class="b_content"><td valign="top" align="left" colspan="3" class="b_content"><%
+
+BlogManager bm = BlogManager.instance();
+if (!user.getAuthenticated()) {
+    %><span class="b_rssMsgErr">Please log in.</span><% 
+}
+else if(!bm.authorizeRemote(user)) { 
+    %><span class="b_rssMsgErr">You are not authorized for remote access.</span><% 
+} else {
+    String url=request.getParameter("url");
+    if(url!=null) url=url.trim();
+    String blog=request.getParameter("blog");
+    if(blog!=null) blog=blog.trim();
+    String tagPrefix=request.getParameter("tagprefix");
+    if(tagPrefix!=null) tagPrefix=tagPrefix.trim();
+    String action = request.getParameter("action");
+    if ( (action != null) && ("Add".equals(action)) ) {
+        if(url==null || blog==null || tagPrefix==null) {
+            %><span class="b_rssImportMsgErr">Please fill in all fields</span><br /><%
+        } else {
+            boolean ret=bm.addRssFeed(url,blog,tagPrefix);
+	    if(!ret) {
+                %><span class="b_rssImportMsgErr">addRssFeed failure.</span><% 
+            } else {
+                %><span class="b_rssImportMsgOk">RSS feed added.</span><% 
+            }
+        }
+    } else if ( (action != null) && ("Change".equals(action)) ) {
+        String lastUrl=request.getParameter("lasturl");
+        String lastBlog=request.getParameter("lastblog");
+        String lastTagPrefix=request.getParameter("lasttagprefix");
+        if(url==null || blog==null || tagPrefix==null || lastUrl==null || lastBlog==null || lastTagPrefix==null) {
+            %><span class="b_rssImportMsgErr">error, some fields were empty.</span><br /><%
+        } else {
+            boolean ret=bm.deleteRssFeed(lastUrl,lastBlog,lastTagPrefix);
+            if(!ret) {
+                %><span class="b_rssImportMsgErr">Could not delete while attempting to change.</span><% 
+            } else {
+                ret=bm.addRssFeed(url,blog,tagPrefix);
+                if(!ret) {
+                    %><span class="b_rssImportMsgErr">Could not add while attempting to change.</span><% 
+                } else {
+                    %><span class="b_rssImportMsgOk">Ok, changed successfully.</span><% 
+                }
+            }
+        }       
+    } else if ( (action != null) && ("Delete".equals(action)) ) {
+        %><span class="b_rssImportMsgErr">Delete some thing</span><br /><%
+        if(url==null || blog==null || tagPrefix==null) {
+            %><span class="b_rssImportMsgErr">error, some fields were empty.</span><br /><%
+        } else {
+            boolean ret=bm.instance().deleteRssFeed(url,blog,tagPrefix);
+            if(!ret) {
+                %><span class="b_rssImportMsgErr">error, could not delete.</span><% 
+            } else {
+                %><span class="b_rssImportMsgOk">ok, deleted successfully.</span><% 
+            }
+        }       
+    }
+    String blogStr=user.getBlogStr();
+    if(blogStr==null)
+        blogStr="";
+    
+    ///////////////////////////////////////////////
+%>
+<p>Here you can add RSS feeds that will be periodically polled and added to your syndie. </p>
+<form action="rssimport.jsp" method="POST"> 
+RSS URL. (e.g. http://tracker.postman.i2p/rss.php)<br />
+<em><span class="b_rssImportField">url:</span></em> <input class="b_rssImportField" type="text" size="50" name="url" /><br />
+Blog hash to which the RSS entries will get posted, defaults to the one you're logged in to.<br />
+<em><span class="b_rssImportField">blog:</span></em> <input class="b_rssImportField" type="text" <%="value=\""+blogStr+"\""%> size="20" name="blog" /><br />
+This will be prepended to any tags that the RSS feed contains. (e.g. feed.tracker)<br />
+<em><span class="b_rssImportField">tagprefix:</span></em> <input class="b_rssImportField" type="text" value="feed" size="20" name="tagprefix" /><br />
+<input class="b_rssImportSubmit" type="submit" name="action" value="Add" /> <input class="b_rssImportCancel" type="reset" value="Cancel" /></form>
+<%
+///////////////////////////////////////////////
+    List feedList = bm.getRssFeeds();
+    if(feedList.size()>0) {
+%>
+<hr /><h3>Subscriptions:</h3><br />
+<table border="0" width="100%" class="b_rss">
+<tr class="b_rssHeader">
+<td class="b_rssHeader"><em class="b_rssHeader">Url</em></td>
+<td class="b_rssHeader"><em class="b_rssHeader">Blog</em></td>
+<td class="b_rssHeader"><em class="b_rssHeader">TagPrefix</em></td>
+<td class="b_rssHeader">&nbsp;</td></tr>
+<%
+        Iterator iter = feedList.iterator();
+        while(iter.hasNext()) {
+            String fields[]=(String[])iter.next();
+            url=fields[0];
+            blog=fields[1];
+            tagPrefix=fields[2];
+            StringBuffer buf = new StringBuffer(128);
+            
+            buf.append("<tr class=\"b_rssDetail\"><form action=\"rssimport.jsp\" method=\"POST\">");
+            buf.append("<input type=\"hidden\" name=\"lasturl\" value=\"").append(url).append("\" />");
+            buf.append("<input type=\"hidden\" name=\"lastblog\" value=\"").append(blog).append("\" />");
+            buf.append("<input type=\"hidden\" name=\"lasttagprefix\" value=\"").append(tagPrefix).append("\" />");
+            
+            buf.append("<td class=\"b_rssUrl\"><input class=\"b_rssUrl\" type=\"text\" size=\"50\" name=\"url\" value=\"").append(url).append("\" /></td>");
+            buf.append("<td class=\"b_rssBlog\"><input class=\"b_rssBlog\" type=\"text\" size=\"20\" name=\"blog\" value=\"").append(blog).append("\" /></td>");
+            buf.append("<td class=\"b_rssPrefix\"><input class=\"b_rssPrefix\" type=\"text\" size=\"20\" name=\"tagprefix\" value=\"").append(tagPrefix).append("\" /></td>");
+            buf.append("<td class=\"b_rssDetail\" nowrap=\"nowrap\">");
+            
+            buf.append("<input class=\"b_rssChange\" type=\"submit\" name=\"action\" value=\"Change\" />");
+            buf.append("<input class=\"b_rssDelete\" type=\"submit\" name=\"action\" value=\"Delete\" />");
+            
+            buf.append("</td></form></tr>");
+            out.write(buf.toString());
+            buf.setLength(0);
+        }
+    }
+/*
+<p><h3>todo:</h3>
+<p>caching (eepget should do it)
+<p>enclosures support (requires cvs rome)
+<p>syndie.sucker.minHistory/maxHistory used to roll over the history file?
+<p>configurable update period
+*/
+%>
+</table>
+<hr />
+<% 
+} 
+%>
+</td></tr>
+</table>
+</body>
diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java
index a52ae0d87365f577702c234c26ce2c68463328b0..e3dde46b8e7040ea30490c056d10b475568279df 100644
--- a/core/java/src/net/i2p/util/EepGet.java
+++ b/core/java/src/net/i2p/util/EepGet.java
@@ -51,6 +51,7 @@ public class EepGet {
     private String _etag;
     private boolean _encodingChunked;
     private boolean _notModified;
+    private String _contentType;
     
     public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
         this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url);
@@ -209,11 +210,13 @@ public class EepGet {
                         if (timeToSend > 0) {
                             StringBuffer buf = new StringBuffer(50);
                             buf.append(" ");
-                            double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining);
-                            synchronized (_pct) {
-                                buf.append(_pct.format(pct));
+                            if ( bytesRemaining > 0 ) {
+                                double pct = ((double)alreadyTransferred + (double)_written) / ((double)alreadyTransferred + (double)bytesRemaining);
+                                synchronized (_pct) {
+                                    buf.append(_pct.format(pct));
+                                }
+                                buf.append(": ");
                             }
-                            buf.append(": ");
                             buf.append(_written+alreadyTransferred);
                             buf.append(" @ ");
                             double lineKBytes = ((double)_markSize * (double)_lineSize)/1024.0d;
@@ -243,9 +246,15 @@ public class EepGet {
             if (notModified) {
                 System.out.println("== Source not modified since last download");
             } else {
-                System.out.println("== Transfer of " + url + " completed with " + (alreadyTransferred+bytesTransferred)
-                        + " and " + (bytesRemaining - bytesTransferred) + " remaining");
-                System.out.println("== Output saved to " + outputFile);
+                if ( bytesRemaining > 0 ) {
+                    System.out.println("== Transfer of " + url + " completed with " + (alreadyTransferred+bytesTransferred)
+                            + " and " + (bytesRemaining - bytesTransferred) + " remaining");
+                    System.out.println("== Output saved to " + outputFile);
+                } else {
+                    System.out.println("== Transfer of " + url + " completed with " + (alreadyTransferred+bytesTransferred)
+                            + " bytes transferred");
+                    System.out.println("== Output saved to " + outputFile);
+                }
             }
             long timeToSend = _context.clock().now() - _startedOn;
             System.out.println("== Transfer time: " + DataHelper.formatDuration(timeToSend));
@@ -355,9 +364,19 @@ public class EepGet {
             _out.write(buf, 0, read);
             _bytesTransferred += read;
             remaining -= read;
+            if (remaining==0 && _encodingChunked) {
+                if(_proxyIn.read()=='\r' && _proxyIn.read()=='\n') {
+                    remaining = (int) readChunkLength();
+                }
+            }
             if (read > 0) 
                 for (int i = 0; i < _listeners.size(); i++) 
-                    ((StatusListener)_listeners.get(i)).bytesTransferred(_alreadyTransferred, read, _bytesTransferred, _bytesRemaining, _url);
+                    ((StatusListener)_listeners.get(i)).bytesTransferred(
+                            _alreadyTransferred, 
+                            read, 
+                            _bytesTransferred, 
+                            _encodingChunked?-1:_bytesRemaining, 
+                            _url);
         }
 
         if (_out != null)
@@ -369,7 +388,13 @@ public class EepGet {
 
         if ( (_bytesRemaining == -1) || (remaining == 0) ){
             for (int i = 0; i < _listeners.size(); i++) 
-                ((StatusListener)_listeners.get(i)).transferComplete(_alreadyTransferred, _bytesTransferred, _bytesRemaining, _url, _outputFile, _notModified);
+                ((StatusListener)_listeners.get(i)).transferComplete(
+                        _alreadyTransferred, 
+                        _bytesTransferred, 
+                        _encodingChunked?-1:_bytesRemaining, 
+                        _url, 
+                        _outputFile, 
+                        _notModified);
         } else {
             throw new IOException("Disconnection on attempt " + _currentAttempt + " after " + _bytesTransferred);
         }
@@ -387,6 +412,7 @@ public class EepGet {
         switch (responseCode) {
             case 200: // full
                 _out = new FileOutputStream(_outputFile, false);
+                _alreadyTransferred = 0;
                 rcOk = true;
                 break;
             case 206: // partial
@@ -405,7 +431,7 @@ public class EepGet {
             default:
                 rcOk = false;
         }
-
+        buf.setLength(0);
         byte lookahead[] = new byte[3];
         while (true) {
             int cur = _proxyIn.read();
@@ -435,7 +461,7 @@ public class EepGet {
                         if (!rcOk)
                             throw new IOException("Invalid HTTP response code: " + responseCode);
                         if (_encodingChunked) {
-                            readChunkLength();
+                            _bytesRemaining = readChunkLength();
                         }
                         return;
                     }
@@ -450,7 +476,7 @@ public class EepGet {
         }
     }
     
-    private void readChunkLength() throws IOException {
+    private long readChunkLength() throws IOException {
         StringBuffer buf = new StringBuffer(8);
         int nl = 0;
         while (true) {
@@ -472,9 +498,9 @@ public class EepGet {
         String len = buf.toString().trim();
         try {
             long bytes = Long.parseLong(len, 16);
-            _bytesRemaining = bytes;
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("Chunked length: " + bytes);
+            return bytes;
         } catch (NumberFormatException nfe) {
             throw new IOException("Invalid chunk length [" + len + "]");
         }
@@ -529,6 +555,8 @@ public class EepGet {
         } else if (key.equalsIgnoreCase("Transfer-encoding")) {
             if (val.indexOf("chunked") != -1)
                 _encodingChunked = true;
+        } else if (key.equalsIgnoreCase("Content-Type")) {
+            _contentType=val;
         } else {
             // ignore the rest
         }
@@ -630,5 +658,9 @@ public class EepGet {
     public boolean getNotModified() {
         return _notModified;
     }
+    
+    public String getContentType() {
+        return _contentType;
+    }
 
 }
diff --git a/history.txt b/history.txt
index 4b1e7ddcca5637a8b79d9abef2cfdf618153209a..53ef9fd2f3bb4d8adfeb601f25e7c6f5c05aba31 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,10 @@
-$Id: history.txt,v 1.308 2005/10/29 16:35:27 jrandom Exp $
+$Id: history.txt,v 1.309 2005/10/29 18:20:04 jrandom Exp $
+
+2005-10-30  dust
+    * Merge sucker into syndie with a rssimport.jsp page.
+    * Add getContentType() to EepGet.
+    * Make chunked transfer work (better) with EepGet.
+    * Do replaceAll("<","&lt;") for logs.
 
 * 2005-10-29  0.6.1.4 released
 
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 13e50dc206a7d5b1b0df09df6fd6f3c1a742ee3a..6cb5c124b2fd1239d0096e92523d7ea806b03846 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
  *
  */
 public class RouterVersion {
-    public final static String ID = "$Revision: 1.279 $ $Date: 2005/10/29 16:35:24 $";
+    public final static String ID = "$Revision: 1.280 $ $Date: 2005/10/29 18:20:05 $";
     public final static String VERSION = "0.6.1.4";
-    public final static long BUILD = 0;
+    public final static long BUILD = 1;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
         System.out.println("Router ID: " + RouterVersion.ID);