106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen/*
206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * Copyright (C) 2008 Apple Inc. All Rights Reserved.
306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/
406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * Copyright (C) 2010 Google Inc. All Rights Reserved.
506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen *
606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * Redistribution and use in source and binary forms, with or without
706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * modification, are permitted provided that the following conditions
806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * are met:
906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * 1. Redistributions of source code must retain the above copyright
1006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen *    notice, this list of conditions and the following disclaimer.
1106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * 2. Redistributions in binary form must reproduce the above copyright
1206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen *    notice, this list of conditions and the following disclaimer in the
1306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen *    documentation and/or other materials provided with the distribution.
1406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen *
1506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
1606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
1806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
1906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
2006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
2106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
2206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
2306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen */
2706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
2806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen#include "config.h"
2906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen#include "HTMLPreloadScanner.h"
3006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
315abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick#include "CachedResourceLoader.h"
3206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen#include "Document.h"
332bde8e466a4451c7319e3a072d118917957d6554Steve Block#include "InputType.h"
3468513a70bcd92384395513322f1b801e7bf9c729Steve Block#include "HTMLDocumentParser.h"
35e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block#include "HTMLTokenizer.h"
3606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen#include "HTMLLinkElement.h"
3706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen#include "HTMLNames.h"
38a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch#include "HTMLParserIdioms.h"
39f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch#include "MediaList.h"
40f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch#include "MediaQueryEvaluator.h"
4106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
4206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsennamespace WebCore {
4306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
4406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenusing namespace HTMLNames;
4506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
4606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsennamespace {
4706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
4806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenclass PreloadTask {
4906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenpublic:
5006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    PreloadTask(const HTMLToken& token)
5106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        : m_tagName(token.name().data(), token.name().size())
5206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        , m_linkIsStyleSheet(false)
53f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        , m_linkMediaAttributeIsScreen(true)
542bde8e466a4451c7319e3a072d118917957d6554Steve Block        , m_inputIsImage(false)
5506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    {
5606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        processAttributes(token.attributes());
5706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    }
5806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
5906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    void processAttributes(const HTMLToken::AttributeList& attributes)
6006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    {
612bde8e466a4451c7319e3a072d118917957d6554Steve Block        if (m_tagName != imgTag
622bde8e466a4451c7319e3a072d118917957d6554Steve Block            && m_tagName != inputTag
632bde8e466a4451c7319e3a072d118917957d6554Steve Block            && m_tagName != linkTag
642bde8e466a4451c7319e3a072d118917957d6554Steve Block            && m_tagName != scriptTag)
6506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            return;
6606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
6706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        for (HTMLToken::AttributeList::const_iterator iter = attributes.begin();
6806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen             iter != attributes.end(); ++iter) {
6906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            AtomicString attributeName(iter->m_name.data(), iter->m_name.size());
7006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            String attributeValue(iter->m_value.data(), iter->m_value.size());
7106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
7206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            if (attributeName == charsetAttr)
7306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen                m_charset = attributeValue;
7406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
7506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            if (m_tagName == scriptTag || m_tagName == imgTag) {
7606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen                if (attributeName == srcAttr)
7706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen                    setUrlToLoad(attributeValue);
7806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            } else if (m_tagName == linkTag) {
7906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen                if (attributeName == hrefAttr)
8006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen                    setUrlToLoad(attributeValue);
8106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen                else if (attributeName == relAttr)
8206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen                    m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue);
83f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch                else if (attributeName == mediaAttr)
84f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch                    m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue);
852bde8e466a4451c7319e3a072d118917957d6554Steve Block            } else if (m_tagName == inputTag) {
862bde8e466a4451c7319e3a072d118917957d6554Steve Block                if (attributeName == srcAttr)
872bde8e466a4451c7319e3a072d118917957d6554Steve Block                    setUrlToLoad(attributeValue);
882bde8e466a4451c7319e3a072d118917957d6554Steve Block                else if (attributeName == typeAttr)
892bde8e466a4451c7319e3a072d118917957d6554Steve Block                    m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image());
9006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            }
9106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        }
9206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    }
9306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
94f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    static bool relAttributeIsStyleSheet(const String& attributeValue)
9506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    {
9606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        HTMLLinkElement::RelAttribute rel;
9706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        HTMLLinkElement::tokenizeRelAttribute(attributeValue, rel);
9806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        return rel.m_isStyleSheet && !rel.m_isAlternate && !rel.m_isIcon && !rel.m_isDNSPrefetch;
9906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    }
1002bde8e466a4451c7319e3a072d118917957d6554Steve Block
101f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    static bool linkMediaAttributeIsScreen(const String& attributeValue)
102f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    {
103f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        if (attributeValue.isEmpty())
104f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch            return true;
105f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        RefPtr<MediaList> mediaList = MediaList::createAllowingDescriptionSyntax(attributeValue);
106f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch
107f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any
108f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which
109f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        // may be problematic here).
110f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        MediaQueryEvaluator mediaQueryEvaluator("screen");
111f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        return mediaQueryEvaluator.eval(mediaList.get());
112f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    }
11306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
11406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    void setUrlToLoad(const String& attributeValue)
11506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    {
11606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        // We only respect the first src/href, per HTML5:
11706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state
11806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        if (!m_urlToLoad.isEmpty())
11906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            return;
120a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue);
12106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    }
12206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
12306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    void preload(Document* document, bool scanningBody)
12406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    {
12506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        if (m_urlToLoad.isEmpty())
12606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            return;
12706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
1285abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick        CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader();
12906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        if (m_tagName == scriptTag)
1305abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick            cachedResourceLoader->preload(CachedResource::Script, m_urlToLoad, m_charset, scanningBody);
1312bde8e466a4451c7319e3a072d118917957d6554Steve Block        else if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage))
1325abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick            cachedResourceLoader->preload(CachedResource::ImageResource, m_urlToLoad, String(), scanningBody);
133f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        else if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen)
1345abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick            cachedResourceLoader->preload(CachedResource::CSSStyleSheet, m_urlToLoad, m_charset, scanningBody);
13506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    }
13606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
13706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    const AtomicString& tagName() const { return m_tagName; }
13806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
13906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenprivate:
14006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    AtomicString m_tagName;
14106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    String m_urlToLoad;
14206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    String m_charset;
14306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    bool m_linkIsStyleSheet;
144f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    bool m_linkMediaAttributeIsScreen;
1452bde8e466a4451c7319e3a072d118917957d6554Steve Block    bool m_inputIsImage;
14606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen};
14706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
14806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen} // namespace
14906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
15006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian MonsenHTMLPreloadScanner::HTMLPreloadScanner(Document* document)
15106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    : m_document(document)
15206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    , m_cssScanner(document)
15368513a70bcd92384395513322f1b801e7bf9c729Steve Block    , m_tokenizer(HTMLTokenizer::create(HTMLDocumentParser::usePreHTML5ParserQuirks(document)))
15406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    , m_bodySeen(false)
15506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    , m_inStyle(false)
15606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen{
15706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen}
15806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
15906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenvoid HTMLPreloadScanner::appendToEnd(const SegmentedString& source)
16006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen{
16106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    m_source.append(source);
16206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen}
16306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
16406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenvoid HTMLPreloadScanner::scan()
16506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen{
16606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    // FIXME: We should save and re-use these tokens in HTMLDocumentParser if
16706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    // the pending script doesn't end up calling document.write.
168e8b154fd68f9b33be40a3590e58347f353835f5cSteve Block    while (m_tokenizer->nextToken(m_source, m_token)) {
16906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        processToken();
17006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        m_token.clear();
17106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    }
17206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen}
17306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
17406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenvoid HTMLPreloadScanner::processToken()
17506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen{
17606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    if (m_inStyle) {
17706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        if (m_token.type() == HTMLToken::Character)
17806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            m_cssScanner.scan(m_token, scanningBody());
17906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        else if (m_token.type() == HTMLToken::EndTag) {
18006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            m_inStyle = false;
18106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen            m_cssScanner.reset();
18206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        }
18306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    }
18406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
18506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    if (m_token.type() != HTMLToken::StartTag)
18606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        return;
18706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
18806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    PreloadTask task(m_token);
18968513a70bcd92384395513322f1b801e7bf9c729Steve Block    m_tokenizer->updateStateFor(task.tagName(), m_document->frame());
19006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
19106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    if (task.tagName() == bodyTag)
19206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        m_bodySeen = true;
19306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
19406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    if (task.tagName() == styleTag)
19506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen        m_inStyle = true;
19606ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
19706ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    task.preload(m_document, scanningBody());
19806ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen}
19906ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
20006ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsenbool HTMLPreloadScanner::scanningBody() const
20106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen{
20206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen    return m_document->body() || m_bodySeen;
20306ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen}
20406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen
20506ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen}
206