1 | /* |
2 | * Copyright (C) 2008, 2010, 2013, 2014 Apple Inc. All Rights Reserved. |
3 | * Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/ |
4 | * Copyright (C) 2010 Google Inc. All Rights Reserved. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above copyright |
12 | * notice, this list of conditions and the following disclaimer in the |
13 | * documentation and/or other materials provided with the distribution. |
14 | * |
15 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
16 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
18 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
19 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
20 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
21 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
22 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
23 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | #include "CSSPreloadScanner.h" |
30 | |
31 | #include "HTMLParserIdioms.h" |
32 | #include <wtf/SetForScope.h> |
33 | |
34 | namespace WebCore { |
35 | |
36 | CSSPreloadScanner::CSSPreloadScanner() |
37 | : m_state(Initial) |
38 | , m_requests(nullptr) |
39 | { |
40 | } |
41 | |
42 | CSSPreloadScanner::~CSSPreloadScanner() = default; |
43 | |
44 | void CSSPreloadScanner::reset() |
45 | { |
46 | m_state = Initial; |
47 | m_rule.clear(); |
48 | m_ruleValue.clear(); |
49 | } |
50 | |
51 | void CSSPreloadScanner::scan(const HTMLToken::DataVector& data, PreloadRequestStream& requests) |
52 | { |
53 | ASSERT(!m_requests); |
54 | SetForScope<PreloadRequestStream*> change(m_requests, &requests); |
55 | |
56 | for (UChar c : data) { |
57 | if (m_state == DoneParsingImportRules) |
58 | break; |
59 | |
60 | tokenize(c); |
61 | } |
62 | } |
63 | |
64 | inline void CSSPreloadScanner::tokenize(UChar c) |
65 | { |
66 | // We are just interested in @import rules, no need for real tokenization here |
67 | // Searching for other types of resources is probably low payoff. |
68 | switch (m_state) { |
69 | case Initial: |
70 | if (isHTMLSpace(c)) |
71 | break; |
72 | if (c == '@') |
73 | m_state = RuleStart; |
74 | else if (c == '/') |
75 | m_state = MaybeComment; |
76 | else |
77 | m_state = DoneParsingImportRules; |
78 | break; |
79 | case MaybeComment: |
80 | if (c == '*') |
81 | m_state = Comment; |
82 | else |
83 | m_state = Initial; |
84 | break; |
85 | case Comment: |
86 | if (c == '*') |
87 | m_state = MaybeCommentEnd; |
88 | break; |
89 | case MaybeCommentEnd: |
90 | if (c == '*') |
91 | break; |
92 | if (c == '/') |
93 | m_state = Initial; |
94 | else |
95 | m_state = Comment; |
96 | break; |
97 | case RuleStart: |
98 | if (isASCIIAlpha(c)) { |
99 | m_rule.clear(); |
100 | m_ruleValue.clear(); |
101 | m_rule.append(c); |
102 | m_state = Rule; |
103 | } else |
104 | m_state = Initial; |
105 | break; |
106 | case Rule: |
107 | if (isHTMLSpace(c)) |
108 | m_state = AfterRule; |
109 | else if (c == ';') |
110 | m_state = Initial; |
111 | else |
112 | m_rule.append(c); |
113 | break; |
114 | case AfterRule: |
115 | if (isHTMLSpace(c)) |
116 | break; |
117 | if (c == ';') |
118 | m_state = Initial; |
119 | else if (c == '{') |
120 | m_state = DoneParsingImportRules; |
121 | else { |
122 | m_state = RuleValue; |
123 | m_ruleValue.append(c); |
124 | } |
125 | break; |
126 | case RuleValue: |
127 | if (isHTMLSpace(c)) |
128 | m_state = AfterRuleValue; |
129 | else if (c == ';') |
130 | emitRule(); |
131 | else |
132 | m_ruleValue.append(c); |
133 | break; |
134 | case AfterRuleValue: |
135 | if (isHTMLSpace(c)) |
136 | break; |
137 | if (c == ';') |
138 | emitRule(); |
139 | else if (c == '{') |
140 | m_state = DoneParsingImportRules; |
141 | else { |
142 | // FIXME: media rules |
143 | m_state = Initial; |
144 | } |
145 | break; |
146 | case DoneParsingImportRules: |
147 | ASSERT_NOT_REACHED(); |
148 | break; |
149 | } |
150 | } |
151 | |
152 | static String parseCSSStringOrURL(const UChar* characters, size_t length) |
153 | { |
154 | size_t offset = 0; |
155 | size_t reducedLength = length; |
156 | |
157 | while (reducedLength && isHTMLSpace(characters[offset])) { |
158 | ++offset; |
159 | --reducedLength; |
160 | } |
161 | while (reducedLength && isHTMLSpace(characters[offset + reducedLength - 1])) |
162 | --reducedLength; |
163 | |
164 | if (reducedLength >= 5 |
165 | && (characters[offset] == 'u' || characters[offset] == 'U') |
166 | && (characters[offset + 1] == 'r' || characters[offset + 1] == 'R') |
167 | && (characters[offset + 2] == 'l' || characters[offset + 2] == 'L') |
168 | && characters[offset + 3] == '(' |
169 | && characters[offset + reducedLength - 1] == ')') { |
170 | offset += 4; |
171 | reducedLength -= 5; |
172 | } |
173 | |
174 | while (reducedLength && isHTMLSpace(characters[offset])) { |
175 | ++offset; |
176 | --reducedLength; |
177 | } |
178 | while (reducedLength && isHTMLSpace(characters[offset + reducedLength - 1])) |
179 | --reducedLength; |
180 | |
181 | if (reducedLength < 2 || characters[offset] != characters[offset + reducedLength - 1] || !(characters[offset] == '\'' || characters[offset] == '"')) |
182 | return String(); |
183 | offset++; |
184 | reducedLength -= 2; |
185 | |
186 | while (reducedLength && isHTMLSpace(characters[offset])) { |
187 | ++offset; |
188 | --reducedLength; |
189 | } |
190 | while (reducedLength && isHTMLSpace(characters[offset + reducedLength - 1])) |
191 | --reducedLength; |
192 | |
193 | return String(characters + offset, reducedLength); |
194 | } |
195 | |
196 | void CSSPreloadScanner::emitRule() |
197 | { |
198 | StringView rule(m_rule.data(), m_rule.size()); |
199 | if (equalLettersIgnoringASCIICase(rule, "import" )) { |
200 | String url = parseCSSStringOrURL(m_ruleValue.data(), m_ruleValue.size()); |
201 | if (!url.isEmpty()) { |
202 | URL baseElementURL; // FIXME: This should be passed in from the HTMLPreloadScanner via scan(): without it we will get relative URLs wrong. |
203 | // FIXME: Should this be including the charset in the preload request? |
204 | m_requests->append(std::make_unique<PreloadRequest>("css" , url, baseElementURL, CachedResource::Type::CSSStyleSheet, String(), PreloadRequest::ModuleScript::No)); |
205 | } |
206 | m_state = Initial; |
207 | } else if (equalLettersIgnoringASCIICase(rule, "charset" )) |
208 | m_state = Initial; |
209 | else |
210 | m_state = DoneParsingImportRules; |
211 | m_rule.clear(); |
212 | m_ruleValue.clear(); |
213 | } |
214 | |
215 | } |
216 | |