Add smtp codec (client side only).
Motivation: When writing a SMTP client a provided SMTP codec that follows RFC2821 is useful. Modification: Add client side codec and test. Results: People who want to write a SMTP client can reuse the codec.
This commit is contained in:
parent
24254b159f
commit
562d8d2200
39
codec-smtp/pom.xml
Normal file
39
codec-smtp/pom.xml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2016 The Netty Project
|
||||||
|
~
|
||||||
|
~ The Netty Project licenses this file to you under the Apache License,
|
||||||
|
~ version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
~ with the License. You may obtain a copy of the License at:
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
~ License for the specific language governing permissions and limitations
|
||||||
|
~ under the License.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-parent</artifactId>
|
||||||
|
<version>4.1.0.Final-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>netty-codec-smtp</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Netty/Codec/SMTP</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>${project.groupId}</groupId>
|
||||||
|
<artifactId>netty-codec</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of {@link LastSmtpContent} that does no validation of the raw data passed in.
|
||||||
|
*/
|
||||||
|
public final class DefaultLastSmtpContent extends DefaultSmtpContent implements LastSmtpContent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance using the given data.
|
||||||
|
*/
|
||||||
|
public DefaultLastSmtpContent(ByteBuf data) {
|
||||||
|
super(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent copy() {
|
||||||
|
return new DefaultLastSmtpContent(content().copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent duplicate() {
|
||||||
|
return new DefaultLastSmtpContent(content().duplicate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent retain() {
|
||||||
|
super.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent retain(int increment) {
|
||||||
|
super.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent touch() {
|
||||||
|
super.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent touch(Object hint) {
|
||||||
|
super.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.DefaultByteBufHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of {@link SmtpContent} that does no validation of the raw data passed in.
|
||||||
|
*/
|
||||||
|
public class DefaultSmtpContent extends DefaultByteBufHolder implements SmtpContent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance using the given data.
|
||||||
|
*/
|
||||||
|
public DefaultSmtpContent(ByteBuf data) {
|
||||||
|
super(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmtpContent copy() {
|
||||||
|
return new DefaultSmtpContent(content().copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmtpContent duplicate() {
|
||||||
|
return new DefaultSmtpContent(content().duplicate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmtpContent retain() {
|
||||||
|
super.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmtpContent retain(int increment) {
|
||||||
|
super.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmtpContent touch() {
|
||||||
|
super.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmtpContent touch(Object hint) {
|
||||||
|
super.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default {@link SmtpRequest} implementation.
|
||||||
|
*/
|
||||||
|
public final class DefaultSmtpRequest implements SmtpRequest {
|
||||||
|
|
||||||
|
private final SmtpCommand command;
|
||||||
|
private final List<CharSequence> parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given command and no parameters.
|
||||||
|
*/
|
||||||
|
public DefaultSmtpRequest(SmtpCommand command) {
|
||||||
|
this.command = ObjectUtil.checkNotNull(command, "command");
|
||||||
|
parameters = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given command and parameters.
|
||||||
|
*/
|
||||||
|
public DefaultSmtpRequest(SmtpCommand command, CharSequence... parameters) {
|
||||||
|
this.command = ObjectUtil.checkNotNull(command, "command");
|
||||||
|
this.parameters = SmtpUtils.toUnmodifiableList(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given command and parameters.
|
||||||
|
*/
|
||||||
|
public DefaultSmtpRequest(CharSequence command, CharSequence... parameters) {
|
||||||
|
this(SmtpCommand.valueOf(command), parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultSmtpRequest(SmtpCommand command, List<CharSequence> parameters) {
|
||||||
|
this.command = ObjectUtil.checkNotNull(command, "command");
|
||||||
|
this.parameters = parameters != null ?
|
||||||
|
Collections.unmodifiableList(parameters) : Collections.<CharSequence>emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SmtpCommand command() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CharSequence> parameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return command.hashCode() * 31 + parameters.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof DefaultSmtpRequest)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultSmtpRequest other = (DefaultSmtpRequest) o;
|
||||||
|
|
||||||
|
return command().equals(other.command()) &&
|
||||||
|
parameters().equals(other.parameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DefaultSmtpRequest{" +
|
||||||
|
"command=" + command +
|
||||||
|
", parameters=" + parameters +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default {@link SmtpResponse} implementation.
|
||||||
|
*/
|
||||||
|
public final class DefaultSmtpResponse implements SmtpResponse {
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final List<CharSequence> details;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given smtp code and no details.
|
||||||
|
*/
|
||||||
|
public DefaultSmtpResponse(int code) {
|
||||||
|
this(code, (List<CharSequence>) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given smtp code and details.
|
||||||
|
*/
|
||||||
|
public DefaultSmtpResponse(int code, CharSequence... details) {
|
||||||
|
this(code, SmtpUtils.toUnmodifiableList(details));
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultSmtpResponse(int code, List<CharSequence> details) {
|
||||||
|
if (code < 100 || code > 599) {
|
||||||
|
throw new IllegalArgumentException("code must be 100 <= code <= 599");
|
||||||
|
}
|
||||||
|
this.code = code;
|
||||||
|
if (details == null) {
|
||||||
|
this.details = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
this.details = Collections.unmodifiableList(details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int code() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CharSequence> details() {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return code * 31 + details.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof DefaultSmtpResponse)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultSmtpResponse other = (DefaultSmtpResponse) o;
|
||||||
|
|
||||||
|
return code() == other.code() &&
|
||||||
|
details().equals(other.details());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DefaultSmtpResponse{" +
|
||||||
|
"code=" + code +
|
||||||
|
", details=" + details +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last part of a sequence of {@link SmtpContent}s that are sent after a {@code DATA} request.
|
||||||
|
* Be aware that a {@link SmtpContent} / {@link LastSmtpContent} sequence must always use CRLF as line delimiter
|
||||||
|
* and the lines that start with a DOT must be escaped with an extra DOT as
|
||||||
|
* specified by <a href="https://www.ietf.org/rfc/rfc2821.txt">RFC2821</a>.
|
||||||
|
*/
|
||||||
|
public interface LastSmtpContent extends SmtpContent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty {@link LastSmtpContent}.
|
||||||
|
*/
|
||||||
|
LastSmtpContent EMPTY_LAST_CONTENT = new LastSmtpContent() {
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent copy() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent duplicate() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent retain() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent retain(int increment) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent touch() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LastSmtpContent touch(Object hint) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf content() {
|
||||||
|
return Unpooled.EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refCnt() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release(int decrement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
LastSmtpContent copy();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
LastSmtpContent duplicate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
LastSmtpContent retain();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
LastSmtpContent retain(int increment);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
LastSmtpContent touch();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
LastSmtpContent touch(Object hint);
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufUtil;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command part of a {@link SmtpRequest}.
|
||||||
|
*/
|
||||||
|
public final class SmtpCommand {
|
||||||
|
public static final SmtpCommand EHLO = new SmtpCommand(new AsciiString("EHLO"), false);
|
||||||
|
public static final SmtpCommand HELO = new SmtpCommand(new AsciiString("HELO"), false);
|
||||||
|
public static final SmtpCommand MAIL = new SmtpCommand(new AsciiString("MAIL"), false);
|
||||||
|
public static final SmtpCommand RCPT = new SmtpCommand(new AsciiString("RCPT"), false);
|
||||||
|
public static final SmtpCommand DATA = new SmtpCommand(new AsciiString("DATA"), true);
|
||||||
|
public static final SmtpCommand NOOP = new SmtpCommand(new AsciiString("NOOP"), false);
|
||||||
|
public static final SmtpCommand RSET = new SmtpCommand(new AsciiString("RSET"), false);
|
||||||
|
public static final SmtpCommand EXPN = new SmtpCommand(new AsciiString("EXPN"), false);
|
||||||
|
public static final SmtpCommand VRFY = new SmtpCommand(new AsciiString("VRFY"), false);
|
||||||
|
public static final SmtpCommand HELP = new SmtpCommand(new AsciiString("HELP"), false);
|
||||||
|
public static final SmtpCommand QUIT = new SmtpCommand(new AsciiString("QUIT"), false);
|
||||||
|
|
||||||
|
private static final CharSequence DATA_CMD = new AsciiString("DATA");
|
||||||
|
private static final Map<CharSequence, SmtpCommand> COMMANDS = new HashMap<CharSequence, SmtpCommand>();
|
||||||
|
static {
|
||||||
|
COMMANDS.put(EHLO.name(), EHLO);
|
||||||
|
COMMANDS.put(HELO.name(), HELO);
|
||||||
|
COMMANDS.put(MAIL.name(), MAIL);
|
||||||
|
COMMANDS.put(RCPT.name(), RCPT);
|
||||||
|
COMMANDS.put(DATA.name(), DATA);
|
||||||
|
COMMANDS.put(NOOP.name(), NOOP);
|
||||||
|
COMMANDS.put(RSET.name(), RSET);
|
||||||
|
COMMANDS.put(EXPN.name(), EXPN);
|
||||||
|
COMMANDS.put(VRFY.name(), VRFY);
|
||||||
|
COMMANDS.put(HELP.name(), HELP);
|
||||||
|
COMMANDS.put(QUIT.name(), QUIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link SmtpCommand} for the given command name.
|
||||||
|
*/
|
||||||
|
public static SmtpCommand valueOf(CharSequence commandName) {
|
||||||
|
SmtpCommand command = COMMANDS.get(commandName);
|
||||||
|
if (command != null) {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
return new SmtpCommand(AsciiString.of(ObjectUtil.checkNotNull(commandName, "commandName")),
|
||||||
|
AsciiString.contentEqualsIgnoreCase(commandName, DATA_CMD));
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AsciiString name;
|
||||||
|
private final boolean contentExpected;
|
||||||
|
private int hashCode;
|
||||||
|
|
||||||
|
private SmtpCommand(AsciiString name, boolean contentExpected) {
|
||||||
|
this.name = name;
|
||||||
|
this.contentExpected = contentExpected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the command name.
|
||||||
|
*/
|
||||||
|
public AsciiString name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void encode(ByteBuf buffer) {
|
||||||
|
ByteBufUtil.writeAscii(buffer, name());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isContentExpected() {
|
||||||
|
return contentExpected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
if (hashCode != -1) {
|
||||||
|
hashCode = AsciiString.hashCode(name);
|
||||||
|
}
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof SmtpCommand)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return name.contentEqualsIgnoreCase(((SmtpCommand) obj).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SmtpCommand{" +
|
||||||
|
"name=" + name +
|
||||||
|
", contentExpected=" + contentExpected +
|
||||||
|
", hashCode=" + hashCode +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content that is sent after the {@code DATA} request.
|
||||||
|
* Be aware that a {@link SmtpContent} / {@link LastSmtpContent} sequence must always use CRLF as line delimiter
|
||||||
|
* and the lines that start with a DOT must be escaped with an extra DOT as
|
||||||
|
* specified by <a href="https://www.ietf.org/rfc/rfc2821.txt">RFC2821</a>.
|
||||||
|
*/
|
||||||
|
public interface SmtpContent extends ByteBufHolder {
|
||||||
|
@Override
|
||||||
|
SmtpContent copy();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SmtpContent duplicate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SmtpContent retain();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SmtpContent retain(int increment);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SmtpContent touch();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SmtpContent touch(Object hint);
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An SMTP request.
|
||||||
|
*/
|
||||||
|
public interface SmtpRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link SmtpCommand} that belongs to the request.
|
||||||
|
*/
|
||||||
|
SmtpCommand command();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link List} which holds all the parameters of a request, which may be an empty list.
|
||||||
|
*/
|
||||||
|
List<CharSequence> parameters();
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufUtil;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.RandomAccess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encoder for SMTP requests.
|
||||||
|
*/
|
||||||
|
public final class SmtpRequestEncoder extends MessageToMessageEncoder<Object> {
|
||||||
|
private static final byte[] CRLF = {'\r', '\n'};
|
||||||
|
private static final byte[] DOT_CRLF = {'.', '\r', '\n'};
|
||||||
|
private static final byte SP = ' ';
|
||||||
|
private static final ByteBuf DOT_CRLF_BUFFER = Unpooled.unreleasableBuffer(
|
||||||
|
Unpooled.directBuffer(3).writeBytes(DOT_CRLF));
|
||||||
|
|
||||||
|
private boolean contentExpected;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptOutboundMessage(Object msg) throws Exception {
|
||||||
|
return msg instanceof SmtpRequest || msg instanceof SmtpContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
|
||||||
|
if (msg instanceof SmtpRequest) {
|
||||||
|
if (contentExpected) {
|
||||||
|
throw new IllegalStateException("SmtpContent expected");
|
||||||
|
}
|
||||||
|
boolean release = true;
|
||||||
|
final ByteBuf buffer = ctx.alloc().buffer();
|
||||||
|
try {
|
||||||
|
final SmtpRequest req = (SmtpRequest) msg;
|
||||||
|
req.command().encode(buffer);
|
||||||
|
writeParameters(req.parameters(), buffer);
|
||||||
|
buffer.writeBytes(CRLF);
|
||||||
|
out.add(buffer);
|
||||||
|
release = false;
|
||||||
|
if (req.command().isContentExpected()) {
|
||||||
|
contentExpected = true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (release) {
|
||||||
|
buffer.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg instanceof SmtpContent) {
|
||||||
|
if (!contentExpected) {
|
||||||
|
throw new IllegalStateException("No SmtpContent expected");
|
||||||
|
}
|
||||||
|
final ByteBuf content = ((SmtpContent) msg).content();
|
||||||
|
out.add(content.retain());
|
||||||
|
if (msg instanceof LastSmtpContent) {
|
||||||
|
out.add(DOT_CRLF_BUFFER.duplicate().retain());
|
||||||
|
contentExpected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeParameters(List<CharSequence> parameters, ByteBuf out) {
|
||||||
|
if (parameters.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out.writeByte(SP);
|
||||||
|
if (parameters instanceof RandomAccess) {
|
||||||
|
final int sizeMinusOne = parameters.size() - 1;
|
||||||
|
for (int i = 0; i < sizeMinusOne; i++) {
|
||||||
|
ByteBufUtil.writeAscii(out, parameters.get(i));
|
||||||
|
out.writeByte(SP);
|
||||||
|
}
|
||||||
|
ByteBufUtil.writeAscii(out, parameters.get(sizeMinusOne));
|
||||||
|
} else {
|
||||||
|
final Iterator<CharSequence> params = parameters.iterator();
|
||||||
|
for (;;) {
|
||||||
|
ByteBufUtil.writeAscii(out, params.next());
|
||||||
|
if (params.hasNext()) {
|
||||||
|
out.writeByte(SP);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides utility methods to create {@link SmtpRequest}s.
|
||||||
|
*/
|
||||||
|
public final class SmtpRequests {
|
||||||
|
|
||||||
|
private static final SmtpRequest DATA = new DefaultSmtpRequest(SmtpCommand.DATA);
|
||||||
|
private static final SmtpRequest NOOP = new DefaultSmtpRequest(SmtpCommand.NOOP);
|
||||||
|
private static final SmtpRequest RSET = new DefaultSmtpRequest(SmtpCommand.RSET);
|
||||||
|
private static final SmtpRequest HELP_NO_ARG = new DefaultSmtpRequest(SmtpCommand.HELP);
|
||||||
|
private static final SmtpRequest QUIT = new DefaultSmtpRequest(SmtpCommand.QUIT);
|
||||||
|
private static final AsciiString FROM_NULL_SENDER = new AsciiString("FROM:<>");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code HELO} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest helo(CharSequence hostname) {
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.HELO, hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code EHLO} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest ehlo(CharSequence hostname) {
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.EHLO, hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code NOOP} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest noop() {
|
||||||
|
return NOOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code DATA} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest data() {
|
||||||
|
return DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code RSET} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest rset() {
|
||||||
|
return RSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code HELP} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest help(String cmd) {
|
||||||
|
return cmd == null ? HELP_NO_ARG : new DefaultSmtpRequest(SmtpCommand.HELP, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code QUIT} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest quit() {
|
||||||
|
return QUIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code MAIL} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest mail(CharSequence sender, CharSequence... mailParameters) {
|
||||||
|
if (mailParameters == null || mailParameters.length == 0) {
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.MAIL,
|
||||||
|
sender != null ? "FROM:<" + sender + '>' : FROM_NULL_SENDER);
|
||||||
|
} else {
|
||||||
|
List<CharSequence> params = new ArrayList<CharSequence>(mailParameters.length + 1);
|
||||||
|
params.add(sender != null? "FROM:<" + sender + '>' : FROM_NULL_SENDER);
|
||||||
|
for (CharSequence param : mailParameters) {
|
||||||
|
params.add(param);
|
||||||
|
}
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.MAIL, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code RCPT} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest rcpt(CharSequence recipient, CharSequence... rcptParameters) {
|
||||||
|
ObjectUtil.checkNotNull(recipient, "recipient");
|
||||||
|
if (rcptParameters == null || rcptParameters.length == 0) {
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.RCPT, "TO:<" + recipient + '>');
|
||||||
|
} else {
|
||||||
|
List<CharSequence> params = new ArrayList<CharSequence>(rcptParameters.length + 1);
|
||||||
|
params.add("TO:<" + recipient + '>');
|
||||||
|
for (CharSequence param : rcptParameters) {
|
||||||
|
params.add(param);
|
||||||
|
}
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.RCPT, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code EXPN} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest expn(CharSequence mailingList) {
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.EXPN, ObjectUtil.checkNotNull(mailingList, "mailingList"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code VRFY} request.
|
||||||
|
*/
|
||||||
|
public static SmtpRequest vrfy(CharSequence user) {
|
||||||
|
return new DefaultSmtpRequest(SmtpCommand.VRFY, ObjectUtil.checkNotNull(user, "user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SmtpRequests() { }
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SMTP response
|
||||||
|
*/
|
||||||
|
public interface SmtpResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the response code.
|
||||||
|
*/
|
||||||
|
int code();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the details if any.
|
||||||
|
*/
|
||||||
|
List<CharSequence> details();
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.DecoderException;
|
||||||
|
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder for SMTP responses.
|
||||||
|
*/
|
||||||
|
public final class SmtpResponseDecoder extends LineBasedFrameDecoder {
|
||||||
|
|
||||||
|
private List<CharSequence> details;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance that enforces the given {@code maxLineLength}.
|
||||||
|
*/
|
||||||
|
public SmtpResponseDecoder(int maxLineLength) {
|
||||||
|
super(maxLineLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SmtpResponse decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
|
||||||
|
ByteBuf frame = (ByteBuf) super.decode(ctx, buffer);
|
||||||
|
if (frame == null) {
|
||||||
|
// No full line received yet.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final int readable = frame.readableBytes();
|
||||||
|
final int readerIndex = frame.readerIndex();
|
||||||
|
if (readable < 3) {
|
||||||
|
throw newDecoderException(buffer, readerIndex, readable);
|
||||||
|
}
|
||||||
|
final int code = parseCode(frame);
|
||||||
|
final int separator = frame.readByte();
|
||||||
|
final CharSequence detail = frame.isReadable() ? frame.toString(CharsetUtil.US_ASCII) : null;
|
||||||
|
|
||||||
|
List<CharSequence> details = this.details;
|
||||||
|
|
||||||
|
switch (separator) {
|
||||||
|
case ' ':
|
||||||
|
// Marks the end of a response.
|
||||||
|
this.details = null;
|
||||||
|
if (details != null) {
|
||||||
|
if (detail != null) {
|
||||||
|
details.add(detail);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
details = Collections.singletonList(detail);
|
||||||
|
}
|
||||||
|
return new DefaultSmtpResponse(code, details);
|
||||||
|
case '-':
|
||||||
|
// Multi-line response.
|
||||||
|
if (detail != null) {
|
||||||
|
if (details == null) {
|
||||||
|
// Using initial capacity as it is very unlikely that we will receive a multi-line response
|
||||||
|
// with more then 3 lines.
|
||||||
|
this.details = details = new ArrayList<CharSequence>(4);
|
||||||
|
}
|
||||||
|
details.add(detail);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw newDecoderException(buffer, readerIndex, readable);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
frame.release();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DecoderException newDecoderException(ByteBuf buffer, int readerIndex, int readable) {
|
||||||
|
return new DecoderException(
|
||||||
|
"Received invalid line: '" + buffer.toString(readerIndex, readable, CharsetUtil.US_ASCII) + '\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the io.netty.handler.codec.smtp code without any allocation, which is three digits.
|
||||||
|
*/
|
||||||
|
private static int parseCode(ByteBuf buffer) {
|
||||||
|
final int first = parseNumber(buffer.readByte()) * 100;
|
||||||
|
final int second = parseNumber(buffer.readByte()) * 10;
|
||||||
|
final int third = parseNumber(buffer.readByte());
|
||||||
|
return first + second + third;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseNumber(byte b) {
|
||||||
|
return Character.digit((char) b, 10);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
final class SmtpUtils {
|
||||||
|
|
||||||
|
static List<CharSequence> toUnmodifiableList(CharSequence... sequences) {
|
||||||
|
if (sequences == null || sequences.length == 0) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(Arrays.asList(sequences));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SmtpUtils() { }
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://www.ietf.org/rfc/rfc2821.txt">SMTP</a> codec.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class SmtpRequestEncoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeEhlo() {
|
||||||
|
testEncode(SmtpRequests.ehlo("localhost"), "EHLO localhost\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeHelo() {
|
||||||
|
testEncode(SmtpRequests.helo("localhost"), "HELO localhost\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeMail() {
|
||||||
|
testEncode(SmtpRequests.mail("me@netty.io"), "MAIL FROM:<me@netty.io>\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeMailNullSender() {
|
||||||
|
testEncode(SmtpRequests.mail(null), "MAIL FROM:<>\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeRcpt() {
|
||||||
|
testEncode(SmtpRequests.rcpt("me@netty.io"), "RCPT TO:<me@netty.io>\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeNoop() {
|
||||||
|
testEncode(SmtpRequests.noop(), "NOOP\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeRset() {
|
||||||
|
testEncode(SmtpRequests.rset(), "RSET\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeHelp() {
|
||||||
|
testEncode(SmtpRequests.help(null), "HELP\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeHelpWithArg() {
|
||||||
|
testEncode(SmtpRequests.help("MAIL"), "HELP MAIL\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeData() {
|
||||||
|
testEncode(SmtpRequests.data(), "DATA\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeDataAndContent() {
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel(new SmtpRequestEncoder());
|
||||||
|
assertTrue(channel.writeOutbound(SmtpRequests.data()));
|
||||||
|
assertTrue(channel.writeOutbound(
|
||||||
|
new DefaultSmtpContent(Unpooled.copiedBuffer("Subject: Test\r\n\r\n", CharsetUtil.US_ASCII))));
|
||||||
|
assertTrue(channel.writeOutbound(
|
||||||
|
new DefaultLastSmtpContent(Unpooled.copiedBuffer("Test\r\n", CharsetUtil.US_ASCII))));
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
|
||||||
|
ByteBuf written = Unpooled.buffer();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ByteBuf buffer = channel.readOutbound();
|
||||||
|
if (buffer == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
written.writeBytes(buffer);
|
||||||
|
buffer.release();
|
||||||
|
}
|
||||||
|
assertEquals("DATA\r\nSubject: Test\r\n\r\nTest\r\n.\r\n", written.toString(CharsetUtil.US_ASCII));
|
||||||
|
written.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testEncode(SmtpRequest request, String expected) {
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel(new SmtpRequestEncoder());
|
||||||
|
assertTrue(channel.writeOutbound(request));
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
ByteBuf buffer = channel.readOutbound();
|
||||||
|
assertEquals(expected, buffer.toString(CharsetUtil.US_ASCII));
|
||||||
|
buffer.release();
|
||||||
|
assertNull(channel.readOutbound());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.smtp;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import io.netty.handler.codec.DecoderException;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class SmtpResponseDecoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeOneLineResponse() {
|
||||||
|
EmbeddedChannel channel = newChannel();
|
||||||
|
assertTrue(channel.writeInbound(newBuffer("200 Ok\r\n")));
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
|
||||||
|
SmtpResponse response = channel.readInbound();
|
||||||
|
assertEquals(200, response.code());
|
||||||
|
List<CharSequence> sequences = response.details();
|
||||||
|
assertEquals(1, sequences.size());
|
||||||
|
|
||||||
|
assertEquals("Ok", sequences.get(0).toString());
|
||||||
|
assertNull(channel.readInbound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeOneLineResponseChunked() {
|
||||||
|
EmbeddedChannel channel = newChannel();
|
||||||
|
assertFalse(channel.writeInbound(newBuffer("200 Ok")));
|
||||||
|
assertTrue(channel.writeInbound(newBuffer("\r\n")));
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
|
||||||
|
SmtpResponse response = channel.readInbound();
|
||||||
|
assertEquals(200, response.code());
|
||||||
|
List<CharSequence> sequences = response.details();
|
||||||
|
assertEquals(1, sequences.size());
|
||||||
|
|
||||||
|
assertEquals("Ok", sequences.get(0).toString());
|
||||||
|
assertNull(channel.readInbound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeTwoLineResponse() {
|
||||||
|
EmbeddedChannel channel = newChannel();
|
||||||
|
assertTrue(channel.writeInbound(newBuffer("200-Hello\r\n200 Ok\r\n")));
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
|
||||||
|
SmtpResponse response = channel.readInbound();
|
||||||
|
assertEquals(200, response.code());
|
||||||
|
List<CharSequence> sequences = response.details();
|
||||||
|
assertEquals(2, sequences.size());
|
||||||
|
|
||||||
|
assertEquals("Hello", sequences.get(0).toString());
|
||||||
|
assertEquals("Ok", sequences.get(1).toString());
|
||||||
|
assertNull(channel.readInbound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeTwoLineResponseChunked() {
|
||||||
|
EmbeddedChannel channel = newChannel();
|
||||||
|
assertFalse(channel.writeInbound(newBuffer("200-")));
|
||||||
|
assertFalse(channel.writeInbound(newBuffer("Hello\r\n2")));
|
||||||
|
assertFalse(channel.writeInbound(newBuffer("00 Ok")));
|
||||||
|
assertTrue(channel.writeInbound(newBuffer("\r\n")));
|
||||||
|
assertTrue(channel.finish());
|
||||||
|
|
||||||
|
SmtpResponse response = channel.readInbound();
|
||||||
|
assertEquals(200, response.code());
|
||||||
|
List<CharSequence> sequences = response.details();
|
||||||
|
assertEquals(2, sequences.size());
|
||||||
|
|
||||||
|
assertEquals("Hello", sequences.get(0).toString());
|
||||||
|
assertEquals("Ok", sequences.get(1).toString());
|
||||||
|
assertNull(channel.readInbound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DecoderException.class)
|
||||||
|
public void testDecodeInvalidSeparator() {
|
||||||
|
EmbeddedChannel channel = newChannel();
|
||||||
|
assertTrue(channel.writeInbound(newBuffer("200:Ok\r\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DecoderException.class)
|
||||||
|
public void testDecodeInvalidCode() {
|
||||||
|
EmbeddedChannel channel = newChannel();
|
||||||
|
assertTrue(channel.writeInbound(newBuffer("xyz Ok\r\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DecoderException.class)
|
||||||
|
public void testDecodeInvalidLine() {
|
||||||
|
EmbeddedChannel channel = newChannel();
|
||||||
|
assertTrue(channel.writeInbound(newBuffer("Ok\r\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EmbeddedChannel newChannel() {
|
||||||
|
return new EmbeddedChannel(new SmtpResponseDecoder(Integer.MAX_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ByteBuf newBuffer(CharSequence seq) {
|
||||||
|
return Unpooled.copiedBuffer(seq, CharsetUtil.US_ASCII);
|
||||||
|
}
|
||||||
|
}
|
1
pom.xml
1
pom.xml
@ -239,6 +239,7 @@
|
|||||||
<module>codec-http2</module>
|
<module>codec-http2</module>
|
||||||
<module>codec-memcache</module>
|
<module>codec-memcache</module>
|
||||||
<module>codec-mqtt</module>
|
<module>codec-mqtt</module>
|
||||||
|
<module>codec-smtp</module>
|
||||||
<module>codec-socks</module>
|
<module>codec-socks</module>
|
||||||
<module>codec-stomp</module>
|
<module>codec-stomp</module>
|
||||||
<module>codec-xml</module>
|
<module>codec-xml</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user