1/*
2 * Copyright (C) 2006, 2008, 2009, 2010 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 COMPUTER, 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 COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
20 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26#include "core/html/HTMLViewSourceDocument.h"
27
28#include "core/HTMLNames.h"
29#include "core/dom/StyleEngine.h"
30#include "core/dom/Text.h"
31#include "core/html/HTMLAnchorElement.h"
32#include "core/html/HTMLBRElement.h"
33#include "core/html/HTMLBaseElement.h"
34#include "core/html/HTMLBodyElement.h"
35#include "core/html/HTMLDivElement.h"
36#include "core/html/HTMLHeadElement.h"
37#include "core/html/HTMLHtmlElement.h"
38#include "core/html/HTMLSpanElement.h"
39#include "core/html/HTMLTableCellElement.h"
40#include "core/html/HTMLTableElement.h"
41#include "core/html/HTMLTableRowElement.h"
42#include "core/html/HTMLTableSectionElement.h"
43#include "core/html/parser/HTMLToken.h"
44#include "core/html/parser/HTMLViewSourceParser.h"
45
46namespace blink {
47
48using namespace HTMLNames;
49
50namespace {
51
52const char kXSSDetected[] = "Token contains a reflected XSS vector";
53
54} // namespace
55
56HTMLViewSourceDocument::HTMLViewSourceDocument(const DocumentInit& initializer, const String& mimeType)
57    : HTMLDocument(initializer)
58    , m_type(mimeType)
59{
60    setIsViewSource(true);
61
62    // FIXME: Why do view-source pages need to load in quirks mode?
63    setCompatibilityMode(QuirksMode);
64    lockCompatibilityMode();
65}
66
67PassRefPtrWillBeRawPtr<DocumentParser> HTMLViewSourceDocument::createParser()
68{
69    return HTMLViewSourceParser::create(*this, m_type);
70}
71
72void HTMLViewSourceDocument::createContainingTable()
73{
74    RefPtrWillBeRawPtr<HTMLHtmlElement> html = HTMLHtmlElement::create(*this);
75    parserAppendChild(html);
76    RefPtrWillBeRawPtr<HTMLHeadElement> head = HTMLHeadElement::create(*this);
77    html->parserAppendChild(head);
78    RefPtrWillBeRawPtr<HTMLBodyElement> body = HTMLBodyElement::create(*this);
79    html->parserAppendChild(body);
80
81    // Create a line gutter div that can be used to make sure the gutter extends down the height of the whole
82    // document.
83    RefPtrWillBeRawPtr<HTMLDivElement> div = HTMLDivElement::create(*this);
84    div->setAttribute(classAttr, "line-gutter-backdrop");
85    body->parserAppendChild(div);
86
87    RefPtrWillBeRawPtr<HTMLTableElement> table = HTMLTableElement::create(*this);
88    body->parserAppendChild(table);
89    m_tbody = HTMLTableSectionElement::create(tbodyTag, *this);
90    table->parserAppendChild(m_tbody);
91    m_current = m_tbody;
92    m_lineNumber = 0;
93}
94
95void HTMLViewSourceDocument::addSource(const String& source, HTMLToken& token, SourceAnnotation annotation)
96{
97    if (!m_current)
98        createContainingTable();
99
100    switch (token.type()) {
101    case HTMLToken::Uninitialized:
102        ASSERT_NOT_REACHED();
103        break;
104    case HTMLToken::DOCTYPE:
105        processDoctypeToken(source, token);
106        break;
107    case HTMLToken::EndOfFile:
108        processEndOfFileToken(source, token);
109        break;
110    case HTMLToken::StartTag:
111    case HTMLToken::EndTag:
112        processTagToken(source, token, annotation);
113        break;
114    case HTMLToken::Comment:
115        processCommentToken(source, token);
116        break;
117    case HTMLToken::Character:
118        processCharacterToken(source, token, annotation);
119        break;
120    }
121}
122
123void HTMLViewSourceDocument::processDoctypeToken(const String& source, HTMLToken&)
124{
125    m_current = addSpanWithClassName("html-doctype");
126    addText(source, "html-doctype");
127    m_current = m_td;
128}
129
130void HTMLViewSourceDocument::processEndOfFileToken(const String& source, HTMLToken&)
131{
132    m_current = addSpanWithClassName("html-end-of-file");
133    addText(source, "html-end-of-file");
134    m_current = m_td;
135}
136
137void HTMLViewSourceDocument::processTagToken(const String& source, HTMLToken& token, SourceAnnotation annotation)
138{
139    maybeAddSpanForAnnotation(annotation);
140    m_current = addSpanWithClassName("html-tag");
141
142    AtomicString tagName(token.name());
143
144    unsigned index = 0;
145    HTMLToken::AttributeList::const_iterator iter = token.attributes().begin();
146    while (index < source.length()) {
147        if (iter == token.attributes().end()) {
148            // We want to show the remaining characters in the token.
149            index = addRange(source, index, source.length(), emptyAtom);
150            ASSERT(index == source.length());
151            break;
152        }
153
154        AtomicString name(iter->name);
155        AtomicString value(StringImpl::create8BitIfPossible(iter->value));
156
157        index = addRange(source, index, iter->nameRange.start - token.startIndex(), emptyAtom);
158        index = addRange(source, index, iter->nameRange.end - token.startIndex(), "html-attribute-name");
159
160        if (tagName == baseTag && name == hrefAttr)
161            addBase(value);
162
163        index = addRange(source, index, iter->valueRange.start - token.startIndex(), emptyAtom);
164
165        bool isLink = name == srcAttr || name == hrefAttr;
166        index = addRange(source, index, iter->valueRange.end - token.startIndex(), "html-attribute-value", isLink, tagName == aTag, value);
167
168        ++iter;
169    }
170    m_current = m_td;
171}
172
173void HTMLViewSourceDocument::processCommentToken(const String& source, HTMLToken&)
174{
175    m_current = addSpanWithClassName("html-comment");
176    addText(source, "html-comment");
177    m_current = m_td;
178}
179
180void HTMLViewSourceDocument::processCharacterToken(const String& source, HTMLToken&, SourceAnnotation annotation)
181{
182    addText(source, "", annotation);
183}
184
185PassRefPtrWillBeRawPtr<Element> HTMLViewSourceDocument::addSpanWithClassName(const AtomicString& className)
186{
187    if (m_current == m_tbody) {
188        addLine(className);
189        return m_current;
190    }
191
192    RefPtrWillBeRawPtr<HTMLSpanElement> span = HTMLSpanElement::create(*this);
193    span->setAttribute(classAttr, className);
194    m_current->parserAppendChild(span);
195    return span.release();
196}
197
198void HTMLViewSourceDocument::addLine(const AtomicString& className)
199{
200    // Create a table row.
201    RefPtrWillBeRawPtr<HTMLTableRowElement> trow = HTMLTableRowElement::create(*this);
202    m_tbody->parserAppendChild(trow);
203
204    // Create a cell that will hold the line number (it is generated in the stylesheet using counters).
205    RefPtrWillBeRawPtr<HTMLTableCellElement> td = HTMLTableCellElement::create(tdTag, *this);
206    td->setAttribute(classAttr, "line-number");
207    td->setIntegralAttribute(valueAttr, ++m_lineNumber);
208    trow->parserAppendChild(td);
209
210    // Create a second cell for the line contents
211    td = HTMLTableCellElement::create(tdTag, *this);
212    td->setAttribute(classAttr, "line-content");
213    trow->parserAppendChild(td);
214    m_current = m_td = td;
215
216    // Open up the needed spans.
217    if (!className.isEmpty()) {
218        if (className == "html-attribute-name" || className == "html-attribute-value")
219            m_current = addSpanWithClassName("html-tag");
220        m_current = addSpanWithClassName(className);
221    }
222}
223
224void HTMLViewSourceDocument::finishLine()
225{
226    if (!m_current->hasChildren()) {
227        RefPtrWillBeRawPtr<HTMLBRElement> br = HTMLBRElement::create(*this);
228        m_current->parserAppendChild(br);
229    }
230    m_current = m_tbody;
231}
232
233void HTMLViewSourceDocument::addText(const String& text, const AtomicString& className, SourceAnnotation annotation)
234{
235    if (text.isEmpty())
236        return;
237
238    // Add in the content, splitting on newlines.
239    Vector<String> lines;
240    text.split('\n', true, lines);
241    unsigned size = lines.size();
242    for (unsigned i = 0; i < size; i++) {
243        String substring = lines[i];
244        if (m_current == m_tbody)
245            addLine(className);
246        if (substring.isEmpty()) {
247            if (i == size - 1)
248                break;
249            finishLine();
250            continue;
251        }
252        RefPtrWillBeRawPtr<Element> oldElement = m_current;
253        maybeAddSpanForAnnotation(annotation);
254        m_current->parserAppendChild(Text::create(*this, substring));
255        m_current = oldElement;
256        if (i < size - 1)
257            finishLine();
258    }
259}
260
261int HTMLViewSourceDocument::addRange(const String& source, int start, int end, const AtomicString& className, bool isLink, bool isAnchor, const AtomicString& link)
262{
263    ASSERT(start <= end);
264    if (start == end)
265        return start;
266
267    String text = source.substring(start, end - start);
268    if (!className.isEmpty()) {
269        if (isLink)
270            m_current = addLink(link, isAnchor);
271        else
272            m_current = addSpanWithClassName(className);
273    }
274    addText(text, className);
275    if (!className.isEmpty() && m_current != m_tbody)
276        m_current = toElement(m_current->parentNode());
277    return end;
278}
279
280PassRefPtrWillBeRawPtr<Element> HTMLViewSourceDocument::addBase(const AtomicString& href)
281{
282    RefPtrWillBeRawPtr<HTMLBaseElement> base = HTMLBaseElement::create(*this);
283    base->setAttribute(hrefAttr, href);
284    m_current->parserAppendChild(base);
285    return base.release();
286}
287
288PassRefPtrWillBeRawPtr<Element> HTMLViewSourceDocument::addLink(const AtomicString& url, bool isAnchor)
289{
290    if (m_current == m_tbody)
291        addLine("html-tag");
292
293    // Now create a link for the attribute value instead of a span.
294    RefPtrWillBeRawPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(*this);
295    const char* classValue;
296    if (isAnchor)
297        classValue = "html-attribute-value html-external-link";
298    else
299        classValue = "html-attribute-value html-resource-link";
300    anchor->setAttribute(classAttr, classValue);
301    anchor->setAttribute(targetAttr, "_blank");
302    anchor->setAttribute(hrefAttr, url);
303    m_current->parserAppendChild(anchor);
304    return anchor.release();
305}
306
307void HTMLViewSourceDocument::maybeAddSpanForAnnotation(SourceAnnotation annotation)
308{
309    if (annotation == AnnotateSourceAsXSS) {
310        m_current = addSpanWithClassName("highlight");
311        m_current->setAttribute(titleAttr, kXSSDetected);
312    }
313}
314
315void HTMLViewSourceDocument::trace(Visitor* visitor)
316{
317    visitor->trace(m_current);
318    visitor->trace(m_tbody);
319    visitor->trace(m_td);
320    HTMLDocument::trace(visitor);
321}
322
323}
324