1
18 package io.undertow.util;
19
20 import static io.undertow.UndertowMessages.MESSAGES;
21
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24
25
30 public class HeaderTokenParser<E extends HeaderToken> {
31
32 private static final char EQUALS = '=';
33 private static final char COMMA = ',';
34 private static final char QUOTE = '"';
35 private static final char ESCAPE = '\\';
36
37 private final Map<String, E> expectedTokens;
38
39 public HeaderTokenParser(final Map<String, E> expectedTokens) {
40 this.expectedTokens = expectedTokens;
41 }
42
43 public Map<E, String> parseHeader(final String header) {
44 char[] headerChars = header.toCharArray();
45
46
47 Map<E, String> response = new LinkedHashMap<>();
48
49 SearchingFor searchingFor = SearchingFor.START_OF_NAME;
50 int nameStart = 0;
51 E currentToken = null;
52 int valueStart = 0;
53
54 int escapeCount = 0;
55 boolean containsEscapes = false;
56
57 for (int i = 0; i < headerChars.length; i++) {
58 switch (searchingFor) {
59 case START_OF_NAME:
60
61 if (headerChars[i] != COMMA && !Character.isWhitespace(headerChars[i])) {
62 nameStart = i;
63 searchingFor = SearchingFor.EQUALS_SIGN;
64 }
65 break;
66 case EQUALS_SIGN:
67 if (headerChars[i] == EQUALS) {
68 String paramName = String.valueOf(headerChars, nameStart, i - nameStart);
69 currentToken = expectedTokens.get(paramName);
70 if (currentToken == null) {
71 throw MESSAGES.unexpectedTokenInHeader(paramName);
72 }
73 searchingFor = SearchingFor.START_OF_VALUE;
74 }
75 break;
76 case START_OF_VALUE:
77 if (!Character.isWhitespace(headerChars[i])) {
78 if (headerChars[i] == QUOTE && currentToken.isAllowQuoted()) {
79 valueStart = i + 1;
80 searchingFor = SearchingFor.LAST_QUOTE;
81 } else {
82 valueStart = i;
83 searchingFor = SearchingFor.END_OF_VALUE;
84 }
85 }
86 break;
87 case LAST_QUOTE:
88 if (headerChars[i] == ESCAPE) {
89 escapeCount++;
90 containsEscapes = true;
91 } else if (headerChars[i] == QUOTE && (escapeCount % 2 == 0)) {
92 String value = String.valueOf(headerChars, valueStart, i - valueStart);
93 if(containsEscapes) {
94 StringBuilder sb = new StringBuilder();
95 boolean lastEscape = false;
96 for(int j = 0; j < value.length(); ++j) {
97 char c = value.charAt(j);
98 if(c == ESCAPE && !lastEscape) {
99 lastEscape = true;
100 } else {
101 lastEscape = false;
102 sb.append(c);
103 }
104 }
105 value = sb.toString();
106 containsEscapes = false;
107 }
108 response.put(currentToken, value);
109
110 searchingFor = SearchingFor.START_OF_NAME;
111 escapeCount = 0;
112 } else {
113 escapeCount = 0;
114 }
115 break;
116 case END_OF_VALUE:
117 if (headerChars[i] == COMMA || Character.isWhitespace(headerChars[i])) {
118 String value = String.valueOf(headerChars, valueStart, i - valueStart);
119 response.put(currentToken, value);
120
121 searchingFor = SearchingFor.START_OF_NAME;
122 }
123 break;
124 }
125 }
126
127 if (searchingFor == SearchingFor.END_OF_VALUE) {
128
129 String value = String.valueOf(headerChars, valueStart, headerChars.length - valueStart);
130 response.put(currentToken, value);
131 } else if (searchingFor != SearchingFor.START_OF_NAME) {
132
133 throw MESSAGES.invalidHeader();
134 }
135
136 return response;
137 }
138
139 enum SearchingFor {
140 START_OF_NAME, EQUALS_SIGN, START_OF_VALUE, LAST_QUOTE, END_OF_VALUE;
141 }
142
143 }
144