]> git.ipfire.org Git - thirdparty/squid.git/blob - src/helper/Reply.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / helper / Reply.cc
1 /*
2 * Copyright (C) 1996-2021 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 84 Helper process maintenance */
10
11 #include "squid.h"
12 #include "ConfigParser.h"
13 #include "Debug.h"
14 #include "helper.h"
15 #include "helper/Reply.h"
16 #include "rfc1738.h"
17 #include "SquidString.h"
18
19 Helper::Reply::Reply() :
20 result(Helper::Unknown)
21 {
22 }
23
24 bool
25 Helper::Reply::accumulate(const char *buf, size_t len)
26 {
27 if (other_.isNull())
28 other_.init(4*1024, 1*1024*1024);
29
30 if (other_.potentialSpaceSize() < static_cast<mb_size_t>(len))
31 return false; // no space left
32
33 other_.append(buf, len);
34 return true;
35 }
36
37 void
38 Helper::Reply::finalize()
39 {
40 debugs(84, 3, "Parsing helper buffer");
41 // check we have something to parse
42 if (!other_.hasContent()) {
43 // empty line response was the old URL-rewriter interface ERR response.
44 result = Helper::Error;
45 // for now ensure that legacy handlers are not presented with NULL strings.
46 debugs(84, 3, "Zero length reply");
47 return;
48 }
49
50 char *p = other_.content();
51 size_t len = other_.contentSize();
52 bool sawNA = false;
53
54 // optimization: do not consider parsing result code if the response is short.
55 // URL-rewriter may return relative URLs or empty response for a large portion
56 // of its replies.
57 if (len >= 2) {
58 debugs(84, 3, "Buff length is larger than 2");
59 // some helper formats (digest auth, URL-rewriter) just send a data string
60 // we must also check for the ' ' character after the response token (if anything)
61 if (!strncmp(p,"OK",2) && (len == 2 || p[2] == ' ')) {
62 debugs(84, 3, "helper Result = OK");
63 result = Helper::Okay;
64 p+=2;
65 } else if (!strncmp(p,"ERR",3) && (len == 3 || p[3] == ' ')) {
66 debugs(84, 3, "helper Result = ERR");
67 result = Helper::Error;
68 p+=3;
69 } else if (!strncmp(p,"BH",2) && (len == 2 || p[2] == ' ')) {
70 debugs(84, 3, "helper Result = BH");
71 result = Helper::BrokenHelper;
72 p+=2;
73 } else if (!strncmp(p,"TT ",3)) {
74 // NTLM challenge token
75 result = Helper::TT;
76 p+=3;
77 // followed by an auth token
78 char *w1 = strwordtok(NULL, &p);
79 if (w1 != NULL) {
80 const char *authToken = w1;
81 notes.add("token",authToken);
82 } else {
83 // token field is mandatory on this response code
84 result = Helper::BrokenHelper;
85 notes.add("message","Missing 'token' data");
86 }
87
88 } else if (!strncmp(p,"AF ",3)) {
89 // NTLM/Negotiate OK response
90 result = Helper::Okay;
91 p+=3;
92 // followed by:
93 // an optional auth token and user field
94 // or, an optional username field
95 char *w1 = strwordtok(NULL, &p);
96 char *w2 = strwordtok(NULL, &p);
97 if (w2 != NULL) {
98 // Negotiate "token user"
99 const char *authToken = w1;
100 notes.add("token",authToken);
101
102 const char *user = w2;
103 notes.add("user",user);
104
105 } else if (w1 != NULL) {
106 // NTLM "user"
107 const char *user = w1;
108 notes.add("user",user);
109 }
110 } else if (!strncmp(p,"NA ",3)) {
111 // NTLM fail-closed ERR response
112 result = Helper::Error;
113 p+=3;
114 sawNA=true;
115 }
116
117 for (; xisspace(*p); ++p); // skip whitespace
118 }
119
120 other_.consume(p - other_.content());
121 other_.consumeWhitespacePrefix();
122
123 // Hack for backward-compatibility: Do not parse for kv-pairs on NA response
124 if (!sawNA)
125 parseResponseKeys();
126
127 // Hack for backward-compatibility: BH and NA used to be a text message...
128 if (other_.hasContent() && (sawNA || result == Helper::BrokenHelper)) {
129 notes.add("message", other_.content());
130 other_.clean();
131 }
132 }
133
134 /// restrict key names to alphanumeric, hyphen, underscore characters
135 static bool
136 isKeyNameChar(char c)
137 {
138 if (c >= 'a' && c <= 'z')
139 return true;
140
141 if (c >= 'A' && c <= 'Z')
142 return true;
143
144 if (c >= '0' && c <= '9')
145 return true;
146
147 if (c == '-' || c == '_')
148 return true;
149
150 // prevent other characters matching the key=value
151 return false;
152 }
153
154 void
155 Helper::Reply::parseResponseKeys()
156 {
157 // parse a "key=value" pair off the 'other()' buffer.
158 while (other_.hasContent()) {
159 char *p = other_.content();
160 const char *key = p;
161 while (*p && isKeyNameChar(*p)) ++p;
162 if (*p != '=')
163 return; // done. Not a key.
164
165 // whitespace between key and value is prohibited.
166 // workaround strwordtok() which skips whitespace prefix.
167 if (xisspace(*(p+1)))
168 return; // done. Not a key.
169
170 *p = '\0';
171 ++p;
172
173 // the value may be a quoted string or a token
174 const bool urlDecode = (*p != '"'); // check before moving p.
175 char *v = strwordtok(NULL, &p);
176 if (v != NULL && urlDecode && (p-v) > 2) // 1-octet %-escaped requires 3 bytes
177 rfc1738_unescape(v);
178
179 notes.add(key, v ? v : ""); // value can be empty, but must not be NULL
180
181 other_.consume(p - other_.content());
182 other_.consumeWhitespacePrefix();
183 }
184 }
185
186 const MemBuf &
187 Helper::Reply::emptyBuf() const
188 {
189 static MemBuf empty;
190 if (empty.isNull())
191 empty.init(1, 1);
192 return empty;
193 }
194
195 std::ostream &
196 operator <<(std::ostream &os, const Helper::Reply &r)
197 {
198 os << "{result=";
199 switch (r.result) {
200 case Helper::Okay:
201 os << "OK";
202 break;
203 case Helper::Error:
204 os << "ERR";
205 break;
206 case Helper::BrokenHelper:
207 os << "BH";
208 break;
209 case Helper::TT:
210 os << "TT";
211 break;
212 case Helper::TimedOut:
213 os << "Timeout";
214 break;
215 case Helper::Unknown:
216 os << "Unknown";
217 break;
218 }
219
220 // dump the helper key=pair "notes" list
221 if (!r.notes.empty()) {
222 os << ", notes={";
223 os << r.notes.toString("; ");
224 os << "}";
225 }
226
227 MemBuf const &o = r.other();
228 if (o.hasContent())
229 os << ", other: \"" << o.content() << '\"';
230
231 os << '}';
232
233 return os;
234 }
235