1/*
2 * Copyright (C) 2015-2018 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "HTMLAttachmentElement.h"
28
29#if ENABLE(ATTACHMENT_ELEMENT)
30
31#include "DOMURL.h"
32#include "Document.h"
33#include "Editor.h"
34#include "File.h"
35#include "Frame.h"
36#include "HTMLImageElement.h"
37#include "HTMLNames.h"
38#include "MIMETypeRegistry.h"
39#include "RenderAttachment.h"
40#include "SharedBuffer.h"
41#include <pal/FileSizeFormatter.h>
42#include <wtf/IsoMallocInlines.h>
43#include <wtf/UUID.h>
44#include <wtf/URLParser.h>
45
46#if PLATFORM(COCOA)
47#include "UTIUtilities.h"
48#endif
49
50namespace WebCore {
51
52WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAttachmentElement);
53
54using namespace HTMLNames;
55
56HTMLAttachmentElement::HTMLAttachmentElement(const QualifiedName& tagName, Document& document)
57 : HTMLElement(tagName, document)
58{
59 ASSERT(hasTagName(attachmentTag));
60}
61
62HTMLAttachmentElement::~HTMLAttachmentElement() = default;
63
64Ref<HTMLAttachmentElement> HTMLAttachmentElement::create(const QualifiedName& tagName, Document& document)
65{
66 return adoptRef(*new HTMLAttachmentElement(tagName, document));
67}
68
69RenderPtr<RenderElement> HTMLAttachmentElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
70{
71 return createRenderer<RenderAttachment>(*this, WTFMove(style));
72}
73
74const String& HTMLAttachmentElement::getAttachmentIdentifier(HTMLImageElement& image)
75{
76 if (auto attachment = image.attachmentElement())
77 return attachment->uniqueIdentifier();
78
79 auto& document = image.document();
80 auto attachment = create(HTMLNames::attachmentTag, document);
81 auto& identifier = attachment->ensureUniqueIdentifier();
82
83 document.registerAttachmentIdentifier(identifier);
84 image.setAttachmentElement(WTFMove(attachment));
85
86 return identifier;
87}
88
89void HTMLAttachmentElement::copyNonAttributePropertiesFromElement(const Element& source)
90{
91 m_uniqueIdentifier = downcast<HTMLAttachmentElement>(source).uniqueIdentifier();
92 HTMLElement::copyNonAttributePropertiesFromElement(source);
93}
94
95URL HTMLAttachmentElement::archiveResourceURL(const String& identifier)
96{
97 auto resourceURL = URL({ }, "applewebdata://attachment/"_s);
98 resourceURL.setPath(identifier);
99 return resourceURL;
100}
101
102File* HTMLAttachmentElement::file() const
103{
104 return m_file.get();
105}
106
107URL HTMLAttachmentElement::blobURL() const
108{
109 return { { }, attributeWithoutSynchronization(HTMLNames::webkitattachmentbloburlAttr).string() };
110}
111
112void HTMLAttachmentElement::setFile(RefPtr<File>&& file, UpdateDisplayAttributes updateAttributes)
113{
114 m_file = WTFMove(file);
115
116 if (updateAttributes == UpdateDisplayAttributes::Yes) {
117 if (m_file) {
118 setAttributeWithoutSynchronization(HTMLNames::titleAttr, m_file->name());
119 setAttributeWithoutSynchronization(HTMLNames::subtitleAttr, fileSizeDescription(m_file->size()));
120 setAttributeWithoutSynchronization(HTMLNames::typeAttr, m_file->type());
121 } else {
122 removeAttribute(HTMLNames::titleAttr);
123 removeAttribute(HTMLNames::subtitleAttr);
124 removeAttribute(HTMLNames::typeAttr);
125 }
126 }
127
128 if (auto* renderer = this->renderer())
129 renderer->invalidate();
130}
131
132Node::InsertedIntoAncestorResult HTMLAttachmentElement::insertedIntoAncestor(InsertionType type, ContainerNode& ancestor)
133{
134 auto result = HTMLElement::insertedIntoAncestor(type, ancestor);
135 if (type.connectedToDocument)
136 document().didInsertAttachmentElement(*this);
137 return result;
138}
139
140void HTMLAttachmentElement::removedFromAncestor(RemovalType type, ContainerNode& ancestor)
141{
142 HTMLElement::removedFromAncestor(type, ancestor);
143 if (type.disconnectedFromDocument)
144 document().didRemoveAttachmentElement(*this);
145}
146
147const String& HTMLAttachmentElement::ensureUniqueIdentifier()
148{
149 if (m_uniqueIdentifier.isEmpty())
150 m_uniqueIdentifier = createCanonicalUUIDString();
151 return m_uniqueIdentifier;
152}
153
154bool HTMLAttachmentElement::hasEnclosingImage() const
155{
156 return is<HTMLImageElement>(shadowHost());
157}
158
159void HTMLAttachmentElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
160{
161 if (name == progressAttr || name == subtitleAttr || name == titleAttr || name == typeAttr) {
162 if (auto* renderer = this->renderer())
163 renderer->invalidate();
164 }
165
166 HTMLElement::parseAttribute(name, value);
167}
168
169String HTMLAttachmentElement::attachmentTitle() const
170{
171 auto& title = attributeWithoutSynchronization(titleAttr);
172 if (!title.isEmpty())
173 return title;
174 return m_file ? m_file->name() : String();
175}
176
177String HTMLAttachmentElement::attachmentTitleForDisplay() const
178{
179 auto title = attachmentTitle();
180
181 auto indexOfLastDot = title.reverseFind('.');
182 if (indexOfLastDot == notFound)
183 return title;
184
185 String name = title.left(indexOfLastDot);
186 String extension = title.substring(indexOfLastDot);
187
188 StringBuilder builder;
189 builder.append(leftToRightMark);
190 builder.append(firstStrongIsolate);
191 builder.append(name);
192 builder.append(popDirectionalIsolate);
193 builder.append(extension);
194
195 return builder.toString();
196}
197
198String HTMLAttachmentElement::attachmentType() const
199{
200 return attributeWithoutSynchronization(typeAttr);
201}
202
203String HTMLAttachmentElement::attachmentPath() const
204{
205 return attributeWithoutSynchronization(webkitattachmentpathAttr);
206}
207
208void HTMLAttachmentElement::updateAttributes(Optional<uint64_t>&& newFileSize, const String& newContentType, const String& newFilename)
209{
210 if (!newFilename.isNull())
211 setAttributeWithoutSynchronization(HTMLNames::titleAttr, newFilename);
212 else
213 removeAttribute(HTMLNames::titleAttr);
214
215 if (!newContentType.isNull())
216 setAttributeWithoutSynchronization(HTMLNames::typeAttr, newContentType);
217 else
218 removeAttribute(HTMLNames::typeAttr);
219
220 if (newFileSize)
221 setAttributeWithoutSynchronization(HTMLNames::subtitleAttr, fileSizeDescription(*newFileSize));
222 else
223 removeAttribute(HTMLNames::subtitleAttr);
224
225 if (auto* renderer = this->renderer())
226 renderer->invalidate();
227}
228
229static bool mimeTypeIsSuitableForInlineImageAttachment(const String& mimeType)
230{
231 return MIMETypeRegistry::isSupportedImageMIMEType(mimeType) || MIMETypeRegistry::isPDFMIMEType(mimeType);
232}
233
234void HTMLAttachmentElement::updateEnclosingImageWithData(const String& contentType, Ref<SharedBuffer>&& data)
235{
236 auto* hostElement = shadowHost();
237 if (!is<HTMLImageElement>(hostElement) || !data->size())
238 return;
239
240 String mimeType = contentType;
241#if PLATFORM(COCOA)
242 if (isDeclaredUTI(contentType))
243 mimeType = MIMETypeFromUTI(contentType);
244#endif
245
246 if (!mimeTypeIsSuitableForInlineImageAttachment(mimeType))
247 return;
248
249 hostElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, DOMURL::createObjectURL(document(), Blob::create(WTFMove(data), mimeType)));
250}
251
252} // namespace WebCore
253
254#endif // ENABLE(ATTACHMENT_ELEMENT)
255