aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/old/vlib/net/smtp
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/old/vlib/net/smtp')
-rw-r--r--v_windows/v/old/vlib/net/smtp/smtp.v190
-rw-r--r--v_windows/v/old/vlib/net/smtp/smtp_test.v89
2 files changed, 279 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/net/smtp/smtp.v b/v_windows/v/old/vlib/net/smtp/smtp.v
new file mode 100644
index 0000000..50c537c
--- /dev/null
+++ b/v_windows/v/old/vlib/net/smtp/smtp.v
@@ -0,0 +1,190 @@
+module smtp
+
+/*
+*
+* smtp module
+* Created by: nedimf (07/2020)
+*/
+import net
+import encoding.base64
+import strings
+import time
+import io
+
+const (
+ recv_size = 128
+)
+
+enum ReplyCode {
+ ready = 220
+ close = 221
+ auth_ok = 235
+ action_ok = 250
+ mail_start = 354
+}
+
+pub enum BodyType {
+ text
+ html
+}
+
+pub struct Client {
+mut:
+ conn net.TcpConn
+ reader io.BufferedReader
+pub:
+ server string
+ port int = 25
+ username string
+ password string
+ from string
+pub mut:
+ is_open bool
+}
+
+pub struct Mail {
+ from string
+ to string
+ cc string
+ bcc string
+ date time.Time = time.now()
+ subject string
+ body_type BodyType
+ body string
+}
+
+// new_client returns a new SMTP client and connects to it
+pub fn new_client(config Client) ?&Client {
+ mut c := &Client{
+ ...config
+ }
+ c.reconnect() ?
+ return c
+}
+
+// reconnect reconnects to the SMTP server if the connection was closed
+pub fn (mut c Client) reconnect() ? {
+ if c.is_open {
+ return error('Already connected to server')
+ }
+
+ conn := net.dial_tcp('$c.server:$c.port') or { return error('Connecting to server failed') }
+ c.conn = conn
+
+ c.reader = io.new_buffered_reader(reader: c.conn)
+
+ c.expect_reply(.ready) or { return error('Received invalid response from server') }
+ c.send_ehlo() or { return error('Sending EHLO packet failed') }
+ c.send_auth() or { return error('Authenticating to server failed') }
+ c.is_open = true
+}
+
+// send sends an email
+pub fn (mut c Client) send(config Mail) ? {
+ if !c.is_open {
+ return error('Disconnected from server')
+ }
+ from := if config.from != '' { config.from } else { c.from }
+ c.send_mailfrom(from) or { return error('Sending mailfrom failed') }
+ c.send_mailto(config.to) or { return error('Sending mailto failed') }
+ c.send_data() or { return error('Sending mail data failed') }
+ c.send_body(Mail{
+ ...config
+ from: from
+ }) or { return error('Sending mail body failed') }
+}
+
+// quit closes the connection to the server
+pub fn (mut c Client) quit() ? {
+ c.send_str('QUIT\r\n') ?
+ c.expect_reply(.close) ?
+ c.conn.close() ?
+ c.is_open = false
+}
+
+// expect_reply checks if the SMTP server replied with the expected reply code
+fn (mut c Client) expect_reply(expected ReplyCode) ? {
+ bytes := io.read_all(reader: c.conn) ?
+
+ str := bytes.bytestr().trim_space()
+ $if smtp_debug ? {
+ eprintln('\n\n[RECV]')
+ eprint(str)
+ }
+
+ if str.len >= 3 {
+ status := str[..3].int()
+ if ReplyCode(status) != expected {
+ return error('Received unexpected status code $status, expecting $expected')
+ }
+ } else {
+ return error('Recieved unexpected SMTP data: $str')
+ }
+}
+
+[inline]
+fn (mut c Client) send_str(s string) ? {
+ $if smtp_debug ? {
+ eprintln('\n\n[SEND START]')
+ eprint(s.trim_space())
+ eprintln('\n[SEND END]')
+ }
+ c.conn.write(s.bytes()) ?
+}
+
+[inline]
+fn (mut c Client) send_ehlo() ? {
+ c.send_str('EHLO $c.server\r\n') ?
+ c.expect_reply(.action_ok) ?
+}
+
+[inline]
+fn (mut c Client) send_auth() ? {
+ if c.username.len == 0 {
+ return
+ }
+ mut sb := strings.new_builder(100)
+ sb.write_b(0)
+ sb.write_string(c.username)
+ sb.write_b(0)
+ sb.write_string(c.password)
+ a := sb.str()
+ auth := 'AUTH PLAIN ${base64.encode_str(a)}\r\n'
+ c.send_str(auth) ?
+ c.expect_reply(.auth_ok) ?
+}
+
+fn (mut c Client) send_mailfrom(from string) ? {
+ c.send_str('MAIL FROM: <$from>\r\n') ?
+ c.expect_reply(.action_ok) ?
+}
+
+fn (mut c Client) send_mailto(to string) ? {
+ c.send_str('RCPT TO: <$to>\r\n') ?
+ c.expect_reply(.action_ok) ?
+}
+
+fn (mut c Client) send_data() ? {
+ c.send_str('DATA\r\n') ?
+ c.expect_reply(.mail_start) ?
+}
+
+fn (mut c Client) send_body(cfg Mail) ? {
+ is_html := cfg.body_type == .html
+ date := cfg.date.utc_string().trim_right(' UTC') // TODO
+ mut sb := strings.new_builder(200)
+ sb.write_string('From: $cfg.from\r\n')
+ sb.write_string('To: <$cfg.to>\r\n')
+ sb.write_string('Cc: <$cfg.cc>\r\n')
+ sb.write_string('Bcc: <$cfg.bcc>\r\n')
+ sb.write_string('Date: $date\r\n')
+ sb.write_string('Subject: $cfg.subject\r\n')
+ if is_html {
+ sb.write_string('Content-Type: text/html; charset=ISO-8859-1')
+ }
+ sb.write_string('\r\n\r\n')
+ sb.write_string(cfg.body)
+ sb.write_string('\r\n.\r\n')
+ c.send_str(sb.str()) ?
+ c.expect_reply(.action_ok) ?
+}
diff --git a/v_windows/v/old/vlib/net/smtp/smtp_test.v b/v_windows/v/old/vlib/net/smtp/smtp_test.v
new file mode 100644
index 0000000..d975e57
--- /dev/null
+++ b/v_windows/v/old/vlib/net/smtp/smtp_test.v
@@ -0,0 +1,89 @@
+import os
+import net.smtp
+import time
+
+// Used to test that a function call returns an error
+fn fn_errors(mut c smtp.Client, m smtp.Mail) bool {
+ c.send(m) or { return true }
+ return false
+}
+
+/*
+*
+* smtp_test
+* Created by: nedimf (07/2020)
+*/
+fn test_smtp() {
+ $if !network ? {
+ return
+ }
+
+ client_cfg := smtp.Client{
+ server: 'smtp.mailtrap.io'
+ from: 'dev@vlang.io'
+ username: os.getenv('VSMTP_TEST_USER')
+ password: os.getenv('VSMTP_TEST_PASS')
+ }
+ if client_cfg.username == '' && client_cfg.password == '' {
+ eprintln('Please set VSMTP_TEST_USER and VSMTP_TEST_PASS before running this test')
+ exit(0)
+ }
+ send_cfg := smtp.Mail{
+ to: 'dev@vlang.io'
+ subject: 'Hello from V2'
+ body: 'Plain text'
+ }
+
+ mut client := smtp.new_client(client_cfg) or {
+ assert false
+ return
+ }
+ assert true
+ client.send(send_cfg) or {
+ assert false
+ return
+ }
+ assert true
+ client.send(smtp.Mail{
+ ...send_cfg
+ from: 'alexander@vlang.io'
+ }) or {
+ assert false
+ return
+ }
+ client.send(smtp.Mail{
+ ...send_cfg
+ cc: 'alexander@vlang.io,joe@vlang.io'
+ bcc: 'spytheman@vlang.io'
+ }) or {
+ assert false
+ return
+ }
+ client.send(smtp.Mail{
+ ...send_cfg
+ date: time.now().add_days(1000)
+ }) or {
+ assert false
+ return
+ }
+ assert true
+ client.quit() or {
+ assert false
+ return
+ }
+ assert true
+ // This call should return an error, since the connection is closed
+ if !fn_errors(mut client, send_cfg) {
+ assert false
+ return
+ }
+ client.reconnect() or {
+ assert false
+ return
+ }
+ client.send(send_cfg) or {
+ assert false
+ return
+ }
+ assert true
+}