/* * Copyright (C) 2005-2018 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import #import #import #import @interface NSString (WebNSDataExtrasInternal) - (NSString *)_web_capitalizeRFC822HeaderFieldName; @end @implementation NSString (WebNSDataExtrasInternal) - (NSString *)_web_capitalizeRFC822HeaderFieldName { CFStringRef name = (__bridge CFStringRef)self; NSString *result = nil; CFIndex len = CFStringGetLength(name); char* charPtr = nullptr; UniChar* uniCharPtr = nullptr; Boolean useUniCharPtr = FALSE; Boolean shouldCapitalize = TRUE; Boolean somethingChanged = FALSE; for (CFIndex i = 0; i < len; i ++) { UniChar ch = CFStringGetCharacterAtIndex(name, i); Boolean replace = FALSE; if (shouldCapitalize && ch >= 'a' && ch <= 'z') { ch = ch + 'A' - 'a'; replace = TRUE; } else if (!shouldCapitalize && ch >= 'A' && ch <= 'Z') { ch = ch + 'a' - 'A'; replace = TRUE; } if (replace) { if (!somethingChanged) { somethingChanged = TRUE; if (CFStringGetBytes(name, CFRangeMake(0, len), kCFStringEncodingISOLatin1, 0, FALSE, NULL, 0, NULL) == len) { // Can be encoded in ISOLatin1 useUniCharPtr = FALSE; charPtr = static_cast(CFAllocatorAllocate(kCFAllocatorDefault, len + 1, 0)); CFStringGetCString(name, charPtr, len+1, kCFStringEncodingISOLatin1); } else { useUniCharPtr = TRUE; uniCharPtr = static_cast(CFAllocatorAllocate(kCFAllocatorDefault, len * sizeof(UniChar), 0)); CFStringGetCharacters(name, CFRangeMake(0, len), uniCharPtr); } } if (useUniCharPtr) uniCharPtr[i] = ch; else charPtr[i] = ch; } if (ch == '-') shouldCapitalize = TRUE; else shouldCapitalize = FALSE; } if (somethingChanged) { if (useUniCharPtr) result = adoptCF(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, uniCharPtr, len, nullptr)).bridgingAutorelease(); else result = adoptCF(CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, charPtr, kCFStringEncodingISOLatin1, nullptr)).bridgingAutorelease(); } else result = self; return result; } @end @implementation NSData (WebKitExtras) - (NSString *)_webkit_guessedMIMETypeForXML { NSUInteger length = [self length]; const UInt8* bytes = static_cast([self bytes]); #define CHANNEL_TAG_LENGTH 7 const char* p = reinterpret_cast(bytes); int remaining = std::min(length, WEB_GUESS_MIME_TYPE_PEEK_LENGTH) - (CHANNEL_TAG_LENGTH - 1); BOOL foundRDF = false; while (remaining > 0) { // Look for a "<". const char* hit = static_cast(memchr(p, '<', remaining)); if (!hit) break; // We are trying to identify RSS or Atom. RSS has a top-level // element of either or . However, there are // non-RSS RDF files, so in the case of we further look // for a element. In the case of an Atom file, a // top-level element is all we need to see. Only tags // starting with , or element // right after those. if (foundRDF) { if (!strncasecmp(hit, "([self bytes]); const char* p = bytes; int remaining = std::min(length, WEB_GUESS_MIME_TYPE_PEEK_LENGTH) - (SCRIPT_TAG_LENGTH - 1); while (remaining > 0) { // Look for a "<". const char* hit = static_cast(memchr(p, '<', remaining)); if (!hit) break; // If we found a "<", look for "" or "", strlen("")) || !strncasecmp(hit, "", strlen(""))) { return @"text/html"; } // Skip the "<" and continue. remaining -= (hit + 1) - p; p = hit + 1; } // Test for a broken server which has sent the content type as part of the content. // This code could be improved to look for other mime types. p = bytes; remaining = std::min<NSUInteger>(length, WEB_GUESS_MIME_TYPE_PEEK_LENGTH) - (TEXT_HTML_LENGTH - 1); while (remaining > 0) { // Look for a "t" or "T". const char* hit = nullptr; const char* lowerhit = static_cast<const char*>(memchr(p, 't', remaining)); const char* upperhit = static_cast<const char*>(memchr(p, 'T', remaining)); if (!lowerhit && !upperhit) break; if (!lowerhit) hit = upperhit; else if (!upperhit) hit = lowerhit; else hit = std::min<const char*>(lowerhit, upperhit); // If we found a "t/T", look for "text/html". if (!strncasecmp(hit, "text/html", TEXT_HTML_LENGTH)) return @"text/html"; // Skip the "t/T" and continue. remaining -= (hit + 1) - p; p = hit + 1; } if ((length >= VCARD_HEADER_LENGTH) && !strncmp(bytes, "BEGIN:VCARD", VCARD_HEADER_LENGTH)) return @"text/vcard"; if ((length >= VCAL_HEADER_LENGTH) && !strncmp(bytes, "BEGIN:VCALENDAR", VCAL_HEADER_LENGTH)) return @"text/calendar"; // Test for plain text. NSUInteger i; for (i = 0; i < length; ++i) { char c = bytes[i]; if ((c < 0x20 || c > 0x7E) && (c != '\t' && c != '\r' && c != '\n')) break; } if (i == length) { // Didn't encounter any bad characters, looks like plain text. return @"text/plain"; } // Looks like this is a binary file. // Sniff for the JPEG magic number. if ((length >= JPEG_MAGIC_NUMBER_LENGTH) && !strncmp(bytes, "\xFF\xD8\xFF\xE0", JPEG_MAGIC_NUMBER_LENGTH)) return @"image/jpeg"; #undef JPEG_MAGIC_NUMBER_LENGTH #undef SCRIPT_TAG_LENGTH #undef TEXT_HTML_LENGTH #undef VCARD_HEADER_LENGTH #undef VCAL_HEADER_LENGTH return nil; } @end @implementation NSData (WebNSDataExtras) - (BOOL)_web_isCaseInsensitiveEqualToCString:(const char *)string { ASSERT(string); const char* bytes = static_cast<const char*>([self bytes]); return !strncasecmp(bytes, string, [self length]); } static const UInt8 *_findEOL(const UInt8 *bytes, CFIndex len) { // According to the HTTP specification EOL is defined as // a CRLF pair. Unfortunately, some servers will use LF // instead. Worse yet, some servers will use a combination // of both (e.g. <header>CRLFLF<body>), so findEOL needs // to be more forgiving. It will now accept CRLF, LF, or // CR. // // It returns NULL if EOL is not found or it will return // a pointer to the first terminating character. for (CFIndex i = 0; i < len; i++) { UInt8 c = bytes[i]; if ('\n' == c) return bytes + i; if ('\r' == c) { // Check to see if spanning buffer bounds // (CRLF is across reads). If so, wait for // next read. if (i + 1 == len) break; return bytes + i; } } return nullptr; } - (NSMutableDictionary *)_webkit_parseRFC822HeaderFields { NSMutableDictionary *headerFields = [NSMutableDictionary dictionary]; const UInt8* bytes = static_cast<const UInt8*>([self bytes]); NSUInteger length = [self length]; RetainPtr<NSString> lastKey; const UInt8 *eol; // Loop over lines until we're past the header, or we can't find any more end-of-lines while ((eol = _findEOL(bytes, length))) { const UInt8 *line = bytes; SInt32 lineLength = eol - bytes; // Move bytes to the character after the terminator as returned by _findEOL. bytes = eol + 1; if (('\r' == *eol) && ('\n' == *bytes)) bytes++; // Safe since _findEOL won't return a spanning CRLF. length -= (bytes - line); if (!lineLength) { // Blank line; we're at the end of the header break; } if (*line == ' ' || *line == '\t') { // Continuation of the previous header if (!lastKey) { // malformed header; ignore it and continue continue; } // Merge the continuation of the previous header NSString *currentValue = [headerFields objectForKey:lastKey.get()]; auto newValue = adoptNS([[NSString alloc] initWithBytes:line length:lineLength encoding:NSISOLatin1StringEncoding]); ASSERT(currentValue); ASSERT(newValue); [headerFields setObject:[currentValue stringByAppendingString:newValue.get()] forKey:lastKey.get()]; } else { // Brand new header const UInt8* colon; for (colon = line; *colon != ':' && colon != eol; colon++) { } if (colon == eol) { // malformed header; ignore it and continue continue; } lastKey = adoptNS([[NSString alloc] initWithBytes:line length:colon - line encoding:NSISOLatin1StringEncoding]); lastKey = [lastKey _web_capitalizeRFC822HeaderFieldName]; for (colon++; colon != eol; colon++) { if (*colon != ' ' && *colon != '\t') break; } auto value = adoptNS([[NSString alloc] initWithBytes:colon length:eol - colon encoding:NSISOLatin1StringEncoding]); if (NSString *oldValue = [headerFields objectForKey:lastKey.get()]) value = adoptNS([[NSString alloc] initWithFormat:@"%@, %@", oldValue, value.get()]); [headerFields setObject:value.get() forKey:lastKey.get()]; } } return headerFields; } - (BOOL)_web_startsWithBlankLine { return [self length] > 0 && ((const char *)[self bytes])[0] == '\n'; } - (NSInteger)_web_locationAfterFirstBlankLine { const char *bytes = (const char *)[self bytes]; NSUInteger length = [self length]; unsigned i; for (i = 0; i < length - 4; i++) { // Support for Acrobat. It sends "\n\n". if (bytes[i] == '\n' && bytes[i+1] == '\n') return i+2; // Returns the position after 2 CRLF's or 1 CRLF if it is the first line. if (bytes[i] == '\r' && bytes[i+1] == '\n') { i += 2; if (i == 2) return i; if (bytes[i] == '\n') { // Support for Director. It sends "\r\n\n" (3880387). return i+1; } if (bytes[i] == '\r' && bytes[i+1] == '\n') { // Support for Flash. It sends "\r\n\r\n" (3758113). return i+2; } } } return NSNotFound; } @end