WebKit/mac/Misc/WebNSDataExtras.m
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebKit/mac/Misc/WebNSDataExtras.m	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2005 Apple Computer, 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. 
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission. 
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 <WebKit/WebNSDataExtras.h>
+#import <WebKit/WebNSDataExtrasPrivate.h>
+
+#import <wtf/Assertions.h>
+
+@interface NSString (WebNSDataExtrasInternal)
+- (NSString *)_web_capitalizeRFC822HeaderFieldName;
+@end
+
+@implementation NSString (WebNSDataExtrasInternal)
+
+-(NSString *)_web_capitalizeRFC822HeaderFieldName
+{
+    CFStringRef name = (CFStringRef)self;
+    NSString *result = nil;
+
+    CFIndex i; 
+    CFIndex len = CFStringGetLength(name);
+    char *charPtr = NULL;
+    UniChar *uniCharPtr = NULL;
+    Boolean useUniCharPtr = FALSE;
+    Boolean shouldCapitalize = TRUE;
+    Boolean somethingChanged = FALSE;
+    
+    for (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 = CFAllocatorAllocate(NULL, len + 1, 0);
+                    CFStringGetCString(name, charPtr, len+1, kCFStringEncodingISOLatin1);
+                } 
+                else {
+                    useUniCharPtr = TRUE;
+                    uniCharPtr = CFAllocatorAllocate(NULL, 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 = (NSString *)CFMakeCollectable(CFStringCreateWithCharactersNoCopy(NULL, uniCharPtr, len, NULL));
+        } 
+        else {
+            result = (NSString *)CFMakeCollectable(CFStringCreateWithCStringNoCopy(NULL, charPtr, kCFStringEncodingISOLatin1, NULL));
+        }
+    } 
+    else {
+        result = [self retain];
+    }
+    
+    return [result autorelease];
+}
+
+@end
+
+@implementation NSData (WebKitExtras)
+
+-(NSString *)_webkit_guessedMIMETypeForXML
+{
+    int length = [self length];
+    const UInt8 *bytes = [self bytes];
+    
+#define CHANNEL_TAG_LENGTH 7
+    
+    const char *p = (const char *)bytes;
+    int remaining = MIN(length, WEB_GUESS_MIME_TYPE_PEEK_LENGTH) - (CHANNEL_TAG_LENGTH - 1);
+    
+    BOOL foundRDF = false;
+    
+    while (remaining > 0) {
+        // Look for a "<".
+        const char *hit = memchr(p, '<', remaining);
+        if (!hit) {
+            break;
+        }
+        
+        // We are trying to identify RSS or Atom. RSS has a top-level
+        // element of either <rss> or <rdf>. However, there are
+        // non-RSS RDF files, so in the case of <rdf> we further look
+        // for a <channel> element. In the case of an Atom file, a
+        // top-level <feed> element is all we need to see. Only tags
+        // starting with <? or <! can precede the root element. We
+        // bail if we don't find an <rss>, <feed> or <rdf> element
+        // right after those.
+        
+        if (foundRDF) {
+            if (strncasecmp(hit, "<channel", strlen("<channel")) == 0) {
+                return @"application/rss+xml";
+            }
+        } else if (strncasecmp(hit, "<rdf", strlen("<rdf")) == 0) {
+            foundRDF = TRUE;
+        } else if (strncasecmp(hit, "<rss", strlen("<rss")) == 0) {
+            return @"application/rss+xml";
+        } else if (strncasecmp(hit, "<feed", strlen("<feed")) == 0) {
+            return @"application/atom+xml";
+        } else if (strncasecmp(hit, "<?", strlen("<?")) != 0 && strncasecmp(hit, "<!", strlen("<!")) != 0) {
+            return nil;
+        }
+        
+        // Skip the "<" and continue.
+        remaining -= (hit + 1) - p;
+        p = hit + 1;
+    }
+    
+    return nil;
+}
+
+-(NSString *)_webkit_guessedMIMEType
+{
+#define JPEG_MAGIC_NUMBER_LENGTH 4
+#define SCRIPT_TAG_LENGTH 7
+#define TEXT_HTML_LENGTH 9
+#define VCARD_HEADER_LENGTH 11
+#define VCAL_HEADER_LENGTH 15
+    
+    NSString *MIMEType = [self _webkit_guessedMIMETypeForXML];
+    if ([MIMEType length])
+        return MIMEType;
+    
+    int length = [self length];
+    const char *bytes = [self bytes];
+    
+    const char *p = bytes;
+    int remaining = MIN(length, WEB_GUESS_MIME_TYPE_PEEK_LENGTH) - (SCRIPT_TAG_LENGTH - 1);
+    while (remaining > 0) {
+        // Look for a "<".
+        const char *hit = memchr(p, '<', remaining);
+        if (!hit) {
+            break;
+        }
+        
+        // If we found a "<", look for "<html>" or "<a " or "<script".
+        if (strncasecmp(hit, "<html>",  strlen("<html>")) == 0 ||
+            strncasecmp(hit, "<a ",     strlen("<a ")) == 0 ||
+            strncasecmp(hit, "<script", strlen("<script")) == 0 ||
+            strncasecmp(hit, "<title>", strlen("<title>")) == 0) {
+            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 = MIN(length, WEB_GUESS_MIME_TYPE_PEEK_LENGTH) - (TEXT_HTML_LENGTH - 1);
+    while (remaining > 0) {
+        // Look for a "t" or "T".
+        const char *hit = NULL;
+        const char *lowerhit = memchr(p, 't', remaining);
+        const char *upperhit = memchr(p, 'T', remaining);
+        if (!lowerhit && !upperhit) {
+            break;
+        }
+        if (!lowerhit) {
+            hit = upperhit;
+        }
+        else if (!upperhit) {
+            hit = lowerhit;
+        }
+        else {
+            hit = MIN(lowerhit, upperhit);
+        }
+        
+        // If we found a "t/T", look for "text/html".
+        if (strncasecmp(hit, "text/html", TEXT_HTML_LENGTH) == 0) {
+            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) == 0) {
+        return @"text/vcard";
+    }
+    if ((length >= VCAL_HEADER_LENGTH) && strncmp(bytes, "BEGIN:VCALENDAR", VCAL_HEADER_LENGTH) == 0) {
+        return @"text/calendar";
+    }
+    
+    // Test for plain text.
+    int 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) == 0) {
+        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 = [self bytes];
+    return strncasecmp(bytes, string, [self length]) == 0;
+}
+
+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.
+    CFIndex i;
+    for (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 NULL;
+}
+
+-(NSMutableDictionary *)_webkit_parseRFC822HeaderFields
+{
+    NSMutableDictionary *headerFields = [NSMutableDictionary dictionary];
+
+    const UInt8 *bytes = [self bytes];
+    unsigned length = [self length];
+    NSString *lastKey = nil;
+    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 == 0) {
+            // Blank line; we're at the end of the header
+            break;
+        }
+        else if (*line == ' ' || *line == '\t') {
+            // Continuation of the previous header
+            if (!lastKey) {
+                // malformed header; ignore it and continue
+                continue;
+            }
+            else {
+                // Merge the continuation of the previous header
+                NSString *currentValue = [headerFields objectForKey:lastKey];
+                NSString *newValue = (NSString *)CFMakeCollectable(CFStringCreateWithBytes(NULL, line, lineLength, kCFStringEncodingISOLatin1, FALSE));
+                ASSERT(currentValue);
+                ASSERT(newValue);
+                NSString *mergedValue = [[NSString alloc] initWithFormat:@"%@%@", currentValue, newValue];
+                [headerFields setObject:(NSString *)mergedValue forKey:lastKey];
+                [newValue release];
+                [mergedValue release];
+                // Note: currentValue is autoreleased
+            }
+        }
+        else {
+            // Brand new header
+            const UInt8 *colon;
+            for (colon = line; *colon != ':' && colon != eol; colon ++) {
+                // empty loop
+            }
+            if (colon == eol) {
+                // malformed header; ignore it and continue
+                continue;
+            }
+            else {
+                lastKey = (NSString *)CFMakeCollectable(CFStringCreateWithBytes(NULL, line, colon - line, kCFStringEncodingISOLatin1, FALSE));
+                [lastKey autorelease];
+                NSString *value = [lastKey _web_capitalizeRFC822HeaderFieldName];
+                lastKey = value;
+                for (colon++; colon != eol; colon++) {
+                    if (*colon != ' ' && *colon != '\t') {
+                        break;
+                    }
+                }
+                if (colon == eol) {
+                    value = [[NSString alloc] initWithString:@""];
+                    [value autorelease];
+                }
+                else {
+                    value = (NSString *)CFMakeCollectable(CFStringCreateWithBytes(NULL, colon, eol-colon, kCFStringEncodingISOLatin1, FALSE));
+                    [value autorelease];
+                }
+                NSString *oldValue = [headerFields objectForKey:lastKey];
+                if (oldValue) {
+                    NSString *newValue = [[NSString alloc] initWithFormat:@"%@, %@", oldValue, value];
+                    value = newValue;
+                    [newValue autorelease];
+                }
+                [headerFields setObject:(NSString *)value forKey:lastKey];
+            }
+        }
+    }
+
+    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];
+    unsigned 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;
+            } else if (bytes[i] == '\n') {
+                // Support for Director. It sends "\r\n\n" (3880387).
+                return i+1;
+            } else if (bytes[i] == '\r' && bytes[i+1] == '\n') {
+                // Support for Flash. It sends "\r\n\r\n" (3758113).
+                return i+2;
+            }
+        }
+    }
+    return NSNotFound;
+}
+
+@end