Repository: netty/netty
Issue: SmtpCommand lacks AUTH

### Expected behavior
I'm writing a smtp client based on netty. SmtpCommand lacks the AUTH command. It is used to login to an SMTP server.

See it in https://www.ietf.org/rfc/rfc4954.txt

Is it possible to add it ?
### Actual behavior
Can not create an AUTH command.

### Steps to reproduce

### Minimal yet complete reproducer code (or URL to code)

### Netty version
4.1.45.Final

### JVM version (e.g. `java -version`)
11.0.1
### OS version (e.g. `uname -a`)
Darwin Mac.local 19.2.0 Darwin Kernel Version 19.2.0: Sat Nov  9 03:47:04 PST 2019; root:xnu-6153.61.1~20/RELEASE_X86_64 x86_64


Issue comments:
We love love contributions so feel free to submit a PR if you have time. Otherwise it may take a bit before I will have a chance to do it 

> Am 05.02.2020 um 04:12 schrieb Ruwei <notifications@github.com>:
> 
> ﻿
> Expected behavior
> 
> I'm writing a smtp client based on netty. SmtpCommand lacks the AUTH command. It is used to login to an SMTP server.
> 
> See it in https://www.ietf.org/rfc/rfc4954.txt
> 
> Is it possible to add it ?
> 
> Actual behavior
> 
> Can not create an AUTH command.
> 
> Steps to reproduce
> 
> Minimal yet complete reproducer code (or URL to code)
> 
> Netty version
> 
> 4.1.45.Final
> 
> JVM version (e.g. java -version)
> 
> 11.0.1
> 
> OS version (e.g. uname -a)
> 
> Darwin Mac.local 19.2.0 Darwin Kernel Version 19.2.0: Sat Nov 9 03:47:04 PST 2019; root:xnu-6153.61.1~20/RELEASE_X86_64 x86_64
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or unsubscribe.

@normanmaurer  ok. I will do it :-)
After send AUTH, the client need to send encoded username and password without any command, but SmtpCommand is a required field for SmtpRequest. So I think it's necessary to add an EMPTY command which means a request with only parameters. WDYT? @normanmaurer 

Pull request review comments:

Patch that resolved the issue:
diff --git a/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpCommand.java b/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpCommand.java
index 63ba3f5847f..a2c69b866df 100644
--- a/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpCommand.java
+++ b/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpCommand.java
@@ -31,6 +31,7 @@
 public final class SmtpCommand {
     public static final SmtpCommand EHLO = new SmtpCommand(AsciiString.cached("EHLO"));
     public static final SmtpCommand HELO = new SmtpCommand(AsciiString.cached("HELO"));
+    public static final SmtpCommand AUTH = new SmtpCommand(AsciiString.cached("AUTH"));
     public static final SmtpCommand MAIL = new SmtpCommand(AsciiString.cached("MAIL"));
     public static final SmtpCommand RCPT = new SmtpCommand(AsciiString.cached("RCPT"));
     public static final SmtpCommand DATA = new SmtpCommand(AsciiString.cached("DATA"));
@@ -40,11 +41,13 @@ public final class SmtpCommand {
     public static final SmtpCommand VRFY = new SmtpCommand(AsciiString.cached("VRFY"));
     public static final SmtpCommand HELP = new SmtpCommand(AsciiString.cached("HELP"));
     public static final SmtpCommand QUIT = new SmtpCommand(AsciiString.cached("QUIT"));
+    public static final SmtpCommand EMPTY = new SmtpCommand(AsciiString.cached(""));
 
     private static final Map<String, SmtpCommand> COMMANDS = new HashMap<String, SmtpCommand>();
     static {
         COMMANDS.put(EHLO.name().toString(), EHLO);
         COMMANDS.put(HELO.name().toString(), HELO);
+        COMMANDS.put(AUTH.name().toString(), AUTH);
         COMMANDS.put(MAIL.name().toString(), MAIL);
         COMMANDS.put(RCPT.name().toString(), RCPT);
         COMMANDS.put(DATA.name().toString(), DATA);
@@ -54,6 +57,7 @@ public final class SmtpCommand {
         COMMANDS.put(VRFY.name().toString(), VRFY);
         COMMANDS.put(HELP.name().toString(), HELP);
         COMMANDS.put(QUIT.name().toString(), QUIT);
+        COMMANDS.put(EMPTY.name().toString(), EMPTY);
     }
 
     /**
diff --git a/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequestEncoder.java b/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequestEncoder.java
index 9fb567f9916..0355a296d8b 100644
--- a/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequestEncoder.java
+++ b/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequestEncoder.java
@@ -58,7 +58,8 @@ protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) t
             final ByteBuf buffer = ctx.alloc().buffer();
             try {
                 req.command().encode(buffer);
-                writeParameters(req.parameters(), buffer);
+                boolean notEmpty = req.command() != SmtpCommand.EMPTY;
+                writeParameters(req.parameters(), buffer, notEmpty);
                 ByteBufUtil.writeShortBE(buffer, CRLF_SHORT);
                 out.add(buffer);
                 release = false;
@@ -85,11 +86,13 @@ protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) t
         }
     }
 
-    private static void writeParameters(List<CharSequence> parameters, ByteBuf out) {
+    private static void writeParameters(List<CharSequence> parameters, ByteBuf out, boolean commandNotEmpty) {
         if (parameters.isEmpty()) {
             return;
         }
-        out.writeByte(SP);
+        if (commandNotEmpty) {
+            out.writeByte(SP);
+        }
         if (parameters instanceof RandomAccess) {
             final int sizeMinusOne = parameters.size() - 1;
             for (int i = 0; i < sizeMinusOne; i++) {
diff --git a/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequests.java b/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequests.java
index 446c6fb9ddf..b9da1a3efe3 100644
--- a/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequests.java
+++ b/codec-smtp/src/main/java/io/netty/handler/codec/smtp/SmtpRequests.java
@@ -50,6 +50,20 @@ public static SmtpRequest ehlo(CharSequence hostname) {
         return new DefaultSmtpRequest(SmtpCommand.EHLO, hostname);
     }
 
+    /**
+     * Creates a {@code EMPTY} request.
+     */
+    public static SmtpRequest empty(CharSequence... parameter) {
+        return new DefaultSmtpRequest(SmtpCommand.EMPTY, parameter);
+    }
+
+    /**
+     * Creates a {@code AUTH} request.
+     */
+    public static SmtpRequest auth(CharSequence... parameter) {
+        return new DefaultSmtpRequest(SmtpCommand.AUTH, parameter);
+    }
+
     /**
      * Creates a {@code NOOP} request.
      */


Test patch:
diff --git a/codec-smtp/src/test/java/io/netty/handler/codec/smtp/SmtpRequestEncoderTest.java b/codec-smtp/src/test/java/io/netty/handler/codec/smtp/SmtpRequestEncoderTest.java
index fb145149225..320bae4514d 100644
--- a/codec-smtp/src/test/java/io/netty/handler/codec/smtp/SmtpRequestEncoderTest.java
+++ b/codec-smtp/src/test/java/io/netty/handler/codec/smtp/SmtpRequestEncoderTest.java
@@ -38,6 +38,21 @@ public void testEncodeHelo() {
         testEncode(SmtpRequests.helo("localhost"), "HELO localhost\r\n");
     }
 
+    @Test
+    public void testEncodeAuth() {
+        testEncode(SmtpRequests.auth("LOGIN"), "AUTH LOGIN\r\n");
+    }
+
+    @Test
+    public void testEncodeAuthWithParameter() {
+        testEncode(SmtpRequests.auth("PLAIN", "dGVzdAB0ZXN0ADEyMzQ="), "AUTH PLAIN dGVzdAB0ZXN0ADEyMzQ=\r\n");
+    }
+
+    @Test
+    public void testEncodeEmpty() {
+        testEncode(SmtpRequests.empty("dGVzdAB0ZXN0ADEyMzQ="),  "dGVzdAB0ZXN0ADEyMzQ=\r\n");
+    }
+
     @Test
     public void testEncodeMail() {
         testEncode(SmtpRequests.mail("me@netty.io"), "MAIL FROM:<me@netty.io>\r\n");