use of i2p.susi.webmail.encoding.Encoding in project i2p.i2p by i2p.
the class SMTPClient method sendMail.
/**
* @param body without the attachments
* @param attachments may be null
* @param boundary non-null if attachments is non-null
* @return success
*/
public boolean sendMail(String host, int port, String user, String pass, String sender, String[] recipients, StringBuilder body, List<Attachment> attachments, String boundary) {
boolean mailSent = false;
boolean ok = true;
Writer out = null;
try {
socket = InternalSocket.getSocket(host, port);
} catch (IOException e) {
error += _t("Cannot connect") + " (" + host + ':' + port + ") : " + e.getMessage() + '\n';
ok = false;
}
try {
// AUTH ref: RFC 4954
if (ok) {
socket.setSoTimeout(120 * 1000);
int result = sendCmd(null);
if (result != 220) {
if (result != 0)
error += _t("Server refused connection") + " (" + result + ")\n";
else
error += _t("Cannot connect") + " (" + host + ':' + port + ")\n";
ok = false;
}
}
if (ok) {
sendCmdNoWait("EHLO localhost");
socket.getOutputStream().flush();
socket.setSoTimeout(60 * 1000);
Result r = getFullResult();
if (r.result == 250) {
String[] caps = DataHelper.split(r.recv, "\r");
for (String c : caps) {
if (c.equals("PIPELINING")) {
supportsPipelining = true;
Debug.debug(Debug.DEBUG, "Server supports pipelining");
} else if (c.startsWith("SIZE ")) {
try {
maxSize = Long.parseLong(c.substring(5));
Debug.debug(Debug.DEBUG, "Server max size: " + maxSize);
} catch (NumberFormatException nfe) {
}
} else if (c.equals("8BITMIME")) {
// unused, see encoding/EightBit.java
eightBitMime = true;
Debug.debug(Debug.DEBUG, "Server supports 8bitmime");
}
}
} else {
error += _t("Server refused connection") + " (" + r + ")\n";
ok = false;
}
}
if (ok && maxSize < DEFAULT_MAX_SIZE) {
Debug.debug(Debug.DEBUG, "Rechecking with new max size");
// recalculate whether we'll fit
// copied from WebMail
long total = body.length();
if (attachments != null && !attachments.isEmpty()) {
for (Attachment a : attachments) {
total += a.getSize();
}
}
long binaryMax = (long) ((maxSize * 57.0d / 78) - 32 * 1024);
if (total > binaryMax) {
ok = false;
error += _t("Email is too large, max is {0}", DataHelper.formatSize2(binaryMax, false) + 'B') + '\n';
}
}
if (ok) {
// RFC 4954 says AUTH must be the last but let's assume
// that includes the user/pass on following lines
List<SendExpect> cmds = new ArrayList<SendExpect>();
cmds.add(new SendExpect("AUTH LOGIN", 334));
cmds.add(new SendExpect(base64.encode(user), 334));
cmds.add(new SendExpect(base64.encode(pass), 235));
if (sendCmds(cmds) != 3) {
error += _t("Login failed") + '\n';
ok = false;
}
}
if (ok) {
List<SendExpect> cmds = new ArrayList<SendExpect>();
cmds.add(new SendExpect("MAIL FROM: " + sender, 250));
for (int i = 0; i < recipients.length; i++) {
cmds.add(new SendExpect("RCPT TO: " + recipients[i], 250));
}
cmds.add(new SendExpect("DATA", 354));
if (sendCmds(cmds) != cmds.size()) {
// TODO which recipient?
error += _t("Mail rejected") + '\n';
ok = false;
}
}
if (ok) {
// in-memory replace, no copies
DataHelper.replace(body, "\r\n.\r\n", "\r\n..\r\n");
// socket.getOutputStream().write(DataHelper.getUTF8(body));
// socket.getOutputStream().write(DataHelper.getASCII("\r\n.\r\n"));
// Do it this way so we don't double the memory
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"));
out.write(body.toString());
// and check the max total size
if (attachments != null && !attachments.isEmpty()) {
for (Attachment attachment : attachments) {
String encodeTo = attachment.getTransferEncoding();
Encoding encoding = EncodingFactory.getEncoding(encodeTo);
if (encoding == null)
throw new EncodingException(_t("No Encoding found for {0}", encodeTo));
// ref: https://blog.nodemailer.com/2017/01/27/the-mess-that-is-attachment-filenames/
// ref: RFC 2231
// split Content-Disposition into 3 lines to maximize room
// TODO filename*0* for long names...
String name = attachment.getFileName();
String name2 = FilenameUtil.sanitizeFilename(name);
String name3 = FilenameUtil.encodeFilenameRFC5987(name);
out.write("\r\n--" + boundary + "\r\nContent-type: " + attachment.getContentType() + "\r\nContent-Disposition: attachment;\r\n\tfilename=\"" + name2 + "\";\r\n\tfilename*=" + name3 + "\r\nContent-Transfer-Encoding: " + attachment.getTransferEncoding() + "\r\n\r\n");
InputStream in = null;
try {
in = attachment.getData();
encoding.encode(in, out);
} finally {
if (in != null)
try {
in.close();
} catch (IOException ioe) {
}
}
}
out.write("\r\n--" + boundary + "--\r\n");
}
out.write("\r\n.\r\n");
out.flush();
socket.setSoTimeout(0);
int result = sendCmd(null);
if (result == 250)
mailSent = true;
else
error += _t("Error sending mail") + " (" + result + ")\n";
}
} catch (IOException e) {
error += _t("Error sending mail") + ": " + e.getMessage() + '\n';
}
if (!mailSent && lastResponse.length() > 0) {
String[] lines = DataHelper.split(lastResponse, "\r");
for (int i = 0; i < lines.length; i++) error += lines[i] + '\n';
}
sendCmd("QUIT", false);
if (socket != null) {
try {
socket.close();
} catch (IOException e1) {
}
if (out != null)
try {
out.close();
} catch (IOException ioe) {
}
}
return mailSent;
}
use of i2p.susi.webmail.encoding.Encoding in project i2p.i2p by i2p.
the class MailPart method decode.
/**
* @param offset 2 for sendAttachment, 0 otherwise, probably for \r\n
* @since 0.9.13
*/
public void decode(int offset, Buffer out) throws IOException {
String encg = encoding;
if (encg == null) {
// throw new DecodingException("No encoding specified");
Debug.debug(Debug.DEBUG, "Warning: no transfer encoding found, fallback to 7bit.");
encg = "7bit";
}
Encoding enc = EncodingFactory.getEncoding(encg);
if (enc == null)
throw new DecodingException(_t("No encoder found for encoding \\''{0}\\''.", WebMail.quoteHTML(encg)));
InputStream in = null;
LimitInputStream lin = null;
CountingOutputStream cos = null;
Buffer dout = null;
try {
in = buffer.getInputStream();
DataHelper.skip(in, buffer.getOffset() + beginBody + offset);
lin = new LimitInputStream(in, end - beginBody - offset);
if (decodedLength < 0) {
cos = new CountingOutputStream(out.getOutputStream());
dout = new OutputStreamBuffer(cos);
} else {
dout = out;
}
enc.decode(lin, dout);
// dout.getOutputStream().flush();
} catch (IOException ioe) {
if (lin != null)
Debug.debug(Debug.DEBUG, "Decode IOE at in position " + lin.getRead() + " offset " + offset, ioe);
else if (cos != null)
Debug.debug(Debug.DEBUG, "Decode IOE at out position " + cos.getWritten() + " offset " + offset, ioe);
else
Debug.debug(Debug.DEBUG, "Decode IOE", ioe);
throw ioe;
} finally {
if (in != null)
try {
in.close();
} catch (IOException ioe) {
}
;
if (lin != null)
try {
lin.close();
} catch (IOException ioe) {
}
;
buffer.readComplete(true);
// let the servlet do this
// if (cos != null) try { cos.close(); } catch (IOException ioe) {};
// if (dout != null)
// dout.writeComplete(true);
// out.writeComplete(true);
}
if (cos != null)
decodedLength = (int) cos.getWritten();
}
use of i2p.susi.webmail.encoding.Encoding in project i2p.i2p by i2p.
the class WebMail method processComposeButtons.
/**
* process buttons of compose message dialog
* This must be called BEFORE processStateChangeButtons so we can add the attachment before SEND
*
* @param sessionObject
* @param request
* @return new state, or null if unknown
*/
private static State processComposeButtons(SessionObject sessionObject, RequestWrapper request) {
State state = null;
String filename = request.getFilename(NEW_FILENAME);
// We handle an attachment whether sending or uploading
if (filename != null && (buttonPressed(request, NEW_UPLOAD) || buttonPressed(request, SEND))) {
int i = filename.lastIndexOf('/');
if (i != -1)
filename = filename.substring(i + 1);
i = filename.lastIndexOf('\\');
if (i != -1)
filename = filename.substring(i + 1);
if (filename != null && filename.length() > 0) {
InputStream in = null;
OutputStream out = null;
I2PAppContext ctx = I2PAppContext.getGlobalContext();
File f = new File(ctx.getTempDir(), "susimail-attachment-" + ctx.random().nextLong());
try {
in = request.getInputStream(NEW_FILENAME);
if (in == null)
throw new IOException("no stream");
out = new SecureFileOutputStream(f);
DataHelper.copy(in, out);
String contentType = request.getContentType(NEW_FILENAME);
String encodeTo;
String ctlc = contentType.toLowerCase(Locale.US);
if (ctlc.startsWith("text/")) {
encodeTo = "quoted-printable";
// interpret it as ISO-8859-1
if (!ctlc.contains("charset="))
contentType += "; charset=\"utf-8\"";
} else {
encodeTo = "base64";
}
Encoding encoding = EncodingFactory.getEncoding(encodeTo);
if (encoding != null) {
if (sessionObject.attachments == null)
sessionObject.attachments = new ArrayList<Attachment>();
sessionObject.attachments.add(new Attachment(filename, contentType, encodeTo, f));
} else {
sessionObject.error += _t("No Encoding found for {0}", encodeTo) + '\n';
}
} catch (IOException e) {
sessionObject.error += _t("Error reading uploaded file: {0}", e.getMessage()) + '\n';
f.delete();
} finally {
if (in != null)
try {
in.close();
} catch (IOException ioe) {
}
if (out != null)
try {
out.close();
} catch (IOException ioe) {
}
}
}
state = State.NEW;
} else if (sessionObject.attachments != null && buttonPressed(request, DELETE_ATTACHMENT)) {
for (String item : getCheckedItems(request)) {
try {
int n = Integer.parseInt(item);
for (int i = 0; i < sessionObject.attachments.size(); i++) {
Attachment attachment = sessionObject.attachments.get(i);
if (attachment.hashCode() == n) {
sessionObject.attachments.remove(i);
break;
}
}
} catch (NumberFormatException nfe) {
}
}
state = State.NEW;
}
return state;
}
use of i2p.susi.webmail.encoding.Encoding in project i2p.i2p by i2p.
the class WebMail method sendMail.
/**
* @param sessionObject
* @param request
* @return success
*/
private static boolean sendMail(SessionObject sessionObject, RequestWrapper request) {
boolean ok = true;
String from = request.getParameter(NEW_FROM);
String to = request.getParameter(NEW_TO);
String cc = request.getParameter(NEW_CC);
String bcc = request.getParameter(NEW_BCC);
String subject = request.getParameter(NEW_SUBJECT, _t("no subject"));
String text = request.getParameter(NEW_TEXT, "");
boolean fixed = Boolean.parseBoolean(Config.getProperty(CONFIG_SENDER_FIXED, "true"));
if (fixed) {
String domain = Config.getProperty(CONFIG_SENDER_DOMAIN, "mail.i2p");
from = "<" + sessionObject.user + "@" + domain + ">";
}
ArrayList<String> toList = new ArrayList<String>();
ArrayList<String> ccList = new ArrayList<String>();
ArrayList<String> bccList = new ArrayList<String>();
ArrayList<String> recipients = new ArrayList<String>();
String sender = null;
if (from == null || !Mail.validateAddress(from)) {
ok = false;
sessionObject.error += _t("Found no valid sender address.") + '\n';
} else {
sender = Mail.getAddress(from);
if (sender == null || sender.length() == 0) {
ok = false;
sessionObject.error += _t("Found no valid address in \\''{0}\\''.", quoteHTML(from)) + '\n';
}
}
ok = Mail.getRecipientsFromList(toList, to, ok);
ok = Mail.getRecipientsFromList(ccList, cc, ok);
ok = Mail.getRecipientsFromList(bccList, bcc, ok);
recipients.addAll(toList);
recipients.addAll(ccList);
recipients.addAll(bccList);
String bccToSelf = request.getParameter(NEW_BCC_TO_SELF);
boolean toSelf = "1".equals(bccToSelf);
// save preference in session
sessionObject.bccToSelf = toSelf;
if (toSelf)
recipients.add(sender);
if (toList.isEmpty()) {
ok = false;
sessionObject.error += _t("No recipients found.") + '\n';
}
Encoding qp = EncodingFactory.getEncoding("quoted-printable");
Encoding hl = EncodingFactory.getEncoding("HEADERLINE");
if (qp == null) {
ok = false;
// can't happen, don't translate
sessionObject.error += "Internal error: Quoted printable encoder not available.";
}
if (hl == null) {
ok = false;
// can't happen, don't translate
sessionObject.error += "Internal error: Header line encoder not available.";
}
long total = text.length();
boolean multipart = sessionObject.attachments != null && !sessionObject.attachments.isEmpty();
if (multipart) {
for (Attachment a : sessionObject.attachments) {
total += a.getSize();
}
}
if (total > SMTPClient.BINARY_MAX_SIZE) {
ok = false;
sessionObject.error += _t("Email is too large, max is {0}", DataHelper.formatSize2(SMTPClient.BINARY_MAX_SIZE, false) + 'B') + '\n';
}
if (ok) {
StringBuilder body = new StringBuilder(1024);
I2PAppContext ctx = I2PAppContext.getGlobalContext();
body.append("Date: " + RFC822Date.to822Date(ctx.clock().now()) + "\r\n");
// todo include real names, and headerline encode them
body.append("From: " + from + "\r\n");
Mail.appendRecipients(body, toList, "To: ");
Mail.appendRecipients(body, ccList, "Cc: ");
try {
body.append(hl.encode("Subject: " + subject.trim()));
} catch (EncodingException e) {
ok = false;
sessionObject.error += e.getMessage();
}
String boundary = "_=" + ctx.random().nextLong();
if (multipart) {
body.append("MIME-Version: 1.0\r\nContent-type: multipart/mixed; boundary=\"" + boundary + "\"\r\n\r\n");
} else {
body.append("MIME-Version: 1.0\r\nContent-type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n");
}
try {
// TODO pass the text separately to SMTP and let it pick the encoding
if (multipart)
body.append("--" + boundary + "\r\nContent-type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n");
body.append(qp.encode(text));
} catch (EncodingException e) {
ok = false;
sessionObject.error += e.getMessage();
}
// set to the StringBuilder so SMTP can replace() in place
sessionObject.sentMail = body;
if (ok) {
SMTPClient relay = new SMTPClient();
if (relay.sendMail(sessionObject.host, sessionObject.smtpPort, sessionObject.user, sessionObject.pass, sender, recipients.toArray(new String[recipients.size()]), sessionObject.sentMail, sessionObject.attachments, boundary)) {
sessionObject.info += _t("Mail sent.");
sessionObject.sentMail = null;
sessionObject.clearAttachments();
} else {
ok = false;
sessionObject.error += relay.error;
}
}
}
return ok;
}
use of i2p.susi.webmail.encoding.Encoding in project i2p.i2p by i2p.
the class Mail method parseHeaders.
/**
* @return all headers, to pass to MailPart, or null on error
*/
private String[] parseHeaders(InputStream in) {
String[] headerLines = null;
error = "";
if (header != null) {
boolean ok = true;
Encoding html = EncodingFactory.getEncoding("HTML");
if (html == null) {
error += "HTML encoder not found.\n";
ok = false;
}
Encoding hl = EncodingFactory.getEncoding("HEADERLINE");
if (hl == null) {
error += "Header line encoder not found.\n";
ok = false;
}
if (ok) {
try {
EOFOnMatchInputStream eofin = new EOFOnMatchInputStream(in, HEADER_MATCH);
MemoryBuffer decoded = new MemoryBuffer(4096);
hl.decode(eofin, decoded);
if (!eofin.wasFound())
Debug.debug(Debug.DEBUG, "EOF hit before \\r\\n\\r\\n in Mail");
// Fixme UTF-8 to bytes to UTF-8
headerLines = DataHelper.split(new String(decoded.getContent(), decoded.getOffset(), decoded.getLength()), "\r\n");
for (int j = 0; j < headerLines.length; j++) {
String line = headerLines[j];
if (line.length() == 0)
break;
String hlc = line.toLowerCase(Locale.US);
if (hlc.startsWith("from:")) {
sender = line.substring(5).trim();
// formattedSender = getAddress( sender );
shortSender = sender.replace("\"", "").trim();
int lt = shortSender.indexOf('<');
if (lt > 0)
shortSender = shortSender.substring(0, lt).trim();
else if (lt < 0 && shortSender.contains("@"))
// add missing <> (but thunderbird doesn't...)
shortSender = '<' + shortSender + '>';
boolean trim = shortSender.length() > 45;
if (trim)
shortSender = ServletUtil.truncate(shortSender, 42).trim();
shortSender = html.encode(shortSender);
if (trim)
// must be after html encode
shortSender += "…";
} else if (hlc.startsWith("date:")) {
dateString = line.substring(5).trim();
long dateLong = RFC822Date.parse822Date(dateString);
if (dateLong > 0)
setDate(dateLong);
} else if (hlc.startsWith("subject:")) {
subject = line.substring(8).trim();
shortSubject = subject;
boolean trim = subject.length() > 75;
if (trim)
shortSubject = ServletUtil.truncate(subject, 72).trim();
shortSubject = html.encode(shortSubject);
if (trim)
// must be after html encode
shortSubject += "…";
} else if (hlc.startsWith("reply-to:")) {
reply = getAddress(line.substring(9).trim());
} else if (hlc.startsWith("to:")) {
ArrayList<String> list = new ArrayList<String>();
getRecipientsFromList(list, line.substring(3).trim(), true);
if (list.isEmpty()) {
// don't set
} else if (to == null) {
to = list.toArray(new String[list.size()]);
} else if (cc == null) {
// Susimail bug before 0.9.33, sent 2nd To line that was really Cc
cc = list.toArray(new String[list.size()]);
} else {
// add to the array, shouldn't happen
for (int i = 0; i < to.length; i++) {
list.add(i, to[i]);
}
to = list.toArray(new String[list.size()]);
}
} else if (hlc.startsWith("cc:")) {
ArrayList<String> list = new ArrayList<String>();
getRecipientsFromList(list, line.substring(3).trim(), true);
if (list.isEmpty()) {
// don't set
} else if (cc == null) {
cc = list.toArray(new String[list.size()]);
} else {
// add to the array, shouldn't happen
for (int i = 0; i < cc.length; i++) {
list.add(i, cc[i]);
}
cc = list.toArray(new String[list.size()]);
}
} else if (hlc.equals("x-spam-flag: yes")) {
// TODO trust.spam.headers config
isSpam = true;
} else if (hlc.startsWith("content-type:")) {
// this is duplicated in MailPart but
// we want to know if we have attachments, even if
// we haven't fetched the body
contentType = line.substring(13).trim();
} else if (hlc.startsWith("message-id:")) {
messageID = line.substring(11).trim();
}
}
} catch (Exception e) {
error += "Error parsing mail header: " + e.getClass().getName() + '\n';
Debug.debug(Debug.ERROR, "Parse error", e);
}
}
}
return headerLines;
}
Aggregations