added untracked carthage files. Pretty sure I should exclude the build directory but for now it remains.

This commit is contained in:
Robert McGovern 2022-04-20 12:39:58 +01:00
parent d861a1abec
commit 16e138204b
20 changed files with 3786 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
version: 2.1
executors:
my-xcode:
macos:
xcode: 12.0.1
workflows:
version: 2
test-validate-release:
jobs:
# Testing
- test-Mac
- test-iOS
- test-ExampleApp
# Validation
- validate-CocoaPods:
requires:
- test-Mac
- test-iOS
- test-ExampleApp
- validate-Carthage:
requires:
- test-Mac
- test-iOS
- test-ExampleApp
# Release
# - release:
# # Only run for tags
# filters:
# branches:
# ignore: /.*/
# tags:
# only: /.*/
# requires:
# - validate-CocoaPods
# - validate-Carthage
jobs:
test-Mac:
executor: my-xcode
steps:
- checkout
# The CLANG arguments and find command fail the build on analyzer errors
- run: xcodebuild -workspace UnrarKit.xcworkspace -scheme UnrarKit -sdk macosx -configuration Release -quiet analyze test CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]]
test-iOS:
executor: my-xcode
steps:
- checkout
# The CLANG arguments and find command fail the build on analyzer errors
- run: xcodebuild -workspace UnrarKit.xcworkspace -scheme UnrarKit -destination 'platform=iOS Simulator,name=iPhone 11,OS=latest' -configuration Release analyze test CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]]
test-ExampleApp:
executor: my-xcode
steps:
- checkout
# The CLANG arguments and find command fail the build on analyzer errors
- run: xcodebuild -workspace UnrarKit.xcworkspace -scheme UnrarExample -sdk iphonesimulator -configuration Release analyze CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer-output && [[ -z `find analyzer-output -name "*.html"` ]]
validate-CocoaPods:
executor: my-xcode
steps:
- checkout
- run: ./Scripts/cocoapod-validate.sh
validate-Carthage:
executor: my-xcode
steps:
- checkout
- run: ./Scripts/carthage-validate.sh
# release:
# executor: my-xcode
# steps:
# - checkout
# - run: ./Scripts/push-output.sh

View File

@ -0,0 +1,24 @@
# Contributing to UnrarKit
First of all, if you're reading this, thanks! I love getting feedback from other developers who use this library and welcome any and all feedback. Issues and Pull requests are welcome, with a few guidelines, laid out below.
# Issues
I need the following, at a minimum:
1. The steps to reproduce your issue, detailed enough for me to follow along and see what you're seeing (bonus points for giving me a sample archive that demonstrates the issue)
2. What you expect to happen
3. What actually happened
If what you're reporting is a crash, a crash report is key (or a stack trace, if you don't have a full crash report).
Beyond that, the **quickest way** to get me to address what you're asking for is to provide a unit test (or tests) to demonstrate what you'd like to see (they should probably fail). I have a pretty complete set of tests already in the library that you can use as an example if you'd like.
# Pull Requests
Pull Requests are always greatly appreciated. The general rule of thumb for how quickly something will make it into a future release is the inverse of how much work I'll need to do on it. Creating a PR instead of an issue report takes a huge burden off of me. If you do create a PR, I'll require these things before I merge it:
1. Style needs to mesh with the rest of the repo. I'd love to have some style enforcement checks at some point, but don't yet. Do your best according to what you see in the rest of the source and I'll point out anything you missed. No big deal
2. If you're fixing a bug, I need to see a unit test that reproduces the issue(s) by failing if I comment out your fix. I try to maintain code coverage that's as complete as possible, so more unit tests are always welcome
3. Don't touch the `Libraries/unrar` directory. This is the UnRAR source code, downloaded from [the RARLAB site](https://www.rarlab.com/rar_add.htm), and updated occasionally. You can usually look at the revision history of that folder to see what version it's currently on. This library does cause some compiler warnings, but I ignore them in Xcode and CocoaPods, relying on unit tests to tell me if anything is truly broken, since I don't maintain patches - I expect to be able to drop in newer versions as needed
4. Be patient with the process - I maintain a high attention to detail, and will take however much time is necessary to get the change to where I'd like it. If you'd rather have a more brief back-and-forth, I can always pull into a separate branch to make the changes myself before merging, but enjoy the interaction of collaborating on PRs whenever possible

View File

@ -0,0 +1,28 @@
Thanks for making a contribution to UnrarKit! I greatly value issue reports, which help make the library better, and more useful to more people. To help me address your issue, please follow the steps below.
_Delete from this line up_
- [ ] Provide a brief, descriptive issue title
- [ ] Fill out the template below
- [ ] **Option A** is the easier, more traditional way of reporting a bug
- [ ] **Option B** gets you bonus points (redeemable in the form of me looking at your issue more quickly). Create a failing unit test that would pass if the issue were resolved. When I go to fix your issue, it's one of the first things I would do anyway. There is a pretty complete set already there, so you can use those as a guide to creating new ones.
## Option A
### Steps to reproduce issue (Detailed enough for me to reproduce - attaching a sample archive can be very helpful):
### What you expect to happen:
### What actually happened:
### (_Optional_) A stack trace or crash report, if you have one:
## Option B
### Brief summary of issue:
### Link to your branch where you've created automated tests to demonstrate the issue (and the name of the unit test(s) you created):

View File

@ -0,0 +1,352 @@
// Return 'true' if we need to exclude the file from processing as result
// of -x switch. If CheckInclList is true, we also check the file against
// the include list created with -n switch.
bool CommandData::ExclCheck(const wchar *CheckName,bool Dir,bool CheckFullPath,bool CheckInclList)
{
if (CheckArgs(&ExclArgs,Dir,CheckName,CheckFullPath,MATCH_WILDSUBPATH))
return true;
if (!CheckInclList || InclArgs.ItemsCount()==0)
return false;
if (CheckArgs(&InclArgs,Dir,CheckName,CheckFullPath,MATCH_WILDSUBPATH))
return false;
return true;
}
bool CommandData::CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,bool CheckFullPath,int MatchMode)
{
wchar *Name=ConvertPath(CheckName,NULL,0);
wchar FullName[NM];
wchar CurMask[NM];
*FullName=0;
Args->Rewind();
while (Args->GetString(CurMask,ASIZE(CurMask)))
{
wchar *LastMaskChar=PointToLastChar(CurMask);
bool DirMask=IsPathDiv(*LastMaskChar); // Mask for directories only.
if (Dir)
{
// CheckName is a directory.
if (DirMask)
{
// We process the directory and have the directory exclusion mask.
// So let's convert "mask\" to "mask" and process it normally.
*LastMaskChar=0;
}
else
{
// REMOVED, we want -npath\* to match empty folders too.
// If mask has wildcards in name part and does not have the trailing
// '\' character, we cannot use it for directories.
// if (IsWildcard(PointToName(CurMask)))
// continue;
}
}
else
{
// If we process a file inside of directory excluded by "dirmask\".
// we want to exclude such file too. So we convert "dirmask\" to
// "dirmask\*". It is important for operations other than archiving
// with -x. When archiving with -x, directory matched by "dirmask\"
// is excluded from further scanning.
if (DirMask)
wcsncatz(CurMask,L"*",ASIZE(CurMask));
}
#ifndef SFX_MODULE
if (CheckFullPath && IsFullPath(CurMask))
{
// We do not need to do the special "*\" processing here, because
// unlike the "else" part of this "if", now we convert names to full
// format, so they all include the path, which is matched by "*\"
// correctly. Moreover, removing "*\" from mask would break
// the comparison, because now all names have the path.
if (*FullName==0)
ConvertNameToFull(CheckName,FullName,ASIZE(FullName));
if (CmpName(CurMask,FullName,MatchMode))
return true;
}
else
#endif
{
wchar NewName[NM+2],*CurName=Name;
// Important to convert before "*\" check below, so masks like
// d:*\something are processed properly.
wchar *CmpMask=ConvertPath(CurMask,NULL,0);
if (CmpMask[0]=='*' && IsPathDiv(CmpMask[1]))
{
// We want "*\name" to match 'name' not only in subdirectories,
// but also in the current directory. We convert the name
// from 'name' to '.\name' to be matched by "*\" part even if it is
// in current directory.
NewName[0]='.';
NewName[1]=CPATHDIVIDER;
wcsncpyz(NewName+2,Name,ASIZE(NewName)-2);
CurName=NewName;
}
if (CmpName(CmpMask,CurName,MatchMode))
return true;
}
}
return false;
}
#ifndef SFX_MODULE
// Now this function performs only one task and only in Windows version:
// it skips symlinks to directories if -e1024 switch is specified.
// Symlinks are skipped in ScanTree class, so their entire contents
// is skipped too. Without this function we would check the attribute
// only directly before archiving, so we would skip the symlink record,
// but not the contents of symlinked directory.
bool CommandData::ExclDirByAttr(uint FileAttr)
{
#ifdef _WIN_ALL
if ((FileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0 &&
(ExclFileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0)
return true;
#endif
return false;
}
#endif
#if !defined(SFX_MODULE)
void CommandData::SetTimeFilters(const wchar *Mod,bool Before,bool Age)
{
bool ModeOR=false,TimeMods=false;
const wchar *S=Mod;
// Check if any 'mca' modifiers are present, set OR mode if 'o' is present,
// skip modifiers and set S to beginning of time string. Be sure to check
// *S!=0, because termination 0 is a part of string for wcschr.
for (;*S!=0 && wcschr(L"MCAOmcao",*S)!=NULL;S++)
if (*S=='o' || *S=='O')
ModeOR=true;
else
TimeMods=true;
if (!TimeMods) // Assume 'm' if no modifiers are specified.
Mod=L"m";
// Set the specified time for every modifier. Be sure to check *Mod!=0,
// because termination 0 is a part of string for wcschr. This check is
// important when we set Mod to "m" above.
for (;*Mod!=0 && wcschr(L"MCAOmcao",*Mod)!=NULL;Mod++)
switch(toupperw(*Mod))
{
case 'M':
if (Before)
{
Age ? FileMtimeBefore.SetAgeText(S):FileMtimeBefore.SetIsoText(S);
FileMtimeBeforeOR=ModeOR;
}
else
{
Age ? FileMtimeAfter.SetAgeText(S):FileMtimeAfter.SetIsoText(S);
FileMtimeAfterOR=ModeOR;
}
break;
case 'C':
if (Before)
{
Age ? FileCtimeBefore.SetAgeText(S):FileCtimeBefore.SetIsoText(S);
FileCtimeBeforeOR=ModeOR;
}
else
{
Age ? FileCtimeAfter.SetAgeText(S):FileCtimeAfter.SetIsoText(S);
FileCtimeAfterOR=ModeOR;
}
break;
case 'A':
if (Before)
{
Age ? FileAtimeBefore.SetAgeText(S):FileAtimeBefore.SetIsoText(S);
FileAtimeBeforeOR=ModeOR;
}
else
{
Age ? FileAtimeAfter.SetAgeText(S):FileAtimeAfter.SetIsoText(S);
FileAtimeAfterOR=ModeOR;
}
break;
}
}
#endif
#ifndef SFX_MODULE
// Return 'true' if we need to exclude the file from processing.
bool CommandData::TimeCheck(RarTime &ftm,RarTime &ftc,RarTime &fta)
{
bool FilterOR=false;
if (FileMtimeBefore.IsSet()) // Filter present.
if (ftm>=FileMtimeBefore) // Condition not matched.
if (FileMtimeBeforeOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileMtimeBeforeOR)
return false; // Include file in OR mode.
if (FileMtimeAfter.IsSet()) // Filter present.
if (ftm<FileMtimeAfter) // Condition not matched.
if (FileMtimeAfterOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileMtimeAfterOR)
return false; // Include file in OR mode.
if (FileCtimeBefore.IsSet()) // Filter present.
if (ftc>=FileCtimeBefore) // Condition not matched.
if (FileCtimeBeforeOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileCtimeBeforeOR)
return false; // Include file in OR mode.
if (FileCtimeAfter.IsSet()) // Filter present.
if (ftc<FileCtimeAfter) // Condition not matched.
if (FileCtimeAfterOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileCtimeAfterOR)
return false; // Include file in OR mode.
if (FileAtimeBefore.IsSet()) // Filter present.
if (fta>=FileAtimeBefore) // Condition not matched.
if (FileAtimeBeforeOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileAtimeBeforeOR)
return false; // Include file in OR mode.
if (FileAtimeAfter.IsSet()) // Filter present.
if (fta<FileAtimeAfter) // Condition not matched.
if (FileAtimeAfterOR)
FilterOR=true; // Not matched OR filter is present.
else
return true; // Exclude file in AND mode.
else // Condition matched.
if (FileAtimeAfterOR)
return false; // Include file in OR mode.
return FilterOR; // Exclude if all OR filters are not matched.
}
#endif
#ifndef SFX_MODULE
// Return 'true' if we need to exclude the file from processing.
bool CommandData::SizeCheck(int64 Size)
{
if (FileSizeLess!=INT64NDF && Size>=FileSizeLess)
return true;
if (FileSizeMore!=INT64NDF && Size<=FileSizeMore)
return true;
return false;
}
#endif
// Return 0 if file must not be processed or a number of matched parameter otherwise.
int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchType,
bool Flags,wchar *MatchedArg,uint MatchedArgSize)
{
if (MatchedArg!=NULL && MatchedArgSize>0)
*MatchedArg=0;
bool Dir=FileHead.Dir;
if (ExclCheck(FileHead.FileName,Dir,false,true))
return 0;
#ifndef SFX_MODULE
if (TimeCheck(FileHead.mtime,FileHead.ctime,FileHead.atime))
return 0;
if ((FileHead.FileAttr & ExclFileAttr)!=0 || FileHead.Dir && ExclDir)
return 0;
if (InclAttrSet && (!FileHead.Dir && (FileHead.FileAttr & InclFileAttr)==0 ||
FileHead.Dir && !InclDir))
return 0;
if (!Dir && SizeCheck(FileHead.UnpSize))
return 0;
#endif
wchar *ArgName;
FileArgs.Rewind();
for (int StringCount=1;(ArgName=FileArgs.GetString())!=NULL;StringCount++)
if (CmpName(ArgName,FileHead.FileName,MatchType))
{
if (ExactMatch!=NULL)
*ExactMatch=wcsicompc(ArgName,FileHead.FileName)==0;
if (MatchedArg!=NULL)
wcsncpyz(MatchedArg,ArgName,MatchedArgSize);
return StringCount;
}
return 0;
}
#if !defined(SFX_MODULE)
void CommandData::SetStoreTimeMode(const wchar *S)
{
if (*S==0 || IsDigit(*S) || *S=='-' || *S=='+')
{
// Apply -ts, -ts1, -ts-, -ts+ to all 3 times.
// Handle obsolete -ts[2,3,4] as ts+.
EXTTIME_MODE Mode=EXTTIME_MAX;
if (*S=='-')
Mode=EXTTIME_NONE;
if (*S=='1')
Mode=EXTTIME_1S;
xmtime=xctime=xatime=Mode;
S++;
}
while (*S!=0)
{
EXTTIME_MODE Mode=EXTTIME_MAX;
if (S[1]=='-')
Mode=EXTTIME_NONE;
if (S[1]=='1')
Mode=EXTTIME_1S;
switch(toupperw(*S))
{
case 'M':
xmtime=Mode;
break;
case 'C':
xctime=Mode;
break;
case 'A':
xatime=Mode;
break;
case 'P':
PreserveAtime=true;
break;
}
S++;
}
}
#endif

View File

@ -0,0 +1,118 @@
void CommandData::OutTitle()
{
if (BareOutput || DisableCopyright)
return;
#if defined(__GNUC__) && defined(SFX_MODULE)
mprintf(St(MCopyrightS));
#else
#ifndef SILENT
static bool TitleShown=false;
if (TitleShown)
return;
TitleShown=true;
wchar Version[80];
if (RARVER_BETA!=0)
swprintf(Version,ASIZE(Version),L"%d.%02d %ls %d",RARVER_MAJOR,RARVER_MINOR,St(MBeta),RARVER_BETA);
else
swprintf(Version,ASIZE(Version),L"%d.%02d",RARVER_MAJOR,RARVER_MINOR);
#if defined(_WIN_32) || defined(_WIN_64)
wcsncatz(Version,L" ",ASIZE(Version));
#endif
#ifdef _WIN_32
wcsncatz(Version,St(Mx86),ASIZE(Version));
#endif
#ifdef _WIN_64
wcsncatz(Version,St(Mx64),ASIZE(Version));
#endif
if (PrintVersion)
{
mprintf(L"%s",Version);
exit(0);
}
mprintf(St(MUCopyright),Version,RARVER_YEAR);
#endif
#endif
}
inline bool CmpMSGID(MSGID i1,MSGID i2)
{
#ifdef MSGID_INT
return i1==i2;
#else
// If MSGID is const char*, we cannot compare pointers only.
// Pointers to different instances of same string can differ,
// so we need to compare complete strings.
return wcscmp(i1,i2)==0;
#endif
}
void CommandData::OutHelp(RAR_EXIT ExitCode)
{
#if !defined(SILENT)
OutTitle();
static MSGID Help[]={
#ifdef SFX_MODULE
// Console SFX switches definition.
MCHelpCmd,MSHelpCmdE,MSHelpCmdT,MSHelpCmdV
#else
// UnRAR switches definition.
MUNRARTitle1,MRARTitle2,MCHelpCmd,MCHelpCmdE,MCHelpCmdL,
MCHelpCmdP,MCHelpCmdT,MCHelpCmdV,MCHelpCmdX,MCHelpSw,MCHelpSwm,
MCHelpSwAT,MCHelpSwAC,MCHelpSwAD,MCHelpSwAG,MCHelpSwAI,MCHelpSwAP,
MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU,
MCHelpSwDH,MCHelpSwEP,MCHelpSwEP3,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR,
MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwN,MCHelpSwNa,MCHelpSwNal,
MCHelpSwO,MCHelpSwOC,MCHelpSwOL,MCHelpSwOR,MCHelpSwOW,MCHelpSwP,
MCHelpSwPm,MCHelpSwR,MCHelpSwRI,MCHelpSwSC,MCHelpSwSL,MCHelpSwSM,
MCHelpSwTA,MCHelpSwTB,MCHelpSwTN,MCHelpSwTO,MCHelpSwTS,MCHelpSwU,
MCHelpSwVUnr,MCHelpSwVER,MCHelpSwVP,MCHelpSwX,MCHelpSwXa,MCHelpSwXal,
MCHelpSwY
#endif
};
for (uint I=0;I<ASIZE(Help);I++)
{
#ifndef SFX_MODULE
if (CmpMSGID(Help[I],MCHelpSwV))
continue;
#ifndef _WIN_ALL
static MSGID Win32Only[]={
MCHelpSwIEML,MCHelpSwVD,MCHelpSwAO,MCHelpSwOS,MCHelpSwIOFF,
MCHelpSwEP2,MCHelpSwOC,MCHelpSwONI,MCHelpSwDR,MCHelpSwRI
};
bool Found=false;
for (uint J=0;J<ASIZE(Win32Only);J++)
if (CmpMSGID(Help[I],Win32Only[J]))
{
Found=true;
break;
}
if (Found)
continue;
#endif
#if !defined(_UNIX) && !defined(_WIN_ALL)
if (CmpMSGID(Help[I],MCHelpSwOW))
continue;
#endif
#if !defined(_WIN_ALL) && !defined(_EMX)
if (CmpMSGID(Help[I],MCHelpSwAC))
continue;
#endif
#ifndef SAVE_LINKS
if (CmpMSGID(Help[I],MCHelpSwOL))
continue;
#endif
#ifndef RAR_SMP
if (CmpMSGID(Help[I],MCHelpSwMT))
continue;
#endif
#endif
mprintf(St(Help[I]));
}
mprintf(L"\n");
ErrHandler.Exit(ExitCode);
#endif
}

View File

@ -0,0 +1,13 @@
EXPORTS
RAROpenArchive
RAROpenArchiveEx
RARCloseArchive
RARReadHeader
RARReadHeaderEx
RARProcessFile
RARProcessFileW
RARSetCallback
RARSetChangeVolProc
RARSetProcessDataProc
; RARSetPassword
RARGetDllVersion

View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Works around a bug in Xcode 12. See Carthage issue #3019:
# https://github.com/Carthage/Carthage/issues/3019
# Usage example: ./carthage.sh build --platform iOS
set -euo pipefail
xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX)
trap 'rm -f "$xcconfig"' INT TERM HUP EXIT
# For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise
# the build will fail on lipo due to duplicate architectures.
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig
echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig
export XCODE_XCCONFIG_FILE="$xcconfig"
carthage "$@"

View File

@ -0,0 +1,174 @@
//
// ExtractBufferedDataTests.m
// UnrarKit
//
//
#import "URKArchiveTestCase.h"
@import os.log;
@import os.signpost;
@interface ExtractBufferedDataTests : URKArchiveTestCase @end
@implementation ExtractBufferedDataTests
- (void)testExtractBufferedData
{
NSURL *archiveURL = self.testFileURLs[@"Test Archive.rar"];
NSString *extractedFile = @"Test File B.jpg";
URKArchive *archive = [[URKArchive alloc] initWithURL:archiveURL error:nil];
NSError *error = nil;
NSMutableData *reconstructedFile = [NSMutableData data];
BOOL success = [archive extractBufferedDataFromFile:extractedFile
error:&error
action:
^(NSData *dataChunk, CGFloat percentDecompressed) {
NSLog(@"Decompressed: %f%%", percentDecompressed);
[reconstructedFile appendBytes:dataChunk.bytes
length:dataChunk.length];
}];
XCTAssertTrue(success, @"Failed to read buffered data");
XCTAssertNil(error, @"Error reading buffered data");
XCTAssertGreaterThan(reconstructedFile.length, 0, @"No data returned");
NSData *originalFile = [NSData dataWithContentsOfURL:self.testFileURLs[extractedFile]];
XCTAssertTrue([originalFile isEqualToData:reconstructedFile],
@"File extracted in buffer not returned correctly");
}
- (void)testExtractBufferedData_ModifiedCRC
{
NSURL *archiveURL = self.testFileURLs[@"Modified CRC Archive.rar"];
NSString *extractedFile = @"README.md";
URKArchive *archive = [[URKArchive alloc] initWithURL:archiveURL error:nil];
NSError *error = nil;
NSMutableData *reconstructedFile = [NSMutableData data];
BOOL success = [archive extractBufferedDataFromFile:extractedFile
error:&error
action:
^(NSData *dataChunk, CGFloat percentDecompressed) {
NSLog(@"Decompressed: %f%%", percentDecompressed);
[reconstructedFile appendBytes:dataChunk.bytes
length:dataChunk.length];
}];
XCTAssertFalse(success, @"Failed to read buffered data");
XCTAssertNotNil(error, @"Error reading buffered data");
NSData *originalFile = [NSData dataWithContentsOfURL:self.testFileURLs[extractedFile]];
XCTAssertTrue([originalFile isEqualToData:reconstructedFile],
@"File extracted in buffer not returned correctly");
}
- (void)testExtractBufferedData_ModifiedCRC_IgnoringMismatches
{
NSURL *archiveURL = self.testFileURLs[@"Modified CRC Archive.rar"];
NSString *extractedFile = @"README.md";
URKArchive *archive = [[URKArchive alloc] initWithURL:archiveURL error:nil];
archive.ignoreCRCMismatches = YES;
NSError *error = nil;
NSMutableData *reconstructedFile = [NSMutableData data];
BOOL success = [archive extractBufferedDataFromFile:extractedFile
error:&error
action:
^(NSData *dataChunk, CGFloat percentDecompressed) {
NSLog(@"Decompressed: %f%%", percentDecompressed);
[reconstructedFile appendBytes:dataChunk.bytes
length:dataChunk.length];
}];
XCTAssertTrue(success, @"Failed to read buffered data");
XCTAssertNil(error, @"Error reading buffered data");
XCTAssertGreaterThan(reconstructedFile.length, 0, @"No data returned");
NSData *originalFile = [NSData dataWithContentsOfURL:self.testFileURLs[extractedFile]];
XCTAssertTrue([originalFile isEqualToData:reconstructedFile],
@"File extracted in buffer not returned correctly");
}
#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
- (void)testExtractBufferedData_VeryLarge
{
os_log_t log = os_log_create("UnrarKit-testExtractBufferedData_VeryLarge", OS_LOG_CATEGORY_POINTS_OF_INTEREST);
os_signpost_id_t createTextFileID;
if (@available(macOS 10.14, *)) {
createTextFileID = os_signpost_id_generate(log);
os_signpost_interval_begin(log, createTextFileID, "Create Text File");
}
NSURL *largeTextFile = [self randomTextFileOfLength:1000000]; // Increase for a more dramatic test
if (@available(macOS 10.14, *)) {
XCTAssertNotNil(largeTextFile, @"No large text file URL returned");
os_signpost_interval_end(log, createTextFileID, "Create Text File");
}
os_signpost_id_t archiveDataID;
if (@available(macOS 10.14, *)) {
archiveDataID = os_signpost_id_generate(log);
os_signpost_interval_begin(log, archiveDataID, "Archive Data");
}
NSURL *archiveURL = [self archiveWithFiles:@[largeTextFile]];
XCTAssertNotNil(archiveURL, @"No archived large text file URL returned");
if (@available(macOS 10.14, *)) {
os_signpost_interval_end(log, archiveDataID, "Archive Data");
}
NSURL *deflatedFileURL = [self.tempDirectory URLByAppendingPathComponent:@"DeflatedTextFile.txt"];
BOOL createSuccess = [[NSFileManager defaultManager] createFileAtPath:deflatedFileURL.path
contents:nil
attributes:nil];
XCTAssertTrue(createSuccess, @"Failed to create empty deflate file");
NSError *handleError = nil;
NSFileHandle *deflated = [NSFileHandle fileHandleForWritingToURL:deflatedFileURL
error:&handleError];
XCTAssertNil(handleError, @"Error creating a file handle");
URKArchive *archive = [[URKArchive alloc] initWithURL:archiveURL error:nil];
os_signpost_id_t extractDataID;
if (@available(macOS 10.14, *)) {
extractDataID = os_signpost_id_generate(log);
os_signpost_interval_begin(log, extractDataID, "Extract Data");
}
NSError *error = nil;
BOOL success = [archive extractBufferedDataFromFile:largeTextFile.lastPathComponent
error:&error
action:
^(NSData *dataChunk, CGFloat percentDecompressed) {
NSLog(@"Decompressed: %f%%", percentDecompressed);
[deflated writeData:dataChunk];
}];
if (@available(macOS 10.14, *)) {
os_signpost_interval_end(log, extractDataID, "Extract Data");
}
XCTAssertTrue(success, @"Failed to read buffered data");
XCTAssertNil(error, @"Error reading buffered data");
[deflated closeFile];
NSData *deflatedData = [NSData dataWithContentsOfURL:deflatedFileURL];
NSData *fileData = [NSData dataWithContentsOfURL:largeTextFile];
XCTAssertTrue([fileData isEqualToData:deflatedData], @"Data didn't restore correctly");
}
#endif
@end

View File

@ -0,0 +1,150 @@
//
// ExtractDataTests.m
// UnrarKit
//
//
#import "URKArchiveTestCase.h"
@interface ExtractDataTests : URKArchiveTestCase
@end
@implementation ExtractDataTests
- (void)testExtractData
{
NSArray *testArchives = @[@"Test Archive.rar",
@"Test Archive (Password).rar",
@"Test Archive (Header Password).rar"];
NSSet *expectedFileSet = [self.testFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
for (NSString *testArchiveName in testArchives) {
NSURL *testArchiveURL = self.testFileURLs[testArchiveName];
NSString *password = ([testArchiveName rangeOfString:@"Password"].location != NSNotFound
? @"password"
: nil);
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:password error:nil];
NSError *error = nil;
NSArray *fileInfos = [archive listFileInfo:&error];
XCTAssertNil(error, @"Error reading file info");
for (NSInteger i = 0; i < expectedFiles.count; i++) {
NSString *expectedFilename = expectedFiles[i];
NSError *error = nil;
NSData *extractedData = [archive extractDataFromFile:expectedFilename error:&error];
XCTAssertNil(error, @"Error in extractData:error:");
NSData *expectedFileData = [NSData dataWithContentsOfURL:self.testFileURLs[expectedFilename]];
XCTAssertNotNil(extractedData, @"No data extracted");
XCTAssertTrue([expectedFileData isEqualToData:extractedData], @"Extracted data doesn't match original file");
error = nil;
NSData *dataFromFileInfo = [archive extractData:fileInfos[i] error:&error];
XCTAssertNil(error, @"Error extracting data by file info");
XCTAssertTrue([expectedFileData isEqualToData:dataFromFileInfo], @"Extracted data from file info doesn't match original file");
}
}
}
- (void)testExtractData_Unicode
{
NSSet *expectedFileSet = [self.unicodeFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
NSURL *testArchiveURL = self.unicodeFileURLs[@"Ⓣest Ⓐrchive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
NSError *error = nil;
NSArray *fileInfos = [archive listFileInfo:&error];
XCTAssertNil(error, @"Error reading file info");
for (NSInteger i = 0; i < expectedFiles.count; i++) {
NSString *expectedFilename = expectedFiles[i];
NSError *error = nil;
NSData *extractedData = [archive extractDataFromFile:expectedFilename error:&error];
XCTAssertNil(error, @"Error in extractData:error:");
NSData *expectedFileData = [NSData dataWithContentsOfURL:self.unicodeFileURLs[expectedFilename]];
XCTAssertNotNil(extractedData, @"No data extracted");
XCTAssertTrue([expectedFileData isEqualToData:extractedData], @"Extracted data doesn't match original file");
error = nil;
NSData *dataFromFileInfo = [archive extractData:fileInfos[i] error:&error];
XCTAssertNil(error, @"Error extracting data by file info");
XCTAssertTrue([expectedFileData isEqualToData:dataFromFileInfo], @"Extracted data from file info doesn't match original file");
}
}
- (void)testExtractData_NoPassword
{
NSArray *testArchives = @[@"Test Archive (Password).rar",
@"Test Archive (Header Password).rar"];
for (NSString *testArchiveName in testArchives) {
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[testArchiveName] error:nil];
NSError *error = nil;
NSData *data = [archive extractDataFromFile:@"Test File A.txt" error:&error];
XCTAssertNotNil(error, @"Extract data without password succeeded");
XCTAssertNil(data, @"Data returned without password");
XCTAssertEqual(error.code, URKErrorCodeMissingPassword, @"Unexpected error code returned");
}
}
- (void)testExtractData_InvalidArchive
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Test File A.txt"] error:nil];
NSError *error = nil;
NSData *data = [archive extractDataFromFile:@"Any file.txt" error:&error];
XCTAssertNotNil(error, @"Extract data for invalid archive succeeded");
XCTAssertNil(data, @"Data returned for invalid archive");
XCTAssertEqual(error.code, URKErrorCodeBadArchive, @"Unexpected error code returned");
}
- (void)testExtractData_ModifiedCRC
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Modified CRC Archive.rar"] error:nil];
NSError *error = nil;
NSData *data = [archive extractDataFromFile:@"README.md" error:&error];
XCTAssertNotNil(error, @"Extract data for invalid archive succeeded");
XCTAssertNil(data, @"Data returned for invalid archive");
XCTAssertEqual(error.code, URKErrorCodeBadData, @"Unexpected error code returned");
}
- (void)testExtractData_ModifiedCRC_IgnoringMismatches
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Modified CRC Archive.rar"] error:nil];
archive.ignoreCRCMismatches = YES;
NSError *error = nil;
NSData *data = [archive extractDataFromFile:@"README.md" error:&error];
NSData *expectedData = [NSData dataWithContentsOfURL:self.testFileURLs[@"README.md"]];
XCTAssertNil(error, @"Extract data for invalid archive succeeded");
XCTAssertNotNil(data, @"Data returned for invalid archive");
XCTAssertEqualObjects(data, expectedData);
}
@end

View File

@ -0,0 +1,213 @@
//
// ListFileInfoTests.m
// UnrarKit
//
//
#import "URKArchiveTestCase.h"
@interface ListFileInfoTests : URKArchiveTestCase
@end
@implementation ListFileInfoTests
- (void)testListFileInfo {
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Test Archive.rar"] error:nil];
NSSet *expectedFileSet = [self.testFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
static NSDateFormatter *testFileInfoDateFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
testFileInfoDateFormatter = [[NSDateFormatter alloc] init];
testFileInfoDateFormatter.dateFormat = @"M/dd/yyyy h:mm a";
testFileInfoDateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
});
NSDictionary *expectedTimestamps = @{@"Test File A.txt": [testFileInfoDateFormatter dateFromString:@"3/13/2014 8:02 PM"],
@"Test File B.jpg": [testFileInfoDateFormatter dateFromString:@"3/13/2014 8:04 PM"],
@"Test File C.m4a": [testFileInfoDateFormatter dateFromString:@"3/13/2014 8:05 PM"],};
NSError *error = nil;
NSArray *filesInArchive = [archive listFileInfo:&error];
XCTAssertNil(error, @"Error returned by listFileInfo");
XCTAssertNotNil(filesInArchive, @"No list of files returned");
XCTAssertEqual(filesInArchive.count, expectedFileSet.count, @"Incorrect number of files listed in archive");
NSFileManager *fm = [NSFileManager defaultManager];
for (NSInteger i = 0; i < filesInArchive.count; i++) {
URKFileInfo *fileInfo = filesInArchive[i];
// Test Archive Name
NSString *expectedArchiveName = archive.filename;
XCTAssertEqualObjects(fileInfo.archiveName, expectedArchiveName, @"Incorrect archive name");
// Test Filename
NSString *expectedFilename = expectedFiles[i];
XCTAssertEqualObjects(fileInfo.filename, expectedFilename, @"Incorrect filename");
// Test CRC
NSUInteger expectedFileCRC = [self crcOfTestFile:expectedFilename];
XCTAssertEqual(fileInfo.CRC, expectedFileCRC, @"Incorrect CRC checksum");
// Test Last Modify Date
NSTimeInterval archiveFileTimeInterval = [fileInfo.timestamp timeIntervalSinceReferenceDate];
NSTimeInterval expectedFileTimeInterval = [expectedTimestamps[fileInfo.filename] timeIntervalSinceReferenceDate];
XCTAssertEqualWithAccuracy(archiveFileTimeInterval, expectedFileTimeInterval, 60, @"Incorrect file timestamp (more than 60 seconds off)");
// Test Uncompressed Size
NSError *attributesError = nil;
NSString *expectedFilePath = [[self urlOfTestFile:expectedFilename] path];
NSDictionary *expectedFileAttributes = [fm attributesOfItemAtPath:expectedFilePath
error:&attributesError];
XCTAssertNil(attributesError, @"Error getting file attributes of %@", expectedFilename);
long long expectedFileSize = expectedFileAttributes.fileSize;
XCTAssertEqual(fileInfo.uncompressedSize, expectedFileSize, @"Incorrect uncompressed file size");
// Test Compression method
XCTAssertEqual(fileInfo.compressionMethod, URKCompressionMethodNormal, @"Incorrect compression method");
// Test Host OS
XCTAssertEqual(fileInfo.hostOS, URKHostOSUnix, @"Incorrect host OS");
}
}
- (void)testListFileInfo_Unicode
{
NSSet *expectedFileSet = [self.unicodeFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
NSURL *testArchiveURL = self.unicodeFileURLs[@"Ⓣest Ⓐrchive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
NSError *error = nil;
NSArray *filesInArchive = [archive listFileInfo:&error];
XCTAssertNil(error, @"Error returned by listFileInfo");
XCTAssertNotNil(filesInArchive, @"No list of files returned");
XCTAssertEqual(filesInArchive.count, expectedFileSet.count,
@"Incorrect number of files listed in archive");
for (NSInteger i = 0; i < filesInArchive.count; i++) {
URKFileInfo *fileInfo = (URKFileInfo *)filesInArchive[i];
XCTAssertEqualObjects(fileInfo.filename, expectedFiles[i], @"Incorrect filename listed");
XCTAssertEqualObjects(fileInfo.archiveName, archive.filename, @"Incorrect archiveName listed");
}
}
#if !TARGET_OS_IPHONE
- (void)testListFileInfo_MultivolumeArchive {
NSArray<NSURL*> *generatedVolumeURLs = [self multiPartArchiveWithName:@"ListFileInfoTests_MultivolumeArchive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:generatedVolumeURLs.firstObject error:nil];
NSError *error = nil;
NSArray *files = [archive listFileInfo:&error];
XCTAssertNil(error, @"Error returned when listing file info for multivolume archive");
XCTAssertEqual(files.count, 1, @"Incorrect number of file info items returned for multivolume archive");
}
#endif
- (void)testListFileInfo_HeaderPassword
{
NSArray *testArchives = @[@"Test Archive (Header Password).rar"];
NSSet *expectedFileSet = [self.testFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
for (NSString *testArchiveName in testArchives) {
NSURL *testArchiveURL = self.testFileURLs[testArchiveName];
URKArchive *archiveNoPassword = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
NSError *error = nil;
NSArray *filesInArchive = [archiveNoPassword listFileInfo:&error];
XCTAssertNotNil(error, @"No error returned by listFileInfo (no password given)");
XCTAssertNil(filesInArchive, @"List of files returned (no password given)");
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:@"password" error:nil];
filesInArchive = nil;
error = nil;
filesInArchive = [archive listFileInfo:&error];
XCTAssertNil(error, @"Error returned by listFileInfo");
XCTAssertEqual(filesInArchive.count, expectedFileSet.count,
@"Incorrect number of files listed in archive");
for (NSInteger i = 0; i < filesInArchive.count; i++) {
URKFileInfo *archiveFileInfo = filesInArchive[i];
NSString *archiveFilename = archiveFileInfo.filename;
NSString *expectedFilename = expectedFiles[i];
XCTAssertEqualObjects(archiveFilename, expectedFilename, @"Incorrect filename listed");
}
}
}
- (void)testListFileInfo_NoHeaderPasswordGiven {
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Test Archive (Header Password).rar"] error:nil];
NSError *error = nil;
NSArray *files = [archive listFileInfo:&error];
XCTAssertNotNil(error, @"List without password succeeded");
XCTAssertNil(files, @"List returned without password");
XCTAssertEqual(error.code, URKErrorCodeMissingPassword, @"Unexpected error code returned");
}
- (void)testListFileInfo_InvalidArchive
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Test File A.txt"] error:nil];
NSError *error = nil;
NSArray *files = [archive listFileInfo:&error];
XCTAssertNotNil(error, @"List files of invalid archive succeeded");
XCTAssertNil(files, @"List returned for invalid archive");
XCTAssertEqual(error.code, URKErrorCodeBadArchive, @"Unexpected error code returned");
}
- (void)testListFileInfo_ModifiedCRC
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Modified CRC Archive.rar"] error:nil];
NSError *error = nil;
NSArray *files = [archive listFileInfo:&error];
XCTAssertNotNil(error, @"List files of invalid archive succeeded");
XCTAssertNil(files, @"List returned for invalid archive");
XCTAssertEqual(error.code, URKErrorCodeBadData, @"Unexpected error code returned");
}
- (void)testListFileInfo_ModifiedCRC_IgnoringMismatches
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Modified CRC Archive.rar"] error:nil];
archive.ignoreCRCMismatches = YES;
NSError *error = nil;
NSArray<URKFileInfo*> *files = [archive listFileInfo:&error];
XCTAssertNil(error, @"List files of invalid archive succeeded");
XCTAssertNotNil(files, @"List returned for invalid archive");
XCTAssertEqual(files.count, 1);
XCTAssertEqualObjects(files[0].filename, @"README.md");
}
@end

View File

@ -0,0 +1,261 @@
//
// PerformOnDataTests.m
// UnrarKit
//
//
#import "URKArchiveTestCase.h"
@interface PerformOnDataTests : URKArchiveTestCase
@end
@implementation PerformOnDataTests
- (void)testPerformOnData
{
NSArray *testArchives = @[@"Test Archive.rar",
@"Test Archive (Password).rar",
@"Test Archive (Header Password).rar"];
NSSet *expectedFileSet = [self.testFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
for (NSString *testArchiveName in testArchives) {
NSURL *testArchiveURL = self.testFileURLs[testArchiveName];
NSString *password = ([testArchiveName rangeOfString:@"Password"].location != NSNotFound
? @"password"
: nil);
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:password error:nil];
__block NSUInteger fileIndex = 0;
NSError *error = nil;
[archive performOnDataInArchive:
^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
NSString *expectedFilename = expectedFiles[fileIndex++];
XCTAssertEqualObjects(fileInfo.filename, expectedFilename, @"Unexpected filename encountered");
NSData *expectedFileData = [NSData dataWithContentsOfURL:self.testFileURLs[expectedFilename]];
XCTAssertNotNil(fileData, @"No data extracted");
XCTAssertTrue([expectedFileData isEqualToData:fileData], @"File data doesn't match original file");
} error:&error];
XCTAssertNil(error, @"Error iterating through files");
XCTAssertEqual(fileIndex, expectedFiles.count, @"Incorrect number of files encountered");
}
}
- (void)testPerformOnData_Unicode
{
NSSet *expectedFileSet = [self.unicodeFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
NSURL *testArchiveURL = self.unicodeFileURLs[@"Ⓣest Ⓐrchive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
__block NSUInteger fileIndex = 0;
NSError *error = nil;
[archive performOnDataInArchive:
^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
NSString *expectedFilename = expectedFiles[fileIndex++];
XCTAssertEqualObjects(fileInfo.filename, expectedFilename, @"Unexpected filename encountered");
NSData *expectedFileData = [NSData dataWithContentsOfURL:self.unicodeFileURLs[expectedFilename]];
XCTAssertNotNil(fileData, @"No data extracted");
XCTAssertTrue([expectedFileData isEqualToData:fileData], @"File data doesn't match original file");
} error:&error];
XCTAssertNil(error, @"Error iterating through files");
XCTAssertEqual(fileIndex, expectedFiles.count, @"Incorrect number of files encountered");
}
- (void)testPerformOnData_ModifiedCRC
{
NSURL *testArchiveURL = self.testFileURLs[@"Modified CRC Archive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
__block BOOL blockCalled = NO;
NSError *error = nil;
[archive performOnDataInArchive:
^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
blockCalled = YES;
} error:&error];
XCTAssertNotNil(error, @"Error iterating through files");
XCTAssertFalse(blockCalled);
}
- (void)testPerformOnData_ModifiedCRC_IgnoringMismatches
{
NSURL *testArchiveURL = self.testFileURLs[@"Modified CRC Archive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
archive.ignoreCRCMismatches = YES;
__block NSUInteger fileIndex = 0;
NSError *error = nil;
[archive performOnDataInArchive:
^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
XCTAssertEqual(fileIndex++, 0, @"performOnDataInArchive called too many times");
XCTAssertEqualObjects(fileInfo.filename, @"README.md");
NSData *expectedFileData = [NSData dataWithContentsOfURL:self.testFileURLs[@"README.md"]];
XCTAssertNotNil(fileData, @"No data extracted");
XCTAssertTrue([expectedFileData isEqualToData:fileData], @"File data doesn't match original file");
} error:&error];
XCTAssertNil(error, @"Error iterating through files");
}
#if !TARGET_OS_IPHONE
- (void)testPerformOnData_FileMoved
{
NSURL *largeArchiveURL = [self largeArchiveURL];
URKArchive *archive = [[URKArchive alloc] initWithURL:largeArchiveURL error:nil];
NSError *error = nil;
NSArray *archiveFiles = [archive listFilenames:&error];
XCTAssertNotNil(archiveFiles, @"No filenames listed from test archive");
XCTAssertNil(error, @"Error listing files in test archive: %@", error);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:1];
NSURL *movedURL = [largeArchiveURL URLByAppendingPathExtension:@"FileMoved"];
NSError *renameError = nil;
NSFileManager *fm = [NSFileManager defaultManager];
[fm moveItemAtURL:largeArchiveURL toURL:movedURL error:&renameError];
XCTAssertNil(renameError, @"Error renaming file: %@", renameError);
});
__block NSUInteger fileCount = 0;
error = nil;
BOOL success = [archive performOnDataInArchive:^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
XCTAssertNotNil(fileData, @"Extracted file is nil: %@", fileInfo.filename);
if (!fileInfo.isDirectory) {
fileCount++;
XCTAssertGreaterThan(fileData.length, 0, @"Extracted file is empty: %@", fileInfo.filename);
}
} error:&error];
XCTAssertEqual(fileCount, 20, @"Not all files read");
XCTAssertTrue(success, @"Failed to read files");
XCTAssertNil(error, @"Error reading files: %@", error);
}
#endif
#if !TARGET_OS_IPHONE
- (void)testPerformOnData_FileDeleted
{
NSURL *largeArchiveURL = [self largeArchiveURL];
URKArchive *archive = [[URKArchive alloc] initWithURL:largeArchiveURL error:nil];
NSError *error = nil;
NSArray *archiveFiles = [archive listFilenames:&error];
XCTAssertNotNil(archiveFiles, @"No filenames listed from test archive");
XCTAssertNil(error, @"Error listing files in test archive: %@", error);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:1];
NSError *removeError = nil;
NSFileManager *fm = [NSFileManager defaultManager];
[fm removeItemAtURL:largeArchiveURL error:&removeError];
XCTAssertNil(removeError, @"Error removing file: %@", removeError);
});
__block NSUInteger fileCount = 0;
error = nil;
BOOL success = [archive performOnDataInArchive:^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
XCTAssertNotNil(fileData, @"Extracted file is nil: %@", fileInfo.filename);
if (!fileInfo.isDirectory) {
fileCount++;
XCTAssertGreaterThan(fileData.length, 0, @"Extracted file is empty: %@", fileInfo.filename);
}
} error:&error];
XCTAssertEqual(fileCount, 20, @"Not all files read");
XCTAssertTrue(success, @"Failed to read files");
XCTAssertNil(error, @"Error reading files: %@", error);
}
#endif
#if !TARGET_OS_IPHONE
- (void)testPerformOnData_FileMovedBeforeBegin
{
NSURL *largeArchiveURL = [self largeArchiveURL];
URKArchive *archive = [[URKArchive alloc] initWithURL:largeArchiveURL error:nil];
NSError *error = nil;
NSArray *archiveFiles = [archive listFilenames:&error];
XCTAssertNotNil(archiveFiles, @"No filenames listed from test archive");
XCTAssertNil(error, @"Error listing files in test archive: %@", error);
NSURL *movedURL = [largeArchiveURL URLByAppendingPathExtension:@"FileMovedBeforeBegin"];
NSError *renameError = nil;
NSFileManager *fm = [NSFileManager defaultManager];
[fm moveItemAtURL:largeArchiveURL toURL:movedURL error:&renameError];
XCTAssertNil(renameError, @"Error renaming file: %@", renameError);
__block NSUInteger fileCount = 0;
error = nil;
BOOL success = [archive performOnDataInArchive:^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
XCTAssertNotNil(fileData, @"Extracted file is nil: %@", fileInfo.filename);
if (!fileInfo.isDirectory) {
fileCount++;
XCTAssertGreaterThan(fileData.length, 0, @"Extracted file is empty: %@", fileInfo.filename);
}
} error:&error];
XCTAssertEqual(fileCount, 20, @"Not all files read");
XCTAssertTrue(success, @"Failed to read files");
XCTAssertNil(error, @"Error reading files: %@", error);
}
#endif
- (void)testPerformOnData_Folder
{
NSURL *testArchiveURL = self.testFileURLs[@"Folder Archive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
NSArray *expectedFiles = @[@"G070-Cliff", @"G070-Cliff/image.jpg"];
__block NSUInteger fileIndex = 0;
NSError *error = nil;
[archive performOnDataInArchive:
^(URKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
NSString *expectedFilename = expectedFiles[fileIndex++];
XCTAssertEqualObjects(fileInfo.filename, expectedFilename, @"Unexpected filename encountered");
} error:&error];
XCTAssertNil(error, @"Error iterating through files");
XCTAssertEqual(fileIndex, expectedFiles.count, @"Incorrect number of files encountered");
}
@end

View File

@ -0,0 +1,194 @@
//
// PerformOnFilesTests.m
// UnrarKit
//
//
#import "URKArchiveTestCase.h"
@interface PerformOnFilesTests : URKArchiveTestCase
@end
@implementation PerformOnFilesTests
- (void)testPerformOnFiles
{
NSArray *testArchives = @[@"Test Archive.rar",
@"Test Archive (Password).rar",
@"Test Archive (Header Password).rar"];
NSSet *expectedFileSet = [self.testFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
for (NSString *testArchiveName in testArchives) {
NSURL *testArchiveURL = self.testFileURLs[testArchiveName];
NSString *password = ([testArchiveName rangeOfString:@"Password"].location != NSNotFound
? @"password"
: nil);
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:password error:nil];
__block NSUInteger fileIndex = 0;
NSError *error = nil;
[archive performOnFilesInArchive:
^(URKFileInfo *fileInfo, BOOL *stop) {
NSString *expectedFilename = expectedFiles[fileIndex++];
XCTAssertEqualObjects(fileInfo.filename, expectedFilename, @"Unexpected filename encountered");
} error:&error];
XCTAssertNil(error, @"Error iterating through files");
XCTAssertEqual(fileIndex, expectedFiles.count, @"Incorrect number of files encountered");
}
}
- (void)testPerformOnFiles_ModifiedCRC
{
NSURL *testArchiveURL = self.testFileURLs[@"Modified CRC Archive.rar"];
NSString *password = nil;
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:password error:nil];
__block BOOL blockCalled = NO;
NSError *error = nil;
[archive performOnFilesInArchive:
^(URKFileInfo *fileInfo, BOOL *stop) {
blockCalled = YES;
} error:&error];
XCTAssertNotNil(error, @"Error iterating through files");
XCTAssertFalse(blockCalled);
}
- (void)testPerformOnFiles_ModifiedCRC_MismatchesIgnored
{
NSURL *testArchiveURL = self.testFileURLs[@"Modified CRC Archive.rar"];
NSString *password = nil;
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL password:password error:nil];
archive.ignoreCRCMismatches = YES;
__block NSUInteger fileIndex = 0;
NSError *error = nil;
[archive performOnFilesInArchive:
^(URKFileInfo *fileInfo, BOOL *stop) {
XCTAssertEqual(fileIndex++, 0, @"performOnFilesInArchive called too many times");
XCTAssertEqualObjects(fileInfo.filename, @"README.md");
} error:&error];
XCTAssertNil(error, @"Error iterating through files");
}
- (void)testPerformOnFiles_Unicode
{
NSSet *expectedFileSet = [self.unicodeFileURLs keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return ![key hasSuffix:@"rar"] && ![key hasSuffix:@"md"];
}];
NSArray *expectedFiles = [[expectedFileSet allObjects] sortedArrayUsingSelector:@selector(compare:)];
NSURL *testArchiveURL = self.unicodeFileURLs[@"Ⓣest Ⓐrchive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];
__block NSUInteger fileIndex = 0;
NSError *error = nil;
[archive performOnFilesInArchive:
^(URKFileInfo *fileInfo, BOOL *stop) {
NSString *expectedFilename = expectedFiles[fileIndex++];
XCTAssertEqualObjects(fileInfo.filename, expectedFilename, @"Unexpected filename encountered");
} error:&error];
XCTAssertNil(error, @"Error iterating through files");
XCTAssertEqual(fileIndex, expectedFiles.count, @"Incorrect number of files encountered");
}
- (void)testPerformOnFiles_Nested_ExtractData
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Test Archive.rar"]
password:@""
error:nil];
NSError *archiveError = nil;
[archive performOnFilesInArchive:^(URKFileInfo *fileInfo, BOOL *stop) {
NSError *extractError = nil;
NSData *data = [archive extractData:fileInfo error:&extractError];
if (data == nil) {
*stop = YES;
XCTFail();
}
} error:&archiveError];
}
- (void)testPerformOnFiles_Nested_ExtractFiles
{
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Test Archive.rar"]
password:@""
error:nil];
NSError *archiveError = nil;
[archive performOnFilesInArchive:^(URKFileInfo *fileInfo, BOOL *stop) {
#if !TARGET_OS_IPHONE
NSURL *extractRootDirectory = self.tempDirectory;
#else
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *extractRootDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] firstObject];
extractRootDirectory = [extractRootDirectory URLByAppendingPathComponent:@"testPerformOnFiles_Nested_ExtractFiles"];
NSLog(@"Documents directory: %@", extractRootDirectory.path);
if ([fm fileExistsAtPath:extractRootDirectory.path]) {
NSError *clearDirError = nil;
XCTAssertTrue([fm removeItemAtURL:extractRootDirectory error:&clearDirError], @"Failed to clear out documents directory");
XCTAssertNil(clearDirError, @"Error while clearing out documents directory");
}
#endif
NSError *extractError = nil;
BOOL success = [archive extractFilesTo:extractRootDirectory.path
overwrite:NO
error:&extractError];
XCTAssertTrue(success);
} error:&archiveError];
}
#if !TARGET_OS_IPHONE
- (void)testPerformOnFiles_Ordering
{
NSArray *testFilenames = @[@"AAA.txt",
@"BBB.txt",
@"CCC.txt"];
NSFileManager *fm = [NSFileManager defaultManager];
NSMutableArray *testFileURLs = [NSMutableArray array];
// Touch test files
[testFilenames enumerateObjectsUsingBlock:^(NSString *filename, NSUInteger idx, BOOL *stop) {
NSURL *outputURL = [self.tempDirectory URLByAppendingPathComponent:filename];
XCTAssertTrue([fm createFileAtPath:outputURL.path contents:nil attributes:nil], @"Failed to create test file: %@", filename);
[testFileURLs addObject:outputURL];
}];
// Create RAR archive with test files, reversed
NSURL *reversedArchiveURL = [self archiveWithFiles:testFileURLs.reverseObjectEnumerator.allObjects];
NSError *error = nil;
__block NSUInteger index = 0;
URKArchive *archive = [[URKArchive alloc] initWithURL:reversedArchiveURL error:nil];
[archive performOnFilesInArchive:^(URKFileInfo *fileInfo, BOOL *stop) {
NSString *expectedFilename = testFilenames[index++];
XCTAssertEqualObjects(fileInfo.filename, expectedFilename, @"Archive files not iterated through in correct order");
} error:&error];
}
#endif
@end

View File

@ -0,0 +1,275 @@
[![Build Status](https://travis-ci.org/abbeycode/UnrarKit.svg?branch=master)](https://travis-ci.org/abbeycode/UnrarKit)
[![Documentation Coverage](https://img.shields.io/cocoapods/metrics/doc-percent/UnrarKit.svg)](http://cocoadocs.org/docsets/UnrarKit)
# About
UnrarKit is here to enable Mac and iOS apps to easily work with RAR files for read-only operations. It is currently based on version 5.2.1 of the [UnRAR library](http://www.rarlab.com/rar/unrarsrc-5.2.1.tar.gz).
There is a main project, with unit tests, and a basic iOS example project, which demonstrates how to use the library. To see all of these, open the main workspace file.
I'm always open to improvements, so please submit your pull requests, or [create issues](https://github.com/abbeycode/UnrarKit/issues) for someone else to implement.
# Installation
UnrarKit supports both [CocoaPods](https://cocoapods.org/) and [Carthage](https://github.com/Carthage/Carthage). CocoaPods does not support dynamic framework targets (as of v0.39.0), so in that case, please use Carthage.
Cartfile:
github "abbeycode/UnrarKit"
Podfile:
pod "UnrarKit"
# Example Usage
```Objective-C
NSError *archiveError = nil;
URKArchive *archive = [[URKArchive alloc] initWithPath:@"An Archive.rar" error:&archiveError];
NSError *error = nil;
```
## Listing the file names in an archive
```Objective-C
NSArray<String*> *filesInArchive = [archive listFilenames:&error];
for (NSString *name in filesInArchive) {
NSLog(@"Archived file: %@", name);
}
```
## Listing the file details in an archive
```Objective-C
NSArray<URKFileInfo*> *fileInfosInArchive = [archive listFileInfo:&error];
for (URKFileInfo *info in fileInfosInArchive) {
NSLog(@"Archive name: %@ | File name: %@ | Size: %lld", info.archiveName, info.filename, info.uncompressedSize);
}
```
## Working with passwords
```Objective-C
NSArray<URKFileInfo*> *fileInfosInArchive = [archive listFileInfo:&error];
if (archive.isPasswordProtected) {
NSString *givenPassword = // prompt user
archive.password = givenPassword
}
// You can now extract the files
```
## Extracting files to a directory
```Objective-C
BOOL extractFilesSuccessful = [archive extractFilesTo:@"some/directory"
overWrite:NO
progress:
^(URKFileInfo *currentFile, CGFloat percentArchiveDecompressed) {
NSLog(@"Extracting %@: %f%% complete", currentFile.filename, percentArchiveDecompressed);
}
error:&error];
```
## Extracting a file into memory
```Objective-C
NSData *extractedData = [archive extractDataFromFile:@"a file in the archive.jpg"
progress:^(CGFloat percentDecompressed) {
NSLog(@"Extracting, %f%% complete", percentDecompressed);
}
error:&error];
```
## Streaming a file
For large files, you may not want the whole contents in memory at once. You can handle it one "chunk" at a time, like so:
```Objective-C
BOOL success = [archive extractBufferedDataFromFile:@"a file in the archive.jpg"
error:&error
action:
^(NSData *dataChunk, CGFloat percentDecompressed) {
NSLog(@"Decompressed: %f%%", percentDecompressed);
// Do something with the NSData chunk
}];
```
# Progress Reporting
The following methods support `NSProgress` and `NSProgressReporting`:
* `extractFilesTo:overwrite:error:`
* `extractData:error:`
* `extractDataFromFile:error:`
* `performOnFilesInArchive:error:`
* `performOnDataInArchive:error:`
* `extractBufferedDataFromFile:error:action:`
## Using implicit `NSProgress` hierarchy
You can create your own instance of `NSProgress` and observe its `fractionCompleted` property with KVO to monitor progress like so:
```Objective-C
static void *ExtractDataContext = &ExtractDataContext;
URKArchive *archive = [[URKArchive alloc] initWithURL:aFileURL error:nil];
NSProgress *extractDataProgress = [NSProgress progressWithTotalUnitCount:1];
[extractDataProgress becomeCurrentWithPendingUnitCount:1];
NSString *observedSelector = NSStringFromSelector(@selector(fractionCompleted));
[extractDataProgress addObserver:self
forKeyPath:observedSelector
options:NSKeyValueObservingOptionInitial
context:ExtractDataContext];
NSError *extractError = nil;
NSData *data = [archive extractDataFromFile:firstFile error:&extractError];
[extractDataProgress resignCurrent];
[extractDataProgress removeObserver:self forKeyPath:observedSelector];
```
## Using your own explicit `NSProgress` instance
If you don't have a hierarchy of `NSProgress` instances, or if you want to observe more details during progress updates in `extractFilesTo:overwrite:error:`, you can create your own instance of `NSProgress` and set the `URKArchive` instance's `progress` property, like so:
```Objective-C
static void *ExtractFilesContext = &ExtractFilesContext;
URKArchive *archive = [[URKArchive alloc] initWithURL:aFileURL error:nil];
NSProgress *extractFilesProgress = [NSProgress progressWithTotalUnitCount:1];
archive.progress = extractFilesProgress;
NSString *observedSelector = NSStringFromSelector(@selector(localizedDescription));
[self.descriptionsReported removeAllObjects];
[extractFilesProgress addObserver:self
forKeyPath:observedSelector
options:NSKeyValueObservingOptionInitial
context:ExtractFilesContext];
NSError *extractError = nil;
BOOL success = [archive extractFilesTo:extractURL.path
overwrite:NO
error:&extractError];
[extractFilesProgress removeObserver:self forKeyPath:observedSelector];
```
## Cancellation with `NSProgress`
Using either method above, you can call `[progress cancel]` to stop the operation in progress. It will cause the operation to fail, returning `nil` or `NO` (depending on the return type, and give an error with error code `URKErrorCodeUserCancelled`.
# Notes
To open in Xcode, use the [UnrarKit.xcworkspace](UnrarKit.xcworkspace) file, which includes the other projects.
# Documentation
Full documentation for the project is available on [CocoaDocs](http://cocoadocs.org/docsets/UnrarKit).
# Logging
For all OS versions from 2016 onward (macOS 10.12, iOS 10, tvOS 10, watchOS 3), UnzipKit uses the new [Unified Logging framework](https://developer.apple.com/documentation/os/logging) for logging and Activity Tracing. You can view messages at the Info or Debug level to view more details of how UnzipKit is working, and use Activity Tracing to help pinpoint the code path that's causing a particular error.
As a fallback, regular `NSLog` is used on older OSes, with all messages logged at the same level.
When debugging your own code, if you'd like to decrease the verbosity of the UnrarKit framework, you can run the following command:
sudo log config --mode "level:default" --subsystem com.abbey-code.UnrarKit
The available levels, in order of increasing verbosity, are `default`, `info`, `debug`, with `debug` being the default.
## Logging guidelines
These are the general rules governing the particulars of how activities and log messages are classified and written. They were written after the initial round of log messages were, so there may be some inconsistencies (such as an incorrect log level). If you think you spot one, open an issue or a pull request!
### Logging
Log messages should follow these conventions.
1. Log messages don't have final punctuation (like these list items)
1. Messages that note a C function is about to be called, rather than a higher level UnrarKit or Cocoa method, end with "...", since it's not expected for them to log any details of their own
#### Default log level
There should be no messages at this level, so that it's possible for a consumer of the API to turn off _all_ diagnostic logging from it, as detailed above. It's only possible to `log config --mode "level:off"` for a process, not a subsystem.
#### Info log level
Info level log statements serve the following specific purposes.
1. Major action is taken, such as initializing an archive object, or deleting a file from an archive
1. Noting each public method has been called, and the arguments with which it was called
1. Signposting the major actions a public method takes
1. Notifying that an atypical condition has occurred (such as an action causing an early stop in a block or a NO return value)
1. Noting that a loop is about to occur, which will contain debug-level messages for each iteration
#### Debug log level
Most messages fall into this category, making it extremely verbose. All non-error messages that don't fall into either of the other two categories should be debug-level, with some examples of specific cases below.
1. Any log message in a private method
1. Noting variable and argument values in a method
1. Indicating that everything is working as expected
1. Indicating what happens during each iteration of a loop (or documenting that an iteration has happened at all)
#### Error log level
1. Every `NSError` generated should get logged with the same detail message as the `NSError` object itself
1. `NSError` log messages should contain the string of the error code's enumeration value (e.g. `"URKErrorCodeArchiveNotFound"`) when it is known at design time
1. Errors should reported everywhere they're encountered, making it easier to trace their flows through the call stack
1. Early exits that result in desired work not being performed
#### Fault log level
So far, there is only one case that gets logged at Fault-level: when a Cocoa framework methods that come back with an error
### Activities
1. Public methods have an English activity names with spaces, and are title-case
1. Private methods each have an activity with the method's name
1. Sub-activities are created for significant scope changes, such as when inside an action block, but not if no significant work is done before entering that action
1. Top-level activities within a method have variables named `activity`, with more specific labels given to sub-activities
1. If a method is strictly an overload that calls out to another overload without doing anything else, it should not define its own activity
# Pushing a new CocoaPods version
New tagged builds (in any branch) get pushed to CocoaPods automatically, provided they meet the following criteria:
1. All builds and tests succeed
2. The library builds successfully for CocoaPods and for Carthage
3. The build is tagged with something resembling a version number (`#.#.#(-beta#)`, e.g. **2.9** or **2.9-beta5**)
4. `pod spec lint` passes, making sure the CocoaPod is 100% valid
Before pushing a build, you must:
1. Add the release notes to the [CHANGELOG.md](CHANGELOG.md), and commit
2. Run [set-version](Scripts/set-version.sh), like so:
`./Scripts/set-version.sh <version number>`
This does the following:
1. Updates the [UnrarKit-Info.plist](Resources/UnrarKit-Info.plist) file to indicate the new version number, and commits it
2. Makes an annotated tag whose message contains the release notes entered in Step 1
Once that's done, you can call `git push --follow-tags` [<sup id=a1>1</sup>](#f1), and let [Travis CI](https://travis-ci.org/abbeycode/UnrarKit/builds) take care of the rest.
# Credits
* Dov Frankel (dov@abbey-code.com)
* Rogerio Pereira Araujo (rogerio.araujo@gmail.com)
* Vicent Scott (vkan388@gmail.com)
<hr>
<span id="f1">1</span>: Or set `followTags = true` in your git config to always get this behavior:
git config --global push.followTags true
[](#a1)

View File

@ -0,0 +1,127 @@
END USER LICENSE AGREEMENT
The following agreement regarding RAR (and its Windows version - WinRAR)
archiver - referred to as "software" - is made between win.rar GmbH -
referred to as "licensor" - and anyone who is installing, accessing
or in any other way using the software - referred to as "user".
1. The author and holder of the copyright of the software is
Alexander L. Roshal. The licensor and as such issuer of the license
and bearer of the worldwide exclusive usage rights including the rights
to reproduce, distribute and make the software available to the public
in any form is win.rar GmbH, Marienstr. 12, 10117 Berlin, Germany.
2. The software is distributed as try before you buy. This means that
anyone may use the software during a test period of a maximum of 40 days
at no charge. Following this test period, the user must purchase
a license to continue using the software.
3. The software's trial version may be freely distributed, with exceptions
noted below, provided the distribution package is not modified in any way.
a. Nobody may distribute separate parts of the package, with the exception
of the UnRAR components, without written permission.
b. The software's unlicensed trial version may not be distributed
inside of any other software package without written permission.
The software must remain in the original unmodified installation
file for download without any barrier and conditions to the user
such as collecting fees for the download or making the download
conditional on the user giving his contact data.
c. The unmodified installation file of WinRAR must be provided pure
and unpaired. Any bundling is interdicted. In particular the use
of any install or download software which is providing any kind
of download bundles is prohibited unless granted by win.rar GmbH
in written form.
d. Hacks/cracks, keys or key generators may not be included, pointed to
or referred to by the distributor of the trial version.
e. In case of violation of the precedent conditions the allowance
lapses immediately and automatically.
4. The trial version of the software can display a registration reminder
dialog. Depending on the software version and configuration such dialog
can contain either a predefined text and links loaded locally
or a web page loaded from the internet. Such web page can contain
licensing instructions or other materials according to the licensor's
choice, including advertisement. When opening a web page, the software
transfers only those parameters which are technically required
by HTTP protocol to successfully open a web page in a browser.
5. The software is distributed "as is". No warranty of any kind is expressed
or implied. You use at your own risk. Neither the author, the licensor
nor the agents of the licensor will be liable for data loss, damages,
loss of profits or any other kind of loss while using or misusing
this software.
6. There are 2 basic types of licenses issued for the software. These are:
a. A single computer usage license. The user purchases one license to
use the software on one computer.
Home users may use their single computer usage license on all
computers and mobile devices (USB drive, external hard drive, etc.)
which are property of the license owner.
Business users require one license per computer or mobile device
on which the software is installed.
b. A multiple usage license. The user purchases a number of usage
licenses for use, by the purchaser or the purchaser's employees
on the same number of computers.
In a network (server/client) environment the user must purchase
a license copy for each separate client (workstation) on which
the software is installed, used or accessed. A separate license copy
for each client (workstation) is needed regardless of whether
the clients (workstations) will use the software simultaneously
or at different times. If for example you wish to have 9 different
clients (workstations) in your network with access to RAR,
you must purchase 9 license copies.
A user who purchased a license, is granted a non-exclusive right to use
the software on as many computers as defined by the licensing terms above
according to the number of licenses purchased, for any legal purpose.
7. There are no additional license fees, apart from the cost of the license,
associated with the creation and distribution of RAR archives,
volumes, self-extracting archives or self-extracting volumes.
Owners of a license may use their copies of the software to produce
archives and self-extracting archives and to distribute those archives
free of any additional royalties.
8. The licensed software may not be rented or leased but may be permanently
transferred, in its entirety, if the recipient agrees to the terms of
this license.
9. To buy a license, please read the file order.htm provided with
the software for details.
10. You may not use, copy, emulate, clone, rent, lease, sell, modify,
decompile, disassemble, otherwise reverse engineer, or transfer
the licensed software, or any subset of the licensed software,
except as provided for in this agreement. Any such unauthorized use
shall result in immediate and automatic termination of this license
and may result in criminal and/or civil prosecution.
Neither RAR binary code, WinRAR binary code, UnRAR source
or UnRAR binary code may be used or reverse engineered to re-create
the RAR compression algorithm, which is proprietary, without written
permission.
The software may be using components developed and/or copyrighted
by third parties. Please read "Acknowledgments" help file topic
for WinRAR or acknow.txt text file for other RAR versions for details.
11. This License Agreement is construed solely and exclusively under
German law. If you are a merchant, the courts at the registered office
of win.rar GmbH in Berlin/Germany shall have exclusive jurisdiction
for any and all disputes arising in connection with this License
Agreement or its validity.
12. Installing and using the software signifies acceptance of these terms
and conditions of the license. If you do not agree with the terms of this
license, you must remove all software files from your storage devices
and cease to use the software.