import java.io.PrintStream; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; class SimpleSmtpClient2 { private final static String DATE_TEMPLET = "d MMM yyyy HH:mm:ss Z";//"EEE, d MMM yyyy HH:mm:ss Z"; //"Sat, 12 Sep 2009 20:52:10 +0800" private final static String BOUNDARY_PREFIX = "--"; private final static String CRLF = "/r/n"; private Socket socket; private BufferedReader input; private PrintStream output; private SimpleDateFormat format; public SimpleSmtpClient2() { try { String smtp = "smtp.126.com"; String user = "yourName"; String pass = "yourPassword"; String from = user + "@126.com"; String to = user + "@126.com"; String subject = "这是测试A test for email"; String charset = "utf-8"; String boundary = "=======ThisIsBoundary======="; String content = "Hi James,/r/nThis is a test message./r/nRegard,/r/nBatman"; String attachment = "SimpleSmtpClient2.class"; socket = new Socket( smtp, 25/*COMMON*/ ); input = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); output = new PrintStream( socket.getOutputStream() ); getResponse( "220", "Failed to connect to: " + smtp, true ); sendCommand( "HELO " + smtp/*NO SPACE IN IT*/ + CRLF, true ); getResponse( "250", "Failed to get HELO response from server.", true ); sendCommand( "AUTH LOGIN" + CRLF, true ); getResponse( "334", "Failed to get USERNAME request from server.", true ); sendCommand( getBase64String( user ) + CRLF, false ); //Username getResponse( "334", "Failed to get PASSWORD request from server.", true ); sendCommand( getBase64String( pass ) + CRLF, false ); //Password getResponse( "235", "Failed to send AUTH LOGIN username and password to server.", true ); sendCommand( "MAIL FROM: <" + from + ">" + CRLF, true ); getResponse( "250", "Failed to get MAIL FROM response from server.", true ); sendCommand( "RCPT TO: <" + to + ">" + CRLF, true ); getResponse( "250", "Failed to get RCPT TO response from server.", false/*NOTE*/ ); sendCommand( "DATA" + CRLF, true ); getResponse( "354", "Failed to get DATA response from server.", true ); //sendCommand( "Subject: " + subject + CRLF, false ); sendCommand( "Subject: " + getBase64Subject( subject, charset ) + CRLF, false ); sendCommand( "Date: " + getDateString() + CRLF, false ); sendCommand( "From: " + user + "<" + from + ">" + CRLF, false ); sendCommand( "To: " + user + "<" + to + ">" + CRLF, false ); sendCommand( "MIME-Version: 1.0" + CRLF, false ); sendCommand( "Content-Type: multipart/mixed; boundary=/"" + boundary + "/"" + CRLF, false ); sendCommand( "Content-Transfer-Encoding: 7bit" + CRLF + CRLF/*NOTE*/, false ); sendCommand( "This is a multi-part message in MIME format." + CRLF + CRLF/*NOTE*/, false ); sendCommand( BOUNDARY_PREFIX + boundary + CRLF, false ); sendCommand( "Content-Type: text/plain;" + CRLF, false ); //sendCommand( "Content-Transfer-Encoding: 7bit" + CRLF + CRLF/*NOTE*/, false ); //sendCommand( content + CRLF + CRLF/*NOTE*/, false ); sendCommand( "Content-Transfer-Encoding: base64" + CRLF + CRLF/*NOTE*/, false ); sendCommand( getBase64String( content ) + CRLF + CRLF/*NOTE*/, false ); sendCommand( BOUNDARY_PREFIX + boundary + CRLF, false ); sendCommand( "Content-Type: application/octet-stream; name=/"" + attachment + "/"" + CRLF, false ); sendCommand( "Content-Transfer-Encoding: base64" + CRLF, false ); sendCommand( "Content-Disposition: attachment; filename=/"" + attachment + "/"" + CRLF + CRLF/*NOTE*/, false ); //sendCommand( "1u3Nt9btzbfO0rCuxOMxOTgwMDUwMg==" + CRLF + CRLF/*NOTE*/, false ); sendAttachment( attachment ); sendCommand( BOUNDARY_PREFIX + boundary + CRLF, false ); sendCommand( CRLF + "." + CRLF/*NOTE*/, false ); //Indicate the end of date using "/r/n./r/n" getResponse( "250", "Failed to send DATA content to server.", true ); sendCommand( "QUIT" + CRLF, true ); getResponse( "221", "Failed to get QUIT response from server.", true ); } catch( Exception e ){ e.printStackTrace(); } finally{ try{ output.close(); input.close(); socket.close(); }catch( Exception e ){} } } private void sendAttachment( String fileName ) throws Exception { FileInputStream inputStream = new FileInputStream( fileName ); //System.err.println( "File " + fileName + " its size: " + inputStream.available() ); int base64PerLine = 76; //76 base64 charactors per line int charsPerLine = 57; //57 = 76 / 4 * 3 byte[] src = new byte[4096]; byte[] dest = new byte[src.length * 2]; int length = 0, remain = 0, sOffset = 0, dOffset = 0; while( ( length = inputStream.read( src, remain, src.length - remain ) ) != -1 ) { length = length + remain; remain = length % charsPerLine; length = length / charsPerLine * charsPerLine; for( sOffset = 0, dOffset = 0; sOffset < length; ) { //System.err.println( length + "/t" + remain + "/t" + sOffset + "/t" + dOffset ); Base64Encode.encode( src, sOffset, charsPerLine, dest, dOffset ); sOffset += charsPerLine; dOffset += base64PerLine; dest[dOffset ++] = '/r'; dest[dOffset ++] = '/n'; //System.err.print( "[" + new String( dest, dOffset, base64PerLine + 2 ) + "]" ); } output.print( new String( dest, 0, dOffset ) ); //System.err.print( "[" + new String( dest, 0, dOffset ) + "]" ); if( remain > 0 ){ System.arraycopy( src, sOffset, src, 0, remain ); } } if( remain > 0 ) { //System.err.println( length + "/t" + remain + "/t" + sOffset + "/t" + dOffset ); Base64Encode.encode( src, 0, remain, dest, 0 ); dOffset = ( remain + 2 ) / 3 * 4; dest[dOffset ++] = '/r'; dest[dOffset ++] = '/n'; output.print( new String( dest, 0, dOffset ) ); //System.err.print( "<" + new String( dest, 0, dOffset ) + ">" ); } inputStream.close(); } private void sendCommand( String command, boolean newLine ) throws Exception { if( output == null || command == null || command.length() < 1 ){ return; } output.print( command ); System.err.print( ( newLine ? CRLF : "" ) + ">>> " + command ); } private void getResponse( String code, String message, boolean shouldQuit ) throws Exception { if( input == null || code == null || code.length() < 1 ){ return; } String line = input.readLine(); System.err.println( "<<< " + line ); if( line.startsWith( code ) ){ /**/ }else if( shouldQuit ){ throw new Exception( message ); } } private String getBase64String( String message ) { if( message == null ){ return null; } byte[] bytes = message.getBytes(); return Base64Encode.encode( bytes, 0, bytes.length ); } private String getBase64Subject( String subject, String charset ) { if( subject == null ){ return null; } byte[] bytes = null; try{ bytes = ( charset == null ? subject.getBytes() : subject.getBytes( charset ) ); }catch( Exception e ){} return ( bytes == null ? null : "=?" + charset + "?B?" + Base64Encode.encode( bytes, 0, bytes.length ) + "?=" ); } private String getDateString() { if( format == null ){ try{ format = new SimpleDateFormat( DATE_TEMPLET, Locale.ENGLISH ); }catch( Exception e ){} } return ( format == null ? new Date().toString() : format.format( new Date() ) ); } public static void main( String[] args ){ new SimpleSmtpClient2(); } } class Base64Encode { private final static char CHAR_TABLE[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; private final static byte BYTE_TABLE[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; public static String encode( byte[] src, int srcOffset, int srcLength ) { //if( src == null || srcOffset < 0 || srcLength < 0 || srcOffset + 1 > src.length || srcOffset + srcLength > src.length ){ return null; } if( src == null || srcOffset < 0 || srcLength < 1 || srcOffset + srcLength > src.length ){ return null; } byte[] buffer = new byte[( ( srcLength + 2 ) / 3 ) * 4]; int round = srcLength / 3, remain = srcLength % 3; int srcIndex = srcOffset, destIndex = 0, b1, b2, b3; for( int j = 0; j < round; j ++ ) { b1 = src[srcIndex ++] & 0xFF; b2 = src[srcIndex ++] & 0xFF; b3 = src[srcIndex ++] & 0xFF; buffer[destIndex ++] = BYTE_TABLE[b1 >> 2]; buffer[destIndex ++] = BYTE_TABLE[b1 << 4 & 0x3F | b2 >> 4]; buffer[destIndex ++] = BYTE_TABLE[b2 << 2 & 0x3F | b3 >> 6]; buffer[destIndex ++] = BYTE_TABLE[b3 & 0x3F]; } if( remain > 0 ) { b1 = src[srcIndex ++] & 0xFF; buffer[destIndex ++] = BYTE_TABLE[b1 >> 2]; if( remain == 1 ) { buffer[destIndex ++] = BYTE_TABLE[b1 << 4 & 0x3F]; buffer[destIndex ++] = '='; buffer[destIndex ++] = '='; } else //remain == 2 { b2 = src[srcIndex ++] & 0xFF; buffer[destIndex ++] = BYTE_TABLE[b1 << 4 & 0x3F | b2 >> 4]; buffer[destIndex ++] = BYTE_TABLE[b2 << 2 & 0x3F]; buffer[destIndex ++] = '='; } } return new String( buffer, 0, destIndex ); } public static int encode( byte[] src, int srcOffset, int srcLength, byte[] dest, int destOffset ) { //if( src == null || srcOffset < 0 || srcLength < 0 || srcOffset + 1 > src.length || srcOffset + srcLength > src.length ){ return -1; } if( src == null || dest == null || srcOffset < 0 || destOffset < 0 || srcLength < 1 || srcOffset + srcLength > src.length ){ return -1; } int destLength = ( srcLength + 2 ) / 3 * 4; //destLength always large then zero if( /*destLength < 1 ||*/ destOffset + destLength > dest.length ){ return -1; } int round = srcLength / 3, remain = srcLength % 3; int srcIndex = srcOffset, destIndex = destOffset, b1, b2, b3; for( int j = 0; j < round; j ++ ) { b1 = src[srcIndex ++] & 0xFF; b2 = src[srcIndex ++] & 0xFF; b3 = src[srcIndex ++] & 0xFF; dest[destIndex ++] = BYTE_TABLE[b1 >> 2]; dest[destIndex ++] = BYTE_TABLE[b1 << 4 & 0x3F | b2 >> 4]; dest[destIndex ++] = BYTE_TABLE[b2 << 2 & 0x3F | b3 >> 6]; dest[destIndex ++] = BYTE_TABLE[b3 & 0x3F]; } if( remain > 0 ) { b1 = src[srcIndex ++] & 0xFF; dest[destIndex ++] = BYTE_TABLE[b1 >> 2]; if( remain == 1 ) { dest[destIndex ++] = BYTE_TABLE[b1 << 4 & 0x3F]; dest[destIndex ++] = '='; dest[destIndex ++] = '='; } else //remain == 2 { b2 = src[srcIndex ++] & 0xFF; dest[destIndex ++] = BYTE_TABLE[b1 << 4 & 0x3F | b2 >> 4]; dest[destIndex ++] = BYTE_TABLE[b2 << 2 & 0x3F]; dest[destIndex ++] = '='; } } return destIndex; } }