1 /*
2 ** ($Header: /var/www/cvsroot/DFileServer/src/DirectoryIndexing.cxx,v 1.41.2.6 2005/10/04 07:45:52 incubus Exp $)
3 **
4 ** Copyright 2005 Chris Laverdure
5 ** All rights reserved.
6 **
7 ** Redistribution and use in source and binary forms, with or without
8 ** modification, are permitted provided that the following conditions
9 ** are met:
10 **
11 ** 1. Redistributions of source code must retain the above copyright
12 **    notice, this list of conditions and the following disclaimer.
13 ** 2. Redistributions in binary form must reproduce the above copyright
14 **    notice, this list of conditions and the following disclaimer in the
15 **    documentation and/or other materials provided with the distribution.
16 ** 3. The name of the author may not be used to endorse or promote products
17 **    derived from this software without specific prior written permission.
18 **
19 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #ifdef _WINDOWS
37 #include <windows.h>
38 #include "contrib/win32dirent.h"
39 #else
40 #include <unistd.h>
41 #include <sys/types.h>
42 #include <sys/param.h>
43 #include <dirent.h>
44 #endif
45 #include <string>
46 #include <fstream>
47 #include <vector>
48 #include <set>
49 // for sort()
50 #include <algorithm>
51 
52 #include "Version.hxx"
53 
54 using namespace std;
55 
56 struct DirectoryEntryStruct
57 {
58 	bool Folder;
59 	string Name;
60 	string CompletePath;
61 	int Size;
62 };
63 
64 void URLEncode( string & );
65 void ParseURLEncoding( char * );
66 
operator <(const DirectoryEntryStruct & FirstElement,const DirectoryEntryStruct & SecondElement)67 bool operator< ( const DirectoryEntryStruct &FirstElement, const DirectoryEntryStruct &SecondElement )
68 {
69 	// ../ always wins so it stays at the top.
70 	if ( FirstElement.Name == ".." )
71 		return true;
72 	else if ( SecondElement.Name == ".." )
73 		return false;
74 
75 	// First check to see if it is a folder.
76 	if ( FirstElement.Folder != SecondElement.Folder )
77 		return FirstElement.Folder > SecondElement.Folder;
78 
79 	// Do a lexical comparison.
80 	return FirstElement.Name < SecondElement.Name;
81 }
82 
FileSize(const char * ArgFileName)83 static int FileSize ( const char *ArgFileName )
84 {
85 	ifstream		FilePointer;
86 	ifstream::pos_type 	FileSize;
87 
88 	// Try to open the file.
89 	FilePointer.open( ArgFileName, ios_base::binary | ios_base::in );
90 
91 	// If something went wrong..
92 	if ( !FilePointer.good() || !FilePointer.is_open() )
93 	{
94 		FilePointer.close();
95 
96 		return -1;
97 	}
98 
99 	// Determine the size of the file.
100 
101 	FilePointer.seekg(0, ios_base::end);
102 	FileSize = FilePointer.tellg();
103 	FilePointer.seekg(0, ios_base::beg);
104 	FileSize -= FilePointer.tellg();
105 
106 	FilePointer.close();
107 
108 	return static_cast<int>(FileSize);
109 }
110 
ConvertSizeToFriendly(int ArgFileSize)111 static char *ConvertSizeToFriendly ( int ArgFileSize )
112 {
113 	static char FriendlyFileSize[50];
114 
115 	if ( ArgFileSize == -1 )
116 	{
117 		snprintf(FriendlyFileSize, sizeof(FriendlyFileSize), "Cannot Access");
118 		return (char *) FriendlyFileSize;
119 	}
120 
121 	// Try to break it down into friendier bits.
122 	if ( ArgFileSize > 1073741824) // Gigabytes
123 		snprintf(FriendlyFileSize, sizeof(FriendlyFileSize), "%iGB", ArgFileSize / 1073741824);
124 	else if ( ArgFileSize > 1048576) // Megabytes
125 		snprintf(FriendlyFileSize, sizeof(FriendlyFileSize), "%iMB", ArgFileSize / 1048576);
126 	else if ( ArgFileSize > 1024) // Kilobytes
127 		snprintf(FriendlyFileSize, sizeof(FriendlyFileSize), "%iKB", ArgFileSize / 1024);
128 	else
129 		snprintf(FriendlyFileSize, sizeof(FriendlyFileSize), "< 1KB");
130 
131 	// Return with the size in bytes.
132 	return (char *) FriendlyFileSize;
133 }
134 
FullPath(string ArgPath,const char * ArgFile)135 static string FullPath ( string ArgPath, const char *ArgFile )
136 {
137 	string Buffer;
138 
139 	// First copy the path into the buffer.
140 	Buffer = ArgPath;
141 
142 	// Does the path end with a slash?
143 	if ( Buffer[Buffer.size() - 1] != '/' )
144 		Buffer += "/";
145 
146 	// Now tag on the filename at the end.
147 	Buffer += string(ArgFile);
148 
149 	return Buffer;
150 }
151 
IsAFolder(string & ArgPath)152 static bool IsAFolder( string &ArgPath )
153 {
154 	DIR *DirectoryPointer = NULL;
155 
156 	DirectoryPointer = opendir( (char *) ArgPath.c_str() );
157 
158 	if (!DirectoryPointer)
159 		return false; // It's not a folder, or doesn't exist.
160 
161 	closedir( DirectoryPointer );
162 
163 	return true; // It is a folder.
164 }
165 
InsertFile(const char * ArgPath,const char * ArgFile)166 static string InsertFile ( const char *ArgPath, const char *ArgFile )
167 {
168 	string FileName;
169 	string FileData;
170 	ifstream                FilePointer;
171 	ifstream::pos_type      FileSize;
172 	char *TempSpace;
173 
174 	// Get the full file path.
175 	FileName = FullPath ( ArgPath, ArgFile );
176 
177 	// Try to open the file.
178 	FilePointer.open( FileName.c_str(), ios_base::binary | ios_base::in );
179 
180 	// If something went wrong..
181 	if ( !FilePointer.good() || !FilePointer.is_open() )
182 	{
183 		FilePointer.close();
184 
185 		return FileData;
186 	}
187 
188 	// Determine the size of the file.
189 
190 	FilePointer.seekg(0, ios_base::end);
191 	FileSize = FilePointer.tellg();
192 	FilePointer.seekg(0, ios_base::beg);
193 	FileSize -= FilePointer.tellg();
194 
195 	TempSpace = (char *) malloc( FileSize );
196 	TempSpace[FileSize] = '\0';
197 
198 	// Read all the information from the file into this char.
199         FilePointer.read ( TempSpace, FileSize );
200 	FilePointer.close();
201 
202         FileData = TempSpace;
203 
204         free ( TempSpace );
205 
206 	return FileData;
207 }
208 
InsertIndexTable(DIR * DirectoryPointer,char * ArgVirtualPath,string ArgPath,set<string> & ArgHidden)209 static string InsertIndexTable ( DIR *DirectoryPointer, char *ArgVirtualPath, string ArgPath, set<string> &ArgHidden )
210 {
211         struct dirent *DirentPointer = NULL;
212         bool AlternatingVariable = false;
213 	string Buffer;
214         vector<DirectoryEntryStruct> DirectoryVector;
215 
216 	// Top Table row.
217 	Buffer += "<table id=\"DFS_table\">\n<tr class=\"DFS_headertablerow\"><th class=\"DFS_entrytype\">Entry Type</th><th class=\"DFS_entryname\">Entry Name</th><th class=\"DFS_entrysize\">Entry Size</th></tr>\n";
218 
219 	// Walk through the folder grabbing files and adding them to our vector.
220 	while ( ( DirentPointer = readdir( DirectoryPointer ) ) != NULL )
221 	{
222 		string CompleteVirtualPath;
223 		string CompleteRealPath;
224 		DirectoryEntryStruct DirectoryEntry;
225 
226 		// In UNIX, hidden files start with periods. This is a good policy.
227 		if ( DirentPointer->d_name[0] == '.')
228 		{
229 			// We must of course allow for .., so make a special case.
230 			if ( !(strlen( DirentPointer->d_name ) == 2
231 					&& DirentPointer->d_name[1] == '.') )
232 				continue; // Don't display it.
233 		}
234 
235 		DirectoryEntry.Name = string( DirentPointer->d_name );
236 
237 		// Check through the ArgHidden vector for files that shouldn't be displayed.
238 		if ( ArgHidden.find( DirectoryEntry.Name ) != ArgHidden.end() )
239 			continue;
240 
241 		// Make sure the path has no URL encodings in it so they don't get URL encoded twice.
242 		ParseURLEncoding ( ArgVirtualPath );
243 
244 		DirectoryEntry.CompletePath = FullPath( ArgVirtualPath, DirentPointer->d_name );
245 
246 		// URL encode the link
247 		URLEncode (DirectoryEntry.CompletePath);
248 
249 		CompleteRealPath = FullPath( ArgPath, DirentPointer->d_name);
250 
251 		DirectoryEntry.Folder = IsAFolder ( CompleteRealPath );
252 
253 		// If it's not a folder, grab the size.
254 		if ( !DirectoryEntry.Folder )
255 			DirectoryEntry.Size = FileSize ( CompleteRealPath.c_str() );
256 
257 		// Is the file accessable?
258 		if ( DirectoryEntry.Size == -1 )
259 			continue; // Don't add it to the vector.
260 
261 		DirectoryVector.push_back ( DirectoryEntry );
262 	}
263 
264 	// Now sort it!
265 	sort(DirectoryVector.begin(), DirectoryVector.end());
266 
267 	// Scan through all the files in it.
268 	for ( unsigned int Iterator = 0; Iterator < DirectoryVector.size(); Iterator++ )
269 	{
270 		// Resize it if we're about to run out of space.
271 		if ( Buffer.capacity() - Buffer.size() < 100 )
272 			Buffer.reserve(1000);
273 
274 		// Alternate the CSS class for every other line.
275 		if ( AlternatingVariable == true )
276 		{
277 			Buffer += "<tr class=\"DFS_eventablerow\">";
278 			AlternatingVariable = false;
279 		}
280 		else
281 		{
282 			Buffer += "<tr class=\"DFS_oddtablerow\">";
283 			AlternatingVariable = true;
284 		}
285 
286 		// Entry Type
287 		if ( DirectoryVector[Iterator].Folder )
288 			Buffer += "<td class=\"DFS_entrytype\">[DIR]</td>";
289 		else
290 			Buffer += "<td class=\"DFS_entrytype\">[FILE]</td>";
291 
292 		// Entry Name
293 		if ( DirectoryVector[Iterator].Folder )
294 		{ // Give folders slashes on the end.
295 			Buffer += "<td class=\"DFS_entryname\"><a href=\"" + DirectoryVector[Iterator].CompletePath +
296 						"/\" class=\"DFS_direntry\">" + DirectoryVector[Iterator].Name + "/</a></td>";
297 		}
298 		else
299 		{
300 			Buffer += "<td class=\"DFS_entryname\"><a href=\"" + DirectoryVector[Iterator].CompletePath +
301 						"\" class=\"DFS_fileentry\">" + DirectoryVector[Iterator].Name + "</a></td>";
302 		}
303 
304 		// Entry Size
305 		if ( DirectoryVector[Iterator].Folder )
306 			Buffer += "<td class=\"DFS_entrysize\">&nbsp;</td></tr>\n";
307 		else
308 			Buffer += "<td class=\"DFS_entrysize\">" + string( ConvertSizeToFriendly( DirectoryVector[Iterator].Size ) ) + "</td></tr>\n";
309 	}
310 
311 	// End the table.
312 	Buffer += "</table>";
313 
314 	// Spit the completed index table back into the fuction that called us.
315 	return Buffer;
316 }
317 
ParseTemplate(ifstream & ArgFile,char * ArgVirtualPath,string ArgPath,DIR * DirectoryPointer)318 static string ParseTemplate ( ifstream &ArgFile, char *ArgVirtualPath, string ArgPath, DIR *DirectoryPointer )
319 {
320 	char *TempSpace;
321 	int FileSize;
322 	bool ActiveLoop = true;
323 	string TemplateData;
324 	string::size_type StringPosition;
325 	string::size_type LowestMatch = 0;
326 	set<string> FilesToHide;
327 
328 	// First determine the size of the file.
329         ArgFile.seekg(0, ios_base::end);
330         FileSize = static_cast<int>( ArgFile.tellg() );
331         ArgFile.seekg(0, ios_base::beg);
332         FileSize -= static_cast<int>( ArgFile.tellg() );
333 
334 	TempSpace = (char *) malloc( FileSize );
335 	TempSpace[FileSize] = '\0';
336 
337 	// Read all the information from the file into this char.
338 	ArgFile.read ( TempSpace, FileSize );
339 
340 	// Dump the char into a string, parse and replace all "keywords"
341 
342 	TemplateData = TempSpace;
343 
344 	free ( TempSpace );
345 
346 	while (ActiveLoop)
347 	{
348 		string::size_type StringSize;
349 		string::size_type LocalLowestMatch = 0;
350 		int StringOffset = 0;
351 
352 		StringSize = TemplateData.size();
353 		ActiveLoop = false;
354 
355 		// $$LOCATION$$
356 		StringPosition = TemplateData.find ("$$LOCATION$$", LowestMatch);
357 		if ( StringPosition != string::npos )
358 		{
359 			TemplateData.replace( StringPosition, sizeof("$$LOCATION$$") - 1, string(ArgVirtualPath) );
360 
361 			if ( StringPosition < LocalLowestMatch || LocalLowestMatch == 0 )
362 				LocalLowestMatch = StringPosition;
363 
364 			StringOffset += strlen(ArgVirtualPath) - sizeof("$$LOCATION$$");
365 			ActiveLoop = true;
366 		}
367 		// $$SERVERVERSION$$
368 		StringPosition = TemplateData.find ("$$SERVERVERSION$$", LowestMatch);
369 		if ( StringPosition != string::npos )
370 		{
371 			string VersionString("DashFileServer [Version " + string(MAJORVERSION) + "." + string(MINORVERSION) + "." + string(PATCHVERSION) + "]");
372 
373 			TemplateData.replace( StringPosition, sizeof("$$SERVERVERSION$$") - 1, VersionString );
374 
375 			if ( StringPosition < LocalLowestMatch || LocalLowestMatch == 0 )
376 				LocalLowestMatch = StringPosition;
377 
378 			StringOffset += VersionString.size() - sizeof("$$SERVERVERSION$$");
379 			ActiveLoop = true;
380 		}
381 		// $$INSERTFILE(*)$$
382 		StringPosition = TemplateData.find ("$$INSERTFILE(", LowestMatch);
383 		if ( StringPosition != string::npos )
384 		{
385 			string FileData;
386 			string::size_type StringEndPosition;
387 
388 			// Find where the end of this tag is...
389 			StringEndPosition = TemplateData.find (")$$", StringPosition);
390 
391 			// Make sure the tag ends
392 			if ( StringEndPosition != string::npos )
393 			{
394 				string TempFileName;
395 				string::size_type TempPosition;
396 
397 				// Now we Seperate the chunck between these two positions.
398 				TempPosition = StringPosition + sizeof("$$INSERTFILE");
399 				TempFileName = TemplateData.substr ( TempPosition, StringEndPosition - TempPosition );
400 
401 				// Make sure the filename doesn't contain certain elements that may be used
402 				// to redirect the folder, and make sure that it's not a file that has already
403 				// been included. ( Included files can have $$INSERTFILE tags, and they will be parsed.
404 				// 		    If an inserted file inserts itself, it will recurse until the computer
405 				//		    runs out of ram and the program dies. Not cool. )
406 				if ( TempFileName.find("/", 0) == string::npos &&
407 					TempFileName.find("\\", 0) == string::npos &&
408 					TempFileName.find("~", 0) == string::npos &&
409 					FilesToHide.find( TempFileName ) == FilesToHide.end() )
410 				{
411 					// Load the file into our string and add it to the FilesToHide vector
412 					// so that it doesn't show up in our vector.
413 					FileData = InsertFile( ArgPath.c_str(), TempFileName.c_str() );
414 					FilesToHide.insert( TempFileName );
415 				}
416 				else
417 					FileData.clear();
418 
419 				// We attempt to substitute $$INSERTFILE(*)$$ with the contents of FileData.
420 				// This will simply remove the tag if FileData is blank.
421 				TemplateData.replace( StringPosition, (StringEndPosition + 3) - StringPosition,
422 							FileData );
423 			}
424 			else
425 			{ // The tag is incomplete, cripple it so we don't scan it again.
426 				TemplateData[StringPosition] = '^';
427 				// Cancel out any offset detection.
428 				StringPosition = ( StringEndPosition + 3 );
429 			}
430 
431 			if ( StringPosition < LocalLowestMatch || LocalLowestMatch == 0 )
432 				LocalLowestMatch = StringPosition;
433 
434 			StringOffset += FileData.size() - ( ( StringEndPosition + 3 ) - StringPosition );
435 			ActiveLoop = true;
436 		}
437 
438 		// Set the global lowest match to the local one.
439 		// This is to save time on the searches.
440 		LowestMatch = LocalLowestMatch;
441 
442 		// Check for a Negative offset.
443 		if ( StringOffset < 0 )
444 		{
445 			string::size_type StringNewEnd;
446 
447 			// Change it to a positive offset.
448 			StringOffset *= -1;
449 
450 			// New end of the string.
451 			StringNewEnd = StringSize - StringOffset;
452 
453 			// Zap the tail of the string, if it exists.
454 			TemplateData.erase ( StringNewEnd, StringOffset );
455 		}
456 
457 	}
458 
459 	// This is a special case for the INDEX. We do it last and after the loop so that noting inside the index gets
460 	// parsed and replaced. Not that I expect that to happen, but since when were such expectations safe?
461 	StringPosition = TemplateData.find ("$$INDEXTABLE$$", 0);
462 	if ( StringPosition != string::npos )
463 		TemplateData.replace( StringPosition, sizeof("$$INDEXTABLE$$") - 1,
464 					InsertIndexTable( DirectoryPointer, ArgVirtualPath, ArgPath, FilesToHide ) );
465 
466 	return TemplateData;
467 }
468 
GenerateFolderIndex(string ArgVirtualPath,char * ArgPath,string & ArgBuffer)469 char GenerateFolderIndex( string ArgVirtualPath, char *ArgPath, string &ArgBuffer )
470 {
471 	DIR *DirectoryPointer = NULL;
472 	int FileTestDescriptor;
473 	ifstream FileStream;
474 	vector<DirectoryEntryStruct> DirectoryVector;
475 	char VirtualPath[255];
476 
477 	// This is a bit stupid, but I don't want to spend all night converting shit to std::string just to get it building again
478 	// before sunrise.
479 	strncpy ( VirtualPath, ArgVirtualPath.c_str(), sizeof(VirtualPath) );
480 
481 	// Open the directory.
482 	DirectoryPointer = opendir( ArgPath );
483 
484 	if ( DirectoryPointer == NULL )
485 	{
486 		// Try to open it as a file now.
487 		FileTestDescriptor = open( ArgPath, O_RDONLY );
488 
489 		if ( FileTestDescriptor < 1 )
490 			return -1; // Path is invalid.
491 		else
492 		{
493 			close ( FileTestDescriptor );
494 			return 0;  // Path points to a file.
495 		}
496 	}
497 
498 	// Check for index.htm and index.html
499 	if ( ( FileTestDescriptor = open( FullPath( ArgPath, "index.html" ).c_str(), O_RDONLY ) ) > 0 )
500 	{ // Index.html found.
501 		close ( FileTestDescriptor );
502 		strcat( ArgPath, "/index.html");
503 		return 0;
504 	}
505 	if ( ( FileTestDescriptor = open( FullPath( ArgPath, "index.htm" ).c_str(), O_RDONLY ) ) > 0 )
506 	{ // Index.htm found.
507 		close ( FileTestDescriptor );
508 		strcat( ArgPath, "/index.htm");
509 		return 0;
510 	}
511 
512 	if ( ArgBuffer.empty() )
513 		ArgBuffer.reserve(1000);
514 
515 	// Toss in a custom template in if one exists. If not, just use a generic form.
516 	FileStream.open ("indextemplates/default.html", ios::in ); // Open our template header.
517 	if ( !FileStream.is_open() || !FileStream.good() )
518 	{ // It doesn't exist, so toss in a generic header.
519 		set<string> Filler;
520 
521 		// <html> and <head>
522 		ArgBuffer = "<html>\n<head>\n<title>Directory listing for " + string(ArgVirtualPath) + "</title>\n";
523 		// A clone of KDE's public fileserver CSS sheet
524 		ArgBuffer += "<style type=\"text/css\">\n<!--\n";
525 		ArgBuffer += "body { font-family: Verdana, Arial, sans-serif;}\n#DFS_table {color: rgb(0, 0, 0);background-color: rgb(234, 233, 232); border: thin outset; width: 100%; }\n";
526 		ArgBuffer += "#DFS_table td { margin: 0px; white-space: nowrap; }\n#DFS_table a { color: rgb(0, 0, 0); text-decoration: none; }\n";
527 		ArgBuffer += "#DFS_table th { color: rgb(0, 0, 0); background-color: rgb(230, 240, 249); text-align: left; white-space: nowrap; border: thin outset; }\n";
528 		ArgBuffer += "tr.DFS_eventablerow { background-color: rgb(255, 255, 255); color: rgb (0, 0, 0); }\n";
529 		ArgBuffer += "tr.DFS_oddtablerow { background-color: rgb(238, 246, 255); color: rgb(0, 0, 0); }\n";
530 		ArgBuffer += ".DFS_entrytype { text-align: center; width: 6em; }\ntd.DFS_entrytype { background-color: rgb(255, 255, 255); font-weight: bold; color: rgb(150, 150, 150);}\n";
531 		ArgBuffer += ".DFS_entrysize { color: rgb(0, 0, 0); text-align: right;}\na.DFS_direntry { font-weight: bold; }\n";
532 		ArgBuffer += "-->\n</style>\n";
533 		// Insert the table.
534 		ArgBuffer += InsertIndexTable( DirectoryPointer, VirtualPath, string( ArgPath ), Filler );
535 		// Server Version Information.
536 		ArgBuffer += "<hr><i>DashFileServer [Version " + string(MAJORVERSION) + "." + string(MINORVERSION) + "." + string(PATCHVERSION) + "]</i>\n";
537 		// End the index
538 		ArgBuffer += "</body>\n</html>\n";
539 	}
540 	else
541 	{ // Let's grab the one from the template and parse it.
542 		ArgBuffer += ParseTemplate( FileStream, VirtualPath, string( ArgPath ), DirectoryPointer );
543 		FileStream.close();
544 	}
545 
546 	closedir( DirectoryPointer );
547 
548 	return 1;
549 }
550