1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ManifestParser.h"
28
29#include "TextResourceDecoder.h"
30#include "URL.h"
31#include <wtf/text/StringView.h>
32#include <wtf/unicode/CharacterNames.h>
33
34namespace WebCore {
35
36enum Mode { Explicit, Fallback, OnlineWhitelist, Unknown };
37
38bool parseManifest(const URL& manifestURL, const char* data, int length, Manifest& manifest)
39{
40    ASSERT(manifest.explicitURLs.isEmpty());
41    ASSERT(manifest.onlineWhitelistedURLs.isEmpty());
42    ASSERT(manifest.fallbackURLs.isEmpty());
43    manifest.allowAllNetworkRequests = false;
44
45    Mode mode = Explicit;
46
47    String s = TextResourceDecoder::create("text/cache-manifest", "UTF-8")->decodeAndFlush(data, length);
48
49    // Look for the magic signature: "^\xFEFF?CACHE MANIFEST[ \t]?" (the BOM is removed by TextResourceDecoder).
50    // Example: "CACHE MANIFEST #comment" is a valid signature.
51    // Example: "CACHE MANIFEST;V2" is not.
52    if (!s.startsWith("CACHE MANIFEST"))
53        return false;
54
55    StringView manifestAfterSignature = StringView(s).substring(14); // "CACHE MANIFEST" is 14 characters.
56    auto upconvertedCharacters = manifestAfterSignature.upconvertedCharacters();
57    const UChar* p = upconvertedCharacters;
58    const UChar* end = p + manifestAfterSignature.length();
59
60    if (p < end && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
61        return false;
62
63    // Skip to the end of the line.
64    while (p < end && *p != '\r' && *p != '\n')
65        p++;
66
67    while (1) {
68        // Skip whitespace
69        while (p < end && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t'))
70            p++;
71
72        if (p == end)
73            break;
74
75        const UChar* lineStart = p;
76
77        // Find the end of the line
78        while (p < end && *p != '\r' && *p != '\n')
79            p++;
80
81        // Check if we have a comment
82        if (*lineStart == '#')
83            continue;
84
85        // Get rid of trailing whitespace
86        const UChar* tmp = p - 1;
87        while (tmp > lineStart && (*tmp == ' ' || *tmp == '\t'))
88            tmp--;
89
90        String line(lineStart, tmp - lineStart + 1);
91
92        if (line == "CACHE:")
93            mode = Explicit;
94        else if (line == "FALLBACK:")
95            mode = Fallback;
96        else if (line == "NETWORK:")
97            mode = OnlineWhitelist;
98        else if (line.endsWith(':'))
99            mode = Unknown;
100        else if (mode == Unknown)
101            continue;
102        else if (mode == Explicit || mode == OnlineWhitelist) {
103            auto upconvertedLineCharacters = StringView(line).upconvertedCharacters();
104            const UChar* p = upconvertedLineCharacters;
105            const UChar* lineEnd = p + line.length();
106
107            // Look for whitespace separating the URL from subsequent ignored tokens.
108            while (p < lineEnd && *p != '\t' && *p != ' ')
109                p++;
110
111            if (mode == OnlineWhitelist && p - upconvertedLineCharacters == 1 && line[0] == '*') {
112                // Wildcard was found.
113                manifest.allowAllNetworkRequests = true;
114                continue;
115            }
116
117            URL url(manifestURL, line.substring(0, p - upconvertedLineCharacters));
118
119            if (!url.isValid())
120                continue;
121
122            if (url.hasFragmentIdentifier())
123                url.removeFragmentIdentifier();
124
125            if (!equalIgnoringCase(url.protocol(), manifestURL.protocol()))
126                continue;
127
128            if (mode == Explicit && manifestURL.protocolIs("https") && !protocolHostAndPortAreEqual(manifestURL, url))
129                continue;
130
131            if (mode == Explicit)
132                manifest.explicitURLs.add(url.string());
133            else
134                manifest.onlineWhitelistedURLs.append(url);
135
136        } else if (mode == Fallback) {
137            auto upconvertedLineCharacters = StringView(line).upconvertedCharacters();
138            const UChar* p = upconvertedLineCharacters;
139            const UChar* lineEnd = p + line.length();
140
141            // Look for whitespace separating the two URLs
142            while (p < lineEnd && *p != '\t' && *p != ' ')
143                p++;
144
145            if (p == lineEnd) {
146                // There was no whitespace separating the URLs.
147                continue;
148            }
149
150            URL namespaceURL(manifestURL, line.substring(0, p - upconvertedLineCharacters));
151            if (!namespaceURL.isValid())
152                continue;
153            if (namespaceURL.hasFragmentIdentifier())
154                namespaceURL.removeFragmentIdentifier();
155
156            if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL))
157                continue;
158
159            // Skip whitespace separating fallback namespace from URL.
160            while (p < lineEnd && (*p == '\t' || *p == ' '))
161                p++;
162
163            // Look for whitespace separating the URL from subsequent ignored tokens.
164            const UChar* fallbackStart = p;
165            while (p < lineEnd && *p != '\t' && *p != ' ')
166                p++;
167
168            URL fallbackURL(manifestURL, String(fallbackStart, p - fallbackStart));
169            if (!fallbackURL.isValid())
170                continue;
171            if (fallbackURL.hasFragmentIdentifier())
172                fallbackURL.removeFragmentIdentifier();
173
174            if (!protocolHostAndPortAreEqual(manifestURL, fallbackURL))
175                continue;
176
177            manifest.fallbackURLs.append(std::make_pair(namespaceURL, fallbackURL));
178        } else
179            ASSERT_NOT_REACHED();
180    }
181
182    return true;
183}
184
185}
186