Ask Ben: Creating An Archive Folder / File List
I have a simple little app im trying to develop that will display end of the month reports. These reports are in excel format.
I need it to do two things..
- allow upload of new excel files
- create new archive folders for the previous month.
I can easily do 1 & 2, but my problem is getting the paths right.
the folder structure has a root folder.. say.. reports. inside that folder are the current excel (xls) files AND the archived folders (april06,may06 etc.). Inside each archived folder there are the archived .xls files for that month.
the way i started creating this little app was to add an index.cfm file to every directory, so I can link directly to the folder and it will pick up index.cfm in that folder which pretty much does the same thing like the root index.cfm. Not at all ideal, but it was working....almost..
In each index.cfm file I display all the xls files for that current directory, as well as it lists all the folders in the root directory with an href to that folder. For the inside folders, the folder link path were incorrect. they now display as http://localhost/reports/april06/april06/
so.... How can i dynamically build the folder structure so I dont have to worry about this? Also what other suggestions do you have for this? It would be nice to not have to create an index.cfm file in every folder...
Any help would be appreciated.
For this, I would just put all the navigation in the root folder. The page should start by listing out all the archive folders. Each folder link should submit back to itself and pass in the desired folder name. The page should then list out the archive documents for that folder. This way, there is only one page required to navigate the root folder AND all the sub folders. Furthermore, this does not require you to do anything when you add new archive folders.
Take a look at the page below and let me know if you have any questions:
<!--- Kill extra output. --->
<cfsilent>
<!---
This is the name of the archive folder for which we
want to list out the archive documents.
--->
<cfparam
name="URL.folder"
type="string"
default=""
/>
<!--- Decode the folder value. --->
<cfset URL.folder = UrlDecode( URL.folder ) />
<!---
For security reasons, we want to get rid of any suspect
values in the folder variable. These include "../"
which would go to a parent directory or "/" which will
go to the root perhaps.
--->
<cfset URL.folder = URL.folder.ReplaceAll(
"(\.\.[\\/])+", ""
).ReplaceAll(
"[\\/]+", ""
) />
<!---
Set the directory path of the root archive
directory (this one).
--->
<cfset strRoot = ExpandPath( "./" ) />
<!---
Check to see if we have an archive folder selected.
If we do, then we want to query that one. If not,
then we want to get the list of all the archive folders.
Furthermore, if we have an archive folder, just
check to make sure the folder exists.
--->
<cfif (
Len( URL.folder ) AND
DirectoryExists( strRoot & "\" & URL.folder )
)>
<!--- Set the query path to be the archive folder. --->
<cfset strQueryPath = (strRoot & "\" & URL.folder) />
<cfelse>
<!---
We have no archive folder selected (or it did not
exist). Query the root directory.
--->
<cfset strQueryPath = strRoot />
<!---
Reset the folder variable so we don't get confused
later on.
--->
<cfset URL.folder = "" />
</cfif>
<!---
Query for the files / folders in the selected
directory (might be root OR an archive folder). When
sorting the directory list, we want to sort by name
DESC if we are listing the archive folders, but sort
by name ASC if we are listing out the documents.
--->
<cfdirectory
action="LIST"
directory="#strQueryPath#"
recurse="false"
sort="name #IIF( Len( URL.folder ), DE( 'ASC' ), DE( 'DESC' ) )#"
name="qDirectoryList"
/>
</cfsilent>
<cfoutput>
<html>
<head>
<title>Document Archive</title>
</head>
<body>
<h1>
Document Archive
</h1>
<!---
Check to see if we have a folder value. If we do then
we are outputting the contents of that folder. If we do
not, then we are outputting the archive folder list.
--->
<cfif Len( URL.folder )>
<!--- We are listing out the archive documents. --->
<h2>
#URL.folder#
</h2>
<cfloop query="qDirectoryList">
<!---
Output the link to the file from the root
directory (since that is where we will are).
Include the name of the sub folder in the path.
--->
<p>
<a href="./#UrlEncodedFormat( URL.folder )#/#UrlEncodedFormat( qDirectoryList.name )#">#qDirectoryList.name#</a>
</p>
</cfloop>
<!--- Provide a link back to parent directory. --->
<p>
« <a href="#CGI.script_name#">Back To Archive</a>
</p>
<cfelse>
<!--- We are viewing the archive folder list. --->
<p>
Please select an archive folder to view documents:
</p>
<cfloop query="qDirectoryList">
<!---
When outputting, we only want to display
directories, not files (otherwise we might
output the index.cfm file.
--->
<cfif (qDirectoryList.Type EQ "Dir")>
<!---
For each link, submit the page back to
itself but pass in the requested folder
name.
--->
<p>
<a href="#CGI.script_name#?folder=#UrlEncodedFormat( qDirectoryList.name )#">#qDirectoryList.name#</a> »
</p>
</cfif>
</cfloop>
</cfif>
</body>
</html>
</cfoutput>
Want to use code from this post? Check out the license.
Reader Comments
Thanks Ben!
Any time dude!
Fabulous!! I just modified and used this code on one of my sites, Ben, and it works perfectly. Man, awesome stuff. Thanks!
I could be wrong about this, but as far as I can tell, your code will not prevent ".." from working. Additionally, it wouldn't prevent things like %system% (can't recall if that's the right env var) from working. This would allow for one upward directory traversal, or traversal to paths predefined in the environment variables. Not too big a deal, especially considering that the intended usage of this would be to display .xls files only, but it's good to know anyway, I think.
@Dan,
The script actually protects against the use of "..". It's a bit hard to read because it is in a regular expression which has to escape the periods:
\.\.
However, I do not know anything about things like %system%. I am not sure if that applies to ColdFusion paths! I will have to look into that. Thanks for the concern.