Thu, 15 Jan 2015 15:55:04 +0100
Back out 97036ab72558 which inappropriately compared turds to third parties.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | /* This parsing code originally lived in xpfe/components/directory/ - bbaetz */ |
michael@0 | 7 | |
michael@0 | 8 | #include "mozilla/ArrayUtils.h" |
michael@0 | 9 | |
michael@0 | 10 | #include "prprf.h" |
michael@0 | 11 | |
michael@0 | 12 | #include "nsDirIndexParser.h" |
michael@0 | 13 | #include "nsEscape.h" |
michael@0 | 14 | #include "nsIInputStream.h" |
michael@0 | 15 | #include "nsCRT.h" |
michael@0 | 16 | #include "mozilla/dom/FallbackEncoding.h" |
michael@0 | 17 | #include "nsITextToSubURI.h" |
michael@0 | 18 | #include "nsIDirIndex.h" |
michael@0 | 19 | #include "nsServiceManagerUtils.h" |
michael@0 | 20 | |
michael@0 | 21 | using namespace mozilla; |
michael@0 | 22 | |
michael@0 | 23 | NS_IMPL_ISUPPORTS(nsDirIndexParser, |
michael@0 | 24 | nsIRequestObserver, |
michael@0 | 25 | nsIStreamListener, |
michael@0 | 26 | nsIDirIndexParser) |
michael@0 | 27 | |
michael@0 | 28 | nsDirIndexParser::nsDirIndexParser() { |
michael@0 | 29 | } |
michael@0 | 30 | |
michael@0 | 31 | nsresult |
michael@0 | 32 | nsDirIndexParser::Init() { |
michael@0 | 33 | mLineStart = 0; |
michael@0 | 34 | mHasDescription = false; |
michael@0 | 35 | mFormat = nullptr; |
michael@0 | 36 | mozilla::dom::FallbackEncoding::FromLocale(mEncoding); |
michael@0 | 37 | |
michael@0 | 38 | nsresult rv; |
michael@0 | 39 | // XXX not threadsafe |
michael@0 | 40 | if (gRefCntParser++ == 0) |
michael@0 | 41 | rv = CallGetService(NS_ITEXTTOSUBURI_CONTRACTID, &gTextToSubURI); |
michael@0 | 42 | else |
michael@0 | 43 | rv = NS_OK; |
michael@0 | 44 | |
michael@0 | 45 | return rv; |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | nsDirIndexParser::~nsDirIndexParser() { |
michael@0 | 49 | delete[] mFormat; |
michael@0 | 50 | // XXX not threadsafe |
michael@0 | 51 | if (--gRefCntParser == 0) { |
michael@0 | 52 | NS_IF_RELEASE(gTextToSubURI); |
michael@0 | 53 | } |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | NS_IMETHODIMP |
michael@0 | 57 | nsDirIndexParser::SetListener(nsIDirIndexListener* aListener) { |
michael@0 | 58 | mListener = aListener; |
michael@0 | 59 | return NS_OK; |
michael@0 | 60 | } |
michael@0 | 61 | |
michael@0 | 62 | NS_IMETHODIMP |
michael@0 | 63 | nsDirIndexParser::GetListener(nsIDirIndexListener** aListener) { |
michael@0 | 64 | NS_IF_ADDREF(*aListener = mListener.get()); |
michael@0 | 65 | return NS_OK; |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | NS_IMETHODIMP |
michael@0 | 69 | nsDirIndexParser::GetComment(char** aComment) { |
michael@0 | 70 | *aComment = ToNewCString(mComment); |
michael@0 | 71 | |
michael@0 | 72 | if (!*aComment) |
michael@0 | 73 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 74 | |
michael@0 | 75 | return NS_OK; |
michael@0 | 76 | } |
michael@0 | 77 | |
michael@0 | 78 | NS_IMETHODIMP |
michael@0 | 79 | nsDirIndexParser::SetEncoding(const char* aEncoding) { |
michael@0 | 80 | mEncoding.Assign(aEncoding); |
michael@0 | 81 | return NS_OK; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | NS_IMETHODIMP |
michael@0 | 85 | nsDirIndexParser::GetEncoding(char** aEncoding) { |
michael@0 | 86 | *aEncoding = ToNewCString(mEncoding); |
michael@0 | 87 | |
michael@0 | 88 | if (!*aEncoding) |
michael@0 | 89 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 90 | |
michael@0 | 91 | return NS_OK; |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | NS_IMETHODIMP |
michael@0 | 95 | nsDirIndexParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt) { |
michael@0 | 96 | return NS_OK; |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | NS_IMETHODIMP |
michael@0 | 100 | nsDirIndexParser::OnStopRequest(nsIRequest *aRequest, nsISupports *aCtxt, |
michael@0 | 101 | nsresult aStatusCode) { |
michael@0 | 102 | // Finish up |
michael@0 | 103 | if (mBuf.Length() > (uint32_t) mLineStart) { |
michael@0 | 104 | ProcessData(aRequest, aCtxt); |
michael@0 | 105 | } |
michael@0 | 106 | |
michael@0 | 107 | return NS_OK; |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | nsDirIndexParser::Field |
michael@0 | 111 | nsDirIndexParser::gFieldTable[] = { |
michael@0 | 112 | { "Filename", FIELD_FILENAME }, |
michael@0 | 113 | { "Description", FIELD_DESCRIPTION }, |
michael@0 | 114 | { "Content-Length", FIELD_CONTENTLENGTH }, |
michael@0 | 115 | { "Last-Modified", FIELD_LASTMODIFIED }, |
michael@0 | 116 | { "Content-Type", FIELD_CONTENTTYPE }, |
michael@0 | 117 | { "File-Type", FIELD_FILETYPE }, |
michael@0 | 118 | { nullptr, FIELD_UNKNOWN } |
michael@0 | 119 | }; |
michael@0 | 120 | |
michael@0 | 121 | nsrefcnt nsDirIndexParser::gRefCntParser = 0; |
michael@0 | 122 | nsITextToSubURI *nsDirIndexParser::gTextToSubURI; |
michael@0 | 123 | |
michael@0 | 124 | nsresult |
michael@0 | 125 | nsDirIndexParser::ParseFormat(const char* aFormatStr) { |
michael@0 | 126 | // Parse a "200" format line, and remember the fields and their |
michael@0 | 127 | // ordering in mFormat. Multiple 200 lines stomp on each other. |
michael@0 | 128 | |
michael@0 | 129 | // Lets find out how many elements we have. |
michael@0 | 130 | // easier to do this then realloc |
michael@0 | 131 | const char* pos = aFormatStr; |
michael@0 | 132 | unsigned int num = 0; |
michael@0 | 133 | do { |
michael@0 | 134 | while (*pos && nsCRT::IsAsciiSpace(char16_t(*pos))) |
michael@0 | 135 | ++pos; |
michael@0 | 136 | |
michael@0 | 137 | ++num; |
michael@0 | 138 | // There are a maximum of six allowed header fields (doubled plus |
michael@0 | 139 | // terminator, just in case) -- Bug 443299 |
michael@0 | 140 | if (num > (2 * ArrayLength(gFieldTable))) |
michael@0 | 141 | return NS_ERROR_UNEXPECTED; |
michael@0 | 142 | |
michael@0 | 143 | if (! *pos) |
michael@0 | 144 | break; |
michael@0 | 145 | |
michael@0 | 146 | while (*pos && !nsCRT::IsAsciiSpace(char16_t(*pos))) |
michael@0 | 147 | ++pos; |
michael@0 | 148 | |
michael@0 | 149 | } while (*pos); |
michael@0 | 150 | |
michael@0 | 151 | delete[] mFormat; |
michael@0 | 152 | mFormat = new int[num+1]; |
michael@0 | 153 | // Prevent nullptr Deref - Bug 443299 |
michael@0 | 154 | if (mFormat == nullptr) |
michael@0 | 155 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 156 | mFormat[num] = -1; |
michael@0 | 157 | |
michael@0 | 158 | int formatNum=0; |
michael@0 | 159 | do { |
michael@0 | 160 | while (*aFormatStr && nsCRT::IsAsciiSpace(char16_t(*aFormatStr))) |
michael@0 | 161 | ++aFormatStr; |
michael@0 | 162 | |
michael@0 | 163 | if (! *aFormatStr) |
michael@0 | 164 | break; |
michael@0 | 165 | |
michael@0 | 166 | nsAutoCString name; |
michael@0 | 167 | int32_t len = 0; |
michael@0 | 168 | while (aFormatStr[len] && !nsCRT::IsAsciiSpace(char16_t(aFormatStr[len]))) |
michael@0 | 169 | ++len; |
michael@0 | 170 | name.SetCapacity(len + 1); |
michael@0 | 171 | name.Append(aFormatStr, len); |
michael@0 | 172 | aFormatStr += len; |
michael@0 | 173 | |
michael@0 | 174 | // Okay, we're gonna monkey with the nsStr. Bold! |
michael@0 | 175 | name.SetLength(nsUnescapeCount(name.BeginWriting())); |
michael@0 | 176 | |
michael@0 | 177 | // All tokens are case-insensitive - http://www.mozilla.org/projects/netlib/dirindexformat.html |
michael@0 | 178 | if (name.LowerCaseEqualsLiteral("description")) |
michael@0 | 179 | mHasDescription = true; |
michael@0 | 180 | |
michael@0 | 181 | for (Field* i = gFieldTable; i->mName; ++i) { |
michael@0 | 182 | if (name.EqualsIgnoreCase(i->mName)) { |
michael@0 | 183 | mFormat[formatNum] = i->mType; |
michael@0 | 184 | ++formatNum; |
michael@0 | 185 | break; |
michael@0 | 186 | } |
michael@0 | 187 | } |
michael@0 | 188 | |
michael@0 | 189 | } while (*aFormatStr); |
michael@0 | 190 | |
michael@0 | 191 | return NS_OK; |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | nsresult |
michael@0 | 195 | nsDirIndexParser::ParseData(nsIDirIndex *aIdx, char* aDataStr) { |
michael@0 | 196 | // Parse a "201" data line, using the field ordering specified in |
michael@0 | 197 | // mFormat. |
michael@0 | 198 | |
michael@0 | 199 | if (!mFormat) { |
michael@0 | 200 | // Ignore if we haven't seen a format yet. |
michael@0 | 201 | return NS_OK; |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | nsresult rv = NS_OK; |
michael@0 | 205 | |
michael@0 | 206 | nsAutoCString filename; |
michael@0 | 207 | |
michael@0 | 208 | for (int32_t i = 0; mFormat[i] != -1; ++i) { |
michael@0 | 209 | // If we've exhausted the data before we run out of fields, just |
michael@0 | 210 | // bail. |
michael@0 | 211 | if (! *aDataStr) |
michael@0 | 212 | break; |
michael@0 | 213 | |
michael@0 | 214 | while (*aDataStr && nsCRT::IsAsciiSpace(*aDataStr)) |
michael@0 | 215 | ++aDataStr; |
michael@0 | 216 | |
michael@0 | 217 | char *value = aDataStr; |
michael@0 | 218 | |
michael@0 | 219 | if (*aDataStr == '"' || *aDataStr == '\'') { |
michael@0 | 220 | // it's a quoted string. snarf everything up to the next quote character |
michael@0 | 221 | const char quotechar = *(aDataStr++); |
michael@0 | 222 | ++value; |
michael@0 | 223 | while (*aDataStr && *aDataStr != quotechar) |
michael@0 | 224 | ++aDataStr; |
michael@0 | 225 | *aDataStr++ = '\0'; |
michael@0 | 226 | |
michael@0 | 227 | if (! aDataStr) { |
michael@0 | 228 | NS_WARNING("quoted value not terminated"); |
michael@0 | 229 | } |
michael@0 | 230 | } else { |
michael@0 | 231 | // it's unquoted. snarf until we see whitespace. |
michael@0 | 232 | value = aDataStr; |
michael@0 | 233 | while (*aDataStr && (!nsCRT::IsAsciiSpace(*aDataStr))) |
michael@0 | 234 | ++aDataStr; |
michael@0 | 235 | *aDataStr++ = '\0'; |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | fieldType t = fieldType(mFormat[i]); |
michael@0 | 239 | switch (t) { |
michael@0 | 240 | case FIELD_FILENAME: { |
michael@0 | 241 | // don't unescape at this point, so that UnEscapeAndConvert() can |
michael@0 | 242 | filename = value; |
michael@0 | 243 | |
michael@0 | 244 | bool success = false; |
michael@0 | 245 | |
michael@0 | 246 | nsAutoString entryuri; |
michael@0 | 247 | |
michael@0 | 248 | if (gTextToSubURI) { |
michael@0 | 249 | char16_t *result = nullptr; |
michael@0 | 250 | if (NS_SUCCEEDED(rv = gTextToSubURI->UnEscapeAndConvert(mEncoding.get(), filename.get(), |
michael@0 | 251 | &result)) && (result)) { |
michael@0 | 252 | if (*result) { |
michael@0 | 253 | aIdx->SetLocation(filename.get()); |
michael@0 | 254 | if (!mHasDescription) |
michael@0 | 255 | aIdx->SetDescription(result); |
michael@0 | 256 | success = true; |
michael@0 | 257 | } |
michael@0 | 258 | NS_Free(result); |
michael@0 | 259 | } else { |
michael@0 | 260 | NS_WARNING("UnEscapeAndConvert error"); |
michael@0 | 261 | } |
michael@0 | 262 | } |
michael@0 | 263 | |
michael@0 | 264 | if (!success) { |
michael@0 | 265 | // if unsuccessfully at charset conversion, then |
michael@0 | 266 | // just fallback to unescape'ing in-place |
michael@0 | 267 | // XXX - this shouldn't be using UTF8, should it? |
michael@0 | 268 | // when can we fail to get the service, anyway? - bbaetz |
michael@0 | 269 | aIdx->SetLocation(filename.get()); |
michael@0 | 270 | if (!mHasDescription) { |
michael@0 | 271 | aIdx->SetDescription(NS_ConvertUTF8toUTF16(value).get()); |
michael@0 | 272 | } |
michael@0 | 273 | } |
michael@0 | 274 | } |
michael@0 | 275 | break; |
michael@0 | 276 | case FIELD_DESCRIPTION: |
michael@0 | 277 | nsUnescape(value); |
michael@0 | 278 | aIdx->SetDescription(NS_ConvertUTF8toUTF16(value).get()); |
michael@0 | 279 | break; |
michael@0 | 280 | case FIELD_CONTENTLENGTH: |
michael@0 | 281 | { |
michael@0 | 282 | int64_t len; |
michael@0 | 283 | int32_t status = PR_sscanf(value, "%lld", &len); |
michael@0 | 284 | if (status == 1) |
michael@0 | 285 | aIdx->SetSize(len); |
michael@0 | 286 | else |
michael@0 | 287 | aIdx->SetSize(UINT64_MAX); // UINT64_MAX means unknown |
michael@0 | 288 | } |
michael@0 | 289 | break; |
michael@0 | 290 | case FIELD_LASTMODIFIED: |
michael@0 | 291 | { |
michael@0 | 292 | PRTime tm; |
michael@0 | 293 | nsUnescape(value); |
michael@0 | 294 | if (PR_ParseTimeString(value, false, &tm) == PR_SUCCESS) { |
michael@0 | 295 | aIdx->SetLastModified(tm); |
michael@0 | 296 | } |
michael@0 | 297 | } |
michael@0 | 298 | break; |
michael@0 | 299 | case FIELD_CONTENTTYPE: |
michael@0 | 300 | aIdx->SetContentType(value); |
michael@0 | 301 | break; |
michael@0 | 302 | case FIELD_FILETYPE: |
michael@0 | 303 | // unescape in-place |
michael@0 | 304 | nsUnescape(value); |
michael@0 | 305 | if (!nsCRT::strcasecmp(value, "directory")) { |
michael@0 | 306 | aIdx->SetType(nsIDirIndex::TYPE_DIRECTORY); |
michael@0 | 307 | } else if (!nsCRT::strcasecmp(value, "file")) { |
michael@0 | 308 | aIdx->SetType(nsIDirIndex::TYPE_FILE); |
michael@0 | 309 | } else if (!nsCRT::strcasecmp(value, "symbolic-link")) { |
michael@0 | 310 | aIdx->SetType(nsIDirIndex::TYPE_SYMLINK); |
michael@0 | 311 | } else { |
michael@0 | 312 | aIdx->SetType(nsIDirIndex::TYPE_UNKNOWN); |
michael@0 | 313 | } |
michael@0 | 314 | break; |
michael@0 | 315 | case FIELD_UNKNOWN: |
michael@0 | 316 | // ignore |
michael@0 | 317 | break; |
michael@0 | 318 | } |
michael@0 | 319 | } |
michael@0 | 320 | |
michael@0 | 321 | return NS_OK; |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | NS_IMETHODIMP |
michael@0 | 325 | nsDirIndexParser::OnDataAvailable(nsIRequest *aRequest, nsISupports *aCtxt, |
michael@0 | 326 | nsIInputStream *aStream, |
michael@0 | 327 | uint64_t aSourceOffset, |
michael@0 | 328 | uint32_t aCount) { |
michael@0 | 329 | if (aCount < 1) |
michael@0 | 330 | return NS_OK; |
michael@0 | 331 | |
michael@0 | 332 | int32_t len = mBuf.Length(); |
michael@0 | 333 | |
michael@0 | 334 | // Ensure that our mBuf has capacity to hold the data we're about to |
michael@0 | 335 | // read. |
michael@0 | 336 | if (!mBuf.SetLength(len + aCount, fallible_t())) |
michael@0 | 337 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 338 | |
michael@0 | 339 | // Now read the data into our buffer. |
michael@0 | 340 | nsresult rv; |
michael@0 | 341 | uint32_t count; |
michael@0 | 342 | rv = aStream->Read(mBuf.BeginWriting() + len, aCount, &count); |
michael@0 | 343 | if (NS_FAILED(rv)) return rv; |
michael@0 | 344 | |
michael@0 | 345 | // Set the string's length according to the amount of data we've read. |
michael@0 | 346 | // Note: we know this to work on nsCString. This isn't guaranteed to |
michael@0 | 347 | // work on other strings. |
michael@0 | 348 | mBuf.SetLength(len + count); |
michael@0 | 349 | |
michael@0 | 350 | return ProcessData(aRequest, aCtxt); |
michael@0 | 351 | } |
michael@0 | 352 | |
michael@0 | 353 | nsresult |
michael@0 | 354 | nsDirIndexParser::ProcessData(nsIRequest *aRequest, nsISupports *aCtxt) { |
michael@0 | 355 | if (!mListener) |
michael@0 | 356 | return NS_ERROR_FAILURE; |
michael@0 | 357 | |
michael@0 | 358 | int32_t numItems = 0; |
michael@0 | 359 | |
michael@0 | 360 | while(true) { |
michael@0 | 361 | ++numItems; |
michael@0 | 362 | |
michael@0 | 363 | int32_t eol = mBuf.FindCharInSet("\n\r", mLineStart); |
michael@0 | 364 | if (eol < 0) break; |
michael@0 | 365 | mBuf.SetCharAt(char16_t('\0'), eol); |
michael@0 | 366 | |
michael@0 | 367 | const char *line = mBuf.get() + mLineStart; |
michael@0 | 368 | |
michael@0 | 369 | int32_t lineLen = eol - mLineStart; |
michael@0 | 370 | mLineStart = eol + 1; |
michael@0 | 371 | |
michael@0 | 372 | if (lineLen >= 4) { |
michael@0 | 373 | nsresult rv; |
michael@0 | 374 | const char *buf = line; |
michael@0 | 375 | |
michael@0 | 376 | if (buf[0] == '1') { |
michael@0 | 377 | if (buf[1] == '0') { |
michael@0 | 378 | if (buf[2] == '0' && buf[3] == ':') { |
michael@0 | 379 | // 100. Human-readable comment line. Ignore |
michael@0 | 380 | } else if (buf[2] == '1' && buf[3] == ':') { |
michael@0 | 381 | // 101. Human-readable information line. |
michael@0 | 382 | mComment.Append(buf + 4); |
michael@0 | 383 | |
michael@0 | 384 | char *value = ((char *)buf) + 4; |
michael@0 | 385 | nsUnescape(value); |
michael@0 | 386 | mListener->OnInformationAvailable(aRequest, aCtxt, NS_ConvertUTF8toUTF16(value)); |
michael@0 | 387 | |
michael@0 | 388 | } else if (buf[2] == '2' && buf[3] == ':') { |
michael@0 | 389 | // 102. Human-readable information line, HTML. |
michael@0 | 390 | mComment.Append(buf + 4); |
michael@0 | 391 | } |
michael@0 | 392 | } |
michael@0 | 393 | } else if (buf[0] == '2') { |
michael@0 | 394 | if (buf[1] == '0') { |
michael@0 | 395 | if (buf[2] == '0' && buf[3] == ':') { |
michael@0 | 396 | // 200. Define field names |
michael@0 | 397 | rv = ParseFormat(buf + 4); |
michael@0 | 398 | if (NS_FAILED(rv)) { |
michael@0 | 399 | return rv; |
michael@0 | 400 | } |
michael@0 | 401 | } else if (buf[2] == '1' && buf[3] == ':') { |
michael@0 | 402 | // 201. Field data |
michael@0 | 403 | nsCOMPtr<nsIDirIndex> idx = do_CreateInstance("@mozilla.org/dirIndex;1",&rv); |
michael@0 | 404 | if (NS_FAILED(rv)) |
michael@0 | 405 | return rv; |
michael@0 | 406 | |
michael@0 | 407 | rv = ParseData(idx, ((char *)buf) + 4); |
michael@0 | 408 | if (NS_FAILED(rv)) { |
michael@0 | 409 | return rv; |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | mListener->OnIndexAvailable(aRequest, aCtxt, idx); |
michael@0 | 413 | } |
michael@0 | 414 | } |
michael@0 | 415 | } else if (buf[0] == '3') { |
michael@0 | 416 | if (buf[1] == '0') { |
michael@0 | 417 | if (buf[2] == '0' && buf[3] == ':') { |
michael@0 | 418 | // 300. Self-referring URL |
michael@0 | 419 | } else if (buf[2] == '1' && buf[3] == ':') { |
michael@0 | 420 | // 301. OUR EXTENSION - encoding |
michael@0 | 421 | int i = 4; |
michael@0 | 422 | while (buf[i] && nsCRT::IsAsciiSpace(buf[i])) |
michael@0 | 423 | ++i; |
michael@0 | 424 | |
michael@0 | 425 | if (buf[i]) |
michael@0 | 426 | SetEncoding(buf+i); |
michael@0 | 427 | } |
michael@0 | 428 | } |
michael@0 | 429 | } |
michael@0 | 430 | } |
michael@0 | 431 | } |
michael@0 | 432 | |
michael@0 | 433 | return NS_OK; |
michael@0 | 434 | } |