/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.job;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.MimeTypes;
import com.mucommander.commons.file.util.FileSet;
import com.mucommander.commons.io.StreamUtils;
import com.mucommander.commons.io.base64.Base64OutputStream;
import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;
import com.mucommander.conf.MuPreferences;
import com.mucommander.text.Translator;
import com.mucommander.ui.dialog.file.ProgressDialog;
import com.mucommander.ui.main.MainFrame;
import java.io.*;
import java.net.Socket;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* This job sends one or several files by email.
*
* @author Maxence Bernard
*/
public class SendMailJob extends TransferFileJob {
/** True after connection to mail server has been established */
private boolean connectedToMailServer;
/** Error dialog title */
private String errorDialogTitle;
/////////////////////
// Mail parameters //
/////////////////////
/** Email recipient(s) */
private String recipientString;
/** Email subject */
private String mailSubject;
/** Email body */
private String mailBody;
/** SMTP server */
private String mailServer;
/** From name */
private String fromName;
/** From address */
private String fromAddress;
/** Email boundary string, delimits the end of the body and attachments */
private String boundary;
/** Connection variable */
private BufferedReader in;
/** OuputStream to the SMTP server */
private OutputStream out;
/** Base64OuputStream to the SMTP server */
private Base64OutputStream out64;
/** Socket connection to the SMTP server */
private Socket socket;
private final static String CLOSE_TEXT = Translator.get("close");
private final static int CLOSE_ACTION = 11;
public SendMailJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet filesToSend, String recipientString, String mailSubject, String mailBody) {
super(progressDialog, mainFrame, filesToSend);
this.boundary = "mucommander"+System.currentTimeMillis();
this.recipientString = recipientString;
this.mailSubject = mailSubject;
this.mailBody = mailBody+"\n\n"+"Sent by muCommander - http://www.mucommander.com\n";
this.mailServer = MuConfigurations.getPreferences().getVariable(MuPreference.SMTP_SERVER);
this.fromName = MuConfigurations.getPreferences().getVariable(MuPreference.MAIL_SENDER_NAME);
this.fromAddress = MuConfigurations.getPreferences().getVariable(MuPreference.MAIL_SENDER_ADDRESS);
this.errorDialogTitle = Translator.get("email_dialog.error_title");
}
/**
* Returns true if mail preferences have been set.
*/
public static boolean mailPreferencesSet() {
return MuConfigurations.getPreferences().isVariableSet(MuPreference.SMTP_SERVER)
&& MuConfigurations.getPreferences().isVariableSet(MuPreference.MAIL_SENDER_NAME)
&& MuConfigurations.getPreferences().isVariableSet(MuPreference.MAIL_SENDER_ADDRESS);
}
/**
* Shows an error dialog with a single action : close, and stops the job.
*/
private void showErrorDialog(String message) {
showErrorDialog(errorDialogTitle, message, new String[]{CLOSE_TEXT}, new int[]{CLOSE_ACTION});
interrupt();
}
/////////////////////////////////////////////
// Methods taking care of sending the mail //
/////////////////////////////////////////////
private void openConnection() throws IOException {
this.socket = new Socket(mailServer, MuConfigurations.getPreferences().getVariable(MuPreference.SMTP_PORT, MuPreferences.DEFAULT_SMTP_PORT));
this.in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
this.out = socket.getOutputStream();
this.out64 = new Base64OutputStream(out, true);
this.connectedToMailServer = true;
}
private void sendBody() throws IOException {
// here you are supposed to send your username
readWriteLine("HELO muCommander");
// warning : some mail server validate the sender address and will fail is an invalid
// address is provided
readWriteLine("MAIL FROM: "+fromAddress);
List<String> recipients = new Vector<String>();
recipientString = splitRecipientString(recipientString, recipients);
int nbRecipients = recipients.size();
for(int i=0; i<nbRecipients; i++)
readWriteLine("RCPT TO: <"+recipients.get(i)+">" );
readWriteLine("DATA");
writeLine("MIME-Version: 1.0");
writeLine("Subject: "+this.mailSubject);
writeLine("From: "+this.fromName+" <"+this.fromAddress+">");
writeLine("To: "+recipientString);
writeLine("Content-Type: multipart/mixed; boundary=\"" + boundary +"\"");
writeLine("\r\n--" + boundary);
// Send the body
// writeLine( "Content-Type: text/plain; charset=\"us-ascii\"\r\n");
writeLine("Content-Type: text/plain; charset=\"utf-8\"\r\n");
writeLine(this.mailBody+"\r\n\r\n");
writeLine("\r\n--" + boundary );
}
/**
* Parses the specified string, replaces delimiter characters if needed and adds recipients (String instances) to the given Vector.
*
* @param recipientsStr String containing one or several recipients that need to be separated by ',' and/or ';' characters.
*/
private String splitRecipientString(String recipientsStr, List<String> recipients) {
// /!\ this piece of code is far from being bullet proof but I'm too lazy now to rewrite it
StringBuilder newRecipientsSb = new StringBuilder();
StringTokenizer st = new StringTokenizer(recipientsStr, ",;");
String rec;
int pos1, pos2;
while(st.hasMoreTokens()) {
rec = st.nextToken().trim();
if((pos1=rec.indexOf('<'))!=-1 && (pos2=rec.indexOf('>', pos1+1))!=-1)
recipients.add(rec.substring(pos1+1, pos2));
else
recipients.add(rec);
newRecipientsSb.append(rec);
if(st.hasMoreTokens())
newRecipientsSb.append(", ");
}
return newRecipientsSb.toString();
}
/**
* Send file as attachment encoded in Base64, and returns true if file was successfully
* and completely transferred.
*/
private void sendAttachment(AbstractFile file) throws IOException {
InputStream fileIn = null;
try {
// Send MIME type of attachment file
String mimeType = MimeTypes.getMimeType(file);
// Default mime type
if(mimeType==null)
mimeType = "application/octet-stream";
writeLine("Content-Type:"+mimeType+"; name="+file.getName());
writeLine("Content-Disposition: attachment;filename=\""+file.getName()+"\"");
writeLine("Content-transfer-encoding: base64\r\n");
fileIn = setCurrentInputStream(file.getInputStream());
// Write file to socket
StreamUtils.copyStream(fileIn, out64);
// Writes padding bytes without closing the stream.
out64.writePadding();
writeLine("\r\n--" + boundary);
}
finally {
if(fileIn!=null)
fileIn.close();
}
}
private void sayGoodBye() throws IOException {
writeLine("\r\n\r\n--" + boundary + "--\r\n");
readWriteLine(".");
readWriteLine("QUIT");
}
private void closeConnection() {
try {
socket.close();
in.close();
out64.close();
}
catch(Exception e){
}
}
private void readWriteLine(String s) throws IOException {
out.write((s + "\r\n").getBytes("UTF-8"));
in.readLine();
}
private void writeLine(String s) throws IOException {
out.write((s + "\r\n").getBytes("UTF-8"));
}
////////////////////////////////////
// TransferFileJob implementation //
////////////////////////////////////
@Override
protected boolean processFile(AbstractFile file, Object recurseParams) {
if(getState()==INTERRUPTED)
return false;
// Send file attachment
try {
sendAttachment(file);
}
catch(IOException e) {
showErrorDialog(Translator.get("email.send_file_error", file.getName()));
return false;
}
// If this was the last file, notify the mail server that the mail is over
if(getCurrentFileIndex()==getNbFiles()-1) {
try {
// Say goodbye to the server
sayGoodBye();
}
catch(IOException e) {
showErrorDialog(Translator.get("email.goodbye_failed"));
return false;
}
}
return true;
}
@Override
protected boolean hasFolderChanged(AbstractFile folder) {
// This job does not modify anything
return false;
}
///////////////////////
// Overridden method //
///////////////////////
/**
* This method is called when this job starts, before the first call to {@link #processFile(AbstractFile,Object) processFile()} is made.
* This method here does nothing but it can be overriden by subclasses to perform some first-time initializations.
*/
@Override
protected void jobStarted() {
super.jobStarted();
// Open socket connection to the mail server, and say hello
try {
openConnection();
}
catch(IOException e) {
showErrorDialog(Translator.get("email.server_unavailable", mailServer));
}
if(getState()==INTERRUPTED)
return;
// Send mail body
try {
sendBody();
}
catch(IOException e) {
showErrorDialog(Translator.get("email.connection_closed"));
}
}
/**
* Method overridden to close connection to the mail server.
*/
@Override
protected void jobStopped() {
super.jobStopped();
// Close the connection
closeConnection();
}
@Override
public String getStatusString() {
if(connectedToMailServer)
return Translator.get("email.sending_file", getCurrentFilename());
else
return Translator.get("email.connecting_to_server", mailServer);
}
}