]> git.ipfire.org Git - thirdparty/squid.git/blob - src/esi/CustomParser.cc
420d06d0126e834a71ab30cbd2ecbb006ba82c69
[thirdparty/squid.git] / src / esi / CustomParser.cc
1 /*
2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 86 ESI processing */
10
11 #include "squid.h"
12 #include "Debug.h"
13 #include "esi/CustomParser.h"
14 #include "fatal.h"
15 #include "libTrie/Trie.h"
16 #include "libTrie/TrieCharTransform.h"
17
18 #include <vector>
19
20 Trie *ESICustomParser::SearchTrie=NULL;
21
22 EsiParserDefinition(ESICustomParser);
23
24 Trie *
25 ESICustomParser::GetTrie()
26 {
27 if (SearchTrie)
28 return SearchTrie;
29
30 SearchTrie = new Trie(new TrieCaseless);
31
32 static const ESITAG_t ESITAG_value = ESITAG;
33
34 assert (SearchTrie->add
35 ("<esi:",5,(void *)&ESITAG_value));
36
37 static const ESITAG_t ESIENDTAG_value = ESIENDTAG;
38
39 assert (SearchTrie->add
40 ("</esi:",6,(void *)&ESIENDTAG_value));
41
42 static const ESITAG_t ESICOMMENT_value = ESICOMMENT;
43
44 assert (SearchTrie->add
45 ("<!--",4,(void *)&ESICOMMENT_value));
46
47 return SearchTrie;
48 }
49
50 ESICustomParser::ESICustomParser(ESIParserClient *aClient) :
51 theClient(aClient),
52 lastTag(ESITAG)
53 {}
54
55 ESICustomParser::~ESICustomParser()
56 {
57 theClient = NULL;
58 }
59
60 char const *
61 ESICustomParser::findTag(char const *buffer, size_t bufferLength)
62 {
63 size_t myOffset (0);
64 ESITAG_t *resulttype = NULL;
65
66 while (myOffset < bufferLength &&
67 (resulttype = static_cast<ESITAG_t *>(GetTrie()->findPrefix (buffer + myOffset, bufferLength - myOffset)))
68 == NULL)
69 ++myOffset;
70
71 if (myOffset == bufferLength)
72 return NULL;
73
74 debugs(86, 9, "ESICustomParser::findTag: found " << *resulttype);
75
76 lastTag = *resulttype;
77
78 return buffer + myOffset;
79 }
80
81 bool
82 ESICustomParser::parse(char const *dataToParse, size_t const lengthOfData, bool const endOfStream)
83 {
84 debugs(86, 9, "ESICustomParser::parse: Appending data to internal buffer");
85 content.append (dataToParse, lengthOfData);
86
87 if (!endOfStream) {
88 return true;
89 }
90
91 size_t openESITags (0);
92 //erring on the safe side. Probably rawBuf would be ok too
93 char const *currentPos = content.termedBuf();
94 size_t remainingCount = content.size();
95 char const *tag = NULL;
96
97 while ((tag = findTag(currentPos, remainingCount))) {
98 if (tag - currentPos)
99 theClient->parserDefault (currentPos,tag - currentPos);
100
101 switch (lastTag) {
102
103 case ESITAG: {
104 ++openESITags;
105 char *tagEnd = strchr(const_cast<char *>(tag), '>');
106
107 if (!tagEnd) {
108 error = "Could not find end ('>') of tag";
109 return false;
110 }
111
112 if (tagEnd - tag > (ssize_t)remainingCount) {
113 error = "Tag ends beyond the parse buffer.";
114 return false;
115 }
116
117 if (*(tagEnd - 1) == '/')
118 --openESITags;
119
120 char * endofName = strpbrk(const_cast<char *>(tag), w_space);
121
122 if (endofName > tagEnd)
123 endofName = const_cast<char *>(tagEnd);
124
125 *endofName = '\0';
126
127 *tagEnd = '\0';
128
129 std::vector<char *>attributes;
130
131 char *attribute = const_cast<char *>(endofName + 1);
132
133 while (attribute > tag && attribute < tagEnd) {
134 /* leading spaces */
135
136 while (attribute < tagEnd && (xisspace(*attribute) || (*attribute == '/')))
137 ++attribute;
138
139 if (! (attribute < tagEnd))
140 break;
141
142 /* attribute name */
143 attributes.push_back(attribute);
144
145 char *nextSpace = strpbrk(attribute, w_space);
146
147 char *equals = strchr(attribute, '=');
148
149 if (!equals) {
150 error = "Missing attribute value.";
151 return false;
152 }
153
154 if (nextSpace && nextSpace < equals)
155 *nextSpace = '\0';
156 else
157 *equals = '\0';
158
159 ++equals;
160
161 while (equals < tagEnd && xisspace(*equals))
162 ++equals;
163
164 char sep = *equals;
165
166 if (sep != '\'' && sep != '"') {
167 error = "Unknown identifier (";
168 error.append (sep);
169 error.append (")");
170 return false;
171 }
172
173 char *value = equals + 1;
174 char *end = strchr(value, sep);
175
176 if (!end) {
177 error = "Missing attribute ending separator (";
178 error.append(sep);
179 error.append(")");
180 return false;
181 }
182 attributes.push_back(value);
183 *end = '\0';
184 attribute = end + 1;
185 }
186
187 // TODO: after c++11, replace &attributes.front() with attributes.data()
188 theClient->start (tag + 1, const_cast<const char **>(&attributes.front()), attributes.size() >> 1);
189 /* TODO: attributes */
190
191 if (*(tagEnd - 1) == '/')
192 theClient->end (tag + 1);
193
194 remainingCount -= tagEnd - currentPos + 1;
195
196 currentPos = tagEnd + 1;
197 }
198
199 break;
200
201 case ESIENDTAG: {
202 if (!openESITags)
203 return false;
204
205 char const *tagEnd = strchr(tag, '>');
206
207 if (!tagEnd)
208 return false;
209
210 if (tagEnd - tag > (ssize_t)remainingCount)
211 return false;
212
213 char * endofName = strpbrk(const_cast<char *>(tag), w_space);
214
215 if (endofName > tagEnd)
216 endofName = const_cast<char *>(tagEnd);
217
218 *endofName = '\0';
219
220 theClient->end (tag + 2);
221
222 --openESITags;
223
224 remainingCount -= tagEnd - currentPos + 1;
225
226 currentPos = tagEnd + 1;
227 }
228
229 break;
230
231 case ESICOMMENT: {
232 /* Further optimisation potential:
233 * 1) recognize end comments for esi and don't callback on
234 * comments.
235 * 2) provide the comment length to the caller.
236 */
237 /* Comments must not be nested, without CDATA
238 * and we don't support CDATA
239 */
240 char *commentEnd = strstr (const_cast<char *>(tag), "-->");
241
242 if (!commentEnd) {
243 error = "missing end of comment";
244 return false;
245 }
246
247 if (commentEnd - tag > (ssize_t)remainingCount) {
248 error = "comment ends beyond parse buffer";
249 return false;
250 }
251
252 *commentEnd = '\0';
253 theClient->parserComment (tag + 4);
254 remainingCount -= commentEnd - currentPos + 3;
255 currentPos = commentEnd + 3;
256 }
257
258 break;
259 break;
260
261 default:
262 fatal ("unknown ESI tag type found");
263 };
264
265 /*
266 * Find next esi tag (open or closing) or comment
267 * send tag, or full comment text
268 * rinse
269 */
270 }
271
272 if (remainingCount)
273 theClient->parserDefault (currentPos,remainingCount);
274
275 debugs(86, 5, "ESICustomParser::parse: Finished parsing, will return " << !openESITags);
276
277 if (openESITags)
278 error = "ESI Tags still open";
279
280 return !openESITags;
281 }
282
283 long int
284 ESICustomParser::lineNumber() const
285 {
286 /* We don't track lines in the body */
287 return 0;
288 }
289
290 char const *
291 ESICustomParser::errorString() const
292 {
293 if (error.size())
294 return error.termedBuf();
295 else
296 return "Parsing error strings not implemented";
297 }
298