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 "PageGroup.h"
28
29#include "ChromeClient.h"
30#include "Document.h"
31#include "Page.h"
32
33namespace WebCore {
34
35// --------
36
37PageGroup::PageGroup(Page* page)
38 : m_visitedLinksPopulated(false)
39{
40 ASSERT(page);
41 m_pages.add(page);
42}
43
44void PageGroup::addPage(Page* page)
45{
46 ASSERT(page);
47 ASSERT(!m_pages.contains(page));
48 m_pages.add(page);
49}
50
51void PageGroup::removePage(Page* page)
52{
53 ASSERT(page);
54 ASSERT(m_pages.contains(page));
55 m_pages.remove(page);
56}
57
58static inline int findSlashDotDotSlash(const UChar* characters, size_t length)
59{
60 if (length < 4)
61 return -1;
62 unsigned loopLimit = length - 3;
63 for (unsigned i = 0; i < loopLimit; ++i) {
64 if (characters[i] == '/' && characters[i + 1] == '.' && characters[i + 2] == '.' && characters[i + 3] == '/')
65 return i;
66 }
67 return -1;
68}
69
70static inline int findSlashSlash(const UChar* characters, size_t length, int position)
71{
72 if (length < 2)
73 return -1;
74 unsigned loopLimit = length - 1;
75 for (unsigned i = position; i < loopLimit; ++i) {
76 if (characters[i] == '/' && characters[i + 1] == '/')
77 return i;
78 }
79 return -1;
80}
81
82static inline int findSlashDotSlash(const UChar* characters, size_t length)
83{
84 if (length < 3)
85 return -1;
86 unsigned loopLimit = length - 2;
87 for (unsigned i = 0; i < loopLimit; ++i) {
88 if (characters[i] == '/' && characters[i + 1] == '.' && characters[i + 2] == '/')
89 return i;
90 }
91 return -1;
92}
93
94static inline bool containsColonSlashSlash(const UChar* characters, unsigned length)
95{
96 if (length < 3)
97 return false;
98 unsigned loopLimit = length - 2;
99 for (unsigned i = 0; i < loopLimit; ++i) {
100 if (characters[i] == ':' && characters[i + 1] == '/' && characters[i + 2] == '/')
101 return true;
102 }
103 return false;
104}
105
106static inline void cleanPath(Vector<UChar, 512>& path)
107{
108 // FIXME: Shold not do this in the query or anchor part.
109 int pos;
110 while ((pos = findSlashDotDotSlash(path.data(), path.size())) != -1) {
111 int prev = reverseFind(path.data(), path.size(), '/', pos - 1);
112 // don't remove the host, i.e. http://foo.org/../foo.html
113 if (prev < 0 || (prev > 3 && path[prev - 2] == ':' && path[prev - 1] == '/'))
114 path.remove(pos, 3);
115 else
116 path.remove(prev, pos - prev + 3);
117 }
118
119 // FIXME: Shold not do this in the query part.
120 // Set refPos to -2 to mean "I haven't looked for the anchor yet".
121 // We don't want to waste a function call on the search for the the anchor
122 // in the vast majority of cases where there is no "//" in the path.
123 pos = 0;
124 int refPos = -2;
125 while ((pos = findSlashSlash(path.data(), path.size(), pos)) != -1) {
126 if (refPos == -2)
127 refPos = find(path.data(), path.size(), '#');
128 if (refPos > 0 && pos >= refPos)
129 break;
130
131 if (pos == 0 || path[pos - 1] != ':')
132 path.remove(pos);
133 else
134 pos += 2;
135 }
136
137 // FIXME: Shold not do this in the query or anchor part.
138 while ((pos = findSlashDotSlash(path.data(), path.size())) != -1)
139 path.remove(pos, 2);
140}
141
142static inline bool matchLetter(UChar c, UChar lowercaseLetter)
143{
144 return (c | 0x20) == lowercaseLetter;
145}
146
147static inline bool needsTrailingSlash(const UChar* characters, unsigned length)
148{
149 if (length < 6)
150 return false;
151 if (!matchLetter(characters[0], 'h')
152 || !matchLetter(characters[1], 't')
153 || !matchLetter(characters[2], 't')
154 || !matchLetter(characters[3], 'p'))
155 return false;
156 if (!(characters[4] == ':'
157 || (matchLetter(characters[4], 's') && characters[5] == ':')))
158 return false;
159
160 unsigned pos = characters[4] == ':' ? 5 : 6;
161
162 // Skip initial two slashes if present.
163 if (pos + 1 < length && characters[pos] == '/' && characters[pos + 1] == '/')
164 pos += 2;
165
166 // Find next slash.
167 while (pos < length && characters[pos] != '/')
168 ++pos;
169
170 return pos == length;
171}
172
173bool PageGroup::isLinkVisited(Document* document, const AtomicString& attributeURL)
174{
175 if (!m_visitedLinksPopulated) {
176 m_visitedLinksPopulated = true;
177 ASSERT(!m_pages.isEmpty());
178 (*m_pages.begin())->chrome()->client()->populateVisitedLinks();
179 }
180
181 const UChar* characters = attributeURL.characters();
182 unsigned length = attributeURL.length();
183 if (!length)
184 return false;
185
186 // FIXME: It is strange that we do not do further processing on strings that have "://" in them.
187 // That's clearly incorrect for at least these reasons:
188 // 1) The "://" could be in the query or anchor.
189 // 2) The URL's path could have a "/./" or a "/../" or a "//" sequence in it.
190
191 // FIXME: needsTrailingSlash does not properly return true for a URL that has no path, but does
192 // have a query or anchor.
193
194 bool hasColonSlashSlash = containsColonSlashSlash(characters, length);
195
196 if (hasColonSlashSlash && !needsTrailingSlash(characters, length))
197 return m_visitedLinkHashes.contains(StringImpl::computeHash(characters, length));
198
199 Vector<UChar, 512> buffer;
200
201 // This is a poor man's completeURL. Faster with less memory allocation.
202 // FIXME: It's missing a lot of what completeURL does and what KURL does.
203 // FIXME: Move this into KURL? Or Document? Even the fast version should be in the right place,
204 // rather than here.
205
206 if (hasColonSlashSlash) {
207 // FIXME: This is incorrect for URLs that have a query or anchor; the "/" needs to go at the
208 // end of the path, *before* the query or anchor.
209 buffer.append(characters, length);
210 buffer.append('/');
211 return m_visitedLinkHashes.contains(StringImpl::computeHash(buffer.data(), buffer.size()));
212 }
213
214 const KURL& baseURL = document->baseURL();
215 switch (characters[0]) {
216 case '/':
217 buffer.append(baseURL.string().characters(), baseURL.pathStart());
218 break;
219 case '#':
220 buffer.append(baseURL.string().characters(), baseURL.pathEnd());
221 break;
222 default:
223 buffer.append(baseURL.string().characters(), baseURL.pathAfterLastSlash());
224 break;
225 }
226 buffer.append(characters, length);
227 cleanPath(buffer);
228 if (needsTrailingSlash(buffer.data(), buffer.size())) {
229 // FIXME: This is incorrect for URLs that have a query or anchor; the "/" needs to go at the
230 // end of the path, *before* the query or anchor.
231 buffer.append('/');
232 }
233
234 return m_visitedLinkHashes.contains(StringImpl::computeHash(buffer.data(), buffer.size()));
235}
236
237void PageGroup::addVisitedLink(const KURL& url)
238{
239 ASSERT(!url.isEmpty());
240 m_visitedLinkHashes.add(url.string().impl()->hash());
241}
242
243void PageGroup::addVisitedLink(const UChar* characters, size_t length)
244{
245 m_visitedLinkHashes.add(StringImpl::computeHash(characters, length));
246}
247
248void PageGroup::removeVisitedLinks()
249{
250 m_visitedLinkHashes.clear();
251}
252
253void PageGroup::removeAllVisitedLinks()
254{
255 Page::removeAllVisitedLinks();
256}
257
258} // namespace WebCore