Find broken links in your cfml sourcecode

[Code updated on March 27, 2014, for ACF compatibility]

For a client which is migrating from a Windows environment to Linux, I needed to check if the source code contained broken links, due to case sensitivity on Linux. So I wrote this script, which goes through all your source files, and checks all links inside href="..." and src="..." to see if they exist.

Hope it helps you out as well :)

Tested on Railo 4.2.0.006. Drop me a comment if there's anything not working in ACF / Railo 4.0.

<cfset maxErrors = 500 />
<cfset root = expandPath('/') />
<cfset files = directoryList(root, true, "path", "*.cfm|*.cfc|*.html|*.htm") />
<cfset currErrors = 0 />

<cfoutput>
<p>#arraylen(files)# cfm/cfc/html/htm files found</p>
</cfoutput>
<table border="1">
<thead>
<tr>
<th>Found in file</th>
<th>Non-existent link</th>
</tr>
</thead>
<tbody>
<cfsetting enablecfoutputonly="true" />
<cfset reg = '(src|href) ?= ?[''"](.*?)[''"]' />
<cfloop array="#files#" index="filePath">
<!--- skip railo directories --->
<cfif find('/WEB-INF/', filePath)>
<cfcontinue />
</cfif>
<cfset contents = fileRead(filePath) />
<!--- remove breaks and cfml comments from page where possible (nested cfml comments won't be completely removed) --->
<cfif refindNoCase(reg, contents)>
<cfset contents = replace(replace(contents, chr(10), chr(9), 'all'), chr(13), chr(9), 'all') />
<cfset contents = rereplace(contents, '<\!\-\-\-.*?\-\-\->', '', 'all') />
</cfif>

<cfset curr = 1 />
<cfloop condition="refindNoCase(reg, contents, curr)">
<cfset found = refindNoCase(reg, contents, curr, true) />
<cfset curr = found.pos[3] />
<cfset link = mid(contents, found.pos[3], found.len[3]) />
<!--- remove query string --->
<cfif find('?', link)>
<cfset link = listToArray(link, '?')[1] />
</cfif>

<!--- js links / dynamic links / abs links / cf code inside: skip --->
<cfif findNoCase('javascript:', link) or find('##', link) or find('http://', link) eq 1
or findNoCase('<cf', link) or link eq 'about:blank'
or find('https://', link) eq 1 or find('ftp://', link) eq 1 or find('mailto:', link) eq 1>
<cfcontinue />
</cfif>

<cfif left(link, 1) neq "/">
<cfset abslink = expandPath(replace(getDirectoryFromPath(filePath), root, "/") & link) />
<cfelse>
<cfset abslink = expandPath(link) />
</cfif>
<cfif not fileExists(abslink)>
<!--- link can be a directory! --->
<cfif directoryExists(abslink)>
<cfcontinue />
</cfif>
<cfoutput>
<tr>
<td>#htmleditformat(filePath)#</td>
<td>#mid(contents, found.pos[1], found.len[1])#<!---<br/>#htmleditformat(abslink)#---></td>
</tr>
</cfoutput>
<cfflush />
<cfif ++currErrors eq maxErrors>
<cfbreak />
</cfif>
<!--- file exists, okay. But did expandPath change case of the file? --->
<cfelseif compare(listLast(link, '/\'), listLast(absLink, '/\')) neq 0>
<cfoutput>
<tr>
<td>#htmleditformat(filePath)#</td>
<td>#mid(contents, found.pos[1], found.len[1])#
&nbsp; <b>Case sensitive mismatch</b>
</td>
</tr>
</cfoutput>
<cfflush />
<cfif ++currErrors eq maxErrors>
<cfbreak />
</cfif>
</cfif>
</cfloop>
<cfif currErrors eq maxErrors>
<cfoutput>
<tr>
<td colspan="2">
<h3 style="color:red">The max number of reported errors, #maxErrors#, has been reached</h3>
</td>
</tr>
</cfoutput>
<cfbreak />
</cfif>
</cfloop>
<cfsetting enablecfoutputonly="false" />
</tbody>
</table>
<h1>Done</h1>
del.icio.us Digg StumbleUpon Facebook Technorati Fav reddit Google Bookmarks
| Viewed 8211 times
  1. Frank Semrau

    #1 by Frank Semrau - March 26, 2014 at 12:51 PM

    Hi Paul,

    found your script today via coldfusionbloggers.org, very nice. I work on a similar problem for a costumer who has an old and large codebase which we first try to clean up before we go one with any change requests. For that we use CF10 and I had to made some slightly changes to your script to got it working on our system:
    - the listInfo attribute for the directoryList function I changed to 'path'
    - I prefer for arrays <cfloop index="i" from="1" to="#arrayLen(files)#"> and then instead using 'file' change that to 'files[i]'

    Know I'm fine tuning the regEx and play with the mime types to minimize the fault results.

    Thanks for that starting point.
    Frank
  2. Paul Klinkenberg

    #2 by Paul Klinkenberg - March 26, 2014 at 5:37 PM

    Hi Frank, great to hear the code is already being used!
    Did you change the listInfo attribute because it did not work in CF10, or because of personal preference?
    Cheers, Paul
  3. Frank Semrau

    #3 by Frank Semrau - March 27, 2014 at 1:07 PM

    Hi Paul,

    I changed the listInfo attribute because it did not work on CF10. There is no 'array' value for this attribute in ColdFusion, you can choose between 'query', 'name' and 'path' (https://wikidocs.adobe.com/wiki/display/coldfusionen/DirectoryList).

    Regards,
    Frank
  4. Scott Busche

    #4 by Scott Busche - March 27, 2014 at 1:46 PM

    "File" is a reserved keyword that always points to the File struct in ACF (at least on 10), I renamed it to filePath and it worked.
  5. Paul Klinkenberg

    #5 by Paul Klinkenberg - March 27, 2014 at 2:15 PM

    Hi Scott, Frank,
    I updated the code with your proposed changes. Thanks for taking the time to let me know.
    Cheers, Paul
(will not be published)
Leave this field empty

respective-eponymous