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\"> </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