Compare commits

...

3 Commits

Author SHA1 Message Date
Stephan Richter 3c54315c6c fixed typos 2 months ago
Stephan Richter 49ecacc797 fixed texts 2 months ago
Stephan Richter 9e363cc0e8 sorted methods alphabetically 2 months ago
  1. 2
      pom.xml
  2. 93
      src/main/java/de/srsoftware/widerhall/Configuration.java
  3. 151
      src/main/java/de/srsoftware/widerhall/Util.java
  4. 48
      src/main/java/de/srsoftware/widerhall/data/Database.java
  5. 79
      src/main/java/de/srsoftware/widerhall/data/ListMember.java
  6. 12
      src/main/java/de/srsoftware/widerhall/data/MailingList.java
  7. 3
      src/main/java/de/srsoftware/widerhall/data/User.java
  8. 122
      src/main/java/de/srsoftware/widerhall/mail/ImapClient.java
  9. 3
      src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java
  10. 41
      src/main/java/de/srsoftware/widerhall/mail/SmtpClient.java
  11. 50
      src/main/java/de/srsoftware/widerhall/web/Rest.java
  12. 2
      src/main/java/de/srsoftware/widerhall/web/TemplateServlet.java
  13. 20
      src/main/java/de/srsoftware/widerhall/web/Web.java
  14. 2
      static/templates/footer.st

2
pom.xml

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>Widerhall</artifactId>
<version>1.0.1</version>
<version>1.0.2</version>
<build>
<plugins>
<plugin>

93
src/main/java/de/srsoftware/widerhall/Configuration.java

@ -17,6 +17,45 @@ public class Configuration { @@ -17,6 +17,45 @@ public class Configuration {
private JSONObject data;
private File file;
public File archiveDir() {
var locations = locations();
if (!locations.containsKey(ARCHIVE)) locations.put(ARCHIVE, String.join(File.separator,baseDir(),"archive"));
return new File((String) locations.get(ARCHIVE));
}
public String baseDir() {
var locations = locations();
if (!locations.containsKey(BASE)) locations.put(BASE,System.getProperty("user.dir"));
return (String) locations.get(BASE);
}
public String baseUrl() {
if (!data.containsKey(BASE_URL)) data.put(BASE_URL,"http://localhost");
return (String) data.get(BASE_URL);
}
public File configFile() {
var locations = locations();
if (!locations.containsKey(CONFIG)) locations.put(CONFIG, String.join(File.separator,baseDir(),"config","config.json"));
return new File((String) locations.get(CONFIG));
}
public File dbFile() {
var locations = locations();
if (!locations.containsKey(DB)) locations.put(DB, String.join(File.separator,baseDir(),"db","db.sqlite3"));
return new File((String) locations.get(DB));
}
public Configuration dbFile(File dbFile){
var locations = locations();
locations.put(DB,dbFile.toString());
return this;
}
public File file() {
return file;
}
public static Configuration instance() {
if (singleton == null) singleton = new Configuration().setDefaults();
return singleton;
@ -42,6 +81,12 @@ public class Configuration { @@ -42,6 +81,12 @@ public class Configuration {
return this;
}
private JSONObject locations() {
Object o = data.get(LOCATIONS);
if (!(o instanceof JSONObject)) data.put(LOCATIONS,o = new JSONObject());
return (JSONObject) o;
}
public Configuration save(File file) throws IOException {
this.file = file;
return save();
@ -64,57 +109,9 @@ public class Configuration { @@ -64,57 +109,9 @@ public class Configuration {
return this;
}
private JSONObject locations() {
Object o = data.get(LOCATIONS);
if (!(o instanceof JSONObject)) data.put(LOCATIONS,o = new JSONObject());
return (JSONObject) o;
}
public String baseDir() {
var locations = locations();
if (!locations.containsKey(BASE)) locations.put(BASE,System.getProperty("user.dir"));
return (String) locations.get(BASE);
}
public File configFile() {
var locations = locations();
if (!locations.containsKey(CONFIG)) locations.put(CONFIG, String.join(File.separator,baseDir(),"config","config.json"));
return new File((String) locations.get(CONFIG));
}
public File dbFile() {
var locations = locations();
if (!locations.containsKey(DB)) locations.put(DB, String.join(File.separator,baseDir(),"db","db.sqlite3"));
return new File((String) locations.get(DB));
}
public Configuration dbFile(File dbFile){
var locations = locations();
locations.put(DB,dbFile.toString());
return this;
}
public File archiveDir() {
var locations = locations();
if (!locations.containsKey(ARCHIVE)) locations.put(ARCHIVE, String.join(File.separator,baseDir(),"archive"));
return new File((String) locations.get(ARCHIVE));
}
public int serverPort() {
if (!data.containsKey(PORT)) data.put(PORT,80L);
var o = data.get(PORT);
return (int) (long) o;
}
public String baseUrl() {
if (!data.containsKey(BASE_URL)) data.put(BASE_URL,"http://localhost");
return (String) data.get(BASE_URL);
}
public File file() {
return file;
}
}

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

@ -28,19 +28,33 @@ public class Util { @@ -28,19 +28,33 @@ public class Util {
private static final MessageDigest SHA256 = getSha256();
private static final String EMAIL_PATTERN = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$";
public static String urlEncode(Map<String, Object> data) {
String params = data.entrySet()
.stream()
.map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue()))
.collect(Collectors.joining("&"));
return params;
public static String dropEmail(String tx) {
return tx.replaceAll( "[.\\-\\w]+@[.\\-\\w]+", "[email_removed]");
}
private static String encode(Object value) {
return URLEncoder.encode(value.toString(), StandardCharsets.UTF_8);
}
public static boolean getCheckbox(HttpServletRequest req, String key) {
return "on".equals(req.getParameter(key));
}
public static MailingList getMailingList(HttpServletRequest req) {
var listEmail = req.getParameter(LIST);
if (listEmail == null || listEmail.isBlank()) return null;
return MailingList.load(listEmail);
}
public static <T> T getNullable(ResultSet rs, String colName) throws SQLException {
final T val = (T) rs.getObject(colName);
return rs.wasNull() ? null : val;
}
public static String getPath(HttpServletRequest req) {
var path = req.getPathInfo();
return path == null ? INDEX : path.substring(1);
}
public static MessageDigest getSha256() {
try {
return MessageDigest.getInstance("SHA-256");
@ -50,9 +64,43 @@ public class Util { @@ -50,9 +64,43 @@ public class Util {
}
}
public static String sha256(String s) {
byte[] bytes = SHA256.digest(s.getBytes(StandardCharsets.UTF_8));
return hex(bytes);
/**
* 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 User getUser(HttpServletRequest req) {
var o = req.getSession().getAttribute(USER);
return o instanceof User ? (User) o : null;
}
private static String hex(byte[] bytes) {
@ -67,14 +115,15 @@ public class Util { @@ -67,14 +115,15 @@ public class Util {
return (char)(upper < 10 ? '0'+upper : 'A'+upper-10)+""+(char)(lower < 10 ? '0'+lower : 'A'+lower-10);
}
public static String t(String tx, Object ... fills){
return Translation.get(Application.class,tx,fills);
}
public static boolean isEmail(String email) {
return email.matches(EMAIL_PATTERN);
}
public static String sha256(String s) {
byte[] bytes = SHA256.digest(s.getBytes(StandardCharsets.UTF_8));
return hex(bytes);
}
public static boolean simplePassword(String pass) {
if (pass.length() < 6) return true;
if (pass.length() < 8){
@ -98,75 +147,23 @@ public class Util { @@ -98,75 +147,23 @@ public class Util {
return false;
}
public static int unset(int value, int...flags) {
for (int flag : flags){
if ((value & flag) > 0) value ^= flag;
}
return value;
}
public static User getUser(HttpServletRequest req) {
var o = req.getSession().getAttribute(USER);
return o instanceof User ? (User) o : null;
}
public static String getPath(HttpServletRequest req) {
var path = req.getPathInfo();
return path == null ? INDEX : path.substring(1);
}
public static MailingList getMailingList(HttpServletRequest req) {
var listEmail = req.getParameter(LIST);
if (listEmail == null || listEmail.isBlank()) return null;
return MailingList.load(listEmail);
public static String t(String tx, Object ... fills){
return Translation.get(Application.class,tx,fills);
}
public static boolean getCheckbox(HttpServletRequest req, String key) {
return "on".equals(req.getParameter(key));
public static String urlEncode(Map<String, Object> data) {
String params = data.entrySet()
.stream()
.map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue()))
.collect(Collectors.joining("&"));
return params;
}
/**
* 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;
}
public static int unset(int value, int...flags) {
for (int flag : flags){
if ((value & flag) > 0) value ^= flag;
}
return null;
}
public static String dropEmail(String tx) {
return tx.replaceAll( "[.\\-\\w]+@[.\\-\\w]+", "[email_removed]");
}
public static <T> T getNullable(ResultSet rs, String colName) throws SQLException {
final T val = (T) rs.getObject(colName);
return rs.wasNull() ? null : val;
return value;
}
}

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

@ -137,30 +137,6 @@ public class Database { @@ -137,30 +137,6 @@ public class Database {
if (!sortFields.isEmpty()) sql.append(" ORDER BY ").append(String.join(", ",sortFields));
}
@Override
protected Request clone() {
Request clone = new Request(new StringBuilder(sql));
clone.where.putAll(where);
clone.values.putAll(values);
return clone;
}
/**
* finalize sql, save sql and arguments as compiled request
* @return
*/
public CompiledRequest compile(Object ...additionalArgs){
var args = new ArrayList<>();
applyValues(args);
applyConditions(args);
applyGrouping();
applySorting();
if (additionalArgs != null) {
for (Object arg : additionalArgs) args.add(arg);
}
return new CompiledRequest(sql.toString(),args);
}
/**
* apply values (for insert or update statements)
* @param args
@ -193,6 +169,30 @@ public class Database { @@ -193,6 +169,30 @@ public class Database {
}
}
@Override
protected Request clone() {
Request clone = new Request(new StringBuilder(sql));
clone.where.putAll(where);
clone.values.putAll(values);
return clone;
}
/**
* finalize sql, save sql and arguments as compiled request
* @return
*/
public CompiledRequest compile(Object ...additionalArgs){
var args = new ArrayList<>();
applyValues(args);
applyConditions(args);
applyGrouping();
applySorting();
if (additionalArgs != null) {
for (Object arg : additionalArgs) args.add(arg);
}
return new CompiledRequest(sql.toString(),args);
}
public Request groupBy(String column) {
groupBy = column;
return this;

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

@ -50,13 +50,13 @@ public class ListMember { @@ -50,13 +50,13 @@ public class ListMember {
}
public String addNewModerator(String userEmail) {
if (!isAllowedToEditMods()) return t("You are not allowed to nominate new mods for {}",list.email());
if (!isAllowedToEditMods()) return t("You are not allowed to nominate new mods for {}!",list.email());
User moderator = null;
try {
moderator = User.load(userEmail);
} catch (SQLException e) {
LOG.warn("Failed to load user for {}",userEmail,e);
return t("Failed to load user for {}",userEmail);
LOG.warn("Failed to load user for {}!",userEmail,e);
return t("Failed to load user for {}!",userEmail);
}
if (moderator == null) return t("No such user: {}",userEmail);
@ -64,8 +64,8 @@ public class ListMember { @@ -64,8 +64,8 @@ public class ListMember {
try {
member = ListMember.load(list,moderator);
} catch (SQLException e) {
LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e);
return t("Failed to load list member for {}/{}",moderator.email(),list.email());
LOG.warn("Failed to load list member for {}/{}!",moderator.email(),list.email(),e);
return t("Failed to load list member for {}/{}!",moderator.email(),list.email());
}
try {
if (member == null) {
@ -112,20 +112,6 @@ public class ListMember { @@ -112,20 +112,6 @@ public class ListMember {
return null;
}
public void sendConfirmationMail(ST template) throws SQLException, MessagingException {
var subject = t("[{}] Subscription complete!",list.name());
var data = new HashMap<String,Object>();
data.put(USER,user.safeMap());
data.put(LIST,list.minimalMap());
data.put(URL,Configuration.instance().baseUrl()+"/web/index");
if (list.isOpenForSubscribers()) data.put("open_list",true);
var text = template.add("data",data).render();
try {
list.smtp().send(list.email(),list.name(),user.email(),subject,text);
} catch (UnsupportedEncodingException e) {
LOG.warn("Failed to send list subscription confirmation!",e);
}
}
/**
* Create a new list member entry in the database.
* If the member has the state AWAITING_CONFIRMATION, a token is assigned with the member, too.
@ -160,13 +146,13 @@ public class ListMember { @@ -160,13 +146,13 @@ public class ListMember {
}
public String dropMember(String userEmail) {
if (!isModerator()) return t("You are not allowed to remove members of {}",list.email());
if (!isModerator()) return t("You are not allowed to remove members of {}!",list.email());
User user = null;
try {
user = User.load(userEmail);
} catch (SQLException e) {
LOG.warn("Failed to load user for {}",userEmail,e);
return t("Failed to load user for {}",userEmail);
LOG.warn("Failed to load user for {}!",userEmail,e);
return t("Failed to load user for {}!",userEmail);
}
if (user == null) return t("No such user: {}",userEmail);
@ -174,30 +160,30 @@ public class ListMember { @@ -174,30 +160,30 @@ public class ListMember {
try {
member = ListMember.load(list,user);
} catch (SQLException e) {
LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e);
return t("Failed to load list member for {}/{}",user.email(),list.email());
LOG.warn("Failed to load list member for {}/{}!",user.email(),list.email(),e);
return t("Failed to load list member for {}/{}!",user.email(),list.email());
}
if (member == null) return t("{} is no member of {}",user.email(),list.email());
if (member.isOwner()) return t("You are not allowed to remvoe the list owner!");
if (member == null) return t("{} is no member of {}!",user.email(),list.email());
if (member.isOwner()) return t("You are not allowed to remove the list owner!");
try {
member.unsubscribe();
} catch (SQLException e) {
LOG.warn("Failed to un-subscribe {} from {}",user.email(),list.email(),e);
return t("Failed to un-subscribe {} from {}",user.email(),list.email());
LOG.warn("Failed to un-subscribe {} from {}!",user.email(),list.email(),e);
return t("Failed to un-subscribe {} from {}!",user.email(),list.email());
}
return null;
}
public String dropModerator(String userEmail) {
if (!isAllowedToEditMods()) return t("You are not allowed to edit mods of {}",list.email());
if (!isAllowedToEditMods()) return t("You are not allowed to edit mods of {}!",list.email());
User moderator = null;
try {
moderator = User.load(userEmail);
} catch (SQLException e) {
LOG.warn("Failed to load user for {}",userEmail,e);
return t("Failed to load user for {}",userEmail);
LOG.warn("Failed to load user for {}!",userEmail,e);
return t("Failed to load user for {}!",userEmail);
}
if (moderator == null) return t("No such user: {}",userEmail);
@ -205,8 +191,8 @@ public class ListMember { @@ -205,8 +191,8 @@ public class ListMember {
try {
member = ListMember.load(list,moderator);
} catch (SQLException e) {
LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e);
return t("Failed to load list member for {}/{}",moderator.email(),list.email());
LOG.warn("Failed to load list member for {}/{}!",moderator.email(),list.email(),e);
return t("Failed to load list member for {}/{}!",moderator.email(),list.email());
}
try {
if (member == null) {
@ -215,8 +201,8 @@ public class ListMember { @@ -215,8 +201,8 @@ public class ListMember {
member.setState(Util.unset(member.state,STATE_MODERATOR));
}
} catch (SQLException e) {
LOG.warn("Failed to make {} a subscriber of {}",moderator.email(),list.email(),e);
return t("Failed to make {} a subscriber of {}",moderator.email(),list.email());
LOG.warn("Failed to make {} a subscriber of {}!",moderator.email(),list.email(),e);
return t("Failed to make {} a subscriber of {}!",moderator.email(),list.email());
}
return null;
}
@ -374,6 +360,21 @@ public class ListMember { @@ -374,6 +360,21 @@ public class ListMember {
return this;
}
public void sendConfirmationMail(ST template) throws SQLException, MessagingException {
var subject = t("[{}] Subscription complete!",list.name());
var data = new HashMap<String,Object>();
data.put(USER,user.safeMap());
data.put(LIST,list.minimalMap());
data.put(URL,Configuration.instance().baseUrl()+"/web/index");
if (list.isOpenForSubscribers()) data.put("open_list",true);
var text = template.add("data",data).render();
try {
list.smtp().send(list.email(),list.name(),user.email(),subject,text);
} catch (UnsupportedEncodingException e) {
LOG.warn("Failed to send list subscription confirmation!",e);
}
}
public ListMember setState(int newState) throws SQLException {
Database.open()
.update(TABLE_NAME)
@ -391,10 +392,10 @@ public class ListMember { @@ -391,10 +392,10 @@ public class ListMember {
*/
public String stateText() {
var words = new ArrayList<String>();
if (isAwaiting()) words.add("awaiting confirmation");
if (isModerator()) words.add("moderator");
if (isOwner()) words.add("owner");
if (isSubscriber()) words.add("subscriber");
if (isAwaiting()) words.add(t("awaiting confirmation"));
if (isModerator()) words.add(t("moderator"));
if (isOwner()) words.add(t("owner"));
if (isSubscriber()) words.add(t("subscriber"));
return String.join(", ",words);
}

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

@ -270,7 +270,7 @@ public class MailingList implements MessageHandler, ProblemListener { @@ -270,7 +270,7 @@ public class MailingList implements MessageHandler, ProblemListener {
var member = ListMember.load(this,user);
return member.hasState(ListMember.STATE_OWNER|ListMember.STATE_SUBSCRIBER); // owners may subscribe their own mailing lists
} catch (SQLException e) {
LOG.warn("Was not able to load ListMember: ",e);
LOG.warn("Was not able to load ListMember:",e);
return false;
}
}
@ -303,7 +303,7 @@ public class MailingList implements MessageHandler, ProblemListener { @@ -303,7 +303,7 @@ public class MailingList implements MessageHandler, ProblemListener {
ml.lastError = rs.getString(LAST_ERROR);
}
} catch (SQLException e) {
LOG.debug("Failed to load MailingList: ",e);
LOG.debug("Failed to load MailingList:",e);
}
return ml;
}
@ -325,7 +325,7 @@ public class MailingList implements MessageHandler, ProblemListener { @@ -325,7 +325,7 @@ public class MailingList implements MessageHandler, ProblemListener {
.compile().exec();
while (rs.next()) list.add(MailingList.from(rs));
} catch (SQLException e) {
LOG.debug("Failed to load MailingLists: ",e);
LOG.debug("Failed to load MailingLists:",e);
}
return list;
}
@ -671,11 +671,11 @@ public class MailingList implements MessageHandler, ProblemListener { @@ -671,11 +671,11 @@ public class MailingList implements MessageHandler, ProblemListener {
try {
var config = Configuration.instance();
var url = new StringBuilder(config.baseUrl()).append("/web/confirm?token=").append(member.token()).toString();
var subject = t("[{}] Please confirm your list subscription",name());
var subject = t("[{}] Please confirm your list subscription!",name());
var text = template.add(URL,url).add(LIST_NAME,name()).render();
smtp.send(email(),name(),user.email(),subject,text);
} catch (UnsupportedEncodingException e) {
throw new MessagingException(t("Failed to send email to {}",user.email()),e);
throw new MessagingException(t("Failed to send email to {}!",user.email()),e);
}
}
@ -711,7 +711,7 @@ public class MailingList implements MessageHandler, ProblemListener { @@ -711,7 +711,7 @@ public class MailingList implements MessageHandler, ProblemListener {
* @throws UnsupportedEncodingException
*/
public void test(User user) throws MessagingException, UnsupportedEncodingException {
var subject = t("[{}]: test mail",name());
var subject = t("[{}] test mail",name());
var text = "If you received this mail, the SMTP settings of your mailing list are correct.";
smtp.login().send(email(),name(),user.email(),subject,text);
}

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

@ -9,6 +9,7 @@ import java.time.LocalDate; @@ -9,6 +9,7 @@ import java.time.LocalDate;
import java.util.*;
import static de.srsoftware.widerhall.Constants.*;
import static de.srsoftware.widerhall.Util.t;
/**
* @author Stephan Richter
@ -268,7 +269,7 @@ public class User { @@ -268,7 +269,7 @@ public class User {
* @return
*/
public Map<String,String> safeMap(){
return Map.of(NAME,name,EMAIL,email,PERMISSIONS,permissionList(),PASSWORD,hashedPassword() == null ? "no" : "yes");
return Map.of(NAME,name,EMAIL,email,PERMISSIONS,permissionList(),PASSWORD,t(hashedPassword() == null ? "no" : "yes"));
}
/**

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

@ -33,28 +33,40 @@ public class ImapClient { @@ -33,28 +33,40 @@ public class ImapClient {
return this;
}
public ListeningThread doStop() {
stopped = true;
return this;
}
public ListeningThread dropListeners() {
listeners.clear();
return this;
}
@Override
public void run() {
while (!stopped) {
try {
sleep(5000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
try {
openInbox();
} catch (MessagingException e){
LOG.warn("Connection problem:",e);
problemListener.onImapException(e);
}
private void handle(Message message) throws MessagingException {
LOG.debug("Handling {}",message.getSubject());
for (MessageHandler listener : listeners) listener.onMessageReceived(message);
}
private void handleMessages() throws MessagingException {
LOG.debug("Reading email of {}:",username);
if (!inbox.isOpen()) inbox.open(IMAPFolder.READ_WRITE);
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);
}
}
private Properties imapProps() {
Properties props = new Properties();
props.put(Constants.PROTOCOL,Constants.IMAPS);
return props;
}
private void openInbox() throws MessagingException {
LOG.debug("Connecting and logging in…");
Properties props = imapProps();
@ -73,40 +85,33 @@ public class ImapClient { @@ -73,40 +85,33 @@ public class ImapClient {
}
}
private void handleMessages() throws MessagingException {
LOG.debug("Reading email of {}:",username);
if (!inbox.isOpen()) inbox.open(IMAPFolder.READ_WRITE);
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);
@Override
public void run() {
while (!stopped) {
try {
sleep(5000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
try {
openInbox();
} catch (MessagingException e){
LOG.warn("Connection problem:",e);
problemListener.onImapException(e);
}
}
}
private void handle(Message message) throws MessagingException {
LOG.debug("Handling {}",message.getSubject());
for (MessageHandler listener : listeners) listener.onMessageReceived(message);
}
private Properties imapProps() {
Properties props = new Properties();
props.put(Constants.PROTOCOL,Constants.IMAPS);
return props;
}
public ListeningThread doStop() {
stopped = true;
return this;
}
}
private class Heartbeat extends Thread{
private static final Logger LOG = LoggerFactory.getLogger(Heartbeat.class);
private boolean stopped = false;
public Heartbeat doStop() {
stopped = true;
return this;
}
@Override
public void run() {
while (!stopped){
@ -123,11 +128,6 @@ public class ImapClient { @@ -123,11 +128,6 @@ public class ImapClient {
}
}
}
public Heartbeat doStop() {
stopped = true;
return this;
}
}
public ImapClient(String host, int port, String username, String password, String folderName,ProblemListener listener) {
@ -165,25 +165,13 @@ public class ImapClient { @@ -165,25 +165,13 @@ public class ImapClient {
inbox.expunge();
}
public String host(){
return host;
}
public String username(){
return username;
}
public String password(){
return password;
public String folderName(){
return folderName;
}
public int port(){
return port;
}
public String folderName(){
return folderName;
public String host(){
return host;
}
public ImapClient move(Message message, String destinationFolder) throws MessagingException {
@ -200,6 +188,14 @@ public class ImapClient { @@ -200,6 +188,14 @@ public class ImapClient {
return this;
}
public String password(){
return password;
}
public int port(){
return port;
}
public ImapClient start() {
stop();
@ -225,4 +221,8 @@ public class ImapClient { @@ -225,4 +221,8 @@ public class ImapClient {
}
return this;
}
public String username(){
return username;
}
}

3
src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java

@ -3,7 +3,6 @@ package de.srsoftware.widerhall.mail; @@ -3,7 +3,6 @@ package de.srsoftware.widerhall.mail;
import javax.mail.MessagingException;
public interface ProblemListener {
public void onImapException(MessagingException e);
public void clearProblems();
public void onImapException(MessagingException e);
}

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

@ -11,6 +11,8 @@ import java.util.Date; @@ -11,6 +11,8 @@ import java.util.Date;
import java.util.List;
import java.util.Properties;
import static de.srsoftware.widerhall.Util.t;
public class SmtpClient {
private static final Logger LOG = LoggerFactory.getLogger(SmtpClient.class);
private static final String HOST = "mail.smtp.host";
@ -62,7 +64,7 @@ public class SmtpClient { @@ -62,7 +64,7 @@ public class SmtpClient {
MimeMultipart multipart = new MimeMultipart();
if (forwardAsAttachment){
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setText("Find the forwarded message in the attachment(s)!\n");
bodyPart.setText(t("Find the forwarded message in the attachment(s)!\n"));
multipart.addBodyPart(bodyPart);
// create another body part to contain the message to be forwarded
@ -81,6 +83,9 @@ public class SmtpClient { @@ -81,6 +83,9 @@ public class SmtpClient {
send(forward);
}
public String host() {
return host;
}
public SmtpClient login(){
if (session == null) {
@ -97,6 +102,20 @@ public class SmtpClient { @@ -97,6 +102,20 @@ public class SmtpClient {
return this;
}
public String password() {
return password;
}
public int port() {
return port;
}
public void send(Message message) throws MessagingException {
LOG.debug("sending mail…");
Transport.send(message,username,password);
LOG.debug("…sent");
}
public void send(String senderAdress, String senderName, String receivers, String subject, String content) throws MessagingException, UnsupportedEncodingException {
login();
MimeMessage message = new MimeMessage(session);
@ -113,27 +132,7 @@ public class SmtpClient { @@ -113,27 +132,7 @@ public class SmtpClient {
send(message);
}
public void send(Message message) throws MessagingException {
LOG.debug("Versende Mail…");
Transport.send(message,username,password);
LOG.debug("…versendet");
}
public String host() {
return host;
}
public int port() {
return port;
}
public String username() {
return username;
}
public String password() {
return password;
}
}

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

@ -47,17 +47,17 @@ public class Rest extends HttpServlet { @@ -47,17 +47,17 @@ public class Rest extends HttpServlet {
private static final String SUCCESS = "success";
private Map addPermission(String userEmail, String permissions) {
if (userEmail == null || userEmail.isBlank()) return Map.of(ERROR,"missing user email address!");
if (userEmail == null || userEmail.isBlank()) return Map.of(ERROR,t("missing user email address!"));
try {
int perm = Integer.parseInt(permissions);
var user = User.loadAll(List.of(userEmail)).stream().findAny().orElse(null);
if (user == null) return Map.of(ERROR,t("Failed to load user for address {}",userEmail));
if (user == null) return Map.of(ERROR,t("Failed to load user for address {}!",userEmail));
user.addPermission(perm);
} catch (NumberFormatException nfe){
return Map.of(ERROR,"no valid permissions provided!");
} catch (SQLException e) {
LOG.debug("Failed to load user for address {}",userEmail,e);
return Map.of(ERROR,t("Failed to load user for address {}",userEmail));
LOG.debug("Failed to load user for address {}!",userEmail,e);
return Map.of(ERROR,t("Failed to load user for address {}!",userEmail));
}
return Map.of(SUCCESS,"Updated user permissions");
}
@ -141,13 +141,13 @@ public class Rest extends HttpServlet { @@ -141,13 +141,13 @@ public class Rest extends HttpServlet {
try {
int perm = Integer.parseInt(permissions);
var user = User.loadAll(List.of(userEmail)).stream().findAny().orElse(null);
if (user == null) return Map.of(ERROR,t("Failed to load user for address {}",userEmail));
if (user == null) return Map.of(ERROR,t("Failed to load user for address {}!",userEmail));
user.dropPermission(perm);
} catch (NumberFormatException nfe){
return Map.of(ERROR,"no valid permissions provided!");
} catch (SQLException e) {
LOG.debug("Failed to load user for address {}",userEmail,e);
return Map.of(ERROR,t("Failed to load user for address {}",userEmail));
LOG.debug("Failed to load user for address {}!",userEmail,e);
return Map.of(ERROR,t("Failed to load user for address {}!",userEmail));
}
return Map.of(SUCCESS,t("Updated user permissions"));
}
@ -157,10 +157,10 @@ public class Rest extends HttpServlet { @@ -157,10 +157,10 @@ public class Rest extends HttpServlet {
if (!list.mayBeAlteredBy(user)) Map.of(ERROR,t("You are not allowed to edit '{}'",list.email()));
try {
list.enable(enable);
return Map.of(SUCCESS,t("Mailing list '{}' was {}!",list.email(),enable ? "enabled" : "disabled"));
return Map.of(SUCCESS,t("Mailing list '{}' was {}!",list.email(),t(enable ? "enabled" : "disabled")));
} catch (SQLException e) {
LOG.error("Failed to enable/disable mailing list: ",e);
return Map.of(ERROR,t("Failed to update list '{}'",list.email()));
LOG.error("Failed to enable/disable mailing list:",e);
return Map.of(ERROR,t("Failed to update list '{}'!",list.email()));
}
}
@ -181,7 +181,7 @@ public class Rest extends HttpServlet { @@ -181,7 +181,7 @@ public class Rest extends HttpServlet {
json.put("users", (user.hashPermission(User.PERMISSION_ADMIN) ? User.loadAll() : List.of(user)).stream().map(User::safeMap).toList());
} catch (SQLException e) {
LOG.debug("Failed to load user list:",e);
json.put(ERROR,"failed to load user list");
json.put(ERROR,t("failed to load user list"));
}
break;
case LIST_MODERATED:
@ -203,7 +203,7 @@ public class Rest extends HttpServlet { @@ -203,7 +203,7 @@ public class Rest extends HttpServlet {
json.put("lists", MailingList.subscribable().stream().map(MailingList::minimalMap).toList());
break;
default:
json.put(ERROR,"Not logged in!");
json.put(ERROR,t("Not logged in!"));
}
}
try {
@ -270,19 +270,19 @@ public class Rest extends HttpServlet { @@ -270,19 +270,19 @@ public class Rest extends HttpServlet {
case USER_ADD_PERMISSION:
if (user.hashPermission(User.PERMISSION_ADMIN)){
json.putAll(addPermission(userEmail,permissions));
} else json.put(ERROR,"You are not allowed to alter user permissions!");
} else json.put(ERROR,t("You are not allowed to alter user permissions!"));
break;
case USER_DROP_PERMISSION:
if (user.hashPermission(User.PERMISSION_ADMIN)){
json.putAll(dropPermission(userEmail,permissions));
} else json.put(ERROR,"You are not allowed to alter user permissions!");
} else json.put(ERROR,t("You are not allowed to alter user permissions!"));
break;
default:
json.put(ERROR,t("No handler for path '{}'!",path));
break;
}
} else {
json.put(ERROR,"Not logged in!");
json.put(ERROR,t("Not logged in!"));
}
try {
resp.setContentType("application/json");
@ -298,9 +298,9 @@ public class Rest extends HttpServlet { @@ -298,9 +298,9 @@ public class Rest extends HttpServlet {
if (!list.mayBeAlteredBy(user)) Map.of(ERROR,t("You are not allowed to edit '{}'",list.email()));
try {
list.hide(hide);
return Map.of(SUCCESS,t("Mailing list '{}' was {}!",list.email(),hide ? "hidden" : "made public"));
return Map.of(SUCCESS,t("Mailing list '{}' was {}!",list.email(),t(hide ? "hidden" : "made public")));
} catch (SQLException e) {
LOG.error("Failed to (un)hide mailing list: ",e);
LOG.error("Failed to (un)hide mailing list:",e);
return Map.of(ERROR,t("Failed to update list '{}'",list.email()));
}
}
@ -313,7 +313,7 @@ public class Rest extends HttpServlet { @@ -313,7 +313,7 @@ public class Rest extends HttpServlet {
LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e);
return Map.of(ERROR,t("Failed to load list member for {}/{}",user.email(),list.email()));
}
if (moderator == null) return Map.of(ERROR,t("{} is not a member of {}",user.email(),list.email()));
if (moderator == null) return Map.of(ERROR,t("{} is not a member of {}!",user.email(),list.email()));
var error = moderator.addNewModerator(userEmail);
@ -321,7 +321,7 @@ public class Rest extends HttpServlet { @@ -321,7 +321,7 @@ public class Rest extends HttpServlet {
}
private Map listDetail(MailingList list, User user) {
if (list == null) return Map.of(ERROR,"no list email provided!");
if (list == null) return Map.of(ERROR,t("no list email provided!"));
var map = new HashMap<>();
if (list.hasState(MailingList.STATE_FORWARD_FROM)) map.put(KEY_FORWARD_FROM,true);
if (list.hasState(MailingList.STATE_FORWARD_ATTACHED)) map.put(KEY_FORWARD_ATTACHED,true);
@ -366,8 +366,8 @@ public class Rest extends HttpServlet { @@ -366,8 +366,8 @@ public class Rest extends HttpServlet {
}
private Map<String, Object> listMembers(MailingList list, User user) {
if (list == null) return Map.of(ERROR,"no list email provided!");
if (!list.membersMayBeListedBy(user)) Map.of(ERROR,t("You are not allowed to list members of '{}'",list.email()));
if (list == null) return Map.of(ERROR,t("no list email provided!"));
if (!list.membersMayBeListedBy(user)) Map.of(ERROR,t("You are not allowed to list members of '{}'!",list.email()));
try {
var members = list.members()
.sorted((m1,m2)->m1.user().name().compareTo(m2.user().name()))
@ -376,19 +376,19 @@ public class Rest extends HttpServlet { @@ -376,19 +376,19 @@ public class Rest extends HttpServlet {
return Map.of(MEMBERS,members,LIST,list.minimalMap());
} catch (SQLException e) {
LOG.error("Failed to load member list: ",e);
return Map.of(ERROR,t("Failed to load member list '{}'",list.email()));
return Map.of(ERROR,t("Failed to load member list '{}'!",list.email()));
}
}
private Map testList(MailingList list, User user) {
if (list == null) return Map.of(ERROR,"no list email provided!");
if (!list.mayBeTestedBy(user)) Map.of(ERROR,t("You are not allowed to test '{}'",list.email()));
if (list == null) return Map.of(ERROR,t("no list email provided!"));
if (!list.mayBeTestedBy(user)) Map.of(ERROR,t("You are not allowed to test '{}'!",list.email()));
try {
list.test(user);
return Map.of(SUCCESS,t("Sent test email to {}",user.email()));
} catch (Exception e) {
LOG.warn("Failed to send test email",e);
return Map.of(ERROR,t("Failed to send test email to {}",user.email()));
return Map.of(ERROR,t("Failed to send test email to {}!",user.email()));
}
}
}

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

@ -52,7 +52,7 @@ public abstract class TemplateServlet extends HttpServlet { @@ -52,7 +52,7 @@ public abstract class TemplateServlet extends HttpServlet {
resp.getWriter().println(template.render());
return null;
} catch (IOException e) {
return t("Failed to load template '{}'",path);
return t("Failed to load template '{}'!",path);
}
}
return t("No template for path '{}'!",path);

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

@ -160,7 +160,7 @@ public class Web extends TemplateServlet { @@ -160,7 +160,7 @@ public class Web extends TemplateServlet {
return loadTemplate(INDEX,Map.of(USER,listMember.user().safeMap(),NOTES,"Confirmed list subscription!"),resp);
}
return t("Unknown user or token");
return t("Unknown user or token!");
} catch (Exception e) {
LOG.debug("Failed to confirm list membership:",e);
return t("Confirmation of list membership failed!");
@ -260,7 +260,7 @@ public class Web extends TemplateServlet { @@ -260,7 +260,7 @@ public class Web extends TemplateServlet {
return loadTemplate(ADMIN,data,resp);
} catch (SQLException e) {
LOG.warn("Editing list {} by {} failed",list.email(),user.email(),e);
return t("Editing list {} by {} failed",list.email(),user.email());
return t("Editing list {} by {} failed!",list.email(),user.email());
}
}
@ -294,7 +294,7 @@ public class Web extends TemplateServlet { @@ -294,7 +294,7 @@ public class Web extends TemplateServlet {
return post(req,resp);
case RELOAD:
loadTemplates();
data.put(NOTES,t("Templates have been reloaded"));
data.put(NOTES,t("Templates have been reloaded!"));
path = INDEX;
case CSS:
case INDEX:
@ -402,7 +402,7 @@ public class Web extends TemplateServlet { @@ -402,7 +402,7 @@ public class Web extends TemplateServlet {
if (!error && !list.mayBeAlteredBy(user)) {
error = true;
data.put(ERROR,t("You are not alter settings of this list!"));
data.put(ERROR,t("You are not allowed to alter settings of this list!"));
}
if (!error){
@ -463,9 +463,9 @@ public class Web extends TemplateServlet { @@ -463,9 +463,9 @@ public class Web extends TemplateServlet {
if (email == null || email.isBlank() ||
name == null || name.isBlank() ||
pass == null || pass.isBlank() ||
pass_repeat == null || pass_repeat.isBlank()) return loadTemplate(REGISTER,Map.of(ERROR,"Fill all fields, please!",NAME,name,EMAIL,email),resp);
if (!pass.equals(pass_repeat)) return loadTemplate(REGISTER,Map.of(ERROR,"Passwords do not match!",NAME,name,EMAIL,email),resp);
if (Util.simplePassword(pass)) return loadTemplate(REGISTER,Map.of(ERROR,"Password to short or to simple!",NAME,name,EMAIL,email),resp);
pass_repeat == null || pass_repeat.isBlank()) return loadTemplate(REGISTER,Map.of(ERROR,t("Fill all fields, please!"),NAME,name,EMAIL,email),resp);
if (!pass.equals(pass_repeat)) return loadTemplate(REGISTER,Map.of(ERROR,t("Passwords do not match!"),NAME,name,EMAIL,email),resp);
if (Util.simplePassword(pass)) return loadTemplate(REGISTER,Map.of(ERROR,t("Password to short or to simple!"),NAME,name,EMAIL,email),resp);
var firstUser = false;
try {
@ -565,12 +565,12 @@ public class Web extends TemplateServlet { @@ -565,12 +565,12 @@ public class Web extends TemplateServlet {
data.put(EMAIL,email);
if (user != null) data.put(USER,user.safeMap());
if (list == null){
data.put(ERROR,"No list provided by form data!");
data.put(ERROR,t("No list provided by form data!"));
return loadTemplate(UNSUBSCRIBE,data,resp);
} else data.put(LIST,list.email());
if (user == null) {
if (email == null || email.isBlank()) {
data.put(ERROR, "Email is required for list un-subscription!");
data.put(ERROR, t("Email is required for list un-subscription!"));
return loadTemplate(UNSUBSCRIBE, data, resp);
}
var pass = req.getParameter(PASSWORD);
@ -581,7 +581,7 @@ public class Web extends TemplateServlet { @@ -581,7 +581,7 @@ public class Web extends TemplateServlet {
req.getSession().setAttribute(USER,user);
data.put(USER,user.safeMap());
} catch (InvalidKeyException | SQLException e) {
data.put(ERROR,"Invalid email/password combination!");
data.put(ERROR,t("Invalid email/password combination!"));
return loadTemplate(UNSUBSCRIBE,data,resp);
}
}

2
static/templates/footer.st

@ -1,3 +1,3 @@ @@ -1,3 +1,3 @@
<div class="footer">
Widerhall Mail Distributor. Version 1.0.1. Get the sources at <a target="_blank" href="https://git.srsoftware.de/StephanRichter/Widerhall">git.srsoftware.de</a>
Widerhall Mail Distributor. Version 1.0.2. Get the sources at <a target="_blank" href="https://git.srsoftware.de/StephanRichter/Widerhall">git.srsoftware.de</a>
</div>

Loading…
Cancel
Save