use strict; use MIME::Types; use Filesys::SmbClientParser; use File::Temp; use Pos::Webtop::Toolbox; package Pos::Webtop::FileShare::Smb; our @ISA = qw( Pos::Webtop::FileShare ); sub new { my $this = shift; my $class = ref($this) || $this; my ($self,$shareUserName,$sharePassword,$serverName,$shareName,$ipAddress,$domain,$webSessionId) = @{{@_}}{qw/self shareUserName sharePassword serverName shareName ipAddress domain webSessionId/}; if ( not defined $self ) { $self = {}; bless($self,$class); } $self->{'tb'} = Pos::Webtop::Toolbox->new(); $self->{'client'} = $self->{'tb'}->getClient(); $self->{'copyRecursionDepth'} = 0; $self->{'downloadDir'} = '/var/webtop/fileshare'; if ( not ( defined $serverName || defined $ipAddress ) ) { $self->{'client'}->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->new(): neither serverName nor ipAddress were defined, returning undef." ); return undef; } $self->{'serverName'} = $serverName; if ( not defined $shareName ) { $self->{'client'}->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->new(): shareName was not defined, returning undef." ); return undef; } $self->{'shareName'} = $shareName; $self->{'shareUserName'} = $shareUserName; $self->{'sharePassword'} = $sharePassword; # Instantiate a FileSys::SmbClient object # Because its constructor will die() if it encounters errors, we must use eval() to trap it eval { $self->{'smb'} = Filesys::SmbClientParser->new( "/usr/local/positive/Binary/smbclient", $webSessionId, ( user => $shareUserName, password => $sharePassword, host => $serverName, share => $shareName, ipadress => $ipAddress, workgroup => $domain ) ); }; # Set debug level to 1 in production $self->{'debug'} = 0; $self->{'smb'}->Debug(0); $self->{'client'}->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->new(): Got a Filesys::SmbClientParser object; username: $self->{'shareUserName'}, password: HIDDEN" ); # If eval() caught a die(), $@ will be set to the error text if ($@) { $self->{'client'}->logger( severity=>'ERROR', logMessage=>"WEBTOP Smb: ->new(): FileSys::SmbClientParser->new failed and attempted to die() ($@), returning undef." ); return undef; } return $self; } # (pure virtual defined in Pos::Webtop::FileShare) # # dirPath => "\dir\dir2" # getExtendedInfo => [0/1] (determines whether to populate the file stats) # # returns a reference to an array of hashes # [( { itemName => "dog.txt", # itemType => "file", # file, directory, link # itemComment => "a good file for work beyond the office" }, # { ... } )] # # The directories '.' and '..' are filtered # sub getDirListing { my $self = shift; my ($dirPath) = @{{@_}}{qw/dirPath/}; my $smb = $self->{'smb'}; my $client = $self->{'client'}; # my %smbTypes = ( # "D" => "directory", # "DA" => "directory", # "DAH" => "directory", # "DAHS" => "directory", # "DR" => "directory", # "DRA" => "directory", # "DAR" => "directory", # "DRAH" => "directory", # "DAHR" => "directory", # "DRAHS" => "directory", # "DAHSR" => "directory" # ); if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getDirListing(): dirPath is not defined, returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ($dirPath=~/\\$/); # Validate the path if (!($self->isValidPath(path => $dirPath))) { $client->logger( severity=>'WARNING', logMessage=>"WEBTOP Smb: ->getDirListing(): dirPath is invalid '$dirPath', returning undef" ); return undef; } $dirPath = "\\" unless $dirPath; # Debug $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getDirListing: Attempting to get directory listing for '$dirPath'" ); # Convert all \ to / $dirPath =~ s/\\/\//g; # Debug $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getDirListing: dirPath is '$dirPath' (after converting all \\ to /)" ); # Read directory contents (gets directory items in an array: type, name, comment) # TODO: Make it not break just because the directory is empty (look for undef instead of 0? dunno..) my @dirItems = $smb->dir("\"$dirPath\""); if ( !(@dirItems) || !$dirItems[0] ) { $client->logger( severity=>'WARNING', logMessage=>"WEBTOP Smb: ->getDirListing(): unable to read directory '$dirPath' ($! / " . (defined($smb->err()) && length($smb->err()) ? $smb->err() : "" ) . "); returning undef" ); if ( $self->{'debug'} ) { $client->logger( severity=>'DEBUG', logMessage=>"WEBTOP Smb: ->getDirListing(): Last response from smbclient:" ); foreach my $line ($smb->LastResponse) { foreach my $array ( @{ $line } ) { warn "\t" . $array; } } warn "\n"; } return undef; } # Populate @dirListing, an array of hashes containing info for each item in the dir my @dirListing; if (@dirListing) { $client->logger( severity=>'ERROR', logMessage=>"WEBTOP Smb: \@dirListing has shit in it: mod_perl problem!" ); } 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 ); foreach my $dirItem (@dirItems) { # Filter special dirs . and .. if ( $dirItem->{'name'} =~ /^\.+$/ ) { next; } # Format modification time my (undef,$smon,$mday,$time,$year) = split(' ',$dirItem->{'date'}); my ($hour,$min,$sec) = split(':', $time); my $mon = $months{$smon}; $hour =~ s/^0//; $min = sprintf("%.2d", $min); #my $itemType = $smbTypes{$dirItem->{'attr'}}; my $itemType; if ( $dirItem->{'attr'} =~ /D/ ) { $itemType = "directory"; } else { $itemType = "file"; } my %dirItem = ( "itemName" => $dirItem->{'name'}, "itemType" => $itemType, "itemBytes" => $dirItem->{'size'}, "itemMtime" => "$sec $min $hour $mday $mon $year" ); push @dirListing, { %dirItem }; } return \@dirListing; } # (pure virtual defined in Pos::Webtop::FileShare) sub createFolder { my $self = shift; my ($dirPath,$folderName) = @{{@_}}{qw/dirPath folderName/}; my $smb = $self->{'smb'}; my $client = $self->{'client'}; if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): dirPath is not defined, returning undef" ); return undef; } if ( not defined $folderName ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): folderName is not defined, returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ( $dirPath =~ /\\$/ ); # Validate the path if (!($self->isValidPath(path => "$dirPath\\$folderName"))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): dirPath\\folderName is invalid '$dirPath\\$folderName', returning undef" ); return undef; } if ( $dirPath !~ /\w/ || $dirPath =~ /^\\+$/ || $dirPath =~ /^\/+$/ || !$dirPath) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): dirPath '$dirPath' appears to be the root directory; i'm refusing to do this" ); } # Convert all \ to / $dirPath =~ s/\\/\//g; $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): attempting to create folder: $dirPath/$folderName" ); if ( $smb->mkdir("\"$folderName\"", $dirPath) ) { return 1; } else { $client->logger( severity=>'ERROR', logMessage=>"WEBTOP Smb: ->deleteFolder(): create folder failed for '$dirPath/$folderName' ($! / " . $smb->err() . ")" ); return 0; } } # (pure virtual defined in Pos::Webtop::FileShare) sub deleteFolder { my $self = shift; my ($dirPath,$folderName) = @{{@_}}{qw/dirPath folderName/}; my $smb = $self->{'smb'}; my $client = $self->{'client'}; if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): dirPath is not defined, returning undef" ); return undef; } if ( not defined $folderName ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): folderName is not defined, returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ( $dirPath =~ /\\$/ ); # Validate the path if (!($self->isValidPath(path => "$dirPath\\$folderName" ))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): dirPath is invalid '$dirPath\\$folderName', returning undef" ); return undef; } if ( ($dirPath !~ /\w/ || $dirPath =~ /^\\+$/ || $dirPath =~ /^\/+$/ || !$dirPath) && (!$folderName) ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): dirPath '$dirPath\\$folderName' appears to be the root directory; i'm refusing to do this" ); return undef; } # Convert all \ to / $dirPath =~ s/\\/\//g; $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): attempting to delete folder: $dirPath//$folderName" ); if ( $smb->rmdir("\"$folderName\"", $dirPath) ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): folder delete succeeded ($! / " . (defined($smb->err()) ? $smb->err() : "") . ")" ); return 1; } else { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFolder(): folder delete failed for '$dirPath' ($! / " . (defined($smb->err()) ? $smb->err() : "") . ")" ); return 0; } } # (pure virtual defined in Pos::Webtop::FileShare) sub deleteFile { my $self = shift; my ($dirPath,$filename) = @{{@_}}{qw/dirPath filename/}; my $smb = $self->{'smb'}; my $client = $self->{'client'}; if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFile(): dirPath is not defined, returning undef" ); return undef; } if ( not defined $filename ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFile(): filename is not defined, returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ( $dirPath =~ /\\$/ ); # Validate the path if (!($self->isValidPath(path => $dirPath))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFile(): dirPath is invalid '$dirPath', returning undef" ); return undef; } # Validate the filename if (!($self->isValidFilename(filename => $filename))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFile(): filename is invalid '$filename', returning undef" ); return undef; } # Convert all \ to / $dirPath =~ s/\\/\//g; $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFile(): attempting to delete file: $dirPath/$filename" ); if ( $smb->del("\"$filename\"", $dirPath) ) { return 1; } else { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->deleteFile(): file delete failed for '$dirPath/$filename' ($! / " . $smb->err() . ")" ); return 0; } } # (pure virtual defined in Pos::Webtop::FileShare) sub getFile { my $self = shift; my ($dirPath,$filename) = @{{@_}}{qw/dirPath filename/}; my $smb = $self->{'smb'}; my $downloadDir = $self->{'downloadDir'}; my $client = $self->{'client'}; if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getFile(): dirPath is not defined, returning undef" ); return undef; } if ( not defined $filename ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getFile(): filename is not defined, returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ( $dirPath =~ /\\$/ ); # Validate the path if (!($self->isValidPath(path => $dirPath))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getFile(): dirPath is invalid '$dirPath', returning undef" ); return undef; } # Validate the filename if (!($self->isValidFilename(filename => $filename))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getFile(): filename is invalid '$filename', returning undef" ); return undef; } # Convert all \ to / $dirPath =~ s/\\/\//g; $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getFile(): attempting to retrieving file from location: $dirPath/$filename" ); my $mimeType = $self->getMimeType(filename => $filename); if ( defined $mimeType && $mimeType ) { print "Content-type: $mimeType\n"; print "Content-Disposition: inline; filename=$filename\n\n"; } else { print "Content-type: application/octet-stream\n"; print "Content-Disposition: attachment; filename=$filename\n\n"; } $| = 1; # Need a random filename #File::Temp->safe_level( File::Temp::HIGH ); #my ($tmpFh,$tmpFilename) = File::Temp::tempfile( DIR => $downloadDir, UNLINK => 1 ); #my (undef,$tmpFilename) = File::Temp::tempfile( DIR => $downloadDir, OPEN=> 0 ); #$client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getFile(): attempting to store file in temporary location: '$tmpFilename'"; #warn("mknod $tmpFilename p"); #open(P,"<$tmpFilename"); # if ( fork() ) # { # # parent # my $buf; # # while ( sysread(P,$buf,64*1024) ) # { # print $buf # } # } # else { # child # Download the file to a temporary location on disk #if ( !$smb->get($filename,$tmpFilename,"\"$dirPath\"") ) if ( !$smb->get($filename,"-","\"$dirPath\"") ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->getFile(): file retrieval error ($! / " . (defined $smb->err() ? $smb->err() : "") . "); returning undef" ); return undef; } #exit(0); } #close(P); # $|=1; # my $tmpBuf; # while ( read($tmpFh,$tmpBuf,1024) ) # { # print $tmpBuf; # } # # close($tmpFh); } # (pure virtual defined in Pos::Webtop::FileShare) sub getFileInfo { my $self = shift; } # (pure virtual defined in Pos::Webtop::FileShare) sub putFile { my $self = shift; my ($dirPath,$filename,$uploadFileHandle,$overwriteFile) = @{{@_}}{qw/dirPath filename uploadFileHandle overwriteFile/}; my $smb = $self->{'smb'}; my $downloadDir = $self->{'downloadDir'}; my $client = $self->{'client'}; # Ensure we have everything if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): dirPath is not defined, returning undef" ); return undef; } if ( not defined $filename ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): filename is not defined, returning undef" ); return undef; } if ( not defined $uploadFileHandle ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): uploadFileHandle is not defined for '$dirPath\\$filename'; returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ( $dirPath =~ /\\$/ ); # Validate the path if ( !($self->isValidPath(path => $dirPath)) ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): dirPath is invalid '$dirPath', returning undef" ); return undef; } # Validate the filename if ( !($self->isValidFilename(filename => $filename)) ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): filename is invalid '$filename', returning undef" ); return undef; } my $dirListing = $self->getDirListing( dirPath => "$dirPath" ); # Convert all \ to / $dirPath =~ s/\\/\//g; my @sortedDirListing = sort { ($a->{'itemType'} . uc($a->{'itemName'})) cmp ($b->{'itemType'} . uc($b->{'itemName'})) } @{$dirListing}; # Go through each file and compare to uploadFile name if(!$overwriteFile) { foreach my $dirItem (@sortedDirListing) { my $dirItemName = $dirItem->{'itemName'}; if ($dirItemName eq $filename) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->uploadItem(): filename $filename already exists in this folder" ); return undef; } } } # Copy the uploaded file to a temporary file so that we have the local path to the file (instead of just a handle) File::Temp->safe_level( File::Temp::HIGH ); my ($tmpFh,$tmpFilename) = File::Temp::tempfile( DIR => $downloadDir, UNLINK => 1 ); my $readBuf; if ($readBuf) { warn "\$readBuf has shit in it"; } while ( sysread($uploadFileHandle, $readBuf, 1024) ) { syswrite($tmpFh, $readBuf); } close($tmpFh); close($uploadFileHandle); $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): copied uploaded file to temp location '$tmpFilename'" ); $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): attempting upload" ); # Upload the temporary file to the server if ( !$smb->put($tmpFilename, $filename, "\"$dirPath\"") ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): file upload error ($! / " . (defined $smb->err() ? $smb->err() : "") . "); returning undef" ); return undef; } return 1; } # (pure virtual defined in Pos::Webtop::FileShare) sub moveItem { my $self = shift; my ($dirPath,$itemName,$newDirPath,$newItemName) = @{{@_}}{qw/dirPath itemName newDirPath newItemName/}; my $smb = $self->{'smb'}; my $client = $self->{'client'}; if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): dirPath is not defined, returning undef" ); return undef; } if ( not defined $newDirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): newDirPath is not defined, returning undef" ); return undef; } if ( not defined $itemName ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): itemName is not defined, returning undef" ); return undef; } if ( not defined $newItemName ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): newItemName is not defined, returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ( $dirPath =~ /\\$/ ); chop($newDirPath) if ( $newDirPath =~ /\\$/ ); # Validate the paths if (!($self->isValidPath(path => $dirPath))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): dirPath is invalid '$dirPath', returning undef" ); return undef; } if (!($self->isValidPath(path => $newDirPath))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): newDirPath is invalid '$newDirPath', returning undef" ); return undef; } # Validate the itemNames (should match filename parameters) if (!($self->isValidFilename(filename => $itemName))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): itemName is invalid '$itemName', returning undef" ); return undef; } if (!($self->isValidFilename(filename => $newItemName))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): newItemName is invalid '$newItemName', returning undef" ); return undef; } # Make sure we aren't moving the file to the same location; this tends to cause random file deletion if ( ($dirPath eq $newDirPath) && ($itemName eq $newItemName) ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): cannot move an item onto itself" ); return undef; } my $dirListing = $self->getDirListing( dirPath => "$newDirPath" ); $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem() about to convert all \\ to /" ); # Convert all \ to / $dirPath =~ s/\\/\//g; $newDirPath =~ s/\\/\//g; $client->logger( severity=>'NORMAL', logMessage=>" ->moveItem() WEBTOP Smb: \$dirPath is now $dirPath and \$newDirPath is now $newDirPath" ); my @sortedDirListing = sort { ($a->{'itemType'} . uc($a->{'itemName'})) cmp ($b->{'itemType'} . uc($b->{'itemName'})) } @{$dirListing}; # Go through each file and compare to itemName name foreach my $dirItem (@sortedDirListing) { my $dirItemName = $dirItem->{'itemName'}; if ($dirItemName eq $newItemName) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->uploadItem(): filename $newItemName already exists in this folder" ); return undef; } } $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): attempting to move item: '$dirPath/$itemName' -> '$newDirPath/$newItemName'" ); if ( $smb->rename("$dirPath/$itemName", "$newDirPath/$newItemName", "/") ) { return 1; } else { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->moveItem(): item move failed: '$dirPath/$itemName' -> '$newDirPath/$newItemName' ($! / " . $smb->err() . ")" ); warn "\n\n"; return 0; } } # (pure virtual defined in Pos::Webtop::FileShare) sub copyItem { my $self = shift; my ($dirPath,$itemName,$newDirPath,$newItemName,$isDir) = @{{@_}}{qw/dirPath itemName newDirPath newItemName isDir/}; my $smb = $self->{'smb'}; my $downloadDir = $self->{'downloadDir'}; my $client = $self->{'client'}; if ( $self->{'copyRecursionDepth'} > 31 ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): I have recursed 32 times; time to stop breaking myself" ); return undef; } if ( not defined $dirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): dirPath is not defined, returning undef" ); return undef; } if ( not defined $newDirPath ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): newDirPath is not defined, returning undef" ); return undef; } if ( not defined $itemName ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): itemName is not defined, returning undef" ); return undef; } if ( not defined $newItemName ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): newItemName is not defined, returning undef" ); return undef; } # Remove trailing \ chop($dirPath) if ( $dirPath =~ /\\$/ ); chop($newDirPath) if ( $newDirPath =~ /\\$/ ); # Validate the paths if (!($self->isValidPath(path => $dirPath))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): dirPath is invalid '$dirPath', returning undef" ); return undef; } if (!($self->isValidPath(path => $newDirPath))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): newDirPath is invalid '$newDirPath', returning undef" ); return undef; } # Validate the itemNames (should match filename parameters) if (!($self->isValidFilename(filename => $itemName))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): itemName is invalid '$itemName', returning undef" ); return undef; } if (!($self->isValidFilename(filename => $newItemName))) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): newItemName is invalid '$newItemName', returning undef" ); return undef; } # Make sure we aren't copying the file to the same location; this tends to cause random file deletion if ( ($dirPath eq $newDirPath) && ($itemName eq $newItemName) ) { warn ref($self). "->copyItem(): cannot copy an item onto itself"; return undef; } my $dirListing = $self->getDirListing( dirPath => "$newDirPath" ); # Convert all \ to / my $fixedDirPath = $dirPath; $fixedDirPath =~ s/\\/\//g; my $fixedNewDirPath = $newDirPath; $fixedNewDirPath =~ s/\\/\//g; my @sortedDirListing = sort { ($a->{'itemType'} . uc($a->{'itemName'})) cmp ($b->{'itemType'} . uc($b->{'itemName'})) } @{$dirListing}; # Go through each file and compare to itemName name foreach my $dirItem (@sortedDirListing) { my $dirItemName = $dirItem->{'itemName'}; if ($dirItemName eq $newItemName) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->uploadItem(): filename $newItemName already exists in this folder" ); return undef; } } $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): attempting to copy item: '$fixedDirPath/$itemName' -> '$fixedNewDirPath/$newItemName'" ); my $currentDir = (($smb->cd())[0]); $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): currently in directory: '$currentDir'" ); if ( $isDir ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): $fixedDirPath/$itemName" ); my $dirListing = $self->getDirListing( dirPath => "$dirPath\\$itemName" ); # Skip this folder if we can't list its contents if ( not defined $dirListing ) { # TODO: notify the user that something wasn't copied properly $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): couldn't get directory listing for '$dirPath\\$itemName', so I can't copy whatever's inside it; returning undef" ); return undef; } # Since we can list the contents of the folder, let's create it $self->createFolder( dirPath => $newDirPath, folderName => $itemName ); $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): creating folder $newDirPath\\$itemName" ); my @sortedDirListing = sort { ($a->{'itemType'} . uc($a->{'itemName'})) cmp ($b->{'itemType'} . uc($b->{'itemName'})) } @{$dirListing}; ## WARNING: RECURSION (INSIDE A LOOP!) ## # Attempt to copy each item inside this directory foreach my $dirItem (@sortedDirListing) { my $dirItemName = $dirItem->{'itemName'}; # Items that are files will just get copied and return # Items that are folders will go through this same algorithm and their file/folders will get copied/listed, recursively # Remember, $itemName is always a folder (at this point) $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): Recursion #" . $self->{'copyRecursionDepth'} . ": copying $dirPath\\$itemName\\$dirItemName -> $newDirPath\\$itemName\\$dirItemName" ); $self->{'copyRecursionDepth'}++; $self->copyItem( dirPath => "$dirPath\\$itemName", itemName => $dirItemName, newDirPath => "$newDirPath\\$itemName", newItemName => $newItemName, isDir=> ($dirItem->{'itemType'} eq "directory" ? 1 : 0) ); $self->{'copyRecursionDepth'}--; } $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem: done processing all of the files and folders within '$dirPath\\$itemName'; returning success" ); return 1; } # ... Back to processing a normal file copy $smb->cd($currentDir); # Need a random filename File::Temp->safe_level( File::Temp::HIGH ); my ($tmpFh,$tmpFilename) = File::Temp::tempfile( DIR => $downloadDir, UNLINK => 1 ); $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): attempting to store a copy of the remote file in temporary location: '$tmpFilename'" ); # Download the file to a temporary location on disk if ( !$smb->get($itemName, $tmpFilename, "\"$fixedDirPath\"") ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->copyItem(): file retrieval error ($! / " . (defined $smb->err() ? $smb->err() : "") . "); returning undef" ); return undef; } # Upload the temporary file to the server if ( !$smb->put($tmpFilename, $itemName, "\"$fixedNewDirPath\"") ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->putFile(): file upload error ($! / " . (defined $smb->err() ? $smb->err() : "") . "); returning undef" ); return undef; } close($tmpFh); return 1; } # Checks for invalid characters in a Windows path (\dog\cow) sub isValidPath { my $self = shift; my ($path) = @{{@_}}{qw/path/}; # Paths can't contain the following characters: " * / : < > ? | # paths must begin with \ if ($path =~ /[\"\*\/\:\<\>?\|]/ || $path =~ /^[^\\]{1}/) { return 0; } return 1; } # Checks for invalid characters in a Windows filename (file.txt) sub isValidFilename { my $self = shift; my ($filename) = @{{@_}}{qw/filename/}; my $client = $self->{'client'}; if ( not ( defined $filename && $filename ) ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->isValidFilename(): filename seems to be empty; returning 0" ); return 0; } elsif ( $filename =~ /[\"\*\/\:\<\>?\\\|]/ ) { $client->logger( severity=>'NORMAL', logMessage=>"WEBTOP Smb: ->isValidFilename(): filename contains invalid characters; returning 0" ); return 0; } return 1; } # Returns the mime type for a file, based on the file extension sub getMimeType { my $self = shift; my ($filename) = @{{@_}}{qw/filename/}; my ($mediaType) = MIME::Types::by_suffix($filename); return $mediaType; } sub getRoot { my $self = shift; return $self->{'shareName'}; } 1;