SusiDNS: Add import feature (ticket #2447)

Box overlap issue remains todo, see ticket #2419
This commit is contained in:
zzz
2019-03-23 16:42:37 +00:00
parent fea5bd4ada
commit ce043943d9
6 changed files with 159 additions and 22 deletions

View File

@@ -97,6 +97,19 @@
<replace file="WEB-INF/web-out.xml">
<replacefilter token="&lt;!-- precompiled servlets --&gt;" value="${jspc.web.fragment}" />
</replace>
<!-- Add multipart config to servlets that need them -->
<property name="__match1" value="&lt;servlet-class&gt;i2p.susi.dns.jsp." />
<property name="__match2" value="_jsp&lt;/servlet-class&gt;" />
<property name="__class1" value="${__match1}addressbook${__match2}" />
<property name="__multipart" value="&#10;
&lt;multipart-config&gt;&#10;
&lt;max-file-size&gt;67108864&lt;/max-file-size&gt;&#10;
&lt;max-request-size&gt;67108864&lt;/max-request-size&gt;&#10;
&lt;file-size-threshold&gt;262144&lt;/file-size-threshold&gt;&#10;
&lt;/multipart-config&gt;" />
<replace file="WEB-INF/web-out.xml">
<replacefilter token="${__class1}" value="${__class1}${__multipart}" />
</replace>
</target>
<uptodate property="precompilejsp.uptodate" targetfile="WEB-INF/web-out.xml">

View File

@@ -21,7 +21,11 @@
package i2p.susi.dns;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Arrays;
import java.util.ArrayList;
@@ -34,9 +38,11 @@ import java.util.Properties;
import java.util.SortedMap;
import net.i2p.client.naming.NamingService;
import net.i2p.client.naming.SingleFileNamingService;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.servlet.RequestWrapper;
/**
* Talk to the NamingService API instead of modifying the hosts.txt files directly,
@@ -356,7 +362,7 @@ public class NamingServiceBean extends AddressbookBean
action = null;
if( message.length() > 0 )
message = "<p class=\"messages\">" + message + "</p>";
message = styleMessage(message);
return message;
}
@@ -494,6 +500,90 @@ public class NamingServiceBean extends AddressbookBean
// No post-filtering for hosts.txt naming services. It is what it is.
}
/**
* @return messages about this action
* @since 0.9.40
*/
public String importFile(RequestWrapper wrequest) throws IOException {
String message = "";
InputStream in = wrequest.getInputStream("file");
OutputStream out = null;
File tmp = null;
SingleFileNamingService sfns = null;
try {
// non-null but zero bytes if no file entered, don't know why
if (in == null || in.available() <= 0) {
return styleMessage(_t("You must enter a file"));
}
// copy to temp file
tmp = new File(_context.getTempDir(), "susidns-import-" + _context.random().nextLong() + ".txt");
out = new FileOutputStream(tmp);
DataHelper.copy(in, out);
in.close();
in = null;
out.close();
out = null;
// new SingleFileNamingService
sfns = new SingleFileNamingService(_context, tmp.getAbsolutePath());
// getEntries, copy over
Map<String, Destination> entries = sfns.getEntries();
int count = entries.size();
if (count <= 0) {
return styleMessage(_t("No entries found in file"));
} else {
NamingService service = getNamingService();
int added = 0, dup = 0;
Properties nsOptions = new Properties();
nsOptions.setProperty("list", getFileName());
String now = Long.toString(_context.clock().now());
nsOptions.setProperty("m", now);
String filename = wrequest.getFilename("file");
if (filename != null)
nsOptions.setProperty("s", _t("Imported from file {0}", filename));
else
nsOptions.setProperty("s", _t("Imported from file"));
for (Map.Entry<String, Destination> e : entries.entrySet()) {
String host = e.getKey();
Destination dest = e.getValue();
boolean ok = service.putIfAbsent(host, dest, nsOptions);
if (ok)
added++;
else
dup++;
}
StringBuilder buf = new StringBuilder(128);
if (added > 0)
buf.append(styleMessage(ngettext("Loaded {0} entry from file",
"Loaded {0} entries from file",
added)));
if (dup > 0)
buf.append(styleMessage(ngettext("Skipped {0} duplicate entry from file",
"Skipped {0} duplicate entries from file",
dup)));
return buf.toString();
}
} catch (IOException ioe) {
return styleMessage(_t("Import from file failed") + " - " + ioe);
} finally {
if (in != null)
try { in.close(); } catch (IOException ioe) {}
if (out != null)
try { out.close(); } catch (IOException ioe) {}
// shutdown SFNS
if (sfns != null)
sfns.shutdown();
if (tmp != null)
tmp.delete();
}
}
/**
* @since 0.9.40
*/
private static String styleMessage(String message) {
return "<p class=\"messages\">" + message + "</p>";
}
/**
* @since 0.9.34
*/

View File

@@ -34,13 +34,18 @@
response.setHeader("Referrer-Policy", "no-referrer");
response.setHeader("Accept-Ranges", "none");
%>
<%@page pageEncoding="UTF-8"%>
<%@ page contentType="text/html"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
%><%@page pageEncoding="UTF-8" contentType="text/html" import="net.i2p.servlet.RequestWrapper"
%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<jsp:useBean id="version" class="i2p.susi.dns.VersionBean" scope="application" />
<jsp:useBean id="book" class="i2p.susi.dns.NamingServiceBean" scope="session" />
<jsp:useBean id="intl" class="i2p.susi.dns.Messages" scope="application" />
<%
String importMessages = null;
if (intl._t("Import").equals(request.getParameter("action"))) {
RequestWrapper wrequest = new RequestWrapper(request);
importMessages = book.importFile(wrequest);
}
%>
<jsp:setProperty name="book" property="*" />
<jsp:setProperty name="book" property="resetDeletionMarks" value="1"/>
<c:forEach items="${paramValues.checked}" var="checked">
@@ -75,7 +80,11 @@
<h4><%=intl._t("Storage")%>: ${book.displayName}</h4>
</div>
<div id="messages">${book.messages}</div>
<div id="messages">${book.messages}<%
if (importMessages != null) {
%><%=importMessages%><%
}
%></div>
${book.loadBookMessages}
@@ -254,6 +263,28 @@ ${book.loadBookMessages}
</div>
</form>
<% if (!book.getBook().equals("published")) { %>
<form method="POST" action="addressbook" enctype="multipart/form-data" accept-charset="UTF-8">
<input type="hidden" name="book" value="${book.book}">
<input type="hidden" name="serial" value="<%=susiNonce%>">
<input type="hidden" name="begin" value="0">
<input type="hidden" name="end" value="49">
<div id="import">
<h3><%=intl._t("Import from hosts.txt file")%></h3>
<table>
<tr>
<td><b><%=intl._t("File")%></b></td>
<td><input name="file" type="file" accept=".txt" value="" /></td>
</tr>
</table>
<p class="buttons">
<input class="cancel" type="reset" value="<%=intl._t("Cancel")%>" >
<input class="download" type="submit" name="action" value="<%=intl._t("Import")%>" >
</p>
</div>
</form>
<% } %>
<div id="footer">
<hr>
<p class="footer">susidns v${version.version} &copy; <a href="${version.url}" target="_top">susi</a> 2005</p>

View File

@@ -346,7 +346,7 @@ public class SingleFileNamingService extends NamingService {
}
/**
* @param options As follows:
* @param options null OK, or as follows:
* Key "search": return only those matching substring
* Key "startsWith": return only those starting with
* ("[0-9]" allowed)
@@ -413,7 +413,7 @@ public class SingleFileNamingService extends NamingService {
/**
* Overridden since we store base64 natively.
*
* @param options As follows:
* @param options null OK, or as follows:
* Key "search": return only those matching substring
* Key "startsWith": return only those starting with
* ("[0-9]" allowed)

View File

@@ -751,14 +751,14 @@ div#book, #emptybook {
margin: 0;
}
div#add {
div#add, div#import {
border: 1px solid #2a5f29;
padding: 0 0 10px;
margin-top: 23px;
background: #000;
}
#add h3 {
#add h3, #import h3 {
margin-top: -6px;
margin-left: -1px;
margin-right: -1px;
@@ -766,21 +766,23 @@ div#add {
font-size: 10pt;
}
#add table {
#add table, #import table {
width: 100%;
width: calc(100% - 1px);
margin: -10px 10px 0 0;
}
#add td:first-child {
#add td:first-child,
#import table td:first-child {
text-align: right;
}
#add td:last-child {
#add td:last-child,
#import td:last-child {
width: 94%;
}
#add p.buttons {
#add p.buttons, #import p.buttons {
margin-top: 5px;
border-top: 1px solid #2a5f29;
padding-top: 5px;

View File

@@ -270,39 +270,40 @@ form[action="subscriptions"] #content {
line-height: 130%;
}
div#add {
div#add, div#import {
border: 1px solid #7778bf;
margin-top: -1px;
padding: 0 15px;
background: #fafaff;
}
.iframed #add {
.iframed #add, .iframed #import {
margin-top: 10px;
}
#add h3 {
#add h3, #import h3 {
border-bottom: 1px solid #7778bf;
margin: 0 -15px;
padding: 5px 10px;
}
#add table {
#add table, #import table {
width: 100%;
margin: 5px 0;
}
#add table td:first-child {
#add table td:first-child,
#import table td:first-child {
width: 50px;
white-space: nowrap;
text-align: right;
}
#add td {
#add td, #import td {
padding: 3px;
}
div#add p.buttons {
div#add p.buttons, div#import p.buttons {
border: 1px solid #7778bf;
margin: 0 -16px -1px;
background: linear-gradient(to bottom, #fff 50%, rgba(220,220,255,0.3)), repeating-linear-gradient(135deg, rgba(255,255,255,0.5) 2px, rgba(221, 221, 255, 0.3) 3px, #fff 5px), #fff !important;
@@ -327,7 +328,7 @@ div.help {
font-size: 10pt;
}
div.help h3, #add h3 {
div.help h3, #add h3, #import h3 {
border: 1px solid #7778bf;
padding: 5px 10px;
margin: -1px -16px 0;