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
34namespace WebCore {
35
36CSSPreloadScanner::CSSPreloadScanner()
37 : m_state(Initial)
38 , m_requests(nullptr)
39{
40}
41
42CSSPreloadScanner::~CSSPreloadScanner() = default;
43
44void CSSPreloadScanner::reset()
45{
46 m_state = Initial;
47 m_rule.clear();
48 m_ruleValue.clear();
49}
50
51void 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
64inline 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
152static 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
196void 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