Browse Source

Merge branch 'main' into lang_de

lang_de
Stephan Richter 2 years ago
parent
commit
d20a1b6f4b
  1. 133
      doc/data structure.dia
  2. 6
      pom.xml
  3. 8
      src/main/java/de/srsoftware/widerhall/Application.java
  4. 3
      src/main/java/de/srsoftware/widerhall/Constants.java
  5. 44
      src/main/java/de/srsoftware/widerhall/Util.java
  6. 3
      src/main/java/de/srsoftware/widerhall/data/Database.java
  7. 4
      src/main/java/de/srsoftware/widerhall/data/ListMember.java
  8. 185
      src/main/java/de/srsoftware/widerhall/data/MailingList.java
  9. 169
      src/main/java/de/srsoftware/widerhall/data/Post.java
  10. 20
      src/main/java/de/srsoftware/widerhall/mail/ImapClient.java
  11. 49
      src/main/java/de/srsoftware/widerhall/mail/SmtpClient.java
  12. 22
      src/main/java/de/srsoftware/widerhall/web/Rest.java
  13. 2
      src/main/java/de/srsoftware/widerhall/web/TemplateServlet.java
  14. 41
      src/main/java/de/srsoftware/widerhall/web/Web.java
  15. 26
      static/templates/archive.st
  16. 16
      static/templates/inspect.st
  17. 25
      static/templates/js.st
  18. 34
      static/templates/post.st

133
doc/data structure.dia

@ -1051,13 +1051,13 @@ @@ -1051,13 +1051,13 @@
<dia:point val="22,18"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="21.95,17.95;27.05,20.05"/>
<dia:rectangle val="21.95,17.95;28.05,20.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="22,18"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="5"/>
<dia:real val="6"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1080,7 +1080,7 @@ @@ -1080,7 +1080,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="24.5,19.1941"/>
<dia:point val="25,19.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1096,13 +1096,13 @@ @@ -1096,13 +1096,13 @@
<dia:point val="23,22"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,21.95;27.05,24.05"/>
<dia:rectangle val="22.95,21.95;28.05,24.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,22"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1125,7 +1125,7 @@ @@ -1125,7 +1125,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,23.1941"/>
<dia:point val="25.5,23.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1141,13 +1141,13 @@ @@ -1141,13 +1141,13 @@
<dia:point val="23,24"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,23.95;27.05,26.05"/>
<dia:rectangle val="22.95,23.95;28.05,26.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,24"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
<dia:real val="5.0000000000000009"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1161,7 +1161,7 @@ @@ -1161,7 +1161,7 @@
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
<dia:string>#From#</dia:string>
<dia:string>#FromAddr#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@ -1170,7 +1170,7 @@ @@ -1170,7 +1170,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,25.1941"/>
<dia:point val="25.5,25.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1186,13 +1186,13 @@ @@ -1186,13 +1186,13 @@
<dia:point val="23,20"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,19.95;27.05,22.05"/>
<dia:rectangle val="22.95,19.95;28.05,22.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,20"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1215,7 +1215,7 @@ @@ -1215,7 +1215,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,21.1941"/>
<dia:point val="25.5,21.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1228,13 +1228,13 @@ @@ -1228,13 +1228,13 @@
</dia:object>
<dia:object type="Standard - ZigZagLine" version="1" id="O26">
<dia:attribute name="obj_pos">
<dia:point val="27.0488,23"/>
<dia:point val="28.0502,23"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="16.95,8.1382;29.05,23.05"/>
</dia:attribute>
<dia:attribute name="orth_points">
<dia:point val="27.0488,23"/>
<dia:point val="28.0502,23"/>
<dia:point val="29,23"/>
<dia:point val="29,17"/>
<dia:point val="19,17"/>
@ -1267,16 +1267,16 @@ @@ -1267,16 +1267,16 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O27">
<dia:attribute name="obj_pos">
<dia:point val="23,26"/>
<dia:point val="23,28"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,25.95;27.05,28.05"/>
<dia:rectangle val="22.95,27.95;28.05,30.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,26"/>
<dia:point val="23,28"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1299,7 +1299,7 @@ @@ -1299,7 +1299,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,27.1941"/>
<dia:point val="25.5,29.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1312,16 +1312,16 @@ @@ -1312,16 +1312,16 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O28">
<dia:attribute name="obj_pos">
<dia:point val="23,28"/>
<dia:point val="23,30"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,27.95;27.05,30.05"/>
<dia:rectangle val="22.95,29.95;28.05,32.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,28"/>
<dia:point val="23,30"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1344,7 +1344,7 @@ @@ -1344,7 +1344,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,29.1941"/>
<dia:point val="25.5,31.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1357,16 +1357,16 @@ @@ -1357,16 +1357,16 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O29">
<dia:attribute name="obj_pos">
<dia:point val="23,30"/>
<dia:point val="23,32"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,29.95;27.05,32.05"/>
<dia:rectangle val="22.95,31.95;28.05,34.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,30"/>
<dia:point val="23,32"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1389,7 +1389,7 @@ @@ -1389,7 +1389,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,31.1941"/>
<dia:point val="25.5,33.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1402,16 +1402,16 @@ @@ -1402,16 +1402,16 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O30">
<dia:attribute name="obj_pos">
<dia:point val="23,32"/>
<dia:point val="23,34"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,31.95;27.05,34.05"/>
<dia:rectangle val="22.95,33.95;28.05,36.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,32"/>
<dia:point val="23,34"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
@ -1425,7 +1425,7 @@ @@ -1425,7 +1425,7 @@
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
<dia:string>#Content#</dia:string>
<dia:string>#File#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
@ -1434,7 +1434,7 @@ @@ -1434,7 +1434,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,33.1941"/>
<dia:point val="25.5,35.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1447,16 +1447,16 @@ @@ -1447,16 +1447,16 @@
</dia:object>
<dia:object type="Standard - ZigZagLine" version="1" id="O31">
<dia:attribute name="obj_pos">
<dia:point val="22.9512,27"/>
<dia:point val="22.9498,29"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="20.95,20.6382;23.0012,27.05"/>
<dia:rectangle val="20.95,20.6382;22.9998,29.05"/>
</dia:attribute>
<dia:attribute name="orth_points">
<dia:point val="22.9512,27"/>
<dia:point val="21,27"/>
<dia:point val="22.9498,29"/>
<dia:point val="21,29"/>
<dia:point val="21,21"/>
<dia:point val="22.9512,21"/>
<dia:point val="22.9498,21"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@ -1482,13 +1482,13 @@ @@ -1482,13 +1482,13 @@
</dia:object>
<dia:object type="Standard - ZigZagLine" version="1" id="O32">
<dia:attribute name="obj_pos">
<dia:point val="27.0449,25"/>
<dia:point val="28.0503,25"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="26.9949,10.1382;33.05,25.05"/>
<dia:rectangle val="28.0003,10.1382;33.05,25.05"/>
</dia:attribute>
<dia:attribute name="orth_points">
<dia:point val="27.0449,25"/>
<dia:point val="28.0503,25"/>
<dia:point val="31,25"/>
<dia:point val="31,10.5"/>
<dia:point val="33,10.5"/>
@ -1695,5 +1695,50 @@ @@ -1695,5 +1695,50 @@
</dia:composite>
</dia:attribute>
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O37">
<dia:attribute name="obj_pos">
<dia:point val="23,26"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,25.95;28.05,28.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,26"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
</dia:attribute>
<dia:attribute name="show_background">
<dia:boolean val="true"/>
</dia:attribute>
<dia:attribute name="padding">
<dia:real val="0.5"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
<dia:string>#FromAddr#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
</dia:attribute>
<dia:attribute name="height">
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25.5,27.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
</dia:attribute>
<dia:attribute name="alignment">
<dia:enum val="1"/>
</dia:attribute>
</dia:composite>
</dia:attribute>
</dia:object>
</dia:layer>
</dia:diagram>

6
pom.xml

@ -6,7 +6,11 @@ @@ -6,7 +6,11 @@
<groupId>org.example</groupId>
<artifactId>Widerhall</artifactId>
<version>0.0.22</version>
<<<<<<< HEAD
<version>0.0.23</version>
=======
<version>0.2.1</version>
>>>>>>> main
<build>
<plugins>
<plugin>

8
src/main/java/de/srsoftware/widerhall/Application.java

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
package de.srsoftware.widerhall;
import de.srsoftware.widerhall.data.MailingList;
import de.srsoftware.widerhall.web.Front;
import de.srsoftware.widerhall.web.Rest;
import de.srsoftware.widerhall.web.Web;
@ -20,8 +21,13 @@ public class Application { @@ -20,8 +21,13 @@ public class Application {
while (!config.configFile().equals(config.file())) config.load(config.configFile());
if (!config.configFile().exists()) config.save();
//startMailSystem(json);
startWebserver();
startMailsystem();
}
private static void startMailsystem() {
MailingList.startEnabled();
}
private static void startWebserver() throws Exception {

3
src/main/java/de/srsoftware/widerhall/Constants.java

@ -12,6 +12,7 @@ public class Constants { @@ -12,6 +12,7 @@ public class Constants {
public static final String IMAPS = "imaps";
public static final String INBOX = "inbox";
public static final String INDEX = "index";
public static final String ID = "id";
public static final String INT = "INT";
public static final String LIST = "list";
public static final String LOCATIONS = "locations";
@ -23,6 +24,8 @@ public class Constants { @@ -23,6 +24,8 @@ public class Constants {
public static final String PREFIX = "prefix";
public static final String PROTOCOL = "mail.store.protocol";
public static final String STATE = "state";
public static final String SUBJECT = "subject";
public static final String TEXT = "text";
public static final String TOKEN = "token";
public static final String USER = "user";
public static final String VARCHAR = "VARCHAR(255)";

44
src/main/java/de/srsoftware/widerhall/Util.java

@ -4,9 +4,14 @@ import de.srsoftware.tools.translations.Translation; @@ -4,9 +4,14 @@ import de.srsoftware.tools.translations.Translation;
import de.srsoftware.widerhall.data.MailingList;
import de.srsoftware.widerhall.data.User;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@ -118,4 +123,43 @@ public class Util { @@ -118,4 +123,43 @@ public class Util {
public static boolean getCheckbox(HttpServletRequest req, String key) {
return "on".equals(req.getParameter(key));
}
/**
* Return the primary text content of the message.
*/
public static String getText(Part p) throws MessagingException, IOException {
// https://javaee.github.io/javamail/FAQ
if (p.isMimeType("text/*")) return (String)p.getContent();
if (p.isMimeType("multipart/alternative")) {
// prefer html text over plain text
Multipart mp = (Multipart)p.getContent();
String text = null;
for (int i = 0; i < mp.getCount(); i++) {
Part bp = mp.getBodyPart(i);
if (bp.isMimeType("text/plain")) {
if (text == null) text = getText(bp);
continue;
} else if (bp.isMimeType("text/html")) {
String s = getText(bp);
if (s != null) return s;
} else {
return getText(bp);
}
}
return text;
} else if (p.isMimeType("multipart/*")) {
Multipart mp = (Multipart)p.getContent();
for (int i = 0; i < mp.getCount(); i++) {
String s = getText(mp.getBodyPart(i));
if (s != null) return s;
}
}
return null;
}
public static String dropEmail(String tx) {
return tx.replaceAll( "[.\\-\\w]+@[.\\-\\w]+", "[email_removed]");
}
}

3
src/main/java/de/srsoftware/widerhall/data/Database.java

@ -236,7 +236,7 @@ public class Database { @@ -236,7 +236,7 @@ public class Database {
/**
* add a where condition in the form of WHERE [key] in ([value])
* @param key
* @param values
* @param value
* @return
*/
private Request where(String key, Object value) {
@ -260,6 +260,7 @@ public class Database { @@ -260,6 +260,7 @@ public class Database {
if (!tableExists(User.TABLE_NAME)) User.createTable();
if (!tableExists(MailingList.TABLE_NAME)) MailingList.createTable();
if (!tableExists(ListMember.TABLE_NAME)) ListMember.createTable();
if (!tableExists(Post.TABLE_NAME)) Post.createTable();
return this;
}

4
src/main/java/de/srsoftware/widerhall/data/ListMember.java

@ -136,6 +136,10 @@ public class ListMember { @@ -136,6 +136,10 @@ public class ListMember {
}
public boolean isOwner(){
return hasState(STATE_OWNER);
}
/**
* return a set of list emails of MailingLists owned by the given user
* @param user

185
src/main/java/de/srsoftware/widerhall/data/MailingList.java

@ -1,20 +1,26 @@ @@ -1,20 +1,26 @@
package de.srsoftware.widerhall.data;
import de.srsoftware.widerhall.Configuration;
import de.srsoftware.widerhall.Util;
import de.srsoftware.widerhall.mail.ImapClient;
import de.srsoftware.widerhall.mail.MessageHandler;
import de.srsoftware.widerhall.mail.SmtpClient;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.mail.Address;
import javax.mail.Flags;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import java.io.UnsupportedEncodingException;
import javax.ws.rs.HEAD;
import java.io.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static de.srsoftware.widerhall.Constants.*;
@ -40,8 +46,14 @@ public class MailingList implements MessageHandler { @@ -40,8 +46,14 @@ public class MailingList implements MessageHandler {
private static final int STATE_PUBLIC = 2;
public static final int STATE_FORWARD_FROM = 4;
public static final int STATE_FORWARD_ATTACHED = 8;
public static final int STATE_HIDE_RECEIVERS = 16;
public static final int STATE_REPLY_TO_LIST = 32;
public static final int STATE_OPEN = 64;
public static final int STATE_PUBLIC_ARCHIVE = 128;
private static final int VISIBLE = 1;
private static final int HIDDEN = 0;
private static final int DEFAULT_STATE = STATE_PENDING|STATE_HIDE_RECEIVERS|STATE_PUBLIC_ARCHIVE;
private static final String RETAINED_FOLDER = "retained";
private final String name;
private final String email;
private int state;
@ -72,6 +84,10 @@ public class MailingList implements MessageHandler { @@ -72,6 +84,10 @@ public class MailingList implements MessageHandler {
this.imap = new ImapClient(imapHost,imapPort,imapUser,imapPass,inbox);
}
public MailingList archive(boolean enabled) throws SQLException {
return setFlag(STATE_PUBLIC_ARCHIVE,enabled);
}
/**
* create a new ML object int the database
* @param email
@ -88,7 +104,7 @@ public class MailingList implements MessageHandler { @@ -88,7 +104,7 @@ public class MailingList implements MessageHandler {
* @throws SQLException
*/
public static MailingList create(String email, String name, String imapHost, int imapPort, String imapUser, String imapPass, String inbox, String smtpHost, int smtpPort, String smtpUser, String smtpPass) throws SQLException {
return new MailingList(email, name, imapHost, imapPort, imapUser, imapPass, inbox, smtpHost, smtpPort, smtpUser, smtpPass, STATE_PENDING).save();
return new MailingList(email, name, imapHost, imapPort, imapUser, imapPass, inbox, smtpHost, smtpPort, smtpUser, smtpPass, DEFAULT_STATE).save();
}
/**
@ -131,32 +147,37 @@ public class MailingList implements MessageHandler { @@ -131,32 +147,37 @@ public class MailingList implements MessageHandler {
}
public void enable(boolean enable) throws SQLException {
public MailingList enable(boolean enable) throws SQLException {
if (!enable) imap.stop();
setFlag(STATE_ENABLED,enable);
if (enable) {
imap.start().addListener(this);
} else {
imap.stop();
}
if (enable) imap.start().addListener(this);
return this;
}
private void forward(Message message) throws MessagingException {
try {
var emails = members().stream().map(ListMember::user).map(User::email).toList();
String sender = (state & STATE_FORWARD_FROM) > 0 ? message.getFrom()[0].toString() : email();
smtp.bccForward(sender,message,emails);
String newSender = !hasState(STATE_FORWARD_FROM) ? email() : null;
var receivers = members()
.stream()
.map(ListMember::user)
.map(User::email)
.toList();
var subject = message.getSubject();
if (!subject.contains(stamp())) subject = stamp()+" "+subject;
var replyTo = (newSender == null && hasState(STATE_REPLY_TO_LIST)) ? email() : null;
smtp.forward(newSender,receivers,message,subject,hasState(STATE_FORWARD_ATTACHED),hasState(STATE_HIDE_RECEIVERS),replyTo);
} catch (SQLException e) {
LOG.error("Laden der Listen-Mitglieder von {} fehlgeschlagen. Nachricht kann nicht weitergeleitet werden!",email(),e);
}
}
public void forwardAttached(boolean forward) throws SQLException {
setFlag(STATE_FORWARD_ATTACHED,forward);
public MailingList forwardAttached(boolean forward) throws SQLException {
return setFlag(STATE_FORWARD_ATTACHED,forward);
}
public void forwardFrom(boolean forward) throws SQLException {
setFlag(STATE_FORWARD_FROM,forward);
public MailingList forwardFrom(boolean forward) throws SQLException {
return setFlag(STATE_FORWARD_FROM,forward);
}
@ -186,12 +207,26 @@ public class MailingList implements MessageHandler { @@ -186,12 +207,26 @@ public class MailingList implements MessageHandler {
return ml;
}
private boolean hashMember(String senderEmail) {
if (senderEmail == null) return false;
try {
return members().stream().map(ListMember::user).map(User::email).anyMatch(senderEmail::equals);
} catch (SQLException e) {
LOG.warn("hasMember() failded for {}",email(),e);
}
return false;
}
public boolean hasState(int test){
return (state & test) > 0;
}
public void hide(boolean hide) throws SQLException {
setFlag(STATE_PUBLIC,!hide);
public MailingList hide(boolean hide) throws SQLException {
return setFlag(STATE_PUBLIC,!hide);
}
public MailingList hideReceivers(boolean hide) throws SQLException {
return setFlag(STATE_HIDE_RECEIVERS,hide);
}
/**
@ -200,7 +235,7 @@ public class MailingList implements MessageHandler { @@ -200,7 +235,7 @@ public class MailingList implements MessageHandler {
* @return
*/
public boolean isOpenFor(User user) {
if ((state & STATE_PUBLIC) > 0) return true; // all users may subscribe public mailing lists
if (hasState(STATE_PUBLIC)) return true; // all users may subscribe public mailing lists
if (user == null) return false;
try {
var member = ListMember.load(this,user);
@ -235,7 +270,7 @@ public class MailingList implements MessageHandler { @@ -235,7 +270,7 @@ public class MailingList implements MessageHandler {
public boolean mayBeAlteredBy(User user) {
if (user.hashPermission(PERMISSION_ADMIN)) return true;
try {
if (ListMember.load(this,user).hasState(ListMember.STATE_OWNER)) return true;
if (ListMember.load(this,user).isOwner()) return true;
} catch (SQLException e) {
LOG.debug("Fehler beim Laden des Listenmitglieds für ({}, {})",user.email(),email());
}
@ -245,7 +280,7 @@ public class MailingList implements MessageHandler { @@ -245,7 +280,7 @@ public class MailingList implements MessageHandler {
public boolean mayBeTestedBy(User user) {
if (user.hashPermission(PERMISSION_ADMIN)) return true;
try {
if (ListMember.load(this,user).hasState(ListMember.STATE_OWNER)) return true;
if (ListMember.load(this,user).isOwner()) return true;
} catch (SQLException e) {
LOG.debug("Fehler beim Laden des Listenmitglieds für ({}, {})",user.email(),email());
}
@ -259,7 +294,7 @@ public class MailingList implements MessageHandler { @@ -259,7 +294,7 @@ public class MailingList implements MessageHandler {
public boolean membersMayBeListedBy(User user) {
if (user.hashPermission(PERMISSION_ADMIN)) return true;
try {
if (ListMember.load(this,user).hasState(ListMember.STATE_OWNER)) return true;
if (ListMember.load(this,user).isOwner()) return true;
} catch (SQLException e) {
LOG.debug("Fehler beim Laden des Listenmitglieds für ({}, {})",user.email(),email());
}
@ -287,10 +322,34 @@ public class MailingList implements MessageHandler { @@ -287,10 +322,34 @@ public class MailingList implements MessageHandler {
@Override
public void onMessageReceived(Message message) throws MessagingException {
LOG.debug("Nachricht empfangen: {}",message.getFrom());
storeMessage(message);
String subject = message.getSubject();
if (subject.toLowerCase().contains("undelivered")){
try {
var receivers = members().stream().filter(ListMember::isOwner).map(ListMember::user).map(User::email).toList();
smtp.forward(email(), receivers, message, message.getSubject(), false,false,null);
} catch (SQLException e){
LOG.error("Was not able to load members of {}; Non-Delivery notification dropped!",this.email(),e);
}
return;
}
Address from = message.getFrom()[0];
if (from instanceof InternetAddress internetAddress){
var senderEmail = ((InternetAddress) from).getAddress();
if (!hasState(STATE_OPEN) && !this.hashMember(senderEmail)){
retainMessage(message);
sentRetentionNotification(senderEmail);
return;
}
}
if (hasState(STATE_PUBLIC_ARCHIVE)) storeMessage(message);
forward(message);
}
public MailingList open(boolean open) throws SQLException {
return setFlag(STATE_OPEN,open);
}
/**
* provide the set of mailing lists that are publicy open to subscriptions
* @return
@ -311,7 +370,33 @@ public class MailingList implements MessageHandler { @@ -311,7 +370,33 @@ public class MailingList implements MessageHandler {
return list;
}
public MailingList replyToList(boolean on) throws SQLException {
return setFlag(STATE_REPLY_TO_LIST,on);
}
private void retainMessage(Message message) {
String subject = "unknown mail";
try {
subject = message.getSubject();
imap.move(message, RETAINED_FOLDER);
return;
} catch (MessagingException e){
LOG.warn("Retaining message {} failed!",subject,e);
}
try {
message.setFlag(Flags.Flag.SEEN, true);
return;
} catch (MessagingException e) {
LOG.warn("Failed to flag message {} as SEEN!",subject,e);
}
try {
LOG.error("Retaining message {} failed. To avoid dead loop, the MailingList '{}' will be stopped!",subject,email());
enable(false);
} catch (SQLException sqle) {
LOG.debug("Failed to update list state in database:",sqle);
}
}
/**
* creates a map of the current ML containing all fields but passwords.
* @return
@ -367,12 +452,36 @@ public class MailingList implements MessageHandler { @@ -367,12 +452,36 @@ public class MailingList implements MessageHandler {
var config = Configuration.instance();
var url = new StringBuilder(config.baseUrl()).append("/confirm?token=").append(token);
var text = t("Botte gehen Sie zu {} um das Abonnieren der Liste abzuschließen!",url);
smtp.login().send(email(),name(),user.email(),subject,text);
smtp.send(email(),name(),user.email(),subject,text);
}
private void sentRetentionNotification(String senderEmail) {
try {
var receivers = members()
.stream()
.filter(ListMember::isOwner)
.map(ListMember::user)
.map(User::email)
.collect(Collectors.joining(", "));
var subject = t("List '{}' requires attention!",name());
var text = t("This list received an email from {}, who is not member of the list.\nThe email has been moved to the '{}' folder.\nYou may manually forward this message or drop it.",senderEmail,RETAINED_FOLDER);
smtp.send(email(), name(), receivers,subject,text);
subject = t("Your message to {} was rejected!",email());
text = t("You have tried to send a message to the list '{}', which failed. This is because you are not a member of this list.\n",name());
if (hasState(STATE_PUBLIC)) text += t("You may go to {} and subscribe to the list, then try again.",Configuration.instance().baseUrl());
smtp.send(email(), name(), senderEmail,subject,text);
} catch (SQLException e){
LOG.error("Failed to load list of owners of mailing list. Retention notification was not sent to owners of {}",email(),e);
} catch (MessagingException | UnsupportedEncodingException e){
LOG.error("Failed to send retention notification to owners of {}",email(),e);
}
}
private void setFlag(int flag, boolean on) throws SQLException {
private MailingList setFlag(int flag, boolean on) throws SQLException {
state = on ? state | flag : state ^ (state & flag);
Database.open().update(TABLE_NAME).set(STATE,state).where(EMAIL, email()).compile().run();
return this;
}
public Map<String,Integer> stateMap(){
@ -380,6 +489,11 @@ public class MailingList implements MessageHandler { @@ -380,6 +489,11 @@ public class MailingList implements MessageHandler {
if (hasState(STATE_ENABLED)) map.put("enabled",VISIBLE);
if (hasState(STATE_PUBLIC)) map.put("public",VISIBLE);
if (hasState(STATE_FORWARD_FROM)) map.put("original_from",HIDDEN);
if (hasState(STATE_FORWARD_ATTACHED)) map.put("forward_attached",HIDDEN);
if (hasState(STATE_HIDE_RECEIVERS)) map.put("hide_receivers",HIDDEN);
if (hasState(STATE_REPLY_TO_LIST)) map.put("reply_to_list",HIDDEN);
if (hasState(STATE_OPEN)) map.put("open",VISIBLE);
if (hasState(STATE_PUBLIC_ARCHIVE)) map.put("archive",VISIBLE);
return map;
}
@ -441,8 +555,25 @@ public class MailingList implements MessageHandler { @@ -441,8 +555,25 @@ public class MailingList implements MessageHandler {
}
}
private void storeMessage(Message message){
// TODO: implement
private String stamp() {
return "["+name+"]";
}
public static void startEnabled() {
try {
var rs = Database.open().select(TABLE_NAME).compile().exec();
while (rs.next()) {
var list = MailingList.from(rs);
if (list.hasState(STATE_ENABLED)) list.enable(true);
}
} catch (SQLException e) {
LOG.debug("Failed to load MailingLists.");
}
}
private void storeMessage(Message message) {
Post.create(this,message);
}

169
src/main/java/de/srsoftware/widerhall/data/Post.java

@ -0,0 +1,169 @@ @@ -0,0 +1,169 @@
package de.srsoftware.widerhall.data;
import de.srsoftware.widerhall.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import static de.srsoftware.widerhall.Constants.*;
import static de.srsoftware.widerhall.Constants.VARCHAR;
public class Post {
public static final Logger LOG = LoggerFactory.getLogger(Post.class);
public static final String TABLE_NAME = "Posts";
private static final String FROM_ADDR = "from_addr";
private static final String FROM_NAME = "from_name";
private static final String PARENT = "parent";
private static final String LONG = "LONG";
private static final String DATE = "date";
private static final String FILE = "file";
private static HashMap<String, Post> cache = new HashMap<>();
private String id, fromAddr, fromName, subject, filename;
private MailingList list;
private Post parent;
private Long timestamp;
public Post(String id, MailingList list, String fromAddr, String fromName, String subject, Long timestamp){
this.id = id;
this.list = list;
this.fromAddr = fromAddr;
this.fromName = fromName;
this.subject = subject;
this.timestamp = timestamp;
this.filename = generateFilename();
}
public static Post create(MailingList list, Message message){
try {
var id = message.getHeader("Message-ID")[0].replace("<", "").replace(">", "");
var addr = ((InternetAddress) message.getFrom()[0]);
var fromEmail = addr.getAddress();
var fromName = addr.getPersonal();
if (fromName == null || fromName.isBlank()) fromName = fromEmail.split("@")[0] + "@xxxxxx";
var subject = message.getSubject();
var text = Util.getText(message);
var time = message.getSentDate().getTime();
Post post = new Post(id,list,fromEmail,fromName,subject,time);
Files.writeString(post.file().toPath(),text, StandardCharsets.UTF_8);
return post.save();
} catch (MessagingException | IOException | SQLException e) {
LOG.warn("Failed to create post from {}",message);
}
return null;
}
/**
* create posts table
* @throws SQLException
*/
public static void createTable() throws SQLException {
var sql = new StringBuilder()
.append("CREATE TABLE ").append(TABLE_NAME)
.append(" (")
.append(ID).append(" ").append(VARCHAR).append(" NOT NULL PRIMARY KEY, ")
.append(LIST).append(" ").append(VARCHAR).append(", ")
.append(FROM_ADDR).append(" ").append(VARCHAR).append(", ")
.append(FROM_NAME).append(" ").append(VARCHAR).append(", ")
.append(PARENT).append(" ").append(VARCHAR).append(", ")
.append(SUBJECT).append(" ").append(VARCHAR).append(", ")
.append(DATE).append(" ").append(LONG).append(", ")
.append(FILE).append(" ").append(VARCHAR)
.append(");");
Database.open().query(sql).compile().run();
}
public File file(){
return new File(filename);
}
public static HashSet<Post> find(MailingList list) throws SQLException {
var rs = Database.open().select(TABLE_NAME).where(LIST,list.email()).compile().exec();
try {
var result = new HashSet<Post>();
while (rs.next()) result.add(Post.from(rs));
return result;
} finally {
rs.close();
}
}
private static Post from(ResultSet rs) {
try {
var id = rs.getString(ID);
var post = cache.get(id);
if (post == null) {
var list = MailingList.load(rs.getString(LIST));
post = new Post(id, list, rs.getString(FROM_ADDR), rs.getString(FROM_NAME), rs.getString(SUBJECT), rs.getLong(DATE));
cache.put(id,post);
}
return post;
} catch (SQLException e){
LOG.debug("Failed to load Post from database!",e);
}
return null;
}
private String generateFilename() {
return "/tmp/"+id+".json";
}
public String id() {
return id;
}
public static Post load(String id) throws SQLException {
var rs = Database.open().select(TABLE_NAME).where(ID,id).compile().exec();
try {
if (rs.next()) return Post.from(rs);
} finally {
rs.close();
}
return null;
}
public Map<String,Object> map() {
return Map.of(ID,id,
LIST,list.email(),
FROM_ADDR,fromAddr,
FROM_NAME,fromName,
SUBJECT,subject,
DATE,timestamp,
FILE,filename);
}
public Map<String,Object> safeMap() {
return Map.of(ID,id,
LIST,list.name(),
FROM_NAME,fromName,
SUBJECT,Util.dropEmail(subject),
DATE,timestamp);
}
private Post save() throws SQLException {
Database.open().insertInto(TABLE_NAME).values(map()).compile().run();
return this;
}
public long timestamp(){
return timestamp;
}
}

20
src/main/java/de/srsoftware/widerhall/mail/ImapClient.java

@ -23,6 +23,7 @@ public class ImapClient { @@ -23,6 +23,7 @@ public class ImapClient {
private static final Logger LOG = LoggerFactory.getLogger(ListeningThread.class);
private HashSet<MessageHandler> listeners = new HashSet<>();
private boolean stopped = false;
private Session session;
public ListeningThread addListener(MessageHandler messageHandler) {
listeners.add(messageHandler);
@ -53,7 +54,7 @@ public class ImapClient { @@ -53,7 +54,7 @@ public class ImapClient {
private void openInbox() throws MessagingException {
LOG.debug("Verbinden und Einloggen…");
Properties props = imapProps();
Session session = Session.getInstance(props);
session = Session.getInstance(props);
Store store = session.getStore(Constants.IMAPS);
store.connect(host,username,password);
LOG.debug("Verbunden. Öffne {}:",folderName);
@ -75,6 +76,8 @@ public class ImapClient { @@ -75,6 +76,8 @@ public class ImapClient {
for (Message message : inbox.getMessages()){
if (message.isSet(Flags.Flag.SEEN)) continue;
handle(message);
Folder folder = message.getFolder();
if (!folder.isOpen()) folder.open(Folder.READ_WRITE);
message.setFlag(Flags.Flag.SEEN,true);
}
}
@ -158,6 +161,21 @@ public class ImapClient { @@ -158,6 +161,21 @@ public class ImapClient {
return folderName;
}
public ImapClient move(Message message, String destinationFolder) throws MessagingException {
if (listeningThread == null || listeningThread.stopped) throw new IllegalStateException("IMAP client not connected!");
var source = message.getFolder();
if (!source.isOpen()) source.open(Folder.READ_WRITE);
var messages = new Message[]{message};
var store = source.getStore();
var dest = store.getFolder(new URLName(destinationFolder));
if (!dest.exists()) dest.create(Folder.HOLDS_MESSAGES);
source.copyMessages(messages,dest);
source.setFlags(messages, new Flags(Flags.Flag.DELETED), true);
source.close(true);
return this;
}
public ImapClient start() {
stop();

49
src/main/java/de/srsoftware/widerhall/mail/SmtpClient.java

@ -30,21 +30,49 @@ public class SmtpClient { @@ -30,21 +30,49 @@ public class SmtpClient {
this.port = port;
}
public void bccForward(String from, Message message, List<String> emails) throws MessagingException {
public void forward(String newSender, List<String> receivers, Message message, String subject, boolean forwardAsAttachment, boolean bcc, String replyTo) throws MessagingException {
if (session == null) login();
MimeMessage forward = new MimeMessage(session);
forward.setFrom(from);
forward.setRecipients(Message.RecipientType.BCC,InternetAddress.parse(String.join(", ",emails)));
forward.setSubject(message.getSubject());
var oldSender = message.getFrom()[0].toString();
if (newSender != null){
var pos = subject.indexOf(" (from ");
while (pos > 0){
var end = subject.indexOf(')',pos);
if (end < pos) break;
subject = (subject.substring(0,pos)+subject.substring(end+1)).trim();
pos = subject.indexOf(" (from ");
}
forward.setFrom(newSender);
forward.setSubject(subject+" (from "+oldSender+")");
} else {
forward.setFrom(oldSender);
forward.setSubject(subject);
}
if (replyTo != null) forward.setReplyTo(InternetAddress.parse(replyTo));
var recipientType = bcc ? Message.RecipientType.BCC : Message.RecipientType.TO;
forward.setRecipients(recipientType,InternetAddress.parse(String.join(", ",receivers)));
MimeMultipart multipart = new MimeMultipart();
MimeBodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(message.getDataHandler());
multipart.addBodyPart(messageBodyPart);
if (forwardAsAttachment){
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setText("Find the forwarded message in the attachment(s)!\n");
multipart.addBodyPart(bodyPart);
// create another body part to contain the message to be forwarded
bodyPart = new MimeBodyPart();
// forwardedMsg is the MimeMessage object you want to forward as an attachment
bodyPart.setContent(message, "message/rfc822");
bodyPart.setDisposition(Part.ATTACHMENT);
multipart.addBodyPart(bodyPart);
} else {
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setDataHandler(message.getDataHandler());
multipart.addBodyPart(bodyPart);
}
forward.setContent(multipart);
send(forward);
}
@ -64,6 +92,7 @@ public class SmtpClient { @@ -64,6 +92,7 @@ public class SmtpClient {
}
public void send(String senderAdress, String senderName, String receivers, String subject, String content) throws MessagingException, UnsupportedEncodingException {
login();
MimeMessage message = new MimeMessage(session);
message.addHeader("Content-Type","text/plain; charset="+UTF8);
message.addHeader("format","flowed");

22
src/main/java/de/srsoftware/widerhall/web/Rest.java

@ -3,6 +3,7 @@ package de.srsoftware.widerhall.web; @@ -3,6 +3,7 @@ package de.srsoftware.widerhall.web;
import de.srsoftware.widerhall.Util;
import de.srsoftware.widerhall.data.ListMember;
import de.srsoftware.widerhall.data.MailingList;
import de.srsoftware.widerhall.data.Post;
import de.srsoftware.widerhall.data.User;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
@ -27,6 +28,7 @@ import static de.srsoftware.widerhall.Util.t; @@ -27,6 +28,7 @@ import static de.srsoftware.widerhall.Util.t;
public class Rest extends HttpServlet {
private static final Logger LOG = LoggerFactory.getLogger(Rest.class);
private static final String LIST_ARCHIVE = "list/archive";
private static final String LIST_DISABLE = "list/disable";
private static final String LIST_EDITABLE = "list/editable";
private static final String LIST_DETAIL = "list/detail";
@ -127,6 +129,9 @@ public class Rest extends HttpServlet { @@ -127,6 +129,9 @@ public class Rest extends HttpServlet {
}
} else {
switch (path) {
case LIST_ARCHIVE:
json.put("archive",archive(req));
break;
case LIST_SUBSCRIBABLE:
json.put("lists", MailingList.subscribable().stream().map(MailingList::minimalMap).toList());
break;
@ -143,6 +148,19 @@ public class Rest extends HttpServlet { @@ -143,6 +148,19 @@ public class Rest extends HttpServlet {
}
}
private Map<Long, Object> archive(HttpServletRequest req) {
var list = Util.getMailingList(req);
if (list != null){
try {
return Post.find(list).stream().collect(Collectors.toMap(Post::timestamp,Post::safeMap));
} catch (SQLException e) {
e.printStackTrace();
}
}
LOG.debug("list: {}",list.email());
return Map.of();
}
public String handlePost(HttpServletRequest req, HttpServletResponse resp){
var user = Util.getUser(req);
@ -220,6 +238,10 @@ public class Rest extends HttpServlet { @@ -220,6 +238,10 @@ public class Rest extends HttpServlet {
var map = new HashMap<>();
if (list.hasState(MailingList.STATE_FORWARD_FROM)) map.put("forward_from",true);
if (list.hasState(MailingList.STATE_FORWARD_ATTACHED)) map.put("forward_attached",true);
if (list.hasState(MailingList.STATE_HIDE_RECEIVERS)) map.put("hide_receivers",true);
if (list.hasState(MailingList.STATE_REPLY_TO_LIST)) map.put("reply_to_list",true);
if (list.hasState(MailingList.STATE_OPEN)) map.put("open",true);
if (list.hasState(MailingList.STATE_PUBLIC_ARCHIVE)) map.put("archive",true);
return map;
}

2
src/main/java/de/srsoftware/widerhall/web/TemplateServlet.java

@ -8,6 +8,7 @@ import javax.servlet.http.HttpServlet; @@ -8,6 +8,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Map;
@ -40,6 +41,7 @@ public abstract class TemplateServlet extends HttpServlet { @@ -40,6 +41,7 @@ public abstract class TemplateServlet extends HttpServlet {
var template = templates.getInstanceOf(path);
if (template != null){
try {
resp.setCharacterEncoding("UTF-8");
template.add("data",data);
resp.getWriter().println(template.render());
return null;

41
src/main/java/de/srsoftware/widerhall/web/Web.java

@ -3,6 +3,7 @@ package de.srsoftware.widerhall.web; @@ -3,6 +3,7 @@ package de.srsoftware.widerhall.web;
import de.srsoftware.widerhall.Util;
import de.srsoftware.widerhall.data.ListMember;
import de.srsoftware.widerhall.data.MailingList;
import de.srsoftware.widerhall.data.Post;
import de.srsoftware.widerhall.data.User;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
@ -12,6 +13,7 @@ import javax.mail.MessagingException; @@ -12,6 +13,7 @@ import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.sql.SQLException;
import java.util.HashMap;
@ -26,9 +28,11 @@ public class Web extends TemplateServlet { @@ -26,9 +28,11 @@ public class Web extends TemplateServlet {
private static final String CONFIRM = "confirm";
private static final Logger LOG = LoggerFactory.getLogger(Web.class);
private static final String ADMIN = "admin";
private static final String ARCHIVE = "archive";
private static final String INSPECT = "inspect";
private static final String LOGIN = "login";
private static final String LOGOUT = "logout";
private static final String POST = "post";
private static final String REGISTER = "register";
private static final String RELOAD = "reload";
private static final String SUBSCRIBE = "subscribe";
@ -130,6 +134,12 @@ public class Web extends TemplateServlet { @@ -130,6 +134,12 @@ public class Web extends TemplateServlet {
}
}
private String archive(HttpServletRequest req, HttpServletResponse resp) {
var domain = req.getParameter(DOMAIN);
var prefix = req.getParameter(PREFIX);
return loadTemplate(ARCHIVE,Map.of(DOMAIN,domain,PREFIX,prefix),resp);
}
private String confirm(HttpServletRequest req, HttpServletResponse resp) {
try {
var token = req.getParameter(TOKEN);
@ -181,8 +191,12 @@ public class Web extends TemplateServlet { @@ -181,8 +191,12 @@ public class Web extends TemplateServlet {
var list = MailingList.load(listEmail);
if (list != null) data.put(LIST,list.minimalMap());
switch (path){
case ARCHIVE:
return archive(req,resp);
case CONFIRM:
return confirm(req,resp);
case POST:
return post(req,resp);
case RELOAD:
loadTemplates();
data.put(NOTES,t("Vorlagen wurden neu geladen"));
@ -223,8 +237,6 @@ public class Web extends TemplateServlet { @@ -223,8 +237,6 @@ public class Web extends TemplateServlet {
return redirectTo(LOGIN,resp);
}
private String handleLogin(HttpServletRequest req, HttpServletResponse resp) {
var email = req.getParameter("email");
var pass = req.getParameter("pass");
@ -288,21 +300,38 @@ public class Web extends TemplateServlet { @@ -288,21 +300,38 @@ public class Web extends TemplateServlet {
}
if (!error){
var dummy = req.getParameterMap();
try {
list.forwardFrom(Util.getCheckbox(req, "forward_from"));
list.forwardAttached(Util.getCheckbox(req, "forward_attached"));
list.forwardFrom(Util.getCheckbox(req, "forward_from"))
.forwardAttached(Util.getCheckbox(req, "forward_attached"))
.hideReceivers(Util.getCheckbox(req, "hide_receivers"))
.replyToList(Util.getCheckbox(req, "reply_to_list"))
.open(Util.getCheckbox(req,"open"))
.archive(Util.getCheckbox(req,"archive"));
data.put(NOTES,t("Mailing-Liste aktualisiert!"));
} catch (SQLException e){
LOG.warn("Aktualisierung der Mailing-Liste fehlgeschlagen:",e);
data.put(ERROR,t("Aktualisierung der Mailing-Liste fehlgeschlagen!"));
}
LOG.debug("params: {}",dummy);
}
return loadTemplate(INSPECT,data,resp);
}
private String post(HttpServletRequest req, HttpServletResponse resp) {
var id = req.getParameter(ID);
if (id == null) return t("Could not find email with id!");
try {
var post = Post.load(id);
var map = new HashMap<String,Object>();
map.putAll(post.safeMap());
String content = Files.readString(post.file().toPath());
map.put("text",Util.dropEmail(content));
return loadTemplate("post",map,resp);
} catch (SQLException | IOException e) {
LOG.debug("Failed to load post from file!",e);
return t("Failed to load post from file!");
}
}
private String redirectTo(String page, HttpServletResponse resp) {
try {

26
static/templates/archive.st

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="jquery"></script>
<script src="js"></script>
<link rel="stylesheet" href="css" />
</head>
<body>
«navigation()»
«userinfo()»
«messages()»
<h1>Widerhall List Archive</h1>
<table id="archive">
<tr>
<th>Date</th>
<th>From</th>
<th>Subject</th>
</tr>
</table>
«footer()»
</body>
<script type="text/javascript">
loadArchive('«data.domain»','«data.prefix»');
</script>
</html>

16
static/templates/inspect.st

@ -19,10 +19,26 @@ @@ -19,10 +19,26 @@
<input type="checkbox" name="forward_from">
Ursprünglichen Absender beim Weiterleiten verwenden
</label>
<label>
<input type="checkbox" name="reply_to_list">
Set list adddress in "ReplyTo" header
</label>
<label>
<input type="checkbox" name="open">
Allow non-members to send mails via list (Danger!)
</label>
<label>
<input type="checkbox" name="forward_attached">
Ursprüngliche Nachricht beim Weiterleiten als Anhang versenden
</label>
<label>
<input type="checkbox" name="hide_receivers">
Hide receivers (using BCC)
</label>
<label>
<input type="checkbox" name="archive">
Collect messages in public archive
</label>
<button type="submit">Speichern</button>
</fieldset>
</form>

25
static/templates/js.st

@ -27,6 +27,11 @@ function hideList(listEmail){ @@ -27,6 +27,11 @@ function hideList(listEmail){
$.post('/api/list/hide',{list:listEmail},showListResult,'json');
}
function loadArchive(domain,prefix){
let listEmail = prefix+'@'+domain;
$.get('/api/list/archive?list='+listEmail,showListArchive,'json');
}
function loadListDetail(listEmail){
$.post('/api/list/detail',{list:listEmail},showListDetail,'json');
}
@ -55,10 +60,26 @@ function showList(listEmail){ @@ -55,10 +60,26 @@ function showList(listEmail){
$.post('/api/list/show',{list:listEmail},showListResult,'json');
}
function showListArchive(data){
for (let time in data.archive){
let post = data.archive[time];
let row = $('<tr/>');
var url = 'post?id='+post.id;
$('<td/>').html('<a href="'+url+'">'+new Date(post.date)+'</a>').appendTo(row);
$('<td/>').html('<a href="'+url+'">'+post.from_name+'</a>').appendTo(row);
$('<td/>').html('<a href="'+url+'">'+post.subject+'</a>').appendTo(row);
row.appendTo($('#archive'));
console.log(post);
}
}
function showListDetail(data){
console.log(data);
if (data.forward_from) $('input[name="forward_from"]').prop('checked',true);
if (data.forward_attached) $('input[name="forward_attached"]').prop('checked',true);
if (data.hide_receivers) $('input[name="hide_receivers"]').prop('checked',true);
if (data.reply_to_list) $('input[name="reply_to_list"]').prop('checked',true);
if (data.open) $('input[name="open"]').prop('checked',true);
if (data.archive) $('input[name="archive"]').prop('checked',true);
}
function showListOfEditableLists(data){
@ -109,7 +130,7 @@ function showListList(data){ @@ -109,7 +130,7 @@ function showListList(data){
for (let i in data.lists){
let list = data.lists[i];
let row = $('<tr/>');
$('<td/>').text(list.name).appendTo(row);
$('<td/>').html('<a href="archive?prefix='+list.email.prefix+'&domain='+list.email.domain+'">'+list.name+'</a>').appendTo(row);
$('<td/>',{class:'right'}).text(list.email.prefix).appendTo(row);
$('<td/>',{class:'right'}).text('@').appendTo(row);
$('<td/>').text(list.email.domain).appendTo(row);

34
static/templates/post.st

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="jquery"></script>
<script src="js"></script>
<link rel="stylesheet" href="css" />
</head>
<body>
«navigation()»
«userinfo()»
«messages()»
<h1>Widerhall Archive:</h1>
<table id="post">
<tr>
<th>Date</th>
<td>«data.date»</td>
</tr>
<tr>
<th>From</th>
<td>«data.from_name»</td>
</tr>
<tr>
<th>Subject</th>
<td>«data.subject»</td>
</tr>
<tr>
<th>Content</th>
<td><pre>«data.text»</pre></td>
</tr>
</table>
«footer()»
</body>
</html>
Loading…
Cancel
Save