15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)/*
25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    This library is free software; you can redistribute it and/or
95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    modify it under the terms of the GNU Library General Public
105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    License as published by the Free Software Foundation; either
115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    version 2 of the License, or (at your option) any later version.
125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    This library is distributed in the hope that it will be useful,
145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    but WITHOUT ANY WARRANTY; without even the implied warranty of
155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Library General Public License for more details.
175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    You should have received a copy of the GNU Library General Public License
195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    along with this library; see the file COPYING.LIB.  If not, write to
205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    Boston, MA 02110-1301, USA.
225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    This class provides all functionality needed for loading images, style sheets and html
245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    pages from the web. It has a memory cache for these objects.
255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)*/
265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "config.h"
28e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#include "core/fetch/CSSStyleSheetResource.h"
2953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
3053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#include "core/css/StyleSheetContents.h"
31e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#include "core/fetch/ResourceClientWalker.h"
32e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#include "core/fetch/StyleSheetResourceClient.h"
338abfc5808a4e34d6e03867af8bc440dee641886fTorne (Richard Coles)#include "core/fetch/TextResourceDecoder.h"
341e202183a5dc46166763171984b285173f8585e5Torne (Richard Coles)#include "platform/SharedBuffer.h"
351e202183a5dc46166763171984b285173f8585e5Torne (Richard Coles)#include "platform/network/HTTPParsers.h"
36591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch#include "wtf/CurrentTime.h"
37591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch#include "wtf/Vector.h"
385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)namespace WebCore {
405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
413c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben MurdochCSSStyleSheetResource::CSSStyleSheetResource(const ResourceRequest& resourceRequest, const String& charset)
42e08f70592b3fc0d5e68b9b914c9196e813720070Torne (Richard Coles)    : StyleSheetResource(resourceRequest, CSSStyleSheet)
435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    , m_decoder(TextResourceDecoder::create("text/css", charset))
445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
455267f701546148b83dfbe1d151cb184385bb5c22Torne (Richard Coles)    DEFINE_STATIC_LOCAL(const AtomicString, acceptCSS, ("text/css,*/*;q=0.1", AtomicString::ConstructFromLiteral));
465267f701546148b83dfbe1d151cb184385bb5c22Torne (Richard Coles)
475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // Prefer text/css but accept any type (dell.com serves a stylesheet
485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // as text/html; see <http://bugs.webkit.org/show_bug.cgi?id=11451>).
495267f701546148b83dfbe1d151cb184385bb5c22Torne (Richard Coles)    setAccept(acceptCSS);
505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
523c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben MurdochCSSStyleSheetResource::~CSSStyleSheetResource()
535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (m_parsedStyleSheetCache)
555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        m_parsedStyleSheetCache->removedFromMemoryCache();
565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
583c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochvoid CSSStyleSheetResource::didAddClient(ResourceClient* c)
595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
603c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch    ASSERT(c->resourceClientType() == StyleSheetResourceClient::expectedType());
61fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch    // Resource::didAddClient() must be before setCSSStyleSheet(),
625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // because setCSSStyleSheet() may cause scripts to be executed, which could destroy 'c' if it is an instance of HTMLLinkElement.
635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // see the comment of HTMLLinkElement::setCSSStyleSheet.
64fff8884795cb540f87cf6e6d67b629519b00eb8bBen Murdoch    Resource::didAddClient(c);
655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!isLoading())
673c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch        static_cast<StyleSheetResourceClient*>(c)->setCSSStyleSheet(m_resourceRequest.url(), m_response.url(), m_decoder->encoding().name(), this);
685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
703c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochvoid CSSStyleSheetResource::setEncoding(const String& chs)
715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader);
735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
753c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben MurdochString CSSStyleSheetResource::encoding() const
765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return m_decoder->encoding().name();
785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
7902772c6a72f1ee0b226341a4f4439970c29fc861Ben Murdoch
803c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochconst String CSSStyleSheetResource::sheetText(bool enforceMIMEType, bool* hasValidMIMEType) const
8102772c6a72f1ee0b226341a4f4439970c29fc861Ben Murdoch{
825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    ASSERT(!isPurgeable());
835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
845c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!m_data || m_data->isEmpty() || !canUseSheet(enforceMIMEType, hasValidMIMEType))
855c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return String();
8602772c6a72f1ee0b226341a4f4439970c29fc861Ben Murdoch
875c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!m_decodedSheetText.isNull())
885c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return m_decodedSheetText;
8902772c6a72f1ee0b226341a4f4439970c29fc861Ben Murdoch
905c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // Don't cache the decoded text, regenerating is cheap and it can use quite a bit of memory
915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    String sheetText = m_decoder->decode(m_data->data(), m_data->size());
925c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    sheetText.append(m_decoder->flush());
935c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return sheetText;
945c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
955c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
963c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochvoid CSSStyleSheetResource::checkNotify()
975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
985c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // Decode the data to find out the encoding and keep the sheet text around during checkNotify()
995c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (m_data) {
1005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        m_decodedSheetText = m_decoder->decode(m_data->data(), m_data->size());
1015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        m_decodedSheetText.append(m_decoder->flush());
1025c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    }
1035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1043c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch    ResourceClientWalker<StyleSheetResourceClient> w(m_clients);
1053c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch    while (StyleSheetResourceClient* c = w.next())
1065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        c->setCSSStyleSheet(m_resourceRequest.url(), m_response.url(), m_decoder->encoding().name(), this);
10753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    // Clear the decoded text as it is unlikely to be needed immediately again and is cheap to regenerate.
10853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    m_decodedSheetText = String();
1095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1113c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochbool CSSStyleSheetResource::canUseSheet(bool enforceMIMEType, bool* hasValidMIMEType) const
1125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
1135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (errorOccurred())
1145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return false;
11502772c6a72f1ee0b226341a4f4439970c29fc861Ben Murdoch
1165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!enforceMIMEType && !hasValidMIMEType)
1175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return true;
1185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1193c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch    // This check exactly matches Firefox. Note that we grab the Content-Type
1205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // header directly because we want to see what the value is BEFORE content
1213c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdoch    // sniffing. Firefox does this by setting a "type hint" on the channel.
1225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // This implementation should be observationally equivalent.
1235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    //
1245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // This code defaults to allowing the stylesheet for non-HTTP protocols so
1255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // folks can use standards mode for local HTML documents.
126a854de003a23bf3c7f95ec0f8154ada64092ff5cTorne (Richard Coles)    const AtomicString& mimeType = extractMIMETypeFromMediaType(response().httpHeaderField("Content-Type"));
1275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    bool typeOK = mimeType.isEmpty() || equalIgnoringCase(mimeType, "text/css") || equalIgnoringCase(mimeType, "application/x-unknown-content-type");
1285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (hasValidMIMEType)
1295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        *hasValidMIMEType = typeOK;
1305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!enforceMIMEType)
1315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return true;
1325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return typeOK;
1335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1353c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochvoid CSSStyleSheetResource::destroyDecodedData()
1365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
1375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!m_parsedStyleSheetCache)
1385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return;
1395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    m_parsedStyleSheetCache->removedFromMemoryCache();
1415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    m_parsedStyleSheetCache.clear();
1425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    setDecodedSize(0);
1445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
14593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)    if (isSafeToMakePurgeable())
1465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        makePurgeable(true);
1475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1493c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben MurdochPassRefPtr<StyleSheetContents> CSSStyleSheetResource::restoreParsedStyleSheet(const CSSParserContext& context)
1505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
1515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (!m_parsedStyleSheetCache)
1525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return 0;
1535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (m_parsedStyleSheetCache->hasFailedOrCanceledSubresources()) {
1545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        m_parsedStyleSheetCache->removedFromMemoryCache();
1555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        m_parsedStyleSheetCache.clear();
1565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return 0;
1575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    }
1585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    ASSERT(m_parsedStyleSheetCache->isCacheable());
1605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    ASSERT(m_parsedStyleSheetCache->isInMemoryCache());
1615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    // Contexts must be identical so we know we would get the same exact result if we parsed again.
1635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (m_parsedStyleSheetCache->parserContext() != context)
1645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return 0;
1655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    didAccessDecodedData(currentTime());
1675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return m_parsedStyleSheetCache;
1695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1705c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1713c9e4aeaee9f9b0a9a814da07bcb33319c7ea363Ben Murdochvoid CSSStyleSheetResource::saveParsedStyleSheet(PassRefPtr<StyleSheetContents> sheet)
1725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){
1735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    ASSERT(sheet && sheet->isCacheable());
1745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    if (m_parsedStyleSheetCache)
1765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        m_parsedStyleSheetCache->removedFromMemoryCache();
1775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    m_parsedStyleSheetCache = sheet;
1785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    m_parsedStyleSheetCache->addedToMemoryCache();
1795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    setDecodedSize(m_parsedStyleSheetCache->estimatedSizeInBytes());
1815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
1825c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)}
184