1 /*
2 * @(#)NetworkClient.java 1.33 01/12/03
3 *
4 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
5 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6 */
7 package org.masukomi.aspirin.core;
8
9 import sun.io.CharToByteConverter;
10
11 import java.io.*;
12
13 import java.net.InetAddress;
14 import java.net.InetSocketAddress;
15 import java.net.Socket;
16
17 //import java.net.URL;
18 import java.net.UnknownHostException;
19
20 import java.util.Arrays;
21 import java.util.Vector;
22
23
24 /***
25 * This is the base class for network clients.
26 *
27 * @version 1.33, 12/03/01
28 * @author Jonathan Payne
29 */
30 public class SMTPClient {
31 //~ Static fields/initializers ---------------------------------------------
32
33 /*** DOCUMENT ME! */
34 static final boolean debug = false;
35
36 /*** DOCUMENT ME! */
37 protected static int defaultSoTimeout;
38
39 /*** DOCUMENT ME! */
40 protected static int defaultConnectTimeout;
41
42 /* Name of encoding to use for output */
43
44 /*** DOCUMENT ME! */
45 protected static String encoding;
46
47 static {
48 Integer tm = (Integer) java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() {
49 public Object run() {
50 return (Integer.getInteger(
51 "sun.net.client.defaultReadTimeout"));
52 }
53 });
54
55 if (tm == null) {
56 defaultSoTimeout = -1;
57 } else {
58 defaultSoTimeout = tm.intValue();
59 }
60
61 tm = (Integer) java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() {
62 public Object run() {
63 return (Integer.getInteger(
64 "sun.net.client.defaultConnectTimeout"));
65 }
66 });
67
68 if (tm == null) {
69 defaultConnectTimeout = -1;
70 } else {
71 defaultConnectTimeout = tm.intValue();
72 }
73
74 encoding = (String) java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction(
75 "file.encoding", "ISO8859_1"));
76
77 try {
78 if (!isASCIISuperset(encoding)) {
79 encoding = "ISO8859_1";
80 }
81 } catch (Exception e) {
82 encoding = "ISO8859_1";
83 }
84 }
85
86 //~ Instance fields --------------------------------------------------------
87
88 /*** Buffered stream for reading replies from server. */
89 public InputStream serverInput;
90
91 /*** Stream for printing to the server. */
92 public PrintStream serverOutput;
93
94 /*** Socket for communicating with server. */
95 protected Socket serverSocket = null;
96
97 /*** Array of strings (usually 1 entry) for the last reply
98 from the server. */
99 protected Vector serverResponse = new Vector(1);
100
101 /*** code for last reply */
102 protected int lastReplyCode;
103
104 /*** DOCUMENT ME! */
105 SmtpPrintStream message;
106
107 /*** DOCUMENT ME! */
108 String mailhost;
109
110 //~ Constructors -----------------------------------------------------------
111
112 /*** Create connection with host <i>host</i> on port <i>port</i> */
113 public SMTPClient(String host, int port) throws IOException {
114 openServer(host, port);
115 }
116
117 /*** New SMTP client connected to host <i>host</i>. */
118 public SMTPClient(String host) throws IOException {
119 super();
120
121 if (host != null) {
122 try {
123 openServer(host);
124 mailhost = host;
125
126 return;
127 } catch (Exception e) {
128 }
129 }
130
131 try {
132 String s;
133 mailhost = (String) java.security.AccessController.doPrivileged(new sun.security.action.GetPropertyAction(
134 "mail.host"));
135
136 if (mailhost != null) {
137 openServer(mailhost);
138
139 return;
140 }
141 } catch (Exception e) {
142 }
143
144 try {
145 mailhost = "localhost";
146 openServer(mailhost);
147 } catch (Exception e) {
148 mailhost = "mailhost";
149 openServer(mailhost);
150 }
151 }
152
153 /*** Create an uninitialized SMTP client. */
154 public SMTPClient() throws IOException {
155 this(null);
156 }
157
158 //~ Methods ----------------------------------------------------------------
159
160 /***
161 * DOCUMENT ME!
162 *
163 * @return DOCUMENT ME!
164 */
165 public String getMailHost() {
166 return mailhost;
167 }
168
169 /*** converts the server response into a string. */
170 public String getResponseString() {
171 return (String) serverResponse.elementAt(0);
172 }
173
174 /*** Returns all server response strings. */
175 public Vector getResponseStrings() {
176 return serverResponse;
177 }
178
179 /***
180 * issue the QUIT command to the SMTP server and close the connection.
181 */
182 public void closeServer() throws IOException {
183 if (serverIsOpen()) {
184 closeMessage();
185 issueCommand("QUIT\r\n", 221);
186 serverSocket.close();
187 serverSocket = null;
188 serverInput = null;
189 serverOutput = null;
190 }
191 }
192
193 /***
194 * DOCUMENT ME!
195 *
196 * @param s DOCUMENT ME!
197 *
198 * @throws IOException DOCUMENT ME!
199 */
200 public void from(String s) throws IOException {
201 if (s.startsWith("<")) {
202 issueCommand("mail from: " + s + "\r\n", 250);
203 } else {
204 issueCommand("mail from: <" + s + ">\r\n", 250);
205 }
206 }
207
208 /*** Open a connection to the server. */
209 public void openServer(String server, int port)
210 throws IOException, UnknownHostException {
211 if (serverSocket != null) {
212 closeServer();
213 }
214
215 serverSocket = doConnect(server, port);
216
217 try {
218 serverOutput = new PrintStream(new BufferedOutputStream(
219 serverSocket.getOutputStream()), true, encoding);
220 } catch (UnsupportedEncodingException e) {
221 throw new InternalError(encoding + "encoding not found");
222 }
223
224 serverInput = new BufferedInputStream(serverSocket.getInputStream());
225 }
226
227 /***
228 * Pulls the response from the server and returns the code as a
229 * number. Returns -1 on failure.
230 */
231 public int readServerResponse() throws IOException {
232 StringBuffer replyBuf = new StringBuffer(32);
233 int c;
234 int continuingCode = -1;
235 int code;
236 String response;
237
238 serverResponse.setSize(0);
239
240 while (true) {
241 while ((c = serverInput.read()) != -1) {
242 if (c == '\r') {
243 if ((c = serverInput.read()) != '\n') {
244 replyBuf.append('\r');
245 }
246 }
247
248 replyBuf.append((char) c);
249
250 if (c == '\n') {
251 break;
252 }
253 }
254
255 response = replyBuf.toString();
256 replyBuf.setLength(0);
257
258 if (debug) {
259 System.out.print(response);
260 }
261
262 if (response.length() == 0) {
263 code = -1;
264 } else {
265 try {
266 code = Integer.parseInt(response.substring(0, 3));
267 } catch (NumberFormatException e) {
268 code = -1;
269 } catch (StringIndexOutOfBoundsException e) {
270 /* this line doesn't contain a response code, so
271 we just completely ignore it */
272 continue;
273 }
274 }
275
276 serverResponse.addElement(response);
277
278 if (continuingCode != -1) {
279 /* we've seen a XXX- sequence */
280 if ((code != continuingCode)
281 || ((response.length() >= 4)
282 && (response.charAt(3) == '-'))) {
283 continue;
284 } else {
285 /* seen the end of code sequence */
286 continuingCode = -1;
287
288 break;
289 }
290 } else if ((response.length() >= 4) && (response.charAt(3) == '-')) {
291 continuingCode = code;
292
293 continue;
294 } else {
295 break;
296 }
297 }
298
299 return lastReplyCode = code;
300 }
301
302 /*** Sends command <i>cmd</i> to the server. */
303 public void sendServer(String cmd) {
304 serverOutput.print(cmd);
305
306 if (debug) {
307 System.out.print("Sending: " + cmd);
308 }
309 }
310
311 /*** Return server connection status */
312 public boolean serverIsOpen() {
313 return serverSocket != null;
314 }
315
316 /***
317 * DOCUMENT ME!
318 *
319 * @return DOCUMENT ME!
320 *
321 * @throws IOException DOCUMENT ME!
322 * @throws InternalError DOCUMENT ME!
323 */
324 public PrintStream startMessage() throws IOException {
325 issueCommand("data\r\n", 354);
326
327 try {
328 message = new SmtpPrintStream(serverOutput, this);
329 } catch (UnsupportedEncodingException e) {
330 throw new InternalError(encoding + " encoding not found");
331 }
332
333 return message;
334 }
335
336 /***
337 * DOCUMENT ME!
338 *
339 * @param s DOCUMENT ME!
340 *
341 * @throws IOException DOCUMENT ME!
342 */
343 public void to(String s) throws IOException {
344 int st = 0;
345 int limit = s.length();
346 int pos = 0;
347 int lastnonsp = 0;
348 int parendepth = 0;
349 boolean ignore = false;
350
351 while (pos < limit) {
352 int c = s.charAt(pos);
353
354 if (parendepth > 0) {
355 if (c == '(') {
356 parendepth++;
357 } else if (c == ')') {
358 parendepth--;
359 }
360
361 if (parendepth == 0) {
362 if (lastnonsp > st) {
363 ignore = true;
364 } else {
365 st = pos + 1;
366 }
367 }
368 } else if (c == '(') {
369 parendepth++;
370 } else if (c == '<') {
371 st = lastnonsp = pos + 1;
372 } else if (c == '>') {
373 ignore = true;
374 } else if (c == ',') {
375 if (lastnonsp > st) {
376 toCanonical(s.substring(st, lastnonsp));
377 }
378
379 st = pos + 1;
380 ignore = false;
381 } else {
382 if ((c > ' ') && !ignore) {
383 lastnonsp = pos + 1;
384 } else if (st == pos) {
385 st++;
386 }
387 }
388
389 pos++;
390 }
391
392 if (lastnonsp > st) {
393 toCanonical(s.substring(st, lastnonsp));
394 }
395 }
396
397 /***
398 * DOCUMENT ME!
399 *
400 * @return DOCUMENT ME!
401 *
402 * @throws IOException DOCUMENT ME!
403 */
404 protected InetAddress getLocalAddress() throws IOException {
405 if (serverSocket == null) {
406 throw new IOException("not connected");
407 }
408
409 return serverSocket.getLocalAddress();
410 }
411
412 /***
413 * Return a socket connected to the server, with any
414 * appropriate options pre-established
415 */
416 protected Socket doConnect(String server, int port)
417 throws IOException, UnknownHostException {
418 Socket s = new Socket();
419
420 if (defaultConnectTimeout > 0) {
421 s.connect(new InetSocketAddress(server, port), defaultConnectTimeout);
422 } else {
423 s.connect(new InetSocketAddress(server, port));
424 }
425
426 if (defaultSoTimeout > 0) {
427 s.setSoTimeout(defaultSoTimeout);
428 }
429
430 return s;
431 }
432
433 /***
434 * DOCUMENT ME!
435 *
436 * @return DOCUMENT ME!
437 */
438 String getEncoding() {
439 return encoding;
440 }
441
442 /***
443 * DOCUMENT ME!
444 *
445 * @throws IOException DOCUMENT ME!
446 */
447 void closeMessage() throws IOException {
448 if (message != null) {
449 message.close();
450 }
451 }
452
453 /***
454 * DOCUMENT ME!
455 *
456 * @param cmd DOCUMENT ME!
457 * @param expect DOCUMENT ME!
458 *
459 * @throws IOException DOCUMENT ME!
460 * @throws SmtpProtocolException DOCUMENT ME!
461 */
462 void issueCommand(String cmd, int expect) throws IOException {
463
464 sendServer(cmd);
465
466 int reply;
467
468 while ((reply = readServerResponse()) != expect) {
469 if (reply != 220) {
470 throw new SmtpProtocolException(getResponseString());
471 }
472 }
473 }
474
475 /***
476 * Test the named character encoding to verify that it converts ASCII
477 * characters correctly. We have to use an ASCII based encoding, or else
478 * the NetworkClients will not work correctly in EBCDIC based systems.
479 * However, we cannot just use ASCII or ISO8859_1 universally, because in
480 * Asian locales, non-ASCII characters may be embedded in otherwise
481 * ASCII based protocols (eg. HTTP). The specifications (RFC2616, 2398)
482 * are a little ambiguous in this matter. For instance, RFC2398 [part 2.1]
483 * says that the HTTP request URI should be escaped using a defined
484 * mechanism, but there is no way to specify in the escaped string what
485 * the original character set is. It is not correct to assume that
486 * UTF-8 is always used (as in URLs in HTML 4.0). For this reason,
487 * until the specifications are updated to deal with this issue more
488 * comprehensively, and more importantly, HTTP servers are known to
489 * support these mechanisms, we will maintain the current behavior
490 * where it is possible to send non-ASCII characters in their original
491 * unescaped form.
492 */
493 private static boolean isASCIISuperset(String encoding)
494 throws Exception {
495 String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
496 + "abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,";
497
498 // Expected byte sequence for string above
499 byte[] chkB = {
500 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71,
501 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
502 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
503 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 45,
504 95, 46, 33, 126, 42, 39, 40, 41, 59, 47, 63, 58, 64, 38, 61, 43, 36,
505 44
506 };
507
508 CharToByteConverter ctob = CharToByteConverter.getConverter(encoding);
509 byte[] b = ctob.convertAll(chkS.toCharArray());
510
511 return Arrays.equals(b, chkB);
512 }
513
514 /*** open a SMTP connection to host <i>host</i>. */
515 private void openServer(String host) throws IOException {
516 mailhost = host;
517 openServer(mailhost, 25);
518 issueCommand("helo " + InetAddress.getLocalHost().getHostName()
519 + "\r\n", 250);
520 }
521
522 /***
523 * DOCUMENT ME!
524 *
525 * @param s DOCUMENT ME!
526 *
527 * @throws IOException DOCUMENT ME!
528 */
529 private void toCanonical(String s) throws IOException {
530 if (s.startsWith("<")) {
531 issueCommand("rcpt to: " + s + "\r\n", 250);
532 } else {
533 issueCommand("rcpt to: <" + s + ">\r\n", 250);
534 }
535 }
536 }
537
538
539 /***
540 * DOCUMENT ME!
541 *
542 * @author $author$
543 * @version $Revision: 1.1 $
544 */
545 class SmtpPrintStream extends java.io.PrintStream {
546 //~ Instance fields --------------------------------------------------------
547
548 /*** DOCUMENT ME! */
549 private SMTPClient target;
550
551 /*** DOCUMENT ME! */
552 private int lastc = '\n';
553
554 //~ Constructors -----------------------------------------------------------
555
556 /***
557 * Creates a new SmtpPrintStream object.
558 *
559 * @param fos DOCUMENT ME!
560 * @param cl DOCUMENT ME!
561 *
562 * @throws UnsupportedEncodingException DOCUMENT ME!
563 */
564 SmtpPrintStream(OutputStream fos, SMTPClient cl)
565 throws UnsupportedEncodingException {
566 super(fos, false, cl.getEncoding());
567 target = cl;
568 }
569
570 //~ Methods ----------------------------------------------------------------
571
572 /***
573 * DOCUMENT ME!
574 */
575 public void close() {
576 if (target == null) {
577 return;
578 }
579
580 if (lastc != '\n') {
581 write('\n');
582 }
583
584 try {
585 target.issueCommand(".\r\n", 250);
586 target.message = null;
587 out = null;
588 target = null;
589 } catch (IOException e) {
590 }
591 }
592
593 /***
594 * DOCUMENT ME!
595 *
596 * @param s DOCUMENT ME!
597 */
598 public void print(String s) {
599 int len = s.length();
600
601 for (int i = 0; i < len; i++) {
602 write(s.charAt(i));
603 }
604 }
605
606 /***
607 * DOCUMENT ME!
608 *
609 * @param b DOCUMENT ME!
610 */
611 public void write(int b) {
612 try {
613 // quote a dot at the beginning of a line
614 if ((lastc == '\n') && (b == '.')) {
615 out.write('.');
616 }
617
618 // translate NL to CRLF
619 if ((b == '\n') && (lastc != '\r')) {
620 out.write('\r');
621 }
622
623 out.write(b);
624 lastc = b;
625 } catch (IOException e) {
626 }
627 }
628
629 /***
630 * DOCUMENT ME!
631 *
632 * @param b DOCUMENT ME!
633 * @param off DOCUMENT ME!
634 * @param len DOCUMENT ME!
635 */
636 public void write(byte[] b, int off, int len) {
637 try {
638 int lc = lastc;
639
640 while (--len >= 0) {
641 int c = b[off++];
642
643 // quote a dot at the beginning of a line
644 if ((lc == '\n') && (c == '.')) {
645 out.write('.');
646 }
647
648 // translate NL to CRLF
649 if ((c == '\n') && (lc != '\r')) {
650 out.write('\r');
651 }
652
653 out.write(c);
654 lc = c;
655 }
656
657 lastc = lc;
658 } catch (IOException e) {
659 }
660 }
661 }
662
This page was automatically generated by Maven