1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::Web::UploadCache::FS; 10 11use strict; 12use warnings; 13 14our @ObjectDependencies = ( 15 'Kernel::Config', 16 'Kernel::System::Log', 17 'Kernel::System::Main', 18); 19 20sub new { 21 my ( $Type, %Param ) = @_; 22 23 # allocate new hash for object 24 my $Self = {}; 25 bless( $Self, $Type ); 26 27 $Self->{TempDir} = $Kernel::OM->Get('Kernel::Config')->Get('TempDir') . '/upload_cache'; 28 29 if ( !-d $Self->{TempDir} ) { 30 mkdir $Self->{TempDir}; 31 } 32 33 return $Self; 34} 35 36sub FormIDCreate { 37 my ( $Self, %Param ) = @_; 38 39 # return requested form id 40 return time() . '.' . rand(12341241); 41} 42 43sub FormIDRemove { 44 my ( $Self, %Param ) = @_; 45 46 if ( !$Param{FormID} ) { 47 $Kernel::OM->Get('Kernel::System::Log')->Log( 48 Priority => 'error', 49 Message => 'Need FormID!' 50 ); 51 return; 52 } 53 54 return if !$Self->_FormIDValidate( $Param{FormID} ); 55 56 my $Directory = $Self->{TempDir} . '/' . $Param{FormID}; 57 58 if ( !-d $Directory ) { 59 return 1; 60 } 61 62 # get main object 63 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 64 65 my @List = $MainObject->DirectoryRead( 66 Directory => $Directory, 67 Filter => "*", 68 ); 69 70 my @Data; 71 for my $File (@List) { 72 $MainObject->FileDelete( 73 Location => $File, 74 ); 75 } 76 77 if ( !rmdir($Directory) ) { 78 $Kernel::OM->Get('Kernel::System::Log')->Log( 79 Priority => 'error', 80 Message => "Can't remove: $Directory: $!!", 81 ); 82 } 83 84 return 1; 85} 86 87sub FormIDAddFile { 88 my ( $Self, %Param ) = @_; 89 90 for (qw(FormID Filename ContentType)) { 91 if ( !$Param{$_} ) { 92 $Kernel::OM->Get('Kernel::System::Log')->Log( 93 Priority => 'error', 94 Message => "Need $_!" 95 ); 96 return; 97 } 98 } 99 100 return if !$Self->_FormIDValidate( $Param{FormID} ); 101 102 $Param{Content} = '' if !defined( $Param{Content} ); 103 104 # create content id 105 my $ContentID = $Param{ContentID}; 106 my $Disposition = $Param{Disposition} || ''; 107 if ( !$ContentID && lc $Disposition eq 'inline' ) { 108 109 my $Random = rand 999999; 110 my $FQDN = $Kernel::OM->Get('Kernel::Config')->Get('FQDN'); 111 112 $ContentID = "$Disposition$Random.$Param{FormID}\@$FQDN"; 113 } 114 115 # create cache subdirectory if not exist 116 my $Directory = $Self->{TempDir} . '/' . $Param{FormID}; 117 if ( !-d $Directory ) { 118 119 # Create directory. This could fail if another process creates the 120 # same directory, so don't use the return value. 121 File::Path::mkpath( $Directory, 0, 0770 ); ## no critic 122 123 if ( !-d $Directory ) { 124 $Kernel::OM->Get('Kernel::System::Log')->Log( 125 Priority => 'error', 126 Message => "Can't create directory '$Directory': $!", 127 ); 128 return; 129 } 130 } 131 132 # get main object 133 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 134 135 # files must readable for creator 136 return if !$MainObject->FileWrite( 137 Directory => $Directory, 138 Filename => "$Param{Filename}", 139 Content => \$Param{Content}, 140 Mode => 'binmode', 141 Permission => '640', 142 NoReplace => 1, 143 ); 144 return if !$MainObject->FileWrite( 145 Directory => $Directory, 146 Filename => "$Param{Filename}.ContentType", 147 Content => \$Param{ContentType}, 148 Mode => 'binmode', 149 Permission => '640', 150 NoReplace => 1, 151 ); 152 return if !$MainObject->FileWrite( 153 Directory => $Directory, 154 Filename => "$Param{Filename}.ContentID", 155 Content => \$ContentID, 156 Mode => 'binmode', 157 Permission => '640', 158 NoReplace => 1, 159 ); 160 return if !$MainObject->FileWrite( 161 Directory => $Directory, 162 Filename => "$Param{Filename}.Disposition", 163 Content => \$Disposition, 164 Mode => 'binmode', 165 Permission => '644', 166 NoReplace => 1, 167 ); 168 return 1; 169} 170 171sub FormIDRemoveFile { 172 my ( $Self, %Param ) = @_; 173 174 for (qw(FormID FileID)) { 175 if ( !$Param{$_} ) { 176 $Kernel::OM->Get('Kernel::System::Log')->Log( 177 Priority => 'error', 178 Message => "Need $_!" 179 ); 180 return; 181 } 182 } 183 184 return if !$Self->_FormIDValidate( $Param{FormID} ); 185 186 my @Index = @{ $Self->FormIDGetAllFilesMeta(%Param) }; 187 188 # finish if files have been already removed by other process 189 return if !@Index; 190 191 my $ID = $Param{FileID} - 1; 192 my %File = %{ $Index[$ID] }; 193 194 my $Directory = $Self->{TempDir} . '/' . $Param{FormID}; 195 196 if ( !-d $Directory ) { 197 return 1; 198 } 199 200 # get main object 201 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 202 203 $MainObject->FileDelete( 204 Directory => $Directory, 205 Filename => "$File{Filename}", 206 NoReplace => 1, 207 ); 208 $MainObject->FileDelete( 209 Directory => $Directory, 210 Filename => "$File{Filename}.ContentType", 211 NoReplace => 1, 212 ); 213 $MainObject->FileDelete( 214 Directory => $Directory, 215 Filename => "$File{Filename}.ContentID", 216 NoReplace => 1, 217 ); 218 $MainObject->FileDelete( 219 Directory => $Directory, 220 Filename => "$File{Filename}.Disposition", 221 NoReplace => 1, 222 ); 223 224 return 1; 225} 226 227sub FormIDGetAllFilesData { 228 my ( $Self, %Param ) = @_; 229 230 if ( !$Param{FormID} ) { 231 $Kernel::OM->Get('Kernel::System::Log')->Log( 232 Priority => 'error', 233 Message => 'Need FormID!' 234 ); 235 return; 236 } 237 238 my @Data; 239 240 return \@Data if !$Self->_FormIDValidate( $Param{FormID} ); 241 242 my $Directory = $Self->{TempDir} . '/' . $Param{FormID}; 243 244 if ( !-d $Directory ) { 245 return \@Data; 246 } 247 248 # get main object 249 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 250 251 my @List = $MainObject->DirectoryRead( 252 Directory => $Directory, 253 Filter => "*", 254 ); 255 256 my $Counter = 0; 257 258 FILE: 259 for my $File (@List) { 260 261 # ignore meta files 262 next FILE if $File =~ /\.ContentType$/; 263 next FILE if $File =~ /\.ContentID$/; 264 next FILE if $File =~ /\.Disposition$/; 265 266 $Counter++; 267 my $FileSize = -s $File; 268 269 # human readable file size 270 if ( defined $FileSize ) { 271 272 # remove meta data in files 273 if ( $FileSize > 30 ) { 274 $FileSize = $FileSize - 30; 275 } 276 } 277 my $Content = $MainObject->FileRead( 278 Location => $File, 279 Mode => 'binmode', # optional - binmode|utf8 280 ); 281 next FILE if !$Content; 282 283 my $ContentType = $MainObject->FileRead( 284 Location => "$File.ContentType", 285 Mode => 'binmode', # optional - binmode|utf8 286 ); 287 next FILE if !$ContentType; 288 289 my $ContentID = $MainObject->FileRead( 290 Location => "$File.ContentID", 291 Mode => 'binmode', # optional - binmode|utf8 292 ); 293 next FILE if !$ContentID; 294 295 # verify if content id is empty, set to undef 296 if ( !${$ContentID} ) { 297 ${$ContentID} = undef; 298 } 299 300 my $Disposition = $MainObject->FileRead( 301 Location => "$File.Disposition", 302 Mode => 'binmode', # optional - binmode|utf8 303 ); 304 next FILE if !$Disposition; 305 306 # strip filename 307 $File =~ s/^.*\/(.+?)$/$1/; 308 push( 309 @Data, 310 { 311 Content => ${$Content}, 312 ContentID => ${$ContentID}, 313 ContentType => ${$ContentType}, 314 Filename => $File, 315 Filesize => $FileSize, 316 FileID => $Counter, 317 Disposition => ${$Disposition}, 318 }, 319 ); 320 } 321 return \@Data; 322 323} 324 325sub FormIDGetAllFilesMeta { 326 my ( $Self, %Param ) = @_; 327 328 if ( !$Param{FormID} ) { 329 $Kernel::OM->Get('Kernel::System::Log')->Log( 330 Priority => 'error', 331 Message => 'Need FormID!' 332 ); 333 return; 334 } 335 336 my @Data; 337 338 return \@Data if !$Self->_FormIDValidate( $Param{FormID} ); 339 340 my $Directory = $Self->{TempDir} . '/' . $Param{FormID}; 341 342 if ( !-d $Directory ) { 343 return \@Data; 344 } 345 346 # get main object 347 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 348 349 my @List = $MainObject->DirectoryRead( 350 Directory => $Directory, 351 Filter => "*", 352 ); 353 354 my $Counter = 0; 355 356 FILE: 357 for my $File (@List) { 358 359 # ignore meta files 360 next FILE if $File =~ /\.ContentType$/; 361 next FILE if $File =~ /\.ContentID$/; 362 next FILE if $File =~ /\.Disposition$/; 363 364 $Counter++; 365 my $FileSize = -s $File; 366 367 # human readable file size 368 if ( defined $FileSize ) { 369 370 # remove meta data in files 371 if ( $FileSize > 30 ) { 372 $FileSize = $FileSize - 30; 373 } 374 } 375 376 my $ContentType = $MainObject->FileRead( 377 Location => "$File.ContentType", 378 Mode => 'binmode', # optional - binmode|utf8 379 ); 380 next FILE if !$ContentType; 381 382 my $ContentID = $MainObject->FileRead( 383 Location => "$File.ContentID", 384 Mode => 'binmode', # optional - binmode|utf8 385 ); 386 next FILE if !$ContentID; 387 388 # verify if content id is empty, set to undef 389 if ( !${$ContentID} ) { 390 ${$ContentID} = undef; 391 } 392 393 my $Disposition = $MainObject->FileRead( 394 Location => "$File.Disposition", 395 Mode => 'binmode', # optional - binmode|utf8 396 ); 397 next FILE if !$Disposition; 398 399 # strip filename 400 $File =~ s/^.*\/(.+?)$/$1/; 401 push( 402 @Data, 403 { 404 ContentID => ${$ContentID}, 405 ContentType => ${$ContentType}, 406 Filename => $File, 407 Filesize => $FileSize, 408 FileID => $Counter, 409 Disposition => ${$Disposition}, 410 }, 411 ); 412 } 413 return \@Data; 414} 415 416sub FormIDCleanUp { 417 my ( $Self, %Param ) = @_; 418 419 # get main object 420 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 421 422 my $RetentionTime = int( time() - 86400 ); # remove subdirs older than 24h 423 my @List = $MainObject->DirectoryRead( 424 Directory => $Self->{TempDir}, 425 Filter => '*' 426 ); 427 428 SUBDIR: 429 for my $Subdir (@List) { 430 my $SubdirTime = $Subdir; 431 432 if ( $SubdirTime =~ /^.*\/\d+\..+$/ ) { 433 $SubdirTime =~ s/^.*\/(\d+?)\..+$/$1/; 434 } 435 else { 436 $Kernel::OM->Get('Kernel::System::Log')->Log( 437 Priority => 'error', 438 Message => 439 "Won't delete upload cache directory $Subdir: timestamp in directory name not found! Please fix it manually.", 440 ); 441 next SUBDIR; 442 } 443 444 if ( $RetentionTime > $SubdirTime ) { 445 my @Sublist = $MainObject->DirectoryRead( 446 Directory => $Subdir, 447 Filter => "*", 448 ); 449 450 for my $File (@Sublist) { 451 $MainObject->FileDelete( 452 Location => $File, 453 ); 454 } 455 456 if ( !rmdir($Subdir) ) { 457 $Kernel::OM->Get('Kernel::System::Log')->Log( 458 Priority => 'error', 459 Message => "Can't remove: $Subdir: $!!", 460 ); 461 next SUBDIR; 462 } 463 } 464 } 465 466 return 1; 467} 468 469sub _FormIDValidate { 470 my ( $Self, $FormID ) = @_; 471 472 return if !$FormID; 473 474 if ( $FormID !~ m{^ \d+ \. \d+ \. \d+ $}xms ) { 475 $Kernel::OM->Get('Kernel::System::Log')->Log( 476 Priority => 'error', 477 Message => 'Invalid FormID!', 478 ); 479 return; 480 } 481 482 return 1; 483} 484 4851; 486