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 < 4) {
85 reset();
86 throw newDecoderException(buffer, readerIndex, readable);
87 }
88 final int code = parseCode(frame);
89 if (code < 0) {
90 reset();
91 throw newDecoderException(buffer, readerIndex, readable);
92 }
93 final int separator = frame.readByte();
94 final CharSequence detail = frame.isReadable() ? frame.toString(CharsetUtil.US_ASCII) : null;
95
96 List<CharSequence> details = this.details;
97
98 switch (separator) {
99 case ' ':
100
101 reset();
102 if (details != null) {
103 if (detail != null) {
104 details.add(detail);
105 }
106 } else {
107 if (detail == null) {
108 details = Collections.emptyList();
109 } else {
110 details = Collections.singletonList(detail);
111 }
112 }
113 return new DefaultSmtpResponse(code, details);
114 case '-':
115
116 if (detail != null) {
117 responseSize += (long) detail.length() + DETAIL_ENTRY_OVERHEAD;
118 if (responseSize > maxResponseSize) {
119 reset();
120 throw new TooLongFrameException("SMTP response exceeds " + maxResponseSize + " bytes");
121 }
122 if (details == null) {
123
124
125 this.details = details = new ArrayList<CharSequence>(4);
126 }
127 details.add(detail);
128 }
129 break;
130 default:
131 reset();
132 throw newDecoderException(buffer, readerIndex, readable);
133 }
134 } finally {
135 frame.release();
136 }
137 return null;
138 }
139
140 private void reset() {
141 this.details = null;
142 this.responseSize = 0;
143 }
144
145 private static DecoderException newDecoderException(ByteBuf buffer, int readerIndex, int readable) {
146 return new DecoderException(
147 "Received invalid line: '" + buffer.toString(readerIndex, readable, CharsetUtil.US_ASCII) + '\'');
148 }
149
150
151
152
153
154
155 private static int parseCode(ByteBuf buffer) {
156 final int first = parseNumber(buffer.readByte());
157 final int second = parseNumber(buffer.readByte());
158 final int third = parseNumber(buffer.readByte());
159 if (first < 0 || second < 0 || third < 0) {
160 return -1;
161 }
162 return first * 100 + second * 10 + third;
163 }
164
165 private static int parseNumber(byte b) {
166 return Character.digit((char) b, 10);
167 }
168 }