1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.smtp;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.handler.codec.DecoderException;
21 import io.netty.handler.codec.LineBasedFrameDecoder;
22 import io.netty.handler.codec.TooLongFrameException;
23 import io.netty.util.CharsetUtil;
24 import io.netty.util.internal.ObjectUtil;
25 import io.netty.util.internal.UnstableApi;
26
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30
31
32
33
34 @UnstableApi
35 public final class SmtpResponseDecoder extends LineBasedFrameDecoder {
36
37 private static final int DEFAULT_MAX_RESPONSE_SIZE = 64 * 1024;
38
39
40 private static final int DETAIL_ENTRY_OVERHEAD = 48;
41
42 private final int maxResponseSize;
43
44 private List<CharSequence> details;
45 private long responseSize;
46
47
48
49
50
51
52
53 public SmtpResponseDecoder(int maxLineLength) {
54 this(maxLineLength, DEFAULT_MAX_RESPONSE_SIZE);
55 }
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public SmtpResponseDecoder(int maxLineLength, int maxResponseSize) {
70 super(maxLineLength);
71 this.maxResponseSize = ObjectUtil.checkPositive(maxResponseSize, "maxResponseSize");
72 }
73
74 @Override
75 protected SmtpResponse decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
76 ByteBuf frame = (ByteBuf) super.decode(ctx, buffer);
77 if (frame == null) {
78
79 return null;
80 }
81 try {
82 final int readable = frame.readableBytes();
83 final int readerIndex = frame.readerIndex();
84 if (readable < 3) {
85 reset();
86 throw newDecoderException(buffer, readerIndex, readable);
87 }
88 final int code = parseCode(frame);
89 final int separator = frame.readByte();
90 final CharSequence detail = frame.isReadable() ? frame.toString(CharsetUtil.US_ASCII) : null;
91
92 List<CharSequence> details = this.details;
93
94 switch (separator) {
95 case ' ':
96
97 reset();
98 if (details != null) {
99 if (detail != null) {
100 details.add(detail);
101 }
102 } else {
103 if (detail == null) {
104 details = Collections.emptyList();
105 } else {
106 details = Collections.singletonList(detail);
107 }
108 }
109 return new DefaultSmtpResponse(code, details);
110 case '-':
111
112 if (detail != null) {
113 responseSize += (long) detail.length() + DETAIL_ENTRY_OVERHEAD;
114 if (responseSize > maxResponseSize) {
115 reset();
116 throw new TooLongFrameException("SMTP response exceeds " + maxResponseSize + " bytes");
117 }
118 if (details == null) {
119
120
121 this.details = details = new ArrayList<CharSequence>(4);
122 }
123 details.add(detail);
124 }
125 break;
126 default:
127 reset();
128 throw newDecoderException(buffer, readerIndex, readable);
129 }
130 } finally {
131 frame.release();
132 }
133 return null;
134 }
135
136 private void reset() {
137 this.details = null;
138 this.responseSize = 0;
139 }
140
141 private static DecoderException newDecoderException(ByteBuf buffer, int readerIndex, int readable) {
142 return new DecoderException(
143 "Received invalid line: '" + buffer.toString(readerIndex, readable, CharsetUtil.US_ASCII) + '\'');
144 }
145
146
147
148
149 private static int parseCode(ByteBuf buffer) {
150 final int first = parseNumber(buffer.readByte()) * 100;
151 final int second = parseNumber(buffer.readByte()) * 10;
152 final int third = parseNumber(buffer.readByte());
153 return first + second + third;
154 }
155
156 private static int parseNumber(byte b) {
157 return Character.digit((char) b, 10);
158 }
159 }