Skip to content

Commit 47942f5

Browse files
authored
file integrity checker (#13)
Signed-off-by: rahul <[email protected]>
1 parent 36519c2 commit 47942f5

8 files changed

+216
-18
lines changed

.env

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
SECRET_KEY="rahul_chavan"
1+
SECRET_KEY="rahul_chavan"
2+
FILE_NAME='/home/rahul/Documents/hashlist'

src/Exception/FileEncryptorException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class FileEncryptorException extends Exception
99
{
10-
public function __construct($message = "could not encrypt file", $code = 0, Throwable $previous = null)
10+
public function __construct(string $message = "could not encrypt file", int $code = 0, Throwable $previous = null)
1111
{
1212
parent::__construct($message, $code, $previous);
1313
}

src/Exception/FileHandlerException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class FileHandlerException extends Exception
99
{
10-
public function __construct($message = "There was an error", $code = 0, Throwable $previous = null)
10+
public function __construct(string $message = "There was an error", int $code = 0, Throwable $previous = null)
1111
{
1212
parent::__construct($message, $code, $previous);
1313
}

src/Exception/HashException.php

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace rcsofttech85\FileHandler\Exception;
4+
5+
use Exception;
6+
use Throwable;
7+
8+
class HashException extends Exception
9+
{
10+
public function __construct(string $message = "There was an error", int $code = 0, Throwable $previous = null)
11+
{
12+
parent::__construct($message, $code, $previous);
13+
}
14+
}

src/Exception/StreamException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class StreamException extends Exception
99
{
10-
public function __construct($message = "could not stream file", $code = 0, Throwable $previous = null)
10+
public function __construct(string $message = "could not stream file", int $code = 0, Throwable $previous = null)
1111
{
1212
parent::__construct($message, $code, $previous);
1313
}

src/FileHashChecker.php

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace rcsofttech85\FileHandler;
4+
5+
use rcsofttech85\FileHandler\Exception\HashException;
6+
7+
class FileHashChecker
8+
{
9+
const ALGO_256 = 'sha3-256';
10+
const ALGO_512 = 'sha3-512';
11+
12+
/**
13+
* @param string $filename
14+
* @throws HashException
15+
*/
16+
public function __construct(private readonly string $filename)
17+
{
18+
if (!file_exists($this->filename)) {
19+
throw new HashException('file not found');
20+
}
21+
}
22+
23+
/**
24+
* @param object $fileHandler
25+
* @param string $storedHashesFile
26+
* @param string $algo
27+
* @return bool
28+
* @throws Exception\FileHandlerException
29+
* @throws HashException
30+
*/
31+
32+
public function verifyHash(object $fileHandler, string $storedHashesFile, string $algo = self::ALGO_256): bool
33+
{
34+
if (!$fileHandler instanceof FileHandler) {
35+
throw new HashException("object must be instance of " . FileHandler::class);
36+
}
37+
38+
if (!$storedHashesFile) {
39+
throw new HashException('file not found');
40+
}
41+
42+
$file = $fileHandler->open(filename: $storedHashesFile)->searchInCsvFile(
43+
$this->filename,
44+
'File',
45+
FileHandler::ARRAY_FORMAT
46+
);
47+
48+
if (!$file) {
49+
throw new HashException('this file is not hashed');
50+
}
51+
52+
$expectedHash = $file['Hash'];
53+
$hash = $this->hashFile($algo);
54+
55+
if ($hash !== $expectedHash) {
56+
return false;
57+
}
58+
59+
return true;
60+
}
61+
62+
/**
63+
* @param string $algo
64+
* @return string
65+
* @throws HashException
66+
*/
67+
68+
public function hashFile(string $algo = self::ALGO_256): string
69+
{
70+
if (!in_array($algo, [self::ALGO_512, self::ALGO_256])) {
71+
throw new HashException('algorithm not supported');
72+
}
73+
return hash_file($algo, $this->filename);
74+
}
75+
}

tests/unit/FileEncryptorTest.php

+16-14
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,26 @@ class FileEncryptorTest extends TestCase
1414

1515
private static string $secret;
1616

17+
protected function setUp(): void
18+
{
19+
$this->fileEncryptor = new FileEncryptor('movie.csv', self::$secret);
20+
parent::setUp();
21+
}
22+
23+
protected function tearDown(): void
24+
{
25+
parent::tearDown();
26+
$this->fileEncryptor = null;
27+
}
28+
1729

1830
public static function setUpBeforeClass(): void
1931
{
2032
$dotenv = new Dotenv();
2133
$dotenv->load('.env');
22-
self::$secret = getenv('SECRET_KEY');
34+
self::$secret = $_ENV['SECRET_KEY'];
35+
36+
2337
$content = "Film,Genre,Lead Studio,Audience score %,Profitability,Rotten Tomatoes %,Worldwide Gross,Year\n"
2438
. "Zack and Miri Make a Porno,Romance,The Weinstein Company,70,1.747541667,64,$41.94 ,2008\n"
2539
. "Youth in Revolt,Comedy,The Weinstein Company,52,1.09,68,$19.62 ,2010\n"
@@ -62,7 +76,7 @@ public function throwExceptionIfAlreadyEncrypted()
6276
#[Test]
6377
public function throwExceptionIfDecryptionFails()
6478
{
65-
$this->fileEncryptor = new FileEncryptor("movie.csv", 'wrongSecret');
79+
$this->fileEncryptor = new FileEncryptor("movie.csv", 'wrong');
6680
$this->expectException(FileEncryptorException::class);
6781
$this->expectExceptionMessage('could not decrypt file');
6882
$this->fileEncryptor->decryptFile();
@@ -75,16 +89,4 @@ public function canDecryptFile()
7589

7690
$this->assertTrue($isFileDecrypted);
7791
}
78-
79-
protected function setUp(): void
80-
{
81-
$this->fileEncryptor = new FileEncryptor('movie.csv', self::$secret);
82-
parent::setUp();
83-
}
84-
85-
protected function tearDown(): void
86-
{
87-
parent::tearDown();
88-
$this->fileEncryptor = null;
89-
}
9092
}

tests/unit/FileHashCheckerTest.php

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace unit;
4+
5+
use PHPUnit\Framework\Attributes\Test;
6+
use PHPUnit\Framework\TestCase;
7+
use rcsofttech85\FileHandler\Exception\HashException;
8+
use rcsofttech85\FileHandler\FileHandler;
9+
use rcsofttech85\FileHandler\FileHashChecker;
10+
use Symfony\Component\Dotenv\Dotenv;
11+
12+
class FileHashCheckerTest extends TestCase
13+
{
14+
private static string $file;
15+
private FileHashChecker|null $fileHasher = null;
16+
17+
public static function setUpBeforeClass(): void
18+
{
19+
parent::setUpBeforeClass();
20+
21+
$dotenv = new Dotenv();
22+
$dotenv->load('.env');
23+
24+
$file = $_ENV['FILE_NAME']; // this file contains list of all hashes
25+
26+
self::$file = $file;
27+
28+
29+
file_put_contents("test", "hello world");
30+
}
31+
32+
public static function tearDownAfterClass(): void
33+
{
34+
parent::tearDownAfterClass();
35+
unlink("test");
36+
unlink("sample");
37+
}
38+
39+
#[Test]
40+
public function shouldGenerateValidHashForDifferentAlgo()
41+
{
42+
$expectedHash = "644bcc7e564373040999aac89e7622f3ca71fba1d972fd94a31c3bfbf24e3938";
43+
44+
$actualHash = $this->fileHasher->hashFile(); //default ALGO_256
45+
46+
$this->assertEquals($expectedHash, $actualHash);
47+
48+
$expectedHash = "840006653e9ac9e95117a15c915caab81662918e925de9e004f774ff82d7079a40d4d27b1b372657c61d46d470304c88c788b3a4527ad074d1dccbee5dbaa99a";
49+
50+
$actualHash = $this->fileHasher->hashFile(FileHashChecker::ALGO_512);
51+
52+
$this->assertEquals($expectedHash, $actualHash);
53+
}
54+
55+
#[Test]
56+
public function checkFileIntegrityReturnsTrueIfHashMatch()
57+
{
58+
$isVerified = $this->fileHasher->verifyHash(new FileHandler(), self::$file);
59+
60+
$this->assertTrue($isVerified);
61+
}
62+
63+
#[Test]
64+
public function shouldReturnFalseIfFileIsModified()
65+
{
66+
$backup = file_get_contents("test");
67+
file_put_contents("test", "modified", FILE_APPEND);
68+
69+
$isVerified = $this->fileHasher->verifyHash(new FileHandler(), self::$file);
70+
71+
$this->assertfalse($isVerified);
72+
73+
file_put_contents("test", $backup);
74+
}
75+
76+
#[Test]
77+
public function shouldReturnFalseIfDifferentAlgoIsUsedForVerifyHash()
78+
{
79+
$isVerified = $this->fileHasher->verifyHash(new FileHandler(), self::$file, FileHashChecker::ALGO_512);
80+
81+
$this->assertFalse($isVerified);
82+
}
83+
84+
#[Test]
85+
public function shouldThrowExceptionIfFileIsNotHashed()
86+
{
87+
file_put_contents("sample", "this file is not hashed");
88+
$this->fileHasher = new FileHashChecker("sample");
89+
90+
$this->expectException(HashException::class);
91+
$this->expectExceptionMessage("this file is not hashed");
92+
$this->fileHasher->verifyHash(new FileHandler(), self::$file, FileHashChecker::ALGO_512);
93+
}
94+
95+
protected function setUp(): void
96+
{
97+
parent::setUp();
98+
$this->fileHasher = new FileHashChecker("test");
99+
}
100+
101+
protected function tearDown(): void
102+
{
103+
parent::tearDown();
104+
$this->fileHasher = null;
105+
}
106+
}

0 commit comments

Comments
 (0)