1414 */
1515package org .htmlunit .html ;
1616
17- import static org .htmlunit .BrowserVersionFeatures .ANCHOR_SEND_PING_REQUEST ;
18- import static org .htmlunit .BrowserVersionFeatures .FORM_SUBMISSION_HEADER_CACHE_CONTROL_MAX_AGE ;
19-
2017import java .io .IOException ;
2118import java .net .MalformedURLException ;
2219import java .net .URL ;
23- import java .util .Locale ;
2420import java .util .Map ;
2521
26- import org .apache .commons .logging .Log ;
27- import org .apache .commons .logging .LogFactory ;
28- import org .htmlunit .BrowserVersion ;
29- import org .htmlunit .FormEncodingType ;
30- import org .htmlunit .HttpHeader ;
31- import org .htmlunit .HttpMethod ;
3222import org .htmlunit .Page ;
3323import org .htmlunit .SgmlPage ;
34- import org .htmlunit .WebClient ;
35- import org .htmlunit .WebRequest ;
3624import org .htmlunit .WebWindow ;
3725import org .htmlunit .javascript .host .event .Event ;
3826import org .htmlunit .javascript .host .html .HTMLElement ;
39- import org .htmlunit .protocol .javascript .JavaScriptURLConnection ;
40- import org .htmlunit .util .ArrayUtils ;
41- import org .htmlunit .util .StringUtils ;
42- import org .htmlunit .util .UrlUtils ;
4327
4428/**
4529 * Wrapper for the HTML element "a".
5337 * @author Frank Danek
5438 * @author Lai Quang Duong
5539 */
56- public class HtmlAnchor extends HtmlElement {
57-
58- private static final Log LOG = LogFactory .getLog (HtmlAnchor .class );
40+ public class HtmlAnchor extends HtmlElement implements HyperlinkElement {
5941
6042 /** The HTML tag represented by this element. */
6143 public static final String TAG_NAME = "a" ;
@@ -108,145 +90,7 @@ public <P extends Page> P click(final Event event,
10890 */
10991 protected void doClickStateUpdate (final boolean shiftKey , final boolean ctrlKey , final String hrefSuffix )
11092 throws IOException {
111- final String href = (getHrefAttribute () + hrefSuffix ).trim ();
112- if (LOG .isDebugEnabled ()) {
113- final String w = getPage ().getEnclosingWindow ().getName ();
114- LOG .debug ("do click action in window '" + w + "', using href '" + href + "'" );
115- }
116- if (ATTRIBUTE_NOT_DEFINED == getHrefAttribute ()) {
117- return ;
118- }
119- final String downloadAttribute = getDownloadAttribute ();
120- HtmlPage page = (HtmlPage ) getPage ();
121- if (StringUtils .startsWithIgnoreCase (href , JavaScriptURLConnection .JAVASCRIPT_PREFIX )) {
122- final StringBuilder builder = new StringBuilder (href .length ());
123- builder .append (JavaScriptURLConnection .JAVASCRIPT_PREFIX );
124- for (int i = JavaScriptURLConnection .JAVASCRIPT_PREFIX .length (); i < href .length (); i ++) {
125- final char ch = href .charAt (i );
126- if (ch == '%' && i + 2 < href .length ()) {
127- final char ch1 = Character .toUpperCase (href .charAt (i + 1 ));
128- final char ch2 = Character .toUpperCase (href .charAt (i + 2 ));
129- if ((Character .isDigit (ch1 ) || ch1 >= 'A' && ch1 <= 'F' )
130- && (Character .isDigit (ch2 ) || ch2 >= 'A' && ch2 <= 'F' )) {
131- builder .append ((char ) Integer .parseInt (href .substring (i + 1 , i + 3 ), 16 ));
132- i += 2 ;
133- continue ;
134- }
135- }
136- builder .append (ch );
137- }
138-
139- final String target ;
140- if (shiftKey || ctrlKey || ATTRIBUTE_NOT_DEFINED != downloadAttribute ) {
141- target = WebClient .TARGET_BLANK ;
142- }
143- else {
144- target = page .getResolvedTarget (getTargetAttribute ());
145- }
146- final WebWindow win = page .getWebClient ().openTargetWindow (page .getEnclosingWindow (),
147- target , WebClient .TARGET_SELF );
148- Page enclosedPage = win .getEnclosedPage ();
149- if (enclosedPage == null ) {
150- win .getWebClient ().getPage (win , WebRequest .newAboutBlankRequest ());
151- enclosedPage = win .getEnclosedPage ();
152- }
153- if (enclosedPage != null && enclosedPage .isHtmlPage ()) {
154- page = (HtmlPage ) enclosedPage ;
155- page .executeJavaScript (builder .toString (), "javascript url" , getStartLineNumber ());
156- }
157- return ;
158- }
159-
160- final URL url = getTargetUrl (href , page );
161-
162- final URL pageUrl = page .getUrl ();
163-
164- final WebClient webClient = page .getWebClient ();
165- final BrowserVersion browser = webClient .getBrowserVersion ();
166- if (ATTRIBUTE_NOT_DEFINED != getPingAttribute () && browser .hasFeature (ANCHOR_SEND_PING_REQUEST )) {
167- final URL pingUrl = getTargetUrl (getPingAttribute (), page );
168- final WebRequest pingRequest = new WebRequest (pingUrl , HttpMethod .POST );
169- pingRequest .setAdditionalHeader (HttpHeader .ACCEPT_ENCODING , browser .getAcceptEncodingHeader ());
170- pingRequest .setAdditionalHeader (HttpHeader .PING_FROM , pageUrl .toExternalForm ());
171- pingRequest .setAdditionalHeader (HttpHeader .PING_TO , url .toExternalForm ());
172- pingRequest .setEncodingType (FormEncodingType .TEXT_PLAIN ); // text/ping
173- try {
174- pingRequest .setAdditionalHeader (HttpHeader .ORIGIN ,
175- UrlUtils .getUrlWithProtocolAndAuthority (pageUrl ).toExternalForm ());
176- }
177- catch (final MalformedURLException e ) {
178- if (LOG .isInfoEnabled ()) {
179- LOG .info ("Invalid origin url '" + pageUrl + "'" );
180- }
181- }
182- if (browser .hasFeature (FORM_SUBMISSION_HEADER_CACHE_CONTROL_MAX_AGE )) {
183- pingRequest .setAdditionalHeader (HttpHeader .CACHE_CONTROL , "max-age=0" );
184- }
185- pingRequest .setRequestBody ("PING" );
186-
187- // Sec-Fetch-* support (https://www.w3.org/TR/fetch-metadata/):
188- // hyperlink-auditing (ping) requests have no destination and are always no-cors
189- pingRequest .setFetchDestination (WebRequest .FetchDestination .EMPTY );
190- pingRequest .setFetchModeOverride (WebRequest .FetchMode .NO_CORS );
191- pingRequest .setRequestingUrl (pageUrl );
192-
193- try {
194- webClient .loadWebResponse (pingRequest );
195- }
196- catch (final Exception e ) {
197- // ignore
198- }
199- }
200-
201- final WebRequest webRequest = new WebRequest (url , browser .getHtmlAcceptHeader (),
202- browser .getAcceptEncodingHeader ());
203- // use the page encoding even if this is a GET requests
204- webRequest .setCharset (page .getCharset ());
205-
206- // Sec-Fetch-* support (https://www.w3.org/TR/fetch-metadata/):
207- // this is a top-level navigation, initiated by the page containing the link.
208- // The initiator must be set regardless of "noreferrer", since Sec-Fetch-Site
209- // reflects the true relationship between initiator and target even when the
210- // Referer header itself is suppressed.
211- // TODO: userActivation is hardcoded to true here because doClickStateUpdate()
212- // currently has no way to tell a real (WebDriver-simulated) click apart from a
213- // script-triggered one (e.g. anchor.click()); real browsers only send
214- // Sec-Fetch-User for the former. Once the click/event dispatch path exposes a
215- // trusted/user-gesture flag, thread it through instead of hardcoding this.
216- webRequest .markAsNavigation (page .getUrl (), true );
217-
218- if (!relContainsNoreferrer ()) {
219- webRequest .setRefererHeader (page .getUrl ());
220- }
221-
222- if (LOG .isDebugEnabled ()) {
223- LOG .debug (
224- "Getting page for " + url .toExternalForm ()
225- + ", derived from href '" + href
226- + "', using the originating URL "
227- + page .getUrl ());
228- }
229-
230- final String target ;
231- if (shiftKey || ctrlKey
232- || (webClient .getAttachmentHandler () == null
233- && ATTRIBUTE_NOT_DEFINED != downloadAttribute )) {
234- target = WebClient .TARGET_BLANK ;
235- }
236- else {
237- target = page .getResolvedTarget (getTargetAttribute ());
238- }
239- page .getWebClient ().download (page .getEnclosingWindow (), target , webRequest ,
240- true , (ATTRIBUTE_NOT_DEFINED == downloadAttribute ) ? null : downloadAttribute , "Link click" );
241- }
242-
243- private boolean relContainsNoreferrer () {
244- String rel = getRelAttribute ();
245- if (rel != null ) {
246- rel = rel .toLowerCase (Locale .ROOT );
247- return ArrayUtils .contains (StringUtils .splitAtBlank (rel ), "noreferrer" );
248- }
249- return false ;
93+ HyperlinkElementSupport .doClickStateUpdate (this , shiftKey , ctrlKey , hrefSuffix );
25094 }
25195
25296 /**
@@ -258,12 +102,7 @@ private boolean relContainsNoreferrer() {
258102 * @throws MalformedURLException if an IO error occurs
259103 */
260104 public static URL getTargetUrl (final String href , final HtmlPage page ) throws MalformedURLException {
261- URL url = page .getFullyQualifiedUrl (href );
262- // fix for empty url
263- if (StringUtils .isEmptyOrNull (href )) {
264- url = UrlUtils .getUrlWithNewRef (url , null );
265- }
266- return url ;
105+ return HyperlinkElementSupport .getTargetUrl (href , page );
267106 }
268107
269108 /**
@@ -315,6 +154,7 @@ public final String getNameAttribute() {
315154 *
316155 * @return the value of the attribute {@code href} or an empty string if that attribute isn't defined
317156 */
157+ @ Override
318158 public final String getHrefAttribute () {
319159 return getAttributeDirect ("href" ).trim ();
320160 }
@@ -337,6 +177,7 @@ public final String getHrefLangAttribute() {
337177 *
338178 * @return the value of the attribute {@code rel} or an empty string if that attribute isn't defined
339179 */
180+ @ Override
340181 public final String getRelAttribute () {
341182 return getAttributeDirect ("rel" );
342183 }
@@ -425,6 +266,7 @@ public final String getOnBlurAttribute() {
425266 *
426267 * @return the value of the attribute {@code target} or an empty string if that attribute isn't defined
427268 */
269+ @ Override
428270 public final String getTargetAttribute () {
429271 return getAttributeDirect ("target" );
430272 }
@@ -477,6 +319,7 @@ public boolean handles(final Event event) {
477319 *
478320 * @return the value of the attribute {@code ping}
479321 */
322+ @ Override
480323 public final String getPingAttribute () {
481324 return getAttributeDirect ("ping" );
482325 }
@@ -486,6 +329,7 @@ public final String getPingAttribute() {
486329 *
487330 * @return the value of the attribute {@code download}
488331 */
332+ @ Override
489333 public final String getDownloadAttribute () {
490334 return getAttributeDirect ("download" );
491335 }
0 commit comments