Importing and exporting config. Bugfixes.
diff --git a/quickstart.txt b/quickstart.txt
new file mode 100644
--- /dev/null
+++ b/quickstart.txt
@@ -0,0 +1,41 @@
+//You are seeing this Quick Start because the wiki has no Front_Page. It will disappear as soon as you edit and save the page. //
+----
+
+== Quick Start ==
+
+===Configuration===
+# Click on the "View" link below to view (and edit) the configuration.
+# Set the "FMS ID" to the human readable part of your FMS ID (everything before the '@').
+# Set the FMS Private SSK to your private FMS SSK (see below if you don't know how to find this).
+# Adjust any other values as necessary. If you're running FMS and Fred on the same machine on the default ports this shouldn't be necessary. # Click the "Done" button to save the configuration changes.
+
+=== Finding Other Versions===
+Click the "Discover" link below to search for other versions of the wiki.
+
+=== Submitting ===
+Use the "Submit" link below to submit your changes. It may take a long time for other people to see them.
+
+=== Finding Your Private SSK ===
+# Go to http://127.0.0.1:18080/localidentities.htm in the FMS web interface and click the "Export Identities" button
+to save your FMS indentities to a file.
+
+# In the text editor of your choice, open the file you saved above and look for the Name and PrivateKey values for the identity you want to use.
+
+In the example identity snippet below, the FMS ID value would be:\\
+SomeUser
+
+and the FMS Private Key would be: \\
+SSK@YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,AQECAAE/
+
+----
+{{{
+<Identity>
+ <Name><![CDATA[SomeUser]]></Name>
+ <PublicKey>SSK@XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,AQACAAE/</PublicKey>
+ <PrivateKey>SSK@YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,AQECAAE/</PrivateKey>
+ <SingleUse>false</SingleUse>
+ <PublishTrustList>false</PublishTrustList>
+ <PublishBoardList>false</PublishBoardList>
+ <PublishFreesite>false</PublishFreesite>
+</Identity>
+}}}
diff --git a/readme.txt b/readme.txt
--- a/readme.txt
+++ b/readme.txt
@@ -1,27 +1,19 @@
-------------------------------------------------------------
-This is a my personal work repo. Unless you pull a version
-tagged as a release (none yet), you can assume that code
-may be buggy / broken.
-
--- djk
-
-------------------------------------------------------------
-
-20110130
+20110205
djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
WARNING:
-THIS IS RAW ALPHA CODE.
-
-I'm publishing this so that other developers in the Freenet community
-can audit the source code.
+This is beta code. I've taken reasonable precautions to make sure it is
+safe, including running the Freenet content filter over the final output
+to trap dangerous HTML. However, this is new code that was written fast and
+hasn't been audited by *anyone*.
DON'T USE IT if violation of your anonymity would put you at risk.
ABOUT:
* jfniki is an experimental serverless wiki implementation which runs over Freenet / FMS.
-* It is written in Java and has no external build dependencies.
+* It is written in Java and has no external build dependencies except for freenet.jar.
* jfniki is INCOMPATIBLE with the existing server based python fniki implementation.
+* It can run either as a standalone web app or as a Freenet Plugin.
REQUIREMENTS:
ant
@@ -31,36 +23,32 @@ Access to a running Freenet Node and FMS
BUILD:
ant jar
-RUN:
-Edit the script/jfniki.sh to set PRIVATE_FMS_SSK and FMS_ID correctly and comment out the warning lines.
-
-./script/jfniki.sh
+RUN STAND ALONE:
+./script/jfniki.sh 8083
Look at http://127.0.0.1:8083 with your web browser.
-BUILD FREENET PLUGIN:
-ant jar
-load the jar file from ./build/jar/jfniki.jar
+Click on the "View" Configuration link and set the "FMS Private SSK" and "FMS ID" fields.
+If you want you can use the "Export" button on the Configuration page to export your
+configuration to a file.
-Click on the "View Configuration" link and set the "FMS Private SSK" and "FMS ID fields".
+Once you've done that you can start the script with the saved configuration. i.e.:
+./script/jfniki.sh path_to_your_saved_config
+
+will start the stand alone app on the same port you used before.
+
+RUN AS A FREENET PLUGIN:
+Load the jar file from ./build/jar/jfniki.jar
+
+Click on the "View" Configuration link and set the "FMS Private SSK" and "FMS ID" fields.
+
+OTHER DOC:
+See quickstart.txt in this directory (The default page when an empty wiki is displayed).
KNOWN ISSUES:
o Pages don't auto-refresh. You need to manually reload to see status changes.
- [Freenet ContentFilter is eating meta-refresh???]
+ [Freenet ContentFilter is eating meta-refresh??? Fixed now with WikiContentFilter.EXCEPTIONS hack]
+o "Cancel" sometimes fails. [WORKAROUND: load and unload the plugin / kill restart the stand alone app.]
-------------------------------------------------------------
-Dev notes:
-------------------------------------------------------------
-Stopped in the middle of implementing config ui state
-- figure out how to make private ssk wrap
-- implement update msgs (mMsg)
-- implement import / export
-- test in plugin (probably broken at the moment)
-
-------------------------------------------------------------
-
-
-
-
diff --git a/script/jfniki.sh b/script/jfniki.sh
--- a/script/jfniki.sh
+++ b/script/jfniki.sh
@@ -4,30 +4,8 @@
# You should be able to copy this script anywhere you want. Just make symlinks
# to the jfniki.jar and freenet.jar files in the same directory.
-# TIP:
-# Look in the XML file generate by FMS when you export and identity on
-# the "Local Identities" page to find these values.
+export set JAR_NAME="jfniki.jar"
-# MUST set this to post. i.e. you can run read only without it if you want.
-# The <PrivateKey> value for the FMS identity you want post wiki submissions with.
-export set PRIVATE_FMS_SSK="SSK@XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,AQECAAE/"
-
-# MUST set this to read new version from FMS. i.e. Don't try to run with setting it!
-# The correponding <name> value for that private key.
-export set FMS_ID="YOUR_FMS_HERE"
-
-# FAIL in an obvious way until properly configured.
-echo "MANUAL CONFIGURATION REQUIRED!"
-echo "Edit PRIVATE_FMS_SSK and FMS_ID in this script, then comment out these 3 lines."
-exit -1
-
-export set ENABLE_IMAGES=1
-export set LISTEN_PORT=8083
-
-export set JAR_NAME="jfniki.jar"
-# Look for the jfniki.jar file in the build dir.
-# If you want to move it to somewhere else, modify the line below.
-#export set JAR_PATH="${0%%/*}/../build/jar"
export set SCRIPT_DIR=`dirname $0`
export set JAR_PATH="${SCRIPT_DIR}/../build/jar"
@@ -74,33 +52,6 @@ fi
echo "Using freenet.jar: ${FN_JAR_FILE}"
echo
-
-# FCP configuration
-export set FCP_HOST="127.0.0.1"
-export set FCP_PORT=9481
-
-# FMS configuration
-export set FMS_HOST="127.0.0.1"
-export set FMS_PORT=1119
-
-export set FMS_GROUP="biss.test000"
-export set WIKI_NAME="testwiki"
-
-# fproxy configuration.
-export set FPROXY_PREFIX="http://127.0.0.1:8888/"
-
export set JAVA_CMD="java"
-${JAVA_CMD} -classpath ${JAR_FILE}:${FN_JAR_FILE} fniki.standalone.ServeHttp \
- ${LISTEN_PORT} \
- ${FCP_HOST} \
- ${FCP_PORT} \
- ${FMS_HOST} \
- ${FMS_PORT} \
- ${PRIVATE_FMS_SSK} \
- "${FMS_ID}" \
- ${FMS_GROUP} \
- ${WIKI_NAME} \
- ${FPROXY_PREFIX} \
- ${ENABLE_IMAGES} \
- $1
+${JAVA_CMD} -classpath ${JAR_FILE}:${FN_JAR_FILE} fniki.standalone.ServeHttp $1 $2
diff --git a/src/fniki/freenet/filter/WikiContentFilter.java b/src/fniki/freenet/filter/WikiContentFilter.java
--- a/src/fniki/freenet/filter/WikiContentFilter.java
+++ b/src/fniki/freenet/filter/WikiContentFilter.java
@@ -131,6 +131,34 @@ class WikiContentFilter implements Conte
public String processTag(ParsedTag pt) { return null; }
////////////////////////////////////////////////////////////
+ // One off hacks to allow specific cases mangled by the filter.
+ private final static String EXCEPTIONS[] = new String[] {
+ // Mangled form
+ "<input name=\"import\" type=\"submit\" value=\"Import Configuration\" />\n\n<hr>\n",
+ // Allowed
+ "<input name=\"import\" type=\"submit\" value=\"Import Configuration\"/>\n" +
+ "<input type=\"file\" name=\"upload\" size=\"64\">\n<hr>\n",
+ // Removed meta refresh
+ "html><head>\n\n<title>",
+ // Allowed.
+ "html><head><meta http-equiv=\"refresh\" content=\"15\" /><title>\n"
+ };
+
+ // Allow a few safe, specific exceptions through the content filter.
+ public String postProcess(String filtered, String unfiltered) {
+ // System.err.println("--- unfiltered ---");
+ // System.err.println(unfiltered);
+ // System.err.println("--- filtered ---");
+ // System.err.println(filtered);
+ // System.err.println("---");
+ int index = 0;
+ while (index < EXCEPTIONS.length) {
+ filtered = filtered.replace(EXCEPTIONS[index], EXCEPTIONS[index + 1]);
+ index += 2;
+ }
+ return filtered;
+ }
+
public String filter(String html) throws ServerErrorException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -140,7 +168,7 @@ class WikiContentFilter implements Conte
baos, "text/html",
UTF8,
this);
- return new String(baos.toByteArray(), UTF8);
+ return postProcess(new String(baos.toByteArray(), UTF8), html);
} catch (UnsafeContentTypeException ucte) {
ucte.printStackTrace();
throw new ServerErrorException("BUG: Generated dangerous html(0)??? But we caught it :-)");
diff --git a/src/fniki/freenet/plugin/Fniki.java b/src/fniki/freenet/plugin/Fniki.java
--- a/src/fniki/freenet/plugin/Fniki.java
+++ b/src/fniki/freenet/plugin/Fniki.java
@@ -29,6 +29,7 @@ import java.io.UnsupportedEncodingExcept
import java.util.Set;
import freenet.pluginmanager.AccessDeniedPluginHTTPException;
+import freenet.pluginmanager.DownloadPluginHTTPException;
import freenet.pluginmanager.FredPlugin;
import freenet.pluginmanager.FredPluginHTTP;
import freenet.pluginmanager.FredPluginThreadless;
@@ -45,13 +46,14 @@ import fniki.wiki.Request;
import fniki.wiki.WikiApp;
import fniki.wiki.AccessDeniedException;
+import fniki.wiki.DownloadException;
import fniki.wiki.NotFoundException;
import fniki.wiki.RedirectException;
import fniki.wiki.ChildContainerException;
public class Fniki implements FredPlugin, FredPluginHTTP, FredPluginThreadless {
private WikiApp mWikiApp;
-
+ private String mContainerPrefix;
public void terminate() {
System.err.println("terminating...");
}
@@ -59,18 +61,13 @@ public class Fniki implements FredPlugin
public void runPlugin(PluginRespirator pr) {
try {
ArchiveManager archiveManager = new ArchiveManager();
-
- // YOU MUST SET THESE OR THE PLUGIN WON'T LOAD.
- // archiveManager.setPrivateSSK("FMS_PRIVATE_SSK");
- archiveManager.setFmsId("SET_THE_FMS_ID");
archiveManager.createEmptyArchive();
WikiApp wikiApp = new WikiApp(archiveManager);
- final String containerPrefix = wikiApp.getString("container_prefix", null);
- if (containerPrefix == null) {
+ if (wikiApp.getString("container_prefix", null) == null) {
throw new RuntimeException("Assertion Failure: container_prefix not set!");
}
- wikiApp.setAllowImages(false); // User can enable
+ mContainerPrefix = wikiApp.getString("container_prefix", null);
// IMPORTANT:
// HTTP POSTS will be rejected without any useful error message if your form
@@ -172,28 +169,27 @@ public class Fniki implements FredPlugin
}
public String handle(HTTPRequest request) throws PluginHTTPException {
- // DCI: cleanup container_prefix usage
+ try {
+ mWikiApp.setRequest(new PluginRequest(request, mContainerPrefix));
+ return mWikiApp.handle(mWikiApp);
- try {
- mWikiApp.setRequest(new PluginRequest(request, mWikiApp.getString("container_prefix", null)));
- return mWikiApp.handle(mWikiApp);
+ // IMPORTANT: Look at these catch blocks carefully. They bypass the freenet ContentFilter.
} catch(AccessDeniedException accessDenied) {
- throw new AccessDeniedPluginHTTPException(accessDenied.getMessage(),
- mWikiApp.getString("container_prefix", null));
+ throw new AccessDeniedPluginHTTPException(accessDenied.getMessage(), mContainerPrefix);
} catch(NotFoundException notFound) {
- throw new NotFoundPluginHTTPException(notFound.getMessage(),
- mWikiApp.getString("container_prefix", null));
+ throw new NotFoundPluginHTTPException(notFound.getMessage(), mContainerPrefix);
} catch(RedirectException redirected) {
- throw new RedirectPluginHTTPException(redirected.getMessage(),
- redirected.getLocation());
+ throw new RedirectPluginHTTPException(redirected.getMessage(), redirected.getLocation());
+ } catch(DownloadException forceDownload) {
+ // This is to allow exporting the configuration.
+ throw new DownloadPluginHTTPException(forceDownload.mData,
+ forceDownload.mFilename,
+ forceDownload.mMimeType);
} catch(ChildContainerException serverError) {
- throw new ServerPluginHTTPException(serverError.getMessage(),
- mWikiApp.getString("container_prefix", null));
+ throw new ServerPluginHTTPException(serverError.getMessage(), mContainerPrefix);
} catch(IOException ioError) {
- throw new ServerPluginHTTPException(ioError.getMessage(),
- mWikiApp.getString("container_prefix", null));
+ throw new ServerPluginHTTPException(ioError.getMessage(), mContainerPrefix);
}
-
}
public String handleHTTPGet(HTTPRequest request) throws PluginHTTPException {
diff --git a/src/fniki/standalone/FnikiContextHandler.java b/src/fniki/standalone/FnikiContextHandler.java
--- a/src/fniki/standalone/FnikiContextHandler.java
+++ b/src/fniki/standalone/FnikiContextHandler.java
@@ -26,6 +26,8 @@ package fniki.standalone;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
import java.util.Map;
import java.util.Set;
import net.freeutils.httpserver.HTTPServer;
@@ -36,6 +38,7 @@ import fniki.wiki.Request;
import fniki.wiki.WikiApp;
import fniki.wiki.AccessDeniedException;
+import fniki.wiki.DownloadException;
import fniki.wiki.NotFoundException;
import fniki.wiki.RedirectException;
import fniki.wiki.ChildContainerException;
@@ -174,6 +177,27 @@ public class FnikiContextHandler impleme
} catch(RedirectException redirected) {
resp.redirect(redirected.getLocation(), false);
return 0;
+ } catch(DownloadException forceDownload) {
+ try {
+ resp.getHeaders().add("Content-disposition",
+ String.format("attachment; filename=%s", forceDownload.mFilename));
+ resp.sendHeaders(200, forceDownload.mData.length, -1,
+ null, forceDownload.mMimeType, null);
+ OutputStream body = resp.getBody();
+ if (body == null) {
+ return 0; // hmmm... getBody() can return null.
+ }
+ try {
+ body.write(forceDownload.mData);
+ } finally {
+ body.close();
+ }
+ return 0;
+ } catch (IOException ioe) {
+ // Totally hosed. We already sent the headers so we can't send a response.
+ ioe.printStackTrace();
+ return 0;
+ }
} catch(ChildContainerException serverError) {
// This also handles ServerErrorException.
resp.sendError(500, serverError.getMessage());
diff --git a/src/fniki/standalone/ServeHttp.java b/src/fniki/standalone/ServeHttp.java
--- a/src/fniki/standalone/ServeHttp.java
+++ b/src/fniki/standalone/ServeHttp.java
@@ -24,9 +24,12 @@
package fniki.standalone;
import java.io.IOException;
+import java.io.FileInputStream;
import net.freeutils.httpserver.HTTPServer;
+import wormarc.IOUtil;
+import fniki.wiki.Configuration;
import fniki.wiki.WikiApp;
import fniki.wiki.ArchiveManager;
@@ -41,64 +44,43 @@ public class ServeHttp {
"Launch a wiki viewer / editor on localhost.\n" +
"This is experimental code. Use it at your own peril.\n\n" +
"USAGE:\n" +
- "java -jar jfniki.jar <listen_port> <fcp_host> <fcp_port> <fms_host> <fms_port> " +
- "<private_fms_ssk> <fms_id> <fms_group> <wiki_name> <fproxy_prefix> <enable_images> [uri]\n\n";
+ "java -jar jfniki.jar <listen_port>\n" +
+ "or\n" +
+ "java -jar jfniki.jar <config_file>\n\n" +
+ "NOTE:\nfreenet.jar MUST be in your classpath.\n\n" +
+ "EXAMPLES:\n" +
+ "java -jar jfniki.jar ~/saved_jfniki.cfg\n\n" +
+ "java -jar jfniki.jar 8099\n\n";
- private final static String ARG_NAMES[] = new String[] {
- "<listen_port>", "<fcp_host>", "<fcp_port>", "<fms_host>","<fms_port>",
- "<private_fms_ssk>", "<fms_id>", "<fms_group>", "<wiki_name>",
- "<fproxy_prefix>", "<enable_images>", "[uri]", };
-
- public static void debugDumpArgs(String[] args) {
- for (int index = 0; index < args.length; index++) {
- String name = "???";
- if (index < ARG_NAMES.length) {
- name = ARG_NAMES[index];
- }
- System.out.println(String.format("[%d]:{%s}[%s]", index, name, args[index]));
- }
- }
- public static int asInt(String value) { return Integer.parseInt(value); }
public static void main(String[] args) throws Exception {
- if (args.length < 11) {
+ if (args.length != 1) {
System.err.println(HELP_TEXT);
System.exit(-1);
}
- debugDumpArgs(args);
- int listenPort = Integer.parseInt(args[0]);
ArchiveManager archiveManager = new ArchiveManager();
+ archiveManager.createEmptyArchive();
- archiveManager.setFcpHost(args[1]);
- archiveManager.setFcpPort(asInt(args[2]));
+ WikiApp wikiApp = new WikiApp(archiveManager);
- archiveManager.setFmsHost(args[3]);
- archiveManager.setFmsPort(asInt(args[4]));
-
- archiveManager.setPrivateSSK(args[5]);
- archiveManager.setFmsId(args[6]);
-
- archiveManager.setFmsGroup(args[7]);
- archiveManager.setBissName(args[8]);
-
- String fproxyPrefix = args[9];
- boolean enableImages = (args[10].equals("1") || args[10].toLowerCase().equals("true")) ? true : false;
-
- if (args.length > 11) {
- archiveManager.load(args[11]);
- } else {
- archiveManager.createEmptyArchive();
+ if(wikiApp.getString("container_prefix", null) == null) {
+ throw new RuntimeException("Assertion Failure: container_prefix not set!");
}
- WikiApp wikiApp = new WikiApp(archiveManager);
+ try {
+ // Try to parse the argument as an integer listen port.
+ wikiApp.setListenPort(Integer.parseInt(args[0]));
+
+ } catch (NumberFormatException nfe) {
+ System.out.println("Reading configuration from: " + args[0]);
+ Configuration config =
+ Configuration.fromStringRep(IOUtil.readUtf8StringAndClose(new FileInputStream(args[0])));
+ wikiApp.setConfiguration(config);
+ }
+
+ int listenPort = wikiApp.getInt("listen_port", WikiApp.LISTEN_PORT);
final String containerPrefix = wikiApp.getString("container_prefix", null);
- if (containerPrefix == null) {
- throw new RuntimeException("Assertion Failure: container_prefix not set!");
- }
- wikiApp.setFproxyPrefix(fproxyPrefix);
- wikiApp.setAllowImages(enableImages);
-
// Redirect non-routed requests to the wiki app.
HTTPServer.ContextHandler defaultRedirect = new HTTPServer.ContextHandler() {
public int serve(HTTPServer.Request req, HTTPServer.Response resp) throws IOException {
diff --git a/src/fniki/wiki/ArchiveManager.java b/src/fniki/wiki/ArchiveManager.java
--- a/src/fniki/wiki/ArchiveManager.java
+++ b/src/fniki/wiki/ArchiveManager.java
@@ -43,13 +43,13 @@ import wormarc.RootObjectKind;
import wormarc.io.FreenetIO;
public class ArchiveManager {
- private final static String FCP_HOST = "127.0.0.1";
- private final static int FCP_PORT = 9481;
+ public final static String FCP_HOST = "127.0.0.1";
+ public final static int FCP_PORT = 9481;
- private final static String FMS_HOST = "127.0.0.1";
- private final static int FMS_PORT = 1119;
- private final static String FMS_GROUP = "biss.test000";
- private final static String BISS_NAME = "testwiki";
+ public final static String FMS_HOST = "127.0.0.1";
+ public final static int FMS_PORT = 1119;
+ public final static String FMS_GROUP = "biss.test000";
+ public final static String BISS_NAME = "testwiki";
// Maximum number of versions to read from FMS.
private final static int MAX_VERSIONS = 50;
diff --git a/src/fniki/wiki/Configuration.java b/src/fniki/wiki/Configuration.java
new file mode 100644
--- /dev/null
+++ b/src/fniki/wiki/Configuration.java
@@ -0,0 +1,170 @@
+/* Configuration settings for the WikiApp
+ *
+ * Copyright (C) 2010, 2011 Darrell Karbott
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
+ *
+ * This file was developed as component of
+ * "fniki" (a wiki implementation running over Freenet).
+ */
+
+package fniki.wiki;
+
+public final class Configuration {
+ public final static class ConfigurationException extends IllegalArgumentException {
+ protected ConfigurationException(String msg) {
+ super(msg);
+ }
+ }
+
+ public final int mListenPort;
+ public final String mFcpHost;
+ public final int mFcpPort;
+ public final String mFproxyPrefix;
+ public final boolean mAllowImages;
+
+ public final String mFmsHost;
+ public final int mFmsPort;
+ public final String mFmsId;
+ public final String mFmsSsk;
+ public final String mFmsGroup;
+
+ public final String mWikiName;
+
+ public Configuration(int listenPort,
+ String fcpHost,
+ int fcpPort,
+ String fproxyPrefix,
+ boolean allowImages,
+ String fmsHost,
+ int fmsPort,
+ String fmsId,
+ String fmsSsk,
+ String fmsGroup,
+ String wikiName) {
+ mListenPort = listenPort;
+ mFcpHost = noNulls(fcpHost);
+ mFcpPort = fcpPort;
+ mFproxyPrefix = noNulls(fproxyPrefix);
+ mAllowImages = allowImages;
+ mFmsHost = noNulls(fmsHost);
+ mFmsPort = fmsPort;
+ mFmsId = noNulls(fmsId);
+ mFmsSsk = noNulls(fmsSsk);
+ mFmsGroup = noNulls(fmsGroup);
+ mWikiName = noNulls(wikiName);
+ }
+
+ private static String noNulls(String text) {
+ if (text == null) {
+ return "";
+ }
+ return text;
+ }
+
+ private static void checkSet(String value, String field) throws ConfigurationException {
+ if (value == null || value.trim().length() == 0) {
+ throw new ConfigurationException(String.format("%s is not set.", field));
+ }
+ }
+
+ private static void checkSet(int value, String field) throws ConfigurationException {
+ if (value <= 0) {
+ throw new ConfigurationException(String.format("%s is not set.", field));
+ }
+ }
+
+ public void validate() throws ConfigurationException {
+ // LATER: Do better. Unlocalized strings which show up in the UI.
+ checkSet(mListenPort, "Listen Port");
+ checkSet(mFcpHost, "FCP Host");
+ checkSet(mFcpPort, "FCP Port");
+ checkSet(mFproxyPrefix, "Fproxy Prefix");
+ //mAllowImages,
+ checkSet(mFmsHost, "FMS Host");
+ checkSet(mFmsPort, "FMS Port");
+ checkSet(mFmsId, "FMS Id");
+ checkSet(mFmsSsk, "FMS Private SSK");
+ checkSet(mFmsGroup, "FMS Group");
+ checkSet(mWikiName, "Wiki Name");
+
+ if (!mFmsSsk.startsWith("SSK@") || !mFmsSsk.endsWith(",AQECAAE/")) {
+ throw new ConfigurationException("The private SSK value must start with 'SSK@' " +
+ "and end with ',AQECAAE/'.");
+ }
+ if (mFmsId.indexOf("@") != -1) {
+ throw new ConfigurationException("FMS Id Should only include the part before the '@'.");
+ }
+ if (!mFproxyPrefix.startsWith("http") || !mFproxyPrefix.endsWith("/")) {
+ throw new ConfigurationException("The fproxy prefix must start with 'http' and end with '/'.");
+ }
+
+ fromStringRep(toStringRep()); // traps '\n's in values.
+ }
+
+ public String toStringRep() throws ConfigurationException {
+ return String.format("%d\n%s\n%d\n%s\n%d\n%s\n%d\n%s\n%s\n%s\n%s\n",
+ mListenPort,
+ mFcpHost,
+ mFcpPort,
+ mFproxyPrefix,
+ mAllowImages ? 1 : 0,
+ mFmsHost,
+ mFmsPort,
+ mFmsId,
+ mFmsSsk,
+ mFmsGroup,
+ mWikiName);
+ }
+
+ // Doesn't validate.
+ public static Configuration fromStringRep(String text) {
+ String[] fields = text.split("\n");
+ if (fields.length != 11) {
+ throw new ConfigurationException("Couldn't parse configuration.");
+ }
+ int listenPort = -1;
+ try { listenPort = Integer.parseInt(fields[0]); } catch (NumberFormatException nfe) { /*NOP*/ }
+ String fcpHost = fields[1];
+ int fcpPort = -1;
+ try { fcpPort = Integer.parseInt(fields[2]); } catch (NumberFormatException nfe) { /*NOP*/ }
+ String fproxyPrefix = fields[3];
+ boolean allowImages = fields[4].equals("1");
+ String fmsHost = fields[5];
+ int fmsPort = -1;
+ try { fmsPort = Integer.parseInt(fields[6]); } catch (NumberFormatException nfe) { /*NOP*/ }
+ String fmsId = fields[7];
+ String fmsSsk = fields[8];
+ String fmsGroup = fields[9];
+ String wikiName = fields[10];
+
+ Configuration config = new Configuration(listenPort,
+ fcpHost,
+ fcpPort,
+ fproxyPrefix,
+ allowImages,
+ fmsHost,
+ fmsPort,
+ fmsId,
+ fmsSsk,
+ fmsGroup,
+ wikiName);
+ return config;
+ }
+
+ public String toString() { return toStringRep().replace("\n", "|"); }
+}
diff --git a/src/fniki/wiki/DownloadException.java b/src/fniki/wiki/DownloadException.java
new file mode 100644
--- /dev/null
+++ b/src/fniki/wiki/DownloadException.java
@@ -0,0 +1,40 @@
+/* Exception raised for HTTP 302 redirects.
+ *
+ * Copyright (C) 2010, 2011 Darrell Karbott
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
+ *
+ * This file was developed as component of
+ * "fniki" (a wiki implementation running over Freenet).
+ */
+
+
+package fniki.wiki;
+
+public class DownloadException extends ChildContainerException {
+ public final String mFilename;
+ public final String mMimeType;
+ public final byte[] mData;
+
+ public DownloadException(byte[] data, String filename, String mimeType) {
+ super("Download of: " + filename);
+ mData = data;
+ mFilename = filename;
+ mMimeType = mimeType;
+ }
+}
+
diff --git a/src/fniki/wiki/QueryBase.java b/src/fniki/wiki/QueryBase.java
--- a/src/fniki/wiki/QueryBase.java
+++ b/src/fniki/wiki/QueryBase.java
@@ -32,8 +32,6 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-
-
public abstract class QueryBase implements Query {
protected Map<String, String> mParamTable = new HashMap<String, String>();
@@ -43,10 +41,10 @@ public abstract class QueryBase implemen
"uri", "goto",
"savepage", "savetext",
"formPassword",
- "saved", "discarded", "done",
- // C&P from SettingConfig.java
+ // Configuration stuff.
+ "defaults", "done", "import", "export", "upload",
"fcphost", "fcpport", "fpprefix", "fmshost", "fmsport",
- "fmsssk", "fmsid", "wikiname", "images",
+ "fmsssk", "fmsid", "fmsgroup", "wikiname", "images",
};
protected static Set<String> paramsSet() {
diff --git a/src/fniki/wiki/WikiApp.java b/src/fniki/wiki/WikiApp.java
--- a/src/fniki/wiki/WikiApp.java
+++ b/src/fniki/wiki/WikiApp.java
@@ -52,6 +52,22 @@ import fniki.freenet.filter.ContentFilte
// Aggregates a bunch of other ChildContainers and runs UI state machine.
public class WikiApp implements ChildContainer, WikiContext {
+ public final static int LISTEN_PORT = 8083;
+ private final static String FPROXY_PREFIX = "http://127.0.0.1:8888/";
+ private final static boolean ALLOW_IMAGES = false;
+ private final static Configuration DEFAULT_CONFIG =
+ new Configuration(LISTEN_PORT,
+ ArchiveManager.FCP_HOST,
+ ArchiveManager.FCP_PORT,
+ FPROXY_PREFIX,
+ ALLOW_IMAGES,
+ ArchiveManager.FMS_HOST,
+ ArchiveManager.FMS_PORT,
+ "",
+ "",
+ ArchiveManager.FMS_GROUP,
+ ArchiveManager.BISS_NAME);
+
// Delegate to implement link, image and macro handling in wikitext.
private final FreenetWikiTextParser.ParserDelegate mParserDelegate;
@@ -79,9 +95,11 @@ public class WikiApp implements ChildCon
// over all output before serving it.
private ContentFilter mFilter;
- private String mFproxyPrefix = "http://127.0.0.1:8888/";
- private boolean mAllowImages = true;
+ private String mFproxyPrefix = FPROXY_PREFIX;
+ private boolean mAllowImages = ALLOW_IMAGES;
private String mFormPassword;
+ private int mListenPort = LISTEN_PORT;
+
// final because it is called from the ctor.
private final void resetContentFilter() {
@@ -96,7 +114,7 @@ public class WikiApp implements ChildCon
mQueryError = new QueryError();
mWikiContainer = new WikiContainer();
- mSettingConfig = new SettingConfig(this, archiveManager);
+ mSettingConfig = new SettingConfig();
mLoadingVersionList = new LoadingVersionList(archiveManager);
mLoadingArchive = new LoadingArchive(archiveManager);
mSubmitting = new Submitting(archiveManager);
@@ -124,6 +142,11 @@ public class WikiApp implements ChildCon
mFormPassword = value;
}
+ // Doesn't change port, just sets value returned by getInt("listen_port", -1)
+ public void setListenPort(int value) {
+ mListenPort = value;
+ }
+
private ChildContainer setState(WikiContext context, ChildContainer container) {
if (mState == container) {
return mState;
@@ -197,6 +220,7 @@ public class WikiApp implements ChildCon
}
}
+ // DCI: Fix. Use a hashmap of paths -> instances for static paths
System.err.println("WikiApp.routeRequest: " + path);
if (path.equals("fniki/config")) {
return setState(request, mSettingConfig);
@@ -357,6 +381,8 @@ public class WikiApp implements ChildCon
return containerPrefix();
} else if (keyName.equals("form_password") && mFormPassword != null) {
return mFormPassword;
+ } else if (keyName.equals("default_wikitext")) {
+ return getDefaultWikiText();
}
return defaultValue;
@@ -366,9 +392,48 @@ public class WikiApp implements ChildCon
if (keyName.equals("allow_images")) {
return mAllowImages ? 1 : 0;
}
+ if (keyName.equals("listen_port")) {
+ return mListenPort;
+ }
+
return defaultValue;
}
+ // Can return an invalid configuration. e.g. if fms id and private ssk are not set.
+ public Configuration getConfiguration() {
+ // Converts null values to ""
+ return new Configuration(getInt("listen_port", LISTEN_PORT), //DCI: clean up magic numbers
+ mArchiveManager.getFcpHost(),
+ mArchiveManager.getFcpPort(),
+ getString("fproxy_prefix", FPROXY_PREFIX),
+ mAllowImages,
+ mArchiveManager.getFmsHost(),
+ mArchiveManager.getFmsPort(),
+ mArchiveManager.getFmsId(),
+ mArchiveManager.getPrivateSSK(),
+ mArchiveManager.getFmsGroup(),
+ mArchiveManager.getBissName());
+ }
+
+ public Configuration getDefaultConfiguration() { return DEFAULT_CONFIG; }
+
+ // For setting data from forms and restoring saved settings.
+ // throws unchecked Configuration.ConfigurationException
+ public void setConfiguration(Configuration config) {
+ config.validate();
+ setListenPort(config.mListenPort);
+ mArchiveManager.setFcpHost(config.mFcpHost);
+ mArchiveManager.setFcpPort(config.mFcpPort);
+ setFproxyPrefix(config.mFproxyPrefix);
+ setAllowImages(config.mAllowImages);
+ mArchiveManager.setFmsHost(config.mFmsHost);
+ mArchiveManager.setFmsPort(config.mFmsPort);
+ mArchiveManager.setFmsId(config.mFmsId);
+ mArchiveManager.setPrivateSSK(config.mFmsSsk);
+ mArchiveManager.setFmsGroup(config.mFmsGroup);
+ mArchiveManager.setBissName(config.mWikiName);
+ }
+
// DCI: Think this through.
public String makeLink(String containerRelativePath) {
// Hacks to find bugs
@@ -402,6 +467,10 @@ public class WikiApp implements ChildCon
throw new ServerErrorException(msg);
}
+ public void raiseDownload(byte[] data, String filename, String mimeType) throws DownloadException {
+ throw new DownloadException(data, filename, mimeType);
+ }
+
public void logError(String msg, Throwable t) {
if (msg == null) {
msg = "null";
@@ -427,4 +496,52 @@ public class WikiApp implements ChildCon
}
mRequest = request;
}
+
+ private static String getDefaultWikiText() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("//You are seeing this Quick Start because the wiki has no Front_Page. It will disappear as soon as you edit and save the page. //\n");
+ sb.append("----\n");
+ sb.append("\n");
+ sb.append("== Quick Start ==\n");
+ sb.append("\n");
+ sb.append("===Configuration===\n");
+ sb.append("# Click on the \"View\" link below to view (and edit) the configuration.\n");
+ sb.append("# Set the \"FMS ID\" to the human readable part of your FMS ID (everything before the '@').\n");
+ sb.append("# Set the FMS Private SSK to your private FMS SSK (see below if you don't know how to find this).\n");
+ sb.append("# Adjust any other values as necessary. If you're running FMS and Fred on the same machine on the default ports this shouldn't be necessary.\n");
+ sb.append("# Click the \"Done\" button to save the configuration changes.\n");
+ sb.append("\n");
+ sb.append("=== Finding Other Versions===\n");
+ sb.append("Click the \"Discover\" link below to search for other versions of the wiki.\n");
+ sb.append("\n");
+ sb.append("=== Submitting ===\n");
+ sb.append("Use the \"Submit\" link below to submit your changes. It may take a long time for other people to see them.\n");
+ sb.append("\n");
+ sb.append("=== Finding Your Private SSK ===\n");
+ sb.append("# Go to http://127.0.0.1:18080/localidentities.htm in the FMS web interface and click the \"Export Identities\" button\n");
+ sb.append("to save your FMS indentities to a file.\n");
+ sb.append("\n");
+ sb.append("# In the text editor of your choice, open the file you saved above and look for the Name and PrivateKey values for the identity you want to use.\n");
+ sb.append("\n");
+ sb.append("In the example identity snippet below, the FMS ID value would be:\\\\ \n");
+ sb.append("SomeUser\n");
+ sb.append("\n");
+ sb.append("and the FMS Private Key would be: \\\\ \n");
+ sb.append("SSK@YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,AQECAAE/ \n");
+ sb.append("\n");
+ sb.append("----\n");
+ sb.append("{{{\n");
+ sb.append("<Identity>\n");
+ sb.append(" <Name><![CDATA[SomeUser]]></Name>\n");
+ sb.append(" <PublicKey>SSK@XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,AQACAAE/</PublicKey>\n");
+ sb.append(" <PrivateKey>SSK@YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY,AQECAAE/</PrivateKey>\n");
+ sb.append(" <SingleUse>false</SingleUse>\n");
+ sb.append(" <PublishTrustList>false</PublishTrustList>\n");
+ sb.append(" <PublishBoardList>false</PublishBoardList>\n");
+ sb.append(" <PublishFreesite>false</PublishFreesite>\n");
+ sb.append("</Identity>\n");
+ sb.append("}}}\n");
+ sb.append("\n");
+ return sb.toString();
+ }
}
diff --git a/src/fniki/wiki/WikiContext.java b/src/fniki/wiki/WikiContext.java
--- a/src/fniki/wiki/WikiContext.java
+++ b/src/fniki/wiki/WikiContext.java
@@ -41,6 +41,12 @@ public interface WikiContext extends Req
String getString(String keyName, String defaultValue);
int getInt(String keyName, int defaultValue);
+ Configuration getConfiguration();
+ Configuration getDefaultConfiguration();
+
+ // throws unchecked Configuration.ConfigurationException
+ void setConfiguration(Configuration config);
+
// throwable can be null
void logError(String msg, Throwable throwable);
@@ -48,4 +54,8 @@ public interface WikiContext extends Req
void raiseNotFound(String msg) throws NotFoundException; // 404
void raiseAccessDenied(String msg) throws AccessDeniedException; // 403
void raiseServerError(String msg) throws ServerErrorException; // 500
+
+ // This is so we can play nice with the Freenet plugin API.
+ // Force a download to disk of data.
+ void raiseDownload(byte[] data, String filename, String mimeType) throws DownloadException;
}
\ No newline at end of file
diff --git a/src/fniki/wiki/child/AsyncTaskContainer.java b/src/fniki/wiki/child/AsyncTaskContainer.java
--- a/src/fniki/wiki/child/AsyncTaskContainer.java
+++ b/src/fniki/wiki/child/AsyncTaskContainer.java
@@ -55,12 +55,13 @@ public abstract class AsyncTaskContainer
protected String mExitPage = "/";
+ // IMPORTANT: See hacks in WikiContentFilter.EXCEPTIONS if this stops working.
// 15 second refresh if the task isn't finished.
protected String metaRefresh() {
if (isFinished()) {
return "";
}
- return "<meta http-equiv=\"refresh\" content=\"15\" />";
+ return "\n<meta http-equiv=\"refresh\" content=\"15\" />\n";
}
// DCI: make these return a string? To get rid of no return value warnings
diff --git a/src/fniki/wiki/child/SettingConfig.java b/src/fniki/wiki/child/SettingConfig.java
--- a/src/fniki/wiki/child/SettingConfig.java
+++ b/src/fniki/wiki/child/SettingConfig.java
@@ -24,30 +24,129 @@
package fniki.wiki.child;
+import java.io.UnsupportedEncodingException;
+import static ys.wikiparser.Utils.*;
+
import fniki.wiki.ArchiveManager;
import fniki.wiki.ChildContainerException;
+import fniki.wiki.Configuration;
import static fniki.wiki.HtmlUtils.*;
import fniki.wiki.ModalContainer;
import fniki.wiki.Query;
-import fniki.wiki.WikiApp;
import fniki.wiki.WikiContext;
public class SettingConfig implements ModalContainer {
- private final WikiApp mWikiApp;
- private final ArchiveManager mArchiveManager;
+ private final static String UTF8 = "UTF-8"; // DCI: import?
+
+ // Used by export.
+ private final static String CONFIG_NAME = "jfniki.cfg";
+ private final static String CONFIG_TYPE = "application/octet-stream";
private boolean mFinished = false;
+ private Configuration mConfig;
private String mMsg = "";
- public SettingConfig(WikiApp wikiApp, ArchiveManager archiveManager) {
- mWikiApp = wikiApp;
- mArchiveManager = archiveManager;
+ // Doesn't validate.
+ private static Configuration parseConfigFromPost(Query query, int listenPort) {
+ boolean allowImages = false;
+ if (query.containsKey("images")) {
+ allowImages = true;
+ }
+ int fcpPort = -1;
+ int fmsPort = -1;
+ try {
+ fcpPort = Integer.parseInt(query.get("fcpport"));
+ } catch (NumberFormatException nfe) {
+ // NOP
+ }
+
+ try {
+ fmsPort = Integer.parseInt(query.get("fmsport"));
+ } catch (NumberFormatException nfe) {
+ // NOP
+ }
+
+ Configuration config = new Configuration(listenPort,
+ query.get("fcphost"),
+ fcpPort,
+ query.get("fpprefix"),
+ allowImages,
+ query.get("fmshost"),
+ fmsPort,
+ query.get("fmsid"),
+ query.get("fmsssk"),
+ query.get("fmsgroup"),
+ query.get("wikiname"));
+ return config;
}
private void handlePost(WikiContext context) throws ChildContainerException {
Query query = context.getQuery();
- if (query.containsKey("discarded")) {
- mMsg = "Discarded changes.";
+
+ if (query.containsKey("export")) {
+ // Pop a save-as dialog for configuration.
+ try {
+ // Save any changes the user made.
+ mConfig = parseConfigFromPost(query, context.getInt("listen_port", 8083));
+ mConfig.validate();
+
+ // Force browser to save the config file to disk.
+ context.raiseDownload(mConfig.toStringRep().getBytes(UTF8),
+ CONFIG_NAME, CONFIG_TYPE);
+ } catch (Configuration.ConfigurationException cfe) {
+ mMsg = "Refused to export: " + cfe.getMessage();
+ return;
+ } catch (UnsupportedEncodingException uee) {
+ mMsg = "Export failed: " + uee.getMessage();
+ return;
+ }
+ }
+
+ if (query.containsKey("import")) {
+ // Read imported config.
+ if (!query.containsKey("upload")) {
+ mMsg = "Set the file you want to import from!";
+ return;
+ }
+
+ try {
+ Configuration config = Configuration.fromStringRep(query.get("upload"));
+ config.validate();
+ mConfig = config;
+ mMsg = "Imported configuration!";
+ return;
+ } catch (Configuration.ConfigurationException cfe) {
+ mMsg = "Refused to import: " + cfe.getMessage();
+ return;
+ }
+ }
+
+ if (query.containsKey("defaults")) {
+ mConfig = context.getDefaultConfiguration();
+ try {
+ mConfig.validate();
+ mMsg = "Reset to default values."; // Won't see this because fms config not set.
+ } catch (Configuration.ConfigurationException cfe) {
+ // Handle invalid parameters;
+ mMsg = cfe.getMessage();
+ return;
+ }
+ return;
+ }
+
+ if (!query.containsKey("done")) {
+ return;
+ }
+
+ mConfig = parseConfigFromPost(query, context.getInt("listen_port", 8083));
+
+ try {
+ mConfig.validate();
+ context.setConfiguration(mConfig);
+ mMsg = "Saved configuration changes.";
+ } catch (Configuration.ConfigurationException cfe) {
+ // Handle invalid parameters;
+ mMsg = cfe.getMessage();
return;
}
@@ -58,148 +157,125 @@ public class SettingConfig implements Mo
"finished", null, null, null);
context.raiseRedirect(redirectHref, "Redirecting...");
}
-
- if (!query.containsKey("saved")) {
- return; // Not sure how this would happen
- }
-
- for (String key : FORM_FIELDS) {
- if (!query.containsKey(key)) {
- continue;
- }
- if (key.equals("fcphost")) { mArchiveManager.setFcpHost(query.get(key)); }
- if (key.equals("fcpport")) { mArchiveManager.setFcpPort(Integer.parseInt(query.get(key))); }
-
- if (key.equals("fpprefix")) { mWikiApp.setFproxyPrefix(query.get(key)); }
-
- if (key.equals("fmshost")) { mArchiveManager.setFmsHost(query.get(key)); }
- if (key.equals("fmsport")) { mArchiveManager.setFmsPort(Integer.parseInt(query.get(key))); }
-
- if (key.equals("fmsssk")) { mArchiveManager.setPrivateSSK(query.get(key)); }
- if (key.equals("fmsid")) { mArchiveManager.setFmsId(query.get(key)); }
-
- if (key.equals("wikiname")) { mArchiveManager.setBissName(query.get(key)); }
- }
-
- // Don't "images" not set in query params when box unchecked.
- if (query.containsKey("images")) {
- System.err.println("images: SET");
- mWikiApp.setAllowImages(true);
- } else {
- System.err.println("images: CLEARED");
- mWikiApp.setAllowImages(false);
- }
- }
-
- private static String noNulls(String text) {
- if (text == null) {
- return "";
- }
- return text;
}
// DCI: back over this. escape quotes.
public String handle(WikiContext context) throws ChildContainerException {
handlePost(context);
- // DCI: Hmmm... it would be more pedantic to use the context interface for all of these.
String href = makeHref(context.makeLink("/fniki/config"),
null, null, null, null);
- System.err.println("images allowd: " + context.getInt("allow_images", 0));
return String.format(formTemplate(),
+ getMsgHtml(),
href,
- noNulls(mArchiveManager.getFcpHost()),
- Integer.toString(mArchiveManager.getFcpPort()),
- context.getString("fproxy_prefix", "http://127.0.0.1:8888/"),
- noNulls(mArchiveManager.getFmsHost()),
- Integer.toString(mArchiveManager.getFmsPort()),
- noNulls(mArchiveManager.getPrivateSSK()),
- noNulls(mArchiveManager.getFmsId()),
- noNulls(mArchiveManager.getFmsGroup()),
- noNulls(mArchiveManager.getBissName()),
- (context.getInt("allow_images", 0) == 1) ? "checked" : "",
+ mConfig.mFcpHost,
+ mConfig.mFcpPort,
+ mConfig.mFproxyPrefix,
+ mConfig.mFmsHost,
+ mConfig.mFmsPort,
+ mConfig.mFmsSsk,
+ mConfig.mFmsId,
+ mConfig.mFmsGroup,
+ mConfig.mWikiName,
+ mConfig.mAllowImages ? "checked" : "",
// IMPORTANT: Won't work as a plugin without this.
context.getString("form_password", "FORM_PASSWORD_NOT_SET"));
}
+ public String getMsgHtml() {
+ if (mMsg == null || mMsg.trim().equals("")) {
+ return "";
+ }
+ return String.format("<hr>%s<hr>\n", escapeHTML(mMsg));
+ }
+
public boolean isFinished() {return mFinished; }
public void cancel() { mFinished = true; }
public void entered(WikiContext context) {
mFinished = false;
+ mConfig = context.getConfiguration();
mMsg = "";
}
+
public void exited() {}
//////////////////////////////////////////////////
- private static final String FORM_FIELDS[] = new String[] {
- "fcphost", "fcpport", "fpprefix", "fmshost", "fmsport",
- "fmsssk", "fmsid", "wikiname", "images",
- };
+ // All fields used by the template must be in QueryBase.PARAMS:
+ // "fcphost", "fcpport", "fpprefix", "fmshost", "fmsport",
+ // "fmsssk", "fmsid", "wikiname", "images", "fmsbase", "formPassword", "defaults", "done"
+
+ // READ the comment above before modifiy the template!
private static String formTemplate() {
StringBuilder sb = new StringBuilder();
-
-sb.append("<html>\n");
-sb.append("<head>\n");
-sb.append("<title>\n");
-sb.append(" Configuration\n");
-sb.append("</title>\n");
-sb.append("</head>\n");
-sb.append("<body>\n");
-sb.append("<h1>Configuration</h1>\n");
-sb.append("<form method=\"post\" action=\"%s\" enctype=\"multipart/form-data\" accept-charset=\"UTF-8\">\n");
-sb.append("<table>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>Fcp Host</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fcphost\" value=\"%s\" /></td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>Fcp Port</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fcpport\" value=\"%s\" /></td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>Fproxy Prefix</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fpprefix\" size=\"64\" value=\"%s\" /></td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>FMS Host</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fmshost\" value=\"%s\" /></td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>FMS Port</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fmsport\" value=\"%s\" /> </td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>FMS Private SSK</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fmsssk\" size=\"128\" value=\"%s\" /> </td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>FMS ID</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fmsid\" value=\"%s\" /> </td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>FMS Group</td>\n");
-sb.append(" <td><input type=\"text\" name=\"fmsgroup\" value=\"%s\" /> </td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>Wiki Name</td>\n");
-sb.append(" <td><input type=\"text\" name=\"wikiname\" value=\"%s\"/> </td>\n");
-sb.append(" </tr>\n");
-sb.append(" <tr>\n");
-sb.append(" <td>Enable Images: <input type=\"checkbox\" name=\"images\" %s/></td>\n");
-sb.append(" </tr>\n");
-sb.append(" <input type=\"hidden\" name=\"formPassword\" value=\"%s\"/>\n");
-sb.append("</table>\n");
-sb.append("<input name=\"saved\" type=\"submit\" value=\"Save Changes\"/>\n");
-sb.append("<input name=\"discarded\" type=\"submit\" value=\"Discard Changes\"/>\n");
-sb.append("<input name=\"done\" type=\"submit\" value=\"Done\"/>\n");
-sb.append("</form>\n");
-sb.append("\n");
-sb.append("</body>\n");
-sb.append("</html>\n");
+ sb.append("<html>\n");
+ sb.append("<head>\n");
+ sb.append("<title>\n");
+ sb.append(" Configuration\n");
+ sb.append("</title>\n");
+ sb.append("</head>\n");
+ sb.append("<body>\n");
+ sb.append("%s\n");
+ sb.append("<h1>Configuration</h1>\n");
+ sb.append("<form method=\"post\" action=\"%s\" enctype=\"multipart/form-data\" accept-charset=\"UTF-8\">\n");
+ sb.append("<table>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>Fcp Host</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fcphost\" value=\"%s\" /></td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>Fcp Port</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fcpport\" value=\"%d\" /></td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>Fproxy Prefix</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fpprefix\" size=\"64\" value=\"%s\" /></td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>FMS Host</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fmshost\" value=\"%s\" /></td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>FMS Port</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fmsport\" value=\"%d\" /> </td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>FMS Private SSK</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fmsssk\" size=\"128\" value=\"%s\" /> </td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>FMS ID</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fmsid\" value=\"%s\" /> </td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>FMS Group</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"fmsgroup\" value=\"%s\" /> </td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td>Wiki Name</td>\n");
+ sb.append(" <td><input type=\"text\" name=\"wikiname\" value=\"%s\"/> </td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <tr>\n");
+ sb.append(" <td><input type=\"checkbox\" name=\"images\" %s/>Enable Images</td>\n");
+ sb.append(" </tr>\n");
+ sb.append(" <input type=\"hidden\" name=\"formPassword\" value=\"%s\"/>\n");
+ sb.append("</table>\n");
+ sb.append("<input name=\"defaults\" type=\"submit\" value=\"Reset Defaults\"/>\n");
+ sb.append("<input name=\"done\" type=\"submit\" value=\"Done\"/>\n");
+ sb.append("<hr>\n");
+ sb.append("<input name=\"import\" type=\"submit\" value=\"Import Configuration\"/>\n");
+ sb.append("<input type=\"file\" name=\"upload\" size=\"64\">\n");
+ sb.append("<hr>\n");
+ sb.append("<input name=\"export\" type=\"submit\" value=\"Export Configuration\"/>\n");
+ sb.append("<hr>\n");
+ sb.append("</form>\n");
+ sb.append("\n");
+ sb.append("</body>\n");
+ sb.append("</html>\n");
return sb.toString();
}
}
+// DCI: include form html and script in comments.
diff --git a/src/fniki/wiki/child/WikiContainer.java b/src/fniki/wiki/child/WikiContainer.java
--- a/src/fniki/wiki/child/WikiContainer.java
+++ b/src/fniki/wiki/child/WikiContainer.java
@@ -140,7 +140,13 @@ public class WikiContainer implements Ch
if (context.getStorage().hasPage(name)) {
buffer.append(renderXHTML(context, context.getStorage().getPage(name)));
} else {
- buffer.append("Page doesn't exist in the wiki yet.");
+ if (name.equals(context.getString("default_page", "Front_Page"))) {
+ buffer.append(renderXHTML(context,
+ context.getString("default_wikitext",
+ "Page doesn't exist in the wiki yet.")));
+ } else {
+ buffer.append("Page doesn't exist in the wiki yet.");
+ }
}
addFooter(context, name, buffer);
return buffer.toString();