/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3.0 only,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3.0 along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#import "OFApplication.h"
#import "OFArray.h"
#import "OFFile.h"
#import "OFIRI.h"
#import "OFIRIHandler.h"
#import "OFLocale.h"
#import "OFMD5Hash.h"
#import "OFOptionsParser.h"
#import "OFRIPEMD160Hash.h"
#import "OFSHA1Hash.h"
#import "OFSHA224Hash.h"
#import "OFSHA256Hash.h"
#import "OFSHA384Hash.h"
#import "OFSHA512Hash.h"
#import "OFSandbox.h"
#import "OFSecureData.h"
#import "OFStdIOStream.h"
#import "OFOpenItemFailedException.h"
#import "OFReadFailedException.h"
@interface OFHash: OFObject <OFApplicationDelegate>
@end
OF_APPLICATION_DELEGATE(OFHash)
static void
help(void)
{
[OFStdErr writeLine: OF_LOCALIZED(@"usage",
@"Usage: %[prog] [--md5] [--ripemd160] [--sha1] [--sha224] "
@"[--sha256] [--sha384] [--sha512] [--iri] file1 [file2 ...]",
@"prog", [OFApplication programName])];
[OFApplication terminateWithStatus: 1];
}
static void
printHash(OFString *algo, OFString *path, id <OFCryptographicHash> hash)
{
size_t digestSize = hash.digestSize;
const unsigned char *digest;
[hash calculate];
digest = hash.digest;
[OFStdOut writeFormat: @"%@ ", algo];
for (size_t i = 0; i < digestSize; i++)
[OFStdOut writeFormat: @"%02x", digest[i]];
[OFStdOut writeFormat: @" %@\n", path];
}
@implementation OFHash
- (void)applicationDidFinishLaunching: (OFNotification *)notification
{
int exitStatus = 0;
bool calculateMD5, calculateRIPEMD160, calculateSHA1, calculateSHA224;
bool calculateSHA256, calculateSHA384, calculateSHA512, isIRI;
const OFOptionsParserOption options[] = {
{ '\0', @"md5", 0, &calculateMD5, NULL },
{ '\0', @"ripemd160", 0, &calculateRIPEMD160, NULL },
{ '\0', @"sha1", 0, &calculateSHA1, NULL },
{ '\0', @"sha224", 0, &calculateSHA224, NULL },
{ '\0', @"sha256", 0, &calculateSHA256, NULL },
{ '\0', @"sha384", 0, &calculateSHA384, NULL },
{ '\0', @"sha512", 0, &calculateSHA512, NULL },
{ '\0', @"iri", 0, &isIRI, NULL },
{ '\0', nil, 0, NULL, NULL }
};
OFOptionsParser *optionsParser =
[OFOptionsParser parserWithOptions: options];
OFUnichar option;
OFMD5Hash *MD5Hash = nil;
OFRIPEMD160Hash *RIPEMD160Hash = nil;
OFSHA1Hash *SHA1Hash = nil;
OFSHA224Hash *SHA224Hash = nil;
OFSHA256Hash *SHA256Hash = nil;
OFSHA384Hash *SHA384Hash = nil;
OFSHA512Hash *SHA512Hash = nil;
#ifndef OF_AMIGAOS
[OFLocale addLocalizationDirectoryIRI:
[OFIRI fileIRIWithPath: @LOCALIZATION_DIR]];
#else
[OFLocale addLocalizationDirectoryIRI:
[OFIRI fileIRIWithPath: @"PROGDIR:/share/ofhash/localization"]];
#endif
while ((option = [optionsParser nextOption]) != '\0') {
switch (option) {
case '?':
if (optionsParser.lastLongOption != nil)
[OFStdErr writeLine: OF_LOCALIZED(
@"unknown_long_option",
@"%[prog]: Unknown option: --%[opt]",
@"prog", [OFApplication programName],
@"opt", optionsParser.lastLongOption)];
else {
OFString *optStr = [OFString stringWithFormat:
@"%C", optionsParser.lastOption];
[OFStdErr writeLine: OF_LOCALIZED(
@"unknown_option",
@"%[prog]: Unknown option: -%[opt]",
@"prog", [OFApplication programName],
@"opt", optStr)];
}
[OFApplication terminateWithStatus: 1];
break;
}
}
#ifdef OF_HAVE_SANDBOX
OFSandbox *sandbox = [OFSandbox sandbox];
@try {
sandbox.allowsStdIO = true;
sandbox.allowsReadingFiles = true;
sandbox.allowsUserDatabaseReading = true;
if (!isIRI)
for (OFString *path in optionsParser.remainingArguments)
[sandbox unveilPath: path permissions: @"r"];
[sandbox unveilPath: @LOCALIZATION_DIR permissions: @"r"];
[OFApplication of_activateSandbox: sandbox];
} @finally {
[sandbox release];
}
#endif
if (!calculateMD5 && !calculateRIPEMD160 && !calculateSHA1 &&
!calculateSHA224 && !calculateSHA256 && !calculateSHA384 &&
!calculateSHA512)
help();
if (optionsParser.remainingArguments.count < 1)
help();
if (calculateMD5)
MD5Hash = [OFMD5Hash hashWithAllowsSwappableMemory: true];
if (calculateRIPEMD160)
RIPEMD160Hash =
[OFRIPEMD160Hash hashWithAllowsSwappableMemory: true];
if (calculateSHA1)
SHA1Hash = [OFSHA1Hash hashWithAllowsSwappableMemory: true];
if (calculateSHA224)
SHA224Hash = [OFSHA224Hash hashWithAllowsSwappableMemory: true];
if (calculateSHA256)
SHA256Hash = [OFSHA256Hash hashWithAllowsSwappableMemory: true];
if (calculateSHA384)
SHA384Hash = [OFSHA384Hash hashWithAllowsSwappableMemory: true];
if (calculateSHA512)
SHA512Hash = [OFSHA512Hash hashWithAllowsSwappableMemory: true];
for (OFString *path in optionsParser.remainingArguments) {
void *pool = objc_autoreleasePoolPush();
OFStream *file;
if (!isIRI && [path isEqual: @"-"])
file = OFStdIn;
else {
@try {
if (isIRI) {
OFIRI *IRI =
[OFIRI IRIWithString: path];
file = [OFIRIHandler
openItemAtIRI: IRI
mode: @"r"];
} else
file = [OFFile fileWithPath: path
mode: @"r"];
} @catch (OFOpenItemFailedException *e) {
OFString *error = [OFString
stringWithCString: strerror(e.errNo)
encoding: [OFLocale encoding]];
OFString *filePath =
(e.IRI != nil ? e.IRI.string : e.path);
[OFStdErr writeLine: OF_LOCALIZED(
@"failed_to_open_file",
@"Failed to open file %[file]: %[error]",
@"file", filePath,
@"error", error)];
exitStatus = 1;
goto outer_loop_end;
}
}
[MD5Hash reset];
[RIPEMD160Hash reset];
[SHA1Hash reset];
[SHA224Hash reset];
[SHA256Hash reset];
[SHA384Hash reset];
[SHA512Hash reset];
while (!file.atEndOfStream) {
uint8_t buffer[1024];
size_t length;
@try {
length = [file readIntoBuffer: buffer
length: 1024];
} @catch (OFReadFailedException *e) {
OFString *error = [OFString
stringWithCString: strerror(e.errNo)
encoding: [OFLocale encoding]];
[OFStdErr writeLine: OF_LOCALIZED(
@"failed_to_read_file",
@"Failed to read %[file]: %[error]",
@"file", path,
@"error", error)];
exitStatus = 1;
goto outer_loop_end;
}
if (calculateMD5)
[MD5Hash updateWithBuffer: buffer
length: length];
if (calculateRIPEMD160)
[RIPEMD160Hash updateWithBuffer: buffer
length: length];
if (calculateSHA1)
[SHA1Hash updateWithBuffer: buffer
length: length];
if (calculateSHA224)
[SHA224Hash updateWithBuffer: buffer
length: length];
if (calculateSHA256)
[SHA256Hash updateWithBuffer: buffer
length: length];
if (calculateSHA384)
[SHA384Hash updateWithBuffer: buffer
length: length];
if (calculateSHA512)
[SHA512Hash updateWithBuffer: buffer
length: length];
}
[file close];
if (calculateMD5)
printHash(@"MD5", path, MD5Hash);
if (calculateRIPEMD160)
printHash(@"RIPEMD160", path, RIPEMD160Hash);
if (calculateSHA1)
printHash(@"SHA1", path, SHA1Hash);
if (calculateSHA224)
printHash(@"SHA224", path, SHA224Hash);
if (calculateSHA256)
printHash(@"SHA256", path, SHA256Hash);
if (calculateSHA384)
printHash(@"SHA384", path, SHA384Hash);
if (calculateSHA512)
printHash(@"SHA512", path, SHA512Hash);
outer_loop_end:
objc_autoreleasePoolPop(pool);
}
[OFApplication terminateWithStatus: exitStatus];
}
@end