View Javadoc
1   package com.nilhcem.fakesmtp.server;
2   
3   import com.nilhcem.fakesmtp.core.ArgsHandler;
4   import com.nilhcem.fakesmtp.core.Configuration;
5   import com.nilhcem.fakesmtp.core.I18n;
6   import com.nilhcem.fakesmtp.model.EmailModel;
7   import com.nilhcem.fakesmtp.model.UIModel;
8   import org.apache.commons.io.FileUtils;
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  
12  import java.io.BufferedReader;
13  import java.io.File;
14  import java.io.InputStream;
15  import java.io.InputStreamReader;
16  import java.io.IOException;
17  import java.io.StringReader;
18  import java.nio.charset.Charset;
19  import java.text.SimpleDateFormat;
20  import java.util.Date;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Observable;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  /**
28   * Saves emails and notifies components so they can refresh their views with new data.
29   *
30   * @author Nilhcem
31   * @since 1.0
32   */
33  public final class MailSaver extends Observable {
34  
35  	private static final Logger LOGGER = LoggerFactory.getLogger(MailSaver.class);
36  	private static final String LINE_SEPARATOR = System.getProperty("line.separator");
37  	// This can be a static variable since it is Thread Safe
38  	private static final Pattern SUBJECT_PATTERN = Pattern.compile("^Subject: (.*)$");
39  
40  	private final SimpleDateFormat dateFormat = new SimpleDateFormat("ddMMyyhhmmssSSS");
41  
42  	/**
43  	 * Saves incoming email in file system and notifies observers.
44  	 *
45  	 * @param from the user who send the email.
46  	 * @param to the recipient of the email.
47  	 * @param data an InputStream object containing the email.
48  	 * @see com.nilhcem.fakesmtp.gui.MainPanel#addObservers to see which observers will be notified
49  	 */
50  	public void saveEmailAndNotify(String from, String to, InputStream data) {
51  		List<String> relayDomains = UIModel.INSTANCE.getRelayDomains();
52  
53  		if (relayDomains != null) {
54  			boolean matches = false;
55  			for (String domain : relayDomains) {
56  				if (to.endsWith(domain)) {
57  					matches = true;
58  					break;
59  				}
60  			}
61  
62  			if (!matches) {
63  				LOGGER.debug("Destination {} doesn't match relay domains", to);
64  				return;
65  			}
66  		}
67  
68  		// We move everything that we can move outside the synchronized block to limit the impact
69  		EmailModel model = new EmailModel();
70  		model.setFrom(from);
71  		model.setTo(to);
72  		String mailContent = convertStreamToString(data);
73  		model.setSubject(getSubjectFromStr(mailContent));
74  		model.setEmailStr(mailContent);
75  
76  		synchronized (getLock()) {
77  			String filePath = saveEmailToFile(mailContent);
78  
79  			model.setReceivedDate(new Date());
80  			model.setFilePath(filePath);
81  
82  			setChanged();
83  			notifyObservers(model);
84  		}
85  	}
86  
87  	/**
88  	 * Deletes all received emails from file system.
89  	 */
90  	public void deleteEmails() {
91  		Map<Integer, String> mails = UIModel.INSTANCE.getListMailsMap();
92  		if (ArgsHandler.INSTANCE.memoryModeEnabled()) {
93  			return;
94  		}
95  		for (String value : mails.values()) {
96  			File file = new File(value);
97  			if (file.exists()) {
98  				try {
99  					if (!file.delete()) {
100 						LOGGER.error("Impossible to delete file {}", value);
101 					}
102 				} catch (SecurityException e) {
103 					LOGGER.error("", e);
104 				}
105 			}
106 		}
107 	}
108 
109 	/**
110 	 * Returns a lock object.
111 	 * <p>
112 	 * This lock will be used to make the application thread-safe, and
113 	 * avoid receiving and deleting emails in the same time.
114 	 * </p>
115 	 *
116 	 * @return a lock object <i>(which is actually the current instance of the {@code MailSaver} object)</i>.
117 	 */
118 	public Object getLock() {
119 		return this;
120 	}
121 
122 	/**
123 	 * Converts an {@code InputStream} into a {@code String} object.
124 	 * <p>
125 	 * The method will not copy the first 4 lines of the input stream.<br>
126 	 * These 4 lines are SubEtha SMTP additional information.
127 	 * </p>
128 	 *
129 	 * @param is the InputStream to be converted.
130 	 * @return the converted string object, containing data from the InputStream passed in parameters.
131 	 */
132 	private String convertStreamToString(InputStream is) {
133 		final long lineNbToStartCopy = 4; // Do not copy the first 4 lines (received part)
134 		BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName(I18n.UTF8)));
135 		StringBuilder sb = new StringBuilder();
136 
137 		String line;
138 		long lineNb = 0;
139 		try {
140 			while ((line = reader.readLine()) != null) {
141 				if (++lineNb > lineNbToStartCopy) {
142 					sb.append(line).append(LINE_SEPARATOR);
143 				}
144 			}
145 		} catch (IOException e) {
146 			LOGGER.error("", e);
147 		}
148 		return sb.toString();
149 	}
150 
151 	/**
152 	 * Saves the content of the email passed in parameters in a file.
153 	 *
154 	 * @param mailContent the content of the email to be saved.
155 	 * @return the path of the created file.
156 	 */
157 	private String saveEmailToFile(String mailContent) {
158 		if (ArgsHandler.INSTANCE.memoryModeEnabled()) {
159 			return null;
160 		}
161 		String filePath = String.format("%s%s%s", UIModel.INSTANCE.getSavePath(), File.separator,
162 				dateFormat.format(new Date()));
163 
164 		// Create file
165 		int i = 0;
166 		File file = null;
167 		while (file == null || file.exists()) {
168 			String iStr;
169 			if (i++ > 0) {
170 				iStr = Integer.toString(i);
171 			} else {
172 				iStr = "";
173 			}
174 			file = new File(filePath + iStr + Configuration.INSTANCE.get("emails.suffix"));
175 		}
176 
177 		// Copy String to file
178 		try {
179 			FileUtils.writeStringToFile(file, mailContent);
180 		} catch (IOException e) {
181 			// If we can't save file, we display the error in the SMTP logs
182 			Logger smtpLogger = LoggerFactory.getLogger(org.subethamail.smtp.server.Session.class);
183 			smtpLogger.error("Error: Can't save email: {}", e.getMessage());
184 		}
185 		return file.getAbsolutePath();
186 	}
187 
188 	/**
189 	 * Gets the subject from the email data passed in parameters.
190 	 *
191 	 * @param data a string representing the email content.
192 	 * @return the subject of the email, or an empty subject if not found.
193 	 */
194 	private String getSubjectFromStr(String data) {
195 		try {
196 			BufferedReader reader = new BufferedReader(new StringReader(data));
197 
198 			String line;
199 			while ((line = reader.readLine()) != null) {
200 				 Matcher matcher = SUBJECT_PATTERN.matcher(line);
201 				 if (matcher.matches()) {
202 					 return matcher.group(1);
203 				 }
204 			}
205 		} catch (IOException e) {
206 			LOGGER.error("", e);
207 		}
208 		return "";
209 	}
210 }