Problem/Motivation

When content editors create links in WYSIWYG fields with an invalid value in the
href attribute, the HTML extractor in Linkchecker
picks up this value as a URL candidate. The value passes HTML extraction but fails URI
validation when $link->save() is called, because the url field
is of type uri and Drupal's Uri class throws an exception for
invalid hosts containing spaces or special characters.

This causes drush linkchecker:check to crash with:

In Uri.php line 76:
Invalid host: "https://<WRONG HREF VALUE>"

The entire check process is aborted, preventing all subsequent links from being
checked.

Steps to reproduce

  1. In a WYSIWYG text field, create a link where the href attribute
    contains an invalid value such as a string "Lorem ipsum".
  2. Run drush linkchecker:analyze – this succeeds and stores the
    invalid URL.
  3. Run drush linkchecker:check – this crashes with an
    InvalidArgumentException from Uri.php.

Proposed resolution

Wrap $link->save() in LinkExtractorService::saveLink()
with a try/catch(\Throwable) block. If saving fails due to an invalid URI,
log a warning message containing the URL, entity type, entity ID and field name so
administrators can locate and fix the invalid content. The invalid link is then skipped
and the check process continues normally.

Remaining tasks

  • Review and testing
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

andrerb created an issue. See original summary.

andrerb changed the visibility of the branch 3604202-invalid-href-attribute to hidden.

andrerb’s picture

Issue summary: View changes
andrerb’s picture

Status: Active » Needs review
nitinkumar_7’s picture

I have reviwed MR The fix looks good, need to improve the testInvalidUrl() test to be more explicit and precise, update the test to add a stronger assertion that specifically verifies the invalid URL was skipped

Core fix : injects LoggerInterface into LinkExtractorService, adds try/catch(\Throwable) around $link->save() in saveLink(), and registers the logger dependency in linkchecker.services.yml

Regression test : adds testInvalidUrl() to LinkcheckerLinkExtractorServiceTest with explicit assertions that valid URLs are saved and the malformed URL is skipped without crashing

andrerb’s picture

If you are also applying the patch from https://www.drupal.org/project/linkchecker/issues/3366753, use this alternative patch instead: 3604202-invalid-href-attribute-in-wysiwyg-with-3366753.patch

andrerb’s picture

Issue summary: View changes
andrerb’s picture

Assigned: andrerb » Unassigned
codebymikey’s picture

Assigned: Unassigned » codebymikey
Status: Needs review » Needs work
Related issues: +#3607990: Trim whitespace from link URLs to prevent MalformedUriException halting link checks

Attached a related issue that was recently committed, but doesn't address when the space is within the host.

I also think the code can be simplified a bit more by just using the UrlHelper or the Uri class to check if the URL is valid rather than using our own logic.

There's also the linkchecker_watchdog_log() method which should avoid making changes to the services and allow this patch to easily apply against others.

I'll pick these changes up.

codebymikey’s picture

Assigned: codebymikey » Unassigned
Status: Needs work » Needs review

Simplified the changes and added a simple test case.

nitinkumar_7’s picture

Assigned: Unassigned » nitinkumar_7
codebymikey’s picture

Attached a simplified version of the patch that works with less conflicts against other patches.

In particular the Utils import used in #3616064: Provide a way to check specific links immediately

ericgsmith’s picture

Status: Needs review » Reviewed & tested by the community

Also experiencing this after semi recent guzzle changes. It is a huge inconvenience that the checker cron stops completely.

I have not spent much time looking through this module, however the fix looks simple enough and includes tests. Have applied and tested on a fresh install.

Created a node with the body field:

<p>
    This is a <a href="https://this is a test">link</a>
</p>

Observed the link was visible as unchecked after saving. Attempted to run drush linkchecker:check which failed

$drush linkchecker:check
> 
> In Uri.php line 76:
>                                   
>   Invalid host: "this is a test"  
>                                   
> 
> In Uri.php line 466:
>                                   
>   Invalid host: "this is a test"  
>                                   
> 

In ProcessBase.php line 155:
                    
  Output is empty.  

Applied patch and now the check completes and I can see the link in the link report correctly showing as "Invalid host: "this is a test" " in the error column.

I did wonder if we wanted to future proof a bit more for expanding which exception is caught? Given we just call getMessage on the exception, should we catch any Throwable to make this a bit more robust?

Aside from that though - I think this is an important fix and good for me. Setting to RTBC but may be worth thinking about the above.

codebymikey’s picture

Given we just call getMessage on the exception, should we catch any Throwable to make this a bit more robust?

I think the rest of the code is simple enough that it shouldn't need the generic catch. The only other exceptions that need to be caught would be within the request itself, which is being handled in #3469263: Links never checked.

ericgsmith’s picture

Sounds good to me - thanks @codebymikey