Add username / password attributes to HTMLAnchorElement / HTMLAreaElement
[WebKit-https.git] / Source / WebCore / html / HTMLAnchorElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2000 Simon Hausmann <hausmann@kde.org>
5  * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
6  *           (C) 2006 Graham Dennis (graham.dennis@gmail.com)
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #include "config.h"
25 #include "HTMLAnchorElement.h"
26
27 #include "AttributeDOMTokenList.h"
28 #include "ElementIterator.h"
29 #include "EventHandler.h"
30 #include "EventNames.h"
31 #include "Frame.h"
32 #include "FrameLoader.h"
33 #include "FrameLoaderClient.h"
34 #include "FrameLoaderTypes.h"
35 #include "FrameSelection.h"
36 #include "HTMLCanvasElement.h"
37 #include "HTMLImageElement.h"
38 #include "HTMLParserIdioms.h"
39 #include "KeyboardEvent.h"
40 #include "MouseEvent.h"
41 #include "PingLoader.h"
42 #include "PlatformMouseEvent.h"
43 #include "RenderImage.h"
44 #include "ResourceRequest.h"
45 #include "SVGImage.h"
46 #include "SecurityOrigin.h"
47 #include "SecurityPolicy.h"
48 #include "Settings.h"
49 #include <wtf/text/StringBuilder.h>
50
51 namespace WebCore {
52
53 using namespace HTMLNames;
54
55 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
56     : HTMLElement(tagName, document)
57     , m_hasRootEditableElementForSelectionOnMouseDown(false)
58     , m_wasShiftKeyDownOnMouseDown(false)
59     , m_linkRelations(0)
60     , m_cachedVisitedLinkHash(0)
61 {
62 }
63
64 Ref<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
65 {
66     return adoptRef(*new HTMLAnchorElement(aTag, document));
67 }
68
69 Ref<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
70 {
71     return adoptRef(*new HTMLAnchorElement(tagName, document));
72 }
73
74 HTMLAnchorElement::~HTMLAnchorElement()
75 {
76     clearRootEditableElementForSelectionOnMouseDown();
77 }
78
79 // This function does not allow leading spaces before the port number.
80 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
81 {
82     portEnd = portStart;
83     while (isASCIIDigit(value[portEnd]))
84         ++portEnd;
85     return value.substring(portStart, portEnd - portStart).toUInt();
86 }
87
88 bool HTMLAnchorElement::supportsFocus() const
89 {
90     if (hasEditableStyle())
91         return HTMLElement::supportsFocus();
92     // If not a link we should still be able to focus the element if it has tabIndex.
93     return isLink() || HTMLElement::supportsFocus();
94 }
95
96 bool HTMLAnchorElement::isMouseFocusable() const
97 {
98     // Only allow links with tabIndex or contentEditable to be mouse focusable.
99     if (isLink())
100         return HTMLElement::supportsFocus();
101
102     return HTMLElement::isMouseFocusable();
103 }
104
105 static bool hasNonEmptyBox(RenderBoxModelObject* renderer)
106 {
107     if (!renderer)
108         return false;
109
110     // Before calling absoluteRects, check for the common case where borderBoundingBox
111     // is non-empty, since this is a faster check and almost always returns true.
112     // FIXME: Why do we need to call absoluteRects at all?
113     if (!renderer->borderBoundingBox().isEmpty())
114         return true;
115
116     // FIXME: Since all we are checking is whether the rects are empty, could we just
117     // pass in 0,0 for the layout point instead of calling localToAbsolute?
118     Vector<IntRect> rects;
119     renderer->absoluteRects(rects, flooredLayoutPoint(renderer->localToAbsolute()));
120     for (auto& rect : rects) {
121         if (!rect.isEmpty())
122             return true;
123     }
124
125     return false;
126 }
127
128 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
129 {
130     if (!isLink())
131         return HTMLElement::isKeyboardFocusable(event);
132
133     if (!isFocusable())
134         return false;
135     
136     if (!document().frame())
137         return false;
138
139     if (!document().frame()->eventHandler().tabsToLinks(event))
140         return false;
141
142     if (!renderer() && ancestorsOfType<HTMLCanvasElement>(*this).first())
143         return true;
144
145     return hasNonEmptyBox(renderBoxModelObject());
146 }
147
148 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
149 {
150     ASSERT(event);
151     if (!is<MouseEvent>(*event))
152         return;
153
154     ASSERT(event->target());
155     Node* target = event->target()->toNode();
156     ASSERT(target);
157     if (!is<HTMLImageElement>(*target))
158         return;
159
160     HTMLImageElement& imageElement = downcast<HTMLImageElement>(*target);
161     if (!imageElement.isServerMap())
162         return;
163
164     if (!is<RenderImage>(imageElement.renderer()))
165         return;
166     auto& renderer = downcast<RenderImage>(*imageElement.renderer());
167
168     // FIXME: This should probably pass true for useTransforms.
169     FloatPoint absolutePosition = renderer.absoluteToLocal(FloatPoint(downcast<MouseEvent>(*event).pageX(), downcast<MouseEvent>(*event).pageY()));
170     int x = absolutePosition.x();
171     int y = absolutePosition.y();
172     url.append('?');
173     url.appendNumber(x);
174     url.append(',');
175     url.appendNumber(y);
176 }
177
178 void HTMLAnchorElement::defaultEventHandler(Event* event)
179 {
180     if (isLink()) {
181         if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
182             event->setDefaultHandled();
183             dispatchSimulatedClick(event);
184             return;
185         }
186
187         if (MouseEvent::canTriggerActivationBehavior(*event) && treatLinkAsLiveForEventType(eventType(event))) {
188             handleClick(event);
189             return;
190         }
191
192         if (hasEditableStyle()) {
193             // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
194             // for the LiveWhenNotFocused editable link behavior
195             if (event->type() == eventNames().mousedownEvent && is<MouseEvent>(*event) && downcast<MouseEvent>(*event).button() != RightButton && document().frame()) {
196                 setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().selection().rootEditableElement());
197                 m_wasShiftKeyDownOnMouseDown = downcast<MouseEvent>(*event).shiftKey();
198             } else if (event->type() == eventNames().mouseoverEvent) {
199                 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
200                 // but drag events happen after mouse out events.
201                 clearRootEditableElementForSelectionOnMouseDown();
202                 m_wasShiftKeyDownOnMouseDown = false;
203             }
204         }
205     }
206
207     HTMLElement::defaultEventHandler(event);
208 }
209
210 void HTMLAnchorElement::setActive(bool down, bool pause)
211 {
212     if (hasEditableStyle()) {
213         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
214         if (Settings* settings = document().settings())
215             editableLinkBehavior = settings->editableLinkBehavior();
216             
217         switch (editableLinkBehavior) {
218             default:
219             case EditableLinkDefaultBehavior:
220             case EditableLinkAlwaysLive:
221                 break;
222
223             case EditableLinkNeverLive:
224                 return;
225
226             // Don't set the link to be active if the current selection is in the same editable block as
227             // this link
228             case EditableLinkLiveWhenNotFocused:
229                 if (down && document().frame() && document().frame()->selection().selection().rootEditableElement() == rootEditableElement())
230                     return;
231                 break;
232             
233             case EditableLinkOnlyLiveWithShiftKey:
234                 return;
235         }
236
237     }
238     
239     HTMLElement::setActive(down, pause);
240 }
241
242 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
243 {
244     if (name == hrefAttr) {
245         bool wasLink = isLink();
246         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
247         if (wasLink != isLink())
248             setNeedsStyleRecalc();
249         if (isLink()) {
250             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
251             if (document().isDNSPrefetchEnabled() && document().frame()) {
252                 if (protocolIsInHTTPFamily(parsedURL) || parsedURL.startsWith("//"))
253                     document().frame()->loader().client().prefetchDNS(document().completeURL(parsedURL).host());
254             }
255         }
256         invalidateCachedVisitedLinkHash();
257     } else if (name == nameAttr || name == titleAttr) {
258         // Do nothing.
259     } else if (name == relAttr) {
260         if (SpaceSplitString::spaceSplitStringContainsValue(value, "noreferrer", true))
261             m_linkRelations |= RelationNoReferrer;
262         if (m_relList)
263             m_relList->attributeValueChanged(value);
264     }
265     else
266         HTMLElement::parseAttribute(name, value);
267 }
268
269 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
270 {
271     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
272 }
273
274 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
275 {
276     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
277 }
278
279 bool HTMLAnchorElement::canStartSelection() const
280 {
281     if (!isLink())
282         return HTMLElement::canStartSelection();
283     return hasEditableStyle();
284 }
285
286 bool HTMLAnchorElement::draggable() const
287 {
288     const AtomicString& value = fastGetAttribute(draggableAttr);
289     if (equalLettersIgnoringASCIICase(value, "true"))
290         return true;
291     if (equalLettersIgnoringASCIICase(value, "false"))
292         return false;
293     return hasAttribute(hrefAttr);
294 }
295
296 URL HTMLAnchorElement::href() const
297 {
298     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
299 }
300
301 void HTMLAnchorElement::setHref(const AtomicString& value)
302 {
303     setAttribute(hrefAttr, value);
304 }
305
306 bool HTMLAnchorElement::hasRel(uint32_t relation) const
307 {
308     return m_linkRelations & relation;
309 }
310
311 DOMTokenList& HTMLAnchorElement::relList()
312 {
313     if (!m_relList) 
314         m_relList = std::make_unique<AttributeDOMTokenList>(*this, HTMLNames::relAttr);
315     return *m_relList;
316 }
317
318 const AtomicString& HTMLAnchorElement::name() const
319 {
320     return getNameAttribute();
321 }
322
323 short HTMLAnchorElement::tabIndex() const
324 {
325     // Skip the supportsFocus check in HTMLElement.
326     return Element::tabIndex();
327 }
328
329 String HTMLAnchorElement::target() const
330 {
331     return getAttribute(targetAttr);
332 }
333
334 String HTMLAnchorElement::hash() const
335 {
336     String fragmentIdentifier = href().fragmentIdentifier();
337     if (fragmentIdentifier.isEmpty())
338         return emptyString();
339     return AtomicString(String("#" + fragmentIdentifier));
340 }
341
342 void HTMLAnchorElement::setHash(const String& value)
343 {
344     URL url = href();
345     if (value[0] == '#')
346         url.setFragmentIdentifier(value.substring(1));
347     else
348         url.setFragmentIdentifier(value);
349     setHref(url.string());
350 }
351
352 String HTMLAnchorElement::host() const
353 {
354     const URL& url = href();
355     if (url.hostEnd() == url.pathStart())
356         return url.host();
357     if (isDefaultPortForProtocol(url.port(), url.protocol()))
358         return url.host();
359     return url.host() + ":" + String::number(url.port());
360 }
361
362 void HTMLAnchorElement::setHost(const String& value)
363 {
364     if (value.isEmpty())
365         return;
366     URL url = href();
367     if (!url.canSetHostOrPort())
368         return;
369
370     size_t separator = value.find(':');
371     if (!separator)
372         return;
373
374     if (separator == notFound)
375         url.setHostAndPort(value);
376     else {
377         unsigned portEnd;
378         unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
379         if (!port) {
380             // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
381             // specifically goes against RFC 3986 (p3.2) and
382             // requires setting the port to "0" if it is set to empty string.
383             url.setHostAndPort(value.substring(0, separator + 1) + "0");
384         } else {
385             if (isDefaultPortForProtocol(port, url.protocol()))
386                 url.setHostAndPort(value.substring(0, separator));
387             else
388                 url.setHostAndPort(value.substring(0, portEnd));
389         }
390     }
391     setHref(url.string());
392 }
393
394 String HTMLAnchorElement::username() const
395 {
396     return href().encodedUser();
397 }
398
399 void HTMLAnchorElement::setUsername(const String& value)
400 {
401     URL url = href();
402     url.setUser(value);
403     setHref(url.string());
404 }
405
406 String HTMLAnchorElement::password() const
407 {
408     return href().encodedPass();
409 }
410
411 void HTMLAnchorElement::setPassword(const String& value)
412 {
413     URL url = href();
414     url.setPass(value);
415     setHref(url.string());
416 }
417
418 String HTMLAnchorElement::hostname() const
419 {
420     return href().host();
421 }
422
423 void HTMLAnchorElement::setHostname(const String& value)
424 {
425     // Before setting new value:
426     // Remove all leading U+002F SOLIDUS ("/") characters.
427     unsigned i = 0;
428     unsigned hostLength = value.length();
429     while (value[i] == '/')
430         i++;
431
432     if (i == hostLength)
433         return;
434
435     URL url = href();
436     if (!url.canSetHostOrPort())
437         return;
438
439     url.setHost(value.substring(i));
440     setHref(url.string());
441 }
442
443 String HTMLAnchorElement::pathname() const
444 {
445     return href().path();
446 }
447
448 void HTMLAnchorElement::setPathname(const String& value)
449 {
450     URL url = href();
451     if (!url.canSetPathname())
452         return;
453
454     if (value[0] == '/')
455         url.setPath(value);
456     else
457         url.setPath("/" + value);
458
459     setHref(url.string());
460 }
461
462 String HTMLAnchorElement::port() const
463 {
464     if (href().hasPort())
465         return String::number(href().port());
466
467     return emptyString();
468 }
469
470 void HTMLAnchorElement::setPort(const String& value)
471 {
472     URL url = href();
473     if (!url.canSetHostOrPort())
474         return;
475
476     // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
477     // specifically goes against RFC 3986 (p3.2) and
478     // requires setting the port to "0" if it is set to empty string.
479     unsigned port = value.toUInt();
480     if (isDefaultPortForProtocol(port, url.protocol()))
481         url.removePort();
482     else
483         url.setPort(port);
484
485     setHref(url.string());
486 }
487
488 String HTMLAnchorElement::protocol() const
489 {
490     return href().protocol() + ":";
491 }
492
493 void HTMLAnchorElement::setProtocol(const String& value)
494 {
495     URL url = href();
496     url.setProtocol(value);
497     setHref(url.string());
498 }
499
500 String HTMLAnchorElement::search() const
501 {
502     String query = href().query();
503     return query.isEmpty() ? emptyString() : "?" + query;
504 }
505
506 String HTMLAnchorElement::origin() const
507 {
508     return SecurityOrigin::create(href()).get().toString();
509 }
510
511 void HTMLAnchorElement::setSearch(const String& value)
512 {
513     URL url = href();
514     String newSearch = (value[0] == '?') ? value.substring(1) : value;
515     // Make sure that '#' in the query does not leak to the hash.
516     url.setQuery(newSearch.replaceWithLiteral('#', "%23"));
517
518     setHref(url.string());
519 }
520
521 String HTMLAnchorElement::text()
522 {
523     return textContent();
524 }
525
526 void HTMLAnchorElement::setText(const String& text, ExceptionCode& ec)
527 {
528     setTextContent(text, ec);
529 }
530
531 String HTMLAnchorElement::toString() const
532 {
533     return href().string();
534 }
535
536 bool HTMLAnchorElement::isLiveLink() const
537 {
538     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
539 }
540
541 void HTMLAnchorElement::sendPings(const URL& destinationURL)
542 {
543     if (!fastHasAttribute(pingAttr) || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
544         return;
545
546     SpaceSplitString pingURLs(fastGetAttribute(pingAttr), false);
547     for (unsigned i = 0; i < pingURLs.size(); i++)
548         PingLoader::sendPing(*document().frame(), document().completeURL(pingURLs[i]), destinationURL);
549 }
550
551 void HTMLAnchorElement::handleClick(Event* event)
552 {
553     event->setDefaultHandled();
554
555     Frame* frame = document().frame();
556     if (!frame)
557         return;
558
559     StringBuilder url;
560     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
561     appendServerMapMousePosition(url, event);
562     URL kurl = document().completeURL(url.toString());
563
564 #if ENABLE(DOWNLOAD_ATTRIBUTE)
565     if (hasAttribute(downloadAttr)) {
566         ResourceRequest request(kurl);
567
568         // FIXME: Why are we not calling addExtraFieldsToMainResourceRequest() if this check fails? It sets many important header fields.
569         if (!hasRel(RelationNoReferrer)) {
570             String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), kurl, frame->loader().outgoingReferrer());
571             if (!referrer.isEmpty())
572                 request.setHTTPReferrer(referrer);
573             frame->loader().addExtraFieldsToMainResourceRequest(request);
574         }
575
576         frame->loader().client().startDownload(request, fastGetAttribute(downloadAttr));
577     } else
578 #endif
579
580     frame->loader().urlSelected(kurl, target(), event, LockHistory::No, LockBackForwardList::No, hasRel(RelationNoReferrer) ? NeverSendReferrer : MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate());
581
582     sendPings(kurl);
583 }
584
585 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
586 {
587     ASSERT(event);
588     if (!is<MouseEvent>(*event))
589         return NonMouseEvent;
590     return downcast<MouseEvent>(*event).shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
591 }
592
593 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
594 {
595     if (!hasEditableStyle())
596         return true;
597
598     Settings* settings = document().settings();
599     if (!settings)
600         return true;
601
602     switch (settings->editableLinkBehavior()) {
603     case EditableLinkDefaultBehavior:
604     case EditableLinkAlwaysLive:
605         return true;
606
607     case EditableLinkNeverLive:
608         return false;
609
610     // If the selection prior to clicking on this link resided in the same editable block as this link,
611     // and the shift key isn't pressed, we don't want to follow the link.
612     case EditableLinkLiveWhenNotFocused:
613         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
614
615     case EditableLinkOnlyLiveWithShiftKey:
616         return eventType == MouseEventWithShiftKey;
617     }
618
619     ASSERT_NOT_REACHED();
620     return false;
621 }
622
623 bool isEnterKeyKeydownEvent(Event* event)
624 {
625     return event->type() == eventNames().keydownEvent && is<KeyboardEvent>(*event) && downcast<KeyboardEvent>(*event).keyIdentifier() == "Enter";
626 }
627
628 bool shouldProhibitLinks(Element* element)
629 {
630     return isInSVGImage(element);
631 }
632
633 bool HTMLAnchorElement::willRespondToMouseClickEvents()
634 {
635     return isLink() || HTMLElement::willRespondToMouseClickEvents();
636 }
637
638 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element>> RootEditableElementMap;
639
640 static RootEditableElementMap& rootEditableElementMap()
641 {
642     static NeverDestroyed<RootEditableElementMap> map;
643     return map;
644 }
645
646 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
647 {
648     if (!m_hasRootEditableElementForSelectionOnMouseDown)
649         return 0;
650     return rootEditableElementMap().get(this);
651 }
652
653 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
654 {
655     if (!m_hasRootEditableElementForSelectionOnMouseDown)
656         return;
657     rootEditableElementMap().remove(this);
658     m_hasRootEditableElementForSelectionOnMouseDown = false;
659 }
660
661 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
662 {
663     if (!element) {
664         clearRootEditableElementForSelectionOnMouseDown();
665         return;
666     }
667
668     rootEditableElementMap().set(this, element);
669     m_hasRootEditableElementForSelectionOnMouseDown = true;
670 }
671
672 }