1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import io.netty.util.ByteProcessor;
19
20 import java.util.BitSet;
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 final class HttpChunkLineValidatingByteProcessor implements ByteProcessor {
56 private static final int SIZE = 0;
57 private static final int CHUNK_EXT_NAME = 1;
58 private static final int CHUNK_EXT_VAL_START = 2;
59 private static final int CHUNK_EXT_VAL_QUOTED = 3;
60 private static final int CHUNK_EXT_VAL_QUOTED_ESCAPE = 4;
61 private static final int CHUNK_EXT_VAL_QUOTED_END = 5;
62 private static final int CHUNK_EXT_VAL_TOKEN = 6;
63
64 static final class Match extends BitSet {
65 private static final long serialVersionUID = 49522994383099834L;
66 private final int then;
67
68 Match(int then) {
69 super(256);
70 this.then = then;
71 }
72
73 Match chars(String chars) {
74 return chars(chars, true);
75 }
76
77 Match chars(String chars, boolean value) {
78 for (int i = 0, len = chars.length(); i < len; i++) {
79 set(chars.charAt(i), value);
80 }
81 return this;
82 }
83
84 Match range(int from, int to) {
85 return range(from, to, true);
86 }
87
88 Match range(int from, int to, boolean value) {
89 for (int i = from; i <= to; i++) {
90 set(i, value);
91 }
92 return this;
93 }
94 }
95
96 private enum State {
97 Size(
98 new Match(SIZE).chars("0123456789abcdefABCDEF \t"),
99 new Match(CHUNK_EXT_NAME).chars(";")),
100 ChunkExtName(
101 new Match(CHUNK_EXT_NAME)
102 .range(0x21, 0x7E)
103 .chars(" \t")
104 .chars("(),/:<=>?@[\\]{}", false),
105 new Match(CHUNK_EXT_VAL_START).chars("=")),
106 ChunkExtValStart(
107 new Match(CHUNK_EXT_VAL_START).chars(" \t"),
108 new Match(CHUNK_EXT_VAL_QUOTED).chars("\""),
109 new Match(CHUNK_EXT_VAL_TOKEN)
110 .range(0x21, 0x7E)
111 .chars("(),/:<=>?@[\\]{}", false)),
112 ChunkExtValQuoted(
113 new Match(CHUNK_EXT_VAL_QUOTED_ESCAPE).chars("\\"),
114 new Match(CHUNK_EXT_VAL_QUOTED_END).chars("\""),
115 new Match(CHUNK_EXT_VAL_QUOTED)
116 .chars("\t !")
117 .range(0x23, 0x5B)
118 .range(0x5D, 0x7E)
119 .range(0x80, 0xFF)),
120 ChunkExtValQuotedEscape(
121 new Match(CHUNK_EXT_VAL_QUOTED)
122 .chars("\t ")
123 .range(0x21, 0x7E)
124 .range(0x80, 0xFF)),
125 ChunkExtValQuotedEnd(
126 new Match(CHUNK_EXT_VAL_QUOTED_END).chars("\t "),
127 new Match(CHUNK_EXT_NAME).chars(";")),
128 ChunkExtValToken(
129 new Match(CHUNK_EXT_VAL_TOKEN)
130 .range(0x21, 0x7E, true)
131 .chars("(),/:<=>?@[\\]{}", false),
132 new Match(CHUNK_EXT_NAME).chars(";")),
133 ;
134
135 private final Match[] matches;
136
137 State(Match... matches) {
138 this.matches = matches;
139 }
140
141 State match(byte value) {
142 for (Match match : matches) {
143 if (match.get(value)) {
144 return STATES_BY_ORDINAL[match.then];
145 }
146 }
147 if (this == Size) {
148 throw new NumberFormatException("Invalid chunk size");
149 } else {
150 throw new InvalidChunkExtensionException("Invalid chunk extension");
151 }
152 }
153 }
154
155 private static final State[] STATES_BY_ORDINAL = State.values();
156
157 private State state = State.Size;
158
159 @Override
160 public boolean process(byte value) {
161 state = state.match(value);
162 return true;
163 }
164
165 public void finish() {
166 if (state != State.Size && state != State.ChunkExtName && state != State.ChunkExtValQuotedEnd) {
167 throw new InvalidChunkExtensionException("Invalid chunk extension");
168 }
169 }
170 }