#!/usr/bin/perl -I.

# TODO: Cache domain/workgroup per-drive share
# TODO: Clickable box header
# TODO: Input validation of form inputs
# TODO: escape 
# TODO: Permission controls
# TODO: Share-independent clipboard
# TODO: GUI changes (?)
# TODO: Set maximum file upload size via Apache::
# TODO: Display drive letter on browse page
# TODO: 'Select all' button
# TODO: Pull-down menu with bookmarks
# TODO: Bookmark sorting
# TODO: Display all file shares if there are no recently used ones
# TODO: Display cut items with faded icon and/or text color
# DONE: Rename (files and folders)
# DONE: Make rename js popup prompt compatible with older browsers
# DONE: Fix timestamps
# DONE: Display file sizes in pretty KB
# DONE: DNS resolution
# DONE: Supernat
# DONE: Set supernat addresses Pos::defaults
# DONE: Error pages
# DONE: WINS resolution
# DONE: Delete confirmation dialog
# DONE: Cut & paste
# DONE: Directory deletion
# DONE: 'Create directory' button
# DONE: Handle deep-linked file shares (in preview pane, as well)
# DONE: Preview pane with most commonly used list
# DONE: Username & password prompt if auth credentials aren't cached
# DONE: Share auth credentials with vpn client
# DONE: Copy & paste
# DONE: New smbclient-compatible Smb.pm

use strict;
use Pos::Webtop::FileShare::Smb;
use Pos::Webtop::Web::ContentModule;
use Digest::MD5 ();
use Pos::Webtop::OcxLauncher ();

package Pos::Webtop::Web::ContentModule::FileShares;

our @ISA = qw(
	Pos::Webtop::Web::ContentModule
);

sub getAclRules
{
	my $self = shift;
	my $tb = $self->getToolBox();
	my $db = $tb->getWebtopDbReader();
	my $client = $tb->getClient();
	my $userId = $client->getUserId();

	if ( not defined $self->{'aclRules'} )
	{
		my @aclRules = ();

		my @rules = $db->query( sql=> "
			select
				upper(protocol_name),
				location,
				upper(target_name)
			from
				w_user_acls a,
				w_acl_targets t,
				w_acl_protocols p
			where
				a.protocol_id = p.protocol_id
				and t.target_id = a.target_id
				and user_id = $userId order by rule_number
		");

		foreach ( @rules )
		{
			my ($protocol,$location,$target) = @{ $_ };

			push @aclRules, {
				protocol=> $protocol,
				location=> $location,
				target=> $target
			};
		}

		$self->{'aclRules'} = \@aclRules;
	}

	return $self->{'aclRules'};
}

sub aclCanAccess
{
	my $self = shift;
	my ($server,$servers,$path,$protocol) = @{{@_}}{qw/server servers path protocol/};

	$path =~ s/\\/\//g; # convert to forward slashes

	# path comes in with a leading slash on it.. remove it
	$path =~ s/^\///g;

	# remove dupe slashes
	$path =~ s/[\/]+/\//g;

	# protocol is FILE for 'file shares (smb or other shares)'
	# protocol is FTP for ftp
	
	# servers allows you to pass an array ref of servers (like ip vs hostname)
	if ( not defined $servers )
	{
		$servers = [$server];
	}

	my @checkLocations;

	foreach ( @{ $servers } )
	{	
		my $checkLocation = $_ . "/" . $path;
		$checkLocation =~ s/[\\\/]$//g;
		$self->{'debug'} and warn "aclCanAccess: checkLocation: $checkLocation\n";
		push @checkLocations, $checkLocation;
		
		# now we also check with a / at end incase it's a dir match in the policy
		$checkLocation .= "/";
		$self->{'debug'} and warn "aclCanAccess: checkLocation: $checkLocation\n";
		push @checkLocations, $checkLocation;
	}

	# now check locations is filled with 'all possible representations of where we are going'

	$protocol = "FILE" unless defined $protocol;

	my $tb = $self->getToolBox();
	my $db = $tb->getWebtopDbReader();

	my $aclRules = $self->getAclRules();

	# we iterate the rules, considering all that are our protocol.

	my $defaultGood = 1;
	my $good = undef;

	foreach my $ar ( @{ $aclRules } )
	{
		$ar->{'protocol'} eq $protocol or next;

		if ( not length( $ar->{'location'} ) )
		{
			# this is a 'default' policy for this protocol... 
			$defaultGood = ( $ar->{'target'} eq 'ALLOW' ? 1 : 0 );
			next;
		}

		# now we have to convert the 'location' with * wildcards into a safe match regex
		# and compare it against.  We have to keep track if the last char was the * char
		# since split will not put an 'empty' at the end of @pieces if it was... we need
		# that.
		
		my @pieces = split(/\*/,$ar->{'location'});
		my @pieces2;

		if ( $ar->{'location'} =~ /\*$/ )
		{
			push @pieces, "";
		}

		foreach my $piece ( @pieces )
		{
			$piece =~ s/^\\\\//g;
			# this will replace \ or / with [\/]... which matches any /\ in regex
			# note that I have to close and reopen the \Q \E combos here
			$piece =~ s/[\\\/]/\E\[\\\/\]\Q/g;
			push @pieces2, $piece;
		}

		# now @pieces2 is filled with safe chunks of regex that are / independent for path matches... 

		@pieces = ();

		foreach my $piece ( @pieces2 ) 
		{
			$piece = "" unless defined $piece;
			push @pieces, "\Q" . $piece . "\E";
		}

		# now @pieces is filled with things that are safe to use in regex
		# place them in one big regex separated by .*

		my $regex = "^" . join(".*",@pieces) . "\$";

		# now we compare the regex to all the @checkLocations entries... 

		foreach ( @checkLocations )
		{
			$self->{'debug'} and warn "aclCanAccess: regex: $regex\n";

			if ( $_ =~ /$regex/i )
			{
				# we match...
				$good = ( $ar->{'target'} eq 'ALLOW' ? 1 : 0 );
				last;
			}
		}

		if ( defined $good )
		{
			last;
		}
	}
	
	if ( not defined $good )
	{
		$good = $defaultGood
	}

	return $good;
}

sub randomMd5
{
	my $data = rand() . rand() . rand() . rand();
	my $md5 = Digest::MD5->new();
	$md5->add( $data );
	return $md5->hexdigest();
}

# be sure to commit on the webtopDbWriter after calling to this
sub storeShareInfo
{
	my $self = shift;
	my ($server, $share, $user, $password) = @{{@_}}{qw/server share user password/};
	$self->{'displayGroup'} = randomMd5() unless defined $self->{'displayGroup'};
	$self->{'storedShareId'} = 0 unless defined $self->{'storedShareId'};
	$self->{'storedShareId'}++;
	my $tb = $self->getToolBox();
	my $db = $tb->getWebtopDbWriter();
	
	$server = "" unless defined $server;
	$share = "" unless defined $share;
	$user = "" unless defined $user;
	$password = "" unless defined $password;

	$db->do( sql=> "
		insert into client_session_share_info (
			client_session_id,
			display_group,
			share_id,
			server,
			sharename,
			username,
			password
		) values (
			" . $tb->getClient()->getSessionId() . ",
			'" . $self->{'displayGroup'} . "',
			" . $self->{'storedShareId'} . ",
			'" . $db->escape( dirty=> $server ) . "',
			'" . $db->escape( dirty=> $share ) . "',
			'" . $db->escape( dirty=> $user ) . "',
			'" . $db->escape( dirty=> $password ) . "'
		)
	");
}

sub getStoredShareInfo
{
	my $self = shift;
	my ($shareId,$displayGroup) = @{{@_}}{qw/shareId displayGroup/};

	return {} unless defined $shareId;
	return {} unless defined $displayGroup;

	my $tb = $self->getToolBox();
	# even though this is a reader, i need the writer to be sure that we get the data from our last write
	my $db = $tb->getWebtopDbWriter(); 

	my @info = $db->query( sql=> "
		select server, sharename, username, password
		from client_session_share_info
		where client_session_id = " . $tb->getClient()->getSessionId() . "
			and display_group = '" . $db->escape( dirty=> $displayGroup ) . "'
			and share_id = '" . $db->escape( dirty=> $shareId ) . "'
	");
	
	return {} unless @info;
	
	my ($server,$share,$user,$password) = @{ $info[0] };

	return {
		server=> $server,
		share=> $share,
		user=> $user,
		password=> $password
	};
}

# be sure to call commit on the webtopDbWriter after using this
# this isn't even used -- all items are just left in db until session expires
# this shouldn't be an issue.. items are only stored on the front page or a 
# manual login.  
sub cleanupDisplayGroup
{
	my $self = shift;
	my ($displayGroup) = @{{@_}}{qw/displayGroup/};
	my $tb = $self->getToolBox();
	my $db = $tb->getWebtopDbWriter(); 

	$db->do( sql=> "
		delete from client_session_share_info
		where client_session_id = " . $tb->getClient()->getSessionId() . "
			and display_group = '" . $db->escape( dirty=> $displayGroup ) . "'
	");
}

sub new 
{
	my $this = shift;
	my $class = ref($this) || $this;
	my ($self) = @{{@_}}{qw/self/};

	if ( not defined $self )
	{
		$self = {};
		bless($self,$class);
	}

	# setup base class 
	my %args = @_;
	$args{'self'} = $self;
	Pos::Webtop::Web::ContentModule->new( %args );

	$self->{'smbShare'} = undef;
	$self->{'debug'} = 0;

	return $self;	
}

sub altLinkCheck
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $ret = "";

	my $userId = $client->getUserId();

	my $db = $toolBox->getWebtopDbReader();
	my @altLink = $db->query( sql=> "select link_text, link_href from w_user_alt_fileshares where user_id = $userId" );
	
	if ( @altLink )
	{
		my ($text,$href) = @{ $altLink[0] };
		$ret .= "<b>Access Your File Shares:</b><br/><a href=\"javascript:popupWindowIntranet('" . HTML::Entities::encode($href) . "');\">" . 
			HTML::Entities::encode($text) . "</a><br/><br/>\n";
		
	}
	
	return $ret;
}

sub canConnectNewFileShare
{
	my $self = shift;
	my $tb = $self->getToolBox();
	my $client = $tb->getClient();
	
	if ( not defined( $self->{'canConnectNewFileShare'} ) )
	{
		my $moduleAccess = $client->getAllowedContentModules();
		
		$self->{'canConnectNewFileShare'} = $moduleAccess->{'FileShares'}->{'sm_newFileshare'};
	}

	return $self->{'canConnectNewFileShare'};
}

# (pure virtual defined in Pos::Webtop::Web::ContentModule)
sub main
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();
	my $ret = "";

	$client->checkSession();

	$ret .= $self->altLinkCheck();

	if ( length($ret) )
	{
		my $page = $toolBox->getPage();
		print $page->getHeaderHtml( title=> "File Share Browser" );
		#$ret .= Pos::Webtop::OcxLauncher::getLauncherHtml( tb=> $toolBox );
		print $ret;
		print $page->getFooterHtml();
		return 1;
	}

	my $act = $cgi->param('act');

	if ( defined $act ) 
	{
		if ( $act eq "browseShareDir" || $act eq "Refresh" )
		{
			$ret .= $self->getShareBrowseHtml();
		}
		elsif ( $act eq "downloadFile" )
		{
			my $temp = $self->downloadFile();
			defined($temp) and $ret .= $self->downloadFile();
		}
		elsif ( $act eq "uploadFile" || $act eq "Upload File" )
		{
			$ret .= $self->uploadFile();
		}
		elsif ( $act eq "Delete" )
		{
			$ret .= $self->deleteItems();
		}
		elsif ( $act eq "Rename" )
		{
			$ret .= $self->renameItems();
		}
		elsif ( $act eq "Cut" )
		{
			$ret .= $self->addItemsToClipboard( cut => 1 );
		}
		elsif ( $act eq "Copy" )
		{
			$ret .= $self->addItemsToClipboard( cut => 0 );
		}
		elsif ( $act eq "Paste" )
		{
			$ret .= $self->pasteClipboardItems();
		}
		elsif ( $act eq "Create Folder" )
		{
			$ret .= $self->createFolder();
		}
		elsif ( $act eq "RemoveDriveShare" )
		{
			$ret .= $self->removeDriveShare();
		}
		else
		{
			warn ref($self) . "->main(): user submitted an unknown act ('$act'); returning login page";
			$ret .= $self->getShareLoginHtml();
		}
	}
	else
	{
		$ret .= $self->getShareLoginHtml();
	}

	print $ret;

	return 1;
}

# (pure virtual defined in Pos::Webtop::Web::ContentModule)
sub getPreviewHtml
{
	my $self = shift;
	my $ret = "";
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();

	$ret .= $self->altLinkCheck();

	if ( length($ret) )
	{
		return $self->getPreviewHeader() . $ret . $self->getPreviewFooter();
	}

	eval { $toolBox->debugLog( msg => "######## ------------- blah") };

	# OBSOLETE
	#my $userId = $client->getUserId();

	$ret = $self->getPreviewHeader();

	$ret .= "
		<table border='0' width='100%' cellpadding=2>
		<tr>
			<td class='CLMPREVIEWDEF' align='left' colspan='3'>
				<b>Recently used file shares:</b><br/>
			</td>
		</tr>
	";

	# Get all of the users file shares out of the db
	my (@appFileShares) = $self->getAppFileShares();
	my (@webtopFileShares) = $self->getWebtopFileShares();

	# TODO: combine these lists, then sort them based on last accessed date, then trim all but the top 5
	my @unsortedFileShares = (@appFileShares,@webtopFileShares);

	my @accessedFileShares;
	if (@accessedFileShares) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->getPreviewHtml() \@accessedFileShares has shit in it" ); }
	my @unaccessedFileShares = ();
	my %nodupes = ();

	foreach my $fileShare (@unsortedFileShares)
	{
		if ( defined $fileShare->{'fshLastAccess'} && length($fileShare->{'fshLastAccess'}))
		{
			if ( not defined $nodupes{ $fileShare->{'fsvHostname'} . ":" . $fileShare->{'fshName'} } )
			{
				push(@accessedFileShares,$fileShare);
				$nodupes{ $fileShare->{'fsvHostname'} . ":" . $fileShare->{'fshName'} } = 1;
			}
		}
		else
		{
			push(@unaccessedFileShares,$fileShare);
		}
	}

	# if now we add more to the end to make it at least 5 even if they weren't accessed ever
	while ( scalar(@accessedFileShares) < 5 && scalar(@unaccessedFileShares) )
	{
		my $fileShare = shift @unaccessedFileShares;

		if ( not defined $nodupes{ $fileShare->{'fsvHostname'} . ":" . $fileShare->{'fshName'} } )
		{
			$fileShare->{'fshLastAccess'} = 0;
			push @accessedFileShares, $fileShare;
		}
	}

	my @fileShares = sort { $b->{'fshLastAccess'} cmp $a->{'fshLastAccess'} } @accessedFileShares;

	# Now that the sorting is done, just take the top 5
	@fileShares = @fileShares[0..4];
	
	my $tb = $self->getToolBox();
	my $dbw = $tb->getWebtopDbWriter();
	my $skinOemId = Pos::Webtop::Web::OEMLoader::getSkinOemId( oemId=> $tb->getOemId );

	my $hadSomeDriveLetter = 0;

	foreach ( @fileShares )
	{
		if ( $_->{'fshDriveLetter'} )
		{
			$hadSomeDriveLetter = 1;
			last;
		}
	}

	foreach my $fileShare (@fileShares)
	{
		#warn "		Last access for ($fileShare->{'fshDriveLetter'}) $fileShare->{'fsvHostname'}: $fileShare->{'fshLastAccess'}";
		if ( not ( defined $fileShare->{'fsvHostname'} && defined $fileShare->{'fshName'} ) ) { next; }

		my $sharePath = "\\\\" . $fileShare->{'fsvHostname'} . "\\" . $fileShare->{'fshName'} . "\\";
		$sharePath .= $fileShare->{'fshInitialFolder'} unless not defined  $fileShare->{'fshInitialFolder'};
		
		$self->storeShareInfo(
			server=> $fileShare->{'fsvHostname'},
			share=> $fileShare->{'fshName'},
			user=> $fileShare->{'fsaUsername'},
			password=> $fileShare->{'fsaPassword'}
		);
		
		if ( $hadSomeDriveLetter eq 1 )
		{
			$ret .= "
				<tr>
					
					<td class='CLMPREVIEWDEF' align='right' width='1'><nobr>&nbsp;&nbsp;<img src='LocalWebLib/OEM/" . $skinOemId . "/OrangeBullet.png'></nobr>
					</td>	
						" .  ( defined $fileShare->{'fshDriveLetter'} ? ("<td class='CLMPREVIEWTD4' align='right' width='1%'><font color='black'>(" . $fileShare->{'fshDriveLetter'} . ":)</font></td>") : "<td></td>" ) . "
			";
		}
		elsif ( $hadSomeDriveLetter eq 0 )
		{
			$ret .= "
				<tr>
					
					<td class='CLMPREVIEWTD4' align='left' width='1'><nobr>&nbsp;&nbsp;<img src='LocalWebLib/OEM/" . $skinOemId . "/OrangeBullet.png'></nobr></td>
			";
		}
		
		$ret .= "
				<td class='CLMPREVIEWTD4' align='left' width='100%'>
				" .
					( $client->getFormGetCode(
						text => "$sharePath",
						values => [
							{ name => "act", value => "browseShareDir" },
							{ name => ( $client->getPersistPrefix() . "module" ), value => "FileShares" },
							{ name => "currentDir", value => HTML::Entities::encode(( "\\" . ( defined $fileShare->{'fshInitialFolder'} ? $fileShare->{'fshInitialFolder'} : "" ) )) },
							{ name => "displayGroup", value=> $self->{'displayGroup'} },
							{ name => "shareId", value=> $self->{'storedShareId'} },
							{ name => "initialFolder", value => HTML::Entities::encode(( "" . ( defined $fileShare->{'fshInitialFolder'} ? $fileShare->{'fshInitialFolder'} : "" ) )) },
							{ name => "isRecent", value=> 1 },
							{ name => "admintool", vaue=> $fileShare->{'fshAdmintool'} }
						])) . "
				</td>
			</tr>
		";
	}

	$dbw->commit();


	$ret .= "
			</td>
		</tr>
		<tr>
			<td class='CLMPREVIEWTD4' align='right' colspan='3'>
				<br/>
				" .	$client->getFormGetCode(
							values => [ { name => 'module', value => 'FileShares' } ],
							text => "More..."
					
						) . "
			</td>
		</tr>
		</table>
	";
#					text => "<img src='LocalWebLib/filePreviewIcon.gif' border='0'><br/>File Share Browser"


	$ret .= "
	";

	$ret .= $self->getPreviewFooter();

	return $ret;
}

sub getPreviewTitle
{
	return shift->{'previewTitle'};
}

sub getTitle
{
	return shift->{'title'};
}

sub getPreviewWidth
{
	return shift->{'previewWidth'};
}

# Returns Share Login Page HTML
sub getShareLoginHtml
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();
	my $isMobile = $client->isMobileWebtop();
	my ($topText) = @{{@_}}{qw/topText/};

	# Setup the form entry fields
	my @loginFields =
	(
	 	{	name => "serverName",
			desc => "Server Address",
			size => ( $isMobile ? "10" : "25" )
		},
		{	name => "shareName",
			desc => "Share Name",
			size => ( $isMobile ? "10" : "15" )
		},
		{	name => "shareUserName",
			desc => "Share Username",
			size => ( $isMobile ? "10" : "15" )
		}
	);

	# Page header
	my $page = $toolBox->getPage();
	
	# Currently, this is the default page for this content module so we won't add anything
	#$page->addNavBarElement( description => "Login" );

	my $ret = $page->getHeaderHtml( title => "File Shares: Login" );
	#$ret .= Pos::Webtop::OcxLauncher::getLauncherHtml( tb=> $toolBox );

	my $listBox = $toolBox->createBox( title => "File Shares", width => '500' );

	my $customBox = $toolBox->createBox( title => "Connect to a New File Share" , width => '500');

	# Form header
	$ret .= "
		<script type='text/javascript'>
			
			function verifySharePath( dirty )
			{
				var re = /^(\\\\\\\\[^\\\\]+)(\\\\[^\\\\]*)*(\\\\?)\$/;

				if ( !String(dirty).match(re) )
				{
					return false;
				}

				return true;

			}

			
			function finalize()
			{
				remoteAddress = document.getElementById('remoteAddress');
				manualFileShare = document.getElementById('manualFileShare');

				if (!verifySharePath(remoteAddress.value))
				{
					alert('Please enter a valid remote address');
					return false;
				}

				manualFileShare.value = 1;

				return true;	
			}
			
		</script>
	
		<form method='POST' name='manualConnect' onSubmit='return finalize();'>
		<input type='hidden' name='act' value='browseShareDir'>
		<input type='hidden' name='admintool' value='0'>
		<input type='hidden' name='manualFileShare' value='0'>
	";

	$ret .= $client->getFormInputs();

	# 'Choose a file share' box
	$ret .= "
		<center>
		" . ( $listBox->getHeaderHtml() ) . "
		
		<table class='BOXTABLE' border='0' cellspacing='3' cellpadding='4' width=\"" . ($isMobile ? "200" : "500") . "\">
		<tr>
			<td class='BOXTDMENU' align='center' colspan=\"" . ($isMobile ? "2" : "3") . "\">
				<font class='BOXSMALL'>Click on a file share below to connect</font>
			</td>
		</tr>
		<tr>
			<td class='BOXTD2' width='43%'>
				<B>
					File Share
				</B>
			</td>" . ($isMobile ? "" : "
			<td class='BOXTD2' width='43%'>
				<B>
					Username
				</B>
			</td>") . "
			<td class='BOXTD2' width='14%'>
				<B>
					Action
				</B>
			</td>
		</tr>
	";

	# TODO: Sort listing by drive letter, then by hostname, then by sharename
	# List all of the file shares from the app and webtop tables 
	my (@appFileShares) = $self->getAppFileShares();
	my (@webtopFileShares) = $self->getWebtopFileShares();
	my $dbw = $toolBox->getWebtopDbWriter();
	
	foreach my $fileShare (@appFileShares,@webtopFileShares)
	{
		my $sharePath = "\\\\" . $fileShare->{'fsvHostname'} . "\\" . $fileShare->{'fshName'} . "\\" . ( defined $fileShare->{'fshInitialFolder'} ? $fileShare->{'fshInitialFolder'} : "" );
		my (undef,$mainServer,$mainShare,$mainFolder) = split(/\\{1,2}/,$sharePath,4); 
		
		$self->storeShareInfo(
			server=> $fileShare->{'fsvHostname'},
			share=> $fileShare->{'fshName'},
			user=> $fileShare->{'fsaUsername'},
			password=> $fileShare->{'fsaPassword'}
		);

		$ret .= "
			<tr>
				<td class='BOXTD1'>
					<nobr>
					" . ( defined $fileShare->{'fshDriveLetter'} ? ("(" . $fileShare->{'fshDriveLetter'} . ":) ") : "" )
					. ( $client->getFormGetCode(
								text => "" . (defined($sharePath) ? "$sharePath" : "") . "",
								values => [
									{ name => "act", value => "browseShareDir" },
									{ name => "currentDir", value => HTML::Entities::encode(( "\\" . ( defined $fileShare->{'fshInitialFolder'} ? $fileShare->{'fshInitialFolder'} : "" ) )) },
									{ name => "displayGroup", value=> (defined($self->{'displayGroup'}) ? $self->{'displayGroup'} : "") },
									{ name => "shareId", value=> (defined($self->{'storedShareId'}) ? $self->{'storedShareId'} : "") },
									{ name => "isRecent", value=> 1 },
									{ name => "initialFolder", value=> HTML::Entities::encode(( "" . ( defined $mainFolder ? $mainFolder : "" ) )) },
									{ name => "admintool", value=> $fileShare->{'fshAdmintool'} },
								])) . " 
					</nobr>
				</td>" . ($isMobile ? "" : "
				<td class='BOXTD1'>
					" . (defined($fileShare->{'fsaUsername'}) ? $fileShare->{'fsaUsername'} : "") . "
				</td>") . "
				<td class='BOXTD1'>
				" . (defined $fileShare->{'fshDriveLetter'} ? "" : "
				[" . $client->getFormGetCode( 
				 			text => 'remove',
							values => [
								{
									name => 'module',
									value => 'FileShares'
								},
								{
									name => 'fileShareHostName',
									value => $mainServer
								},
								{
									name => 'act',
									value => 'RemoveDriveShare'
								},
								{
									name => 'fileShareName',
									value => $mainShare
								},
								{
									name	=>	'fileShareInitialFolder',
									value	=>	$mainFolder
								}
							]) ."]") . "

				</td>
			</tr>
		";
	}

	$dbw->commit();

	$ret .= "</table>\n" . $listBox->getFooterHtml() . "<br/>\n";

	if ($self->canConnectNewFileShare())
	{
		# 'New file share' box
		$ret .= "
			" . ( $customBox->getHeaderHtml() ) . "
			
			<table class='BOXTABLE' border='0' cellspacing='1' cellpadding='4' width=\"" . ($isMobile ? "200" : "500") . "\">
			<tr>
				<td class='BOXTDMENU' align='center' colspan='2'>
					<font class='BOXSMALL'>Please enter the information below to access a file share</font>
				</td>
			</tr>
			<tr>
				<td class='BOXTD1' align='right' width='50%'>
					Remote Address
				</td>
				<td class='BOXTD1' align='left' width='50%'>
					<input type='text' id='remoteAddress' name='remoteAddress' value='' size=\"" . ($isMobile ? "10" : "25") . "\">
				</td>
			</tr>
			<tr>
				<td class='BOXTD1' align='right' width='50%'>
					Share Username	
				</td>
				<td class='BOXTD1' align='left' width='50%'>
					<input type='text' name='shareUserName' value='' size='15'>
				</td>
			<tr>
				<td class='BOXTD1' align='right' width='50%'>
					Share Password	
				</td>
				<td class='BOXTD1' align='left' width='50%'>
					<input type='password' name='sharePassword' value='' size='15'>
				</td>
			</tr>
		";

		# Form and table footer
		$ret .= "
			<tr>
				<td class='BOXTDMENU' colspan='2' align='center'>
					<input type='submit' name='share_login_submit' value='Connect'>
				</td>
			</tr>
			</table>
			" . $customBox->getFooterHtml() . "
			</form>
			</center>\n";
	}
	$ret .= $page->getFooterHtml();

	return $ret;
}

# Unable to get directory listing (unknown error) - asks users to re-enter server, share, username, password
sub getShareConnectErrorHtml
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $cgi = $toolBox->getCgi();
	my $dbr = $toolBox->getWebtopDbReader();
	my $dbw = $toolBox->getWebtopDbWriter();
	my $client = $toolBox->getClient();
	my $userId = $client->getUserId();
	my $isMobile = $client->isMobileWebtop();
	my ($initialTry) = @{{@_}}{qw/initialTry/};
	$initialTry = 0 unless defined $initialTry;

	my ($admintool) = @{{@_}}{qw/admintool/};
	$admintool = 1 unless defined $admintool;
	
	# Format currentDir 
	my $currentDir = $cgi->param('currentDir');
	$currentDir = '\\' unless $currentDir;

	my $shareId = $client->getPersistedValue( name=> 'shareId' );
	$shareId = $cgi->param('shareId') if defined $cgi->param('shareId');
	my $displayGroup = $client->getPersistedValue( name=> 'displayGroup' );
	$displayGroup = $cgi->param('displayGroup') if defined $cgi->param('displayGroup');
	my $shareInfo = $self->getStoredShareInfo( shareId => $shareId, displayGroup=> $displayGroup );
	my $shareUserName = $cgi->param('shareUserName');
	!defined($shareUserName) || !length($shareUserName) and $shareUserName = $shareInfo->{'user'};
	my $sharePassword = $shareInfo->{'password'};
	#my $shareName = $shareInfo->{'share'};
	my $shareName = $cgi->param('shareName');
	#my $serverName = $shareInfo->{'server'};
	my $serverName = $cgi->param('serverName');
	defined($serverName) and $serverName =~ s/^\\\\//g;
	$client->setPersistedValue( name=> "shareId", value=> $shareId );
	$client->setPersistedValue( name=> "displayGroup", value=> $displayGroup );
	my $initialFolder = $cgi->param('initialFolder');

	my $errorMessageText = undef;
	my $inputTextForServer = undef;
	my $inputTextForShare = undef;

	# Page header
	my $page = $toolBox->getPage();
	
	# Currently, this is the default page for this content module so we won't add anything
	#$page->addNavBarElement( description => "Error: " );

	my $ret = $page->getHeaderHtml( title => "File Shares: Login" );
	#$ret .= Pos::Webtop::OcxLauncher::getLauncherHtml( tb=> $toolBox );

	my $customBox = $toolBox->createBox( title => "File Share Login", width => '500' );

	my $remoteAddress = $cgi->param('remoteAddress');
	
	if($admintool eq '1')
	{ 
		!defined($remoteAddress) || !length($remoteAddress) and $remoteAddress = "\\\\" . $shareInfo->{'server'} . (defined($shareInfo->{'share'}) ? "\\" . $shareInfo->{'share'} . "" : "") . (defined($currentDir) ? "$currentDir" : "");
		
		while ( $remoteAddress =~ /\\$/ ) { chop $remoteAddress }
		
		$errorMessageText = "The drive share or username and password specified by your administrator are invalid. You may enter
						your username and password to gain access to the file share. If this problem persists, contact your administrator.
						<font class='BOXSMALL'><i>(Note: Use your network username and password from work)</i></font>";

		$inputTextForServer = "<input type='hidden' id='remoteAddress' name='remoteAddress' value=\"" . HTML::Entities::encode($remoteAddress) . "\">" . HTML::Entities::encode($remoteAddress) . "";
	}
	else
	{
		!defined($remoteAddress) || !length($remoteAddress) and $remoteAddress = "\\\\" . $shareInfo->{'server'} . (defined($shareInfo->{'share'}) ? "\\" . $shareInfo->{'share'} . "" : "") . (defined($currentDir) ? "$currentDir" : "");
		
		while ( $remoteAddress =~ /\\$/ ) { chop $remoteAddress }
		
		#!defined($remoteAddress) and $remoteAddress = $serverName . (defined($shareName) && length($shareName)? "\\$serverName" : "") . (defined($currentDir) && length($currentDir) ? "\\$currentDir" : "");
		$errorMessageText = "Invalid drive share, username, or password. Please verify information and reenter your password.
						<font class='BOXSMALL'><i>(Note: Use your network username and password from work)</i></font>";

		$inputTextForServer = "<input type='text' id='remoteAddress' name='remoteAddress' value=\"" . HTML::Entities::encode($remoteAddress) . "\" size=\"" . ($isMobile ? "10" : "25" ) . "\">";
	}

	# Form header
	$ret .= "
		<script type='text/javascript'>
			
			function verifySharePath( dirty )
			{
				var re = /^(\\\\\\\\[^\\\\]+)(\\\\[^\\\\]*)*(\\\\?)\$/;

				if ( !String(dirty).match(re) )
				{
					return false;
				}

				return true;

			}

			
			function finalize()
			{
				remoteAddress = document.getElementById('remoteAddress');
				manualFileShare = document.getElementById('manualFileShare');

				if (!verifySharePath(remoteAddress.value))
				{
					alert('Please enter a valid remote address');
					return false;
				}

				manualFileShare.value = 1;

				return true;	
			}
			
		</script>
	
		<form autocomplete='off' method='POST' onSubmit='return finalize();'>
		<input type='hidden' name='manualFileShare' value='1'>
		<input type='hidden' name='act' value='browseShareDir'>
		<input type='hidden' name='initialFolder' value=\"" . HTML::Entities::encode($initialFolder) . "\">
		<input type='hidden' name='currentDir' value=\"" . HTML::Entities::encode($currentDir) . "\">
		<input type='hidden' name='admintool' value=\"" . HTML::Entities::encode($admintool) . "\">
	";

	$ret .= $client->getFormInputs();

	$ret .= "
		<center>
		" . ( $customBox->getHeaderHtml() ) . "
		
		<table class='BOXTABLE' border='0' cellspacing='1' cellpadding='4' width=\"" . ($isMobile ? "200" : "500") . "\">
		<tr>
			<td class='BOXTDMENU' align='center' colspan='2'>
				$errorMessageText
			</td>
		</tr>
		<tr>
			<td class='BOXTD1' align='right' width='50%'>
				Server Name
			</td>
			<td class='BOXTD1' align='left' width='50%'>
				$inputTextForServer
			</td>
		</tr>
		<tr>
			<td class='BOXTD1' align='right' width='50%'>
				Share Username
			</td>
			<td class='BOXTD1' align='left' width='50%'>
				<input type='text' name='shareUserName' value=\"" . HTML::Entities::encode($shareUserName) . "\" size=\"" . ($isMobile ? "10" : "15" ) . "\">
			</td>
		</tr>
		<tr>
			<td class='BOXTD1' align='right' width='50%'>
				Share Password	
			</td>
			<td class='BOXTD1' align='left' width='50%'>
				<input type='password' name='sharePassword' value='' size=\"" . ( $isMobile ? "10" : "15" ) . "\">
			</td>
		</tr>
	";

	# Form and table footer
	$ret .= "
		<tr>
			<td class='BOXTD3' colspan='2' align='center'>
				<input type='submit' name='share_login_submit' value='" . ( $initialTry ? "Connect" : "Retry" ) . "'>
			</td>
		</tr>
		<tr>
			<td class='BOXTDMENU' colspan='2'>
				" .	$client->getFormGetCode(
							values => [ { name => 'module', value => 'FileShares' } ],
							text => "Click here to go back"
						) . "
			</td>
		<tr>
		</table>
		" . $customBox->getFooterHtml() . "
		</form>
		</center>\n";

	$ret .= $page->getFooterHtml();

	return $ret;
}

# Normal browsing page - displays the contents of a folder in a file share
sub getShareBrowseHtml
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $cgi = $toolBox->getCgi();
	my $dbr = $toolBox->getWebtopDbReader();
	my $dbw = $toolBox->getWebtopDbWriter();
	my $client = $toolBox->getClient();
	my $userId = $client->getUserId();
	my $isMobile = $client->isMobileWebtop();
	
	# So far, I am unable to detect the file share block size with stat(), so it's static
	my $shareBlockSize = 512;

	# TODO: Persist the username and password in the database
	# TODO: Detect the share type (NetWare, DFS, etc.)

	my $shareId = $client->getPersistedValue( name=> 'shareId' );
	$shareId = $cgi->param('shareId') if defined $cgi->param('shareId');
	my $displayGroup = $client->getPersistedValue( name=> 'displayGroup' );
	$displayGroup = $cgi->param('displayGroup') if defined $cgi->param('displayGroup');
	my $shareInfo = $self->getStoredShareInfo( shareId => $shareId, displayGroup=> $displayGroup );
	my $shareUserName = $shareInfo->{'user'};
	my $sharePassword = $shareInfo->{'password'};
	my $shareName = $shareInfo->{'share'};
	my $serverName = $shareInfo->{'server'};
	$serverName =~ s/^\\\\//g;
	$client->setPersistedValue( name=> "shareId", value=> $shareId );
	$client->setPersistedValue( name=> "displayGroup", value=> $displayGroup );
	my $admintool = $cgi->param('admintool');
	my $manualFileShare = $cgi->param('manualFileShare');
	my $manualFileShareAddress = $cgi->param('remoteAddress');
	my $fromCache = 0;
	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');
	
	my $usedUserValues = 0; # if this is set, we will NOT look for cached goodness later

	if ( (defined $cgi->param('shareUserName') and length $cgi->param('shareUserName')) || $manualFileShare )
	{
		(undef,$serverName,$shareName,$currentDir) = split(/\\{1,2}/,$manualFileShareAddress,4); 
		$initialFolder = $currentDir;
		$currentDir = "\\" . $currentDir;
		$usedUserValues = 1;
		$shareUserName = $cgi->param('shareUserName');
		$sharePassword = $cgi->param('sharePassword');
		$serverName =~ s/^\\\\//g;

		$self->storeShareInfo( user=> $shareUserName, password=> $sharePassword, server=> $serverName, share=> $shareName );
		$dbw->commit();
		$client->setPersistedValue( name=> 'displayGroup', value=> $self->{'displayGroup'} );
		$client->setPersistedValue( name=> 'shareId', value=> $self->{'storedShareId'} );
		$self->{'initShareId'} = $self->{'storedShareId'};
		$self->{'initDisplayGroup'} = $self->{'displayGroup'};
	}

	my ($canCacheCredentials) = $dbr->query( sql=> " select can_Cache_credentials from user_share_credential_caching where user_id = $userId " );
	$canCacheCredentials = 1 unless defined $canCacheCredentials; 

	# here's a special hack that raz put in... the idea is this:  with the new policy to allow or disallow
	# credential caching, people without caching turned on can click on recently used links and they will not
	# have usernames or passwords assigned to those shares.  This means that if they previously used a share
	# with login creds and then go back via the recent list, they are trying to auth without any creds initially...
	# this is normally fine but if the remote server allows anonymous logins they are going to get in without the right
	# permissions.  To work around this, I have made the system return the connection error / login page screen before trying
	# anything if the user has no password and username.... and only if it doesn't see the submit from the password prompt.
	# This allows the user to enter an initial credential set for this use case.

	# Format currentDir
	$currentDir = '\\' unless $currentDir;

	# Remove duplicate backslashes
	while ( $currentDir =~ s/\\\\/\\/g ) { }

	# OBSOLETE
	#my $currentDirEsc = $currentDir;
	#$currentDirEsc =~ s/\\/\\\\/g;

	#cut off trailing slashes
	chop($currentDir) if ($currentDir =~ /^(.+)\\$/);
	chop($initialFolder) if ($initialFolder =~ /^(.+)\\$/);

	# Set the upDir and upDirEsc strings
	my $upDir = undef;
	
	if ($currentDir eq "\\" . $initialFolder)
	{
		$upDir = undef;
	}
	elsif ( $currentDir =~ /^(.*)\\[^\\].*$/ )
	{  
		$upDir = $1;
	}
	elsif ( $currentDir ne "\\" && $currentDir ne "" )
   {  
		$upDir = "\\";
	}

	my $upDirEsc = $upDir;
	if ( defined $upDir ) { $upDirEsc =~ s/\\/\\\\/g; }

	# Page header
	my $page = $toolBox->getPage();
	$page->addNavBarElement(
		description => "\\\\$serverName\\$shareName",
		values => [
			{ name => "act", value => "browseShareDir" },
			{ name => "currentDir", value => ($upDir) }
		]
	);

	my $path = $dbr->escapeLike(dirty => "\\\\$serverName\\$shareName");

	# this block now runs to 'try auth cache for share' no matter what (this MUST happen before the page header so the 
	# block of code can replace a persisted value and have it correctly in the page header 
	if ( ! $usedUserValues )
	{
		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->getShareBrowserHtml() Looking for auth credentials in netresource auth cache" );

		my (@authCacheRows) = $dbr->query( sql => "
			SELECT
				net_user, net_pass
			FROM
				netresource_auth_cache
			WHERE
				network_path like '$path%'
				AND user_id = '$userId'
				" . $dbr->getLimitOneRowString(prefix => 'AND' ) . "
		");

		foreach my $authCacheRow (@authCacheRows) 
		{
			my $username = $authCacheRow->[0];
			my $password = $authCacheRow->[1];

			if ( ( defined $username && length $username ) || ( defined $password && length $password ) )
			{
				$password = "" unless defined $password;
				$username = "" unless defined $username;

				$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->getShareBrowserHtml() Got cached username '$username' and password HIDDEN; resetting persisted values" );

				#$client->setPersistedValue( name => 'shareUserName', value => $username );
				#$client->setPersistedValue( name => 'sharePassword', value => $password );
				$shareUserName = $username;
				$sharePassword = $password;
				
				# the init share function will use the new share number here to setup the share...
				# they will be retrieved via the getPersistedValue function
 				$self->storeShareInfo( user=> $shareUserName, password=> $sharePassword, share=> $shareName, server=> $serverName );
 				$client->setPersistedValue( name=> 'shareId', value=> $self->{'storedShareId'} ); 
 				$client->setPersistedValue( name=> 'displayGroup', value=> $self->{'displayGroup'} ); 
 				$dbw->commit(); # make sure stored share info is saved
				# we have to override the stored share id and display group for the init function to work right
				$self->{'initShareId'} = $self->{'storedShareId'};
				$self->{'initDisplayGroup'} = $self->{'displayGroup'};

				# Indicate that credentials were obtained through the cache.
				$fromCache = 1;
			}
		}
	}

	# If the credentials weren't obtained through the cache and 
	# they have an empty password, prompt them to enter it.
	if (!$fromCache and 
		not ( defined($shareUserName) and length($shareUserName) and defined($sharePassword) and length($sharePassword) )
		and defined $cgi->param('isRecent')
	)
	{
		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->getShareBrowserHtml() detected user with no password caching ability -- giving a chance to provide credentials." );
		return $self->getShareConnectErrorHtml( initialTry=> 1, admintool=> $admintool );
	}


	my $ret .= $page->getHeaderHtml( title => "File Share Browser: Connected");
	#$ret .= Pos::Webtop::OcxLauncher::getLauncherHtml( tb=> $toolBox );

# TODO: Clickable folders in link on 
#	my $titleHtml = "File Share Browser: \\" . 
#		( $client->getFormGetCode(
#			text => "$sharePath",
#			values => [
#				{ name => "act", value => "browseShareDir" },
#				{ name => "currentDir", value => ($upDir) }
#			])
#		) . "";

#	my $browseBox = $toolBox->createBox(
#		title => "File Share Browser: \\\\$serverName\\$shareName$currentDir",
#		width => "100%"
#	);

my $currentDirCopy = $currentDir;
my $initialFolderCopy = $initialFolder;
$initialFolderCopy =~ s/\\/\\\\/g;
$currentDirCopy =~ s/^\\$initialFolderCopy//g;
$initialFolderCopy =~ s/\\\\/\\/g;

my @initialFolders = split /\\/,$initialFolderCopy;
my @currentDirs = split /\\/,$currentDirCopy;

my $cleanShareName = $shareName;
$cleanShareName =~ s/\\//g;

my $shareLink = 
						$client->getFormGetCode(
						      	text => ("$cleanShareName",
									tagAttributes => [
										{ name => "class", value => "BOXLINK"}, 
									],
						         values => [
										{ name => "act", value => "browseShareDir" },
										{ name => "initialFolder", value => $initialFolder },
										{ name => "currentDir", value => "\\" }
									]));

my $dow = "";
my $dizzle = "";

my $i = 1;

foreach my $initialFolders ( @initialFolders )
{
	if (!length($initialFolders))
	{
		next;
	}

	$dizzle .= "\\$initialFolders";
	$dizzle =~ s/\\\\/\\/g;

	if ($i == scalar(@initialFolders))
	{
		$dow .= "\\";
		$dow .= 
						$client->getFormGetCode(
									text => ("$initialFolders",
									tagAttributes => [
										{ name => "class", value => "BOXLINK"}, 
									],
									values => [
										{ name => "act", value => "browseShareDir" },
										{ name => "initialFolder", value => $initialFolder },
										{ name => "currentDir", value => "$dizzle" }
									]));
	}
	else
	{
		$dow .= "\\$initialFolders";
	}

	$i++;
}

my $dow2 = "";
my $dizzle2 = "";

foreach my $currentDirs ( @currentDirs )
{
	if (!length($currentDirs))
	{
		next;
	}

	$dizzle2 .= "\\$currentDirs";
	$dizzle2 =~ s/\\\\/\\/g;

	$dow2 .= "\\";
	$dow2 .= 
					$client->getFormGetCode(
								text => ("$currentDirs",
								tagAttributes => [
									{ name => "class", value => "BOXLINK"}, 
								],
								values => [
									{ name => "act", value => "browseShareDir" },
									{ name => "initialFolder", value => $initialFolder },
									{ name => "currentDir", value => "\\$initialFolder$dizzle2" }
								]));
}

my $browseBox = $toolBox->createBox(
	title=>"File Share Browser: \\\\$serverName\\" . (length($dow) ? "$cleanShareName$dow" : "$shareLink" ) . "" . (length($dow2) ? "$dow2" : "" ) . "", 
	width => "100%"
	);

	# JS, Form and table header
	$ret .= "
		<script language='JavaScript' version='1.3'>
			var isRenaming = 0;
			var isDeleting = 0;
			var isCutting = 0;
			var isCreatingDir = 0;
			var isUpload = 0;

			function setRename() {
				isRenaming = 1;
			}

			function setDelete() {
				isDeleting = 1;
			}

			function setCut() {
				isCutting = 1;
			}

			function setCreateDir() {
				isCreatingDir = 1;
			}

			function setUpload() {
				isUpload = 1;
			}
			
			function checkForm( form ) {
				if (isRenaming == 1) {
					var selectedItem;
					var selectedItemCount = 0;

					form.act.value = \"browseShareDir\";

					for (i=0, n=form.selectedFiles.length; i<n; i++) {
						if (form.selectedFiles[i].checked) {
							selectedItemCount++;
							selectedItem = form.selectedFiles[i].value;
						}
					}

					for (i=0, n=form.selectedFolders.length; i<n; i++) {
						if (form.selectedFolders[i].checked) {
							selectedItemCount++;
							selectedItem = form.selectedFolders[i].value;
						}
					}

					if (selectedItemCount > 1) {
						alert(\"Error: You may only select one file or folder to rename at a time.\");
						isRenaming = 0;
						return false;
					}

					if (selectedItemCount < 1) {
						alert(\"Error: You must select a file or folder to rename.\");
						isRenaming = 0;
						return false;
					}

					var newItemName = prompt(\"Enter the new name for '\" + selectedItem + \"':\",selectedItem);

					if (newItemName == null) {
						isRenaming = 0;
						return false;
					}
					else if (newItemName == \"\" || newItemName == \"undefined\") {
						alert(\"Error: You entered an invalid name.\");
						return checkForm(form);
					}
					else if (newItemName == selectedItem) {
						alert(\"Error: You entered the same name.\");
						return checkForm(form);
					}
					else {
						for (i=0, n=form.selectedFiles.length; i<n; i++) {
							if (form.selectedFiles[i].value == newItemName) {
								alert(\"A file with the name you specified already exists. Specify a different name.\"); 
								return checkForm(form);
							}
						}

						for (i=0, n=form.selectedFolders.length; i<n; i++) {
							if (form.selectedFolders[i].value == newItemName) {
								alert(\"A folder with the name you specified already exists. Specify a different name.\");
								return checkForm(form);
							}
						}

						isRenaming = 0;
						form.selectedItem.value = selectedItem;
						form.newItemName.value = newItemName;
						form.act.value = \"Rename\";
						return true;
					}
				}

				if (isDeleting == 1) {
					var selectedItem;
					var selectedItemCount = 0;
					isDeleting = 0;

					form.act.value = \"browseShareDir\";

					for (i=0, n=form.selectedFiles.length; i<n; i++) {
						if (form.selectedFiles[i].checked) {
							selectedItemCount++;
						}
					}

					for (i=0, n=form.selectedFolders.length; i<n; i++) {
						if (form.selectedFolders[i].checked) {
							selectedItemCount++;
						}
					}

				if (selectedItemCount < 1) {
						alert(\"Error: You must select a file or folder to delete.\");
						return false;
					}

					var confirmMsg;
					if (selectedItemCount > 1) {
						confirmMsg = \"Are you sure you want to permanently delete these \" + selectedItemCount + \" items?\";
					}
					else {
						confirmMsg = \"Are you sure you want to permanently delete this item?\";
					}

					if ( confirm(confirmMsg) ) {
						form.act.value = \"Delete\";
						return true;
					}
					else {
						return false;
					}
				}

				if (isCutting == 1) {
					var selectedItem;
					var selectedItemCount = 0;
					isCutting = 0;

					form.act.value = \"browseShareDir\";

					for (i=0, n=form.selectedFiles.length; i<n; i++) {
						if (form.selectedFiles[i].checked) {
							selectedItemCount++;
						}
					}

					for (i=0, n=form.selectedFolders.length; i<n; i++) {
						if (form.selectedFolders[i].checked) {
							selectedItemCount++;
						}
					}

			//		if (selectedItemCount < 1) {
			//			alert(\"Error: You must select a file or folder to cut.\");
			//			isDeleting = 0;
			//			return false;
			//		}

					form.act.value = \"Delete\";
					return true;
				}

				if (isCreatingDir == 1) {
					form.act.value = \"browseShareDir\";

					var newItemName = prompt(\"Enter the name of the folder you want to create:\",\"\");

					if (newItemName == null) {
						isCreatingDir = 0;
						return false;
					}
					else if (newItemName == \"\" || newItemName == \"undefined\") {
						alert(\"Error: You entered an invalid name.\");
						return checkForm(form);
					}
					else {
						for (i=0, n=form.selectedFiles.length; i<n; i++) {
							if (form.selectedFiles[i].value == newItemName) {
								alert(\"A file with the name you specified already exists. Specify a different name.\"); 
								return checkForm(form);
							}
						}

						for (i=0, n=form.selectedFolders.length; i<n; i++) {
							if (form.selectedFolders[i].value == newItemName) {
								alert(\"A folder with the name you specified already exists. Specify a different name.\");
								return checkForm(form);
							}
						}

						isCreatingDir = 0;
						form.newItemName.value = newItemName;
						form.act.value = \"Create Folder\";
						return true;
					}
				}

				if (isUpload == 1) {
					form.act.value = \"browseShareDir\";

					var newItemPath = document.getElementById('uploadFile').value;

					var regex = /.*[\\\\\\/]/;

					var newItemName = String(newItemPath).replace(regex, '');	

					if (newItemName == null || newItemName == '') {
						isUpload = 0;
						return false;
					}
						
					for (i=0, n=form.selectedFiles.length; i<n; i++) {
						if (form.selectedFiles[i].value == newItemName) {
							if (confirm(\"This folder already contains a file named '\" + newItemName + \"'. Would you like to replace the existing file?\"))
							{
								isUpload = 0;
								form.overwriteFile.value = 1;
								form.act.value = \"Upload File\";
								return true;

							}
							else
							{
								isUpload = 0;
								return false;
							}
						}
					}

					
				}

				return true;
			}
		</script>
	";

	$ret .= "
		<form id='formBrowseShare' method='POST' enctype='multipart/form-data' onSubmit='return checkForm(this)'>
		<input id='inputNewItemName' type='hidden' name='newItemName' value=''>
		<input type='hidden' name='overwriteFile'>
		<input type='hidden' name='selectedItem'>
		<input type='hidden' name='selectedFiles'>
		<input type='hidden' name='selectedFolders'>
		<input type='hidden' name='initialFolder' value=\"" . HTML::Entities::encode($initialFolder) . "\">
		<input type='hidden' name='currentDir' value=\"" . HTML::Entities::encode($currentDir) . "\">
		" . ( $client->getFormInputs ) . $browseBox->getHeaderHtml() . "
		<table border='0' cellspacing='1' cellpadding='1' width='100%'>
			<tr>
				<th colspan='3'>
		" . ( $isMobile ? "" : "
					<table cellpadding='0' cellspacing='0' border='0' width='100%'>
						<tr>
							<td align='left' class='BOXTDFILEMENU'>
								<nobr>
								<input type='submit' name='act' value='Refresh' class='BOXTDFILEMENUINPUT'>&nbsp;&nbsp;
								<input type='submit' name='act' value='Delete' class='BOXTDFILEMENUINPUT' onClick='setDelete()'>&nbsp;&nbsp;
								<input type='submit' name='act' value='Create Folder' class='BOXTDFILEMENUINPUT' onClick='setCreateDir()'>&nbsp;&nbsp;
								<input type='submit' name='act' value='Cut' class='BOXTDFILEMENUINPUT' onClick='setCut()'>&nbsp;&nbsp;
								<input type='submit' name='act' value='Copy' class='BOXTDFILEMENUINPUT'>&nbsp;&nbsp;
								<input type='submit' name='act' value='Paste' class='BOXTDFILEMENUINPUT'>&nbsp;&nbsp;
								<input type='submit' name='act' value='Rename' class='BOXTDFILEMENUINPUT' onClick='setRename()'>&nbsp;&nbsp;
								<wbr>
								Filename: <input type='file' id='uploadFile' name='uploadFile' class='BOXTDFILEMENUUPLOAD'>&nbsp;&nbsp;
								<input type='submit' name='act' value='Upload File' class='BOXTDFILEMENUINPUT' onClick='setUpload()'>
								</nobr>
							</td>
						</tr>
					</table>" ) . "
				</th>
			</tr>
			<tr>
				<td class='BOXTH'>
					<table width='100%'>
						<tr>
							<td class='BOXTH'>
								<font class='DEF'>Name</font>
							</td>
							<td align='center' class='BOXTDFILEMENU' style='background-color: #dfdfdf;'>
								To download a file, right-click it and select \"Save Target As...\"&nbsp;&nbsp;
							</td>
						</tr>
					</table>
				</td>
				<td class='BOXTH'>
					<font class='DEF'>Size</font>
				</td>
				" . ($isMobile ? "" : " 
				<td class='BOXTH'>
					<font class='DEF'>Date Modified</font>
				</td>" ) . "
			</tr>
	";
	
	# If no username was entered, try to get username/password from netresource auth cache
	#if ( ( not defined $shareUserName ) || ( $shareUserName eq "" ) )
	#if ( ( not defined $shareUserName ) || ( $shareUserName eq "" ) || ( not defined $shareUserName ) || ( $shareUserName eq "" ) )

	# Initialize smbShare object
	if ( !$self->initSmbShare() )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->getShareBrowserHtml() couldn't initialize Smb object; returning error text" );
		if($admintool eq '1')
		{
			return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to connect to file share", currentDir => $currentDir, initialFolder => $initialFolder  );
		}
		else
		{
			return $self->getShareConnectErrorHtml( admintool => 0 );
		}
	}

	my $share = $self->{'smbShare'};

	if ( not $self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir, protocol=> "FILE" ) )
	{
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Get directory listing ( { itemType => 'file', itemName= 'dog.txt' }, ... )
	my $dirListing = $share->getDirListing( dirPath => $currentDir, getExtendedInfo => 1 );

	if ( not defined $dirListing)
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->getShareBrowserHtml() couldn't get directory listing for '$currentDir'; returning error text" );
		#return $self->getShareConnectErrorHtml( admintool => $admintool );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to get directory listing (you may not have access to this folder)" );
	}

	# If this was a custom drive map, add it to the webtop fileshare database
	# TODO: Update the 'recently used file shares' table, as well
	# (Note: We wouldn't want to do this if we hadn't gotten a directory listing)
	my $fileShareId;
	if ( defined $fileShareId) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->getShareBrowserHtml() \$fileShareId has shit in it" ); }

	# This is to prevent adding duplicate drive shares
	my ($testUserId) = $dbr->query( sql => "
					select
						user_id
					from
						client_drive_shares
					where
						user_id = '$userId'
						and remote_addr = '" . $dbr->escape( dirty => "\\\\$serverName\\$shareName" . (defined($currentDir) && $currentDir ne "\\" ? "$currentDir" : "" ) . "" ) . "'
					");
					
	my $customerId = $client->getCustomerId();

	if ( defined $cgi->param('share_login_submit') && $cgi->param('share_login_submit') eq 'Connect' && not defined($testUserId))
	{
		# Check for existing fileserver entry in db for this customer
		my ($fileServerId) = $dbr->query( sql => "
			SELECT
				fileserver_id
			FROM
				w_smb_fileservers
			WHERE
				customer_id = '$customerId'
				AND fileserver_hostname = '" . $dbr->escape( dirty => $serverName ) . "'
		");

		# If there isn't one with the same hostname already, create one
		if ( not defined $fileServerId ) {
			# Get the next fileserver_id sequence
			($fileServerId) = $dbr->getNextSequenceValue(sequence => "w_smb_fileservers_pk");

			# Add the new fileserver
			$dbw->do( sql => "
				INSERT INTO
					w_smb_fileservers
					(fileserver_id, customer_id, fileserver_hostname, display_name)
				VALUES
					('$fileServerId', '$customerId', '" . $dbr->escape( dirty => $serverName ) . "',
					 '". $dbr->escape( dirty => $serverName ) . "')
			");
		}

		# Check for existing fileshare entry in db for this fileserver
		($fileShareId) = $dbr->query( sql => "
			SELECT
				fileshare_id
			FROM
				w_smb_fileshares
			WHERE
				fileserver_id = '$fileServerId'
				AND fileshare_name = '" . $dbr->escape( dirty => "$shareName" . (defined($currentDir) && $currentDir ne "\\" ? "$currentDir" : "" ) . "" ) . "'
		");

		# If there isn't a fileshare with the same sharename already, create one
		if ( not (defined $fileShareId && $fileShareId) )
		{
			# Get the next available fileshare_id (or 0 if none)
			($fileShareId) = $dbw->getNextSequenceValue( sequence => "w_smb_fileshares_pk");

			# Add the new fileshare
			$dbw->do( sql => "
				INSERT INTO
					w_smb_fileshares
					(fileshare_id, fileserver_id, fileshare_name)
				VALUES
					('$fileShareId', '$fileServerId', '" . $dbr->escape( dirty => "$shareName" . (defined($currentDir) && $currentDir ne "\\" ? "$currentDir" : "" ) . "" ) . "')
			");

			# Link the newly-created fileshare to the user
			$dbw->do( sql => "
				INSERT INTO
					w_smb_fileshare_users
					(fileshare_id, user_id)
				VALUES
					('$fileShareId', '$userId')
			");
		}

		# In all cases, we need to make sure the user is linked to this fileserver
		# See if there's already an entry in the db linking the fileshare to this user
		my ($areUserShareLinked) = $dbw->query( sql => "
			SELECT
				count(fileshare_id)
			FROM
				w_smb_fileshare_users
			WHERE
				user_id = '$userId'
				AND fileshare_id = '$fileShareId'
		");

		# If the user isn't linked to the fileshare...
		if ( not ( defined $areUserShareLinked && $areUserShareLinked ) )
		{
			# Link the existing fileshare to the user
			$dbw->do( sql => "
				INSERT INTO
					w_smb_fileshare_users
					(fileshare_id, user_id)
				VALUES
					('$fileShareId', '$userId')
			");
		}

		# Check for an existing entry in the webtop fileshare auth cache
		my ($authCached) = $dbw->query( sql => "
			SELECT
				count(fileshare_id)
			FROM
				w_smb_fileshare_auth_cache
			WHERE
				user_id = '$userId'
				AND fileshare_id = '$fileShareId'
		");

		# If there are no auth credentials cached...
		if ( !$authCached and $canCacheCredentials and defined($shareUserName) and length($shareUserName) )
		{
			my $sessionId = $client->getSessionId();
			# Cache the ones being used (they seem to have worked)
			# Note: insert a web_session_id to cache them for this session only
			$dbw->do( sql => "
				INSERT INTO
					w_smb_fileshare_auth_cache
					(client_session_id, user_id, fileshare_id, user_name, password)
				VALUES
					('$sessionId', '$userId', '$fileShareId',
					 '" . $dbr->escape( dirty => $shareUserName ) . "',
					 '" . $dbr->escape( dirty => $sharePassword ) . "')
			");
		}
	}

	# Get a fileShareId, if there is one
	($fileShareId) = $dbw->query( sql => "
		SELECT
			fsh.fileshare_id
		FROM
			w_smb_fileshares fsh, w_smb_fileservers fsv, w_smb_fileshare_users fsu
		WHERE
			fsh.fileshare_name = '" .  $dbr->escape( dirty => $shareName ) . "'
			AND fsh.fileserver_id = fsv.fileserver_id
			AND fsv.fileserver_hostname = '" . $dbr->escape( dirty => $serverName ) . "'
			AND fsv.customer_id = '$customerId'
			AND fsu.user_id = '$userId'
			AND fsu.fileshare_id = fsh.fileshare_id
	");

	# Update the last access date so we can sort by that in the preview pane
	# Only if this is a fileshare created from the webtop should we attempt to update the webtop tables
	if ( defined $fileShareId )
	{
		$client->logger( severity=>'NORMAL', logMessage=>'WEBTOP Fileshares: ->getShareBrowserHtml() Updating last access date for webtop fileshare');

		$dbw->do( sql => "
			UPDATE
				w_smb_fileshare_users
			SET
				last_access_date = " . $dbw->getCurrentDateString() . "
			WHERE
				fileshare_id = '$fileShareId'
				AND user_id = '$userId'
		");
	}
#	else
#	{
		$client->logger( severity=>'NORMAL', logMessage=>'WEBTOP Fileshares: ->getShareBrowserHtml() Updating last access date for vpn fileshare' );

		# TODO: Only update the drive share they're actually using from client_drive_shares
		$dbw->do( sql => "
			UPDATE
				client_drive_shares
			SET
				last_access_date = " . $dbw->getCurrentDateString() . "
			WHERE
				user_id = '$userId'
				AND restrict_set_id = " . $client->getRestrictionSetId . "
				AND remote_addr LIKE '$path%'
		");
#	}

	# Store (potentially) updated auth credentials in netresource_auth_cache
	# TODO: Don't break multiple folders with different auth credentials (DFS, etc.)

	# First, see if there are any auth credentials cached
	my ($isAuthCached) = $dbw->query( sql => "
		SELECT
			count(user_id)
		FROM
			netresource_auth_cache
		WHERE
			network_path like '$path%'
			AND user_id = '$userId'
	");

	$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->getShareBrowserHtml() ISAUTHCACHED: $isAuthCached SERVERNAME: " . $serverName . " SHARE: " . $shareName . "");
	# If there's already an entry, just update it
	if ( $isAuthCached )
	{
		$dbw->do( sql => "
			UPDATE
				netresource_auth_cache
			SET
				net_user = '" . $dbr->escape( dirty => $shareUserName )  . "',
				net_pass = '" . $dbr->escape( dirty => $sharePassword ) . "'
			WHERE
				network_path like '$path%'
				AND user_id = '$userId'
		");
	}
	# Otherwise, create a new one
	elsif ( $canCacheCredentials )
	{
		$dbw->do( sql => "
			INSERT INTO
				netresource_auth_cache
				(user_id, network_path, net_user, net_pass)
			VALUES
				('$userId','\\\\" . ( Pos::defaults::isPostgresDb() ? "\\\\" : "") . "" . $dbr->escape( dirty => $serverName ) .
				 "\\" . (Pos::defaults::isPostgresDb() ? "\\" : "") . "" . $dbr->escape( dirty => $shareName ) . "',
				 '" . $dbr->escape( dirty => $shareUserName )  . "',
				 '" . $dbr->escape( dirty => $sharePassword ) . "')
		");
	}

	# Sort the directory listing (first by name, then by type)
	my @sortedDirListing = 
		sort { ($a->{'itemType'} . uc($a->{'itemName'})) cmp ($b->{'itemType'} . uc($b->{'itemName'})) } @{$dirListing};

	# List all items in the directory

	# First, a link to upDir
	if ( defined $upDir )
	{
		$ret .= "
			<tr>
				<td class='BOXTD1'>
						" . ( $client->getFormGetCode(
						      	text => "<span style='text-decoration: none'><img src='LocalWebLib/fileParentDirIcon.gif' border='0'>&nbsp;Parent Directory</span>",
									tagAttributes => [
										{ name => "class", value => "BOXLINK" }
									],
						         values => [
										{ name => "act", value => "browseShareDir" },
										{ name => "initialFolder", value => $initialFolder },
										{ name => "currentDir", value => ($upDir) }
									])) . "
				</td>
				<td class='BOXTD2'>
					&nbsp;
				</td>
				<td class='BOXTD1'>
					&nbsp;
				</td>
			</tr>
		";
	}
	
	# Used for converting the string-like time that is returned by Smb.pm
	my %months = (
		"Jan" => 0,
		"Feb" => 1,
		"Mar" => 2,
		"Apr" => 3,
		"May" => 4,
		"Jun" => 5,
		"Jul" => 6,
		"Aug" => 7,
		"Sep" => 8,
		"Oct" => 9,
		"Nov" => 10,
		"Dec" => 11
	);

	# Then, list each subdirectory
	foreach my $dirItem (@sortedDirListing)
	{
		if ( not defined $dirItem->{'itemType'} ) { next; }

		if ( $dirItem->{'itemType'} eq "directory" )
		{
			# Create printable time
			my ($sec,$min,$hour,$mday,$mon,$year) = split(/\s/,$dirItem->{'itemMtime'});

			my $modTime = ( $mon + 1 ) . "/$mday/" . ( $year ) . " " . ( $hour > 12 ? (24 - $hour) : $hour ) . ":$min " . ( $hour > 12 ? "PM" : "AM" );

			$ret .= "
				<tr>
					<td class='BOXTD1' width='100%'>
					" . ($isMobile ? "\&nbsp;\&nbsp;" : "
						<input type='checkbox' name='selectedFolders' value=\"" . HTML::Entities::encode( ( $dirItem->{'itemName'} ) ) . "\">" ) . "
						" . ( $client->getFormGetCode(
						      	text => ("<span style='text-decoration: none'><img src='LocalWebLib/fileFolderIcon.gif' border='0'>&nbsp;" . $dirItem->{'itemName'}) . "</span>",
									tagAttributes => [
										{ name => "class", value => "BOXLINK" }
									],
						         values => [
										{ name => "act", value => "browseShareDir" },
										{ name => "initialFolder", value => HTML::Entities::encode(( "" . ( defined $initialFolder ? $initialFolder : "" ) )) },
										{ name => "currentDir", value => ("$currentDir\\" . $dirItem->{'itemName'}) }
									] ) ) . "
					</td>
					<td class='BOXTD2'>
						&nbsp;
					</td>
					" . ($isMobile ? "" : " 
					<td class='BOXTD1'>
						<nobr>$modTime</nobr>
					</td>" ) . "
				</tr>
			";
		}
		# Finally, list each file
		elsif ( $dirItem->{'itemType'} eq "file" )
		{
			# Create printable time
			my ($sec,$min,$hour,$mday,$mon,$year) = split(/\s/,$dirItem->{'itemMtime'});
			my $modTime = ( $mon + 1 ) . "/$mday/" . ( $year ) . " " . ( $hour > 12 ? (24 - $hour) : $hour ) . ":$min " . ( $hour > 12 ? "PM" : "AM" );

			# Create printable filesize (in kb)
			my $rfileSizeKb = reverse (sprintf("%d", ( $dirItem->{'itemBytes'} / 1024 )));
			$rfileSizeKb =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
			my $fileSizeKb = reverse $rfileSizeKb;

			# DEPRECATED: Display MB
			# <nobr>" . ( $fileSizeKb < 1024 ? "$fileSizeKb KB" : "$fileSizeMb MB" ) . "</nobr>
			$ret .= "
				<tr>
					<td class='BOXTD1' width='100%'>
					" . ($isMobile ? "\&nbsp;\&nbsp;" : "
						<input type='checkbox' name='selectedFiles' value=\"" . HTML::Entities::encode( ( $dirItem->{'itemName'} ) ) . "\">" ) . "
						" . ( $client->getFormGetCode(
						      	text => ("<span style='text-decoration: none'><img src='LocalWebLib/fileFileIcon.gif' border='0'>&nbsp;" . $dirItem->{'itemName'}) . "</span>",
									tagAttributes => [
										{ name => "class", value => "BOXLINK"}, 
										{ name => "target", value => "_blank"}
									],
						         values => [
										{ name => "act", value => "downloadFile" },
										{ name => "currentDir", value => ($currentDir) },
										{ name => "downloadFilename", value => ($dirItem->{'itemName'}) }
									])) . "
					</td>
					<td class='BOXTD2' align=right>
						<nobr>$fileSizeKb KB</nobr>
					</td>
					" . ($isMobile ? "" : "
					<td class='BOXTD1'>
						<nobr>$modTime</nobr>
					</td>" ) . "
				</tr>
			";
		}
	}

	$ret .= "<tr>
					<th colspan='3'>
					" . ($isMobile ? "" : "
						<table cellpadding='0' cellspacing='0' border='0' width='100%'>
							<tr>
								<td align='left' class='BOXTDFILEMENU'>
									<nobr>
										<input type='submit' name='act' value='Refresh' class='BOXTDFILEMENUINPUT'>&nbsp;&nbsp;
										<input type='submit' name='act' value='Delete' class='BOXTDFILEMENUINPUT' onClick='setDelete()'>&nbsp;&nbsp;
										<input type='submit' name='act' value='Create Folder' class='BOXTDFILEMENUINPUT' onClick='setCreateDir()'>&nbsp;&nbsp;
										<input type='submit' name='act' value='Cut' class='BOXTDFILEMENUINPUT' onClick='setCut()'>&nbsp;&nbsp;
										<input type='submit' name='act' value='Copy' class='BOXTDFILEMENUINPUT'>&nbsp;&nbsp;
										<input type='submit' name='act' value='Paste' class='BOXTDFILEMENUINPUT'>&nbsp;&nbsp;
										<input type='submit' name='act' value='Rename' class='BOXTDFILEMENUINPUT' onClick='setRename()'>&nbsp;&nbsp;
									</nobr>
								</td>
							</tr>
						</table>" ) . "
					</th>
				</tr>
		";

	# Form and table footer
	$dbr->commit();
	$dbw->commit();

	$ret .= "</form>\n</table>\n" . ( $browseBox->getFooterHtml() ); 
	$ret .= $page->getFooterHtml();

	return $ret;
}

# Receieve the file, then display the browse page
# This is a multi-part form post containing the file upload
sub uploadFile
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();

	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');
	my $uploadFilename = $cgi->param('uploadFile');
	my $uploadFileHandle = $cgi->upload('uploadFile');
	my $overwriteFile = $cgi->param('overwriteFile');

	# Make sure we have all of the form parameters
	if ( not ( defined $currentDir && $currentDir ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->uploadFile() Can't get current directory to upload the file" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Your web browser was unable to upload the file", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	if ( not ( defined $uploadFileHandle && $uploadFileHandle ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->uploadFile() uploadFileHandle ($uploadFileHandle) was empty for file '$uploadFilename'" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Your web browser was unable to upload the file", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	if ( not ( defined $uploadFilename && $uploadFilename ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->uploadFile() didn't get filename for upload to '$currentDir'" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "An invalid filename was specified for upload", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	if ( $uploadFilename =~ s/.*[\\\/]+(.*)$/$1/ )
	{
		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->uploadFile() removed slashes from filename; result: '$uploadFilename'" );
	}

	# Initialize smbShare object
	if ( !$self->initSmbShare() )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->uploadFile() couldn't initialize Smb object to upload file; returning error msg" );	
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to connect to the file share", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
	my $share = $self->{'smbShare'};
	my $shareName = $share->getRoot();

	if
	(
		not
		(
			$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $uploadFilename, protocol=> "FILE" )
			and
			$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir, protocol=> "FILE" )
		)
	)
	{
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Upload the file; once that's done, display the browse page
	$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->uploadFile() uploadFileHandle: $uploadFileHandle" );

	my $dbh = $toolBox->getWebtopDbWriter()->{'dbh'};
	my $oldAutoCommit = $dbh->{'AutoCommit'};
	$dbh->{'AutoCommit'} = 1;
	$dbh->commit();
	
	my $result = $share->putFile( dirPath => $currentDir, filename => $uploadFilename, uploadFileHandle => $uploadFileHandle, overwriteFile => $overwriteFile );

	$dbh->{'AutoCommit'} = $oldAutoCommit;
	$dbh->commit();

	if ( $result )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->uploadFile() file upload was successful; returning skillz" );
		my $ret .= $self->getShareBrowseHtml();

		return $ret;
	}
	else
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->uploadFile() file upload failed; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "File upload failed. You may not have access to upload files to this folder or there may already be a file named \"$uploadFilename\" in this folder.", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
}

# Create new folder
sub createFolder
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();

	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');

	# Remove trailing \ from currentDir
	chop($currentDir) if ($currentDir =~ /\\$/);

	# They need to enter something into the javascript pop-up asking for a new filename/foldername
	my $newItemName = $cgi->param('newItemName');

	if ( not ( defined $newItemName && $newItemName ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->createFolder() Didn't get new name to create folder; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "The name of the new folder is invalid. Please try again.", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Initialize smbShare object
	if ( !$self->initSmbShare() )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->createFolder() couldn't initialize Smb object to create folder; returning error msg" );	
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to connect to the file share", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
	my $share = $self->{'smbShare'};
	my $shareName = $share->getRoot();

	if
	(
		not 
		(
			$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $newItemName, protocol=> "FILE" )
			and
			$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir, protocol=> "FILE" )
		)
	)
	{
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
	
	# Rename the file or folder
	# TODO: Visual indicator of rename success or failure
	if ( $share->createFolder( dirPath => $currentDir, folderName => $newItemName ) )
	{
		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->createFolder() folder creation succeeded: $currentDir\\$newItemName" );
	}
	else
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: folder creation failed: $currentDir\\$newItemName" );
	}

	my $ret .= $self->getShareBrowseHtml();

	return $ret;
}

# Rename selected file or folder using Pos::Webtop::FileShare::Smb::moveItem
sub renameItems
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();

	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');

	# Make sure that the user selected stuff, in case javascript didn't catch it
	my ($selectedItem,$newItemName);
	if (defined $selectedItem || defined $newItemName)
	{ 
		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->renameItems()\$selectedItem or \$newItemName have shit in them" ); 
	}

	if ( defined $cgi->param('selectedItem') )
	{
		$selectedItem = $cgi->param('selectedItem');
	}

	if ( not ( defined $selectedItem && $selectedItem ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->renameItems(): No item was selected; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "No item was selected for renaming", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# They need to enter something into the javascript pop-up asking for a new filename/foldername
	if ( defined $cgi->param('newItemName') )
	{
		$newItemName = $cgi->param('newItemName');
	}

	if ( not ( defined $newItemName && $newItemName ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->renameItems(): Didn't get new name for file/folder; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "The new name for the selected item was not specified", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Initialize smbShare object
	if ( !$self->initSmbShare() )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->renameItems(): couldn't initialize Smb object; returning error msg" );	
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to connect to the file share", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
	my $share = $self->{'smbShare'};
	my $shareName = $share->getRoot();

	if
	(
		not 
		(
			$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $selectedItem, protocol=> "FILE" )
			and
			$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir, protocol=> "FILE" )
			and
			$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $newItemName, protocol=> "FILE" )
		)
	)
	{
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
	
	# Rename the file or folder
	# TODO: Visual indicator of rename success or failure
	if ( $share->moveItem( dirPath => $currentDir, itemName => $selectedItem, newDirPath => $currentDir, newItemName => $newItemName ) )
	{
		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->renameItems(): item rename succeeded: $currentDir\\$selectedItem -> $newItemName" );
	}
	else
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->renameItems(): item rename failed: $currentDir\\$selectedItem -> $newItemName" );
	}

	my $ret .= $self->getShareBrowseHtml();

	return $ret;
}

# Delete selected files and/or folders
sub deleteItems
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();

	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');

	# Make sure that the user selected stuff, in case javascript didn't catch it
	my (@selectedFiles,@selectedFolders);

	@selectedFiles = $cgi->param('selectedFiles');
	@selectedFolders = $cgi->param('selectedFolders');
	
	if ( !$#selectedFiles && !$#selectedFolders )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->deleteItems(): No items were selected; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "No items were selected for deletion", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Initialize smbShare object
	if ( !$self->initSmbShare() )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Filshares: ->deleteItems(): couldn't initialize Smb object; returning error msg" );	
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to connect to the file share", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
	my $share = $self->{'smbShare'};
	my $shareName = $share->getRoot();

	if ( not $self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir, protocol=> "FILE" ) )
	{
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	foreach my $file ( @selectedFiles )
	{
		next unless defined $file and length $file;

		if ( not $self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $file, protocol=> "FILE" ) )
		{
			return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource ($file) is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
		}
	}

	foreach my $folder ( @selectedFolders )
	{
		next unless defined $folder and length $folder;

		if ( not $self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $folder, protocol=> "FILE" ) )
		{
			return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource ($folder) is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
		}
	}
	
	# TODO: Visual indicator of delete success or failure
	foreach my $file ( @selectedFiles ) {
		next if !$file;

		if ( $share->deleteFile( dirPath => $currentDir, filename => $file ) )
		{
			$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->deleteItems(): file delete succeeded: $currentDir\\$file" );
		}
		else
		{
			$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->deleteItems(): file delete failed: $currentDir\\$file" );
		}
	}

	# TODO: Folder deletion
	foreach my $folder ( @selectedFolders ) {
		next if !$folder;

		# Remove trailing \
		chop($currentDir) if ($currentDir =~ /\\$/);

		if ( $share->deleteFolder( dirPath => "$currentDir", folderName => "$folder" ) )
		{
			$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->deleteItems(): folder delete succeeded: $currentDir\\$folder" );
		}
		else
		{
			$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->deleteItems(): folder delete failed: $currentDir\\$folder" );
			return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to delete \"$folder\": The folder is not empty", currentDir => $currentDir, initialFolder => $initialFolder  );
		}
	}

	my $ret .= $self->getShareBrowseHtml();

	return $ret;
}

# Copy/move items on the clipboard to the current directory
sub pasteClipboardItems
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $cgi = $toolBox->getCgi();
	my $dbw = $toolBox->getWebtopDbWriter();
	my $client = $toolBox->getClient();
	my $sessionId = $client->getSessionId();

	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');

	my $shareId = $client->getPersistedValue( name=> 'shareId' );
	$shareId = $cgi->param('shareId') if defined $cgi->param('shareId');
	my $displayGroup = $client->getPersistedValue( name=> 'displayGroup' );
	$displayGroup = $cgi->param('displayGroup') if defined $cgi->param('displayGroup');
	my $shareInfo = $self->getStoredShareInfo( shareId => $shareId, displayGroup=> $displayGroup );
	my $shareName = $shareInfo->{'share'};
	my $serverName = $shareInfo->{'server'};
	$serverName =~ s/^\\\\//g;
	$client->setPersistedValue( name=> "shareId", value=> $shareId );
	$client->setPersistedValue( name=> "displayGroup", value=> $displayGroup );

	if ( not ( defined $shareName && defined $serverName ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems: missing one of the following form fields: shareName, serverName; displaying error page" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to paste items from clipboard", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# TODO: Support pasting from other fileshares (requires changes to initSmbShare and Smb->moveItem)
	# Get items from clipboard for the current fileshare
	my @clipboardItems = $dbw->query( sql => "
		SELECT
			directory, filename, fileshare_cut
		FROM
			w_web_smb_fileshare_clipboard
		WHERE
			fileserver_hostname = '" . $dbw->escape(dirty => $serverName) . "'
			AND fileshare_name = '" . $dbw->escape(dirty => $shareName) . "'
			AND client_session_id = '$sessionId'
	");

	# Initialize smbShare object
	if ( !$self->initSmbShare() )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems(): couldn't initialize Smb object; returning error msg" );	
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to connect to the file share", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	my $share = $self->{'smbShare'};

	if ( not $self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir, protocol=> "FILE" ) )
	{
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	foreach my $item (@clipboardItems)
	{
		my $origDir = $item->[0];
		my $itemName = $item->[1];
		my $cut = $item->[2];

		if
		( 
			not 
			(
				$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $origDir . "\\" . $itemName, protocol=> "FILE" )
				and
				$self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $itemName, protocol=> "FILE" )
			)
		)
		{
			return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource ($itemName) is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder  );
		}
	}

	if ( !@clipboardItems )
	{
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "You must cut/copy at least one item before you can paste", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Go through each file and folder on the clipboard and move it to the current dir
	foreach my $item (@clipboardItems)
	{
		my $origDir = $item->[0];
		my $itemName = $item->[1];
		my $cut = $item->[2];
		my $isDir;
		
		# Remove trailing \
		chop($origDir) if ($origDir =~ /\\$/);

		# If itemName is empty, there was no filename in the database. Ergo, this is a directory
		# So, the directory becomes the itemName
		if ( not (defined $itemName && $itemName) )
		{
			$origDir =~ /(.*)\\(.*)/;
			$origDir = $1;
			$itemName = $2;
			$isDir = 1;
		}

		# Cut/paste or copy/paste the items from the clipboard
		if ($cut)
		{
			# Move the items
			$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems(): Attempting to cut/paste (move) item: '$origDir\\$itemName' -> '$currentDir\\$itemName'" );
			if ( $share->moveItem( dirPath => $origDir, itemName => $itemName, newDirPath => $currentDir, newItemName => $itemName ) )                   
			{
				$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems(): Cut/paste succeeded" );
			}
			else
			{
				$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems(): Cut/paste failed" );
				return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Could not paste item ($itemName). Make sure there are no items with the same name in this directory.", currentDir => $currentDir, initialFolder => $initialFolder  );
			}

			# Clear clipboard since the files were cut
			$dbw->do( sql => "
				DELETE FROM
					w_web_smb_fileshare_clipboard
				WHERE
					fileserver_hostname = '" . $dbw->escape(dirty => $serverName) . "'
					AND fileshare_name = '" . $dbw->escape(dirty => $shareName) . "'
					AND client_session_id = '$sessionId'
			");
		}
		else
		{
			# Copy the items; leave them on the clipboard
			$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems(): Attempting to copy/paste item: '$origDir\\$itemName' -> '$currentDir\\$itemName'" );

			if ( $share->copyItem( dirPath => $origDir, itemName => $itemName, newDirPath => $currentDir, newItemName => $itemName, isDir => $isDir ) )                   
			{
				$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems(): Copy/paste succeeded" );
			}
			else
			{
				$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->pasteClipboardItems(): Copy/paste failed" );
				return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Could not paste item ($itemName). Make sure there are no items with the same name in this directory.", currentDir => $currentDir, initialFolder => $initialFolder );
			}
		}
	}

	my $ret .= $self->getShareBrowseHtml();

	return $ret;
}

# Insert selected selected files and/or folders into the clipboard database
sub addItemsToClipboard
{
	my $self = shift;
	my ($cut) = @{{@_}}{qw/cut/};
	$cut = 0 unless $cut == 1;

	my $toolBox = $self->getToolBox();
	my $cgi = $toolBox->getCgi();
	my $dbw = $toolBox->getWebtopDbWriter();
	my $client = $toolBox->getClient();
	my $sessionId = $client->getSessionId();

	my $act = $cgi->param('act');
	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');

	# Remove trailing \ from currentDir
	chop($currentDir) if ($currentDir =~ /.*\\$/);
	$currentDir = '\\' if !$currentDir;

	my $shareId = $client->getPersistedValue( name=> 'shareId' );
	$shareId = $cgi->param('shareId') if defined $cgi->param('shareId');
	my $displayGroup = $client->getPersistedValue( name=> 'displayGroup' );
	$displayGroup = $cgi->param('displayGroup') if defined $cgi->param('displayGroup');
	my $shareInfo = $self->getStoredShareInfo( shareId => $shareId, displayGroup=> $displayGroup );
	my $shareName = $shareInfo->{'share'};
	my $serverName = $shareInfo->{'server'};
	$serverName =~ s/^\\\\//g;
	$client->setPersistedValue( name=> "shareId", value=> $shareId );
	$client->setPersistedValue( name=> "displayGroup", value=> $displayGroup );

	if ( not ( defined $shareName && defined $serverName ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->addItemsToClipboard: missing one of the following form fields: shareName, serverName; displaying error page" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to add items to clipboard", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Make sure that the user selected stuff, in case javascript didn't catch it
	my (@selectedFiles,@selectedFolders);
	if (@selectedFiles || @selectedFolders) 
	{ 
		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->addItemsToClipboard() \@selectedFiles or \@selectedFolders have shit in them" ); 
	}

	@selectedFiles = $cgi->param('selectedFiles');
	@selectedFolders = $cgi->param('selectedFolders');
	
	if ( !$#selectedFiles && !$#selectedFolders )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->addItemsToClipboard(): No items were selected; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "No items were selected to add to the clipboard", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	# Clear the clipboard
	$dbw->do( sql => "
		DELETE FROM
			w_web_smb_fileshare_clipboard
		WHERE
			fileserver_hostname = '" . $dbw->escape(dirty => $serverName) . "'
			AND fileshare_name = '" . $dbw->escape(dirty => $shareName) . "'
			AND client_session_id = '$sessionId'
	");

	# Insert each item into the database
	foreach my $file ( @selectedFiles )
	{
		if ($file eq "") { next; }

		$dbw->do( sql => "
			INSERT INTO
				w_web_smb_fileshare_clipboard
				(client_session_id, clipboard_id, fileshare_cut, fileserver_hostname, fileshare_name, directory, filename)
			VALUES
				('$sessionId', " . $dbw->getNextValueString( sequence => 'w_smb_fileshare_clipboard_pk') . ", '$cut', 
				'" . $dbw->escape(dirty => $serverName) . "', 
				'" . $dbw->escape(dirty => $shareName) . "', 
				'" . $dbw->escape(dirty => $currentDir) . "', 
				'" . $dbw->escape(dirty => $file) . "')
		");
	}

	foreach my $folder ( @selectedFolders )
	{
		if ($folder eq "") { next; }

		$dbw->do( sql => "
			INSERT INTO
				w_web_smb_fileshare_clipboard
				(client_session_id, clipboard_id, fileshare_cut, fileserver_hostname, fileshare_name, directory)
			VALUES
				('$sessionId', " . $dbw->getNextValueString( sequence => 'w_smb_fileshare_clipboard_pk') . ", '$cut', 
				'" . $dbw->escape(dirty => $serverName) . "', 
				'" . $dbw->escape(dirty => $shareName) . "', 
				'" . $dbw->escape(dirty => "$currentDir\\$folder") . "')
		");
	}

	my $ret .= $self->getShareBrowseHtml();

	return $ret;
}

# Stream the file to the user
sub downloadFile
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $client = $toolBox->getClient();
	my $cgi = $toolBox->getCgi();

	my $currentDir = $cgi->param('currentDir');
	my $initialFolder = $cgi->param('initialFolder');
	my $downloadFilename = $cgi->param('downloadFilename');

	if ( not defined $downloadFilename || !$downloadFilename )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->downloadFile() didn't get downloadFilename form variable; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to download file (an invalid filename was requested for download)", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	if ( not defined $currentDir || $currentDir eq "" )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->downloadFile() didn't get currentDir form variable; returning error msg" );
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to download file (can't determine current directory)", currentDir => $currentDir, initialFolder => $initialFolder  );
	}
	 $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->downloadFile() currentDir: '$currentDir'" );

	# Initialize smbShare object
	if ( !$self->initSmbShare() )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->downloadFile() couldn't initialize Smb object; returning error msg" );	
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Unable to connect to file share", currentDir => $currentDir, initialFolder => $initialFolder  );
	}

	my $share = $self->{'smbShare'};
	my $shareName = $share->getRoot();

	if ( not $self->aclCanAccess( servers=> $self->{'remoteServerValues'}, path=> $shareName . $currentDir . "\\" . $downloadFilename, protocol=> "FILE" ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->downloadFile() Access to the resource is denied by policies" );	
		return $toolBox->getErrorPage( errorTitle => "File Share Browser", errorMsg => "Access to the requested resource is denied by your policies", currentDir => $currentDir, initialFolder => $initialFolder );
	}

	$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->downloadFile: attempting to send file '$currentDir\\$downloadFilename' to user " );

	my $dbh = $toolBox->getWebtopDbWriter()->{'dbh'};
	my $oldAutoCommit = $dbh->{'AutoCommit'};
	$dbh->{'AutoCommit'} = 1;
	$dbh->commit();

	# getFile prints directly to STDOUT
	$share->getFile(
		dirPath => $currentDir,
		filename => $downloadFilename
	);

	$dbh->{'AutoCommit'} = $oldAutoCommit;
	$dbh->commit();

	return undef;
}

# Retrieve all file shares configured for the user via the policy manager (ipsec vpn)
sub getAppFileShares
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $cgi = $toolBox->getCgi();
	my $dbr = $toolBox->getWebtopDbReader();
	my $client = $toolBox->getClient();
	my $userId = $client->getUserId();
	
	# Get the file shares 
	my @fileShareRows = $dbr->query( sql => "
		SELECT
			net_user, net_pass, drive_letter, remote_addr, to_char(last_access_date, 'yyyy/mm/dd hh24:mi:ss'), 1
		FROM
			client_drive_shares
		WHERE
			user_id = '$userId'
			AND restrict_set_id = " . $client->getRestrictionSetId . "
	");

	# Store file share info in an array of hashes
	# TODO: Get fshLastAccessDate
	my @fileShares;
	if (@fileShares) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Fileshares: ->getAppFileShares() \@fileShares has shit" ); }
	foreach my $fileShareRow (@fileShareRows)
	{
		my $fsaUsername = $fileShareRow->[0];
		my $fsaPassword = $fileShareRow->[1];
		my $fshDriveLetter = $fileShareRow->[2];
		my (undef,$fsvHostname,$fshName,$fshInitialFolder) = split(/\\{1,2}/,$fileShareRow->[3],4); 
		my $fshLastAccess = $fileShareRow->[4];
		my $fshAdmintool = $fileShareRow->[5];

		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP FileShares: ->getAppFileShares() server: $fsvHostname, share: $fshName" );

		push @fileShares, {
			fsvHostname      => $fsvHostname,
			fshName          => $fshName,
			fshDriveLetter   => $fshDriveLetter,
			fsaUsername      => $fsaUsername,
			fsaPassword      => $fsaPassword,
			fshInitialFolder => $fshInitialFolder,
			fshLastAccess    => $fshLastAccess,
			fshAdmintool		=> $fshAdmintool
		};
	}

	return @fileShares;
}

# Retrieve all file shares for the current user from the webtop database
sub getWebtopFileShares
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $cgi = $toolBox->getCgi();
	my $dbr = $toolBox->getWebtopDbReader();
	my $client = $toolBox->getClient();
	my $userId = $client->getUserId();
	my $sessionId = $client->getSessionId();
	#my $customerId = $client->getCustomerId();


	# Store file share info in an array of hashes
	my @fileShares;
	if (@fileShares) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP FileShares: ->getWebtopFileshares() \@fileShares has shit in it" ); }

	# TODO: Don't retrieve auth credentials if they were cached for another session (web_session_id would be populated)
	# TODO: Don't break in stupid ways if there are no auth credentials
	my @fileShareRows = $dbr->query( sql => "
		SELECT
			fsv.display_name, 
			fsv.fileserver_hostname, 
			fsv.fileserver_desc,
			fsh.fileshare_name, 
			fsh.fileshare_desc, 
			fsu.display_drive_letter,
			to_char(fsu.last_access_date, 'yyyy/mm/dd hh24:mi:ss'),
			fsh.fileshare_id, 
			0
		FROM
			w_smb_fileservers fsv, 
			w_smb_fileshares fsh, 
			w_smb_fileshare_users fsu
		WHERE
			fsh.fileshare_id = fsu.fileshare_id
			AND fsh.fileserver_id = fsv.fileserver_id
			AND fsu.user_id = '$userId'
	");

	my %fileShare;
	if (%fileShare) { $client->logger( severity=>'NORAML', logMessage=>"WEBTOP Fileshares: ->getWebtopFileShares() \%fileShare has shit in it" ); }
	foreach my $fileShareRow (@fileShareRows)
	{
		# raz changed this to two queries so that we can return share info if there is no
		# auth cache (we added a no auth cache policy)
		my $fsvDisplayName = $fileShareRow->[0];
		my $fsvHostname = $fileShareRow->[1];
		my $fsvDesc = $fileShareRow->[2];
		my $fshName = $fileShareRow->[3];
		my $fshDesc = $fileShareRow->[4];
		my $fshDriveLetter = $fileShareRow->[5];
		my $fshLastAccess = $fileShareRow->[6];
		my $fshFileShareId = $fileShareRow->[7];
		my $fshAdmintool = $fileShareRow->[8];


		my ($fsaUsername,$fsaPassword) = (undef,undef);
		my @row = $dbr->query( sql=> " select user_name, password from W_SMB_FILESHARE_AUTH_CACHE
			where user_id = $userId and fileshare_id = $fshFileShareId ");
		@row and ($fsaUsername,$fsaPassword) = @{ $row[0] };

		$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP FileShares: ->getWebtopFileshares() server: $fsvHostname, share: $fshName, user: " . (defined($fsaUsername) ? $fsaUsername : "NONE") . "" );

		push @fileShares, {
			fsaDisplayName => $fsvDisplayName,
			fsvHostname    => $fsvHostname,
			fsvDesc        => $fsvDesc,
			fshName        => $fshName,
			fshDesc        => $fshDesc,
			fshDriveLetter => $fshDriveLetter,
			fsaUsername    => $fsaUsername,
			fsaPassword    => $fsaPassword,
			fshLastAccess  => $fshLastAccess,
			fshAdmintool	=>	$fshAdmintool
		};
	}

	return @fileShares;
}

sub removeDriveShare
{
	my $self = shift;
	my $tb = $self->getToolBox();
	my $cgi = $tb->getCgi();
	my $db = $tb->getWebtopDbReader();
	my $client = $tb->getClient();
	my $userId = $client->getUserId();
	my $fileShareHostName		=	$cgi->param('fileShareHostName');
	my $fileShareName		=	$cgi->param('fileShareName');
	my $fileShareInitialFolder	=	$cgi->param('fileShareInitialFolder');
	my $okay 				=	$cgi->param('okay');
	my $fileServerId = undef;
	my $fileShareId = undef;

	if(defined($okay) and $okay eq '1')
	{
		($fileServerId) = $db->query( sql => "
					select 
						fileserver_id 
					from 
						w_smb_fileservers 
					where 
						fileserver_hostname = '" . $fileShareHostName . "'
						and customer_id = (
									select customer_id from groups
									where group_id = (
										select group_id from users 
										where user_id = '" . $userId . "'
										))");
		if( defined($fileServerId))
		{
			my $fullShareDir = $fileShareInitialFolder; 
		
			$fullShareDir =~ s/\\$//g;
			
			$fullShareDir = $fileShareName . "" . (defined($fullShareDir) && length($fullShareDir) ? "\\$fullShareDir" : "") . "";
			
			($fileShareId) = $db->query( sql => "
						select
							fileshare_id
						from
							w_smb_fileshares 
						where 
							fileserver_id = '" . $fileServerId ."'
							and fileshare_name = '" . $db->escape( dirty=> $fullShareDir ) . "'
							");
		}
		
		if( defined($fileShareId))
		{
			$db->do(sql => "
					delete from 
						w_smb_fileshare_users 
					where user_id = '" . $userId . "'
					AND fileshare_id = '" . $fileShareId . "'
				");
			$self->getShareLoginHtml(topText => "<b><span class='SMALL'>Successfully removed File share \"\\\\$fileShareHostName\\$fileShareName\\$fileShareInitialFolder\".</span></b><br/><br/>");
		}
		else
		{
			$self->getShareLoginHtml(topText => "<b><span class='SMALL'> File share \"\\\\$fileShareHostName\\$fileShareName\\$fileShareInitialFolder\" NOT removed. Please call Technical Support for assistance</span></b><br/><br/>");
		}
		
	}
	else
	{
		my $p = $tb->getPage();
		
		$p->addNavBarElement(description => "Remove Drive Share",
										values => [
											{
												name => "act",
												value => "RemoveDriveShare"
											},
											{
												name => "fileShareHostName",
												value => $fileShareHostName
											},
											{
												name => "fileShareName",
												value => $fileShareName
											},
											{
												name	=>	"fileShareInitialFolder",
												value	=>	$fileShareInitialFolder
											}
										]);


		print $p->getHeaderHtml(title	=> "Webtop: File share: Remove") . "<center>";
		#print Pos::Webtop::OcxLauncher::getLauncherHtml( tb=> $tb );

		my $box	=	$tb->createBox(title => "Remove a File share.", width => "400");

		print $box->getHeaderHtml;

		print "<br/> <b>This will remove \"\\\\$fileShareHostName\\$fileShareName\\$fileShareInitialFolder\" from your list of File Share browsers.</b><br/><br/>

				<table border='0' width='100%' cellspacing='2' cellpadding='0'>
					<tr>
						<td class='BOXTD1' align='right' width='50%'>
							<form method='GET'>
								<input type='submit' value='OK' style='width: 60'/>
								" . $client->getFormInputs(
										values => [
											{
												name  => 'act',
												value => 'RemoveDriveShare'
											},
											{
												name  => 'okay',
												value => '1'
											},
											{
												name  => 'fileShareHostName',
												value => $fileShareHostName
											},
											{
												name  => 'fileShareName',
												value => $fileShareName
											},
											{
												name	=>	'fileShareInitialFolder',
												value	=>	$fileShareInitialFolder
											}
										]) . "
							</form>
						</td>
						<td class='BOXTD1' align='left' width='50%'>
							<form method='GET'>
								<input type='submit' value='Cancel' style='width: 60'/>
								" . $client->getFormInputs() . "
							</form>
						</td>
					</tr>
				</table>
				";

		print $box->getFooterHtml;

		print "</center>" . $p->getFooterHtml;
	}

}

# Initialize the Pos::Webtop::FileShare::Smb object
sub initSmbShare
{
	my $self = shift;
	my $toolBox = $self->getToolBox();
	my $cgi = $toolBox->getCgi();
	my $dbr = $toolBox->getWebtopDbReader();
	my $dbw = $toolBox->getWebtopDbWriter();
	my $client = $toolBox->getClient();
	my $userId = $client->getUserId();
	
	# Reset the class attribute
	$self->{'smbShare'} = undef;

	my $shareId = $client->getPersistedValue( name=> 'shareId' );
	$shareId = $cgi->param('shareId') if defined $cgi->param('shareId');
	$shareId = $self->{'initShareId'} if defined $self->{'initShareId'};
	my $displayGroup = $client->getPersistedValue( name=> 'displayGroup' );
	$displayGroup = $cgi->param('displayGroup') if defined $cgi->param('displayGroup');
	$displayGroup = $self->{'initDisplayGroup'} if defined $self->{'initDisplayGroup'};
	my $shareInfo = $self->getStoredShareInfo( shareId => $shareId, displayGroup=> $displayGroup );
	my $shareUserName = $shareInfo->{'user'};
	my $sharePassword = $shareInfo->{'password'};
	my $shareName = $shareInfo->{'share'};
	my $serverName = $shareInfo->{'server'};
	$serverName =~ s/^\\\\//g;
	$client->setPersistedValue( name=> "shareId", value=> $shareId );
	$client->setPersistedValue( name=> "displayGroup", value=> $displayGroup );
	
	# this block was not needed since the only place that will ever take a username password
	# combo from the user will always go to the share browse screen... it will then 
	# seed us with a initShareId etc just like auth cache would... otherwise we will use the 
	# persisted or form post values for the shareid and display group.

	#	# incase we came from a login... we have the special case of needing to read from the form
	#	# we check initShareId incase to avoid reading form values if we are being overridden
	#	# by an auth cache seed.
	#	if ( defined $cgi->param('shareUserName') and not defined $self->{'initShareId'} )
	#	{
	#		$shareUserName = $cgi->param('shareUserName');
	#		$sharePassword = $cgi->param('sharePassword');
	#		$shareName = $cgi->param('shareName');
	#		$serverName = $cgi->param('serverName');
	#
	#		# note that we don't save the stored info since that is handled by the caller
	#	}

	#
	# 2021.02.18 - LMoody
	#	Bugfix, if sharename was not provided
	#
	$shareName = undef unless ( length ( $shareName ) );
	#$shareUserName = undef unless ( length ( $shareUserName ) );
	#$sharePassword = undef unless ( length ( $sharePassword ) );
	# end
	
	if ( not ( defined $shareName && defined $serverName ) )
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->initSmbShare(): missing one of the following form fields: shareName, serverName; returning 0" );
		return 0;
	}

	#if ( ! defined( $shareUserName ) || ! defined( $sharePassword ) )
	#{
	#	$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->initSmbShare(): missing one of the following form fields: username, password; returning 0" );
	#	return 0;
	#}
	
	push @{ $self->{'remoteServerValues'} }, $serverName;

	my $supernattedServerIp;
	my $coIp;
	my $ipInt;

	{
		$coIp = $toolBox->resolveHost( host=> $serverName );
		defined $coIp or last;
		push @{ $self->{'remoteServerValues'} }, $coIp;

		$ipInt = 0;
		my $i = 0;
		my @ot = split(/\./,'127.0.0.1');
		$ipInt += $ot[0] << 24;
		$ipInt += $ot[1] << 16;
		$ipInt += $ot[2] << 8;
		$ipInt += $ot[3];
		
		push @{ $self->{'remoteServerValues'} }, $ipInt;
	}

	# we decide on the 'thing we will use' (could be ip or name)
	my $nameToUse = (defined $coIp ? $coIp : $serverName );

# no supernat anymore, taken care of by smbclient binary
# 	# Right now dev doesn't have supernat, so don't try to do supernat if we're on dev.
# 	if ($ENV{'SERVER_NAME'} eq 'dev.positivenetworks.net')
# 	{
# 		$supernattedServerIp = $serverName;
# 	}
# 	else
# 	{
# 		$supernattedServerIp = $toolBox->getSupernattedIp( host => $nameToUse );
# 	}
# 
# 	if ( not defined $supernattedServerIp )
# 	{
# 		warn ref($self) . "->initSmbShare(): unable to resolve or get supernatted IP for serverName '$serverName'";
# 		return 0;
# 	}
#
	
	$supernattedServerIp = $serverName;

	my ($domain) = $dbr->query( sql => "
		SELECT
			corp_network_domain
		FROM
			users
		WHERE
			user_id = '$userId'
	");

	my $webSessionId = $client->getSessionId();

	if(defined $domain and $domain)
	{
		$client->logger( severity=>'NORMAL',
								logMessage=>"WEBTOP Fileshares: initSmbShare: 
			shareUserName => $shareUserName,
			sharePassword => HIDDEN,
			serverName    => $serverName,
			shareName     => $shareName,
			domain        => $domain,
			webSessionId  => $webSessionId");

		# Create Smb object
		$self->{'smbShare'} = Pos::Webtop::FileShare::Smb->new(
			shareUserName => $shareUserName,
			sharePassword => $sharePassword,
			serverName    => $serverName,
			shareName     => $shareName,
			domain        => $domain,
			webSessionId  => $webSessionId
		);
	}
	else
	{
		$client->logger( severity=>'NORMAL',
								logMessage=>"WEBTOP Fileshares: initSmbShare 
			shareUserName => $shareUserName,
			sharePassword => HIDDEN,
			serverName    => $serverName,
			shareName     => $shareName,
			webSessionId  => $webSessionId");

		# Create Smb object
		$self->{'smbShare'} = Pos::Webtop::FileShare::Smb->new(
			shareUserName => $shareUserName,
			sharePassword => $sharePassword,
			serverName    => $serverName,
			shareName     => $shareName,
			webSessionId  => $webSessionId
		);
	}

	# Return 1 if creation succeeded, else return 0
	if ( $self->{'smbShare'} )
	{
		return 1;
	}
	else
	{
		$client->logger( severity=>'ERROR', logMessage=>"WEBTOP Fileshares: ->initSmbShare: unable to create Pos::Webtop::FileShare::Smb object; returning 0" );
		return 0;
	}
}

1;

