1 : /* ***** BEGIN LICENSE BLOCK *****
2 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 : *
4 : * The contents of this file are subject to the Mozilla Public License Version
5 : * 1.1 (the "License"); you may not use this file except in compliance with
6 : * the License. You may obtain a copy of the License at
7 : * http://www.mozilla.org/MPL/
8 : *
9 : * Software distributed under the License is distributed on an "AS IS" basis,
10 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 : * for the specific language governing rights and limitations under the
12 : * License.
13 : *
14 : * The Original Code is Url Classifier code
15 : *
16 : * The Initial Developer of the Original Code is
17 : * Google Inc.
18 : * Portions created by the Initial Developer are Copyright (C) 2007
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : *
23 : * Alternatively, the contents of this file may be used under the terms of
24 : * either the GNU General Public License Version 2 or later (the "GPL"), or
25 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 : * in which case the provisions of the GPL or the LGPL are applicable instead
27 : * of those above. If you wish to allow use of your version of this file only
28 : * under the terms of either the GPL or the LGPL, and not to allow others to
29 : * use your version of this file under the terms of the MPL, indicate your
30 : * decision by deleting the provisions above and replace them with the notice
31 : * and other provisions required by the GPL or the LGPL. If you do not delete
32 : * the provisions above, a recipient may use your version of this file under
33 : * the terms of any one of the MPL, the GPL or the LGPL.
34 : *
35 : * ***** END LICENSE BLOCK ***** */
36 :
37 : #include "nsEscape.h"
38 : #include "nsString.h"
39 : #include "nsIURI.h"
40 : #include "nsNetUtil.h"
41 : #include "nsUrlClassifierUtils.h"
42 : #include "nsTArray.h"
43 : #include "nsReadableUtils.h"
44 : #include "plbase64.h"
45 : #include "prmem.h"
46 : #include "prprf.h"
47 :
48 0 : static char int_to_hex_digit(PRInt32 i)
49 : {
50 0 : NS_ASSERTION((i >= 0) && (i <= 15), "int too big in int_to_hex_digit");
51 0 : return static_cast<char>(((i < 10) ? (i + '0') : ((i - 10) + 'A')));
52 : }
53 :
54 : static bool
55 9 : IsDecimal(const nsACString & num)
56 : {
57 25 : for (PRUint32 i = 0; i < num.Length(); i++) {
58 17 : if (!isdigit(num[i])) {
59 1 : return false;
60 : }
61 : }
62 :
63 8 : return true;
64 : }
65 :
66 : static bool
67 1 : IsHex(const nsACString & num)
68 : {
69 1 : if (num.Length() < 3) {
70 1 : return false;
71 : }
72 :
73 0 : if (num[0] != '0' || !(num[1] == 'x' || num[1] == 'X')) {
74 0 : return false;
75 : }
76 :
77 0 : for (PRUint32 i = 2; i < num.Length(); i++) {
78 0 : if (!isxdigit(num[i])) {
79 0 : return false;
80 : }
81 : }
82 :
83 0 : return true;
84 : }
85 :
86 : static bool
87 9 : IsOctal(const nsACString & num)
88 : {
89 9 : if (num.Length() < 2) {
90 5 : return false;
91 : }
92 :
93 4 : if (num[0] != '0') {
94 4 : return false;
95 : }
96 :
97 0 : for (PRUint32 i = 1; i < num.Length(); i++) {
98 0 : if (!isdigit(num[i]) || num[i] == '8' || num[i] == '9') {
99 0 : return false;
100 : }
101 : }
102 :
103 0 : return true;
104 : }
105 :
106 5 : nsUrlClassifierUtils::nsUrlClassifierUtils() : mEscapeCharmap(nsnull)
107 : {
108 5 : }
109 :
110 : nsresult
111 5 : nsUrlClassifierUtils::Init()
112 : {
113 : // Everything but alpha numerics, - and .
114 : mEscapeCharmap = new Charmap(0xffffffff, 0xfc009fff, 0xf8000001, 0xf8000001,
115 5 : 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff);
116 5 : if (!mEscapeCharmap)
117 0 : return NS_ERROR_OUT_OF_MEMORY;
118 5 : return NS_OK;
119 : }
120 :
121 830 : NS_IMPL_ISUPPORTS1(nsUrlClassifierUtils, nsIUrlClassifierUtils)
122 :
123 : /////////////////////////////////////////////////////////////////////////////
124 : // nsIUrlClassifierUtils
125 :
126 : NS_IMETHODIMP
127 161 : nsUrlClassifierUtils::GetKeyForURI(nsIURI * uri, nsACString & _retval)
128 : {
129 322 : nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
130 161 : if (!innerURI)
131 0 : innerURI = uri;
132 :
133 322 : nsCAutoString host;
134 161 : innerURI->GetAsciiHost(host);
135 :
136 161 : if (host.IsEmpty()) {
137 0 : return NS_ERROR_MALFORMED_URI;
138 : }
139 :
140 161 : nsresult rv = CanonicalizeHostname(host, _retval);
141 161 : NS_ENSURE_SUCCESS(rv, rv);
142 :
143 322 : nsCAutoString path;
144 161 : rv = innerURI->GetPath(path);
145 161 : NS_ENSURE_SUCCESS(rv, rv);
146 :
147 : // strip out anchors
148 161 : PRInt32 ref = path.FindChar('#');
149 161 : if (ref != kNotFound)
150 0 : path.SetLength(ref);
151 :
152 322 : nsCAutoString temp;
153 161 : rv = CanonicalizePath(path, temp);
154 161 : NS_ENSURE_SUCCESS(rv, rv);
155 :
156 161 : _retval.Append(temp);
157 :
158 161 : return NS_OK;
159 : }
160 :
161 : /////////////////////////////////////////////////////////////////////////////
162 : // non-interface methods
163 :
164 : nsresult
165 161 : nsUrlClassifierUtils::CanonicalizeHostname(const nsACString & hostname,
166 : nsACString & _retval)
167 : {
168 322 : nsCAutoString unescaped;
169 322 : if (!NS_UnescapeURL(PromiseFlatCString(hostname).get(),
170 322 : PromiseFlatCString(hostname).Length(),
171 161 : 0, unescaped)) {
172 161 : unescaped.Assign(hostname);
173 : }
174 :
175 322 : nsCAutoString cleaned;
176 161 : CleanupHostname(unescaped, cleaned);
177 :
178 322 : nsCAutoString temp;
179 161 : ParseIPAddress(cleaned, temp);
180 161 : if (!temp.IsEmpty()) {
181 2 : cleaned.Assign(temp);
182 : }
183 :
184 161 : ToLowerCase(cleaned);
185 161 : SpecialEncode(cleaned, false, _retval);
186 :
187 161 : return NS_OK;
188 : }
189 :
190 :
191 : nsresult
192 161 : nsUrlClassifierUtils::CanonicalizePath(const nsACString & path,
193 : nsACString & _retval)
194 : {
195 161 : _retval.Truncate();
196 :
197 322 : nsCAutoString decodedPath(path);
198 322 : nsCAutoString temp;
199 322 : while (NS_UnescapeURL(decodedPath.get(), decodedPath.Length(), 0, temp)) {
200 0 : decodedPath.Assign(temp);
201 0 : temp.Truncate();
202 : }
203 :
204 161 : SpecialEncode(decodedPath, true, _retval);
205 : // XXX: lowercase the path?
206 :
207 161 : return NS_OK;
208 : }
209 :
210 : void
211 161 : nsUrlClassifierUtils::CleanupHostname(const nsACString & hostname,
212 : nsACString & _retval)
213 : {
214 161 : _retval.Truncate();
215 :
216 161 : const char* curChar = hostname.BeginReading();
217 161 : const char* end = hostname.EndReading();
218 161 : char lastChar = '\0';
219 1595 : while (curChar != end) {
220 1273 : unsigned char c = static_cast<unsigned char>(*curChar);
221 1273 : if (c == '.' && (lastChar == '\0' || lastChar == '.')) {
222 : // skip
223 : } else {
224 1273 : _retval.Append(*curChar);
225 : }
226 1273 : lastChar = c;
227 1273 : ++curChar;
228 : }
229 :
230 : // cut off trailing dots
231 322 : while (_retval.Length() > 0 && _retval[_retval.Length() - 1] == '.') {
232 0 : _retval.SetLength(_retval.Length() - 1);
233 : }
234 161 : }
235 :
236 : void
237 161 : nsUrlClassifierUtils::ParseIPAddress(const nsACString & host,
238 : nsACString & _retval)
239 : {
240 161 : _retval.Truncate();
241 161 : nsACString::const_iterator iter, end;
242 161 : host.BeginReading(iter);
243 161 : host.EndReading(end);
244 :
245 161 : if (host.Length() <= 15) {
246 : // The Windows resolver allows a 4-part dotted decimal IP address to
247 : // have a space followed by any old rubbish, so long as the total length
248 : // of the string doesn't get above 15 characters. So, "10.192.95.89 xy"
249 : // is resolved to 10.192.95.89.
250 : // If the string length is greater than 15 characters, e.g.
251 : // "10.192.95.89 xy.wildcard.example.com", it will be resolved through
252 : // DNS.
253 :
254 159 : if (FindCharInReadable(' ', iter, end)) {
255 0 : end = iter;
256 : }
257 : }
258 :
259 359 : for (host.BeginReading(iter); iter != end; iter++) {
260 356 : if (!(isxdigit(*iter) || *iter == 'x' || *iter == 'X' || *iter == '.')) {
261 : // not an IP
262 158 : return;
263 : }
264 : }
265 :
266 3 : host.BeginReading(iter);
267 6 : nsTArray<nsCString> parts;
268 3 : ParseString(PromiseFlatCString(Substring(iter, end)), '.', parts);
269 3 : if (parts.Length() > 4) {
270 : return;
271 : }
272 :
273 : // If any potentially-octal numbers (start with 0 but not hex) have
274 : // non-octal digits, no part of the ip can be in octal
275 : // XXX: this came from the old javascript implementation, is it really
276 : // supposed to be like this?
277 3 : bool allowOctal = true;
278 : PRUint32 i;
279 :
280 14 : for (i = 0; i < parts.Length(); i++) {
281 11 : const nsCString& part = parts[i];
282 11 : if (part[0] == '0') {
283 2 : for (PRUint32 j = 1; j < part.Length(); j++) {
284 0 : if (part[j] == 'x') {
285 0 : break;
286 : }
287 0 : if (part[j] == '8' || part[j] == '9') {
288 0 : allowOctal = false;
289 0 : break;
290 : }
291 : }
292 : }
293 : }
294 :
295 11 : for (i = 0; i < parts.Length(); i++) {
296 18 : nsCAutoString canonical;
297 :
298 9 : if (i == parts.Length() - 1) {
299 2 : CanonicalNum(parts[i], 5 - parts.Length(), allowOctal, canonical);
300 : } else {
301 7 : CanonicalNum(parts[i], 1, allowOctal, canonical);
302 : }
303 :
304 9 : if (canonical.IsEmpty()) {
305 1 : _retval.Truncate();
306 : return;
307 : }
308 :
309 8 : if (_retval.IsEmpty()) {
310 2 : _retval.Assign(canonical);
311 : } else {
312 6 : _retval.Append('.');
313 6 : _retval.Append(canonical);
314 : }
315 : }
316 : return;
317 : }
318 :
319 : void
320 9 : nsUrlClassifierUtils::CanonicalNum(const nsACString& num,
321 : PRUint32 bytes,
322 : bool allowOctal,
323 : nsACString& _retval)
324 : {
325 9 : _retval.Truncate();
326 :
327 9 : if (num.Length() < 1) {
328 0 : return;
329 : }
330 :
331 : PRUint32 val;
332 9 : if (allowOctal && IsOctal(num)) {
333 0 : if (PR_sscanf(PromiseFlatCString(num).get(), "%o", &val) != 1) {
334 0 : return;
335 : }
336 9 : } else if (IsDecimal(num)) {
337 8 : if (PR_sscanf(PromiseFlatCString(num).get(), "%u", &val) != 1) {
338 0 : return;
339 : }
340 1 : } else if (IsHex(num)) {
341 0 : if (PR_sscanf(PromiseFlatCString(num).get(), num[1] == 'X' ? "0X%x" : "0x%x",
342 0 : &val) != 1) {
343 0 : return;
344 : }
345 : } else {
346 1 : return;
347 : }
348 :
349 24 : while (bytes--) {
350 : char buf[20];
351 8 : PR_snprintf(buf, sizeof(buf), "%u", val & 0xff);
352 8 : if (_retval.IsEmpty()) {
353 8 : _retval.Assign(buf);
354 : } else {
355 0 : _retval = nsDependentCString(buf) + NS_LITERAL_CSTRING(".") + _retval;
356 : }
357 8 : val >>= 8;
358 : }
359 : }
360 :
361 : // This function will encode all "special" characters in typical url
362 : // encoding, that is %hh where h is a valid hex digit. It will also fold
363 : // any duplicated slashes.
364 : bool
365 322 : nsUrlClassifierUtils::SpecialEncode(const nsACString & url,
366 : bool foldSlashes,
367 : nsACString & _retval)
368 : {
369 322 : bool changed = false;
370 322 : const char* curChar = url.BeginReading();
371 322 : const char* end = url.EndReading();
372 :
373 322 : unsigned char lastChar = '\0';
374 2284 : while (curChar != end) {
375 1640 : unsigned char c = static_cast<unsigned char>(*curChar);
376 1640 : if (ShouldURLEscape(c)) {
377 0 : _retval.Append('%');
378 0 : _retval.Append(int_to_hex_digit(c / 16));
379 0 : _retval.Append(int_to_hex_digit(c % 16));
380 :
381 0 : changed = true;
382 1640 : } else if (foldSlashes && (c == '/' && lastChar == '/')) {
383 : // skip
384 : } else {
385 1640 : _retval.Append(*curChar);
386 : }
387 1640 : lastChar = c;
388 1640 : curChar++;
389 : }
390 322 : return changed;
391 : }
392 :
393 : bool
394 1640 : nsUrlClassifierUtils::ShouldURLEscape(const unsigned char c) const
395 : {
396 1640 : return c <= 32 || c == '%' || c >=127;
397 : }
398 :
399 : /* static */
400 : void
401 7 : nsUrlClassifierUtils::UnUrlsafeBase64(nsACString &str)
402 : {
403 7 : nsACString::iterator iter, end;
404 7 : str.BeginWriting(iter);
405 7 : str.EndWriting(end);
406 194 : while (iter != end) {
407 180 : if (*iter == '-') {
408 0 : *iter = '+';
409 180 : } else if (*iter == '_') {
410 0 : *iter = '/';
411 : }
412 180 : iter++;
413 : }
414 7 : }
415 :
416 : /* static */
417 : nsresult
418 2 : nsUrlClassifierUtils::DecodeClientKey(const nsACString &key,
419 : nsACString &_retval)
420 : {
421 : // Client key is sent in urlsafe base64, we need to decode it first.
422 4 : nsCAutoString base64(key);
423 2 : UnUrlsafeBase64(base64);
424 :
425 : // PL_Base64Decode doesn't null-terminate unless we let it allocate,
426 : // so we need to calculate the length ourselves.
427 : PRUint32 destLength;
428 2 : destLength = base64.Length();
429 2 : if (destLength > 0 && base64[destLength - 1] == '=') {
430 2 : if (destLength > 1 && base64[destLength - 2] == '=') {
431 2 : destLength -= 2;
432 : } else {
433 0 : destLength -= 1;
434 : }
435 : }
436 :
437 2 : destLength = ((destLength * 3) / 4);
438 2 : _retval.SetLength(destLength);
439 2 : if (destLength != _retval.Length())
440 0 : return NS_ERROR_OUT_OF_MEMORY;
441 :
442 2 : if (!PL_Base64Decode(base64.BeginReading(), base64.Length(),
443 2 : _retval.BeginWriting())) {
444 0 : return NS_ERROR_FAILURE;
445 : }
446 :
447 2 : return NS_OK;
448 : }
|