diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..001d019 --- /dev/null +++ b/.php_cs @@ -0,0 +1,1500 @@ + true, + + // Each element of an array must be indented exactly once. + 'array_indentation' => true, + + // PHP arrays should be declared using the configured syntax. + 'array_syntax' => [ + 'syntax' => 'short', + ], + + /* + * Converts backtick operators to `shell_exec` calls. + * + * Conversion is done only when it is non risky, so when special + * chars like single-quotes, double-quotes and backticks are not + * used inside the command. + */ + 'backtick_to_shell_exec' => true, + + // Binary operators should be surrounded by space as configured. + 'binary_operator_spaces' => true, + + // There MUST be one blank line after the namespace declaration. + 'blank_line_after_namespace' => true, + + /* + * Ensure there is no code on the same line as the PHP open tag and + * it is followed by a blank line. + */ + 'blank_line_after_opening_tag' => true, + + // An empty line feed must precede any configured statement. + 'blank_line_before_statement' => [ + 'statements' => [ + 'continue', + 'declare', + 'return', + 'throw', + 'try', + 'case', + 'die', + 'exit', + 'do', + 'foreach', + 'goto', + 'if', + 'while', + ], + ], + + /* + * The body of each structure MUST be enclosed by braces. Braces + * should be properly placed. Body of braces should be properly + * indented. + */ + 'braces' => [ + 'allow_single_line_closure' => true, + ], + + // A single space or none should be between cast and variable. + 'cast_spaces' => true, + + /* + * Class, trait and interface elements must be separated with one + * blank line. + */ + 'class_attributes_separation' => true, + + /* + * Whitespace around the keywords of a class, trait or interfaces + * definition should be one space. + */ + 'class_definition' => [ + 'single_item_single_line' => true, + ], + + // Converts `::class` keywords to FQCN strings. + 'class_keyword_remove' => false, + + // Using `isset($var) &&` multiple times should be done in one call. + 'combine_consecutive_issets' => true, + + // Calling `unset` on multiple items should be done in one call. + 'combine_consecutive_unsets' => true, + + /* + * Replace multiple nested calls of `dirname` by only one call with + * second `$level` parameter. Requires PHP >= 7.0. + * + * Risky! + * Risky when the function `dirname` is overridden. + */ + 'combine_nested_dirname' => false, + + /* + * Comments with annotation should be docblock when used on + * structural elements. + * + * Risky! + * Risky as new docblocks might mean more, e.g. a Doctrine entity + * might have a new column in database + */ + 'comment_to_phpdoc' => [ + 'ignored_tags' => [ + 'noinspection', + ], + ], + + /* + * Remove extra spaces in a nullable typehint. + * + * Rule is applied only in a PHP 7.1+ environment. + */ + 'compact_nullable_typehint' => false, + + // Concatenation should be spaced according configuration. + 'concat_space' => [ + 'spacing' => 'one', + ], + + /* + * The PHP constants `true`, `false`, and `null` MUST be written + * using the correct casing. + */ + 'constant_case' => true, + + /* + * Class `DateTimeImmutable` should be used instead of `DateTime`. + * + * Risky! + * Risky when the code relies on modifying `DateTime` objects or if + * any of the `date_create*` functions are overridden. + */ + 'date_time_immutable' => true, + + /* + * Equal sign in declare statement should be surrounded by spaces or + * not following configuration. + */ + 'declare_equal_normalize' => true, + + /* + * Force strict types declaration in all files. Requires PHP >= 7.0. + * + * Risky! + * Forcing strict types will stop non strict code from working. + */ + 'declare_strict_types' => false, + + /* + * Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` + * constant. + * + * Risky! + * Risky when the function `dirname` is overridden. + */ + 'dir_constant' => true, + + /* + * Doctrine annotations must use configured operator for assignment + * in arrays. + */ + 'doctrine_annotation_array_assignment' => true, + + /* + * Doctrine annotations without arguments must use the configured + * syntax. + */ + 'doctrine_annotation_braces' => true, + + // Doctrine annotations must be indented with four spaces. + 'doctrine_annotation_indentation' => true, + + /* + * Fixes spaces in Doctrine annotations. + * + * There must not be any space around parentheses; commas must be + * preceded by no space and followed by one space; there must be no + * space around named arguments assignment operator; there must be + * one space around array assignment operator. + */ + 'doctrine_annotation_spaces' => true, + + /* + * The keyword `elseif` should be used instead of `else if` so that + * all control keywords look like single words. + */ + 'elseif' => true, + + // PHP code MUST use only UTF-8 without BOM (remove BOM). + 'encoding' => true, + + /* + * Replace deprecated `ereg` regular expression functions with + * `preg`. + * + * Risky! + * Risky if the `ereg` function is overridden. + */ + 'ereg_to_preg' => true, + + /* + * Error control operator should be added to deprecation notices + * and/or removed from other cases. + * + * Risky! + * Risky because adding/removing `@` might cause changes to code + * behaviour or if `trigger_error` function is overridden. + */ + 'error_suppression' => [ + 'mute_deprecation_error' => true, + 'noise_remaining_usages' => true, + 'noise_remaining_usages_exclude' => [ + 'gzinflate', + 'fclose', + 'fopen', + 'mime_content_type', + 'rename', + 'unlink', + ], + ], + + /* + * Escape implicit backslashes in strings and heredocs to ease the + * understanding of which are special chars interpreted by PHP and + * which not. + * + * In PHP double-quoted strings and heredocs some chars like `n`, + * `$` or `u` have special meanings if preceded by a backslash (and + * some are special only if followed by other special chars), while + * a backslash preceding other chars are interpreted like a plain + * backslash. The precise list of those special chars is hard to + * remember and to identify quickly: this fixer escapes backslashes + * that do not start a special interpretation with the char after + * them. + * It is possible to fix also single-quoted strings: in this case + * there is no special chars apart from single-quote and backslash + * itself, so the fixer simply ensure that all backslashes are + * escaped. Both single and double backslashes are allowed in + * single-quoted strings, so the purpose in this context is mainly + * to have a uniformed way to have them written all over the + * codebase. + */ + 'escape_implicit_backslashes' => true, + + /* + * Add curly braces to indirect variables to make them clear to + * understand. Requires PHP >= 7.0. + */ + 'explicit_indirect_variable' => false, + + /* + * Converts implicit variables into explicit ones in double-quoted + * strings or heredoc syntax. + * + * The reasoning behind this rule is the following: + * - When there are two valid ways of doing the same thing, using + * both is confusing, there should be a coding standard to follow + * - PHP manual marks `"$var"` syntax as implicit and `"${var}"` + * syntax as explicit: explicit code should always be preferred + * - Explicit syntax allows word concatenation inside strings, e.g. + * `"${var}IsAVar"`, implicit doesn't + * - Explicit syntax is easier to detect for IDE/editors and + * therefore has colors/hightlight with higher contrast, which is + * easier to read + * Backtick operator is skipped because it is harder to handle; you + * can use `backtick_to_shell_exec` fixer to normalize backticks to + * strings + */ + 'explicit_string_variable' => true, + + /* + * All classes must be final, except abstract ones and Doctrine + * entities. + * + * No exception and no configuration are intentional. Beside + * Doctrine entities and of course abstract classes, there is no + * single reason not to declare all classes final. If you want to + * subclass a class, mark the parent class as abstract and create + * two child classes, one empty if necessary: you'll gain much more + * fine grained type-hinting. If you need to mock a standalone + * class, create an interface, or maybe it's a value-object that + * shouldn't be mocked at all. If you need to extend a standalone + * class, create an interface and use the Composite pattern. If you + * aren't ready yet for serious OOP, go with + * FinalInternalClassFixer, it's fine. + * + * Risky! + * Risky when subclassing non-abstract classes. + */ + 'final_class' => false, + + /* + * Internal classes should be `final`. + * + * Risky! + * Changing classes to `final` might cause code execution to break. + */ + 'final_internal_class' => false, + + /* + * All `public` methods of `abstract` classes should be `final`. + * + * Enforce API encapsulation in an inheritance architecture. If you + * want to override a method, use the Template method pattern. + * + * Risky! + * Risky when overriding `public` methods of `abstract` classes + */ + 'final_public_method_for_abstract_class' => false, + + // Converts `static` access to `self` access in `final` classes. + 'final_static_access' => true, + + /* + * Order the flags in `fopen` calls, `b` and `t` must be last. + * + * Risky! + * Risky when the function `fopen` is overridden. + */ + 'fopen_flag_order' => true, + + /* + * The flags in `fopen` calls must omit `t`, and `b` must be omitted + * or included consistently. + * + * Risky! + * Risky when the function `fopen` is overridden. + */ + 'fopen_flags' => [ + 'b_mode' => true, + ], + + /* + * PHP code must use the long ` true, + + /* + * Transforms imported FQCN parameters and return types in function + * arguments to short version. + */ + 'fully_qualified_strict_types' => true, + + // Spaces should be properly placed in a function declaration. + 'function_declaration' => true, + + /* + * Replace core functions calls returning constants with the + * constants. + * + * Risky! + * Risky when any of the configured functions to replace are + * overridden. + */ + 'function_to_constant' => [ + 'functions' => [ + 'get_class', + 'php_sapi_name', + 'phpversion', + 'pi', + 'get_called_class', + ], + ], + + // Ensure single space between function's argument and its typehint. + 'function_typehint_space' => true, + + // Configured annotations should be omitted from PHPDoc. + 'general_phpdoc_annotation_remove' => true, + + // Imports or fully qualifies global classes/functions/constants. + 'global_namespace_import' => [ + 'import_constants' => false, + 'import_functions' => false, + 'import_classes' => false, + ], + + // Add, replace or remove header comment. + 'header_comment' => false, + + /* + * Heredoc/nowdoc content must be properly indented. Requires PHP >= + * 7.3. + */ + 'heredoc_indentation' => false, + + // Convert `heredoc` to `nowdoc` where possible. + 'heredoc_to_nowdoc' => true, + + /* + * Function `implode` must be called with 2 arguments in the + * documented order. + * + * Risky! + * Risky when the function `implode` is overridden. + */ + 'implode_call' => true, + + /* + * Include/Require and file path should be divided with a single + * space. File path should not be placed under brackets. + */ + 'include' => true, + + /* + * Pre- or post-increment and decrement operators should be used if + * possible. + */ + 'increment_style' => false, + + // Code MUST use configured indentation type. + 'indentation_type' => true, + + /* + * Replaces `is_null($var)` expression with `null === $var`. + * + * Risky! + * Risky when the function `is_null` is overridden. + */ + 'is_null' => true, + + // All PHP files must use same line ending. + 'line_ending' => true, + + // Ensure there is no code on the same line as the PHP open tag. + 'linebreak_after_opening_tag' => true, + + /* + * List (`array` destructuring) assignment should be declared using + * the configured syntax. Requires PHP >= 7.1. + */ + 'list_syntax' => false, + + /* + * Use `&&` and `||` logical operators instead of `and` and `or`. + * + * Risky! + * Risky, because you must double-check if using and/or with lower + * precedence was intentional. + */ + 'logical_operators' => true, + + // Cast should be written in lower case. + 'lowercase_cast' => true, + + // PHP keywords MUST be in lower case. + 'lowercase_keywords' => true, + + /* + * Class static references `self`, `static` and `parent` MUST be in + * lower case. + */ + 'lowercase_static_reference' => true, + + // Magic constants should be referred to using the correct casing. + 'magic_constant_casing' => true, + + /* + * Magic method definitions and calls must be using the correct + * casing. + */ + 'magic_method_casing' => true, + + /* + * Replace non multibyte-safe functions with corresponding mb + * function. + * + * Risky! + * Risky when any of the functions are overridden. + */ + 'mb_str_functions' => false, + + /* + * In method arguments and method call, there MUST NOT be a space + * before each comma and there MUST be one space after each comma. + * Argument lists MAY be split across multiple lines, where each + * subsequent line is indented once. When doing so, the first item + * in the list MUST be on the next line, and there MUST be only one + * argument per line. + */ + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + ], + + /* + * Method chaining MUST be properly indented. Method chaining with + * different levels of indentation is not supported. + */ + 'method_chaining_indentation' => true, + + /* + * Replaces `intval`, `floatval`, `doubleval`, `strval` and + * `boolval` function calls with according type casting operator. + * + * Risky! + * Risky if any of the functions `intval`, `floatval`, `doubleval`, + * `strval` or `boolval` are overridden. + */ + 'modernize_types_casting' => true, + + /* + * DocBlocks must start with two asterisks, multiline comments must + * start with a single asterisk, after the opening slash. Both must + * end with a single asterisk before the closing slash. + */ + 'multiline_comment_opening_closing' => true, + + /* + * Forbid multi-line whitespace before the closing semicolon or move + * the semicolon to the new line for chained calls. + */ + 'multiline_whitespace_before_semicolons' => [ + 'strategy' => 'new_line_for_chained_calls', + ], + + /* + * Add leading `\` before constant invocation of internal constant + * to speed up resolving. Constant name match is case-sensitive, + * except for `null`, `false` and `true`. + * + * Risky! + * Risky when any of the constants are namespaced or overridden. + */ + 'native_constant_invocation' => true, + + /* + * Function defined by PHP should be called using the correct + * casing. + */ + 'native_function_casing' => true, + + /* + * Add leading `\` before function invocation to speed up resolving. + * + * Risky! + * Risky when any of the functions are overridden. + */ + 'native_function_invocation' => [ + 'include' => [ + '@compiler_optimized', + ], + 'scope' => 'namespaced', + 'strict' => true, + ], + + // Native type hints for functions should use the correct case. + 'native_function_type_declaration_casing' => true, + + /* + * All instances created with new keyword must be followed by + * braces. + */ + 'new_with_braces' => true, + + /* + * Master functions shall be used instead of aliases. + * + * Risky! + * Risky when any of the alias functions are overridden. + */ + 'no_alias_functions' => [ + 'sets' => [ + '@all', + ], + ], + + // Replace control structure alternative syntax to use braces. + 'no_alternative_syntax' => true, + + // There should not be a binary flag before strings. + 'no_binary_string' => true, + + // There should be no empty lines after class opening brace. + 'no_blank_lines_after_class_opening' => true, + + /* + * There should not be blank lines between docblock and the + * documented element. + */ + 'no_blank_lines_after_phpdoc' => true, + + // There should be no blank lines before a namespace declaration. + 'no_blank_lines_before_namespace' => false, + + /* + * There must be a comment when fall-through is intentional in a + * non-empty case body. + * + * Adds a "no break" comment before fall-through cases, and removes + * it if there is no fall-through. + */ + 'no_break_comment' => [ + 'comment_text' => 'no break', + ], + + /* + * The closing `?>` tag MUST be omitted from files containing only + * PHP. + */ + 'no_closing_tag' => true, + + // There should not be any empty comments. + 'no_empty_comment' => true, + + // There should not be empty PHPDoc blocks. + 'no_empty_phpdoc' => true, + + // Remove useless semicolon statements. + 'no_empty_statement' => true, + + /* + * Removes extra blank lines and/or blank lines following + * configuration. + */ + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'extra', + 'case', + 'continue', + 'default', + 'curly_brace_block', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'use', + 'throw', + 'use_trait', + 'useTrait', + 'switch', + ], + ], + + /* + * Replace accidental usage of homoglyphs (non ascii characters) in + * names. + * + * Risky! + * Renames classes and cannot rename the files. You might have + * string references to renamed code (`$$name`). + */ + 'no_homoglyph_names' => true, + + // Remove leading slashes in `use` clauses. + 'no_leading_import_slash' => true, + + /* + * The namespace declaration line shouldn't contain leading + * whitespace. + */ + 'no_leading_namespace_whitespace' => true, + + // Either language construct `print` or `echo` should be used. + 'no_mixed_echo_print' => true, + + // Operator `=>` should not be surrounded by multi-line whitespaces. + 'no_multiline_whitespace_around_double_arrow' => true, + + /* + * Properties MUST not be explicitly initialized with `null` except + * when they have a type declaration (PHP 7.4). + */ + 'no_null_property_initialization' => true, + + /* + * Convert PHP4-style constructors to `__construct`. + * + * Risky! + * Risky when old style constructor being fixed is overridden or + * overrides parent one. + */ + 'no_php4_constructor' => true, + + /* + * Short cast `bool` using double exclamation mark should not be + * used. + */ + 'no_short_bool_cast' => true, + + // Replace short-echo `=` with long format ` false, + + // Single-line whitespace before closing semicolon are prohibited. + 'no_singleline_whitespace_before_semicolons' => true, + + /* + * When making a method or function call, there MUST NOT be a space + * between the method or function name and the opening parenthesis. + */ + 'no_spaces_after_function_name' => true, + + // There MUST NOT be spaces around offset braces. + 'no_spaces_around_offset' => true, + + /* + * There MUST NOT be a space after the opening parenthesis. There + * MUST NOT be a space before the closing parenthesis. + */ + 'no_spaces_inside_parenthesis' => true, + + // Replaces superfluous `elseif` with `if`. + 'no_superfluous_elseif' => true, + + /* + * Removes `@param` and `@return` tags that don't provide any useful + * information. + */ + 'no_superfluous_phpdoc_tags' => false, + + // Remove trailing commas in list function calls. + 'no_trailing_comma_in_list_call' => true, + + // PHP single-line arrays should not have trailing comma. + 'no_trailing_comma_in_singleline_array' => true, + + // Remove trailing whitespace at the end of non-blank lines. + 'no_trailing_whitespace' => true, + + // There MUST be no trailing spaces inside comment or PHPDoc. + 'no_trailing_whitespace_in_comment' => true, + + // Removes unneeded parentheses around control statements. + 'no_unneeded_control_parentheses' => true, + + /* + * Removes unneeded curly braces that are superfluous and aren't + * part of a control structure's body. + */ + 'no_unneeded_curly_braces' => true, + + // A final class must not have final methods. + 'no_unneeded_final_method' => true, + + /* + * In function arguments there must not be arguments with default + * values before non-default ones. + * + * Risky! + * Modifies the signature of functions; therefore risky when using + * systems (such as some Symfony components) that rely on those (for + * example through reflection). + */ + 'no_unreachable_default_argument_value' => false, + + // Variables must be set `null` instead of using `(unset)` casting. + 'no_unset_cast' => true, + + /* + * Properties should be set to `null` instead of using `unset`. + * + * Risky! + * Changing variables to `null` instead of unsetting them will mean + * they still show up when looping over class variables. With PHP + * 7.4, this rule might introduce `null` assignments to property + * whose type declaration does not allow it. + */ + 'no_unset_on_property' => false, + + // Unused `use` statements must be removed. + 'no_unused_imports' => true, + + // There should not be useless `else` cases. + 'no_useless_else' => true, + + /* + * There should not be an empty `return` statement at the end of a + * function. + */ + 'no_useless_return' => true, + + /* + * In array declaration, there MUST NOT be a whitespace before each + * comma. + */ + 'no_whitespace_before_comma_in_array' => true, + + // Remove trailing whitespace at the end of blank lines. + 'no_whitespace_in_blank_line' => true, + + /* + * Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and + * other invisible unicode symbols. + * + * Risky! + * Risky when strings contain intended invisible characters. + */ + 'non_printable_character' => true, + + // Array index should always be written by using square braces. + 'normalize_index_brace' => true, + + /* + * Logical NOT operators (`!`) should have leading and trailing + * whitespaces. + */ + 'not_operator_with_space' => false, + + // Logical NOT operators (`!`) should have one trailing whitespace. + 'not_operator_with_successor_space' => false, + + /* + * Adds or removes `?` before type declarations for parameters with + * a default `null` value. + * + * Rule is applied only in a PHP 7.1+ environment. + */ + 'nullable_type_declaration_for_default_null_value' => false, + + /* + * There should not be space before or after object + * `T_OBJECT_OPERATOR` `->`. + */ + 'object_operator_without_whitespace' => true, + + // Orders the elements of classes/interfaces/traits. + 'ordered_class_elements' => false, + + // Ordering `use` statements. + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => [ + 'class', + 'const', + 'function', + ], + ], + + /* + * Orders the interfaces in an `implements` or `interface extends` + * clause. + * + * Risky! + * Risky for `implements` when specifying both an interface and its + * parent interface, because PHP doesn't break on `parent, child` + * but does on `child, parent`. + */ + 'ordered_interfaces' => false, + + /* + * PHPUnit assertion method calls like `->assertSame(true, $foo)` + * should be written with dedicated method like + * `->assertTrue($foo)`. + * + * Risky! + * Fixer could be risky if one is overriding PHPUnit's native + * methods. + */ + 'php_unit_construct' => true, + + /* + * PHPUnit assertions like `assertInternalType`, `assertFileExists`, + * should be used over `assertTrue`. + * + * Risky! + * Fixer could be risky if one is overriding PHPUnit's native + * methods. + */ + 'php_unit_dedicate_assert' => [ + 'target' => '3.5', + ], + + /* + * PHPUnit assertions like `assertIsArray` should be used over + * `assertInternalType`. + * + * Risky! + * Risky when PHPUnit methods are overridden or when project has + * PHPUnit incompatibilities. + */ + 'php_unit_dedicate_assert_internal_type' => false, + + /* + * Usages of `->setExpectedException*` methods MUST be replaced by + * `->expectException*` methods. + * + * Risky! + * Risky when PHPUnit classes are overridden or not accessible, or + * when project has PHPUnit incompatibilities. + */ + 'php_unit_expectation' => false, + + // PHPUnit annotations should be a FQCNs including a root namespace. + 'php_unit_fqcn_annotation' => true, + + // All PHPUnit test classes should be marked as internal. + 'php_unit_internal_class' => true, + + /* + * Enforce camel (or snake) case for PHPUnit test methods, following + * configuration. + */ + 'php_unit_method_casing' => true, + + /* + * Usages of `->getMock` and + * `->getMockWithoutInvokingTheOriginalConstructor` methods MUST be + * replaced by `->createMock` or `->createPartialMock` methods. + * + * Risky! + * Risky when PHPUnit classes are overridden or not accessible, or + * when project has PHPUnit incompatibilities. + */ + 'php_unit_mock' => false, + + /* + * Usage of PHPUnit's mock e.g. `->will($this->returnValue(..))` + * must be replaced by its shorter equivalent such as + * `->willReturn(...)`. + * + * Risky! + * Risky when PHPUnit classes are overridden or not accessible, or + * when project has PHPUnit incompatibilities. + */ + 'php_unit_mock_short_will_return' => false, + + /* + * PHPUnit classes MUST be used in namespaced version, e.g. + * `\PHPUnit\Framework\TestCase` instead of + * `\PHPUnit_Framework_TestCase`. + * + * PHPUnit v6 has finally fully switched to namespaces. + * You could start preparing the upgrade by switching from + * non-namespaced TestCase to namespaced one. + * Forward compatibility layer (`\PHPUnit\Framework\TestCase` class) + * was backported to PHPUnit v4.8.35 and PHPUnit v5.4.0. + * Extended forward compatibility layer (`PHPUnit\Framework\Assert`, + * `PHPUnit\Framework\BaseTestListener`, + * `PHPUnit\Framework\TestListener` classes) was introduced in + * v5.7.0. + * + * Risky! + * Risky when PHPUnit classes are overridden or not accessible, or + * when project has PHPUnit incompatibilities. + */ + 'php_unit_namespaced' => [ + 'target' => '4.8', + ], + + /* + * Usages of `@expectedException*` annotations MUST be replaced by + * `->setExpectedException*` methods. + * + * Risky! + * Risky when PHPUnit classes are overridden or not accessible, or + * when project has PHPUnit incompatibilities. + */ + 'php_unit_no_expectation_annotation' => [ + 'target' => 'newest', + ], + + // Order `@covers` annotation of PHPUnit tests. + 'php_unit_ordered_covers' => true, + + /* + * Changes the visibility of the `setUp()` and `tearDown()` + * functions of PHPUnit to `protected`, to match the PHPUnit + * TestCase. + * + * Risky! + * This fixer may change functions named `setUp()` or `tearDown()` + * outside of PHPUnit tests, when a class is wrongly seen as a + * PHPUnit test. + */ + 'php_unit_set_up_tear_down_visibility' => true, + + /* + * All PHPUnit test cases should have `@small`, `@medium` or + * `@large` annotation to enable run time limits. + * + * The special groups [small, medium, large] provides a way to + * identify tests that are taking long to be executed. + */ + 'php_unit_size_class' => true, + + /* + * PHPUnit methods like `assertSame` should be used instead of + * `assertEquals`. + * + * Risky! + * Risky when any of the functions are overridden or when testing + * object equality. + */ + 'php_unit_strict' => false, + + /* + * Adds or removes @test annotations from tests, following + * configuration. + * + * Risky! + * This fixer may change the name of your tests, and could cause + * incompatibility with abstract classes or interfaces. + */ + 'php_unit_test_annotation' => true, + + /* + * Calls to `PHPUnit\Framework\TestCase` static methods must all be + * of the same type, either `$this->`, `self::` or `static::`. + * + * Risky! + * Risky when PHPUnit methods are overridden or not accessible, or + * when project has PHPUnit incompatibilities. + */ + 'php_unit_test_case_static_method_calls' => true, + + /* + * Adds a default `@coversNothing` annotation to PHPUnit test + * classes that have no `@covers*` annotation. + */ + 'php_unit_test_class_requires_covers' => false, + + // PHPDoc should contain `@param` for all params. + 'phpdoc_add_missing_param_annotation' => [ + 'only_untyped' => false, + ], + + /* + * All items of the given phpdoc tags must be either left-aligned or + * (by default) aligned vertically. + */ + 'phpdoc_align' => [ + 'tags' => [ + 'return', + 'throws', + 'type', + 'var', + 'property', + 'method', + 'param', + ], + 'align' => 'vertical', + ], + + // PHPDoc annotation descriptions should not be a sentence. + 'phpdoc_annotation_without_dot' => true, + + /* + * Docblocks should have the same indentation as the documented + * subject. + */ + 'phpdoc_indent' => true, + + // Fix PHPDoc inline tags, make `@inheritdoc` always inline. + 'phpdoc_inline_tag' => true, + + /* + * Changes doc blocks from single to multi line, or reversed. Works + * for class constants, properties and methods only. + */ + 'phpdoc_line_span' => [ + 'const' => 'single', + 'property' => 'single', + 'method' => 'multi', + ], + + // `@access` annotations should be omitted from PHPDoc. + 'phpdoc_no_access' => true, + + // No alias PHPDoc tags should be used. + 'phpdoc_no_alias_tag' => true, + + /* + * `@return void` and `@return null` annotations should be omitted + * from PHPDoc. + */ + 'phpdoc_no_empty_return' => true, + + /* + * `@package` and `@subpackage` annotations should be omitted from + * PHPDoc. + */ + 'phpdoc_no_package' => true, + + // Classy that does not inherit must not have `@inheritdoc` tags. + 'phpdoc_no_useless_inheritdoc' => true, + + /* + * Annotations in PHPDoc should be ordered so that `@param` + * annotations come first, then `@throws` annotations, then + * `@return` annotations. + */ + 'phpdoc_order' => true, + + /* + * The type of `@return` annotations of methods returning a + * reference to itself must the configured one. + */ + 'phpdoc_return_self_reference' => true, + + /* + * Scalar types should always be written in the same form. `int` not + * `integer`, `bool` not `boolean`, `float` not `real` or `double`. + */ + 'phpdoc_scalar' => true, + + /* + * Annotations in PHPDoc should be grouped together so that + * annotations of the same type immediately follow each other, and + * annotations of a different type are separated by a single blank + * line. + */ + 'phpdoc_separation' => true, + + // Single line `@var` PHPDoc should have proper spacing. + 'phpdoc_single_line_var_spacing' => true, + + /* + * PHPDoc summary should end in either a full stop, exclamation + * mark, or question mark. + */ + 'phpdoc_summary' => true, + + // Docblocks should only be used on structural elements. + 'phpdoc_to_comment' => false, + + /* + * EXPERIMENTAL: Takes `@param` annotations of non-mixed types and + * adjusts accordingly the function signature. Requires PHP >= 7.0. + * + * Risky! + * [1] This rule is EXPERIMENTAL and is not covered with backward + * compatibility promise. [2] `@param` annotation is mandatory for + * the fixer to make changes, signatures of methods without it (no + * docblock, inheritdocs) will not be fixed. [3] Manual actions are + * required if inherited signatures are not properly documented. + */ + 'phpdoc_to_param_type' => false, + + /* + * EXPERIMENTAL: Takes `@return` annotation of non-mixed types and + * adjusts accordingly the function signature. Requires PHP >= 7.0. + * + * Risky! + * [1] This rule is EXPERIMENTAL and is not covered with backward + * compatibility promise. [2] `@return` annotation is mandatory for + * the fixer to make changes, signatures of methods without it (no + * docblock, inheritdocs) will not be fixed. [3] Manual actions are + * required if inherited signatures are not properly documented. [4] + * `@inheritdocs` support is under construction. + */ + 'phpdoc_to_return_type' => false, + + /* + * PHPDoc should start and end with content, excluding the very + * first and last line of the docblocks. + */ + 'phpdoc_trim' => true, + + /* + * Removes extra blank lines after summary and after description in + * PHPDoc. + */ + 'phpdoc_trim_consecutive_blank_line_separation' => true, + + // The correct case must be used for standard PHP types in PHPDoc. + 'phpdoc_types' => true, + + // Sorts PHPDoc types. + 'phpdoc_types_order' => [ + 'sort_algorithm' => 'none', + 'null_adjustment' => 'always_last', + ], + + /* + * `@var` and `@type` annotations must have type and name in the + * correct order. + */ + 'phpdoc_var_annotation_correct_order' => true, + + /* + * `@var` and `@type` annotations should not contain the variable + * name. + */ + 'phpdoc_var_without_name' => false, + + /* + * Converts `pow` to the `**` operator. + * + * Risky! + * Risky when the function `pow` is overridden. + */ + 'pow_to_exponentiation' => false, + + /* + * Converts `protected` variables and methods to `private` where + * possible. + */ + 'protected_to_private' => true, + + /* + * Classes must be in a path that matches their namespace, be at + * least one namespace deep and the class name should match the file + * name. + * + * Risky! + * This fixer may change your class name, which will break the code + * that depends on the old name. + */ + 'psr0' => false, + + /* + * Class names should match the file name. + * + * Risky! + * This fixer may change your class name, which will break the code + * that depends on the old name. + */ + 'psr4' => true, + + /* + * Replaces `rand`, `srand`, `getrandmax` functions calls with their + * `mt_*` analogs. + * + * Risky! + * Risky when the configured functions are overridden. + */ + 'random_api_migration' => [ + 'replacements' => [ + 'getrandmax' => 'mt_getrandmax', + 'rand' => 'mt_rand', + 'srand' => 'mt_srand', + ], + ], + + /* + * Local, dynamic and directly referenced variables should not be + * assigned and directly returned by a function or method. + */ + 'return_assignment' => true, + + /* + * There should be one or no space before colon, and one space after + * it in return type declarations, according to configuration. + * + * Rule is applied only in a PHP 7+ environment. + */ + 'return_type_declaration' => false, + + /* + * Inside class or interface element `self` should be preferred to + * the class name itself. + * + * Risky! + * Risky when using dynamic calls like get_called_class() or late + * static binding. + */ + 'self_accessor' => true, + + /* + * Inside a `final` class or anonymous class `self` should be + * preferred to `static`. + */ + 'self_static_accessor' => true, + + // Instructions must be terminated with a semicolon. + 'semicolon_after_instruction' => true, + + /* + * Cast shall be used, not `settype`. + * + * Risky! + * Risky when the `settype` function is overridden or when used as + * the 2nd or 3rd expression in a `for` loop . + */ + 'set_type_to_cast' => true, + + /* + * Cast `(boolean)` and `(integer)` should be written as `(bool)` + * and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as + * `(string)`. + */ + 'short_scalar_cast' => true, + + /* + * Converts explicit variables in double-quoted strings and heredoc + * syntax from simple to complex format (`${` to `{$`). + * + * Doesn't touch implicit variables. Works together nicely with + * `explicit_string_variable`. + */ + 'simple_to_complex_string_variable' => true, + + /* + * A return statement wishing to return `void` should not return + * `null`. + */ + 'simplified_null_return' => false, + + /* + * A PHP file without end tag must always end with a single empty + * line feed. + */ + 'single_blank_line_at_eof' => true, + + /* + * There should be exactly one blank line before a namespace + * declaration. + */ + 'single_blank_line_before_namespace' => true, + + /* + * There MUST NOT be more than one property or constant declared per + * statement. + */ + 'single_class_element_per_statement' => true, + + // There MUST be one use keyword per declaration. + 'single_import_per_statement' => true, + + /* + * Each namespace use MUST go on its own line and there MUST be one + * blank line after the use statements block. + */ + 'single_line_after_imports' => true, + + /* + * Single-line comments and multi-line comments with only one line + * of actual content should use the `//` syntax. + */ + 'single_line_comment_style' => true, + + // Throwing exception must be done in single line. + 'single_line_throw' => false, + + // Convert double quotes to single quotes for simple strings. + 'single_quote' => [ + 'strings_containing_single_quote_chars' => false, + ], + + // Each trait `use` must be done as single statement. + 'single_trait_insert_per_statement' => true, + + // Fix whitespace after a semicolon. + 'space_after_semicolon' => true, + + // Increment and decrement operators should be used if possible. + 'standardize_increment' => true, + + // Replace all `<>` with `!=`. + 'standardize_not_equals' => true, + + /* + * Lambdas not (indirect) referencing `$this` must be declared + * `static`. + * + * Risky! + * Risky when using "->bindTo" on lambdas without referencing to + * `$this`. + */ + 'static_lambda' => true, + + /* + * Comparisons should be strict. + * + * Risky! + * Changing comparisons to strict might change code behavior. + */ + 'strict_comparison' => false, + + /* + * Functions should be used with `$strict` param set to `true`. + * + * The functions "array_keys", "array_search", "base64_decode", + * "in_array" and "mb_detect_encoding" should be used with $strict + * param. + * + * Risky! + * Risky when the fixed function is overridden or if the code relies + * on non-strict usage. + */ + 'strict_param' => false, + + /* + * All multi-line strings must use correct line ending. + * + * Risky! + * Changing the line endings of multi-line strings might affect + * string comparisons and outputs. + */ + 'string_line_ending' => true, + + // A case should be followed by a colon and not a semicolon. + 'switch_case_semicolon_to_colon' => true, + + // Removes extra spaces between colon and case value. + 'switch_case_space' => true, + + // Standardize spaces around ternary operator. + 'ternary_operator_spaces' => true, + + /* + * Use `null` coalescing operator `??` where possible. Requires PHP + * >= 7.0. + */ + 'ternary_to_null_coalescing' => false, + + // PHP multi-line arrays should have a trailing comma. + 'trailing_comma_in_multiline_array' => true, + + /* + * Arrays should be formatted like function/method arguments, + * without leading or trailing single line space. + */ + 'trim_array_spaces' => true, + + // Unary operators should be placed adjacent to their operands. + 'unary_operator_spaces' => true, + + /* + * Visibility MUST be declared on all properties and methods; + * `abstract` and `final` MUST be declared before the visibility; + * `static` MUST be declared after the visibility. + */ + 'visibility_required' => true, + + /* + * Add `void` return type to functions with missing or empty return + * statements, but priority is given to `@return` annotations. + * Requires PHP >= 7.1. + * + * Risky! + * Modifies the signature of functions. + */ + 'void_return' => false, + + /* + * In array declaration, there MUST be a whitespace after each + * comma. + */ + 'whitespace_after_comma_in_array' => true, + + /* + * Write conditions in Yoda style (`true`), non-Yoda style (`false`) + * or ignore those conditions (`null`) based on configuration. + */ + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], +]; + +if (\PHP_SAPI === 'cli' && !class_exists(\PhpCsFixer\Config::class)) { + $binFixer = __DIR__ . '/vendor/bin/php-cs-fixer'; + if (!is_file($binFixer)) { + $binFixer = 'php-cs-fixer'; + } + $dryRun = !\in_array('--force', $_SERVER['argv'], true); + + $command = escapeshellarg($binFixer) . ' fix --config ' . escapeshellarg(__FILE__) . ' --diff-format udiff --ansi'; + if ($dryRun) { + $command .= ' --dry-run'; + } + + system($command, $returnCode); + + if ($dryRun || $returnCode === 8) { + fwrite(\STDOUT, "\n\e[1;40;93m\e[K\n"); + fwrite(\STDOUT, " [DEBUG] Dry run php-cs-fixer config.\e[K\n"); + fwrite(\STDOUT, " Only shows which files would have been modified.\e[K\n"); + fwrite(\STDOUT, " To apply the rules, use the --force option:\e[K\n\e[K\n"); + fwrite(\STDOUT, " \e[1;40;92mphp {$_SERVER['argv'][0]} --force\e[K\n\e[0m\n"); + } elseif ($returnCode !== 0) { + fwrite(\STDERR, "\n\e[1;41;97m\e[K\n ERROR CODE: {$returnCode}\e[K\n\e[0m\n"); + } + + exit($returnCode); +} + +return \PhpCsFixer\Config::create() + ->setUsingCache(true) + ->setCacheFile(__DIR__ . '/.php_cs.cache') + ->setRules($rules) + ->setRiskyAllowed(true) + ->setFinder( + \PhpCsFixer\Finder::create() + ->in(__DIR__) + ) +; diff --git a/.travis.yml b/.travis.yml index b2525a0..a3328f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +dist: trusty + language: php php: - '5.5' @@ -6,12 +8,7 @@ php: - '7.1' - '7.2' - '7.3' - -# cache vendor dirs -cache: - directories: - - vendor - - $HOME/.composer/cache + - '7.4' install: - travis_retry composer self-update && composer --version diff --git a/composer.json b/composer.json index 8f3ca0a..b0a938f 100644 --- a/composer.json +++ b/composer.json @@ -21,12 +21,13 @@ } ], "require": { - "ext-zlib": "*", "php": "^5.5 || ^7.0", - "psr/http-message": "^1.0" + "ext-zlib": "*", + "psr/http-message": "^1.0", + "paragonie/random_compat": ">=1 <9.99" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.7", + "phpunit/phpunit": "^4.8|^5.7", "zendframework/zend-diactoros": "^1.4" }, "autoload": { @@ -45,5 +46,15 @@ "ext-bz2": "Needed to support BZIP2 compression", "ext-fileinfo": "Needed to get mime-type file" }, - "minimum-stability": "stable" + "minimum-stability": "stable", + "scripts": { + "php:fix": "php .php_cs --force", + "php:fix:debug": "php .php_cs" + }, + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev", + "dev-develop": "3.2.x-dev" + } + } } diff --git a/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php b/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php index 8a5978b..1d12603 100644 --- a/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php +++ b/src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php @@ -2,75 +2,297 @@ namespace PhpZip\Crypto; +use PhpZip\Exception\RuntimeException; use PhpZip\Exception\ZipAuthenticationException; use PhpZip\Exception\ZipCryptoException; use PhpZip\Model\ZipEntry; -use PhpZip\Util\CryptoUtil; use PhpZip\Util\PackUtil; /** * Traditional PKWARE Encryption Engine. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine { - /** - * Encryption header size - */ + /** Encryption header size */ const STD_DEC_HDR_SIZE = 12; /** - * Crc table + * Crc table. * * @var array */ private static $CRC_TABLE = [ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + 0x00000000, + 0x77073096, + 0xee0e612c, + 0x990951ba, + 0x076dc419, + 0x706af48f, + 0xe963a535, + 0x9e6495a3, + 0x0edb8832, + 0x79dcb8a4, + 0xe0d5e91e, + 0x97d2d988, + 0x09b64c2b, + 0x7eb17cbd, + 0xe7b82d07, + 0x90bf1d91, + 0x1db71064, + 0x6ab020f2, + 0xf3b97148, + 0x84be41de, + 0x1adad47d, + 0x6ddde4eb, + 0xf4d4b551, + 0x83d385c7, + 0x136c9856, + 0x646ba8c0, + 0xfd62f97a, + 0x8a65c9ec, + 0x14015c4f, + 0x63066cd9, + 0xfa0f3d63, + 0x8d080df5, + 0x3b6e20c8, + 0x4c69105e, + 0xd56041e4, + 0xa2677172, + 0x3c03e4d1, + 0x4b04d447, + 0xd20d85fd, + 0xa50ab56b, + 0x35b5a8fa, + 0x42b2986c, + 0xdbbbc9d6, + 0xacbcf940, + 0x32d86ce3, + 0x45df5c75, + 0xdcd60dcf, + 0xabd13d59, + 0x26d930ac, + 0x51de003a, + 0xc8d75180, + 0xbfd06116, + 0x21b4f4b5, + 0x56b3c423, + 0xcfba9599, + 0xb8bda50f, + 0x2802b89e, + 0x5f058808, + 0xc60cd9b2, + 0xb10be924, + 0x2f6f7c87, + 0x58684c11, + 0xc1611dab, + 0xb6662d3d, + 0x76dc4190, + 0x01db7106, + 0x98d220bc, + 0xefd5102a, + 0x71b18589, + 0x06b6b51f, + 0x9fbfe4a5, + 0xe8b8d433, + 0x7807c9a2, + 0x0f00f934, + 0x9609a88e, + 0xe10e9818, + 0x7f6a0dbb, + 0x086d3d2d, + 0x91646c97, + 0xe6635c01, + 0x6b6b51f4, + 0x1c6c6162, + 0x856530d8, + 0xf262004e, + 0x6c0695ed, + 0x1b01a57b, + 0x8208f4c1, + 0xf50fc457, + 0x65b0d9c6, + 0x12b7e950, + 0x8bbeb8ea, + 0xfcb9887c, + 0x62dd1ddf, + 0x15da2d49, + 0x8cd37cf3, + 0xfbd44c65, + 0x4db26158, + 0x3ab551ce, + 0xa3bc0074, + 0xd4bb30e2, + 0x4adfa541, + 0x3dd895d7, + 0xa4d1c46d, + 0xd3d6f4fb, + 0x4369e96a, + 0x346ed9fc, + 0xad678846, + 0xda60b8d0, + 0x44042d73, + 0x33031de5, + 0xaa0a4c5f, + 0xdd0d7cc9, + 0x5005713c, + 0x270241aa, + 0xbe0b1010, + 0xc90c2086, + 0x5768b525, + 0x206f85b3, + 0xb966d409, + 0xce61e49f, + 0x5edef90e, + 0x29d9c998, + 0xb0d09822, + 0xc7d7a8b4, + 0x59b33d17, + 0x2eb40d81, + 0xb7bd5c3b, + 0xc0ba6cad, + 0xedb88320, + 0x9abfb3b6, + 0x03b6e20c, + 0x74b1d29a, + 0xead54739, + 0x9dd277af, + 0x04db2615, + 0x73dc1683, + 0xe3630b12, + 0x94643b84, + 0x0d6d6a3e, + 0x7a6a5aa8, + 0xe40ecf0b, + 0x9309ff9d, + 0x0a00ae27, + 0x7d079eb1, + 0xf00f9344, + 0x8708a3d2, + 0x1e01f268, + 0x6906c2fe, + 0xf762575d, + 0x806567cb, + 0x196c3671, + 0x6e6b06e7, + 0xfed41b76, + 0x89d32be0, + 0x10da7a5a, + 0x67dd4acc, + 0xf9b9df6f, + 0x8ebeeff9, + 0x17b7be43, + 0x60b08ed5, + 0xd6d6a3e8, + 0xa1d1937e, + 0x38d8c2c4, + 0x4fdff252, + 0xd1bb67f1, + 0xa6bc5767, + 0x3fb506dd, + 0x48b2364b, + 0xd80d2bda, + 0xaf0a1b4c, + 0x36034af6, + 0x41047a60, + 0xdf60efc3, + 0xa867df55, + 0x316e8eef, + 0x4669be79, + 0xcb61b38c, + 0xbc66831a, + 0x256fd2a0, + 0x5268e236, + 0xcc0c7795, + 0xbb0b4703, + 0x220216b9, + 0x5505262f, + 0xc5ba3bbe, + 0xb2bd0b28, + 0x2bb45a92, + 0x5cb36a04, + 0xc2d7ffa7, + 0xb5d0cf31, + 0x2cd99e8b, + 0x5bdeae1d, + 0x9b64c2b0, + 0xec63f226, + 0x756aa39c, + 0x026d930a, + 0x9c0906a9, + 0xeb0e363f, + 0x72076785, + 0x05005713, + 0x95bf4a82, + 0xe2b87a14, + 0x7bb12bae, + 0x0cb61b38, + 0x92d28e9b, + 0xe5d5be0d, + 0x7cdcefb7, + 0x0bdbdf21, + 0x86d3d2d4, + 0xf1d4e242, + 0x68ddb3f8, + 0x1fda836e, + 0x81be16cd, + 0xf6b9265b, + 0x6fb077e1, + 0x18b74777, + 0x88085ae6, + 0xff0f6a70, + 0x66063bca, + 0x11010b5c, + 0x8f659eff, + 0xf862ae69, + 0x616bffd3, + 0x166ccf45, + 0xa00ae278, + 0xd70dd2ee, + 0x4e048354, + 0x3903b3c2, + 0xa7672661, + 0xd06016f7, + 0x4969474d, + 0x3e6e77db, + 0xaed16a4a, + 0xd9d65adc, + 0x40df0b66, + 0x37d83bf0, + 0xa9bcae53, + 0xdebb9ec5, + 0x47b2cf7f, + 0x30b5ffe9, + 0xbdbdf21c, + 0xcabac28a, + 0x53b39330, + 0x24b4a3a6, + 0xbad03605, + 0xcdd70693, + 0x54de5729, + 0x23d967bf, + 0xb3667a2e, + 0xc4614ab8, + 0x5d681b02, + 0x2a6f2b94, + 0xb40bbe37, + 0xc30c8ea1, + 0x5a05df1b, + 0x2d02ef8d, ]; /** - * Encryption keys + * Encryption keys. * * @var array */ private $keys = []; - /** - * @var ZipEntry - */ + + /** @var ZipEntry */ private $entry; /** @@ -84,7 +306,7 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine } /** - * Initial keys + * Initial keys. * * @param string $password */ @@ -93,6 +315,7 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine $this->keys[0] = 305419896; $this->keys[1] = 591751049; $this->keys[2] = 878082192; + foreach (unpack('C*', $password) as $b) { $this->updateKeys($b); } @@ -101,21 +324,22 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine /** * Update keys. * - * @param string $charAt + * @param int $charAt */ private function updateKeys($charAt) { - $this->keys[0] = self::crc32($this->keys[0], $charAt); - $this->keys[1] = $this->keys[1] + ($this->keys[0] & 0xff); + $this->keys[0] = $this->crc32($this->keys[0], $charAt); + $this->keys[1] += ($this->keys[0] & 0xff); $this->keys[1] = PackUtil::toSignedInt32($this->keys[1] * 134775813 + 1); - $this->keys[2] = PackUtil::toSignedInt32(self::crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff)); + $this->keys[2] = PackUtil::toSignedInt32($this->crc32($this->keys[2], ($this->keys[1] >> 24) & 0xff)); } /** * Update crc. * * @param int $oldCrc - * @param string $charAt + * @param int $charAt + * * @return int */ private function crc32($oldCrc, $charAt) @@ -125,18 +349,25 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine /** * @param string $content - * @return string + * * @throws ZipAuthenticationException + * + * @return string */ public function decrypt($content) { + if (\PHP_INT_SIZE === 4) { + throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.'); + } + $password = $this->entry->getPassword(); $this->initKeys($password); $headerBytes = array_values(unpack('C*', substr($content, 0, self::STD_DEC_HDR_SIZE))); $byte = 0; - foreach ($headerBytes as &$byte) { - $byte = ($byte ^ $this->decryptByte()) & 0xff; + + for ($i = 0; $i < self::STD_DEC_HDR_SIZE; $i++) { + $byte = ($headerBytes[$i] ^ $this->decryptByte()) & 0xff; $this->updateKeys($byte); } @@ -147,19 +378,24 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine // compare against the CRC otherwise $checkByte = ($this->entry->getCrc() >> 24) & 0xff; } + if ($byte !== $checkByte) { - throw new ZipAuthenticationException(sprintf( - 'Invalid password for zip entry "%s"', - $this->entry->getName() - )); + throw new ZipAuthenticationException( + sprintf( + 'Invalid password for zip entry "%s"', + $this->entry->getName() + ) + ); } - $outputContent = ""; + $outputContent = ''; + foreach (unpack('C*', substr($content, self::STD_DEC_HDR_SIZE)) as $val) { $val = ($val ^ $this->decryptByte()) & 0xff; $this->updateKeys($val); $outputContent .= pack('c', $val); } + return $outputContent; } @@ -171,22 +407,34 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine private function decryptByte() { $temp = $this->keys[2] | 2; + return (($temp * ($temp ^ 1)) >> 8) & 0xffffff; } /** - * Encryption data + * Encryption data. * * @param string $data - * @return string + * * @throws ZipCryptoException + * + * @return string */ public function encrypt($data) { + if (\PHP_INT_SIZE === 4) { + throw new RuntimeException('Traditional PKWARE Encryption is not supported in 32-bit PHP.'); + } + $crc = $this->entry->isDataDescriptorRequired() ? ($this->entry->getDosTime() & 0x0000ffff) << 16 : $this->entry->getCrc(); - $headerBytes = CryptoUtil::randomBytes(self::STD_DEC_HDR_SIZE); + + try { + $headerBytes = random_bytes(self::STD_DEC_HDR_SIZE); + } catch (\Exception $e) { + throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e); + } // Initialize again since the generated bytes were encrypted. $password = $this->entry->getPassword(); @@ -196,13 +444,16 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine $headerBytes[self::STD_DEC_HDR_SIZE - 2] = pack('c', ($crc >> 16) & 0xff); $headerBytes = $this->encryptData($headerBytes); + return $headerBytes . $this->encryptData($data); } /** * @param string $content - * @return string + * * @throws ZipCryptoException + * + * @return string */ private function encryptData($content) { @@ -210,20 +461,24 @@ class TraditionalPkwareEncryptionEngine implements ZipEncryptionEngine throw new ZipCryptoException('content is null'); } $buff = ''; + foreach (unpack('C*', $content) as $val) { $buff .= pack('c', $this->encryptByte($val)); } + return $buff; } /** * @param int $byte + * * @return int */ private function encryptByte($byte) { $tempVal = $byte ^ $this->decryptByte() & 0xff; $this->updateKeys($byte); + return $tempVal; } } diff --git a/src/PhpZip/Crypto/WinZipAesEngine.php b/src/PhpZip/Crypto/WinZipAesEngine.php index 5bdd7a7..97d8e44 100644 --- a/src/PhpZip/Crypto/WinZipAesEngine.php +++ b/src/PhpZip/Crypto/WinZipAesEngine.php @@ -8,12 +8,12 @@ use PhpZip\Exception\ZipCryptoException; use PhpZip\Exception\ZipException; use PhpZip\Extra\Fields\WinZipAesEntryExtraField; use PhpZip\Model\ZipEntry; -use PhpZip\Util\CryptoUtil; /** * WinZip Aes Encryption Engine. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ @@ -24,18 +24,18 @@ class WinZipAesEngine implements ZipEncryptionEngine * in bits (AES_BLOCK_SIZE_BITS). */ const AES_BLOCK_SIZE_BITS = 128; + const PWD_VERIFIER_BITS = 16; - /** - * The iteration count for the derived keys of the cipher, KLAC and MAC. - */ + + /** The iteration count for the derived keys of the cipher, KLAC and MAC. */ const ITERATION_COUNT = 1000; - /** - * @var ZipEntry - */ + + /** @var ZipEntry */ private $entry; /** * WinZipAesEngine constructor. + * * @param ZipEntry $entry */ public function __construct(ZipEntry $entry) @@ -47,17 +47,19 @@ class WinZipAesEngine implements ZipEncryptionEngine * Decrypt from stream resource. * * @param string $content Input stream buffer - * @return string + * + * @throws ZipException * @throws ZipAuthenticationException * @throws ZipCryptoException - * @throws \PhpZip\Exception\ZipException + * + * @return string */ public function decrypt($content) { $extraFieldsCollection = $this->entry->getExtraFieldsCollection(); if (!isset($extraFieldsCollection[WinZipAesEntryExtraField::getHeaderId()])) { - throw new ZipCryptoException($this->entry->getName() . " (missing extra field for WinZip AES entry)"); + throw new ZipCryptoException($this->entry->getName() . ' (missing extra field for WinZip AES entry)'); } /** @@ -78,32 +80,39 @@ class WinZipAesEngine implements ZipEncryptionEngine // Init start, end and size of encrypted data. $start = $pos; - $endPos = strlen($content); + $endPos = \strlen($content); $footerSize = $sha1Size / 2; $end = $endPos - $footerSize; $size = $end - $start; - if (0 > $size) { - throw new ZipCryptoException($this->entry->getName() . " (false positive WinZip AES entry is too short)"); + if ($size < 0) { + throw new ZipCryptoException($this->entry->getName() . ' (false positive WinZip AES entry is too short)'); } // Load authentication code. $authenticationCode = substr($content, $end, $footerSize); + if ($end + $footerSize !== $endPos) { // This should never happen unless someone is writing to the // end of the file concurrently! - throw new ZipCryptoException("Expected end of file after WinZip AES authentication code!"); + throw new ZipCryptoException('Expected end of file after WinZip AES authentication code!'); } $password = $this->entry->getPassword(); - assert($password !== null); - assert(self::AES_BLOCK_SIZE_BITS <= $keyStrengthBits); - // WinZip 99-character limit - // @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + if ($password === null) { + throw new ZipException(sprintf('Password not set for entry %s', $this->entry->getName())); + } + + /** + * WinZip 99-character limit. + * + * @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + */ $password = substr($password, 0, 99); $ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8; - $iv = str_repeat(chr(0), $ctrIvSize); + $iv = str_repeat(\chr(0), $ctrIvSize); + do { // Here comes the strange part about WinZip AES encryption: // Its unorthodox use of the Password-Based Key Derivation @@ -111,7 +120,7 @@ class WinZipAesEngine implements ZipEncryptionEngine // Yes, the password verifier is only a 16 bit value. // So we must use the MAC for password verification, too. $keyParam = hash_pbkdf2( - "sha1", + 'sha1', $password, $salt, self::ITERATION_COUNT, @@ -126,9 +135,11 @@ class WinZipAesEngine implements ZipEncryptionEngine $content = substr($content, $start, $size); $mac = hash_hmac('sha1', $content, $sha1MacParam, true); - if (substr($mac, 0, 10) !== $authenticationCode) { - throw new ZipAuthenticationException($this->entry->getName() . - " (authenticated WinZip AES entry content has been tampered with)"); + if (strpos($mac, $authenticationCode) !== 0) { + throw new ZipAuthenticationException( + $this->entry->getName() . + ' (authenticated WinZip AES entry content has been tampered with)' + ); } return self::aesCtrSegmentIntegerCounter($content, $key, $iv, false); @@ -137,25 +148,27 @@ class WinZipAesEngine implements ZipEncryptionEngine /** * Decryption or encryption AES-CTR with Segment Integer Count (SIC). * - * @param string $str Data - * @param string $key Key - * @param string $iv IV - * @param bool $encrypted If true encryption else decryption + * @param string $str Data + * @param string $key Key + * @param string $iv IV + * @param bool $encrypted If true encryption else decryption + * * @return string */ private static function aesCtrSegmentIntegerCounter($str, $key, $iv, $encrypted = true) { - $numOfBlocks = ceil(strlen($str) / 16); + $numOfBlocks = ceil(\strlen($str) / 16); $ctrStr = ''; for ($i = 0; $i < $numOfBlocks; ++$i) { for ($j = 0; $j < 16; ++$j) { - $n = ord($iv[$j]); + $n = \ord($iv[$j]); + if (++$n === 0x100) { // overflow, set this one to 0, increment next - $iv[$j] = chr(0); + $iv[$j] = \chr(0); } else { // no overflow, just write incremented number back and abort - $iv[$j] = chr($n); + $iv[$j] = \chr($n); break; } } @@ -164,6 +177,7 @@ class WinZipAesEngine implements ZipEncryptionEngine self::encryptCtr($data, $key, $iv) : self::decryptCtr($data, $key, $iv); } + return $ctrStr; } @@ -171,78 +185,101 @@ class WinZipAesEngine implements ZipEncryptionEngine * Encrypt AES-CTR. * * @param string $data Raw data - * @param string $key Aes key - * @param string $iv Aes IV + * @param string $key Aes key + * @param string $iv Aes IV + * * @return string Encrypted data */ private static function encryptCtr($data, $key, $iv) { - if (extension_loaded("openssl")) { - $numBits = strlen($key) * 8; + if (\extension_loaded('openssl')) { + $numBits = \strlen($key) * 8; /** @noinspection PhpComposerExtensionStubsInspection */ - return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv); - } elseif (extension_loaded("mcrypt")) { - /** @noinspection PhpDeprecationInspection */ - /** @noinspection PhpComposerExtensionStubsInspection */ - return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv); - } else { - throw new RuntimeException('Extension openssl or mcrypt not loaded'); + return openssl_encrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv); } + + if (\extension_loaded('mcrypt')) { + /** @noinspection PhpComposerExtensionStubsInspection */ + return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv); + } + + throw new RuntimeException('Extension openssl or mcrypt not loaded'); } /** * Decrypt AES-CTR. * * @param string $data Encrypted data - * @param string $key Aes key - * @param string $iv Aes IV + * @param string $key Aes key + * @param string $iv Aes IV + * * @return string Raw data */ private static function decryptCtr($data, $key, $iv) { - if (extension_loaded("openssl")) { - $numBits = strlen($key) * 8; + if (\extension_loaded('openssl')) { + $numBits = \strlen($key) * 8; /** @noinspection PhpComposerExtensionStubsInspection */ - return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, OPENSSL_RAW_DATA, $iv); - } elseif (extension_loaded("mcrypt")) { - /** @noinspection PhpDeprecationInspection */ - /** @noinspection PhpComposerExtensionStubsInspection */ - return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, "ctr", $iv); - } else { - throw new RuntimeException('Extension openssl or mcrypt not loaded'); + return openssl_decrypt($data, 'AES-' . $numBits . '-CTR', $key, \OPENSSL_RAW_DATA, $iv); } + + if (\extension_loaded('mcrypt')) { + /** @noinspection PhpComposerExtensionStubsInspection */ + return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv); + } + + throw new RuntimeException('Extension openssl or mcrypt not loaded'); } /** * Encryption string. * * @param string $content + * + * @throws ZipException + * * @return string - * @throws \PhpZip\Exception\ZipException */ public function encrypt($content) { // Init key strength. $password = $this->entry->getPassword(); + if ($password === null) { - throw new ZipException('No password was set for the entry "'.$this->entry->getName().'"'); + throw new ZipException('No password was set for the entry "' . $this->entry->getName() . '"'); } - // WinZip 99-character limit - // @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + /** + * WinZip 99-character limit. + * + * @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + */ $password = substr($password, 0, 99); - $keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($this->entry->getEncryptionMethod()); + $keyStrengthBits = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod( + $this->entry->getEncryptionMethod() + ); $keyStrengthBytes = $keyStrengthBits / 8; - $salt = CryptoUtil::randomBytes($keyStrengthBytes / 2); + try { + $salt = random_bytes($keyStrengthBytes / 2); + } catch (\Exception $e) { + throw new \RuntimeException('Oops, our server is bust and cannot generate any random data.', 1, $e); + } - $keyParam = hash_pbkdf2("sha1", $password, $salt, self::ITERATION_COUNT, (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, true); + $keyParam = hash_pbkdf2( + 'sha1', + $password, + $salt, + self::ITERATION_COUNT, + (2 * $keyStrengthBits + self::PWD_VERIFIER_BITS) / 8, + true + ); $sha1HMacParam = substr($keyParam, $keyStrengthBytes, $keyStrengthBytes); // Can you believe they "forgot" the nonce in the CTR mode IV?! :-( $ctrIvSize = self::AES_BLOCK_SIZE_BITS / 8; - $iv = str_repeat(chr(0), $ctrIvSize); + $iv = str_repeat(\chr(0), $ctrIvSize); $key = substr($keyParam, 0, $keyStrengthBytes); @@ -250,10 +287,9 @@ class WinZipAesEngine implements ZipEncryptionEngine $mac = hash_hmac('sha1', $content, $sha1HMacParam, true); - return ($salt . + return $salt . substr($keyParam, 2 * $keyStrengthBytes, self::PWD_VERIFIER_BITS / 8) . $content . - substr($mac, 0, 10) - ); + substr($mac, 0, 10); } } diff --git a/src/PhpZip/Crypto/ZipEncryptionEngine.php b/src/PhpZip/Crypto/ZipEncryptionEngine.php index 3187969..55dbe10 100644 --- a/src/PhpZip/Crypto/ZipEncryptionEngine.php +++ b/src/PhpZip/Crypto/ZipEncryptionEngine.php @@ -5,9 +5,10 @@ namespace PhpZip\Crypto; use PhpZip\Exception\ZipAuthenticationException; /** - * Encryption Engine + * Encryption Engine. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ @@ -17,8 +18,10 @@ interface ZipEncryptionEngine * Decryption string. * * @param string $encryptionContent - * @return string + * * @throws ZipAuthenticationException + * + * @return string */ public function decrypt($encryptionContent); @@ -26,6 +29,7 @@ interface ZipEncryptionEngine * Encryption string. * * @param string $content + * * @return string */ public function encrypt($content); diff --git a/src/PhpZip/Exception/Crc32Exception.php b/src/PhpZip/Exception/Crc32Exception.php index b0ec2d0..04feb19 100644 --- a/src/PhpZip/Exception/Crc32Exception.php +++ b/src/PhpZip/Exception/Crc32Exception.php @@ -32,20 +32,19 @@ class Crc32Exception extends ZipException * Crc32Exception constructor. * * @param string $name - * @param int $expected - * @param int $actual + * @param int $expected + * @param int $actual */ public function __construct($name, $expected, $actual) { parent::__construct( sprintf( - "%s (expected CRC32 value 0x%x, but is actually 0x%x)", + '%s (expected CRC32 value 0x%x, but is actually 0x%x)', $name, $expected, $actual ) ); - assert($expected != $actual); $this->expectedCrc = $expected; $this->actualCrc = $actual; } diff --git a/src/PhpZip/Exception/ZipEntryNotFoundException.php b/src/PhpZip/Exception/ZipEntryNotFoundException.php index e8cdbae..7524cf7 100644 --- a/src/PhpZip/Exception/ZipEntryNotFoundException.php +++ b/src/PhpZip/Exception/ZipEntryNotFoundException.php @@ -10,21 +10,22 @@ namespace PhpZip\Exception; */ class ZipEntryNotFoundException extends ZipException { - /** - * @var string - */ + /** @var string */ private $entryName; /** * ZipEntryNotFoundException constructor. + * * @param string $entryName */ public function __construct($entryName) { - parent::__construct(sprintf( - "Zip Entry \"%s\" was not found in the archive.", - $entryName - )); + parent::__construct( + sprintf( + 'Zip Entry "%s" was not found in the archive.', + $entryName + ) + ); $this->entryName = $entryName; } diff --git a/src/PhpZip/Exception/ZipException.php b/src/PhpZip/Exception/ZipException.php index bd123f8..e59ec60 100644 --- a/src/PhpZip/Exception/ZipException.php +++ b/src/PhpZip/Exception/ZipException.php @@ -7,6 +7,7 @@ namespace PhpZip\Exception; * * @author Ne-Lexa alexey@nelexa.ru * @license MIT + * * @see \Exception */ class ZipException extends \Exception diff --git a/src/PhpZip/Exception/ZipNotFoundEntry.php b/src/PhpZip/Exception/ZipNotFoundEntry.php deleted file mode 100644 index ffc3064..0000000 --- a/src/PhpZip/Exception/ZipNotFoundEntry.php +++ /dev/null @@ -1,14 +0,0 @@ -collection); + return \count($this->collection); } /** * Returns the Extra Field with the given Header ID or null * if no such Extra Field exists. * - * @param int $headerId The requested Header ID. - * @return ExtraField The Extra Field with the given Header ID or - * if no such Extra Field exists. - * @throws ZipException If headerId is out of range. + * @param int $headerId the requested Header ID + * + * @throws ZipException if headerId is out of range + * + * @return ExtraField|null the Extra Field with the given Header ID or + * if no such Extra Field exists */ public function get($headerId) { - if (0x0000 > $headerId || $headerId > 0xffff) { + if ($headerId < 0x0000 || $headerId > 0xffff) { throw new ZipException('headerId out of range'); } + if (isset($this->collection[$headerId])) { return $this->collection[$headerId]; } + return null; } /** * Stores the given Extra Field in this collection. * - * @param ExtraField $extraField The Extra Field to store in this collection. - * @return ExtraField The Extra Field previously associated with the Header ID of - * of the given Extra Field or null if no such Extra Field existed. - * @throws ZipException If headerId is out of range. + * @param ExtraField $extraField the Extra Field to store in this collection + * + * @throws ZipException if headerId is out of range + * + * @return ExtraField the Extra Field previously associated with the Header ID of + * of the given Extra Field or null if no such Extra Field existed */ public function add(ExtraField $extraField) { $headerId = $extraField::getHeaderId(); - if (0x0000 > $headerId || $headerId > 0xffff) { + + if ($headerId < 0x0000 || $headerId > 0xffff) { throw new ZipException('headerId out of range'); } $this->collection[$headerId] = $extraField; + return $extraField; } /** - * Returns Extra Field exists + * Returns Extra Field exists. + * + * @param int $headerId the requested Header ID * - * @param int $headerId The requested Header ID. * @return bool */ public function has($headerId) @@ -86,34 +95,43 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator /** * Removes the Extra Field with the given Header ID. * - * @param int $headerId The requested Header ID. - * @return ExtraField The Extra Field with the given Header ID or null - * if no such Extra Field exists. - * @throws ZipException If headerId is out of range or extra field not found. + * @param int $headerId the requested Header ID + * + * @throws ZipException if headerId is out of range or extra field not found + * + * @return ExtraField the Extra Field with the given Header ID or null + * if no such Extra Field exists */ public function remove($headerId) { - if (0x0000 > $headerId || $headerId > 0xffff) { + if ($headerId < 0x0000 || $headerId > 0xffff) { throw new ZipException('headerId out of range'); } + if (isset($this->collection[$headerId])) { $ef = $this->collection[$headerId]; unset($this->collection[$headerId]); + return $ef; } + throw new ZipException('ExtraField not found'); } /** - * Whether a offset exists - * @link http://php.net/manual/en/arrayaccess.offsetexists.php + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * * @param mixed $offset
- * An offset to check for. - *
- * @return boolean true on success or false on failure. - * - *- * The return value will be casted to boolean if non-boolean was returned. + * An offset to check for. + *
+ * + * @return bool true on success or false on failure. + * + *+ * The return value will be casted to boolean if non-boolean was returned. + * * @since 5.0.0 */ public function offsetExists($offset) @@ -122,14 +140,19 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Offset to retrieve - * @link http://php.net/manual/en/arrayaccess.offsetget.php + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * * @param mixed $offset
- * The offset to retrieve. - *
- * @return mixed Can return all value types. - * @since 5.0.0 + * The offset to retrieve. + * + * * @throws ZipException + * + * @return mixed can return all value types + * + * @since 5.0.0 */ public function offsetGet($offset) { @@ -137,23 +160,26 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Offset to set - * @link http://php.net/manual/en/arrayaccess.offsetset.php + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * * @param mixed $offset- * The offset to assign the value to. - *
- * @param mixed $value- * The value to set. - *
- * @return void + * The offset to assign the value to. + * + * @param mixed $value+ * The value to set. + *
+ * * @throws ZipException + * * @since 5.0.0 */ public function offsetSet($offset, $value) { if ($value instanceof ExtraField) { if ($offset !== $value::getHeaderId()) { - throw new InvalidArgumentException("Value header id !== array access key"); + throw new InvalidArgumentException('Value header id !== array access key'); } $this->add($value); } else { @@ -162,13 +188,16 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Offset to unset - * @link http://php.net/manual/en/arrayaccess.offsetunset.php + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * * @param mixed $offset- * The offset to unset. - *
- * @return void + * The offset to unset. + * + * * @since 5.0.0 + * * @throws ZipException */ public function offsetUnset($offset) @@ -177,9 +206,12 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Return the current element - * @link http://php.net/manual/en/iterator.current.php - * @return mixed Can return any type. + * Return the current element. + * + * @see http://php.net/manual/en/iterator.current.php + * + * @return mixed can return any type + * * @since 5.0.0 */ public function current() @@ -188,9 +220,9 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Move forward to next element - * @link http://php.net/manual/en/iterator.next.php - * @return void Any returned value is ignored. + * Move forward to next element. + * + * @see http://php.net/manual/en/iterator.next.php * @since 5.0.0 */ public function next() @@ -199,9 +231,12 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Return the key of the current element - * @link http://php.net/manual/en/iterator.key.php - * @return mixed scalar on success, or null on failure. + * Return the key of the current element. + * + * @see http://php.net/manual/en/iterator.key.php + * + * @return mixed scalar on success, or null on failure + * * @since 5.0.0 */ public function key() @@ -210,10 +245,13 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Checks if current position is valid - * @link http://php.net/manual/en/iterator.valid.php - * @return boolean The return value will be casted to boolean and then evaluated. - * Returns true on success or false on failure. + * Checks if current position is valid. + * + * @see http://php.net/manual/en/iterator.valid.php + * + * @return bool The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + * * @since 5.0.0 */ public function valid() @@ -222,9 +260,9 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator } /** - * Rewind the Iterator to the first element - * @link http://php.net/manual/en/iterator.rewind.php - * @return void Any returned value is ignored. + * Rewind the Iterator to the first element. + * + * @see http://php.net/manual/en/iterator.rewind.php * @since 5.0.0 */ public function rewind() diff --git a/src/PhpZip/Extra/ExtraFieldsFactory.php b/src/PhpZip/Extra/ExtraFieldsFactory.php index 4d6e3c9..08c0648 100644 --- a/src/PhpZip/Extra/ExtraFieldsFactory.php +++ b/src/PhpZip/Extra/ExtraFieldsFactory.php @@ -13,16 +13,14 @@ use PhpZip\Model\ZipEntry; use PhpZip\Util\StringUtil; /** - * Extra Fields Factory + * Extra Fields Factory. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class ExtraFieldsFactory { - /** - * @var array|null - */ + /** @var array|null */ protected static $registry; private function __construct() @@ -30,18 +28,22 @@ class ExtraFieldsFactory } /** - * @param string $extra + * @param string $extra * @param ZipEntry|null $entry - * @return ExtraFieldsCollection + * * @throws ZipException + * + * @return ExtraFieldsCollection */ public static function createExtraFieldCollections($extra, ZipEntry $entry = null) { $extraFieldsCollection = new ExtraFieldsCollection(); + if ($extra !== null) { - $extraLength = strlen($extra); + $extraLength = \strlen($extra); + if ($extraLength > 0xffff) { - throw new ZipException("Extra Fields too large: " . $extraLength); + throw new ZipException('Extra Fields too large: ' . $extraLength); } $pos = 0; $endPos = $extraLength; @@ -49,38 +51,53 @@ class ExtraFieldsFactory while ($endPos - $pos >= 4) { $unpack = unpack('vheaderId/vdataSize', substr($extra, $pos, 4)); $pos += 4; - $headerId = (int)$unpack['headerId']; - $dataSize = (int)$unpack['dataSize']; - $extraField = ExtraFieldsFactory::create($headerId); + $headerId = (int) $unpack['headerId']; + $dataSize = (int) $unpack['dataSize']; + $extraField = self::create($headerId); + if ($extraField instanceof Zip64ExtraField && $entry !== null) { $extraField->setEntry($entry); } - $extraField->deserialize(substr($extra, $pos, $dataSize)); + + if ($dataSize > 0) { + $content = substr($extra, $pos, $dataSize); + + if ($content !== false) { + $extraField->deserialize($content); + $extraFieldsCollection[$headerId] = $extraField; + } + } + $pos += $dataSize; - $extraFieldsCollection[$headerId] = $extraField; } } + return $extraFieldsCollection; } /** * @param ExtraFieldsCollection $extraFieldsCollection - * @return string + * * @throws ZipException + * + * @return string */ public static function createSerializedData(ExtraFieldsCollection $extraFieldsCollection) { $extraData = ''; + foreach ($extraFieldsCollection as $extraField) { $data = $extraField->serialize(); - $extraData .= pack('vv', $extraField::getHeaderId(), strlen($data)); + $extraData .= pack('vv', $extraField::getHeaderId(), \strlen($data)); $extraData .= $data; } - $size = strlen($extraData); - if (0x0000 > $size || $size > 0xffff) { + $size = \strlen($extraData); + + if ($size < 0x0000 || $size > 0xffff) { throw new ZipException('Size extra out of range: ' . $size . '. Extra data: ' . $extraData); } + return $extraData; } @@ -90,29 +107,31 @@ class ExtraFieldsFactory * The returned Extra Field still requires proper initialization, for * example by calling ExtraField::readFrom. * - * @param int $headerId An unsigned short integer (two bytes) which indicates - * the type of the returned Extra Field. - * @return ExtraField A new Extra Field or null if not support header id. - * @throws ZipException If headerId is out of range. + * @param int $headerId an unsigned short integer (two bytes) which indicates + * the type of the returned Extra Field + * + * @throws ZipException if headerId is out of range + * + * @return ExtraField a new Extra Field or null if not support header id */ public static function create($headerId) { - if (0x0000 > $headerId || $headerId > 0xffff) { + if ($headerId < 0x0000 || $headerId > 0xffff) { throw new ZipException('headerId out of range'); } - /** - * @var ExtraField $extraField - */ if (isset(self::getRegistry()[$headerId])) { $extraClassName = self::getRegistry()[$headerId]; - $extraField = new $extraClassName; + /** @var ExtraField $extraField */ + $extraField = new $extraClassName(); + if ($headerId !== $extraField::getHeaderId()) { throw new ZipException('Runtime error support headerId ' . $headerId); } } else { $extraField = new DefaultExtraField($headerId); } + return $extraField; } @@ -130,6 +149,7 @@ class ExtraFieldsFactory self::$registry[ApkAlignmentExtraField::getHeaderId()] = ApkAlignmentExtraField::class; self::$registry[JarMarkerExtraField::getHeaderId()] = JarMarkerExtraField::class; } + return self::$registry; } @@ -151,6 +171,7 @@ class ExtraFieldsFactory /** * @param ZipEntry $entry + * * @return Zip64ExtraField */ public static function createZip64Extra(ZipEntry $entry) @@ -160,19 +181,22 @@ class ExtraFieldsFactory /** * @param ZipEntry $entry - * @param int $padding + * @param int $padding + * * @return ApkAlignmentExtraField */ public static function createApkAlignExtra(ZipEntry $entry, $padding) { - $padding = (int)$padding; + $padding = (int) $padding; $multiple = 4; + if (StringUtil::endsWith($entry->getName(), '.so')) { $multiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; } $extraField = new ApkAlignmentExtraField(); $extraField->setMultiple($multiple); $extraField->setPadding($padding); + return $extraField; } } diff --git a/src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php b/src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php index a9b9031..9548f63 100644 --- a/src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php +++ b/src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php @@ -6,7 +6,7 @@ use PhpZip\Exception\InvalidArgumentException; use PhpZip\Extra\ExtraField; /** - * Apk Alignment Extra Field + * Apk Alignment Extra Field. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT @@ -21,13 +21,10 @@ class ApkAlignmentExtraField implements ExtraField const ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096; - /** - * @var int - */ + /** @var int */ private $multiple; - /** - * @var int - */ + + /** @var int */ private $padding; /** @@ -44,6 +41,7 @@ class ApkAlignmentExtraField implements ExtraField /** * Serializes a Data Block. + * * @return string */ public function serialize() @@ -53,25 +51,31 @@ class ApkAlignmentExtraField implements ExtraField ['vc*', $this->multiple], array_fill(2, $this->padding, 0) ); - return call_user_func_array('pack', $args); + + return \call_user_func_array('pack', $args); } + return pack('v', $this->multiple); } /** * Initializes this Extra Field by deserializing a Data Block. + * * @param string $data */ public function deserialize($data) { - $length = strlen($data); + $length = \strlen($data); + if ($length < 2) { // This is APK alignment field. // FORMAT: // * uint16 alignment multiple (in bytes) // * remaining bytes -- padding to achieve alignment of data which starts after // the extra field - throw new InvalidArgumentException("Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries."); + throw new InvalidArgumentException( + 'Minimum 6 bytes of the extensible data block/field used for alignment of uncompressed entries.' + ); } $this->multiple = unpack('v', $data)[1]; $this->padding = $length - 2; diff --git a/src/PhpZip/Extra/Fields/DefaultExtraField.php b/src/PhpZip/Extra/Fields/DefaultExtraField.php index 77af380..231fa32 100644 --- a/src/PhpZip/Extra/Fields/DefaultExtraField.php +++ b/src/PhpZip/Extra/Fields/DefaultExtraField.php @@ -14,26 +14,23 @@ use PhpZip\Extra\ExtraField; */ class DefaultExtraField implements ExtraField { - /** - * @var int - */ + /** @var int */ private static $headerId; - /** - * @var string - */ + /** @var string */ protected $data; /** * Constructs a new Extra Field. * * @param int $headerId an unsigned short integer (two bytes) indicating the - * type of the Extra Field. + * type of the Extra Field + * * @throws ZipException */ public function __construct($headerId) { - if (0x0000 > $headerId || $headerId > 0xffff) { + if ($headerId < 0x0000 || $headerId > 0xffff) { throw new ZipException('headerId out of range'); } self::$headerId = $headerId; @@ -53,6 +50,7 @@ class DefaultExtraField implements ExtraField /** * Serializes a Data Block. + * * @return string */ public function serialize() @@ -62,6 +60,7 @@ class DefaultExtraField implements ExtraField /** * Initializes this Extra Field by deserializing a Data Block. + * * @param string $data */ public function deserialize($data) diff --git a/src/PhpZip/Extra/Fields/JarMarkerExtraField.php b/src/PhpZip/Extra/Fields/JarMarkerExtraField.php index 01c66b4..8914204 100644 --- a/src/PhpZip/Extra/Fields/JarMarkerExtraField.php +++ b/src/PhpZip/Extra/Fields/JarMarkerExtraField.php @@ -30,6 +30,7 @@ class JarMarkerExtraField implements ExtraField /** * Serializes a Data Block. + * * @return string */ public function serialize() @@ -39,12 +40,14 @@ class JarMarkerExtraField implements ExtraField /** * Initializes this Extra Field by deserializing a Data Block. + * * @param string $data + * * @throws ZipException */ public function deserialize($data) { - if (strlen($data) !== 0) { + if ($data !== '') { throw new ZipException("JarMarker doesn't expect any data"); } } diff --git a/src/PhpZip/Extra/Fields/NtfsExtraField.php b/src/PhpZip/Extra/Fields/NtfsExtraField.php index 02f761c..626a88b 100644 --- a/src/PhpZip/Extra/Fields/NtfsExtraField.php +++ b/src/PhpZip/Extra/Fields/NtfsExtraField.php @@ -6,31 +6,31 @@ use PhpZip\Extra\ExtraField; use PhpZip\Util\PackUtil; /** - * NTFS Extra Field + * NTFS Extra Field. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class NtfsExtraField implements ExtraField { - /** - * Modify time + * Modify time. * * @var int Unix Timestamp */ private $mtime; /** - * Access Time + * Access Time. * * @var int Unix Timestamp */ private $atime; /** - * Create Time + * Create Time. * * @var int Unix Time */ @@ -50,11 +50,13 @@ class NtfsExtraField implements ExtraField /** * Initializes this Extra Field by deserializing a Data Block. + * * @param string $data */ public function deserialize($data) { $unpack = unpack('vtag/vsizeAttr', substr($data, 0, 4)); + if ($unpack['sizeAttr'] === 24) { $tagData = substr($data, 4, $unpack['sizeAttr']); $this->mtime = PackUtil::unpackLongLE(substr($tagData, 0, 8)) / 10000000 - 11644473600; @@ -65,11 +67,13 @@ class NtfsExtraField implements ExtraField /** * Serializes a Data Block. + * * @return string */ public function serialize() { $serialize = ''; + if ($this->mtime !== null && $this->atime !== null && $this->ctime !== null) { $mtimeLong = ($this->mtime + 11644473600) * 10000000; $atimeLong = ($this->atime + 11644473600) * 10000000; @@ -80,6 +84,7 @@ class NtfsExtraField implements ExtraField . PackUtil::packLongLE($atimeLong) . PackUtil::packLongLE($ctimeLong); } + return $serialize; } @@ -96,7 +101,7 @@ class NtfsExtraField implements ExtraField */ public function setMtime($mtime) { - $this->mtime = (int)$mtime; + $this->mtime = (int) $mtime; } /** @@ -112,7 +117,7 @@ class NtfsExtraField implements ExtraField */ public function setAtime($atime) { - $this->atime = (int)$atime; + $this->atime = (int) $atime; } /** @@ -128,6 +133,6 @@ class NtfsExtraField implements ExtraField */ public function setCtime($ctime) { - $this->ctime = (int)$ctime; + $this->ctime = (int) $ctime; } } diff --git a/src/PhpZip/Extra/Fields/WinZipAesEntryExtraField.php b/src/PhpZip/Extra/Fields/WinZipAesEntryExtraField.php index 7f640bf..582514a 100644 --- a/src/PhpZip/Extra/Fields/WinZipAesEntryExtraField.php +++ b/src/PhpZip/Extra/Fields/WinZipAesEntryExtraField.php @@ -4,47 +4,54 @@ namespace PhpZip\Extra\Fields; use PhpZip\Exception\ZipException; use PhpZip\Extra\ExtraField; -use PhpZip\ZipFileInterface; +use PhpZip\ZipFile; /** * WinZip AES Extra Field. * - * @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2 (WinZip Computing, S.L.) + * @see http://www.winzip.com/win/en/aes_info.htm AES Encryption Information: Encryption Specification AE-1 and AE-2 + * (WinZip Computing, S.L.) * @see http://www.winzip.com/win/en/aes_tips.htm AES Coding Tips for Developers (WinZip Computing, S.L.) + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class WinZipAesEntryExtraField implements ExtraField { const DATA_SIZE = 7; + const VENDOR_ID = 17729; // 'A' | ('E' << 8); /** * Entries of this type do include the standard ZIP CRC-32 value. - * For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion(). + * For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see + * WinZipAesEntryExtraField::getVendorVersion(). */ const VV_AE_1 = 1; /** * Entries of this type do not include the standard ZIP CRC-32 value. - * For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see WinZipAesEntryExtraField::getVendorVersion(). + * For use with @see WinZipAesEntryExtraField::setVendorVersion()}/@see + * WinZipAesEntryExtraField::getVendorVersion(). */ const VV_AE_2 = 2; const KEY_STRENGTH_128BIT = 128; + const KEY_STRENGTH_192BIT = 192; + const KEY_STRENGTH_256BIT = 256; protected static $keyStrengths = [ self::KEY_STRENGTH_128BIT => 0x01, self::KEY_STRENGTH_192BIT => 0x02, - self::KEY_STRENGTH_256BIT => 0x03 + self::KEY_STRENGTH_256BIT => 0x03, ]; protected static $encryptionMethods = [ - self::KEY_STRENGTH_128BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128, - self::KEY_STRENGTH_192BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192, - self::KEY_STRENGTH_256BIT => ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 + self::KEY_STRENGTH_128BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128, + self::KEY_STRENGTH_192BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192, + self::KEY_STRENGTH_256BIT => ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256, ]; /** @@ -94,14 +101,16 @@ class WinZipAesEntryExtraField implements ExtraField /** * Sets the vendor version. * + * @param int $vendorVersion the vendor version + * + * @throws ZipException unsupport vendor version + * * @see WinZipAesEntryExtraField::VV_AE_1 * @see WinZipAesEntryExtraField::VV_AE_2 - * @param int $vendorVersion the vendor version. - * @throws ZipException Unsupport vendor version. */ public function setVendorVersion($vendorVersion) { - if ($vendorVersion < self::VV_AE_1 || self::VV_AE_2 < $vendorVersion) { + if ($vendorVersion < self::VV_AE_1 || $vendorVersion > self::VV_AE_2) { throw new ZipException($vendorVersion); } $this->vendorVersion = $vendorVersion; @@ -118,8 +127,9 @@ class WinZipAesEntryExtraField implements ExtraField } /** - * @return bool|int * @throws ZipException + * + * @return bool|int */ public function getKeyStrength() { @@ -127,16 +137,20 @@ class WinZipAesEntryExtraField implements ExtraField } /** - * @param int $encryptionStrength Encryption strength as bits. + * @param int $encryptionStrength encryption strength as bits + * + * @throws ZipException if unsupport encryption strength + * * @return int - * @throws ZipException If unsupport encryption strength. */ public static function keyStrength($encryptionStrength) { $flipKeyStrength = array_flip(self::$keyStrengths); + if (!isset($flipKeyStrength[$encryptionStrength])) { - throw new ZipException("Unsupport encryption strength " . $encryptionStrength); + throw new ZipException('Unsupport encryption strength ' . $encryptionStrength); } + return $flipKeyStrength[$encryptionStrength]; } @@ -153,8 +167,9 @@ class WinZipAesEntryExtraField implements ExtraField /** * Internal encryption method. * - * @return int * @throws ZipException + * + * @return int */ public function getEncryptionMethod() { @@ -165,15 +180,19 @@ class WinZipAesEntryExtraField implements ExtraField /** * @param int $encryptionMethod - * @return int + * * @throws ZipException + * + * @return int */ public static function getKeyStrangeFromEncryptionMethod($encryptionMethod) { $flipKey = array_flip(self::$encryptionMethods); + if (!isset($flipKey[$encryptionMethod])) { - throw new ZipException("Unsupport encryption method " . $encryptionMethod); + throw new ZipException('Unsupport encryption method ' . $encryptionMethod); } + return $flipKey[$encryptionMethod]; } @@ -181,11 +200,12 @@ class WinZipAesEntryExtraField implements ExtraField * Sets compression method. * * @param int $compressionMethod Compression method - * @throws ZipException Compression method out of range. + * + * @throws ZipException compression method out of range */ public function setMethod($compressionMethod) { - if (0x0000 > $compressionMethod || $compressionMethod > 0xffff) { + if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) { throw new ZipException('Compression method out of range'); } $this->method = $compressionMethod; @@ -204,7 +224,8 @@ class WinZipAesEntryExtraField implements ExtraField /** * Returns encryption strength. * - * @param int $keyStrength Key strength in bits. + * @param int $keyStrength key strength in bits + * * @return int */ public static function encryptionStrength($keyStrength) @@ -216,6 +237,7 @@ class WinZipAesEntryExtraField implements ExtraField /** * Serializes a Data Block. + * * @return string */ public function serialize() @@ -231,13 +253,16 @@ class WinZipAesEntryExtraField implements ExtraField /** * Initializes this Extra Field by deserializing a Data Block. + * * @param string $data + * * @throws ZipException */ public function deserialize($data) { - $size = strlen($data); - if (self::DATA_SIZE !== $size) { + $size = \strlen($data); + + if ($size !== self::DATA_SIZE) { throw new ZipException('WinZip AES Extra data invalid size: ' . $size . '. Must be ' . self::DATA_SIZE); } @@ -249,7 +274,8 @@ class WinZipAesEntryExtraField implements ExtraField */ $unpack = unpack('vvendorVersion/vvendorId/ckeyStrength/vmethod', $data); $this->setVendorVersion($unpack['vendorVersion']); - if (self::VENDOR_ID !== $unpack['vendorId']) { + + if ($unpack['vendorId'] !== self::VENDOR_ID) { throw new ZipException('Vendor id invalid: ' . $unpack['vendorId'] . '. Must be ' . self::VENDOR_ID); } $this->setKeyStrength(self::keyStrength($unpack['keyStrength'])); // checked diff --git a/src/PhpZip/Extra/Fields/Zip64ExtraField.php b/src/PhpZip/Extra/Fields/Zip64ExtraField.php index c39dff0..7d424ac 100644 --- a/src/PhpZip/Extra/Fields/Zip64ExtraField.php +++ b/src/PhpZip/Extra/Fields/Zip64ExtraField.php @@ -3,14 +3,16 @@ namespace PhpZip\Extra\Fields; use PhpZip\Exception\RuntimeException; +use PhpZip\Exception\ZipException; use PhpZip\Extra\ExtraField; use PhpZip\Model\ZipEntry; use PhpZip\Util\PackUtil; /** - * ZIP64 Extra Field + * ZIP64 Extra Field. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ @@ -18,13 +20,13 @@ class Zip64ExtraField implements ExtraField { /** The Header ID for a ZIP64 Extended Information Extra Field. */ const ZIP64_HEADER_ID = 0x0001; - /** - * @var ZipEntry - */ + + /** @var ZipEntry */ protected $entry; /** * Zip64ExtraField constructor. + * * @param ZipEntry $entry */ public function __construct(ZipEntry $entry = null) @@ -56,61 +58,65 @@ class Zip64ExtraField implements ExtraField /** * Serializes a Data Block. + * * @return string */ public function serialize() { if ($this->entry === null) { - throw new RuntimeException("entry is null"); + throw new RuntimeException('entry is null'); } $data = ''; // Write out Uncompressed Size. $size = $this->entry->getSize(); - if (0xffffffff <= $size) { + + if ($size >= 0xffffffff) { $data .= PackUtil::packLongLE($size); } // Write out Compressed Size. $compressedSize = $this->entry->getCompressedSize(); - if (0xffffffff <= $compressedSize) { + + if ($compressedSize >= 0xffffffff) { $data .= PackUtil::packLongLE($compressedSize); } // Write out Relative Header Offset. $offset = $this->entry->getOffset(); - if (0xffffffff <= $offset) { + + if ($offset >= 0xffffffff) { $data .= PackUtil::packLongLE($offset); } + return $data; } /** * Initializes this Extra Field by deserializing a Data Block. + * * @param string $data - * @throws \PhpZip\Exception\ZipException + * + * @throws ZipException */ public function deserialize($data) { if ($this->entry === null) { - throw new RuntimeException("entry is null"); + throw new RuntimeException('entry is null'); } $off = 0; + // Read in Uncompressed Size. - $size = $this->entry->getSize(); - if (0xffffffff <= $size) { - assert(0xffffffff === $size); + if ($this->entry->getSize() === 0xffffffff) { $this->entry->setSize(PackUtil::unpackLongLE(substr($data, $off, 8))); $off += 8; } + // Read in Compressed Size. - $compressedSize = $this->entry->getCompressedSize(); - if (0xffffffff <= $compressedSize) { - assert(0xffffffff === $compressedSize); + if ($this->entry->getCompressedSize() === 0xffffffff) { $this->entry->setCompressedSize(PackUtil::unpackLongLE(substr($data, $off, 8))); $off += 8; } + // Read in Relative Header Offset. - $offset = $this->entry->getOffset(); - if (0xffffffff <= $offset) { - assert(0xffffffff, $offset); + if ($this->entry->getOffset() === 0xffffffff) { $this->entry->setOffset(PackUtil::unpackLongLE(substr($data, $off, 8))); } } diff --git a/src/PhpZip/Mapper/OffsetPositionMapper.php b/src/PhpZip/Mapper/OffsetPositionMapper.php deleted file mode 100644 index 7ea9116..0000000 --- a/src/PhpZip/Mapper/OffsetPositionMapper.php +++ /dev/null @@ -1,43 +0,0 @@ -offset = (int)$offset; - } - - /** - * @param int $position - * @return int - */ - public function map($position) - { - return parent::map($position) + $this->offset; - } - - /** - * @param int $position - * @return int - */ - public function unmap($position) - { - return parent::unmap($position) - $this->offset; - } -} diff --git a/src/PhpZip/Mapper/PositionMapper.php b/src/PhpZip/Mapper/PositionMapper.php deleted file mode 100644 index e5d67c8..0000000 --- a/src/PhpZip/Mapper/PositionMapper.php +++ /dev/null @@ -1,30 +0,0 @@ -entryCount = $entryCount; - $this->comment = $comment; + $this->cdOffset = $cdOffset; + $this->cdSize = $cdSize; $this->zip64 = $zip64; + $this->comment = $comment; } /** - * @return null|string + * @param string|null $comment */ - public function getComment() + public function setComment($comment) { - return $this->comment; + $this->comment = $comment; } /** @@ -110,6 +123,30 @@ class EndOfCentralDirectory return $this->entryCount; } + /** + * @return int + */ + public function getCdOffset() + { + return $this->cdOffset; + } + + /** + * @return int + */ + public function getCdSize() + { + return $this->cdSize; + } + + /** + * @return string|null + */ + public function getComment() + { + return $this->comment; + } + /** * @return bool */ diff --git a/src/PhpZip/Model/Entry/OutputOffsetEntry.php b/src/PhpZip/Model/Entry/OutputOffsetEntry.php index 94bd15b..b968212 100644 --- a/src/PhpZip/Model/Entry/OutputOffsetEntry.php +++ b/src/PhpZip/Model/Entry/OutputOffsetEntry.php @@ -9,20 +9,19 @@ use PhpZip\Model\ZipEntry; * * @author Ne-Lexa alexey@nelexa.ru * @license MIT + * + * @internal */ class OutputOffsetEntry { - /** - * @var int - */ + /** @var int */ private $offset; - /** - * @var ZipEntry - */ + + /** @var ZipEntry */ private $entry; /** - * @param int $pos + * @param int $pos * @param ZipEntry $entry */ public function __construct($pos, ZipEntry $entry) diff --git a/src/PhpZip/Model/Entry/ZipAbstractEntry.php b/src/PhpZip/Model/Entry/ZipAbstractEntry.php index b8cdb5e..8736baa 100644 --- a/src/PhpZip/Model/Entry/ZipAbstractEntry.php +++ b/src/PhpZip/Model/Entry/ZipAbstractEntry.php @@ -10,65 +10,60 @@ use PhpZip\Extra\Fields\WinZipAesEntryExtraField; use PhpZip\Model\ZipEntry; use PhpZip\Util\DateTimeConverter; use PhpZip\Util\StringUtil; -use PhpZip\ZipFileInterface; +use PhpZip\ZipFile; /** * Abstract ZIP entry. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ abstract class ZipAbstractEntry implements ZipEntry { - /** - * @var int Bit flags for init state. - */ - private $init; - /** - * @var string Entry name (filename in archive) - */ + /** @var string Entry name (filename in archive) */ private $name; - /** - * @var int Made by platform - */ - private $platform; - /** - * @var int - */ - private $versionNeededToExtract = 20; - /** - * @var int Compression method - */ - private $method; - /** - * @var int - */ - private $general; - /** - * @var int Dos time - */ - private $dosTime; - /** - * @var int Crc32 - */ - private $crc; - /** - * @var int Compressed size - */ + + /** @var int Made by platform */ + private $createdOS = self::UNKNOWN; + + /** @var int Extracted by platform */ + private $extractedOS = self::UNKNOWN; + + /** @var int */ + private $softwareVersion = self::UNKNOWN; + + /** @var int */ + private $versionNeededToExtract = self::UNKNOWN; + + /** @var int Compression method */ + private $method = self::UNKNOWN; + + /** @var int */ + private $generalPurposeBitFlags = 0; + + /** @var int Dos time */ + private $dosTime = self::UNKNOWN; + + /** @var int Crc32 */ + private $crc = self::UNKNOWN; + + /** @var int Compressed size */ private $compressedSize = self::UNKNOWN; - /** - * @var int Uncompressed size - */ + + /** @var int Uncompressed size */ private $size = self::UNKNOWN; - /** - * @var int External attributes - */ - private $externalAttributes; - /** - * @var int Relative Offset Of Local File Header. - */ - private $offset = self::UNKNOWN; + + /** @var int Internal attributes */ + private $internalAttributes = 0; + + /** @var int External attributes */ + private $externalAttributes = 0; + + /** @var int relative Offset Of Local File Header */ + private $offset = 0; + /** * Collections of Extra Fields. * Keys from Header ID [int] and value Extra Field [ExtraField]. @@ -77,27 +72,27 @@ abstract class ZipAbstractEntry implements ZipEntry * @var ExtraFieldsCollection */ private $extraFieldsCollection; - /** - * @var string Comment field. - */ + + /** @var string|null comment field */ private $comment; - /** - * @var string Entry password for read or write encryption data. - */ + + /** @var string entry password for read or write encryption data */ private $password; + /** * Encryption method. - * @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 + * + * @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128 + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192 + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256 + * * @var int */ - private $encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL; - /** - * @var int - */ - private $compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION; + private $encryptionMethod = ZipFile::ENCRYPTION_METHOD_TRADITIONAL; + + /** @var int */ + private $compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION; /** * ZipAbstractEntry constructor. @@ -109,12 +104,15 @@ abstract class ZipAbstractEntry implements ZipEntry /** * @param ZipEntry $entry + * * @throws ZipException */ public function setEntry(ZipEntry $entry) { $this->setName($entry->getName()); - $this->setPlatform($entry->getPlatform()); + $this->setSoftwareVersion($entry->getSoftwareVersion()); + $this->setCreatedOS($entry->getCreatedOS()); + $this->setExtractedOS($entry->getExtractedOS()); $this->setVersionNeededToExtract($entry->getVersionNeededToExtract()); $this->setMethod($entry->getMethod()); $this->setGeneralPurposeBitFlags($entry->getGeneralPurposeBitFlags()); @@ -122,6 +120,7 @@ abstract class ZipAbstractEntry implements ZipEntry $this->setCrc($entry->getCrc()); $this->setCompressedSize($entry->getCompressedSize()); $this->setSize($entry->getSize()); + $this->setInternalAttributes($entry->getInternalAttributes()); $this->setExternalAttributes($entry->getExternalAttributes()); $this->setOffset($entry->getOffset()); $this->setExtra($entry->getExtra()); @@ -146,87 +145,150 @@ abstract class ZipAbstractEntry implements ZipEntry * Set entry name. * * @param string $name New entry name - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry */ public function setName($name) { - $length = strlen($name); - if (0x0000 > $length || $length > 0xffff) { + $length = \strlen($name); + + if ($length < 0x0000 || $length > 0xffff) { throw new ZipException('Illegal zip entry name parameter'); } $this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true); $this->name = $name; + $this->externalAttributes = $this->isDirectory() ? 0x10 : 0; + return $this; } /** * Sets the indexed General Purpose Bit Flag. * - * @param int $mask + * @param int $mask * @param bool $bit + * * @return ZipEntry */ public function setGeneralPurposeBitFlag($mask, $bit) { if ($bit) { - $this->general |= $mask; + $this->generalPurposeBitFlags |= $mask; } else { - $this->general &= ~$mask; + $this->generalPurposeBitFlags &= ~$mask; } + return $this; } /** * @return int Get platform + * + * @deprecated Use {@see ZipEntry::getCreatedOS()} + * @noinspection PhpUsageOfSilenceOperatorInspection */ public function getPlatform() { - return $this->isInit(self::BIT_PLATFORM) ? $this->platform & 0xffff : self::UNKNOWN; + @trigger_error('ZipEntry::getPlatform() is deprecated. Use ZipEntry::getCreatedOS()', \E_USER_DEPRECATED); + + return $this->getCreatedOS(); } /** - * Set platform - * * @param int $platform + * * @return ZipEntry + * * @throws ZipException + * + * @deprecated Use {@see ZipEntry::setCreatedOS()} + * @noinspection PhpUsageOfSilenceOperatorInspection */ public function setPlatform($platform) { - $known = self::UNKNOWN !== $platform; - if ($known) { - if (0x00 > $platform || $platform > 0xff) { - throw new ZipException("Platform out of range"); - } - $this->platform = $platform; - } else { - $this->platform = 0; + @trigger_error('ZipEntry::setPlatform() is deprecated. Use ZipEntry::setCreatedOS()', \E_USER_DEPRECATED); + + return $this->setCreatedOS($platform); + } + + /** + * @return int platform + */ + public function getCreatedOS() + { + return $this->createdOS; + } + + /** + * Set platform. + * + * @param int $platform + * + * @throws ZipException + * + * @return ZipEntry + */ + public function setCreatedOS($platform) + { + $platform = (int) $platform; + + if ($platform < 0x00 || $platform > 0xff) { + throw new ZipException('Platform out of range'); } - $this->setInit(self::BIT_PLATFORM, $known); + $this->createdOS = $platform; + return $this; } /** - * @param int $mask - * @return bool + * @return int */ - protected function isInit($mask) + public function getExtractedOS() { - return ($this->init & $mask) !== 0; + return $this->extractedOS; } /** - * @param int $mask - * @param bool $init + * Set extracted OS. + * + * @param int $platform + * + * @throws ZipException + * + * @return ZipEntry */ - protected function setInit($mask, $init) + public function setExtractedOS($platform) { - if ($init) { - $this->init |= $mask; - } else { - $this->init &= ~$mask; + $platform = (int) $platform; + + if ($platform < 0x00 || $platform > 0xff) { + throw new ZipException('Platform out of range'); } + $this->extractedOS = $platform; + + return $this; + } + + /** + * @return int + */ + public function getSoftwareVersion() + { + return $this->softwareVersion; + } + + /** + * @param int $softwareVersion + * + * @return ZipEntry + */ + public function setSoftwareVersion($softwareVersion) + { + $this->softwareVersion = (int) $softwareVersion; + + return $this; } /** @@ -236,6 +298,24 @@ abstract class ZipAbstractEntry implements ZipEntry */ public function getVersionNeededToExtract() { + if ($this->versionNeededToExtract === self::UNKNOWN) { + $method = $this->getMethod(); + + if ($method === self::METHOD_WINZIP_AES) { + return 51; + } + + if ($method === ZipFile::METHOD_BZIP2) { + return 46; + } + + if ($this->isZip64ExtensionsRequired()) { + return 45; + } + + return $method === ZipFile::METHOD_DEFLATED || $this->isDirectory() ? 20 : 10; + } + return $this->versionNeededToExtract; } @@ -243,11 +323,13 @@ abstract class ZipAbstractEntry implements ZipEntry * Set version needed to extract. * * @param int $version + * * @return ZipEntry */ public function setVersionNeededToExtract($version) { $this->versionNeededToExtract = $version; + return $this; } @@ -256,8 +338,8 @@ abstract class ZipAbstractEntry implements ZipEntry */ public function isZip64ExtensionsRequired() { - return 0xffffffff <= $this->getCompressedSize() - || 0xffffffff <= $this->getSize(); + return $this->getCompressedSize() >= 0xffffffff + || $this->getSize() >= 0xffffffff; } /** @@ -273,12 +355,14 @@ abstract class ZipAbstractEntry implements ZipEntry /** * Sets the compressed size of this entry. * - * @param int $compressedSize The Compressed Size. + * @param int $compressedSize the Compressed Size + * * @return ZipEntry */ public function setCompressedSize($compressedSize) { $this->compressedSize = $compressedSize; + return $this; } @@ -295,12 +379,14 @@ abstract class ZipAbstractEntry implements ZipEntry /** * Sets the uncompressed size of this entry. * - * @param int $size The (Uncompressed) Size. + * @param int $size the (Uncompressed) Size + * * @return ZipEntry */ public function setSize($size) { $this->size = $size; + return $this; } @@ -316,49 +402,59 @@ abstract class ZipAbstractEntry implements ZipEntry /** * @param int $offset + * * @return ZipEntry */ public function setOffset($offset) { - $this->offset = $offset; + $this->offset = (int) $offset; + return $this; } /** * Returns the General Purpose Bit Flags. + * * @return int */ public function getGeneralPurposeBitFlags() { - return $this->general & 0xffff; + return $this->generalPurposeBitFlags & 0xffff; } /** * Sets the General Purpose Bit Flags. * - * @var int general - * @return ZipEntry + * @param mixed $general + * * @throws ZipException + * + * @return ZipEntry + * + * @var int general */ public function setGeneralPurposeBitFlags($general) { - if (0x0000 > $general || $general > 0xffff) { + if ($general < 0x0000 || $general > 0xffff) { throw new ZipException('general out of range'); } - $this->general = $general; - if ($this->method === ZipFileInterface::METHOD_DEFLATED) { + $this->generalPurposeBitFlags = $general; + + if ($this->method === ZipFile::METHOD_DEFLATED) { $bit1 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG1); $bit2 = $this->getGeneralPurposeBitFlag(self::GPBF_COMPRESSION_FLAG2); + if ($bit1 && !$bit2) { - $this->compressionLevel = ZipFileInterface::LEVEL_BEST_COMPRESSION; + $this->compressionLevel = ZipFile::LEVEL_BEST_COMPRESSION; } elseif (!$bit1 && $bit2) { - $this->compressionLevel = ZipFileInterface::LEVEL_FAST; + $this->compressionLevel = ZipFile::LEVEL_FAST; } elseif ($bit1 && $bit2) { - $this->compressionLevel = ZipFileInterface::LEVEL_SUPER_FAST; + $this->compressionLevel = ZipFile::LEVEL_SUPER_FAST; } else { - $this->compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION; + $this->compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION; } } + return $this; } @@ -376,35 +472,38 @@ abstract class ZipAbstractEntry implements ZipEntry * Returns the indexed General Purpose Bit Flag. * * @param int $mask + * * @return bool */ public function getGeneralPurposeBitFlag($mask) { - return ($this->general & $mask) !== 0; + return ($this->generalPurposeBitFlags & $mask) !== 0; } /** * Sets the encryption property to false and removes any other * encryption artifacts. * - * @return ZipEntry * @throws ZipException + * + * @return ZipEntry */ public function disableEncryption() { $this->setEncrypted(false); $headerId = WinZipAesEntryExtraField::getHeaderId(); + if (isset($this->extraFieldsCollection[$headerId])) { - /** - * @var WinZipAesEntryExtraField $field - */ + /** @var WinZipAesEntryExtraField $field */ $field = $this->extraFieldsCollection[$headerId]; - if (self::METHOD_WINZIP_AES === $this->getMethod()) { + + if ($this->getMethod() === self::METHOD_WINZIP_AES) { $this->setMethod($field === null ? self::UNKNOWN : $field->getMethod()); } unset($this->extraFieldsCollection[$headerId]); } $this->password = null; + return $this; } @@ -412,12 +511,14 @@ abstract class ZipAbstractEntry implements ZipEntry * Sets the encryption flag for this ZIP entry. * * @param bool $encrypted + * * @return ZipEntry */ public function setEncrypted($encrypted) { - $encrypted = (bool)$encrypted; + $encrypted = (bool) $encrypted; $this->setGeneralPurposeBitFlag(self::GPBF_ENCRYPTED, $encrypted); + return $this; } @@ -428,59 +529,60 @@ abstract class ZipAbstractEntry implements ZipEntry */ public function getMethod() { - $isInit = $this->isInit(self::BIT_METHOD); - return $isInit ? - $this->method & 0xffff : - self::UNKNOWN; + return $this->method; } /** * Sets the compression method for this entry. * * @param int $method + * + * @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN + * * @return ZipEntry - * @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN. */ public function setMethod($method) { if ($method === self::UNKNOWN) { $this->method = $method; - $this->setInit(self::BIT_METHOD, false); + return $this; } - if (0x0000 > $method || $method > 0xffff) { + + if ($method < 0x0000 || $method > 0xffff) { throw new ZipException('method out of range: ' . $method); } switch ($method) { case self::METHOD_WINZIP_AES: - case ZipFileInterface::METHOD_STORED: - case ZipFileInterface::METHOD_DEFLATED: - case ZipFileInterface::METHOD_BZIP2: + case ZipFile::METHOD_STORED: + case ZipFile::METHOD_DEFLATED: + case ZipFile::METHOD_BZIP2: $this->method = $method; - $this->setInit(self::BIT_METHOD, true); break; default: - throw new ZipException($this->name . " (unsupported compression method $method)"); + throw new ZipException($this->name . " (unsupported compression method {$method})"); } + return $this; } /** - * Get Unix Timestamp + * Get Unix Timestamp. * * @return int */ public function getTime() { - if (!$this->isInit(self::BIT_DATE_TIME)) { + if ($this->getDosTime() === self::UNKNOWN) { return self::UNKNOWN; } + return DateTimeConverter::toUnixTimestamp($this->getDosTime()); } /** - * Get Dos Time + * Get Dos Time. * * @return int */ @@ -490,70 +592,96 @@ abstract class ZipAbstractEntry implements ZipEntry } /** - * Set Dos Time + * Set Dos Time. + * * @param int $dosTime + * * @throws ZipException + * + * @return ZipEntry */ public function setDosTime($dosTime) { - $dosTime = sprintf('%u', $dosTime); - if (0x00000000 > $dosTime || $dosTime > 0xffffffff) { + $dosTime = (int) $dosTime; + + if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) { throw new ZipException('DosTime out of range'); } $this->dosTime = $dosTime; - $this->setInit(self::BIT_DATE_TIME, true); + + return $this; } /** * Set time from unix timestamp. * * @param int $unixTimestamp - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry */ public function setTime($unixTimestamp) { - $known = self::UNKNOWN != $unixTimestamp; + $known = $unixTimestamp !== self::UNKNOWN; + if ($known) { $this->dosTime = DateTimeConverter::toDosTime($unixTimestamp); } else { $this->dosTime = 0; } - $this->setInit(self::BIT_DATE_TIME, $known); + return $this; } /** * Returns the external file attributes. * - * @return int The external file attributes. + * @return int the external file attributes */ public function getExternalAttributes() { - if (!$this->isInit(self::BIT_EXTERNAL_ATTR)) { - return $this->isDirectory() ? 0x10 : 0; - } return $this->externalAttributes; } /** * Sets the external file attributes. * - * @param int $externalAttributes the external file attributes. + * @param int $externalAttributes the external file attributes + * * @return ZipEntry */ public function setExternalAttributes($externalAttributes) { - $known = self::UNKNOWN != $externalAttributes; - if ($known) { - $this->externalAttributes = $externalAttributes; - } else { - $this->externalAttributes = 0; - } - $this->setInit(self::BIT_EXTERNAL_ATTR, $known); + $this->externalAttributes = $externalAttributes; + return $this; } + /** + * Sets the internal file attributes. + * + * @param int $attributes the internal file attributes + * + * @return ZipEntry + */ + public function setInternalAttributes($attributes) + { + $this->internalAttributes = (int) $attributes; + + return $this; + } + + /** + * Returns the internal file attributes. + * + * @return int the internal file attributes + */ + public function getInternalAttributes() + { + return $this->internalAttributes; + } + /** * Returns true if and only if this ZIP entry represents a directory entry * (i.e. end with '/'). @@ -575,8 +703,10 @@ abstract class ZipAbstractEntry implements ZipEntry /** * Returns a protective copy of the serialized Extra Fields. - * @return string + * * @throws ZipException + * + * @return string */ public function getExtra() { @@ -591,41 +721,50 @@ abstract class ZipAbstractEntry implements ZipEntry * (application) data. * Consider storing such data in a separate entry instead. * - * @param string $data The byte array holding the serialized Extra Fields. + * @param string $data the byte array holding the serialized Extra Fields + * * @throws ZipException if the serialized Extra Fields exceed 64 KB + * + * @return ZipEntry */ public function setExtra($data) { $this->extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($data, $this); + + return $this; } /** - * Returns comment entry + * Returns comment entry. * * @return string */ public function getComment() { - return null !== $this->comment ? $this->comment : ""; + return $this->comment !== null ? $this->comment : ''; } /** * Set entry comment. * - * @param $comment - * @return ZipEntry + * @param string|null $comment + * * @throws ZipException + * + * @return ZipEntry */ public function setComment($comment) { if ($comment !== null) { - $commentLength = strlen($comment); - if (0x0000 > $commentLength || $commentLength > 0xffff) { - throw new ZipException("Comment too long"); + $commentLength = \strlen($comment); + + if ($commentLength < 0x0000 || $commentLength > 0xffff) { + throw new ZipException('Comment too long'); } + $this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true); } - $this->setGeneralPurposeBitFlag(self::GPBF_UTF8, true); $this->comment = $comment; + return $this; } @@ -634,11 +773,11 @@ abstract class ZipAbstractEntry implements ZipEntry */ public function isDataDescriptorRequired() { - return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) == self::UNKNOWN; + return ($this->getCrc() | $this->getCompressedSize() | $this->getSize()) === self::UNKNOWN; } /** - * Return crc32 content or 0 for WinZip AES v2 + * Return crc32 content or 0 for WinZip AES v2. * * @return int */ @@ -651,12 +790,13 @@ abstract class ZipAbstractEntry implements ZipEntry * Set crc32 content. * * @param int $crc + * * @return ZipEntry */ public function setCrc($crc) { - $this->crc = $crc; - $this->setInit(self::BIT_CRC, true); + $this->crc = (int) $crc; + return $this; } @@ -669,24 +809,29 @@ abstract class ZipAbstractEntry implements ZipEntry } /** - * Set password and encryption method from entry + * Set password and encryption method from entry. + * + * @param string $password + * @param int|null $encryptionMethod * - * @param string $password - * @param null|int $encryptionMethod - * @return ZipEntry * @throws ZipException + * + * @return ZipEntry */ public function setPassword($password, $encryptionMethod = null) { $this->password = $password; + if ($encryptionMethod !== null) { $this->setEncryptionMethod($encryptionMethod); } + if (!empty($this->password)) { $this->setEncrypted(true); } else { $this->disableEncryption(); } + return $this; } @@ -699,30 +844,33 @@ abstract class ZipAbstractEntry implements ZipEntry } /** - * Set encryption method - * - * @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 + * Set encryption method. * * @param int $encryptionMethod - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry + * + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256 + * @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128 + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192 */ public function setEncryptionMethod($encryptionMethod) { - if (null !== $encryptionMethod) { + if ($encryptionMethod !== null) { if ( - $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL - && $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 - && $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 - && $encryptionMethod !== ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 + $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_TRADITIONAL + && $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128 + && $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192 + && $encryptionMethod !== ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256 ) { throw new ZipException('Invalid encryption method'); } $this->encryptionMethod = $encryptionMethod; } + return $this; } @@ -736,22 +884,26 @@ abstract class ZipAbstractEntry implements ZipEntry /** * @param int $compressionLevel + * * @return ZipEntry */ - public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION) + public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION) { - if ($compressionLevel < ZipFileInterface::LEVEL_DEFAULT_COMPRESSION || - $compressionLevel > ZipFileInterface::LEVEL_BEST_COMPRESSION + if ($compressionLevel < ZipFile::LEVEL_DEFAULT_COMPRESSION || + $compressionLevel > ZipFile::LEVEL_BEST_COMPRESSION ) { - throw new InvalidArgumentException('Invalid compression level. Minimum level ' . - ZipFileInterface::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFileInterface::LEVEL_BEST_COMPRESSION); + throw new InvalidArgumentException( + 'Invalid compression level. Minimum level ' . + ZipFile::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . ZipFile::LEVEL_BEST_COMPRESSION + ); } $this->compressionLevel = $compressionLevel; + return $this; } /** - * Clone extra fields + * Clone extra fields. */ public function __clone() { diff --git a/src/PhpZip/Model/Entry/ZipChangesEntry.php b/src/PhpZip/Model/Entry/ZipChangesEntry.php index 17e8144..c17415f 100644 --- a/src/PhpZip/Model/Entry/ZipChangesEntry.php +++ b/src/PhpZip/Model/Entry/ZipChangesEntry.php @@ -2,26 +2,29 @@ namespace PhpZip\Model\Entry; +use PhpZip\Exception\InvalidArgumentException; use PhpZip\Exception\ZipException; /** - * Source Entry Changes + * Source Entry Changes. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT + * + * @internal */ class ZipChangesEntry extends ZipAbstractEntry { - /** - * @var ZipSourceEntry - */ + /** @var ZipSourceEntry */ protected $entry; /** * ZipChangesEntry constructor. + * * @param ZipSourceEntry $entry + * * @throws ZipException - * @throws \PhpZip\Exception\InvalidArgumentException + * @throws InvalidArgumentException */ public function __construct(ZipSourceEntry $entry) { @@ -47,8 +50,9 @@ class ZipChangesEntry extends ZipAbstractEntry /** * Returns an string content of the given entry. * - * @return null|string * @throws ZipException + * + * @return string|null */ public function getEntryContent() { diff --git a/src/PhpZip/Model/Entry/ZipNewEntry.php b/src/PhpZip/Model/Entry/ZipNewEntry.php index 57074f9..5930a74 100644 --- a/src/PhpZip/Model/Entry/ZipNewEntry.php +++ b/src/PhpZip/Model/Entry/ZipNewEntry.php @@ -3,7 +3,6 @@ namespace PhpZip\Model\Entry; use PhpZip\Exception\InvalidArgumentException; -use PhpZip\ZipFileInterface; /** * @author Ne-Lexa alexey@nelexa.ru @@ -11,23 +10,22 @@ use PhpZip\ZipFileInterface; */ class ZipNewEntry extends ZipAbstractEntry { - /** - * @var resource|string|null - */ + /** @var resource|string|null */ protected $content; - /** - * @var bool - */ + + /** @var bool */ private $clone = false; /** * ZipNewEntry constructor. + * * @param string|resource|null $content */ public function __construct($content = null) { parent::__construct(); - if ($content !== null && !is_string($content) && !is_resource($content)) { + + if ($content !== null && !\is_string($content) && !\is_resource($content)) { throw new InvalidArgumentException('invalid content'); } $this->content = $content; @@ -36,39 +34,23 @@ class ZipNewEntry extends ZipAbstractEntry /** * Returns an string content of the given entry. * - * @return null|string + * @return string|null */ public function getEntryContent() { - if (is_resource($this->content)) { + if (\is_resource($this->content)) { if (stream_get_meta_data($this->content)['seekable']) { rewind($this->content); } + return stream_get_contents($this->content); } + return $this->content; } /** - * Version needed to extract. - * - * @return int - */ - public function getVersionNeededToExtract() - { - $method = $this->getMethod(); - return self::METHOD_WINZIP_AES === $method ? 51 : - ( - ZipFileInterface::METHOD_BZIP2 === $method ? 46 : - ( - $this->isZip64ExtensionsRequired() ? 45 : - (ZipFileInterface::METHOD_DEFLATED === $method || $this->isDirectory() ? 20 : 10) - ) - ); - } - - /** - * Clone extra fields + * Clone extra fields. */ public function __clone() { @@ -78,7 +60,7 @@ class ZipNewEntry extends ZipAbstractEntry public function __destruct() { - if (!$this->clone && $this->content !== null && is_resource($this->content)) { + if (!$this->clone && $this->content !== null && \is_resource($this->content)) { fclose($this->content); $this->content = null; } diff --git a/src/PhpZip/Model/Entry/ZipNewFileEntry.php b/src/PhpZip/Model/Entry/ZipNewFileEntry.php index 3c1335c..7a2bead 100644 --- a/src/PhpZip/Model/Entry/ZipNewFileEntry.php +++ b/src/PhpZip/Model/Entry/ZipNewFileEntry.php @@ -12,28 +12,31 @@ use PhpZip\Exception\ZipException; */ class ZipNewFileEntry extends ZipAbstractEntry { - /** - * @var string Filename - */ + /** @var string Filename */ protected $file; /** * ZipNewEntry constructor. + * * @param string $file + * * @throws ZipException */ public function __construct($file) { parent::__construct(); + if ($file === null) { - throw new InvalidArgumentException("file is null"); + throw new InvalidArgumentException('file is null'); } - $file = (string)$file; + $file = (string) $file; + if (!is_file($file)) { - throw new ZipException("File $file does not exist."); + throw new ZipException("File {$file} does not exist."); } + if (!is_readable($file)) { - throw new ZipException("The '$file' file could not be read. Check permissions."); + throw new ZipException("The '{$file}' file could not be read. Check permissions."); } $this->file = $file; } @@ -41,13 +44,14 @@ class ZipNewFileEntry extends ZipAbstractEntry /** * Returns an string content of the given entry. * - * @return null|string + * @return string|null */ public function getEntryContent() { if (!is_file($this->file)) { throw new RuntimeException("File {$this->file} does not exist."); } + return file_get_contents($this->file); } } diff --git a/src/PhpZip/Model/Entry/ZipSourceEntry.php b/src/PhpZip/Model/Entry/ZipSourceEntry.php index dc80c27..5a992ea 100644 --- a/src/PhpZip/Model/Entry/ZipSourceEntry.php +++ b/src/PhpZip/Model/Entry/ZipSourceEntry.php @@ -9,34 +9,27 @@ use PhpZip\Stream\ZipInputStreamInterface; * This class is used to represent a ZIP file entry. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class ZipSourceEntry extends ZipAbstractEntry { - /** - * Max size cached content in memory. - */ + /** Max size cached content in memory. */ const MAX_SIZE_CACHED_CONTENT_IN_MEMORY = 524288; // 512 kb - /** - * @var ZipInputStreamInterface - */ + + /** @var ZipInputStreamInterface */ protected $inputStream; - /** - * @var string|resource Cached entry content. - */ + + /** @var string|resource cached entry content */ protected $entryContent; - /** - * @var string - */ - protected $readPassword; - /** - * @var bool - */ + + /** @var bool */ private $clone = false; /** * ZipSourceEntry constructor. + * * @param ZipInputStreamInterface $inputStream */ public function __construct(ZipInputStreamInterface $inputStream) @@ -56,6 +49,8 @@ class ZipSourceEntry extends ZipAbstractEntry /** * Returns an string content of the given entry. * + * @throws ZipException + * * @return string */ public function getEntryContent() @@ -63,23 +58,28 @@ class ZipSourceEntry extends ZipAbstractEntry if ($this->entryContent === null) { // In order not to unpack again, we cache the content in memory or on disk $content = $this->inputStream->readEntryContent($this); + if ($this->getSize() < self::MAX_SIZE_CACHED_CONTENT_IN_MEMORY) { $this->entryContent = $content; } else { $this->entryContent = fopen('php://temp', 'r+b'); fwrite($this->entryContent, $content); } + return $content; } - if (is_resource($this->entryContent)) { + + if (\is_resource($this->entryContent)) { rewind($this->entryContent); + return stream_get_contents($this->entryContent); } + return $this->entryContent; } /** - * Clone extra fields + * Clone extra fields. */ public function __clone() { @@ -89,7 +89,7 @@ class ZipSourceEntry extends ZipAbstractEntry public function __destruct() { - if (!$this->clone && $this->entryContent !== null && is_resource($this->entryContent)) { + if (!$this->clone && $this->entryContent !== null && \is_resource($this->entryContent)) { fclose($this->entryContent); } } diff --git a/src/PhpZip/Model/ZipEntry.php b/src/PhpZip/Model/ZipEntry.php index 015b4ac..1360a71 100644 --- a/src/PhpZip/Model/ZipEntry.php +++ b/src/PhpZip/Model/ZipEntry.php @@ -4,32 +4,27 @@ namespace PhpZip\Model; use PhpZip\Exception\ZipException; use PhpZip\Extra\ExtraFieldsCollection; -use PhpZip\ZipFileInterface; +use PhpZip\ZipFile; /** * ZIP file entry. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ interface ZipEntry { - // Bit masks for initialized fields. - const BIT_PLATFORM = 1, - BIT_METHOD = 2 /* 1 << 1 */, - BIT_CRC = 4 /* 1 << 2 */, - BIT_DATE_TIME = 64 /* 1 << 6 */, - BIT_EXTERNAL_ATTR = 128 /* 1 << 7*/ - ; - /** The unknown value for numeric properties. */ const UNKNOWN = -1; /** Windows platform. */ const PLATFORM_FAT = 0; + /** Unix platform. */ const PLATFORM_UNIX = 3; + /** MacOS platform */ const PLATFORM_OS_X = 19; @@ -40,26 +35,33 @@ interface ZipEntry const METHOD_WINZIP_AES = 99; /** General Purpose Bit Flag mask for encrypted data. */ - const GPBF_ENCRYPTED = 1; // 1 << 0 -// (For Methods 8 and 9 - Deflating) -// Bit 2 Bit 1 -// 0 0 Normal compression -// 0 1 Maximum compression -// 1 0 Fast compression -// 1 1 Super Fast compression + const GPBF_ENCRYPTED = 1; + + // (For Methods 8 and 9 - Deflating) + // Bit 2 Bit 1 + // 0 0 Normal compression + // 0 1 Maximum compression + // 1 0 Fast compression + // 1 1 Super Fast compression const GPBF_COMPRESSION_FLAG1 = 2; // 1 << 1 + const GPBF_COMPRESSION_FLAG2 = 4; // 1 << 2 + /** General Purpose Bit Flag mask for data descriptor. */ const GPBF_DATA_DESCRIPTOR = 8; // 1 << 3 + /** General Purpose Bit Flag mask for strong encryption. */ const GPBF_STRONG_ENCRYPTION = 64; // 1 << 6 + /** General Purpose Bit Flag mask for UTF-8. */ const GPBF_UTF8 = 2048; // 1 << 11 /** Local File Header signature. */ const LOCAL_FILE_HEADER_SIG = 0x04034B50; + /** Data Descriptor signature. */ const DATA_DESCRIPTOR_SIG = 0x08074B50; + /** * The minimum length of the Local File Header record. * @@ -76,6 +78,7 @@ interface ZipEntry * extra field length 2 */ const LOCAL_FILE_HEADER_MIN_LEN = 30; + /** * Local File Header signature 4 * Version Needed To Extract 2 @@ -85,12 +88,11 @@ interface ZipEntry * Last Mod File Date 2 * CRC-32 4 * Compressed Size 4 - * Uncompressed Size 4 + * Uncompressed Size 4. */ const LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS = 26; - /** - * Default compression level for bzip2 - */ + + /** Default compression level for bzip2 */ const LEVEL_DEFAULT_BZIP2_COMPRESSION = 4; /** @@ -104,25 +106,77 @@ interface ZipEntry * Set entry name. * * @param string $name New entry name - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry */ public function setName($name); /** * @return int Get platform + * + * @deprecated Use {@see ZipEntry::getCreatedOS()} */ public function getPlatform(); /** - * Set platform - * * @param int $platform - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry + * + * @deprecated Use {@see ZipEntry::setCreatedOS()} */ public function setPlatform($platform); + /** + * Returns created OS. + * + * @return int Get platform + */ + public function getCreatedOS(); + + /** + * Set created OS. + * + * @param int $platform + * + * @throws ZipException + * + * @return ZipEntry + */ + public function setCreatedOS($platform); + + /** + * @return int + */ + public function getExtractedOS(); + + /** + * Set extracted OS. + * + * @param int $platform + * + * @throws ZipException + * + * @return ZipEntry + */ + public function setExtractedOS($platform); + + /** + * @return int + */ + public function getSoftwareVersion(); + + /** + * @param int $softwareVersion + * + * @return ZipEntry + */ + public function setSoftwareVersion($softwareVersion); + /** * Version needed to extract. * @@ -134,6 +188,7 @@ interface ZipEntry * Set version needed to extract. * * @param int $version + * * @return ZipEntry */ public function setVersionNeededToExtract($version); @@ -153,9 +208,11 @@ interface ZipEntry /** * Sets the compressed size of this entry. * - * @param int $compressedSize The Compressed Size. - * @return ZipEntry + * @param int $compressedSize the Compressed Size + * * @throws ZipException + * + * @return ZipEntry */ public function setCompressedSize($compressedSize); @@ -169,9 +226,11 @@ interface ZipEntry /** * Sets the uncompressed size of this entry. * - * @param int $size The (Uncompressed) Size. - * @return ZipEntry + * @param int $size the (Uncompressed) Size + * * @throws ZipException + * + * @return ZipEntry */ public function setSize($size); @@ -184,8 +243,10 @@ interface ZipEntry /** * @param int $offset - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry */ public function setOffset($offset); @@ -207,9 +268,13 @@ interface ZipEntry /** * Sets the General Purpose Bit Flags. * - * @var int general - * @return ZipEntry + * @param mixed $general + * * @throws ZipException + * + * @return ZipEntry + * + * @var int general */ public function setGeneralPurposeBitFlags($general); @@ -217,6 +282,7 @@ interface ZipEntry * Returns the indexed General Purpose Bit Flag. * * @param int $mask + * * @return bool */ public function getGeneralPurposeBitFlag($mask); @@ -224,8 +290,9 @@ interface ZipEntry /** * Sets the indexed General Purpose Bit Flag. * - * @param int $mask + * @param int $mask * @param bool $bit + * * @return ZipEntry */ public function setGeneralPurposeBitFlag($mask, $bit); @@ -241,6 +308,7 @@ interface ZipEntry * Sets the encryption flag for this ZIP entry. * * @param bool $encrypted + * * @return ZipEntry */ public function setEncrypted($encrypted); @@ -264,13 +332,15 @@ interface ZipEntry * Sets the compression method for this entry. * * @param int $method + * + * @throws ZipException if method is not STORED, DEFLATED, BZIP2 or UNKNOWN + * * @return ZipEntry - * @throws ZipException If method is not STORED, DEFLATED, BZIP2 or UNKNOWN. */ public function setMethod($method); /** - * Get Unix Timestamp + * Get Unix Timestamp. * * @return int */ @@ -280,37 +350,62 @@ interface ZipEntry * Set time from unix timestamp. * * @param int $unixTimestamp + * * @return ZipEntry */ public function setTime($unixTimestamp); /** - * Get Dos Time + * Get Dos Time. * * @return int */ public function getDosTime(); /** - * Set Dos Time + * Set Dos Time. + * * @param int $dosTime + * * @throws ZipException + * + * @return ZipEntry */ public function setDosTime($dosTime); /** * Returns the external file attributes. * - * @return int The external file attributes. + * @return int the external file attributes */ public function getExternalAttributes(); + /** + * Sets the internal file attributes. + * + * @param int $attributes the internal file attributes + * + * @throws ZipException + * + * @return ZipEntry + */ + public function setInternalAttributes($attributes); + + /** + * Returns the internal file attributes. + * + * @return int the internal file attributes + */ + public function getInternalAttributes(); + /** * Sets the external file attributes. * - * @param int $externalAttributes the external file attributes. - * @return ZipEntry + * @param int $externalAttributes the external file attributes + * * @throws ZipException + * + * @return ZipEntry */ public function setExternalAttributes($externalAttributes); @@ -335,13 +430,16 @@ interface ZipEntry * (application) data. * Consider storing such data in a separate entry instead. * - * @param string $data The byte array holding the serialized Extra Fields. + * @param string $data the byte array holding the serialized Extra Fields + * * @throws ZipException if the serialized Extra Fields exceed 64 KB + * + * @return ZipEntry */ public function setExtra($data); /** - * Returns comment entry + * Returns comment entry. * * @return string */ @@ -351,6 +449,7 @@ interface ZipEntry * Set entry comment. * * @param $comment + * * @return ZipEntry */ public function setComment($comment); @@ -361,7 +460,7 @@ interface ZipEntry public function isDataDescriptorRequired(); /** - * Return crc32 content or 0 for WinZip AES v2 + * Return crc32 content or 0 for WinZip AES v2. * * @return int */ @@ -371,8 +470,10 @@ interface ZipEntry * Set crc32 content. * * @param int $crc - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry */ public function setCrc($crc); @@ -382,10 +483,11 @@ interface ZipEntry public function getPassword(); /** - * Set password and encryption method from entry + * Set password and encryption method from entry. + * + * @param string $password + * @param int|null $encryptionMethod * - * @param string $password - * @param null|int $encryptionMethod * @return ZipEntry */ public function setPassword($password, $encryptionMethod = null); @@ -396,32 +498,36 @@ interface ZipEntry public function getEncryptionMethod(); /** - * Set encryption method - * - * @see ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 - * @see ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 + * Set encryption method. * * @param int $encryptionMethod - * @return ZipEntry + * * @throws ZipException + * + * @return ZipEntry + * + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256 + * @see ZipFile::ENCRYPTION_METHOD_TRADITIONAL + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128 + * @see ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192 */ public function setEncryptionMethod($encryptionMethod); /** * Returns an string content of the given entry. * - * @return null|string * @throws ZipException + * + * @return string|null */ public function getEntryContent(); /** * @param int $compressionLevel + * * @return ZipEntry */ - public function setCompressionLevel($compressionLevel = ZipFileInterface::LEVEL_DEFAULT_COMPRESSION); + public function setCompressionLevel($compressionLevel = ZipFile::LEVEL_DEFAULT_COMPRESSION); /** * @return int diff --git a/src/PhpZip/Model/ZipEntryMatcher.php b/src/PhpZip/Model/ZipEntryMatcher.php index 0c99138..4ff484f 100644 --- a/src/PhpZip/Model/ZipEntryMatcher.php +++ b/src/PhpZip/Model/ZipEntryMatcher.php @@ -8,18 +8,15 @@ namespace PhpZip\Model; */ class ZipEntryMatcher implements \Countable { - /** - * @var ZipModel - */ + /** @var ZipModel */ protected $zipModel; - /** - * @var array - */ + /** @var array */ protected $matches = []; /** * ZipEntryMatcher constructor. + * * @param ZipModel $zipModel */ public function __construct(ZipModel $zipModel) @@ -29,44 +26,59 @@ class ZipEntryMatcher implements \Countable /** * @param string|array $entries + * * @return ZipEntryMatcher */ public function add($entries) { - $entries = (array)$entries; - $entries = array_map(function ($entry) { - return $entry instanceof ZipEntry ? $entry->getName() : $entry; - }, $entries); - $this->matches = array_unique( - array_merge( - $this->matches, - array_keys( - array_intersect_key( - $this->zipModel->getEntries(), - array_flip($entries) + $entries = (array) $entries; + $entries = array_map( + static function ($entry) { + return $entry instanceof ZipEntry ? $entry->getName() : (string) $entry; + }, + $entries + ); + $this->matches = array_values( + array_map( + 'strval', + array_unique( + array_merge( + $this->matches, + array_keys( + array_intersect_key( + $this->zipModel->getEntries(), + array_flip($entries) + ) + ) ) ) ) ); + return $this; } /** * @param string $regexp + * * @return ZipEntryMatcher */ public function match($regexp) { - array_walk($this->zipModel->getEntries(), function ( - /** @noinspection PhpUnusedParameterInspection */ - $entry, - $entryName - ) use ($regexp) { - if (preg_match($regexp, $entryName)) { - $this->matches[] = $entryName; + array_walk( + $this->zipModel->getEntries(), + function ( + /** @noinspection PhpUnusedParameterInspection */ + $entry, + $entryName + ) use ($regexp) { + if (preg_match($regexp, $entryName)) { + $this->matches[] = (string) $entryName; + } } - }); + ); $this->matches = array_unique($this->matches); + return $this; } @@ -76,6 +88,7 @@ class ZipEntryMatcher implements \Countable public function all() { $this->matches = array_keys($this->zipModel->getEntries()); + return $this; } @@ -90,9 +103,12 @@ class ZipEntryMatcher implements \Countable public function invoke(callable $callable) { if (!empty($this->matches)) { - array_walk($this->matches, function ($entryName) use ($callable) { - call_user_func($callable, $entryName); - }); + array_walk( + $this->matches, + static function ($entryName) use ($callable) { + $callable($entryName); + } + ); } } @@ -106,24 +122,31 @@ class ZipEntryMatcher implements \Countable public function delete() { - array_walk($this->matches, function ($entry) { - $this->zipModel->deleteEntry($entry); - }); + array_walk( + $this->matches, + function ($entry) { + $this->zipModel->deleteEntry($entry); + } + ); $this->matches = []; } /** * @param string|null $password - * @param int|null $encryptionMethod + * @param int|null $encryptionMethod */ public function setPassword($password, $encryptionMethod = null) { - array_walk($this->matches, function ($entry) use ($password, $encryptionMethod) { - $entry = $this->zipModel->getEntry($entry); - if (!$entry->isDirectory()) { - $this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod); + array_walk( + $this->matches, + function ($entry) use ($password, $encryptionMethod) { + $entry = $this->zipModel->getEntry($entry); + + if (!$entry->isDirectory()) { + $this->zipModel->getEntryForChanges($entry)->setPassword($password, $encryptionMethod); + } } - }); + ); } /** @@ -131,36 +154,47 @@ class ZipEntryMatcher implements \Countable */ public function setEncryptionMethod($encryptionMethod) { - array_walk($this->matches, function ($entry) use ($encryptionMethod) { - $entry = $this->zipModel->getEntry($entry); - if (!$entry->isDirectory()) { - $this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod); + array_walk( + $this->matches, + function ($entry) use ($encryptionMethod) { + $entry = $this->zipModel->getEntry($entry); + + if (!$entry->isDirectory()) { + $this->zipModel->getEntryForChanges($entry)->setEncryptionMethod($encryptionMethod); + } } - }); + ); } public function disableEncryption() { - array_walk($this->matches, function ($entry) { - $entry = $this->zipModel->getEntry($entry); - if (!$entry->isDirectory()) { - $entry = $this->zipModel->getEntryForChanges($entry); - $entry->disableEncryption(); + array_walk( + $this->matches, + function ($entry) { + $entry = $this->zipModel->getEntry($entry); + + if (!$entry->isDirectory()) { + $entry = $this->zipModel->getEntryForChanges($entry); + $entry->disableEncryption(); + } } - }); + ); } /** - * Count elements of an object - * @link http://php.net/manual/en/countable.count.php + * Count elements of an object. + * + * @see http://php.net/manual/en/countable.count.php + * * @return int The custom count as an integer. - * - *- * The return value is cast to an integer. + *
+ *+ * The return value is cast to an integer. + * * @since 5.1.0 */ public function count() { - return count($this->matches); + return \count($this->matches); } } diff --git a/src/PhpZip/Model/ZipInfo.php b/src/PhpZip/Model/ZipInfo.php index 4fbb608..235efef 100644 --- a/src/PhpZip/Model/ZipInfo.php +++ b/src/PhpZip/Model/ZipInfo.php @@ -1,14 +1,15 @@ - 'FAT', self::MADE_BY_AMIGA => 'Amiga', self::MADE_BY_OPEN_VMS => 'OpenVMS', @@ -86,9 +130,9 @@ class ZipInfo self::MADE_BY_OS_X => 'Mac OS X', ]; - private static $valuesCompressionMethod = [ + private static $compressionMethodNames = [ ZipEntry::UNKNOWN => 'unknown', - ZipFileInterface::METHOD_STORED => 'no compression', + ZipFile::METHOD_STORED => 'no compression', 1 => 'shrink', 2 => 'reduce level 1', 3 => 'reduce level 2', @@ -96,7 +140,7 @@ class ZipInfo 5 => 'reduce level 4', 6 => 'implode', 7 => 'reserved for Tokenizing compression algorithm', - ZipFileInterface::METHOD_DEFLATED => 'deflate', + ZipFile::METHOD_DEFLATED => 'deflate', 9 => 'deflate64', 10 => 'PKWARE Data Compression Library Imploding (old IBM TERSE)', 11 => 'reserved by PKWARE', @@ -113,80 +157,64 @@ class ZipInfo ZipEntry::METHOD_WINZIP_AES => 'WinZip AES', ]; - /** - * @var string - */ + /** @var string */ private $name; - /** - * @var bool - */ + + /** @var bool */ private $folder; - /** - * @var int - */ + + /** @var int */ private $size; - /** - * @var int - */ + + /** @var int */ private $compressedSize; - /** - * @var int - */ + + /** @var int */ private $mtime; - /** - * @var int|null - */ + + /** @var int|null */ private $ctime; - /** - * @var int|null - */ + + /** @var int|null */ private $atime; - /** - * @var bool - */ + + /** @var bool */ private $encrypted; - /** - * @var string|null - */ + + /** @var string|null */ private $comment; - /** - * @var int - */ + + /** @var int */ private $crc; - /** - * @var string - */ + + /** @var string */ private $methodName; - /** - * @var int - */ + + /** @var int */ private $compressionMethod; - /** - * @var string - */ + + /** @var string */ private $platform; - /** - * @var int - */ + + /** @var int */ private $version; - /** - * @var string - */ + + /** @var string */ private $attributes; - /** - * @var int|null - */ + + /** @var int|null */ private $encryptionMethod; - /** - * @var int|null - */ + + /** @var int|null */ private $compressionLevel; /** * ZipInfo constructor. * * @param ZipEntry $entry - * @throws \PhpZip\Exception\ZipException + * + * @throws ZipException + * @noinspection PhpMissingBreakStatementInspection */ public function __construct(ZipEntry $entry) { @@ -195,7 +223,8 @@ class ZipInfo $ctime = null; $field = $entry->getExtraFieldsCollection()->get(NtfsExtraField::getHeaderId()); - if (null !== $field && $field instanceof NtfsExtraField) { + + if ($field instanceof NtfsExtraField) { /** * @var NtfsExtraField $field */ @@ -206,12 +235,8 @@ class ZipInfo $this->name = $entry->getName(); $this->folder = $entry->isDirectory(); - $this->size = PHP_INT_SIZE === 4 ? - sprintf('%u', $entry->getSize()) : - $entry->getSize(); - $this->compressedSize = PHP_INT_SIZE === 4 ? - sprintf('%u', $entry->getCompressedSize()) : - $entry->getCompressedSize(); + $this->size = $entry->getSize(); + $this->compressedSize = $entry->getCompressedSize(); $this->mtime = $mtime; $this->ctime = $ctime; $this->atime = $atime; @@ -225,65 +250,71 @@ class ZipInfo $this->version = $entry->getVersionNeededToExtract(); $this->compressionLevel = $entry->getCompressionLevel(); - $attributes = str_repeat(" ", 12); + $attributes = str_repeat(' ', 12); $externalAttributes = $entry->getExternalAttributes(); - $externalAttributes = PHP_INT_SIZE === 4 ? - sprintf('%u', $externalAttributes) : - $externalAttributes; $xattr = (($externalAttributes >> 16) & 0xFFFF); - switch ($entry->getPlatform()) { + switch ($entry->getCreatedOS()) { case self::MADE_BY_MS_DOS: case self::MADE_BY_WINDOWS_NTFS: - if ($entry->getPlatform() != self::MADE_BY_MS_DOS || - ($xattr & 0700) != - (0400 | + if ($entry->getCreatedOS() !== self::MADE_BY_MS_DOS || + ($xattr & self::UNX_IRWXU) !== + (self::UNX_IRUSR | (!($externalAttributes & 1) << 7) | (($externalAttributes & 0x10) << 2)) ) { $xattr = $externalAttributes & 0xFF; - $attributes = ".r.-... "; + $attributes = '.r.-... '; $attributes[2] = ($xattr & 0x01) ? '-' : 'w'; $attributes[5] = ($xattr & 0x02) ? 'h' : '-'; $attributes[6] = ($xattr & 0x04) ? 's' : '-'; $attributes[4] = ($xattr & 0x20) ? 'a' : '-'; + if ($xattr & 0x10) { $attributes[0] = 'd'; $attributes[3] = 'x'; } else { $attributes[0] = '-'; } + if ($xattr & 0x08) { $attributes[0] = 'V'; } else { - $ext = strtolower(pathinfo($entry->getName(), PATHINFO_EXTENSION)); - if (in_array($ext, ["com", "exe", "btm", "cmd", "bat"])) { + $ext = strtolower(pathinfo($entry->getName(), \PATHINFO_EXTENSION)); + + if (\in_array($ext, ['com', 'exe', 'btm', 'cmd', 'bat'])) { $attributes[3] = 'x'; } } break; - } /* else: fall through! */ + } // else: fall through! // no break - default: /* assume Unix-like */ + default: // assume Unix-like switch ($xattr & self::UNX_IFMT) { case self::UNX_IFDIR: $attributes[0] = 'd'; break; + case self::UNX_IFREG: $attributes[0] = '-'; break; + case self::UNX_IFLNK: $attributes[0] = 'l'; break; + case self::UNX_IFBLK: $attributes[0] = 'b'; break; + case self::UNX_IFCHR: $attributes[0] = 'c'; break; + case self::UNX_IFIFO: $attributes[0] = 'p'; break; + case self::UNX_IFSOCK: $attributes[0] = 's'; break; @@ -302,91 +333,97 @@ class ZipInfo $attributes[3] = ($xattr & self::UNX_ISUID) ? 's' : 'x'; } else { $attributes[3] = ($xattr & self::UNX_ISUID) ? 'S' : '-'; - } /* S==undefined */ + } // S==undefined if ($xattr & self::UNX_IXGRP) { $attributes[6] = ($xattr & self::UNX_ISGID) ? 's' : 'x'; - } /* == UNX_ENFMT */ + } // == UNX_ENFMT else { $attributes[6] = ($xattr & self::UNX_ISGID) ? 'S' : '-'; - } /* SunOS 4.1.x */ + } // SunOS 4.1.x if ($xattr & self::UNX_IXOTH) { $attributes[9] = ($xattr & self::UNX_ISVTX) ? 't' : 'x'; - } /* "sticky bit" */ + } // "sticky bit" else { $attributes[9] = ($xattr & self::UNX_ISVTX) ? 'T' : '-'; - } /* T==undefined */ + } // T==undefined } $this->attributes = trim($attributes); } /** * @param ZipEntry $entry + * + * @throws ZipException + * * @return int - * @throws \PhpZip\Exception\ZipException */ private static function getMethodId(ZipEntry $entry) { $method = $entry->getMethod(); - if ($entry->isEncrypted()) { - if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) { - $field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId()); - if (null !== $field) { - /** - * @var WinZipAesEntryExtraField $field - */ - $method = $field->getMethod(); - } + + if ($entry->isEncrypted() && $entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) { + $field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId()); + + if ($field !== null) { + /** @var WinZipAesEntryExtraField $field */ + $method = $field->getMethod(); } } + return $method; } /** * @param ZipEntry $entry + * + * @throws ZipException + * * @return string - * @throws \PhpZip\Exception\ZipException */ private static function getEntryMethodName(ZipEntry $entry) { $return = ''; + + $compressionMethod = $entry->getMethod(); + if ($entry->isEncrypted()) { if ($entry->getMethod() === ZipEntry::METHOD_WINZIP_AES) { - $return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]); + $return .= ucfirst(self::$compressionMethodNames[$entry->getMethod()]); + /** @var WinZipAesEntryExtraField|null $field */ $field = $entry->getExtraFieldsCollection()->get(WinZipAesEntryExtraField::getHeaderId()); - if (null !== $field) { - /** - * @var WinZipAesEntryExtraField $field - */ + + if ($field !== null) { $return .= '-' . $field->getKeyStrength(); - if (isset(self::$valuesCompressionMethod[$field->getMethod()])) { - $return .= ' ' . ucfirst(self::$valuesCompressionMethod[$field->getMethod()]); - } + $compressionMethod = $field->getMethod(); } } else { $return .= 'ZipCrypto'; - if (isset(self::$valuesCompressionMethod[$entry->getMethod()])) { - $return .= ' ' . ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]); - } } - } elseif (isset(self::$valuesCompressionMethod[$entry->getMethod()])) { - $return = ucfirst(self::$valuesCompressionMethod[$entry->getMethod()]); - } else { - $return = 'unknown'; + + $return .= ' '; } + + if (isset(self::$compressionMethodNames[$compressionMethod])) { + $return .= ucfirst(self::$compressionMethodNames[$compressionMethod]); + } else { + $return .= 'unknown'; + } + return $return; } /** * @param ZipEntry $entry + * * @return string */ public static function getPlatformName(ZipEntry $entry) { - if (isset(self::$valuesMadeBy[$entry->getPlatform()])) { - return self::$valuesMadeBy[$entry->getPlatform()]; - } else { - return 'unknown'; + if (isset(self::$platformNames[$entry->getCreatedOS()])) { + return self::$platformNames[$entry->getCreatedOS()]; } + + return 'unknown'; } /** @@ -399,6 +436,7 @@ class ZipInfo /** * @return string + * * @deprecated use \PhpZip\Model\ZipInfo::getName() */ public function getPath() @@ -407,7 +445,7 @@ class ZipInfo } /** - * @return boolean + * @return bool */ public function isFolder() { @@ -463,7 +501,7 @@ class ZipInfo } /** - * @return boolean + * @return bool */ public function isEncrypted() { @@ -471,7 +509,7 @@ class ZipInfo } /** - * @return null|string + * @return string|null */ public function getComment() { @@ -488,6 +526,7 @@ class ZipInfo /** * @return string + * * @deprecated use \PhpZip\Model\ZipInfo::getMethodName() */ public function getMethod() @@ -566,7 +605,7 @@ class ZipInfo 'method_name' => $this->getMethodName(), 'compression_method' => $this->getCompressionMethod(), 'platform' => $this->getPlatform(), - 'version' => $this->getVersion() + 'version' => $this->getVersion(), ]; } @@ -580,9 +619,9 @@ class ZipInfo . ($this->isFolder() ? 'Folder, ' : '') . 'Size="' . FilesUtil::humanSize($this->getSize()) . '"' . ', Compressed size="' . FilesUtil::humanSize($this->getCompressedSize()) . '"' - . ', Modified time="' . date(DATE_W3C, $this->getMtime()) . '", ' - . ($this->getCtime() !== null ? 'Created time="' . date(DATE_W3C, $this->getCtime()) . '", ' : '') - . ($this->getAtime() !== null ? 'Accessed time="' . date(DATE_W3C, $this->getAtime()) . '", ' : '') + . ', Modified time="' . date(\DATE_W3C, $this->getMtime()) . '", ' + . ($this->getCtime() !== null ? 'Created time="' . date(\DATE_W3C, $this->getCtime()) . '", ' : '') + . ($this->getAtime() !== null ? 'Accessed time="' . date(\DATE_W3C, $this->getAtime()) . '", ' : '') . ($this->isEncrypted() ? 'Encrypted, ' : '') . (!empty($this->comment) ? 'Comment="' . $this->getComment() . '", ' : '') . (!empty($this->crc) ? 'Crc=0x' . dechex($this->getCrc()) . ', ' : '') diff --git a/src/PhpZip/Model/ZipModel.php b/src/PhpZip/Model/ZipModel.php index 49d2d0c..7e5b0af 100644 --- a/src/PhpZip/Model/ZipModel.php +++ b/src/PhpZip/Model/ZipModel.php @@ -7,68 +7,63 @@ use PhpZip\Exception\ZipEntryNotFoundException; use PhpZip\Exception\ZipException; use PhpZip\Model\Entry\ZipChangesEntry; use PhpZip\Model\Entry\ZipSourceEntry; -use PhpZip\ZipFileInterface; +use PhpZip\ZipFile; /** - * Zip Model + * Zip Model. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class ZipModel implements \Countable { - /** - * @var ZipSourceEntry[] - */ + /** @var ZipSourceEntry[] */ protected $inputEntries = []; - /** - * @var ZipEntry[] - */ + + /** @var ZipEntry[] */ protected $outEntries = []; - /** - * @var string|null - */ + + /** @var string|null */ protected $archiveComment; - /** - * @var string|null - */ + + /** @var string|null */ protected $archiveCommentChanges; - /** - * @var bool - */ + + /** @var bool */ protected $archiveCommentChanged = false; - /** - * @var int|null - */ + + /** @var int|null */ protected $zipAlign; - /** - * @var bool - */ + + /** @var bool */ private $zip64; /** - * @param ZipSourceEntry[] $entries + * @param ZipSourceEntry[] $entries * @param EndOfCentralDirectory $endOfCentralDirectory + * * @return ZipModel */ public static function newSourceModel(array $entries, EndOfCentralDirectory $endOfCentralDirectory) { - $model = new self; + $model = new self(); $model->inputEntries = $entries; $model->outEntries = $entries; $model->archiveComment = $endOfCentralDirectory->getComment(); $model->zip64 = $endOfCentralDirectory->isZip64(); + return $model; } /** - * @return null|string + * @return string|null */ public function getArchiveComment() { if ($this->archiveCommentChanged) { return $this->archiveCommentChanges; } + return $this->archiveComment; } @@ -77,13 +72,15 @@ class ZipModel implements \Countable */ public function setArchiveComment($comment) { - if ($comment !== null && strlen($comment) !== 0) { - $comment = (string)$comment; - $length = strlen($comment); - if (0x0000 > $length || $length > 0xffff) { + if ($comment !== null && $comment !== '') { + $comment = (string) $comment; + $length = \strlen($comment); + + if ($length > 0xffff) { throw new InvalidArgumentException('Length comment out of range'); } } + if ($comment !== $this->archiveComment) { $this->archiveCommentChanges = $comment; $this->archiveCommentChanged = true; @@ -95,7 +92,8 @@ class ZipModel implements \Countable /** * Specify a password for extracting files. * - * @param null|string $password + * @param string|null $password + * * @throws ZipException */ public function setReadPassword($password) @@ -110,6 +108,7 @@ class ZipModel implements \Countable /** * @param string $entryName * @param string $password + * * @throws ZipEntryNotFoundException * @throws ZipException */ @@ -118,6 +117,7 @@ class ZipModel implements \Countable if (!isset($this->inputEntries[$entryName])) { throw new ZipEntryNotFoundException($entryName); } + if ($this->inputEntries[$entryName]->isEncrypted()) { $this->inputEntries[$entryName]->setPassword($password); } @@ -136,7 +136,7 @@ class ZipModel implements \Countable */ public function setZipAlign($zipAlign) { - $this->zipAlign = $zipAlign === null ? null : (int)$zipAlign; + $this->zipAlign = $zipAlign === null ? null : (int) $zipAlign; } /** @@ -144,11 +144,11 @@ class ZipModel implements \Countable */ public function isZipAlign() { - return $this->zipAlign != null; + return $this->zipAlign !== null; } /** - * @param null|string $writePassword + * @param string|null $writePassword */ public function setWritePassword($writePassword) { @@ -156,7 +156,7 @@ class ZipModel implements \Countable } /** - * Remove password + * Remove password. */ public function removePassword() { @@ -182,15 +182,16 @@ class ZipModel implements \Countable /** * @param string|ZipEntry $old * @param string|ZipEntry $new + * * @throws ZipException */ public function renameEntry($old, $new) { - $old = $old instanceof ZipEntry ? $old->getName() : (string)$old; - $new = $new instanceof ZipEntry ? $new->getName() : (string)$new; + $old = $old instanceof ZipEntry ? $old->getName() : (string) $old; + $new = $new instanceof ZipEntry ? $new->getName() : (string) $new; if (isset($this->outEntries[$new])) { - throw new InvalidArgumentException("New entry name " . $new . ' is exists.'); + throw new InvalidArgumentException('New entry name ' . $new . ' is exists.'); } $entry = $this->getEntryForChanges($old); @@ -201,45 +202,57 @@ class ZipModel implements \Countable /** * @param string|ZipEntry $entry - * @return ZipChangesEntry|ZipEntry - * @throws ZipException + * * @throws ZipEntryNotFoundException + * @throws ZipException + * + * @return ZipChangesEntry|ZipEntry */ public function getEntryForChanges($entry) { $entry = $this->getEntry($entry); + if ($entry instanceof ZipSourceEntry) { $entry = new ZipChangesEntry($entry); $this->addEntry($entry); } + return $entry; } /** * @param string|ZipEntry $entryName - * @return ZipEntry + * * @throws ZipEntryNotFoundException + * + * @return ZipEntry */ public function getEntry($entryName) { - $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName; + $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName; + if (isset($this->outEntries[$entryName])) { return $this->outEntries[$entryName]; } + throw new ZipEntryNotFoundException($entryName); } /** * @param string|ZipEntry $entry + * * @return bool */ public function deleteEntry($entry) { - $entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry; + $entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry; + if (isset($this->outEntries[$entry])) { unset($this->outEntries[$entry]); + return true; } + return false; } @@ -263,11 +276,13 @@ class ZipModel implements \Countable /** * @param string|ZipEntry $entryName + * * @return bool */ public function hasEntry($entryName) { - $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string)$entryName; + $entryName = $entryName instanceof ZipEntry ? $entryName->getName() : (string) $entryName; + return isset($this->outEntries[$entryName]); } @@ -280,21 +295,24 @@ class ZipModel implements \Countable } /** - * Count elements of an object - * @link http://php.net/manual/en/countable.count.php + * Count elements of an object. + * + * @see http://php.net/manual/en/countable.count.php + * * @return int The custom count as an integer. - *
- *- * The return value is cast to an integer. + *
+ *+ * The return value is cast to an integer. + * * @since 5.1.0 */ public function count() { - return sizeof($this->outEntries); + return \count($this->outEntries); } /** - * Undo all changes done in the archive + * Undo all changes done in the archive. */ public function unchangeAll() { @@ -303,7 +321,7 @@ class ZipModel implements \Countable } /** - * Undo change archive comment + * Undo change archive comment. */ public function unchangeArchiveComment() { @@ -315,22 +333,26 @@ class ZipModel implements \Countable * Revert all changes done to an entry with the given name. * * @param string|ZipEntry $entry Entry name or ZipEntry + * * @return bool */ public function unchangeEntry($entry) { - $entry = $entry instanceof ZipEntry ? $entry->getName() : (string)$entry; - if (isset($this->outEntries[$entry]) && isset($this->inputEntries[$entry])) { + $entry = $entry instanceof ZipEntry ? $entry->getName() : (string) $entry; + + if (isset($this->outEntries[$entry], $this->inputEntries[$entry])) { $this->outEntries[$entry] = $this->inputEntries[$entry]; + return true; } + return false; } /** * @param int $encryptionMethod */ - public function setEncryptionMethod($encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256) + public function setEncryptionMethod($encryptionMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256) { $this->matcher()->all()->setEncryptionMethod($encryptionMethod); } diff --git a/src/PhpZip/Stream/ResponseStream.php b/src/PhpZip/Stream/ResponseStream.php index 172de1e..0f37f4b 100644 --- a/src/PhpZip/Stream/ResponseStream.php +++ b/src/PhpZip/Stream/ResponseStream.php @@ -5,59 +5,77 @@ namespace PhpZip\Stream; use Psr\Http\Message\StreamInterface; /** - * Implement PSR Message Stream + * Implement PSR Message Stream. */ class ResponseStream implements StreamInterface { - /** - * @var array - */ + /** @var array */ private static $readWriteHash = [ 'read' => [ - 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, - 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, - 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a+' => true, + 'r' => true, + 'w+' => true, + 'r+' => true, + 'x+' => true, + 'c+' => true, + 'rb' => true, + 'w+b' => true, + 'r+b' => true, + 'x+b' => true, + 'c+b' => true, + 'rt' => true, + 'w+t' => true, + 'r+t' => true, + 'x+t' => true, + 'c+t' => true, + 'a+' => true, ], 'write' => [ - 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, - 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, - 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, - 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true, + 'w' => true, + 'w+' => true, + 'rw' => true, + 'r+' => true, + 'x+' => true, + 'c+' => true, + 'wb' => true, + 'w+b' => true, + 'r+b' => true, + 'x+b' => true, + 'c+b' => true, + 'w+t' => true, + 'r+t' => true, + 'x+t' => true, + 'c+t' => true, + 'a' => true, + 'a+' => true, ], ]; - /** - * @var resource - */ + + /** @var resource */ private $stream; - /** - * @var int - */ + + /** @var int */ private $size; - /** - * @var bool - */ + + /** @var bool */ private $seekable; - /** - * @var bool - */ + + /** @var bool */ private $readable; - /** - * @var bool - */ + + /** @var bool */ private $writable; - /** - * @var array|mixed|null - */ + + /** @var array|mixed|null */ private $uri; /** - * @param resource $stream Stream resource to wrap. + * @param resource $stream stream resource to wrap + * * @throws \InvalidArgumentException if the stream is not a stream resource */ public function __construct($stream) { - if (!is_resource($stream)) { + if (!\is_resource($stream)) { throw new \InvalidArgumentException('Stream must be a resource'); } $this->stream = $stream; @@ -74,11 +92,13 @@ class ResponseStream implements StreamInterface * The keys returned are identical to the keys returned from PHP's * stream_get_meta_data() function. * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * @param string $key Specific metadata to retrieve. + * @see http://php.net/manual/en/function.stream-get-meta-data.php + * + * @param string $key specific metadata to retrieve + * * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found. + * provided. Returns a specific key value if a key is provided and the + * value is found, or null if the key is not found. */ public function getMetadata($key = null) { @@ -86,6 +106,7 @@ class ResponseStream implements StreamInterface return $key ? null : []; } $meta = stream_get_meta_data($this->stream); + return isset($meta[$key]) ? $meta[$key] : null; } @@ -101,6 +122,7 @@ class ResponseStream implements StreamInterface * string casting operations. * * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * * @return string */ public function __toString() @@ -109,7 +131,8 @@ class ResponseStream implements StreamInterface return ''; } $this->rewind(); - return (string)stream_get_contents($this->stream); + + return (string) stream_get_contents($this->stream); } /** @@ -118,9 +141,10 @@ class ResponseStream implements StreamInterface * If the stream is not seekable, this method will raise an exception; * otherwise, it will perform a seek(0). * + * @throws \RuntimeException on failure + * + * @see http://www.php.net/manual/en/function.fseek.php * @see seek() - * @link http://www.php.net/manual/en/function.fseek.php - * @throws \RuntimeException on failure. */ public function rewind() { @@ -130,13 +154,14 @@ class ResponseStream implements StreamInterface /** * Get the size of the stream if known. * - * @return int|null Returns the size in bytes if known, or null if unknown. + * @return int|null returns the size in bytes if known, or null if unknown */ public function getSize() { if ($this->size !== null) { return $this->size; } + if (!$this->stream) { return null; } @@ -145,18 +170,22 @@ class ResponseStream implements StreamInterface clearstatcache(true, $this->uri); } $stats = fstat($this->stream); + if (isset($stats['size'])) { $this->size = $stats['size']; + return $this->size; } + return null; } /** - * Returns the current position of the file read/write pointer + * Returns the current position of the file read/write pointer. + * + * @throws \RuntimeException on error * * @return int Position of the file pointer - * @throws \RuntimeException on error. */ public function tell() { @@ -186,16 +215,18 @@ class ResponseStream implements StreamInterface /** * Seek to a position in the stream. * - * @link http://www.php.net/manual/en/function.fseek.php + * @see http://www.php.net/manual/en/function.fseek.php + * * @param int $offset Stream offset * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset. - * @throws \RuntimeException on failure. + * based on the seek offset. Valid values are identical to the built-in + * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to + * offset bytes SEEK_CUR: Set position to current location plus offset + * SEEK_END: Set position to end-of-stream plus offset. + * + * @throws \RuntimeException on failure */ - public function seek($offset, $whence = SEEK_SET) + public function seek($offset, $whence = \SEEK_SET) { $this->seekable && fseek($this->stream, $offset, $whence); } @@ -213,13 +244,16 @@ class ResponseStream implements StreamInterface /** * Write data to the stream. * - * @param string $string The string that is to be written. - * @return int Returns the number of bytes written to the stream. - * @throws \RuntimeException on failure. + * @param string $string the string that is to be written + * + * @throws \RuntimeException on failure + * + * @return int returns the number of bytes written to the stream */ public function write($string) { $this->size = null; + return $this->writable ? fwrite($this->stream, $string) : false; } @@ -237,23 +271,26 @@ class ResponseStream implements StreamInterface * Read data from the stream. * * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes. - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available. - * @throws \RuntimeException if an error occurs. + * them. Fewer than $length bytes may be returned if underlying stream + * call returns fewer bytes. + * + * @throws \RuntimeException if an error occurs + * + * @return string returns the data read from the stream, or an empty string + * if no bytes are available */ public function read($length) { - return $this->readable ? fread($this->stream, $length) : ""; + return $this->readable ? fread($this->stream, $length) : ''; } /** - * Returns the remaining contents in a string + * Returns the remaining contents in a string. + * + * @throws \RuntimeException if unable to read or an error occurs while + * reading * * @return string - * @throws \RuntimeException if unable to read or an error occurs while - * reading. */ public function getContents() { @@ -261,7 +298,7 @@ class ResponseStream implements StreamInterface } /** - * Closes the stream when the destructed + * Closes the stream when the destructed. */ public function __destruct() { @@ -270,12 +307,10 @@ class ResponseStream implements StreamInterface /** * Closes the stream and any underlying resources. - * - * @return void */ public function close() { - if (is_resource($this->stream)) { + if (\is_resource($this->stream)) { fclose($this->stream); } $this->detach(); @@ -293,6 +328,7 @@ class ResponseStream implements StreamInterface $result = $this->stream; $this->stream = $this->size = $this->uri = null; $this->readable = $this->writable = $this->seekable = false; + return $result; } } diff --git a/src/PhpZip/Stream/ZipInputStream.php b/src/PhpZip/Stream/ZipInputStream.php index dbbbd3e..6d1779d 100644 --- a/src/PhpZip/Stream/ZipInputStream.php +++ b/src/PhpZip/Stream/ZipInputStream.php @@ -14,61 +14,45 @@ use PhpZip\Extra\ExtraFieldsCollection; use PhpZip\Extra\ExtraFieldsFactory; use PhpZip\Extra\Fields\ApkAlignmentExtraField; use PhpZip\Extra\Fields\WinZipAesEntryExtraField; -use PhpZip\Mapper\OffsetPositionMapper; -use PhpZip\Mapper\PositionMapper; use PhpZip\Model\EndOfCentralDirectory; use PhpZip\Model\Entry\ZipSourceEntry; use PhpZip\Model\ZipEntry; use PhpZip\Model\ZipModel; use PhpZip\Util\PackUtil; use PhpZip\Util\StringUtil; -use PhpZip\ZipFileInterface; +use PhpZip\ZipFile; /** - * Read zip file + * Read zip file. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class ZipInputStream implements ZipInputStreamInterface { - /** - * @var resource - */ + /** @var resource */ protected $in; - /** - * @var PositionMapper - */ - protected $mapper; - /** - * @var int The number of bytes in the preamble of this ZIP file. - */ - protected $preamble = 0; - /** - * @var int The number of bytes in the postamble of this ZIP file. - */ - protected $postamble = 0; - /** - * @var ZipModel - */ + + /** @var ZipModel */ protected $zipModel; /** * ZipInputStream constructor. + * * @param resource $in */ public function __construct($in) { - if (!is_resource($in)) { + if (!\is_resource($in)) { throw new RuntimeException('$in must be resource'); } $this->in = $in; - $this->mapper = new PositionMapper(); } /** - * @return ZipModel * @throws ZipException + * + * @return ZipModel */ public function readZip() { @@ -76,11 +60,12 @@ class ZipInputStream implements ZipInputStreamInterface $endOfCentralDirectory = $this->readEndOfCentralDirectory(); $entries = $this->mountCentralDirectory($endOfCentralDirectory); $this->zipModel = ZipModel::newSourceModel($entries, $endOfCentralDirectory); + return $this->zipModel; } /** - * Check zip file signature + * Check zip file signature. * * @throws ZipException if this not .ZIP file. */ @@ -90,152 +75,203 @@ class ZipInputStream implements ZipInputStreamInterface // Constraint: A ZIP file must start with a Local File Header // or a (ZIP64) End Of Central Directory Record if it's empty. $signatureBytes = fread($this->in, 4); - if (strlen($signatureBytes) < 4) { - throw new ZipException("Invalid zip file."); + + if (\strlen($signatureBytes) < 4) { + throw new ZipException('Invalid zip file.'); } $signature = unpack('V', $signatureBytes)[1]; + if ( - ZipEntry::LOCAL_FILE_HEADER_SIG !== $signature - && EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature - && EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $signature + $signature !== ZipEntry::LOCAL_FILE_HEADER_SIG + && $signature !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG + && $signature !== EndOfCentralDirectory::END_OF_CD_SIG ) { - throw new ZipException("Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: " . $signature); + throw new ZipException( + 'Expected Local File Header or (ZIP64) End Of Central Directory Record! Signature: ' . $signature + ); } } /** - * @return EndOfCentralDirectory * @throws ZipException + * + * @return EndOfCentralDirectory */ protected function readEndOfCentralDirectory() { + if (!$this->findEndOfCentralDirectory()) { + throw new ZipException('Invalid zip file. The end of the central directory could not be found.'); + } + + $positionECD = ftell($this->in) - 4; + $buffer = fread($this->in, fstat($this->in)['size'] - $positionECD); + + $unpack = unpack( + 'vdiskNo/vcdDiskNo/vcdEntriesDisk/' . + 'vcdEntries/VcdSize/VcdPos/vcommentLength', + substr($buffer, 0, 18) + ); + + if ( + $unpack['diskNo'] !== 0 || + $unpack['cdDiskNo'] !== 0 || + $unpack['cdEntriesDisk'] !== $unpack['cdEntries'] + ) { + throw new ZipException( + 'ZIP file spanning/splitting is not supported!' + ); + } + // .ZIP file comment (variable sizeECD) $comment = null; - // Search for End of central directory record. - $stats = fstat($this->in); - $size = $stats['size']; - $max = $size - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN; + + if ($unpack['commentLength'] > 0) { + $comment = substr($buffer, 18, $unpack['commentLength']); + } + + // Check for ZIP64 End Of Central Directory Locator exists. + $zip64ECDLocatorPosition = $positionECD - EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_LEN; + fseek($this->in, $zip64ECDLocatorPosition); + // zip64 end of central dir locator + // signature 4 bytes (0x07064b50) + if ($zip64ECDLocatorPosition > 0 && unpack( + 'V', + fread($this->in, 4) + )[1] === EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG) { + $positionECD = $this->findZip64ECDPosition(); + $endCentralDirectory = $this->readZip64EndOfCentralDirectory($positionECD); + $endCentralDirectory->setComment($comment); + } else { + $endCentralDirectory = new EndOfCentralDirectory( + $unpack['cdEntries'], + $unpack['cdPos'], + $unpack['cdSize'], + false, + $comment + ); + } + + return $endCentralDirectory; + } + + /** + * @throws ZipException + * + * @return bool + */ + protected function findEndOfCentralDirectory() + { + $max = fstat($this->in)['size'] - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN; + + if ($max < 0) { + throw new ZipException('Too short to be a zip file'); + } $min = $max >= 0xffff ? $max - 0xffff : 0; - for ($endOfCentralDirRecordPos = $max; $endOfCentralDirRecordPos >= $min; $endOfCentralDirRecordPos--) { - fseek($this->in, $endOfCentralDirRecordPos, SEEK_SET); + // Search for End of central directory record. + for ($position = $max; $position >= $min; $position--) { + fseek($this->in, $position); // end of central dir signature 4 bytes (0x06054b50) - if (EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== unpack('V', fread($this->in, 4))[1]) { + if (unpack('V', fread($this->in, 4))[1] !== EndOfCentralDirectory::END_OF_CD_SIG) { continue; } - // number of this disk - 2 bytes - // number of the disk with the start of the - // central directory - 2 bytes - // total number of entries in the central - // directory on this disk - 2 bytes - // total number of entries in the central - // directory - 2 bytes - // size of the central directory - 4 bytes - // offset of start of central directory with - // respect to the starting disk number - 4 bytes - // ZIP file comment length - 2 bytes - $data = unpack( - 'vdiskNo/vcdDiskNo/vcdEntriesDisk/vcdEntries/VcdSize/VcdPos/vcommentLength', - fread($this->in, 18) - ); - - if (0 !== $data['diskNo'] || 0 !== $data['cdDiskNo'] || $data['cdEntriesDisk'] !== $data['cdEntries']) { - throw new ZipException( - "ZIP file spanning/splitting is not supported!" - ); - } - // .ZIP file comment (variable size) - if ($data['commentLength'] > 0) { - $comment = ''; - $offset = 0; - while ($offset < $data['commentLength']) { - $read = min(8192 /* chunk size */, $data['commentLength'] - $offset); - $comment .= fread($this->in, $read); - $offset += $read; - } - } - $this->preamble = $endOfCentralDirRecordPos; - $this->postamble = $size - ftell($this->in); - - // Check for ZIP64 End Of Central Directory Locator. - $endOfCentralDirLocatorPos = $endOfCentralDirRecordPos - EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN; - - fseek($this->in, $endOfCentralDirLocatorPos, SEEK_SET); - // zip64 end of central dir locator - // signature 4 bytes (0x07064b50) - if ( - 0 > $endOfCentralDirLocatorPos || - ftell($this->in) === $size || - EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG !== unpack('V', fread($this->in, 4))[1] - ) { - // Seek and check first CFH, probably requiring an offset mapper. - $offset = $endOfCentralDirRecordPos - $data['cdSize']; - fseek($this->in, $offset, SEEK_SET); - $offset -= $data['cdPos']; - if ($offset !== 0) { - $this->mapper = new OffsetPositionMapper($offset); - } - $entryCount = $data['cdEntries']; - return new EndOfCentralDirectory($entryCount, $comment); - } - - // number of the disk with the - // start of the zip64 end of - // central directory 4 bytes - $zip64EndOfCentralDirectoryRecordDisk = unpack('V', fread($this->in, 4))[1]; - // relative offset of the zip64 - // end of central directory record 8 bytes - $zip64EndOfCentralDirectoryRecordPos = PackUtil::unpackLongLE(fread($this->in, 8)); - // total number of disks 4 bytes - $totalDisks = unpack('V', fread($this->in, 4))[1]; - if (0 !== $zip64EndOfCentralDirectoryRecordDisk || 1 !== $totalDisks) { - throw new ZipException("ZIP file spanning/splitting is not supported!"); - } - fseek($this->in, $zip64EndOfCentralDirectoryRecordPos, SEEK_SET); - // zip64 end of central dir - // signature 4 bytes (0x06064b50) - $zip64EndOfCentralDirSig = unpack('V', fread($this->in, 4))[1]; - if (EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG !== $zip64EndOfCentralDirSig) { - throw new ZipException("Expected ZIP64 End Of Central Directory Record!"); - } - // size of zip64 end of central - // directory record 8 bytes - // version made by 2 bytes - // version needed to extract 2 bytes - fseek($this->in, 12, SEEK_CUR); - // number of this disk 4 bytes - $diskNo = unpack('V', fread($this->in, 4))[1]; - // number of the disk with the - // start of the central directory 4 bytes - $cdDiskNo = unpack('V', fread($this->in, 4))[1]; - // total number of entries in the - // central directory on this disk 8 bytes - $cdEntriesDisk = PackUtil::unpackLongLE(fread($this->in, 8)); - // total number of entries in the - // central directory 8 bytes - $cdEntries = PackUtil::unpackLongLE(fread($this->in, 8)); - if (0 !== $diskNo || 0 !== $cdDiskNo || $cdEntriesDisk !== $cdEntries) { - throw new ZipException("ZIP file spanning/splitting is not supported!"); - } - if ($cdEntries < 0 || 0x7fffffff < $cdEntries) { - throw new ZipException("Total Number Of Entries In The Central Directory out of range!"); - } - // size of the central directory 8 bytes - fseek($this->in, 8, SEEK_CUR); - // offset of start of central - // directory with respect to - // the starting disk number 8 bytes - $cdPos = PackUtil::unpackLongLE(fread($this->in, 8)); - // zip64 extensible data sector (variable size) - fseek($this->in, $cdPos, SEEK_SET); - $this->preamble = $zip64EndOfCentralDirectoryRecordPos; - $entryCount = $cdEntries; - $zip64 = true; - return new EndOfCentralDirectory($entryCount, $comment, $zip64); + return true; } - // Start recovering file entries from min. - $this->preamble = $min; - $this->postamble = $size - $min; - return new EndOfCentralDirectory(0, $comment); + + return false; + } + + /** + * Read Zip64 end of central directory locator and returns + * Zip64 end of central directory position. + * + * number of the disk with the + * start of the zip64 end of + * central directory 4 bytes + * relative offset of the zip64 + * end of central directory record 8 bytes + * total number of disks 4 bytes + * + * @throws ZipException + * + * @return int Zip64 End Of Central Directory position + */ + protected function findZip64ECDPosition() + { + $diskNo = unpack('V', fread($this->in, 4))[1]; + $zip64ECDPos = PackUtil::unpackLongLE(fread($this->in, 8)); + $totalDisks = unpack('V', fread($this->in, 4))[1]; + + if ($diskNo !== 0 || $totalDisks > 1) { + throw new ZipException('ZIP file spanning/splitting is not supported!'); + } + + return $zip64ECDPos; + } + + /** + * Read zip64 end of central directory locator and zip64 end + * of central directory record. + * + * zip64 end of central dir + * signature 4 bytes (0x06064b50) + * size of zip64 end of central + * directory record 8 bytes + * version made by 2 bytes + * version needed to extract 2 bytes + * number of this disk 4 bytes + * number of the disk with the + * start of the central directory 4 bytes + * total number of entries in the + * central directory on this disk 8 bytes + * total number of entries in the + * central directory 8 bytes + * size of the central directory 8 bytes + * offset of start of central + * directory with respect to + * the starting disk number 8 bytes + * zip64 extensible data sector (variable size) + * + * @param int $zip64ECDPosition + * + * @throws ZipException + * + * @return EndOfCentralDirectory + */ + protected function readZip64EndOfCentralDirectory($zip64ECDPosition) + { + fseek($this->in, $zip64ECDPosition); + + $buffer = fread($this->in, 56 /* zip64 end of cd rec length */); + + if (unpack('V', $buffer)[1] !== EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG) { + throw new ZipException('Expected ZIP64 End Of Central Directory Record!'); + } + + $data = unpack( + 'VdiskNo/VcdDiskNo', + substr($buffer, 16) + ); + $cdEntriesDisk = PackUtil::unpackLongLE(substr($buffer, 24, 8)); + $entryCount = PackUtil::unpackLongLE(substr($buffer, 32, 8)); + $cdSize = PackUtil::unpackLongLE(substr($buffer, 40, 8)); + $cdPos = PackUtil::unpackLongLE(substr($buffer, 48, 8)); + + if ($data['diskNo'] !== 0 || $data['cdDiskNo'] !== 0 || $entryCount !== $cdEntriesDisk) { + throw new ZipException('ZIP file spanning/splitting is not supported!'); + } + + if ($entryCount < 0 || $entryCount > 0x7fffffff) { + throw new ZipException('Total Number Of Entries In The Central Directory out of range!'); + } + + // skip zip64 extensible data sector (variable sizeEndCD) + + return new EndOfCentralDirectory( + $entryCount, + $cdPos, + $cdSize, + true + ); } /** @@ -247,151 +283,148 @@ class ZipInputStream implements ZipInputStreamInterface * file header or additional data to be read. * * @param EndOfCentralDirectory $endOfCentralDirectory - * @return ZipEntry[] + * * @throws ZipException + * + * @return ZipEntry[] */ protected function mountCentralDirectory(EndOfCentralDirectory $endOfCentralDirectory) { - $numEntries = $endOfCentralDirectory->getEntryCount(); $entries = []; - for (; $numEntries > 0; $numEntries--) { - $entry = $this->readEntry(); - // Re-load virtual offset after ZIP64 Extended Information - // Extra Field may have been parsed, map it to the real - // offset and conditionally update the preamble size from it. - $lfhOff = $this->mapper->map($entry->getOffset()); - $lfhOff = PHP_INT_SIZE === 4 ? sprintf('%u', $lfhOff) : $lfhOff; - if ($lfhOff < $this->preamble) { - $this->preamble = $lfhOff; - } + fseek($this->in, $endOfCentralDirectory->getCdOffset()); + + if (!($cdStream = fopen('php://temp', 'w+b'))) { + throw new ZipException('Temp resource can not open from write'); + } + stream_copy_to_stream($this->in, $cdStream, $endOfCentralDirectory->getCdSize()); + rewind($cdStream); + for ($numEntries = $endOfCentralDirectory->getEntryCount(); $numEntries > 0; $numEntries--) { + $entry = $this->readCentralDirectoryEntry($cdStream); $entries[$entry->getName()] = $entry; } - - if (($numEntries % 0x10000) !== 0) { - throw new ZipException("Expected " . abs($numEntries) . - ($numEntries > 0 ? " more" : " less") . - " entries in the Central Directory!"); - } - - if ($this->preamble + $this->postamble >= fstat($this->in)['size']) { - $this->checkZipFileSignature(); - } + fclose($cdStream); return $entries; } /** - * @return ZipEntry + * Read central directory entry. + * + * central file header signature 4 bytes (0x02014b50) + * version made by 2 bytes + * version needed to extract 2 bytes + * general purpose bit flag 2 bytes + * compression method 2 bytes + * last mod file time 2 bytes + * last mod file date 2 bytes + * crc-32 4 bytes + * compressed size 4 bytes + * uncompressed size 4 bytes + * file name length 2 bytes + * extra field length 2 bytes + * file comment length 2 bytes + * disk number start 2 bytes + * internal file attributes 2 bytes + * external file attributes 4 bytes + * relative offset of local header 4 bytes + * + * file name (variable size) + * extra field (variable size) + * file comment (variable size) + * + * @param resource $stream + * * @throws ZipException + * + * @return ZipEntry */ - public function readEntry() + public function readCentralDirectoryEntry($stream) { - // central file header signature 4 bytes (0x02014b50) - $fileHeaderSig = unpack('V', fread($this->in, 4))[1]; - if ($fileHeaderSig !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) { - throw new InvalidArgumentException("Corrupt zip file. Can not read zip entry."); + if (unpack('V', fread($stream, 4))[1] !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) { + throw new ZipException('Corrupt zip file. Cannot read central dir entry.'); } - // version made by 2 bytes - // version needed to extract 2 bytes - // general purpose bit flag 2 bytes - // compression method 2 bytes - // last mod file time 2 bytes - // last mod file date 2 bytes - // crc-32 4 bytes - // compressed size 4 bytes - // uncompressed size 4 bytes - // file name length 2 bytes - // extra field length 2 bytes - // file comment length 2 bytes - // disk number start 2 bytes - // internal file attributes 2 bytes - // external file attributes 4 bytes - // relative offset of local header 4 bytes $data = unpack( - 'vversionMadeBy/vversionNeededToExtract/vgpbf/' . - 'vrawMethod/VrawTime/VrawCrc/VrawCompressedSize/' . - 'VrawSize/vfileLength/vextraLength/vcommentLength/' . - 'VrawInternalAttributes/VrawExternalAttributes/VlfhOff', - fread($this->in, 42) + 'vversionMadeBy/vversionNeededToExtract/' . + 'vgeneralPurposeBitFlag/vcompressionMethod/' . + 'VlastModFile/Vcrc/VcompressedSize/' . + 'VuncompressedSize/vfileNameLength/vextraFieldLength/' . + 'vfileCommentLength/vdiskNumberStart/vinternalFileAttributes/' . + 'VexternalFileAttributes/VoffsetLocalHeader', + fread($stream, 42) ); -// $utf8 = ($data['gpbf'] & ZipEntry::GPBF_UTF8) !== 0; + $createdOS = ($data['versionMadeBy'] & 0xFF00) >> 8; + $softwareVersion = $data['versionMadeBy'] & 0x00FF; - // See appendix D of PKWARE's ZIP File Format Specification. - $name = ''; - $offset = 0; - while ($offset < $data['fileLength']) { - $read = min(8192 /* chunk size */, $data['fileLength'] - $offset); - $name .= fread($this->in, $read); - $offset += $read; + $extractOS = ($data['versionNeededToExtract'] & 0xFF00) >> 8; + $extractVersion = $data['versionNeededToExtract'] & 0x00FF; + + $name = fread($stream, $data['fileNameLength']); + + $extra = ''; + + if ($data['extraFieldLength'] > 0) { + $extra = fread($stream, $data['extraFieldLength']); + } + + $comment = null; + + if ($data['fileCommentLength'] > 0) { + $comment = fread($stream, $data['fileCommentLength']); } $entry = new ZipSourceEntry($this); $entry->setName($name); - $entry->setVersionNeededToExtract($data['versionNeededToExtract']); - $entry->setPlatform($data['versionMadeBy'] >> 8); - $entry->setMethod($data['rawMethod']); - $entry->setGeneralPurposeBitFlags($data['gpbf']); - $entry->setDosTime($data['rawTime']); - $entry->setCrc($data['rawCrc']); - $entry->setCompressedSize($data['rawCompressedSize']); - $entry->setSize($data['rawSize']); - $entry->setExternalAttributes($data['rawExternalAttributes']); - $entry->setOffset($data['lfhOff']); // must be unmapped! - if ($data['extraLength'] > 0) { - $extra = ''; - $offset = 0; - while ($offset < $data['extraLength']) { - $read = min(8192 /* chunk size */, $data['extraLength'] - $offset); - $extra .= fread($this->in, $read); - $offset += $read; - } - $entry->setExtra($extra); - } - if ($data['commentLength'] > 0) { - $comment = ''; - $offset = 0; - while ($offset < $data['commentLength']) { - $read = min(8192 /* chunk size */, $data['commentLength'] - $offset); - $comment .= fread($this->in, $read); - $offset += $read; - } - $entry->setComment($comment); - } + $entry->setCreatedOS($createdOS); + $entry->setSoftwareVersion($softwareVersion); + $entry->setVersionNeededToExtract($extractVersion); + $entry->setExtractedOS($extractOS); + $entry->setMethod($data['compressionMethod']); + $entry->setGeneralPurposeBitFlags($data['generalPurposeBitFlag']); + $entry->setDosTime($data['lastModFile']); + $entry->setCrc($data['crc']); + $entry->setCompressedSize($data['compressedSize']); + $entry->setSize($data['uncompressedSize']); + $entry->setInternalAttributes($data['internalFileAttributes']); + $entry->setExternalAttributes($data['externalFileAttributes']); + $entry->setOffset($data['offsetLocalHeader']); + $entry->setComment($comment); + $entry->setExtra($extra); + return $entry; } /** * @param ZipEntry $entry - * @return string + * * @throws ZipException + * + * @return string */ public function readEntryContent(ZipEntry $entry) { if ($entry->isDirectory()) { return null; } + if (!($entry instanceof ZipSourceEntry)) { throw new InvalidArgumentException('entry must be ' . ZipSourceEntry::class); } $isEncrypted = $entry->isEncrypted(); + if ($isEncrypted && $entry->getPassword() === null) { - throw new ZipException("Can not password from entry " . $entry->getName()); + throw new ZipException('Can not password from entry ' . $entry->getName()); } - $pos = $entry->getOffset(); - $pos = PHP_INT_SIZE === 4 - ? sprintf('%u', $pos) // PHP 32-Bit - : $pos; // PHP 64-Bit + $startPos = $pos = $entry->getOffset(); - $startPos = $pos = $this->mapper->map($pos); fseek($this->in, $startPos); // local file header signature 4 bytes (0x04034b50) if (unpack('V', fread($this->in, 4))[1] !== ZipEntry::LOCAL_FILE_HEADER_SIG) { - throw new ZipException($entry->getName() . " (expected Local File Header)"); + throw new ZipException($entry->getName() . ' (expected Local File Header)'); } fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_FILE_NAME_LENGTH_POS); // file name length 2 bytes @@ -399,7 +432,9 @@ class ZipInputStream implements ZipInputStreamInterface $data = unpack('vfileLength/vextraLength', fread($this->in, 4)); $pos += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $data['fileLength'] + $data['extraLength']; - assert(ZipEntry::UNKNOWN !== $entry->getCrc()); + if ($entry->getCrc() === ZipEntry::UNKNOWN) { + throw new ZipException(sprintf('Missing crc for entry %s', $entry->getName())); + } $method = $entry->getMethod(); @@ -407,10 +442,11 @@ class ZipInputStream implements ZipInputStreamInterface // Get raw entry content $compressedSize = $entry->getCompressedSize(); - $compressedSize = PHP_INT_SIZE === 4 ? sprintf('%u', $compressedSize) : $compressedSize; $content = ''; + if ($compressedSize > 0) { $offset = 0; + while ($offset < $compressedSize) { $read = min(8192 /* chunk size */, $compressedSize - $offset); $content .= fread($this->in, $read); @@ -419,6 +455,7 @@ class ZipInputStream implements ZipInputStreamInterface } $skipCheckCrc = false; + if ($isEncrypted) { if ($method === ZipEntry::METHOD_WINZIP_AES) { // Strong Encryption Specification - WinZip AES @@ -435,12 +472,13 @@ class ZipInputStream implements ZipInputStreamInterface // Traditional PKWARE Decryption $zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry); $content = $zipCryptoEngine->decrypt($content); - $entry->setEncryptionMethod(ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL); + $entry->setEncryptionMethod(ZipFile::ENCRYPTION_METHOD_TRADITIONAL); } if (!$skipCheckCrc) { // Check CRC32 in the Local File Header or Data Descriptor. $localCrc = null; + if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) { // The CRC32 is in the Data Descriptor after the compressed size. // Note the Data Descriptor's Signature is optional: @@ -448,72 +486,88 @@ class ZipInputStream implements ZipInputStreamInterface // but older apps might not. fseek($this->in, $pos + $compressedSize); $localCrc = unpack('V', fread($this->in, 4))[1]; + if ($localCrc === ZipEntry::DATA_DESCRIPTOR_SIG) { $localCrc = unpack('V', fread($this->in, 4))[1]; } } else { fseek($this->in, $startPos + 14); // The CRC32 in the Local File Header. - $localCrc = sprintf('%u', fread($this->in, 4)[1]); - $localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc; + $localCrc = fread($this->in, 4)[1]; } - $crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc(); - - if ($crc != $localCrc) { - throw new Crc32Exception($entry->getName(), $crc, $localCrc); + if (\PHP_INT_SIZE === 4) { + if (sprintf('%u', $entry->getCrc()) === sprintf('%u', $localCrc)) { + throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc); + } + } elseif ($localCrc !== $entry->getCrc()) { + throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc); } } } switch ($method) { - case ZipFileInterface::METHOD_STORED: + case ZipFile::METHOD_STORED: break; - case ZipFileInterface::METHOD_DEFLATED: + + case ZipFile::METHOD_DEFLATED: + /** @noinspection PhpUsageOfSilenceOperatorInspection */ $content = @gzinflate($content); break; - case ZipFileInterface::METHOD_BZIP2: - if (!extension_loaded('bz2')) { + + case ZipFile::METHOD_BZIP2: + if (!\extension_loaded('bz2')) { throw new ZipException('Extension bzip2 not install'); } /** @noinspection PhpComposerExtensionStubsInspection */ $content = bzdecompress($content); - if (is_int($content)) { // decompress error + + if (\is_int($content)) { // decompress error $content = false; } break; default: - throw new ZipUnsupportMethodException($entry->getName() . - " (compression method " . $method . " is not supported)"); + throw new ZipUnsupportMethodException( + $entry->getName() . + ' (compression method ' . $method . ' is not supported)' + ); } if ($content === false) { if ($isEncrypted) { - throw new ZipAuthenticationException(sprintf( - 'Invalid password for zip entry "%s"', - $entry->getName() - )); + throw new ZipAuthenticationException( + sprintf( + 'Invalid password for zip entry "%s"', + $entry->getName() + ) + ); } - throw new ZipException(sprintf( - 'Failed to get the contents of the zip entry "%s"', - $entry->getName() - )); + + throw new ZipException( + sprintf( + 'Failed to get the contents of the zip entry "%s"', + $entry->getName() + ) + ); } if (!$skipCheckCrc) { $localCrc = crc32($content); - $localCrc = PHP_INT_SIZE === 4 ? sprintf('%u', $localCrc) : $localCrc; - $crc = PHP_INT_SIZE === 4 ? sprintf('%u', $entry->getCrc()) : $entry->getCrc(); - if ($crc != $localCrc) { + + if (sprintf('%u', $entry->getCrc()) !== sprintf('%u', $localCrc)) { if ($isEncrypted) { - throw new ZipAuthenticationException(sprintf( - 'Invalid password for zip entry "%s"', - $entry->getName() - )); + throw new ZipAuthenticationException( + sprintf( + 'Invalid password for zip entry "%s"', + $entry->getName() + ) + ); } - throw new Crc32Exception($entry->getName(), $crc, $localCrc); + + throw new Crc32Exception($entry->getName(), $entry->getCrc(), $localCrc); } } + return $content; } @@ -529,36 +583,40 @@ class ZipInputStream implements ZipInputStreamInterface * Copy the input stream of the LOC entry zip and the data into * the output stream and zip the alignment if necessary. * - * @param ZipEntry $entry + * @param ZipEntry $entry * @param ZipOutputStreamInterface $out + * * @throws ZipException */ public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out) { $pos = $entry->getOffset(); - assert(ZipEntry::UNKNOWN !== $pos); - $pos = PHP_INT_SIZE === 4 ? sprintf('%u', $pos) : $pos; - $pos = $this->mapper->map($pos); - $nameLength = strlen($entry->getName()); + if ($pos === ZipEntry::UNKNOWN) { + throw new ZipException(sprintf('Missing local header offset for entry %s', $entry->getName())); + } - fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET); + $nameLength = \strlen($entry->getName()); + + fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, \SEEK_SET); $sourceExtraLength = $destExtraLength = unpack('v', fread($this->in, 2))[1]; if ($sourceExtraLength > 0) { // read Local File Header extra fields - fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, SEEK_SET); + fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength, \SEEK_SET); $extra = ''; $offset = 0; + while ($offset < $sourceExtraLength) { $read = min(8192 /* chunk size */, $sourceExtraLength - $offset); $extra .= fread($this->in, $read); $offset += $read; } $extraFieldsCollection = ExtraFieldsFactory::createExtraFieldCollections($extra, $entry); + if (isset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]) && $this->zipModel->isZipAlign()) { unset($extraFieldsCollection[ApkAlignmentExtraField::getHeaderId()]); - $destExtraLength = strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection)); + $destExtraLength = \strlen(ExtraFieldsFactory::createSerializedData($extraFieldsCollection)); } } else { $extraFieldsCollection = new ExtraFieldsCollection(); @@ -567,12 +625,12 @@ class ZipInputStream implements ZipInputStreamInterface $dataAlignmentMultiple = $this->zipModel->getZipAlign(); $copyInToOutLength = $entry->getCompressedSize(); - fseek($this->in, $pos, SEEK_SET); + fseek($this->in, $pos, \SEEK_SET); if ( $this->zipModel->isZipAlign() && !$entry->isEncrypted() && - $entry->getMethod() === ZipFileInterface::METHOD_STORED + $entry->getMethod() === ZipFile::METHOD_STORED ) { if (StringUtil::endsWith($entry->getName(), '.so')) { $dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; @@ -599,23 +657,25 @@ class ZipInputStream implements ZipInputStreamInterface // from input stream to output stream stream_copy_to_stream($this->in, $out->getStream(), ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2); // write new extra field length (2 bytes) to output stream - fwrite($out->getStream(), pack('v', strlen($extra))); + fwrite($out->getStream(), pack('v', \strlen($extra))); // skip 2 bytes to input stream - fseek($this->in, 2, SEEK_CUR); + fseek($this->in, 2, \SEEK_CUR); // copy name from input stream to output stream stream_copy_to_stream($this->in, $out->getStream(), $nameLength); // write extra field to output stream fwrite($out->getStream(), $extra); // skip source extraLength from input stream - fseek($this->in, $sourceExtraLength, SEEK_CUR); + fseek($this->in, $sourceExtraLength, \SEEK_CUR); } else { $copyInToOutLength += ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $sourceExtraLength + $nameLength; } + if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) { // crc-32 4 bytes // compressed size 4 bytes // uncompressed size 4 bytes $copyInToOutLength += 12; + if ($entry->isZip64ExtensionsRequired()) { // compressed size +4 bytes // uncompressed size +4 bytes @@ -627,20 +687,18 @@ class ZipInputStream implements ZipInputStreamInterface } /** - * @param ZipEntry $entry + * @param ZipEntry $entry * @param ZipOutputStreamInterface $out */ public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out) { $offset = $entry->getOffset(); - $offset = PHP_INT_SIZE === 4 ? sprintf('%u', $offset) : $offset; - $offset = $this->mapper->map($offset); - $nameLength = strlen($entry->getName()); + $nameLength = \strlen($entry->getName()); - fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET); + fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, \SEEK_SET); $extraLength = unpack('v', fread($this->in, 2))[1]; - fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, SEEK_SET); + fseek($this->in, $offset + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN + $nameLength + $extraLength, \SEEK_SET); // copy raw data from input stream to output stream stream_copy_to_stream($this->in, $out->getStream(), $entry->getCompressedSize()); } diff --git a/src/PhpZip/Stream/ZipInputStreamInterface.php b/src/PhpZip/Stream/ZipInputStreamInterface.php index cf8e122..e4f2c9c 100644 --- a/src/PhpZip/Stream/ZipInputStreamInterface.php +++ b/src/PhpZip/Stream/ZipInputStreamInterface.php @@ -7,7 +7,7 @@ use PhpZip\Model\ZipEntry; use PhpZip\Model\ZipModel; /** - * Read zip file + * Read zip file. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT @@ -20,14 +20,22 @@ interface ZipInputStreamInterface public function readZip(); /** + * Read central directory entry. + * + * @param resource $stream + * + * @throws ZipException + * * @return ZipEntry */ - public function readEntry(); + public function readCentralDirectoryEntry($stream); /** * @param ZipEntry $entry - * @return string + * * @throws ZipException + * + * @return string */ public function readEntryContent(ZipEntry $entry); @@ -40,13 +48,13 @@ interface ZipInputStreamInterface * Copy the input stream of the LOC entry zip and the data into * the output stream and zip the alignment if necessary. * - * @param ZipEntry $entry + * @param ZipEntry $entry * @param ZipOutputStreamInterface $out */ public function copyEntry(ZipEntry $entry, ZipOutputStreamInterface $out); /** - * @param ZipEntry $entry + * @param ZipEntry $entry * @param ZipOutputStreamInterface $out */ public function copyEntryData(ZipEntry $entry, ZipOutputStreamInterface $out); diff --git a/src/PhpZip/Stream/ZipOutputStream.php b/src/PhpZip/Stream/ZipOutputStream.php index ce0cd74..ae36c2b 100644 --- a/src/PhpZip/Stream/ZipOutputStream.php +++ b/src/PhpZip/Stream/ZipOutputStream.php @@ -19,33 +19,31 @@ use PhpZip\Model\ZipEntry; use PhpZip\Model\ZipModel; use PhpZip\Util\PackUtil; use PhpZip\Util\StringUtil; -use PhpZip\ZipFileInterface; +use PhpZip\ZipFile; /** - * Write zip file + * Write zip file. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ class ZipOutputStream implements ZipOutputStreamInterface { - /** - * @var resource - */ + /** @var resource */ protected $out; - /** - * @var ZipModel - */ + + /** @var ZipModel */ protected $zipModel; /** * ZipOutputStream constructor. + * * @param resource $out * @param ZipModel $zipModel */ public function __construct($out, ZipModel $zipModel) { - if (!is_resource($out)) { + if (!\is_resource($out)) { throw new InvalidArgumentException('$out must be resource'); } $this->out = $out; @@ -59,11 +57,13 @@ class ZipOutputStream implements ZipOutputStreamInterface { $entries = $this->zipModel->getEntries(); $outPosEntries = []; + foreach ($entries as $entry) { $outPosEntries[] = new OutputOffsetEntry(ftell($this->out), $entry); $this->writeEntry($entry); } $centralDirectoryOffset = ftell($this->out); + foreach ($outPosEntries as $outputEntry) { $this->writeCentralDirectoryHeader($outputEntry); } @@ -72,12 +72,14 @@ class ZipOutputStream implements ZipOutputStreamInterface /** * @param ZipEntry $entry + * * @throws ZipException */ public function writeEntry(ZipEntry $entry) { if ($entry instanceof ZipSourceEntry) { $entry->getInputStream()->copyEntry($entry, $this); + return; } @@ -88,16 +90,17 @@ class ZipOutputStream implements ZipOutputStreamInterface $extra = $entry->getExtra(); - $nameLength = strlen($entry->getName()); - $extraLength = strlen($extra); + $nameLength = \strlen($entry->getName()); + $extraLength = \strlen($extra); // zip align if ( $this->zipModel->isZipAlign() && !$entry->isEncrypted() && - $entry->getMethod() === ZipFileInterface::METHOD_STORED + $entry->getMethod() === ZipFile::METHOD_STORED ) { $dataAlignmentMultiple = $this->zipModel->getZipAlign(); + if (StringUtil::endsWith($entry->getName(), '.so')) { $dataAlignmentMultiple = ApkAlignmentExtraField::ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; } @@ -120,15 +123,16 @@ class ZipOutputStream implements ZipOutputStreamInterface $extraFieldsCollection->add($alignExtra); $extra = ExtraFieldsFactory::createSerializedData($extraFieldsCollection); - $extraLength = strlen($extra); + $extraLength = \strlen($extra); } $size = $nameLength + $extraLength; + if ($size > 0xffff) { throw new ZipException( - $entry->getName() . " (the total size of " . $size . - " bytes for the name, extra fields and comment " . - "exceeds the maximum size of " . 0xffff . " bytes)" + $entry->getName() . ' (the total size of ' . $size . + ' bytes for the name, extra fields and comment ' . + 'exceeds the maximum size of ' . 0xffff . ' bytes)' ); } @@ -140,7 +144,7 @@ class ZipOutputStream implements ZipOutputStreamInterface // local file header signature 4 bytes (0x04034b50) ZipEntry::LOCAL_FILE_HEADER_SIG, // version needed to extract 2 bytes - $entry->getVersionNeededToExtract(), + ($entry->getExtractedOS() << 8) | $entry->getVersionNeededToExtract(), // general purpose bit flag 2 bytes $entry->getGeneralPurposeBitFlags(), // compression method 2 bytes @@ -160,9 +164,11 @@ class ZipOutputStream implements ZipOutputStreamInterface $extraLength ) ); + if ($nameLength > 0) { fwrite($this->out, $entry->getName()); } + if ($extraLength > 0) { fwrite($this->out, $extra); } @@ -173,8 +179,18 @@ class ZipOutputStream implements ZipOutputStreamInterface fwrite($this->out, $entryContent); } - assert(ZipEntry::UNKNOWN !== $entry->getCrc()); - assert(ZipEntry::UNKNOWN !== $entry->getSize()); + if ($entry->getCrc() === ZipEntry::UNKNOWN) { + throw new ZipException(sprintf('No crc for entry %s', $entry->getName())); + } + + if ($entry->getSize() === ZipEntry::UNKNOWN) { + throw new ZipException(sprintf('No uncompressed size for entry %s', $entry->getName())); + } + + if ($entry->getCompressedSize() === ZipEntry::UNKNOWN) { + throw new ZipException(sprintf('No compressed size for entry %s', $entry->getName())); + } + if ($entry->getGeneralPurposeBitFlag(ZipEntry::GPBF_DATA_DESCRIPTOR)) { // data descriptor signature 4 bytes (0x08074b50) // crc-32 4 bytes @@ -187,25 +203,32 @@ class ZipOutputStream implements ZipOutputStreamInterface } else { fwrite($this->out, pack('VV', $entry->getCompressedSize(), $entry->getSize())); } - } elseif ($compressedSize != $entry->getCompressedSize()) { + } elseif ($compressedSize !== $entry->getCompressedSize()) { throw new ZipException( - $entry->getName() . " (expected compressed entry size of " - . $entry->getCompressedSize() . " bytes, " . - "but is actually " . $compressedSize . " bytes)" + $entry->getName() . ' (expected compressed entry size of ' + . $entry->getCompressedSize() . ' bytes, ' . + 'but is actually ' . $compressedSize . ' bytes)' ); } } /** * @param ZipEntry $entry - * @return null|string + * * @throws ZipException + * + * @return string|null */ protected function entryCommitChangesAndReturnContent(ZipEntry $entry) { - if ($entry->getPlatform() === ZipEntry::UNKNOWN) { - $entry->setPlatform(ZipEntry::PLATFORM_UNIX); + if ($entry->getCreatedOS() === ZipEntry::UNKNOWN) { + $entry->setCreatedOS(ZipEntry::PLATFORM_UNIX); } + + if ($entry->getExtractedOS() === ZipEntry::UNKNOWN) { + $entry->setExtractedOS(ZipEntry::PLATFORM_UNIX); + } + if ($entry->getTime() === ZipEntry::UNKNOWN) { $entry->setTime(time()); } @@ -216,7 +239,7 @@ class ZipOutputStream implements ZipOutputStreamInterface $utf8 = true; if ($encrypted && $entry->getPassword() === null) { - throw new ZipException("Can not password from entry " . $entry->getName()); + throw new ZipException(sprintf('Password not set for entry %s', $entry->getName())); } // Compose General Purpose Bit Flag. @@ -226,11 +249,12 @@ class ZipOutputStream implements ZipOutputStreamInterface $entryContent = null; $extraFieldsCollection = $entry->getExtraFieldsCollection(); + if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) { $entryContent = $entry->getEntryContent(); if ($entryContent !== null) { - $entry->setSize(strlen($entryContent)); + $entry->setSize(\strlen($entryContent)); $entry->setCrc(crc32($entryContent)); if ($encrypted && $method === ZipEntry::METHOD_WINZIP_AES) { @@ -238,26 +262,28 @@ class ZipOutputStream implements ZipOutputStreamInterface * @var WinZipAesEntryExtraField $field */ $field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId()); + if ($field !== null) { $method = $field->getMethod(); } } switch ($method) { - case ZipFileInterface::METHOD_STORED: + case ZipFile::METHOD_STORED: break; - case ZipFileInterface::METHOD_DEFLATED: + case ZipFile::METHOD_DEFLATED: $entryContent = gzdeflate($entryContent, $entry->getCompressionLevel()); break; - case ZipFileInterface::METHOD_BZIP2: - $compressionLevel = $entry->getCompressionLevel() === ZipFileInterface::LEVEL_DEFAULT_COMPRESSION ? + case ZipFile::METHOD_BZIP2: + $compressionLevel = $entry->getCompressionLevel() === ZipFile::LEVEL_DEFAULT_COMPRESSION ? ZipEntry::LEVEL_DEFAULT_BZIP2_COMPRESSION : $entry->getCompressionLevel(); /** @noinspection PhpComposerExtensionStubsInspection */ $entryContent = bzcompress($entryContent, $compressionLevel); - if (is_int($entryContent)) { + + if (\is_int($entryContent)) { throw new ZipException('Error bzip2 compress. Error code: ' . $entryContent); } break; @@ -268,22 +294,22 @@ class ZipOutputStream implements ZipOutputStreamInterface break; default: - throw new ZipException($entry->getName() . " (unsupported compression method " . $method . ")"); + throw new ZipException($entry->getName() . ' (unsupported compression method ' . $method . ')'); } - if ($method === ZipFileInterface::METHOD_DEFLATED) { + if ($method === ZipFile::METHOD_DEFLATED) { $bit1 = false; $bit2 = false; switch ($entry->getCompressionLevel()) { - case ZipFileInterface::LEVEL_BEST_COMPRESSION: + case ZipFile::LEVEL_BEST_COMPRESSION: $bit1 = true; break; - case ZipFileInterface::LEVEL_FAST: + case ZipFile::LEVEL_FAST: $bit2 = true; break; - case ZipFileInterface::LEVEL_SUPER_FAST: + case ZipFile::LEVEL_SUPER_FAST: $bit1 = true; $bit2 = true; break; @@ -294,17 +320,24 @@ class ZipOutputStream implements ZipOutputStreamInterface } if ($encrypted) { - if (in_array($entry->getEncryptionMethod(), [ - ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128, - ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192, - ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256, - ], true)) { - $keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits + if (\in_array( + $entry->getEncryptionMethod(), + [ + ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128, + ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192, + ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256, + ], + true + )) { + $keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod( + $entry->getEncryptionMethod() + ); // size bits $field = ExtraFieldsFactory::createWinZipAesEntryExtra(); $field->setKeyStrength($keyStrength); $field->setMethod($method); $size = $entry->getSize(); - if ($size >= 20 && $method !== ZipFileInterface::METHOD_BZIP2) { + + if ($size >= 20 && $method !== ZipFile::METHOD_BZIP2) { $field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1); } else { $field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2); @@ -315,13 +348,13 @@ class ZipOutputStream implements ZipOutputStreamInterface $winZipAesEngine = new WinZipAesEngine($entry); $entryContent = $winZipAesEngine->encrypt($entryContent); - } elseif ($entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL) { + } elseif ($entry->getEncryptionMethod() === ZipFile::ENCRYPTION_METHOD_TRADITIONAL) { $zipCryptoEngine = new TraditionalPkwareEncryptionEngine($entry); $entryContent = $zipCryptoEngine->encrypt($entryContent); } } - $compressedSize = strlen($entryContent); + $compressedSize = \strlen($entryContent); $entry->setCompressedSize($compressedSize); } } @@ -334,25 +367,31 @@ class ZipOutputStream implements ZipOutputStreamInterface } elseif ($extraFieldsCollection->has(Zip64ExtraField::getHeaderId())) { $extraFieldsCollection->remove(Zip64ExtraField::getHeaderId()); } + return $entryContent; } /** * @param ZipEntry $entry - * @param string $content - * @return string + * @param string $content + * * @throws ZipException + * + * @return string */ protected function determineBestCompressionMethod(ZipEntry $entry, $content) { if ($content !== null) { $entryContent = gzdeflate($content, $entry->getCompressionLevel()); - if (strlen($entryContent) < strlen($content)) { - $entry->setMethod(ZipFileInterface::METHOD_DEFLATED); + + if (\strlen($entryContent) < \strlen($content)) { + $entry->setMethod(ZipFile::METHOD_DEFLATED); + return $entryContent; } - $entry->setMethod(ZipFileInterface::METHOD_STORED); + $entry->setMethod(ZipFile::METHOD_STORED); } + return $content; } @@ -369,12 +408,12 @@ class ZipOutputStream implements ZipOutputStreamInterface // This test MUST NOT include the CRC-32 because VV_AE_2 sets it to // UNKNOWN! if (($compressedSize | $size) === ZipEntry::UNKNOWN) { - throw new RuntimeException("invalid entry"); + throw new RuntimeException('invalid entry'); } $extra = $entry->getExtra(); - $extraSize = strlen($extra); + $extraSize = \strlen($extra); - $commentLength = strlen($entry->getComment()); + $commentLength = \strlen($entry->getComment()); fwrite( $this->out, pack( @@ -382,9 +421,9 @@ class ZipOutputStream implements ZipOutputStreamInterface // central file header signature 4 bytes (0x02014b50) self::CENTRAL_FILE_HEADER_SIG, // version made by 2 bytes - ($entry->getPlatform() << 8) | 63, + ($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(), // version needed to extract 2 bytes - $entry->getVersionNeededToExtract(), + ($entry->getExtractedOS() << 8) | $entry->getVersionNeededToExtract(), // general purpose bit flag 2 bytes $entry->getGeneralPurposeBitFlags(), // compression method 2 bytes @@ -398,7 +437,7 @@ class ZipOutputStream implements ZipOutputStreamInterface // uncompressed size 4 bytes $entry->getSize(), // file name length 2 bytes - strlen($entry->getName()), + \strlen($entry->getName()), // extra field length 2 bytes $extraSize, // file comment length 2 bytes @@ -406,7 +445,7 @@ class ZipOutputStream implements ZipOutputStreamInterface // disk number start 2 bytes 0, // internal file attributes 2 bytes - 0, + $entry->getInternalAttributes(), // external file attributes 4 bytes $entry->getExternalAttributes(), // relative offset of local header 4 bytes @@ -415,82 +454,116 @@ class ZipOutputStream implements ZipOutputStreamInterface ); // file name (variable size) fwrite($this->out, $entry->getName()); + if ($extraSize > 0) { // extra field (variable size) fwrite($this->out, $extra); } + if ($commentLength > 0) { // file comment (variable size) fwrite($this->out, $entry->getComment()); } } + /** + * @param int $centralDirectoryOffset + */ protected function writeEndOfCentralDirectoryRecord($centralDirectoryOffset) { - $centralDirectoryEntriesCount = count($this->zipModel); + $cdEntriesCount = \count($this->zipModel); + $position = ftell($this->out); $centralDirectorySize = $position - $centralDirectoryOffset; - $centralDirectoryEntriesZip64 = $centralDirectoryEntriesCount > 0xffff; - $centralDirectorySizeZip64 = $centralDirectorySize > 0xffffffff; - $centralDirectoryOffsetZip64 = $centralDirectoryOffset > 0xffffffff; - $centralDirectoryEntries16 = $centralDirectoryEntriesZip64 ? 0xffff : (int)$centralDirectoryEntriesCount; - $centralDirectorySize32 = $centralDirectorySizeZip64 ? 0xffffffff : $centralDirectorySize; - $centralDirectoryOffset32 = $centralDirectoryOffsetZip64 ? 0xffffffff : $centralDirectoryOffset; - $zip64 // ZIP64 extensions? - = $centralDirectoryEntriesZip64 - || $centralDirectorySizeZip64 - || $centralDirectoryOffsetZip64; - if ($zip64) { - // [zip64 end of central directory record] - // relative offset of the zip64 end of central directory record - $zip64EndOfCentralDirectoryOffset = $position; - // zip64 end of central dir + + $cdEntriesZip64 = $cdEntriesCount > 0xFFFF; + $cdSizeZip64 = $centralDirectorySize > 0xFFFFFFFF; + $cdOffsetZip64 = $centralDirectoryOffset > 0xFFFFFFFF; + + $zip64Required = $cdEntriesZip64 || $cdSizeZip64 || $cdOffsetZip64; + + if ($zip64Required) { + $zip64EndOfCentralDirectoryOffset = ftell($this->out); + + // find max software version, version needed to extract and most common platform + list($softwareVersion, $versionNeededToExtract) = array_reduce( + $this->zipModel->getEntries(), + static function (array $carry, ZipEntry $entry) { + $carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF); + $carry[1] = max($carry[1], $entry->getVersionNeededToExtract() & 0xFF); + + return $carry; + }, + [10 /* simple file min ver */, 45 /* zip64 ext min ver */] + ); + + $createdOS = $extractedOS = ZipEntry::PLATFORM_FAT; + $versionMadeBy = ($createdOS << 8) | max($softwareVersion, 45 /* zip64 ext min ver */); + $versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, 45 /* zip64 ext min ver */); + // signature 4 bytes (0x06064b50) - fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG)); + fwrite($this->out, pack('V', EndOfCentralDirectory::ZIP64_END_OF_CD_RECORD_SIG)); // size of zip64 end of central // directory record 8 bytes - fwrite($this->out, PackUtil::packLongLE(EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN - 12)); - // version made by 2 bytes - // version needed to extract 2 bytes - // due to potential use of BZIP2 compression - // number of this disk 4 bytes - // number of the disk with the - // start of the central directory 4 bytes - fwrite($this->out, pack('vvVV', 63, 46, 0, 0)); + fwrite($this->out, PackUtil::packLongLE(44)); + fwrite( + $this->out, + pack( + 'vvVV', + // version made by 2 bytes + $versionMadeBy & 0xFFFF, + // version needed to extract 2 bytes + $versionExtractedBy & 0xFFFF, + // number of this disk 4 bytes + 0, + // number of the disk with the + // start of the central directory 4 bytes + 0 + ) + ); // total number of entries in the // central directory on this disk 8 bytes - fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount)); + fwrite($this->out, PackUtil::packLongLE($cdEntriesCount)); // total number of entries in the // central directory 8 bytes - fwrite($this->out, PackUtil::packLongLE($centralDirectoryEntriesCount)); + fwrite($this->out, PackUtil::packLongLE($cdEntriesCount)); // size of the central directory 8 bytes fwrite($this->out, PackUtil::packLongLE($centralDirectorySize)); // offset of start of central // directory with respect to // the starting disk number 8 bytes fwrite($this->out, PackUtil::packLongLE($centralDirectoryOffset)); - // zip64 extensible data sector (variable size) - // [zip64 end of central directory locator] - // signature 4 bytes (0x07064b50) - // number of the disk with the - // start of the zip64 end of - // central directory 4 bytes - fwrite($this->out, pack('VV', EndOfCentralDirectory::ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG, 0)); + // write zip64 end of central directory locator + fwrite( + $this->out, + pack( + 'VV', + // zip64 end of central dir locator + // signature 4 bytes (0x07064b50) + EndOfCentralDirectory::ZIP64_END_OF_CD_LOCATOR_SIG, + // number of the disk with the + // start of the zip64 end of + // central directory 4 bytes + 0 + ) + ); // relative offset of the zip64 // end of central directory record 8 bytes fwrite($this->out, PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset)); // total number of disks 4 bytes fwrite($this->out, pack('V', 1)); } + $comment = $this->zipModel->getArchiveComment(); - $commentLength = strlen($comment); + $commentLength = $comment !== null ? \strlen($comment) : 0; + fwrite( $this->out, pack( 'VvvvvVVv', // end of central dir signature 4 bytes (0x06054b50) - EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, + EndOfCentralDirectory::END_OF_CD_SIG, // number of this disk 2 bytes 0, // number of the disk with the @@ -498,20 +571,21 @@ class ZipOutputStream implements ZipOutputStreamInterface 0, // total number of entries in the // central directory on this disk 2 bytes - $centralDirectoryEntries16, + $cdEntriesZip64 ? 0xFFFF : $cdEntriesCount, // total number of entries in // the central directory 2 bytes - $centralDirectoryEntries16, + $cdEntriesZip64 ? 0xFFFF : $cdEntriesCount, // size of the central directory 4 bytes - $centralDirectorySize32, + $cdSizeZip64 ? 0xFFFFFFFF : $centralDirectorySize, // offset of start of central // directory with respect to // the starting disk number 4 bytes - $centralDirectoryOffset32, + $cdOffsetZip64 ? 0xFFFFFFFF : $centralDirectoryOffset, // .ZIP file comment length 2 bytes $commentLength ) ); + if ($commentLength > 0) { // .ZIP file comment (variable size) fwrite($this->out, $comment); diff --git a/src/PhpZip/Stream/ZipOutputStreamInterface.php b/src/PhpZip/Stream/ZipOutputStreamInterface.php index 57c397e..dc50801 100644 --- a/src/PhpZip/Stream/ZipOutputStreamInterface.php +++ b/src/PhpZip/Stream/ZipOutputStreamInterface.php @@ -5,7 +5,7 @@ namespace PhpZip\Stream; use PhpZip\Model\ZipEntry; /** - * Write zip file + * Write zip file. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT diff --git a/src/PhpZip/Util/CryptoUtil.php b/src/PhpZip/Util/CryptoUtil.php index d7a659c..d2da78f 100644 --- a/src/PhpZip/Util/CryptoUtil.php +++ b/src/PhpZip/Util/CryptoUtil.php @@ -2,38 +2,26 @@ namespace PhpZip\Util; -use PhpZip\Exception\RuntimeException; - /** - * Crypto Utils + * Crypto Utils. + * + * @deprecated */ class CryptoUtil { - /** * Returns random bytes. * * @param int $length + * + * @throws \Exception + * * @return string + * + * @deprecated Use random_bytes() */ final public static function randomBytes($length) { - $length = (int)$length; - if (function_exists('random_bytes')) { - try { - return random_bytes($length); - } catch (\Exception $e) { - throw new \RuntimeException("Could not generate a random string."); - } - } elseif (function_exists('openssl_random_pseudo_bytes')) { - /** @noinspection PhpComposerExtensionStubsInspection */ - return openssl_random_pseudo_bytes($length); - } elseif (function_exists('mcrypt_create_iv')) { - /** @noinspection PhpDeprecationInspection */ - /** @noinspection PhpComposerExtensionStubsInspection */ - return mcrypt_create_iv($length); - } else { - throw new RuntimeException('Extension openssl or mcrypt not loaded'); - } + return random_bytes($length); } } diff --git a/src/PhpZip/Util/DateTimeConverter.php b/src/PhpZip/Util/DateTimeConverter.php index e63f56b..5007bf9 100644 --- a/src/PhpZip/Util/DateTimeConverter.php +++ b/src/PhpZip/Util/DateTimeConverter.php @@ -28,13 +28,14 @@ class DateTimeConverter * Convert a 32 bit integer DOS date/time value to a UNIX timestamp value. * * @param int $dosTime Dos date/time + * * @return int Unix timestamp */ public static function toUnixTimestamp($dosTime) { - if (self::MIN_DOS_TIME > $dosTime) { + if ($dosTime < self::MIN_DOS_TIME) { $dosTime = self::MIN_DOS_TIME; - } elseif (self::MAX_DOS_TIME < $dosTime) { + } elseif ($dosTime > self::MAX_DOS_TIME) { $dosTime = self::MAX_DOS_TIME; } @@ -51,17 +52,19 @@ class DateTimeConverter /** * Converts a UNIX timestamp value to a DOS date/time value. * - * @param int $unixTimestamp The number of seconds since midnight, January 1st, - * 1970 AD UTC. - * @return int A DOS date/time value reflecting the local time zone and - * rounded down to even seconds - * and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME. - * @throws ZipException If unix timestamp is negative. + * @param int $unixTimestamp the number of seconds since midnight, January 1st, + * 1970 AD UTC + * + * @throws ZipException if unix timestamp is negative + * + * @return int a DOS date/time value reflecting the local time zone and + * rounded down to even seconds + * and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME */ public static function toDosTime($unixTimestamp) { - if (0 > $unixTimestamp) { - throw new ZipException("Negative unix timestamp: " . $unixTimestamp); + if ($unixTimestamp < 0) { + throw new ZipException('Negative unix timestamp: ' . $unixTimestamp); } $date = getdate($unixTimestamp); @@ -71,8 +74,9 @@ class DateTimeConverter } $date['year'] -= 1980; - return ($date['year'] << 25 | $date['mon'] << 21 | + + return $date['year'] << 25 | $date['mon'] << 21 | $date['mday'] << 16 | $date['hours'] << 11 | - $date['minutes'] << 5 | $date['seconds'] >> 1); + $date['minutes'] << 5 | $date['seconds'] >> 1; } } diff --git a/src/PhpZip/Util/FilesUtil.php b/src/PhpZip/Util/FilesUtil.php index 0aae7bf..784a6e4 100644 --- a/src/PhpZip/Util/FilesUtil.php +++ b/src/PhpZip/Util/FilesUtil.php @@ -10,14 +10,16 @@ use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator; * * @author Ne-Lexa alexey@nelexa.ru * @license MIT + * + * @internal */ -class FilesUtil +final class FilesUtil { - /** - * Is empty directory + * Is empty directory. * * @param string $dir Directory + * * @return bool */ public static function isEmptyDir($dir) @@ -25,13 +27,14 @@ class FilesUtil if (!is_readable($dir)) { return false; } - return count(scandir($dir)) === 2; + + return \count(scandir($dir)) === 2; } /** * Remove recursive directory. * - * @param string $dir Directory path. + * @param string $dir directory path */ public static function removeDir($dir) { @@ -39,6 +42,7 @@ class FilesUtil new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST ); + foreach ($files as $fileInfo) { $function = ($fileInfo->isDir() ? 'rmdir' : 'unlink'); $function($fileInfo->getRealPath()); @@ -46,11 +50,11 @@ class FilesUtil rmdir($dir); } - /** * Convert glob pattern to regex pattern. * * @param string $globPattern + * * @return string */ public static function convertGlobToRegEx($globPattern) @@ -61,16 +65,19 @@ class FilesUtil $inCurrent = 0; $chars = str_split($globPattern); $regexPattern = ''; + foreach ($chars as $currentChar) { switch ($currentChar) { case '*': - $regexPattern .= ($escaping ? "\\*" : '.*'); + $regexPattern .= ($escaping ? '\\*' : '.*'); $escaping = false; break; + case '?': - $regexPattern .= ($escaping ? "\\?" : '.'); + $regexPattern .= ($escaping ? '\\?' : '.'); $escaping = false; break; + case '.': case '(': case ')': @@ -83,41 +90,45 @@ class FilesUtil $regexPattern .= '\\' . $currentChar; $escaping = false; break; + case '\\': if ($escaping) { - $regexPattern .= "\\\\"; + $regexPattern .= '\\\\'; $escaping = false; } else { $escaping = true; } break; + case '{': if ($escaping) { - $regexPattern .= "\\{"; + $regexPattern .= '\\{'; } else { $regexPattern = '('; $inCurrent++; } $escaping = false; break; + case '}': if ($inCurrent > 0 && !$escaping) { $regexPattern .= ')'; $inCurrent--; } elseif ($escaping) { - $regexPattern = "\\}"; + $regexPattern = '\\}'; } else { - $regexPattern = "}"; + $regexPattern = '}'; } $escaping = false; break; + case ',': if ($inCurrent > 0 && !$escaping) { $regexPattern .= '|'; } elseif ($escaping) { - $regexPattern .= "\\,"; + $regexPattern .= '\\,'; } else { - $regexPattern = ","; + $regexPattern = ','; } break; default: @@ -125,6 +136,7 @@ class FilesUtil $regexPattern .= $currentChar; } } + return $regexPattern; } @@ -132,8 +144,9 @@ class FilesUtil * Search files. * * @param string $inputDir - * @param bool $recursive - * @param array $ignoreFiles + * @param bool $recursive + * @param array $ignoreFiles + * * @return array Searched file list */ public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = []) @@ -153,11 +166,13 @@ class FilesUtil new \IteratorIterator($directoryIterator); $fileList = []; + foreach ($iterator as $file) { if ($file instanceof \SplFileInfo) { $fileList[] = $file->getPathname(); } } + return $fileList; } @@ -165,21 +180,27 @@ class FilesUtil * Search files from glob pattern. * * @param string $globPattern - * @param int $flags - * @param bool $recursive + * @param int $flags + * @param bool $recursive + * * @return array Searched file list */ public static function globFileSearch($globPattern, $flags = 0, $recursive = true) { - $flags = (int)$flags; - $recursive = (bool)$recursive; + $flags = (int) $flags; + $recursive = (bool) $recursive; $files = glob($globPattern, $flags); + if (!$recursive) { return $files; } - foreach (glob(dirname($globPattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { + + foreach (glob(\dirname($globPattern) . '/*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) { + // Unpacking the argument via ... is supported starting from php 5.6 only + /** @noinspection SlowArrayOperationsInLoopInspection */ $files = array_merge($files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive)); } + return $files; } @@ -188,48 +209,58 @@ class FilesUtil * * @param string $folder * @param string $pattern - * @param bool $recursive + * @param bool $recursive + * * @return array Searched file list */ public static function regexFileSearch($folder, $pattern, $recursive = true) { $directoryIterator = $recursive ? new \RecursiveDirectoryIterator($folder) : new \DirectoryIterator($folder); - $iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator($directoryIterator); + $iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator( + $directoryIterator + ); $regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH); $fileList = []; + foreach ($regexIterator as $file) { if ($file instanceof \SplFileInfo) { $fileList[] = $file->getPathname(); } } + return $fileList; } /** * Convert bytes to human size. * - * @param int $size Size bytes + * @param int $size Size bytes * @param string|null $unit Unit support 'GB', 'MB', 'KB' + * * @return string */ public static function humanSize($size, $unit = null) { - if (($unit === null && $size >= 1 << 30) || $unit === "GB") { - return number_format($size / (1 << 30), 2) . "GB"; + if (($unit === null && $size >= 1 << 30) || $unit === 'GB') { + return number_format($size / (1 << 30), 2) . 'GB'; } - if (($unit === null && $size >= 1 << 20) || $unit === "MB") { - return number_format($size / (1 << 20), 2) . "MB"; + + if (($unit === null && $size >= 1 << 20) || $unit === 'MB') { + return number_format($size / (1 << 20), 2) . 'MB'; } - if (($unit === null && $size >= 1 << 10) || $unit === "KB") { - return number_format($size / (1 << 10), 2) . "KB"; + + if (($unit === null && $size >= 1 << 10) || $unit === 'KB') { + return number_format($size / (1 << 10), 2) . 'KB'; } - return number_format($size) . " bytes"; + + return number_format($size) . ' bytes'; } /** * Normalizes zip path. * * @param string $path Zip path + * * @return string */ public static function normalizeZipPath($path) @@ -237,7 +268,7 @@ class FilesUtil return implode( '/', array_filter( - explode('/', (string)$path), + explode('/', (string) $path), static function ($part) { return $part !== '.' && $part !== '..'; } diff --git a/src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php b/src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php index 40e8fe0..c13734e 100644 --- a/src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php +++ b/src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php @@ -13,7 +13,7 @@ use PhpZip\Util\StringUtil; class IgnoreFilesFilterIterator extends \FilterIterator { /** - * Ignore list files + * Ignore list files. * * @var array */ @@ -21,7 +21,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator /** * @param \Iterator $iterator - * @param array $ignoreFiles + * @param array $ignoreFiles */ public function __construct(\Iterator $iterator, array $ignoreFiles) { @@ -30,9 +30,12 @@ class IgnoreFilesFilterIterator extends \FilterIterator } /** - * Check whether the current element of the iterator is acceptable - * @link http://php.net/manual/en/filteriterator.accept.php - * @return bool true if the current element is acceptable, otherwise false. + * Check whether the current element of the iterator is acceptable. + * + * @see http://php.net/manual/en/filteriterator.accept.php + * + * @return bool true if the current element is acceptable, otherwise false + * * @since 5.1.0 */ public function accept() @@ -42,6 +45,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator */ $fileInfo = $this->current(); $pathname = str_replace('\\', '/', $fileInfo->getPathname()); + foreach ($this->ignoreFiles as $ignoreFile) { // handler dir and sub dir if ($fileInfo->isDir() @@ -56,6 +60,7 @@ class IgnoreFilesFilterIterator extends \FilterIterator return false; } } + return true; } } diff --git a/src/PhpZip/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php b/src/PhpZip/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php index da6670f..580805b 100644 --- a/src/PhpZip/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php +++ b/src/PhpZip/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php @@ -13,7 +13,7 @@ use PhpZip\Util\StringUtil; class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator { /** - * Ignore list files + * Ignore list files. * * @var array */ @@ -21,7 +21,7 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator /** * @param \RecursiveIterator $iterator - * @param array $ignoreFiles + * @param array $ignoreFiles */ public function __construct(\RecursiveIterator $iterator, array $ignoreFiles) { @@ -30,9 +30,12 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator } /** - * Check whether the current element of the iterator is acceptable - * @link http://php.net/manual/en/filteriterator.accept.php - * @return bool true if the current element is acceptable, otherwise false. + * Check whether the current element of the iterator is acceptable. + * + * @see http://php.net/manual/en/filteriterator.accept.php + * + * @return bool true if the current element is acceptable, otherwise false + * * @since 5.1.0 */ public function accept() @@ -42,10 +45,11 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator */ $fileInfo = $this->current(); $pathname = str_replace('\\', '/', $fileInfo->getPathname()); + foreach ($this->ignoreFiles as $ignoreFile) { // handler dir and sub dir if ($fileInfo->isDir() - && $ignoreFile[strlen($ignoreFile) - 1] === '/' + && $ignoreFile[\strlen($ignoreFile) - 1] === '/' && StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1)) ) { return false; @@ -56,15 +60,16 @@ class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator return false; } } + return true; } /** * @return IgnoreFilesRecursiveFilterIterator + * @noinspection PhpMissingParentCallCommonInspection */ public function getChildren() { - /** @noinspection PhpUndefinedMethodInspection */ return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles); } } diff --git a/src/PhpZip/Util/PackUtil.php b/src/PhpZip/Util/PackUtil.php index 5b3be4e..bd50ee2 100644 --- a/src/PhpZip/Util/PackUtil.php +++ b/src/PhpZip/Util/PackUtil.php @@ -3,22 +3,24 @@ namespace PhpZip\Util; /** - * Pack util + * Pack util. * * @author Ne-Lexa alexey@nelexa.ru * @license MIT + * + * @internal */ -class PackUtil +final class PackUtil { - /** * @param int|string $longValue + * * @return string */ public static function packLongLE($longValue) { - if (PHP_INT_SIZE === 8 && PHP_VERSION_ID >= 506030) { - return pack("P", $longValue); + if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) { + return pack('P', $longValue); } $left = 0xffffffff00000000; @@ -32,31 +34,36 @@ class PackUtil /** * @param string|int $value + * * @return int */ public static function unpackLongLE($value) { - if (PHP_INT_SIZE === 8 && PHP_VERSION_ID >= 506030) { + if (\PHP_INT_SIZE === 8 && \PHP_VERSION_ID >= 506030) { return unpack('P', $value)[1]; } $unpack = unpack('Va/Vb', $value); + return $unpack['a'] + ($unpack['b'] << 32); } /** - * Cast to signed int 32-bit + * Cast to signed int 32-bit. * * @param int $int + * * @return int */ public static function toSignedInt32($int) { - if (PHP_INT_SIZE === 8) { - $int = $int & 0xffffffff; + if (\PHP_INT_SIZE === 8) { + $int &= 0xffffffff; + if ($int & 0x80000000) { return $int - 0x100000000; } } + return $int; } } diff --git a/src/PhpZip/Util/StringUtil.php b/src/PhpZip/Util/StringUtil.php index b579eac..8b067db 100644 --- a/src/PhpZip/Util/StringUtil.php +++ b/src/PhpZip/Util/StringUtil.php @@ -3,54 +3,32 @@ namespace PhpZip\Util; /** - * String Util + * String Util. + * + * @internal */ -class StringUtil +final class StringUtil { - /** * @param string $haystack * @param string $needle + * * @return bool */ public static function startsWith($haystack, $needle) { - return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false; + return $needle === '' || strrpos($haystack, $needle, -\strlen($haystack)) !== false; } /** * @param string $haystack * @param string $needle + * * @return bool */ public static function endsWith($haystack, $needle) { - return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 + return $needle === '' || (($temp = \strlen($haystack) - \strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== false); } - - /** - * @param string $str - * @return string - */ - public static function cp866toUtf8($str) - { - if (function_exists('iconv')) { - /** @noinspection PhpComposerExtensionStubsInspection */ - return iconv('CP866', 'UTF-8//IGNORE', $str); - } elseif (function_exists('mb_convert_encoding')) { - /** @noinspection PhpComposerExtensionStubsInspection */ - return mb_convert_encoding($str, 'UTF-8', 'CP866'); - } elseif (class_exists('UConverter')) { - /** @noinspection PhpComposerExtensionStubsInspection */ - $converter = new \UConverter('UTF-8', 'CP866'); - return $converter->convert($str, false); - } else { - static $cp866Utf8Pairs; - if (empty($cp866Utf8Pairs)) { - $cp866Utf8Pairs = require __DIR__ . '/encodings/cp866-utf8.php'; - } - return strtr($str, $cp866Utf8Pairs); - } - } } diff --git a/src/PhpZip/Util/encodings/cp866-utf8.php b/src/PhpZip/Util/encodings/cp866-utf8.php deleted file mode 100644 index b61d753..0000000 Binary files a/src/PhpZip/Util/encodings/cp866-utf8.php and /dev/null differ diff --git a/src/PhpZip/ZipFile.php b/src/PhpZip/ZipFile.php index 4c1d97e..a91e013 100644 --- a/src/PhpZip/ZipFile.php +++ b/src/PhpZip/ZipFile.php @@ -1,5 +1,7 @@ 'application/zip', 'apk' => 'application/vnd.android.package-archive', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'jar' => 'application/java-archive', - 'epub' => 'application/epub+zip' + 'epub' => 'application/epub+zip', ]; - /** - * @var ZipInputStreamInterface Input seekable input stream. - */ + /** @var ZipInputStreamInterface input seekable input stream */ protected $inputStream; - /** - * @var ZipModel - */ + + /** @var ZipModel */ protected $zipModel; /** @@ -83,21 +77,25 @@ class ZipFile implements ZipFileInterface } /** - * Open zip archive from file + * Open zip archive from file. * * @param string $filename + * + * @throws ZipException if can't open file + * * @return ZipFileInterface - * @throws ZipException if can't open file. */ public function openFile($filename) { if (!file_exists($filename)) { - throw new ZipException("File $filename does not exist."); + throw new ZipException("File {$filename} does not exist."); } + if (!($handle = @fopen($filename, 'rb'))) { - throw new ZipException("File $filename can't open."); + throw new ZipException("File {$filename} can't open."); } $this->openFromStream($handle); + return $this; } @@ -105,61 +103,71 @@ class ZipFile implements ZipFileInterface * Open zip archive from raw string data. * * @param string $data + * + * @throws ZipException if can't open temp stream + * * @return ZipFileInterface - * @throws ZipException if can't open temp stream. */ public function openFromString($data) { - if ($data === null || strlen($data) === 0) { - throw new InvalidArgumentException("Empty string passed"); + if ($data === null || $data === '') { + throw new InvalidArgumentException('Empty string passed'); } + if (!($handle = fopen('php://temp', 'r+b'))) { throw new ZipException("Can't open temp stream."); } fwrite($handle, $data); rewind($handle); $this->openFromStream($handle); + return $this; } /** - * Open zip archive from stream resource + * Open zip archive from stream resource. * * @param resource $handle - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function openFromStream($handle) { - if (!is_resource($handle)) { - throw new InvalidArgumentException("Invalid stream resource."); + if (!\is_resource($handle)) { + throw new InvalidArgumentException('Invalid stream resource.'); } $type = get_resource_type($handle); + if ($type !== 'stream') { - throw new InvalidArgumentException("Invalid resource type - $type."); + throw new InvalidArgumentException("Invalid resource type - {$type}."); } $meta = stream_get_meta_data($handle); + if ($meta['stream_type'] === 'dir') { throw new InvalidArgumentException("Invalid stream type - {$meta['stream_type']}."); } + if (!$meta['seekable']) { - throw new InvalidArgumentException("Resource cannot seekable stream."); + throw new InvalidArgumentException('Resource cannot seekable stream.'); } $this->inputStream = new ZipInputStream($handle); $this->zipModel = $this->inputStream->readZip(); + return $this; } /** - * @return string[] Returns the list files. + * @return string[] returns the list files */ public function getListFiles() { - return array_keys($this->zipModel->getEntries()); + return array_map('strval', array_keys($this->zipModel->getEntries())); } /** - * @return int Returns the number of entries in this ZIP file. + * @return int returns the number of entries in this ZIP file */ public function count() { @@ -169,7 +177,7 @@ class ZipFile implements ZipFileInterface /** * Returns the file comment. * - * @return string The file comment. + * @return string|null the file comment */ public function getArchiveComment() { @@ -179,12 +187,14 @@ class ZipFile implements ZipFileInterface /** * Set archive comment. * - * @param null|string $comment + * @param string|null $comment + * * @return ZipFileInterface */ public function setArchiveComment($comment = null) { $this->zipModel->setArchiveComment($comment); + return $this; } @@ -194,8 +204,10 @@ class ZipFile implements ZipFileInterface * (i.e. end with '/'). * * @param string $entryName - * @return bool + * * @throws ZipEntryNotFoundException + * + * @return bool */ public function isDirectory($entryName) { @@ -206,8 +218,10 @@ class ZipFile implements ZipFileInterface * Returns entry comment. * * @param string $entryName - * @return string + * * @throws ZipEntryNotFoundException + * + * @return string */ public function getEntryComment($entryName) { @@ -217,15 +231,18 @@ class ZipFile implements ZipFileInterface /** * Set entry comment. * - * @param string $entryName + * @param string $entryName * @param string|null $comment - * @return ZipFileInterface + * * @throws ZipException * @throws ZipEntryNotFoundException + * + * @return ZipFileInterface */ public function setEntryComment($entryName, $comment = null) { $this->zipModel->getEntryForChanges($entryName)->setComment($comment); + return $this; } @@ -233,8 +250,10 @@ class ZipFile implements ZipFileInterface * Returns the entry contents. * * @param string $entryName - * @return string + * * @throws ZipException + * + * @return string */ public function getEntryContents($entryName) { @@ -245,6 +264,7 @@ class ZipFile implements ZipFileInterface * Checks if there is an entry in the archive. * * @param string $entryName + * * @return bool */ public function hasEntry($entryName) @@ -256,9 +276,11 @@ class ZipFile implements ZipFileInterface * Get info by entry. * * @param string|ZipEntry $entryName - * @return ZipInfo + * * @throws ZipEntryNotFoundException * @throws ZipException + * + * @return ZipInfo */ public function getEntryInfo($entryName) { @@ -284,24 +306,28 @@ class ZipFile implements ZipFileInterface } /** - * Extract the archive contents + * Extract the archive contents. * * Extract the complete archive or the given files to the specified destination. * - * @param string $destination Location where to extract the files. - * @param array|string|null $entries The entries to extract. It accepts either - * a single entry name or an array of names. - * @return ZipFileInterface + * @param string $destination location where to extract the files + * @param array|string|null $entries The entries to extract. It accepts either + * a single entry name or an array of names. + * * @throws ZipException + * + * @return ZipFileInterface */ public function extractTo($destination, $entries = null) { if (!file_exists($destination)) { throw new ZipException(sprintf('Destination %s not found', $destination)); } + if (!is_dir($destination)) { throw new ZipException('Destination is not directory'); } + if (!is_writable($destination)) { throw new ZipException('Destination is not writable directory'); } @@ -309,21 +335,26 @@ class ZipFile implements ZipFileInterface $zipEntries = $this->zipModel->getEntries(); if (!empty($entries)) { - if (is_string($entries)) { - $entries = (array)$entries; + if (\is_string($entries)) { + $entries = (array) $entries; } - if (is_array($entries)) { + + if (\is_array($entries)) { $entries = array_unique($entries); $flipEntries = array_flip($entries); - $zipEntries = array_filter($zipEntries, static function (ZipEntry $zipEntry) use ($flipEntries) { - return isset($flipEntries[$zipEntry->getName()]); - }); + $zipEntries = array_filter( + $zipEntries, + static function (ZipEntry $zipEntry) use ($flipEntries) { + return isset($flipEntries[$zipEntry->getName()]); + } + ); } } foreach ($zipEntries as $entry) { $entryName = FilesUtil::normalizeZipPath($entry->getName()); - $file = $destination . DIRECTORY_SEPARATOR . $entryName; + $file = $destination . \DIRECTORY_SEPARATOR . $entryName; + if ($entry->isDirectory()) { if (!is_dir($file)) { if (!mkdir($file, 0755, true) && !is_dir($file)) { @@ -332,9 +363,11 @@ class ZipFile implements ZipFileInterface chmod($file, 0755); touch($file, $entry->getTime()); } + continue; } - $dir = dirname($file); + $dir = \dirname($file); + if (!is_dir($dir)) { if (!mkdir($dir, 0755, true) && !is_dir($dir)) { throw new ZipException('Can not create dir ' . $dir); @@ -342,49 +375,57 @@ class ZipFile implements ZipFileInterface chmod($dir, 0755); touch($dir, $entry->getTime()); } + if (file_put_contents($file, $entry->getEntryContent()) === false) { throw new ZipException('Can not extract file ' . $entry->getName()); } touch($file, $entry->getTime()); } + return $this; } /** * Add entry from the string. * - * @param string $localName Zip entry name. - * @param string $contents String contents. + * @param string $localName zip entry name + * @param string $contents string contents * @param int|null $compressionMethod Compression method. - * Use {@see ZipFile::METHOD_STORED}, {@see ZipFile::METHOD_DEFLATED} or {@see ZipFile::METHOD_BZIP2}. - * If null, then auto choosing method. - * @return ZipFileInterface + * Use {@see ZipFile::METHOD_STORED}, {@see ZipFile::METHOD_DEFLATED} or + * {@see ZipFile::METHOD_BZIP2}. If null, then auto choosing method. + * * @throws ZipException - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @return ZipFileInterface + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFromString($localName, $contents, $compressionMethod = null) { if ($contents === null) { - throw new InvalidArgumentException("Contents is null"); + throw new InvalidArgumentException('Contents is null'); } + if ($localName === null) { - throw new InvalidArgumentException("Entry name is null"); + throw new InvalidArgumentException('Entry name is null'); } - $localName = ltrim((string)$localName, "\\/"); - if (strlen($localName) === 0) { - throw new InvalidArgumentException("Empty entry name"); + $localName = ltrim((string) $localName, '\\/'); + + if ($localName === '') { + throw new InvalidArgumentException('Empty entry name'); } - $contents = (string)$contents; - $length = strlen($contents); + $contents = (string) $contents; + $length = \strlen($contents); + if ($compressionMethod === null) { if ($length >= 512) { $compressionMethod = ZipEntry::UNKNOWN; } else { $compressionMethod = self::METHOD_STORED; } - } elseif (!in_array($compressionMethod, self::$allowCompressionMethods, true)) { + } elseif (!\in_array($compressionMethod, self::$allowCompressionMethods, true)) { throw new ZipUnsupportMethodException('Unsupported compression method ' . $compressionMethod); } $externalAttributes = 0100644 << 16; @@ -396,32 +437,37 @@ class ZipFile implements ZipFileInterface $entry->setExternalAttributes($externalAttributes); $this->zipModel->addEntry($entry); + return $this; } /** * Add entry from the file. * - * @param string $filename Destination file. - * @param string|null $localName Zip Entry name. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param string $filename destination file + * @param string|null $localName zip Entry name + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @return ZipFileInterface + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFile($filename, $localName = null, $compressionMethod = null) { $entry = new ZipNewFileEntry($filename); if ($compressionMethod === null) { - if (function_exists('mime_content_type')) { + if (\function_exists('mime_content_type')) { /** @noinspection PhpComposerExtensionStubsInspection */ $mimeType = @mime_content_type($filename); $type = strtok($mimeType, '/'); + if ($type === 'image') { $compressionMethod = self::METHOD_STORED; } elseif ($type === 'text' && filesize($filename) < 150) { @@ -434,16 +480,17 @@ class ZipFile implements ZipFileInterface } else { $compressionMethod = self::METHOD_STORED; } - } elseif (!in_array($compressionMethod, self::$allowCompressionMethods, true)) { + } elseif (!\in_array($compressionMethod, self::$allowCompressionMethods, true)) { throw new ZipUnsupportMethodException('Unsupported compression method ' . $compressionMethod); } if ($localName === null) { $localName = basename($filename); } - $localName = ltrim((string)$localName, "\\/"); - if (strlen($localName) === 0) { - throw new InvalidArgumentException("Empty entry name"); + $localName = ltrim((string) $localName, '\\/'); + + if ($localName === '') { + throw new InvalidArgumentException('Empty entry name'); } $stat = stat($filename); @@ -456,48 +503,62 @@ class ZipFile implements ZipFileInterface $entry->setExternalAttributes($externalAttributes); $this->zipModel->addEntry($entry); + return $this; } /** * Add entry from the stream. * - * @param resource $stream Stream resource. - * @param string $localName Zip Entry name. + * @param resource $stream stream resource + * @param string $localName zip Entry name * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. + * If null, then auto choosing method. + * * @throws ZipException - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @return ZipFileInterface + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFromStream($stream, $localName, $compressionMethod = null) { - if (!is_resource($stream)) { - throw new InvalidArgumentException("Stream is not resource"); + if (!\is_resource($stream)) { + throw new InvalidArgumentException('Stream is not resource'); } + if ($localName === null) { - throw new InvalidArgumentException("Entry name is null"); + throw new InvalidArgumentException('Entry name is null'); } - $localName = ltrim((string)$localName, "\\/"); - if (strlen($localName) === 0) { - throw new InvalidArgumentException("Empty entry name"); + $localName = ltrim((string) $localName, '\\/'); + + if ($localName === '') { + throw new InvalidArgumentException('Empty entry name'); } $fstat = fstat($stream); - $length = $fstat['size']; - if ($compressionMethod === null) { - if ($length >= 512) { - $compressionMethod = ZipEntry::UNKNOWN; - } else { - $compressionMethod = self::METHOD_STORED; + + if ($fstat !== false) { + $mode = sprintf('%o', $fstat['mode']); + $length = $fstat['size']; + + if ($compressionMethod === null) { + if ($length >= 512) { + $compressionMethod = ZipEntry::UNKNOWN; + } else { + $compressionMethod = self::METHOD_STORED; + } } - } elseif (!in_array($compressionMethod, self::$allowCompressionMethods, true)) { + } else { + $mode = 010644; + } + + if ($compressionMethod !== null && !\in_array($compressionMethod, self::$allowCompressionMethods, true)) { throw new ZipUnsupportMethodException('Unsupported method ' . $compressionMethod); } - $mode = sprintf('%o', $fstat['mode']); $externalAttributes = (octdec($mode) & 0xffff) << 16; $entry = new ZipNewEntry($stream); @@ -507,6 +568,7 @@ class ZipFile implements ZipFileInterface $entry->setExternalAttributes($externalAttributes); $this->zipModel->addEntry($entry); + return $this; } @@ -514,17 +576,20 @@ class ZipFile implements ZipFileInterface * Add an empty directory in the zip archive. * * @param string $dirName - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function addEmptyDir($dirName) { if ($dirName === null) { - throw new InvalidArgumentException("Dir name is null"); + throw new InvalidArgumentException('Dir name is null'); } - $dirName = ltrim((string)$dirName, "\\/"); - if (strlen($dirName) === 0) { - throw new InvalidArgumentException("Empty dir name"); + $dirName = ltrim((string) $dirName, '\\/'); + + if ($dirName === '') { + throw new InvalidArgumentException('Empty dir name'); } $dirName = rtrim($dirName, '\\/') . '/'; $externalAttributes = 040755 << 16; @@ -539,94 +604,110 @@ class ZipFile implements ZipFileInterface $entry->setExternalAttributes($externalAttributes); $this->zipModel->addEntry($entry); + return $this; } /** * Add directory not recursively to the zip archive. * - * @param string $inputDir Input directory - * @param string $localPath Add files to this directory, or the root. + * @param string $inputDir Input directory + * @param string $localPath add files to this directory, or the root * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. + * If null, then auto choosing method. + * * @throws ZipException + * + * @return ZipFileInterface */ - public function addDir($inputDir, $localPath = "/", $compressionMethod = null) + public function addDir($inputDir, $localPath = '/', $compressionMethod = null) { if ($inputDir === null) { throw new InvalidArgumentException('Input dir is null'); } - $inputDir = (string)$inputDir; - if (strlen($inputDir) === 0) { + $inputDir = (string) $inputDir; + + if ($inputDir === '') { throw new InvalidArgumentException('The input directory is not specified'); } + if (!is_dir($inputDir)) { throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); } - $inputDir = rtrim($inputDir, '/\\') . DIRECTORY_SEPARATOR; + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; $directoryIterator = new \DirectoryIterator($inputDir); + return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod); } /** * Add recursive directory to the zip archive. * - * @param string $inputDir Input directory - * @param string $localPath Add files to this directory, or the root. + * @param string $inputDir Input directory + * @param string $localPath add files to this directory, or the root * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. + * If null, then auto choosing method. + * * @throws ZipException - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @return ZipFileInterface + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ - public function addDirRecursive($inputDir, $localPath = "/", $compressionMethod = null) + public function addDirRecursive($inputDir, $localPath = '/', $compressionMethod = null) { if ($inputDir === null) { throw new InvalidArgumentException('Input dir is null'); } - $inputDir = (string)$inputDir; - if (strlen($inputDir) === 0) { + $inputDir = (string) $inputDir; + + if ($inputDir === '') { throw new InvalidArgumentException('The input directory is not specified'); } + if (!is_dir($inputDir)) { throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); } - $inputDir = rtrim($inputDir, '/\\') . DIRECTORY_SEPARATOR; + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; $directoryIterator = new \RecursiveDirectoryIterator($inputDir); + return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod); } /** * Add directories from directory iterator. * - * @param \Iterator $iterator Directory iterator. - * @param string $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param \Iterator $iterator directory iterator + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @return ZipFileInterface + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFilesFromIterator( \Iterator $iterator, $localPath = '/', $compressionMethod = null ) { - $localPath = (string)$localPath; - if (strlen($localPath) !== 0) { + $localPath = (string) $localPath; + + if ($localPath !== '') { $localPath = trim($localPath, '\\/'); } else { - $localPath = ""; + $localPath = ''; } $iterator = $iterator instanceof \RecursiveIterator ? @@ -634,50 +715,49 @@ class ZipFile implements ZipFileInterface new \IteratorIterator($iterator); /** * @var string[] $files - * @var string $path + * @var string $path */ $files = []; + foreach ($iterator as $file) { if ($file instanceof \SplFileInfo) { if ($file->getBasename() === '..') { continue; } + if ($file->getBasename() === '.') { - $files[] = dirname($file->getPathname()); + $files[] = \dirname($file->getPathname()); } else { $files[] = $file->getPathname(); } } } + if (empty($files)) { return $this; } natcasesort($files); $path = array_shift($files); - foreach ($files as $file) { - $relativePath = str_replace($path, $localPath, $file); - $relativePath = ltrim($relativePath, '\\/'); - if (is_dir($file) && FilesUtil::isEmptyDir($file)) { - $this->addEmptyDir($relativePath); - } elseif (is_file($file)) { - $this->addFile($file, $relativePath, $compressionMethod); - } - } + + $this->doAddFiles($path, $files, $localPath, $compressionMethod); + return $this; } /** * Add files from glob pattern. * - * @param string $inputDir Input directory - * @param string $globPattern Glob pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException + * + * @return ZipFileInterface * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $compressionMethod = null) @@ -688,15 +768,17 @@ class ZipFile implements ZipFileInterface /** * Add files from glob pattern. * - * @param string $inputDir Input directory - * @param string $globPattern Glob pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param bool $recursive Recursive search. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string|null $localPath add files to this directory, or the root + * @param bool $recursive recursive search + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException + * + * @return ZipFileInterface * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ private function addGlob( @@ -709,57 +791,48 @@ class ZipFile implements ZipFileInterface if ($inputDir === null) { throw new InvalidArgumentException('Input dir is null'); } - $inputDir = (string)$inputDir; - if (strlen($inputDir) === 0) { + $inputDir = (string) $inputDir; + + if ($inputDir === '') { throw new InvalidArgumentException('The input directory is not specified'); } + if (!is_dir($inputDir)) { throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); } - $globPattern = (string)$globPattern; + $globPattern = (string) $globPattern; + if (empty($globPattern)) { throw new InvalidArgumentException('The glob pattern is not specified'); } - $inputDir = rtrim($inputDir, '/\\') . DIRECTORY_SEPARATOR; + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; $globPattern = $inputDir . $globPattern; - $filesFound = FilesUtil::globFileSearch($globPattern, GLOB_BRACE, $recursive); + $filesFound = FilesUtil::globFileSearch($globPattern, \GLOB_BRACE, $recursive); + if ($filesFound === false || empty($filesFound)) { return $this; } - if (!empty($localPath) && is_string($localPath)) { - $localPath = trim($localPath, '/\\') . '/'; - } else { - $localPath = "/"; - } - /** - * @var string $file - */ - foreach ($filesFound as $file) { - $filename = str_replace($inputDir, $localPath, $file); - $filename = ltrim($filename, '\\/'); - if (is_dir($file) && FilesUtil::isEmptyDir($file)) { - $this->addEmptyDir($filename); - } elseif (is_file($file)) { - $this->addFile($file, $filename, $compressionMethod); - } - } + $this->doAddFiles($inputDir, $filesFound, $localPath, $compressionMethod); + return $this; } /** * Add files recursively from glob pattern. * - * @param string $inputDir Input directory - * @param string $globPattern Glob pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException + * + * @return ZipFileInterface * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ public function addFilesFromGlobRecursive($inputDir, $globPattern, $localPath = '/', $compressionMethod = null) @@ -770,17 +843,20 @@ class ZipFile implements ZipFileInterface /** * Add files from regex pattern. * - * @param string $inputDir Search files in this directory. - * @param string $regexPattern Regex pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException - * @internal param bool $recursive Recursive search. + * + * @return ZipFileInterface + * + * @internal param bool $recursive Recursive search */ - public function addFilesFromRegex($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null) + public function addFilesFromRegex($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null) { return $this->addRegex($inputDir, $regexPattern, $localPath, false, $compressionMethod); } @@ -788,76 +864,102 @@ class ZipFile implements ZipFileInterface /** * Add files from regex pattern. * - * @param string $inputDir Search files in this directory. - * @param string $regexPattern Regex pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param bool $recursive Recursive search. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string|null $localPath add files to this directory, or the root + * @param bool $recursive recursive search + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException + * + * @return ZipFileInterface */ private function addRegex( $inputDir, $regexPattern, - $localPath = "/", + $localPath = '/', $recursive = true, $compressionMethod = null ) { - $regexPattern = (string)$regexPattern; + $regexPattern = (string) $regexPattern; + if (empty($regexPattern)) { throw new InvalidArgumentException('The regex pattern is not specified'); } - $inputDir = (string)$inputDir; - if (strlen($inputDir) === 0) { + $inputDir = (string) $inputDir; + + if ($inputDir === '') { throw new InvalidArgumentException('The input directory is not specified'); } + if (!is_dir($inputDir)) { throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); } - $inputDir = rtrim($inputDir, '/\\') . DIRECTORY_SEPARATOR; + $inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; $files = FilesUtil::regexFileSearch($inputDir, $regexPattern, $recursive); + if (empty($files)) { return $this; } - if (!empty($localPath) && is_string($localPath)) { - $localPath = trim($localPath, '\\/') . '/'; + + $this->doAddFiles($inputDir, $files, $localPath, $compressionMethod); + + return $this; + } + + /** + * @param string $fileSystemDir + * @param array $files + * @param string $zipPath + * @param int|null $compressionMethod + * + * @throws ZipException + */ + private function doAddFiles($fileSystemDir, array $files, $zipPath, $compressionMethod = null) + { + $fileSystemDir = rtrim($fileSystemDir, '/\\') . \DIRECTORY_SEPARATOR; + + if (!empty($zipPath) && \is_string($zipPath)) { + $zipPath = trim($zipPath, '\\/') . '/'; } else { - $localPath = "/"; + $zipPath = '/'; } - $inputDir = rtrim($inputDir, '/\\') . DIRECTORY_SEPARATOR; /** * @var string $file */ foreach ($files as $file) { - $filename = str_replace($inputDir, $localPath, $file); + $filename = str_replace($fileSystemDir, $zipPath, $file); $filename = ltrim($filename, '\\/'); + if (is_dir($file) && FilesUtil::isEmptyDir($file)) { $this->addEmptyDir($filename); } elseif (is_file($file)) { $this->addFile($file, $filename, $compressionMethod); } } - return $this; } /** * Add files recursively from regex pattern. * - * @param string $inputDir Search files in this directory. - * @param string $regexPattern Regex pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. - * @return ZipFileInterface + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @throws ZipException - * @internal param bool $recursive Recursive search. + * + * @return ZipFileInterface + * + * @internal param bool $recursive Recursive search */ - public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null) + public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null) { return $this->addRegex($inputDir, $regexPattern, $localPath, true, $compressionMethod); } @@ -867,7 +969,7 @@ class ZipFile implements ZipFileInterface * Keys is local names. * Values is contents. * - * @param array $mapData Associative array for added to zip. + * @param array $mapData associative array for added to zip */ public function addAll(array $mapData) { @@ -879,37 +981,45 @@ class ZipFile implements ZipFileInterface /** * Rename the entry. * - * @param string $oldName Old entry name. - * @param string $newName New entry name. - * @return ZipFileInterface + * @param string $oldName old entry name + * @param string $newName new entry name + * * @throws ZipException + * + * @return ZipFileInterface */ public function rename($oldName, $newName) { if ($oldName === null || $newName === null) { - throw new InvalidArgumentException("name is null"); + throw new InvalidArgumentException('name is null'); } - $oldName = ltrim((string)$oldName, '\\/'); - $newName = ltrim((string)$newName, '\\/'); + $oldName = ltrim((string) $oldName, '\\/'); + $newName = ltrim((string) $newName, '\\/'); + if ($oldName !== $newName) { $this->zipModel->renameEntry($oldName, $newName); } + return $this; } /** * Delete entry by name. * - * @param string $entryName Zip Entry name. + * @param string $entryName zip Entry name + * + * @throws ZipEntryNotFoundException if entry not found + * * @return ZipFileInterface - * @throws ZipEntryNotFoundException If entry not found. */ public function deleteFromName($entryName) { - $entryName = ltrim((string)$entryName, '\\/'); + $entryName = ltrim((string) $entryName, '\\/'); + if (!$this->zipModel->deleteEntry($entryName)) { throw new ZipEntryNotFoundException($entryName); } + return $this; } @@ -917,16 +1027,18 @@ class ZipFile implements ZipFileInterface * Delete entries by glob pattern. * * @param string $globPattern Glob pattern + * * @return ZipFileInterface * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ public function deleteFromGlob($globPattern) { - if ($globPattern === null || !is_string($globPattern) || empty($globPattern)) { - throw new InvalidArgumentException("The glob pattern is not specified"); + if ($globPattern === null || !\is_string($globPattern) || empty($globPattern)) { + throw new InvalidArgumentException('The glob pattern is not specified'); } $globPattern = '~' . FilesUtil::convertGlobToRegEx($globPattern) . '~si'; $this->deleteFromRegex($globPattern); + return $this; } @@ -934,24 +1046,28 @@ class ZipFile implements ZipFileInterface * Delete entries by regex pattern. * * @param string $regexPattern Regex pattern + * * @return ZipFileInterface */ public function deleteFromRegex($regexPattern) { - if ($regexPattern === null || !is_string($regexPattern) || empty($regexPattern)) { - throw new InvalidArgumentException("The regex pattern is not specified"); + if ($regexPattern === null || !\is_string($regexPattern) || empty($regexPattern)) { + throw new InvalidArgumentException('The regex pattern is not specified'); } $this->matcher()->match($regexPattern)->delete(); + return $this; } /** - * Delete all entries + * Delete all entries. + * * @return ZipFileInterface */ public function deleteAll() { $this->zipModel->deleteAll(); + return $this; } @@ -959,74 +1075,94 @@ class ZipFile implements ZipFileInterface * Set compression level for new entries. * * @param int $compressionLevel + * * @return ZipFileInterface - * @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION - * @see ZipFileInterface::LEVEL_SUPER_FAST - * @see ZipFileInterface::LEVEL_FAST - * @see ZipFileInterface::LEVEL_BEST_COMPRESSION + * + * @see ZipFile::LEVEL_DEFAULT_COMPRESSION + * @see ZipFile::LEVEL_SUPER_FAST + * @see ZipFile::LEVEL_FAST + * @see ZipFile::LEVEL_BEST_COMPRESSION */ public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION) { if ($compressionLevel < self::LEVEL_DEFAULT_COMPRESSION || $compressionLevel > self::LEVEL_BEST_COMPRESSION ) { - throw new InvalidArgumentException('Invalid compression level. Minimum level ' . - self::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . self::LEVEL_BEST_COMPRESSION); + throw new InvalidArgumentException( + 'Invalid compression level. Minimum level ' . + self::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . self::LEVEL_BEST_COMPRESSION + ); } - $this->matcher()->all()->invoke(function ($entry) use ($compressionLevel) { - $this->setCompressionLevelEntry($entry, $compressionLevel); - }); + $this->matcher()->all()->invoke( + function ($entry) use ($compressionLevel) { + $this->setCompressionLevelEntry($entry, $compressionLevel); + } + ); + return $this; } /** * @param string $entryName - * @param int $compressionLevel - * @return ZipFileInterface + * @param int $compressionLevel + * * @throws ZipException - * @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION - * @see ZipFileInterface::LEVEL_SUPER_FAST - * @see ZipFileInterface::LEVEL_FAST - * @see ZipFileInterface::LEVEL_BEST_COMPRESSION + * + * @return ZipFileInterface + * + * @see ZipFile::LEVEL_DEFAULT_COMPRESSION + * @see ZipFile::LEVEL_SUPER_FAST + * @see ZipFile::LEVEL_FAST + * @see ZipFile::LEVEL_BEST_COMPRESSION */ public function setCompressionLevelEntry($entryName, $compressionLevel) { if ($compressionLevel !== null) { - if ($compressionLevel < ZipFileInterface::LEVEL_DEFAULT_COMPRESSION || - $compressionLevel > ZipFileInterface::LEVEL_BEST_COMPRESSION + if ($compressionLevel < self::LEVEL_DEFAULT_COMPRESSION || + $compressionLevel > self::LEVEL_BEST_COMPRESSION ) { - throw new InvalidArgumentException('Invalid compression level. Minimum level ' . - self::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . self::LEVEL_BEST_COMPRESSION); + throw new InvalidArgumentException( + 'Invalid compression level. Minimum level ' . + self::LEVEL_DEFAULT_COMPRESSION . '. Maximum level ' . self::LEVEL_BEST_COMPRESSION + ); } $entry = $this->zipModel->getEntry($entryName); + if ($compressionLevel !== $entry->getCompressionLevel()) { $entry = $this->zipModel->getEntryForChanges($entry); $entry->setCompressionLevel($compressionLevel); } } + return $this; } /** * @param string $entryName - * @param int $compressionMethod - * @return ZipFileInterface + * @param int $compressionMethod + * * @throws ZipException - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @return ZipFileInterface + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function setCompressionMethodEntry($entryName, $compressionMethod) { - if (!in_array($compressionMethod, self::$allowCompressionMethods, true)) { + if (!\in_array($compressionMethod, self::$allowCompressionMethods, true)) { throw new ZipUnsupportMethodException('Unsupported method ' . $compressionMethod); } $entry = $this->zipModel->getEntry($entryName); + if ($compressionMethod !== $entry->getMethod()) { $this->zipModel ->getEntryForChanges($entry) - ->setMethod($compressionMethod); + ->setMethod($compressionMethod) + ; } + return $this; } @@ -1034,12 +1170,15 @@ class ZipFile implements ZipFileInterface * zipalign is optimization to Android application (APK) files. * * @param int|null $align + * * @return ZipFileInterface - * @link https://developer.android.com/studio/command-line/zipalign.html + * + * @see https://developer.android.com/studio/command-line/zipalign.html */ public function setZipAlign($align = null) { $this->zipModel->setZipAlign($align); + return $this; } @@ -1047,9 +1186,12 @@ class ZipFile implements ZipFileInterface * Set password to all input encrypted entries. * * @param string $password Password - * @return ZipFileInterface + * * @throws ZipException - * @deprecated using ZipFileInterface::setReadPassword() + * + * @return ZipFileInterface + * + * @deprecated using ZipFile::setReadPassword() */ public function withReadPassword($password) { @@ -1060,12 +1202,15 @@ class ZipFile implements ZipFileInterface * Set password to all input encrypted entries. * * @param string $password Password - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function setReadPassword($password) { $this->zipModel->setReadPassword($password); + return $this; } @@ -1073,24 +1218,30 @@ class ZipFile implements ZipFileInterface * Set password to concrete input entry. * * @param string $entryName - * @param string $password Password - * @return ZipFileInterface + * @param string $password Password + * * @throws ZipException + * + * @return ZipFileInterface */ public function setReadPasswordEntry($entryName, $password) { $this->zipModel->setReadPasswordEntry($entryName, $password); + return $this; } /** * Set password for all entries for update. * - * @param string $password If password null then encryption clear + * @param string $password If password null then encryption clear * @param int|null $encryptionMethod Encryption method - * @return ZipFileInterface - * @deprecated using ZipFileInterface::setPassword() + * * @throws ZipException + * + * @return ZipFileInterface + * + * @deprecated using ZipFile::setPassword() */ public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256) { @@ -1100,47 +1251,54 @@ class ZipFile implements ZipFileInterface /** * Sets a new password for all files in the archive. * - * @param string $password + * @param string $password * @param int|null $encryptionMethod Encryption method - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256) { $this->zipModel->setWritePassword($password); + if ($encryptionMethod !== null) { - if (!in_array($encryptionMethod, self::$allowEncryptionMethods, true)) { + if (!\in_array($encryptionMethod, self::$allowEncryptionMethods, true)) { throw new ZipException('Invalid encryption method "' . $encryptionMethod . '"'); } $this->zipModel->setEncryptionMethod($encryptionMethod); } + return $this; } /** * Sets a new password of an entry defined by its name. * - * @param string $entryName - * @param string $password + * @param string $entryName + * @param string $password * @param int|null $encryptionMethod - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function setPasswordEntry($entryName, $password, $encryptionMethod = null) { - if ($encryptionMethod !== null) { - if (!in_array($encryptionMethod, self::$allowEncryptionMethods, true)) { - throw new ZipException('Invalid encryption method "' . $encryptionMethod . '"'); - } + if ($encryptionMethod !== null && !\in_array($encryptionMethod, self::$allowEncryptionMethods, true)) { + throw new ZipException('Invalid encryption method "' . $encryptionMethod . '"'); } $this->matcher()->add($entryName)->setPassword($password, $encryptionMethod); + return $this; } /** * Remove password for all entries for update. + * * @return ZipFileInterface - * @deprecated using ZipFileInterface::disableEncryption() + * + * @deprecated using ZipFile::disableEncryption() */ public function withoutPassword() { @@ -1149,42 +1307,51 @@ class ZipFile implements ZipFileInterface /** * Disable encryption for all entries that are already in the archive. + * * @return ZipFileInterface */ public function disableEncryption() { $this->zipModel->removePassword(); + return $this; } /** * Disable encryption of an entry defined by its name. + * * @param string $entryName + * * @return ZipFileInterface */ public function disableEncryptionEntry($entryName) { $this->zipModel->removePasswordEntry($entryName); + return $this; } /** - * Undo all changes done in the archive + * Undo all changes done in the archive. + * * @return ZipFileInterface */ public function unchangeAll() { $this->zipModel->unchangeAll(); + return $this; } /** - * Undo change archive comment + * Undo change archive comment. + * * @return ZipFileInterface */ public function unchangeArchiveComment() { $this->zipModel->unchangeArchiveComment(); + return $this; } @@ -1192,11 +1359,13 @@ class ZipFile implements ZipFileInterface * Revert all changes done to an entry with the given name. * * @param string|ZipEntry $entry Entry name or ZipEntry + * * @return ZipFileInterface */ public function unchangeEntry($entry) { $this->zipModel->unchangeEntry($entry); + return $this; } @@ -1204,16 +1373,19 @@ class ZipFile implements ZipFileInterface * Save as file. * * @param string $filename Output filename - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function saveAsFile($filename) { - $filename = (string)$filename; + $filename = (string) $filename; $tempFilename = $filename . '.temp' . uniqid('', true); + if (!($handle = @fopen($tempFilename, 'w+b'))) { - throw new InvalidArgumentException("File " . $tempFilename . ' can not open from write.'); + throw new InvalidArgumentException('File ' . $tempFilename . ' can not open from write.'); } $this->saveAsStream($handle); @@ -1221,8 +1393,10 @@ class ZipFile implements ZipFileInterface if (is_file($tempFilename)) { unlink($tempFilename); } + throw new ZipException('Can not move ' . $tempFilename . ' to ' . $filename); } + return $this; } @@ -1230,17 +1404,20 @@ class ZipFile implements ZipFileInterface * Save as stream. * * @param resource $handle Output stream resource - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function saveAsStream($handle) { - if (!is_resource($handle)) { + if (!\is_resource($handle)) { throw new InvalidArgumentException('handle is not resource'); } ftruncate($handle, 0); $this->writeZipToStream($handle); fclose($handle); + return $this; } @@ -1248,86 +1425,112 @@ class ZipFile implements ZipFileInterface * Output .ZIP archive as attachment. * Die after output. * - * @param string $outputFilename Output filename - * @param string|null $mimeType Mime-Type - * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * @param string $outputFilename Output filename + * @param string|null $mimeType Mime-Type + * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * * @throws ZipException + * + * @return string */ public function outputAsAttachment($outputFilename, $mimeType = null, $attachment = true) { - $outputFilename = (string)$outputFilename; + $outputFilename = (string) $outputFilename; - if (empty($mimeType) || (!is_string($mimeType) && !empty($outputFilename))) { - $ext = strtolower(pathinfo($outputFilename, PATHINFO_EXTENSION)); - - if (!empty($ext) && isset(self::$defaultMimeTypes[$ext])) { - $mimeType = self::$defaultMimeTypes[$ext]; - } - } - if (empty($mimeType)) { - $mimeType = self::$defaultMimeTypes['zip']; + if ($mimeType === null) { + $mimeType = $this->getMimeTypeByFilename($outputFilename); } - $content = $this->outputAsString(); + if (!($handle = fopen('php://temp', 'w+b'))) { + throw new InvalidArgumentException('php://temp cannot open for write.'); + } + $this->writeZipToStream($handle); $this->close(); + $size = fstat($handle)['size']; + $headerContentDisposition = 'Content-Disposition: ' . ($attachment ? 'attachment' : 'inline'); + if (!empty($outputFilename)) { $headerContentDisposition .= '; filename="' . basename($outputFilename) . '"'; } header($headerContentDisposition); - header("Content-Type: " . $mimeType); - header("Content-Length: " . strlen($content)); - exit($content); + header('Content-Type: ' . $mimeType); + header('Content-Length: ' . $size); + + rewind($handle); + + try { + return stream_get_contents($handle, -1, 0); + } finally { + fclose($handle); + } + } + + /** + * @param string $outputFilename + * + * @return string + */ + protected function getMimeTypeByFilename($outputFilename) + { + $outputFilename = (string) $outputFilename; + $ext = strtolower(pathinfo($outputFilename, \PATHINFO_EXTENSION)); + + if (!empty($ext) && isset(self::$defaultMimeTypes[$ext])) { + return self::$defaultMimeTypes[$ext]; + } + + return self::$defaultMimeTypes['zip']; } /** * Output .ZIP archive as PSR-7 Response. * - * @param ResponseInterface $response Instance PSR-7 Response - * @param string $outputFilename Output filename - * @param string|null $mimeType Mime-Type - * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline - * @return ResponseInterface + * @param ResponseInterface $response Instance PSR-7 Response + * @param string $outputFilename Output filename + * @param string|null $mimeType Mime-Type + * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * * @throws ZipException + * + * @return ResponseInterface */ public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true) { - $outputFilename = (string)$outputFilename; + $outputFilename = (string) $outputFilename; - if (empty($mimeType) || (!is_string($mimeType) && !empty($outputFilename))) { - $ext = strtolower(pathinfo($outputFilename, PATHINFO_EXTENSION)); - - if (!empty($ext) && isset(self::$defaultMimeTypes[$ext])) { - $mimeType = self::$defaultMimeTypes[$ext]; - } - } - if (empty($mimeType)) { - $mimeType = self::$defaultMimeTypes['zip']; + if ($mimeType === null) { + $mimeType = $this->getMimeTypeByFilename($outputFilename); } - if (!($handle = fopen('php://memory', 'w+b'))) { - throw new InvalidArgumentException("Memory can not open from write."); + if (!($handle = fopen('php://temp', 'w+b'))) { + throw new InvalidArgumentException('php://temp cannot open for write.'); } $this->writeZipToStream($handle); + $this->close(); rewind($handle); $contentDispositionValue = ($attachment ? 'attachment' : 'inline'); + if (!empty($outputFilename)) { $contentDispositionValue .= '; filename="' . basename($outputFilename) . '"'; } $stream = new ResponseStream($handle); + return $response ->withHeader('Content-Type', $mimeType) ->withHeader('Content-Disposition', $contentDispositionValue) ->withHeader('Content-Length', $stream->getSize()) - ->withBody($stream); + ->withBody($stream) + ; } /** * @param resource $handle + * * @throws ZipException */ protected function writeZipToStream($handle) @@ -1340,19 +1543,24 @@ class ZipFile implements ZipFileInterface /** * Returns the zip archive as a string. - * @return string + * * @throws ZipException + * + * @return string */ public function outputAsString() { - if (!($handle = fopen('php://memory', 'w+b'))) { - throw new InvalidArgumentException("Memory can not open from write."); + if (!($handle = fopen('php://temp', 'w+b'))) { + throw new InvalidArgumentException('php://temp cannot open for write.'); } $this->writeZipToStream($handle); rewind($handle); - $content = stream_get_contents($handle); - fclose($handle); - return $content; + + try { + return stream_get_contents($handle); + } finally { + fclose($handle); + } } /** @@ -1376,8 +1584,10 @@ class ZipFile implements ZipFileInterface /** * Save and reopen zip archive. - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function rewrite() { @@ -1387,24 +1597,29 @@ class ZipFile implements ZipFileInterface $meta = stream_get_meta_data($this->inputStream->getStream()); $content = $this->outputAsString(); $this->close(); + if ($meta['wrapper_type'] === 'plainfile') { /** * @var resource $uri */ $uri = $meta['uri']; + if (file_put_contents($uri, $content) === false) { - throw new ZipException("Can not overwrite the zip file in the $uri file."); + throw new ZipException("Can not overwrite the zip file in the {$uri} file."); } + if (!($handle = @fopen($uri, 'rb'))) { - throw new ZipException("File $uri can't open."); + throw new ZipException("File {$uri} can't open."); } + return $this->openFromStream($handle); } + return $this->openFromString($content); } /** - * Release all resources + * Release all resources. */ public function __destruct() { @@ -1412,11 +1627,15 @@ class ZipFile implements ZipFileInterface } /** - * Offset to set - * @link http://php.net/manual/en/arrayaccess.offsetset.php - * @param string $entryName The offset to assign the value to. - * @param mixed $contents The value to set. + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param string $entryName the offset to assign the value to + * @param mixed $contents the value to set + * * @throws ZipException + * * @see ZipFile::addFromString * @see ZipFile::addEmptyDir * @see ZipFile::addFile @@ -1427,31 +1646,39 @@ class ZipFile implements ZipFileInterface if ($entryName === null) { throw new InvalidArgumentException('entryName is null'); } - $entryName = ltrim((string)$entryName, "\\/"); - if (strlen($entryName) === 0) { + $entryName = ltrim((string) $entryName, '\\/'); + + if ($entryName === '') { throw new InvalidArgumentException('entryName is empty'); } + if ($contents instanceof \SplFileInfo) { if ($contents instanceof \DirectoryIterator) { $this->addFilesFromIterator($contents, $entryName); + return; } $this->addFile($contents->getPathname(), $entryName); + return; } + if (StringUtil::endsWith($entryName, '/')) { $this->addEmptyDir($entryName); - } elseif (is_resource($contents)) { + } elseif (\is_resource($contents)) { $this->addFromStream($contents, $entryName); } else { - $this->addFromString($entryName, (string)$contents); + $this->addFromString($entryName, (string) $contents); } } /** - * Offset to unset - * @link http://php.net/manual/en/arrayaccess.offsetunset.php - * @param string $entryName The offset to unset. + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param string $entryName the offset to unset + * * @throws ZipEntryNotFoundException */ public function offsetUnset($entryName) @@ -1460,11 +1687,15 @@ class ZipFile implements ZipFileInterface } /** - * Return the current element - * @link http://php.net/manual/en/iterator.current.php - * @return mixed Can return any type. - * @since 5.0.0 + * Return the current element. + * + * @see http://php.net/manual/en/iterator.current.php + * * @throws ZipException + * + * @return mixed can return any type + * + * @since 5.0.0 */ public function current() { @@ -1472,11 +1703,15 @@ class ZipFile implements ZipFileInterface } /** - * Offset to retrieve - * @link http://php.net/manual/en/arrayaccess.offsetget.php - * @param string $entryName The offset to retrieve. - * @return string|null + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param string $entryName the offset to retrieve + * * @throws ZipException + * + * @return string|null */ public function offsetGet($entryName) { @@ -1484,9 +1719,12 @@ class ZipFile implements ZipFileInterface } /** - * Return the key of the current element - * @link http://php.net/manual/en/iterator.key.php - * @return mixed scalar on success, or null on failure. + * Return the key of the current element. + * + * @see http://php.net/manual/en/iterator.key.php + * + * @return mixed scalar on success, or null on failure + * * @since 5.0.0 */ public function key() @@ -1495,9 +1733,9 @@ class ZipFile implements ZipFileInterface } /** - * Move forward to next element - * @link http://php.net/manual/en/iterator.next.php - * @return void Any returned value is ignored. + * Move forward to next element. + * + * @see http://php.net/manual/en/iterator.next.php * @since 5.0.0 */ public function next() @@ -1506,10 +1744,13 @@ class ZipFile implements ZipFileInterface } /** - * Checks if current position is valid - * @link http://php.net/manual/en/iterator.valid.php - * @return boolean The return value will be casted to boolean and then evaluated. - * Returns true on success or false on failure. + * Checks if current position is valid. + * + * @see http://php.net/manual/en/iterator.valid.php + * + * @return bool The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + * * @since 5.0.0 */ public function valid() @@ -1518,11 +1759,14 @@ class ZipFile implements ZipFileInterface } /** - * Whether a offset exists - * @link http://php.net/manual/en/arrayaccess.offsetexists.php - * @param string $entryName An offset to check for. - * @return boolean true on success or false on failure. - * The return value will be casted to boolean if non-boolean was returned. + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param string $entryName an offset to check for + * + * @return bool true on success or false on failure. + * The return value will be casted to boolean if non-boolean was returned. */ public function offsetExists($entryName) { @@ -1530,9 +1774,9 @@ class ZipFile implements ZipFileInterface } /** - * Rewind the Iterator to the first element - * @link http://php.net/manual/en/iterator.rewind.php - * @return void Any returned value is ignored. + * Rewind the Iterator to the first element. + * + * @see http://php.net/manual/en/iterator.rewind.php * @since 5.0.0 */ public function rewind() diff --git a/src/PhpZip/ZipFileInterface.php b/src/PhpZip/ZipFileInterface.php index 2237283..c17db22 100644 --- a/src/PhpZip/ZipFileInterface.php +++ b/src/PhpZip/ZipFileInterface.php @@ -18,6 +18,7 @@ use Psr\Http\Message\ResponseInterface; * Support ZipAlign functional. * * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification + * * @author Ne-Lexa alexey@nelexa.ru * @license MIT */ @@ -25,67 +26,66 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator { /** * Method for Stored (uncompressed) entries. + * * @see ZipEntry::setMethod() */ const METHOD_STORED = 0; + /** * Method for Deflated compressed entries. + * * @see ZipEntry::setMethod() */ const METHOD_DEFLATED = 8; + /** * Method for BZIP2 compressed entries. * Require php extension bz2. + * * @see ZipEntry::setMethod() */ const METHOD_BZIP2 = 12; - /** - * Default compression level. - */ + /** Default compression level. */ const LEVEL_DEFAULT_COMPRESSION = -1; - /** - * Compression level for fastest compression. - */ + + /** Compression level for fastest compression. */ const LEVEL_FAST = 2; - /** - * Compression level for fastest compression. - */ + + /** Compression level for fastest compression. */ const LEVEL_BEST_SPEED = 1; + const LEVEL_SUPER_FAST = self::LEVEL_BEST_SPEED; - /** - * Compression level for best compression. - */ + + /** Compression level for best compression. */ const LEVEL_BEST_COMPRESSION = 9; - /** - * No specified method for set encryption method to Traditional PKWARE encryption. - */ + /** No specified method for set encryption method to Traditional PKWARE encryption. */ const ENCRYPTION_METHOD_TRADITIONAL = 0; + /** * No specified method for set encryption method to WinZip AES encryption. - * Default value 256 bit + * Default value 256 bit. */ const ENCRYPTION_METHOD_WINZIP_AES = self::ENCRYPTION_METHOD_WINZIP_AES_256; - /** - * No specified method for set encryption method to WinZip AES encryption 128 bit. - */ + + /** No specified method for set encryption method to WinZip AES encryption 128 bit. */ const ENCRYPTION_METHOD_WINZIP_AES_128 = 2; - /** - * No specified method for set encryption method to WinZip AES encryption 194 bit. - */ + + /** No specified method for set encryption method to WinZip AES encryption 194 bit. */ const ENCRYPTION_METHOD_WINZIP_AES_192 = 3; - /** - * No specified method for set encryption method to WinZip AES encryption 256 bit. - */ + + /** No specified method for set encryption method to WinZip AES encryption 256 bit. */ const ENCRYPTION_METHOD_WINZIP_AES_256 = 1; /** - * Open zip archive from file + * Open zip archive from file. * * @param string $filename + * + * @throws ZipException if can't open file + * * @return ZipFileInterface - * @throws ZipException if can't open file. */ public function openFile($filename); @@ -93,35 +93,39 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Open zip archive from raw string data. * * @param string $data + * + * @throws ZipException if can't open temp stream + * * @return ZipFileInterface - * @throws ZipException if can't open temp stream. */ public function openFromString($data); /** - * Open zip archive from stream resource + * Open zip archive from stream resource. * * @param resource $handle + * * @return ZipFileInterface */ public function openFromStream($handle); /** - * @return string[] Returns the list files. + * @return string[] returns the list files */ public function getListFiles(); /** * Returns the file comment. * - * @return string The file comment. + * @return string the file comment */ public function getArchiveComment(); /** * Set archive comment. * - * @param null|string $comment + * @param string|null $comment + * * @return ZipFileInterface */ public function setArchiveComment($comment = null); @@ -132,8 +136,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * (i.e. end with '/'). * * @param string $entryName - * @return bool + * * @throws ZipEntryNotFoundException + * + * @return bool */ public function isDirectory($entryName); @@ -141,18 +147,22 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Returns entry comment. * * @param string $entryName - * @return string + * * @throws ZipEntryNotFoundException + * + * @return string */ public function getEntryComment($entryName); /** * Set entry comment. * - * @param string $entryName + * @param string $entryName * @param string|null $comment - * @return ZipFileInterface + * * @throws ZipEntryNotFoundException + * + * @return ZipFileInterface */ public function setEntryComment($entryName, $comment = null); @@ -160,6 +170,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Returns the entry contents. * * @param string $entryName + * * @return string */ public function getEntryContents($entryName); @@ -168,6 +179,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Checks if there is an entry in the archive. * * @param string $entryName + * * @return bool */ public function hasEntry($entryName); @@ -176,8 +188,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Get info by entry. * * @param string|ZipEntry $entryName - * @return ZipInfo + * * @throws ZipEntryNotFoundException + * + * @return ZipInfo */ public function getEntryInfo($entryName); @@ -194,60 +208,68 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator public function matcher(); /** - * Extract the archive contents + * Extract the archive contents. * * Extract the complete archive or the given files to the specified destination. * - * @param string $destination Location where to extract the files. - * @param array|string|null $entries The entries to extract. It accepts either - * a single entry name or an array of names. - * @return ZipFileInterface + * @param string $destination location where to extract the files + * @param array|string|null $entries The entries to extract. It accepts either + * a single entry name or an array of names. + * * @throws ZipException + * + * @return ZipFileInterface */ public function extractTo($destination, $entries = null); /** * Add entry from the string. * - * @param string $localName Zip entry name. - * @param string $contents String contents. + * @param string $localName zip entry name + * @param string $contents string contents * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. + * If null, then auto choosing method. + * * @return ZipFileInterface - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFromString($localName, $contents, $compressionMethod = null); /** * Add entry from the file. * - * @param string $filename Destination file. - * @param string|null $localName Zip Entry name. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * @param string $filename destination file + * @param string|null $localName zip Entry name + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @return ZipFileInterface - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFile($filename, $localName = null, $compressionMethod = null); /** * Add entry from the stream. * - * @param resource $stream Stream resource. - * @param string $localName Zip Entry name. + * @param resource $stream stream resource + * @param string $localName zip Entry name * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. + * If null, then auto choosing method. + * * @return ZipFileInterface - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFromStream($stream, $localName, $compressionMethod = null); @@ -255,6 +277,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Add an empty directory in the zip archive. * * @param string $dirName + * * @return ZipFileInterface */ public function addEmptyDir($dirName); @@ -262,54 +285,60 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator /** * Add directory not recursively to the zip archive. * - * @param string $inputDir Input directory - * @param string $localPath Add files to this directory, or the root. + * @param string $inputDir Input directory + * @param string $localPath add files to this directory, or the root * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. + * If null, then auto choosing method. + * * @return ZipFileInterface */ - public function addDir($inputDir, $localPath = "/", $compressionMethod = null); + public function addDir($inputDir, $localPath = '/', $compressionMethod = null); /** * Add recursive directory to the zip archive. * - * @param string $inputDir Input directory - * @param string $localPath Add files to this directory, or the root. + * @param string $inputDir Input directory + * @param string $localPath add files to this directory, or the root * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. + * If null, then auto choosing method. + * * @return ZipFileInterface - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ - public function addDirRecursive($inputDir, $localPath = "/", $compressionMethod = null); + public function addDirRecursive($inputDir, $localPath = '/', $compressionMethod = null); /** * Add directories from directory iterator. * - * @param \Iterator $iterator Directory iterator. - * @param string $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * @param \Iterator $iterator directory iterator + * @param string $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @return ZipFileInterface - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function addFilesFromIterator(\Iterator $iterator, $localPath = '/', $compressionMethod = null); /** * Add files from glob pattern. * - * @param string $inputDir Input directory - * @param string $globPattern Glob pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @return ZipFileInterface * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ @@ -318,12 +347,13 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator /** * Add files recursively from glob pattern. * - * @param string $inputDir Input directory - * @param string $globPattern Glob pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * @param string $inputDir Input directory + * @param string $globPattern glob pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @return ZipFileInterface * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ @@ -332,56 +362,64 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator /** * Add files from regex pattern. * - * @param string $inputDir Search files in this directory. - * @param string $regexPattern Regex pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @return ZipFileInterface - * @internal param bool $recursive Recursive search. + * + * @internal param bool $recursive Recursive search */ - public function addFilesFromRegex($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null); + public function addFilesFromRegex($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null); /** * Add files recursively from regex pattern. * - * @param string $inputDir Search files in this directory. - * @param string $regexPattern Regex pattern. - * @param string|null $localPath Add files to this directory, or the root. - * @param int|null $compressionMethod Compression method. - * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or ZipFile::METHOD_BZIP2. - * If null, then auto choosing method. + * @param string $inputDir search files in this directory + * @param string $regexPattern regex pattern + * @param string|null $localPath add files to this directory, or the root + * @param int|null $compressionMethod Compression method. + * Use ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED or + * ZipFile::METHOD_BZIP2. If null, then auto choosing method. + * * @return ZipFileInterface - * @internal param bool $recursive Recursive search. + * + * @internal param bool $recursive Recursive search */ - public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = "/", $compressionMethod = null); + public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null); /** * Add array data to archive. * Keys is local names. * Values is contents. * - * @param array $mapData Associative array for added to zip. + * @param array $mapData associative array for added to zip */ public function addAll(array $mapData); /** * Rename the entry. * - * @param string $oldName Old entry name. - * @param string $newName New entry name. - * @return ZipFileInterface + * @param string $oldName old entry name + * @param string $newName new entry name + * * @throws ZipEntryNotFoundException + * + * @return ZipFileInterface */ public function rename($oldName, $newName); /** * Delete entry by name. * - * @param string $entryName Zip Entry name. + * @param string $entryName zip Entry name + * + * @throws ZipEntryNotFoundException if entry not found + * * @return ZipFileInterface - * @throws ZipEntryNotFoundException If entry not found. */ public function deleteFromName($entryName); @@ -389,6 +427,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Delete entries by glob pattern. * * @param string $globPattern Glob pattern + * * @return ZipFileInterface * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax */ @@ -398,12 +437,14 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Delete entries by regex pattern. * * @param string $regexPattern Regex pattern + * * @return ZipFileInterface */ public function deleteFromRegex($regexPattern); /** - * Delete all entries + * Delete all entries. + * * @return ZipFileInterface */ public function deleteAll(); @@ -412,34 +453,42 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Set compression level for new entries. * * @param int $compressionLevel - * @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION - * @see ZipFileInterface::LEVEL_SUPER_FAST - * @see ZipFileInterface::LEVEL_FAST - * @see ZipFileInterface::LEVEL_BEST_COMPRESSION + * * @return ZipFileInterface + * + * @see ZipFile::LEVEL_SUPER_FAST + * @see ZipFile::LEVEL_FAST + * @see ZipFile::LEVEL_BEST_COMPRESSION + * @see ZipFile::LEVEL_DEFAULT_COMPRESSION */ public function setCompressionLevel($compressionLevel = self::LEVEL_DEFAULT_COMPRESSION); /** * @param string $entryName - * @param int $compressionLevel - * @return ZipFileInterface + * @param int $compressionLevel + * * @throws ZipException - * @see ZipFileInterface::LEVEL_DEFAULT_COMPRESSION - * @see ZipFileInterface::LEVEL_SUPER_FAST - * @see ZipFileInterface::LEVEL_FAST - * @see ZipFileInterface::LEVEL_BEST_COMPRESSION + * + * @return ZipFileInterface + * + * @see ZipFile::LEVEL_DEFAULT_COMPRESSION + * @see ZipFile::LEVEL_SUPER_FAST + * @see ZipFile::LEVEL_FAST + * @see ZipFile::LEVEL_BEST_COMPRESSION */ public function setCompressionLevelEntry($entryName, $compressionLevel); /** * @param string $entryName - * @param int $compressionMethod - * @return ZipFileInterface + * @param int $compressionMethod + * * @throws ZipException - * @see ZipFileInterface::METHOD_STORED - * @see ZipFileInterface::METHOD_DEFLATED - * @see ZipFileInterface::METHOD_BZIP2 + * + * @return ZipFileInterface + * + * @see ZipFile::METHOD_STORED + * @see ZipFile::METHOD_DEFLATED + * @see ZipFile::METHOD_BZIP2 */ public function setCompressionMethodEntry($entryName, $compressionMethod); @@ -447,8 +496,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * zipalign is optimization to Android application (APK) files. * * @param int|null $align + * * @return ZipFileInterface - * @link https://developer.android.com/studio/command-line/zipalign.html + * + * @see https://developer.android.com/studio/command-line/zipalign.html */ public function setZipAlign($align = null); @@ -456,8 +507,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Set password to all input encrypted entries. * * @param string $password Password + * * @return ZipFileInterface - * @deprecated using ZipFileInterface::setReadPassword() + * + * @deprecated using ZipFile::setReadPassword() */ public function withReadPassword($password); @@ -465,6 +518,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Set password to all input encrypted entries. * * @param string $password Password + * * @return ZipFileInterface */ public function setReadPassword($password); @@ -473,7 +527,8 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Set password to concrete input entry. * * @param string $entryName - * @param string $password Password + * @param string $password Password + * * @return ZipFileInterface */ public function setReadPasswordEntry($entryName, $password); @@ -481,18 +536,21 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator /** * Set password for all entries for update. * - * @param string $password If password null then encryption clear + * @param string $password If password null then encryption clear * @param int|null $encryptionMethod Encryption method + * * @return ZipFileInterface - * @deprecated using ZipFileInterface::setPassword() + * + * @deprecated using ZipFile::setPassword() */ public function withNewPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256); /** * Sets a new password for all files in the archive. * - * @param string $password + * @param string $password * @param int|null $encryptionMethod Encryption method + * * @return ZipFileInterface */ public function setPassword($password, $encryptionMethod = self::ENCRYPTION_METHOD_WINZIP_AES_256); @@ -500,41 +558,49 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator /** * Sets a new password of an entry defined by its name. * - * @param string $entryName - * @param string $password + * @param string $entryName + * @param string $password * @param int|null $encryptionMethod + * * @return ZipFileInterface */ public function setPasswordEntry($entryName, $password, $encryptionMethod = null); /** * Remove password for all entries for update. + * * @return ZipFileInterface - * @deprecated using ZipFileInterface::disableEncryption() + * + * @deprecated using ZipFile::disableEncryption() */ public function withoutPassword(); /** * Disable encryption for all entries that are already in the archive. + * * @return ZipFileInterface */ public function disableEncryption(); /** * Disable encryption of an entry defined by its name. + * * @param string $entryName + * * @return ZipFileInterface */ public function disableEncryptionEntry($entryName); /** - * Undo all changes done in the archive + * Undo all changes done in the archive. + * * @return ZipFileInterface */ public function unchangeAll(); /** - * Undo change archive comment + * Undo change archive comment. + * * @return ZipFileInterface */ public function unchangeArchiveComment(); @@ -543,6 +609,7 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Revert all changes done to an entry with the given name. * * @param string|ZipEntry $entry Entry name or ZipEntry + * * @return ZipFileInterface */ public function unchangeEntry($entry); @@ -551,8 +618,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Save as file. * * @param string $filename Output filename - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function saveAsFile($filename); @@ -560,8 +629,10 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Save as stream. * * @param resource $handle Output stream resource - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function saveAsStream($handle); @@ -569,33 +640,42 @@ interface ZipFileInterface extends \Countable, \ArrayAccess, \Iterator * Output .ZIP archive as attachment. * Die after output. * - * @param string $outputFilename Output filename - * @param string|null $mimeType Mime-Type - * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * @param string $outputFilename Output filename + * @param string|null $mimeType Mime-Type + * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline */ public function outputAsAttachment($outputFilename, $mimeType = null, $attachment = true); /** * Output .ZIP archive as PSR-7 Response. * - * @param ResponseInterface $response Instance PSR-7 Response - * @param string $outputFilename Output filename - * @param string|null $mimeType Mime-Type - * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * @param ResponseInterface $response Instance PSR-7 Response + * @param string $outputFilename Output filename + * @param string|null $mimeType Mime-Type + * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline + * * @return ResponseInterface */ - public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true); + public function outputAsResponse( + ResponseInterface $response, + $outputFilename, + $mimeType = null, + $attachment = true + ); /** * Returns the zip archive as a string. + * * @return string */ public function outputAsString(); /** * Save and reopen zip archive. - * @return ZipFileInterface + * * @throws ZipException + * + * @return ZipFileInterface */ public function rewrite(); diff --git a/tests/PhpZip/Internal/DummyFileSystemStream.php b/tests/PhpZip/Internal/DummyFileSystemStream.php new file mode 100644 index 0000000..a278dcd --- /dev/null +++ b/tests/PhpZip/Internal/DummyFileSystemStream.php @@ -0,0 +1,72 @@ +fp = fopen($path, $mode); + + return true; + } + + /** + * @param $count + * + * @return false|string + */ + public function stream_read($count) + { + return fread($this->fp, $count); + } + + /** + * @return false|int + */ + public function stream_tell() + { + return ftell($this->fp); + } + + /** + * @return bool + */ + public function stream_eof() + { + return feof($this->fp); + } + + /** + * @param $offset + * @param $whence + */ + public function stream_seek($offset, $whence) + { + fseek($this->fp, $offset, $whence); + } + + /** + * @return array + */ + public function stream_stat() + { + return fstat($this->fp); + } +} diff --git a/tests/PhpZip/Internal/ZipFileExtended.php b/tests/PhpZip/Internal/ZipFileExtended.php new file mode 100644 index 0000000..5e00777 --- /dev/null +++ b/tests/PhpZip/Internal/ZipFileExtended.php @@ -0,0 +1,18 @@ +setZipAlign(4); + $this->deleteFromRegex('~^META\-INF/~i'); + } +} diff --git a/tests/PhpZip/Issue24Test.php b/tests/PhpZip/Issue24Test.php index e3ed10c..4250470 100644 --- a/tests/PhpZip/Issue24Test.php +++ b/tests/PhpZip/Issue24Test.php @@ -3,24 +3,31 @@ namespace PhpZip; use PhpZip\Exception\ZipException; -use PhpZip\Util\CryptoUtil; +/** + * @internal + * + * @small + */ class Issue24Test extends ZipTestCase { /** * This method is called before the first test of this test class is run. + * + * @noinspection PhpMissingParentCallCommonInspection */ public static function setUpBeforeClass() { - stream_wrapper_register("dummyfs", DummyFileSystemStream::class); + stream_wrapper_register('dummyfs', Internal\DummyFileSystemStream::class); } /** * @throws ZipException + * @throws \Exception */ public function testDummyFS() { - $fileContents = str_repeat(base64_encode(CryptoUtil::randomBytes(12000)), 100); + $fileContents = str_repeat(base64_encode(random_bytes(12000)), 100); // create zip file $zip = new ZipFile(); @@ -32,73 +39,13 @@ class Issue24Test extends ZipTestCase $zip->saveAsFile($this->outputFilename); $zip->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $stream = fopen('dummyfs://localhost/' . $this->outputFilename, 'rb'); - $this->assertNotFalse($stream); + static::assertNotFalse($stream); $zip->openFromStream($stream); - $this->assertEquals($zip->getListFiles(), ['file.txt']); - $this->assertEquals($zip['file.txt'], $fileContents); + static::assertSame($zip->getListFiles(), ['file.txt']); + static::assertSame($zip['file.txt'], $fileContents); $zip->close(); } } - -/** - * Try to load using dummy stream - */ -class DummyFileSystemStream -{ - /** - * @var resource - */ - private $fp; - - public function stream_open($path, $mode, $options, &$opened_path) - { -// echo "DummyFileSystemStream->stream_open($path, $mode, $options)" . PHP_EOL; - - $parsedUrl = parse_url($path); - $path = $parsedUrl['path']; - $this->fp = fopen($path, $mode); - - return true; - } - - public function stream_read($count) - { -// echo "DummyFileSystemStream->stream_read($count)" . PHP_EOL; - $position = ftell($this->fp); - -// echo "Loading chunk " . $position . " to " . ($position + $count - 1) . PHP_EOL; - $ret = fread($this->fp, $count); - -// echo "String length: " . strlen($ret) . PHP_EOL; - - return $ret; - } - - public function stream_tell() - { -// echo "DummyFileSystemStream->stream_tell()" . PHP_EOL; - return ftell($this->fp); - } - - public function stream_eof() - { -// echo "DummyFileSystemStream->stream_eof()" . PHP_EOL; - $isfeof = feof($this->fp); - return $isfeof; - } - - public function stream_seek($offset, $whence) - { -// echo "DummyFileSystemStream->stream_seek($offset, $whence)" . PHP_EOL; - fseek($this->fp, $offset, $whence); - } - - public function stream_stat() - { -// echo "DummyFileSystemStream->stream_stat()" . PHP_EOL; - return fstat($this->fp); - } -} diff --git a/tests/PhpZip/PhpZipExtResourceTest.php b/tests/PhpZip/PhpZipExtResourceTest.php index 0c4dba7..859ebe5 100644 --- a/tests/PhpZip/PhpZipExtResourceTest.php +++ b/tests/PhpZip/PhpZipExtResourceTest.php @@ -2,16 +2,25 @@ namespace PhpZip; +use PhpZip\Exception\Crc32Exception; +use PhpZip\Exception\RuntimeException; +use PhpZip\Exception\ZipAuthenticationException; use PhpZip\Exception\ZipException; /** * Some tests from the official extension of php-zip. + * + * @internal + * + * @small */ class PhpZipExtResourceTest extends ZipTestCase { /** - * Bug #7214 (zip_entry_read() binary safe) + * Bug #7214 (zip_entry_read() binary safe). + * * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug7214.phpt + * * @throws ZipException */ public function testBinaryNull() @@ -20,18 +29,21 @@ class PhpZipExtResourceTest extends ZipTestCase $zipFile = new ZipFile(); $zipFile->openFile($filename); + foreach ($zipFile as $name => $contents) { $info = $zipFile->getEntryInfo($name); - $this->assertEquals(strlen($contents), $info->getSize()); + static::assertSame(\strlen($contents), $info->getSize()); } $zipFile->close(); - $this->assertCorrectZipArchive($filename); + static::assertCorrectZipArchive($filename); } /** - * Bug #8009 (cannot add again same entry to an archive) + * Bug #8009 (cannot add again same entry to an archive). + * * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug8009.phpt + * * @throws ZipException */ public function testBug8009() @@ -44,36 +56,42 @@ class PhpZipExtResourceTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertCount(2, $zipFile); - $this->assertTrue(isset($zipFile['1.txt'])); - $this->assertTrue(isset($zipFile['2.txt'])); - $this->assertEquals($zipFile['2.txt'], $zipFile['1.txt']); + static::assertCount(2, $zipFile); + static::assertTrue(isset($zipFile['1.txt'])); + static::assertTrue(isset($zipFile['2.txt'])); + static::assertSame($zipFile['2.txt'], $zipFile['1.txt']); $zipFile->close(); } /** - * Bug #40228 (extractTo does not create recursive empty path) + * Bug #40228 (extractTo does not create recursive empty path). + * * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228.phpt * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug40228-mb.phpt * @dataProvider provideBug40228 + * * @param string $filename + * * @throws ZipException */ public function testBug40228($filename) { - $this->assertTrue(mkdir($this->outputDirname, 0755, true)); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); $zipFile = new ZipFile(); $zipFile->openFile($filename); $zipFile->extractTo($this->outputDirname); $zipFile->close(); - $this->assertTrue(is_dir($this->outputDirname . '/test/empty')); + static::assertTrue(is_dir($this->outputDirname . '/test/empty')); } + /** + * @return array + */ public function provideBug40228() { return [ @@ -82,14 +100,16 @@ class PhpZipExtResourceTest extends ZipTestCase } /** - * Bug #49072 (feof never returns true for damaged file in zip) + * Bug #49072 (feof never returns true for damaged file in zip). + * * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug49072.phpt - * @expectedException \PhpZip\Exception\Crc32Exception - * @expectedExceptionMessage file1 + * * @throws ZipException */ public function testBug49072() { + $this->setExpectedException(Crc32Exception::class, 'file1'); + $filename = __DIR__ . '/php-zip-ext-test-resources/bug49072.zip'; $zipFile = new ZipFile(); @@ -98,51 +118,59 @@ class PhpZipExtResourceTest extends ZipTestCase } /** - * Bug #70752 (Depacking with wrong password leaves 0 length files) + * Bug #70752 (Depacking with wrong password leaves 0 length files). + * * @see https://github.com/php/php-src/blob/master/ext/zip/tests/bug70752.phpt - * @expectedException \PhpZip\Exception\ZipAuthenticationException - * @expectedExceptionMessage nvalid password for zip entry "bug70752.txt" + * * @throws ZipException */ public function testBug70752() { + if (\PHP_INT_SIZE === 4) { // php 32 bit + $this->setExpectedException( + RuntimeException::class, + 'Traditional PKWARE Encryption is not supported in 32-bit PHP.' + ); + } else { // php 64 bit + $this->setExpectedException( + ZipAuthenticationException::class, + 'nvalid password for zip entry "bug70752.txt"' + ); + } + $filename = __DIR__ . '/php-zip-ext-test-resources/bug70752.zip'; - $this->assertTrue(mkdir($this->outputDirname, 0755, true)); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); $zipFile = new ZipFile(); + $zipFile->openFile($filename); + $zipFile->setReadPassword('bar'); + try { - $zipFile->openFile($filename); - $zipFile->setReadPassword('bar'); $zipFile->extractTo($this->outputDirname); - $this->markTestIncomplete('failed test'); + static::markTestIncomplete('failed test'); } catch (ZipException $exception) { - $this->assertFalse(file_exists($this->outputDirname . '/bug70752.txt')); + static::assertFileNotExists($this->outputDirname . '/bug70752.txt'); $zipFile->close(); + throw $exception; } } /** - * Bug #12414 ( extracting files from damaged archives) + * Bug #12414 ( extracting files from damaged archives). + * * @see https://github.com/php/php-src/blob/master/ext/zip/tests/pecl12414.phpt + * * @throws ZipException */ public function testPecl12414() { - $filename = __DIR__ . '/php-zip-ext-test-resources/pecl12414.zip'; + $this->setExpectedException(ZipException::class, 'Corrupt zip file. Cannot read central dir entry.'); - $entryName = 'MYLOGOV2.GFX'; + $filename = __DIR__ . '/php-zip-ext-test-resources/pecl12414.zip'; $zipFile = new ZipFile(); $zipFile->openFile($filename); - - $info = $zipFile->getEntryInfo($entryName); - $this->assertTrue($info->getSize() > 0); - - $contents = $zipFile[$entryName]; - $this->assertEquals(strlen($contents), $info->getSize()); - - $zipFile->close(); } } diff --git a/tests/PhpZip/Zip64Test.php b/tests/PhpZip/Zip64Test.php new file mode 100644 index 0000000..e9ebe85 --- /dev/null +++ b/tests/PhpZip/Zip64Test.php @@ -0,0 +1,44 @@ + 65535 files in archive and open and extract to /dev/null. + * + * @throws ZipException + */ + public function testCreateAndOpenZip64Ext() + { + $countFiles = 0xffff + 1; + + $zipFile = new ZipFile(); + for ($i = 0; $i < $countFiles; $i++) { + $zipFile[$i . '.txt'] = (string) $i; + } + $zipFile->saveAsFile($this->outputFilename); + $zipFile->close(); + + static::assertCorrectZipArchive($this->outputFilename); + + $zipFile->openFile($this->outputFilename); + static::assertSame($zipFile->count(), $countFiles); + $i = 0; + + foreach ($zipFile as $entry => $content) { + static::assertSame($entry, $i . '.txt'); + static::assertSame($content, (string) $i); + $i++; + } + $zipFile->close(); + } +} diff --git a/tests/PhpZip/ZipAlignTest.php b/tests/PhpZip/ZipAlignTest.php index 161cc33..427a5d4 100644 --- a/tests/PhpZip/ZipAlignTest.php +++ b/tests/PhpZip/ZipAlignTest.php @@ -3,10 +3,13 @@ namespace PhpZip; use PhpZip\Exception\ZipException; -use PhpZip\Util\CryptoUtil; /** - * Test ZipAlign + * Test ZipAlign. + * + * @internal + * + * @small */ class ZipAlignTest extends ZipTestCase { @@ -17,10 +20,11 @@ class ZipAlignTest extends ZipTestCase { $filename = __DIR__ . '/resources/apk.zip'; - $this->assertCorrectZipArchive($filename); - $result = $this->assertVerifyZipAlign($filename); - if (null !== $result) { - $this->assertTrue($result); + static::assertCorrectZipArchive($filename); + $result = static::assertVerifyZipAlign($filename); + + if ($result !== null) { + static::assertTrue($result); } $zipFile = new ZipFile(); @@ -29,16 +33,19 @@ class ZipAlignTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); - $result = $this->assertVerifyZipAlign($this->outputFilename, true); - if (null !== $result) { - $this->assertTrue($result); + static::assertCorrectZipArchive($this->outputFilename); + $result = static::assertVerifyZipAlign($this->outputFilename, true); + + if ($result !== null) { + static::assertTrue($result); } } /** * Test zip alignment. + * * @throws ZipException + * @throws \Exception */ public function testZipAlignSourceZip() { @@ -46,39 +53,41 @@ class ZipAlignTest extends ZipTestCase for ($i = 0; $i < 100; $i++) { $zipFile->addFromString( 'entry' . $i . '.txt', - CryptoUtil::randomBytes(mt_rand(100, 4096)), - ZipFileInterface::METHOD_STORED + random_bytes(mt_rand(100, 4096)), + ZipFile::METHOD_STORED ); } $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); + + $result = static::assertVerifyZipAlign($this->outputFilename); - $result = $this->assertVerifyZipAlign($this->outputFilename); if ($result === null) { return; } // zip align not installed // check not zip align - $this->assertFalse($result); + static::assertFalse($result); $zipFile->openFile($this->outputFilename); $zipFile->setZipAlign(4); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); - $result = $this->assertVerifyZipAlign($this->outputFilename, true); - $this->assertNotNull($result); + $result = static::assertVerifyZipAlign($this->outputFilename, true); + static::assertNotNull($result); // check zip align - $this->assertTrue($result); + static::assertTrue($result); } /** * @throws ZipException + * @throws \Exception */ public function testZipAlignNewFiles() { @@ -86,26 +95,28 @@ class ZipAlignTest extends ZipTestCase for ($i = 0; $i < 100; $i++) { $zipFile->addFromString( 'entry' . $i . '.txt', - CryptoUtil::randomBytes(mt_rand(100, 4096)), - ZipFileInterface::METHOD_STORED + random_bytes(mt_rand(100, 4096)), + ZipFile::METHOD_STORED ); } $zipFile->setZipAlign(4); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); + + $result = static::assertVerifyZipAlign($this->outputFilename); - $result = $this->assertVerifyZipAlign($this->outputFilename); if ($result === null) { return; } // zip align not installed // check not zip align - $this->assertTrue($result); + static::assertTrue($result); } /** * @throws ZipException + * @throws \Exception */ public function testZipAlignFromModifiedZipArchive() { @@ -113,46 +124,47 @@ class ZipAlignTest extends ZipTestCase for ($i = 0; $i < 100; $i++) { $zipFile->addFromString( 'entry' . $i . '.txt', - CryptoUtil::randomBytes(mt_rand(100, 4096)), - ZipFileInterface::METHOD_STORED + random_bytes(mt_rand(100, 4096)), + ZipFile::METHOD_STORED ); } $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); + + $result = static::assertVerifyZipAlign($this->outputFilename); - $result = $this->assertVerifyZipAlign($this->outputFilename); if ($result === null) { return; } // zip align not installed // check not zip align - $this->assertFalse($result); + static::assertFalse($result); $zipFile->openFile($this->outputFilename); - $zipFile->deleteFromRegex("~entry2[\d]+\.txt$~s"); + $zipFile->deleteFromRegex('~entry2[\\d]+\\.txt$~s'); for ($i = 0; $i < 100; $i++) { - $isStored = (bool)mt_rand(0, 1); + $isStored = (bool) mt_rand(0, 1); $zipFile->addFromString( 'entry_new_' . ($isStored ? 'stored' : 'deflated') . '_' . $i . '.txt', - CryptoUtil::randomBytes(mt_rand(100, 4096)), + random_bytes(mt_rand(100, 4096)), $isStored ? - ZipFileInterface::METHOD_STORED : - ZipFileInterface::METHOD_DEFLATED + ZipFile::METHOD_STORED : + ZipFile::METHOD_DEFLATED ); } $zipFile->setZipAlign(4); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); - $result = $this->assertVerifyZipAlign($this->outputFilename, true); - $this->assertNotNull($result); + $result = static::assertVerifyZipAlign($this->outputFilename, true); + static::assertNotNull($result); // check zip align - $this->assertTrue($result); + static::assertTrue($result); } } diff --git a/tests/PhpZip/ZipEventTest.php b/tests/PhpZip/ZipEventTest.php index 02e2c7b..950a645 100644 --- a/tests/PhpZip/ZipEventTest.php +++ b/tests/PhpZip/ZipEventTest.php @@ -4,16 +4,11 @@ namespace PhpZip; use PhpZip\Exception\ZipException; -class ZipFileExtended extends ZipFile -{ - protected function onBeforeSave() - { - parent::onBeforeSave(); - $this->setZipAlign(4); - $this->deleteFromRegex('~^META\-INF/~i'); - } -} - +/** + * @internal + * + * @small + */ class ZipEventTest extends ZipTestCase { /** @@ -21,27 +16,28 @@ class ZipEventTest extends ZipTestCase */ public function testBeforeSave() { - $zipFile = new ZipFileExtended(); + $zipFile = new Internal\ZipFileExtended(); $zipFile->openFile(__DIR__ . '/resources/apk.zip'); - $this->assertTrue(isset($zipFile['META-INF/MANIFEST.MF'])); - $this->assertTrue(isset($zipFile['META-INF/CERT.SF'])); - $this->assertTrue(isset($zipFile['META-INF/CERT.RSA'])); + static::assertTrue(isset($zipFile['META-INF/MANIFEST.MF'])); + static::assertTrue(isset($zipFile['META-INF/CERT.SF'])); + static::assertTrue(isset($zipFile['META-INF/CERT.RSA'])); $zipFile->saveAsFile($this->outputFilename); - $this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF'])); - $this->assertFalse(isset($zipFile['META-INF/CERT.SF'])); - $this->assertFalse(isset($zipFile['META-INF/CERT.RSA'])); + static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF'])); + static::assertFalse(isset($zipFile['META-INF/CERT.SF'])); + static::assertFalse(isset($zipFile['META-INF/CERT.RSA'])); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); - $result = $this->assertVerifyZipAlign($this->outputFilename); - if (null !== $result) { - $this->assertTrue($result); + static::assertCorrectZipArchive($this->outputFilename); + $result = static::assertVerifyZipAlign($this->outputFilename); + + if ($result !== null) { + static::assertTrue($result); } $zipFile->openFile($this->outputFilename); - $this->assertFalse(isset($zipFile['META-INF/MANIFEST.MF'])); - $this->assertFalse(isset($zipFile['META-INF/CERT.SF'])); - $this->assertFalse(isset($zipFile['META-INF/CERT.RSA'])); + static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF'])); + static::assertFalse(isset($zipFile['META-INF/CERT.SF'])); + static::assertFalse(isset($zipFile['META-INF/CERT.RSA'])); $zipFile->close(); } } diff --git a/tests/PhpZip/ZipFileAddDirTest.php b/tests/PhpZip/ZipFileAddDirTest.php index 3b13553..69d5c22 100644 --- a/tests/PhpZip/ZipFileAddDirTest.php +++ b/tests/PhpZip/ZipFileAddDirTest.php @@ -8,6 +8,10 @@ use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator; /** * Test add directory to zip archive. + * + * @internal + * + * @small */ class ZipFileAddDirTest extends ZipTestCase { @@ -28,7 +32,7 @@ class ZipFileAddDirTest extends ZipTestCase ]; /** - * Before test + * Before test. */ protected function setUp() { @@ -40,12 +44,14 @@ class ZipFileAddDirTest extends ZipTestCase { foreach (self::$files as $name => $content) { $fullName = $this->outputDirname . '/' . $name; + if ($content === null) { if (!is_dir($fullName)) { mkdir($fullName, 0755, true); } } else { - $dirname = dirname($fullName); + $dirname = \dirname($fullName); + if (!is_dir($dirname)) { mkdir($dirname, 0755, true); } @@ -54,23 +60,33 @@ class ZipFileAddDirTest extends ZipTestCase } } - protected static function assertFilesResult(ZipFileInterface $zipFile, array $actualResultFiles = [], $localPath = '/') - { + /** + * @param ZipFileInterface $zipFile + * @param array $actualResultFiles + * @param string $localPath + */ + protected static function assertFilesResult( + ZipFileInterface $zipFile, + array $actualResultFiles = [], + $localPath = '/' + ) { $localPath = rtrim($localPath, '/'); - $localPath = empty($localPath) ? "" : $localPath . '/'; - self::assertEquals(sizeof($zipFile), sizeof($actualResultFiles)); + $localPath = empty($localPath) ? '' : $localPath . '/'; + static::assertCount(\count($zipFile), $actualResultFiles); $actualResultFiles = array_flip($actualResultFiles); + foreach (self::$files as $file => $content) { $zipEntryName = $localPath . $file; + if (isset($actualResultFiles[$file])) { - self::assertTrue(isset($zipFile[$zipEntryName])); - self::assertEquals($zipFile[$zipEntryName], $content); + static::assertTrue(isset($zipFile[$zipEntryName])); + static::assertSame($zipFile[$zipEntryName], $content); unset($actualResultFiles[$file]); } else { - self::assertFalse(isset($zipFile[$zipEntryName])); + static::assertFalse(isset($zipFile[$zipEntryName])); } } - self::assertEmpty($actualResultFiles); + static::assertEmpty($actualResultFiles); } /** @@ -85,15 +101,19 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - '.hidden', - 'text file.txt', - 'Текстовый документ.txt', - 'empty dir/', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + '.hidden', + 'text file.txt', + 'Текстовый документ.txt', + 'empty dir/', + ], + $localPath + ); $zipFile->close(); } @@ -107,15 +127,18 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - '.hidden', - 'text file.txt', - 'Текстовый документ.txt', - 'empty dir/', - ]); + static::assertFilesResult( + $zipFile, + [ + '.hidden', + 'text file.txt', + 'Текстовый документ.txt', + 'empty dir/', + ] + ); $zipFile->close(); } @@ -133,15 +156,19 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - '.hidden', - 'text file.txt', - 'Текстовый документ.txt', - 'empty dir/', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + '.hidden', + 'text file.txt', + 'Текстовый документ.txt', + 'empty dir/', + ], + $localPath + ); $zipFile->close(); } @@ -159,15 +186,18 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - '.hidden', - 'text file.txt', - 'Текстовый документ.txt', - 'empty dir/', - ]); + static::assertFilesResult( + $zipFile, + [ + '.hidden', + 'text file.txt', + 'Текстовый документ.txt', + 'empty dir/', + ] + ); $zipFile->close(); } @@ -185,10 +215,10 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, array_keys(self::$files), $localPath); + static::assertFilesResult($zipFile, array_keys(self::$files), $localPath); $zipFile->close(); } @@ -204,10 +234,10 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, array_keys(self::$files), $localPath); + static::assertFilesResult($zipFile, array_keys(self::$files), $localPath); $zipFile->close(); } @@ -221,10 +251,10 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, array_keys(self::$files)); + static::assertFilesResult($zipFile, array_keys(self::$files)); $zipFile->close(); } @@ -236,7 +266,7 @@ class ZipFileAddDirTest extends ZipTestCase $localPath = 'to/project'; $ignoreFiles = [ 'Текстовый документ.txt', - 'empty dir/' + 'empty dir/', ]; $directoryIterator = new \DirectoryIterator($this->outputDirname); @@ -247,13 +277,17 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - '.hidden', - 'text file.txt', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + '.hidden', + 'text file.txt', + ], + $localPath + ); $zipFile->close(); } @@ -278,24 +312,29 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - 'text file.txt', - 'Текстовый документ.txt', - 'empty dir/', - 'catalog/New File', - 'catalog/New File 2', - 'catalog/Empty Dir/', - 'category/Pictures/128x160/Car/01.jpg', - 'category/Pictures/128x160/Car/02.jpg', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + 'text file.txt', + 'Текстовый документ.txt', + 'empty dir/', + 'catalog/New File', + 'catalog/New File 2', + 'catalog/Empty Dir/', + 'category/Pictures/128x160/Car/01.jpg', + 'category/Pictures/128x160/Car/02.jpg', + ], + $localPath + ); $zipFile->close(); } /** - * Create archive and add files from glob pattern + * Create archive and add files from glob pattern. + * * @throws ZipException */ public function testAddFilesFromGlob() @@ -307,18 +346,23 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - 'text file.txt', - 'Текстовый документ.txt', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + 'text file.txt', + 'Текстовый документ.txt', + ], + $localPath + ); $zipFile->close(); } /** - * Create archive and add recursively files from glob pattern + * Create archive and add recursively files from glob pattern. + * * @throws ZipException */ public function testAddFilesFromGlobRecursive() @@ -330,23 +374,28 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - 'text file.txt', - 'Текстовый документ.txt', - 'category/list.txt', - 'category/Pictures/128x160/Car/01.jpg', - 'category/Pictures/128x160/Car/02.jpg', - 'category/Pictures/240x320/Car/01.jpg', - 'category/Pictures/240x320/Car/02.jpg', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + 'text file.txt', + 'Текстовый документ.txt', + 'category/list.txt', + 'category/Pictures/128x160/Car/01.jpg', + 'category/Pictures/128x160/Car/02.jpg', + 'category/Pictures/240x320/Car/01.jpg', + 'category/Pictures/240x320/Car/02.jpg', + ], + $localPath + ); $zipFile->close(); } /** - * Create archive and add files from regex pattern + * Create archive and add files from regex pattern. + * * @throws ZipException */ public function testAddFilesFromRegex() @@ -358,18 +407,23 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - 'text file.txt', - 'Текстовый документ.txt', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + 'text file.txt', + 'Текстовый документ.txt', + ], + $localPath + ); $zipFile->close(); } /** - * Create archive and add files recursively from regex pattern + * Create archive and add files recursively from regex pattern. + * * @throws ZipException */ public function testAddFilesFromRegexRecursive() @@ -381,18 +435,22 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, [ - 'text file.txt', - 'Текстовый документ.txt', - 'category/list.txt', - 'category/Pictures/128x160/Car/01.jpg', - 'category/Pictures/128x160/Car/02.jpg', - 'category/Pictures/240x320/Car/01.jpg', - 'category/Pictures/240x320/Car/02.jpg', - ], $localPath); + static::assertFilesResult( + $zipFile, + [ + 'text file.txt', + 'Текстовый документ.txt', + 'category/list.txt', + 'category/Pictures/128x160/Car/01.jpg', + 'category/Pictures/128x160/Car/02.jpg', + 'category/Pictures/240x320/Car/01.jpg', + 'category/Pictures/240x320/Car/02.jpg', + ], + $localPath + ); $zipFile->close(); } @@ -409,10 +467,10 @@ class ZipFileAddDirTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFilesResult($zipFile, array_keys(self::$files), $localPath); + static::assertFilesResult($zipFile, array_keys(self::$files), $localPath); $zipFile->close(); } } diff --git a/tests/PhpZip/ZipFileTest.php b/tests/PhpZip/ZipFileTest.php index 96c291e..3ad1b22 100644 --- a/tests/PhpZip/ZipFileTest.php +++ b/tests/PhpZip/ZipFileTest.php @@ -2,101 +2,120 @@ namespace PhpZip; +use PhpZip\Exception\InvalidArgumentException; use PhpZip\Exception\ZipEntryNotFoundException; use PhpZip\Exception\ZipException; +use PhpZip\Exception\ZipUnsupportMethodException; use PhpZip\Model\ZipEntry; use PhpZip\Model\ZipInfo; -use PhpZip\Util\CryptoUtil; use PhpZip\Util\FilesUtil; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response; /** - * ZipFile test + * ZipFile test. + * + * @internal + * + * @small */ class ZipFileTest extends ZipTestCase { - /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage does not exist + * @throws ZipException */ public function testOpenFileCantExists() { + $this->setExpectedException(ZipException::class, 'does not exist'); + $zipFile = new ZipFile(); - $zipFile->openFile(uniqid()); + $zipFile->openFile(uniqid('', true)); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage can't open + * @throws ZipException */ public function testOpenFileCantOpen() { + $this->setExpectedException(ZipException::class, 'can\'t open'); + /** @noinspection PhpComposerExtensionStubsInspection */ if (posix_getuid() === 0) { - $this->markTestSkipped('Skip the test for a user with root privileges'); + static::markTestSkipped('Skip the test for a user with root privileges'); + + return; } - $this->assertNotFalse(file_put_contents($this->outputFilename, 'content')); - $this->assertTrue(chmod($this->outputFilename, 0222)); + static::assertNotFalse(file_put_contents($this->outputFilename, 'content')); + static::assertTrue(chmod($this->outputFilename, 0222)); $zipFile = new ZipFile(); $zipFile->openFile($this->outputFilename); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Invalid zip file + * @throws ZipException */ public function testOpenFileEmptyFile() { - $this->assertNotFalse(touch($this->outputFilename)); + $this->setExpectedException(ZipException::class, 'Invalid zip file'); + + static::assertNotFalse(touch($this->outputFilename)); $zipFile = new ZipFile(); $zipFile->openFile($this->outputFilename); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Expected Local File Header or (ZIP64) End Of Central Directory Record + * @throws ZipException + * @throws \Exception */ public function testOpenFileInvalidZip() { - $this->assertNotFalse(file_put_contents($this->outputFilename, CryptoUtil::randomBytes(255))); + $this->setExpectedException( + ZipException::class, + 'Expected Local File Header or (ZIP64) End Of Central Directory Record' + ); + + static::assertNotFalse(file_put_contents($this->outputFilename, random_bytes(255))); $zipFile = new ZipFile(); $zipFile->openFile($this->outputFilename); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Empty string passed * @throws ZipException */ public function testOpenFromStringNullString() { + $this->setExpectedException(InvalidArgumentException::class, 'Empty string passed'); + $zipFile = new ZipFile(); $zipFile->openFromString(null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Empty string passed * @throws ZipException */ public function testOpenFromStringEmptyString() { + $this->setExpectedException(InvalidArgumentException::class, 'Empty string passed'); + $zipFile = new ZipFile(); - $zipFile->openFromString(""); + $zipFile->openFromString(''); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Expected Local File Header or (ZIP64) End Of Central Directory Record + * @throws ZipException + * @throws \Exception */ public function testOpenFromStringInvalidZip() { + $this->setExpectedException( + ZipException::class, + 'Expected Local File Header or (ZIP64) End Of Central Directory Record' + ); + $zipFile = new ZipFile(); - $zipFile->openFromString(CryptoUtil::randomBytes(255)); + $zipFile->openFromString(random_bytes(255)); } /** @@ -111,73 +130,79 @@ class ZipFileTest extends ZipTestCase $zipFile->close(); $zipFile->openFromString($zipContents); - $this->assertEquals($zipFile->count(), 2); - $this->assertTrue(isset($zipFile['file'])); - $this->assertTrue(isset($zipFile['file2'])); - $this->assertEquals($zipFile['file'], 'content'); - $this->assertEquals($zipFile['file2'], 'content 2'); + static::assertSame($zipFile->count(), 2); + static::assertTrue(isset($zipFile['file'])); + static::assertTrue(isset($zipFile['file2'])); + static::assertSame($zipFile['file'], 'content'); + static::assertSame($zipFile['file2'], 'content 2'); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid stream resource * @throws ZipException */ public function testOpenFromStreamNullStream() { + $this->setExpectedException(InvalidArgumentException::class, 'Invalid stream resource'); + $zipFile = new ZipFile(); $zipFile->openFromStream(null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid stream resource * @throws ZipException */ public function testOpenFromStreamInvalidResourceType() { + $this->setExpectedException(InvalidArgumentException::class, 'Invalid stream resource'); + $zipFile = new ZipFile(); /** @noinspection PhpParamsInspection */ - $zipFile->openFromStream("stream resource"); + $zipFile->openFromStream('stream resource'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid resource type - gd. * @throws ZipException */ public function testOpenFromStreamInvalidResourceType2() { + $this->setExpectedException(InvalidArgumentException::class, 'Invalid resource type - gd.'); + $zipFile = new ZipFile(); - if (!extension_loaded("gd")) { - $this->markTestSkipped('not extension gd'); + + if (!\extension_loaded('gd')) { + static::markTestSkipped('not extension gd'); + + return; } /** @noinspection PhpComposerExtensionStubsInspection */ $zipFile->openFromStream(imagecreate(1, 1)); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid stream type - dir. * @throws ZipException */ public function testOpenFromStreamInvalidResourceType3() { + $this->setExpectedException(InvalidArgumentException::class, 'Invalid stream type - dir.'); + $zipFile = new ZipFile(); $zipFile->openFromStream(opendir(__DIR__)); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Resource cannot seekable stream. * @throws ZipException + * @noinspection PhpUsageOfSilenceOperatorInspection + * @noinspection NestedPositiveIfStatementsInspection */ public function testOpenFromStreamNoSeekable() { - if (!$fp = @fopen("http://localhost", 'r')) { - if (!$fp = @fopen("http://example.org", 'r')) { - $this->markTestSkipped('not connected to localhost or remote host'); + $this->setExpectedException(InvalidArgumentException::class, 'Resource cannot seekable stream.'); + + if (!$fp = @fopen('http://localhost', 'rb')) { + if (!$fp = @fopen('http://example.org', 'rb')) { + static::markTestSkipped('not connected to localhost or remote host'); + return; } } @@ -187,24 +212,30 @@ class ZipFileTest extends ZipTestCase } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Invalid zip file + * @throws ZipException */ public function testOpenFromStreamEmptyContents() { + $this->setExpectedException(ZipException::class, 'Invalid zip file'); + $fp = fopen($this->outputFilename, 'w+b'); $zipFile = new ZipFile(); $zipFile->openFromStream($fp); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Expected Local File Header or (ZIP64) End Of Central Directory Record + * @throws ZipException + * @throws \Exception */ public function testOpenFromStreamInvalidZip() { + $this->setExpectedException( + ZipException::class, + 'Expected Local File Header or (ZIP64) End Of Central Directory Record' + ); + $fp = fopen($this->outputFilename, 'w+b'); - fwrite($fp, CryptoUtil::randomBytes(255)); + fwrite($fp, random_bytes(255)); $zipFile = new ZipFile(); $zipFile->openFromStream($fp); } @@ -218,18 +249,20 @@ class ZipFileTest extends ZipTestCase $zipFile ->addFromString('file', 'content') ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; $handle = fopen($this->outputFilename, 'rb'); $zipFile->openFromStream($handle); - $this->assertEquals($zipFile->count(), 1); - $this->assertTrue(isset($zipFile['file'])); - $this->assertEquals($zipFile['file'], 'content'); + static::assertSame($zipFile->count(), 1); + static::assertTrue(isset($zipFile['file'])); + static::assertSame($zipFile['file'], 'content'); $zipFile->close(); } /** * Test create, open and extract empty archive. + * * @throws ZipException */ public function testEmptyArchive() @@ -237,56 +270,61 @@ class ZipFileTest extends ZipTestCase $zipFile = new ZipFile(); $zipFile ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; - $this->assertCorrectEmptyZip($this->outputFilename); - $this->assertTrue(mkdir($this->outputDirname, 0755, true)); + static::assertCorrectEmptyZip($this->outputFilename); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->count(), 0); + static::assertSame($zipFile->count(), 0); $zipFile ->extractTo($this->outputDirname) - ->close(); + ->close() + ; - $this->assertTrue(FilesUtil::isEmptyDir($this->outputDirname)); + static::assertTrue(FilesUtil::isEmptyDir($this->outputDirname)); } /** - * No modified archive + * No modified archive. + * + * @throws ZipException * * @see ZipOutputFile::create() - * @throws ZipException */ public function testNoModifiedArchive() { - $this->assertTrue(mkdir($this->outputDirname, 0755, true)); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); - $fileActual = $this->outputDirname . DIRECTORY_SEPARATOR . 'file_actual.zip'; - $fileExpected = $this->outputDirname . DIRECTORY_SEPARATOR . 'file_expected.zip'; + $fileActual = $this->outputDirname . \DIRECTORY_SEPARATOR . 'file_actual.zip'; + $fileExpected = $this->outputDirname . \DIRECTORY_SEPARATOR . 'file_expected.zip'; $zipFile = new ZipFile(); - $zipFile->addDirRecursive(__DIR__.'/../../src'); + $zipFile->addDirRecursive(__DIR__ . '/../../src'); $sourceCount = $zipFile->count(); - $this->assertTrue($sourceCount > 0); + static::assertTrue($sourceCount > 0); $zipFile ->saveAsFile($fileActual) - ->close(); - $this->assertCorrectZipArchive($fileActual); + ->close() + ; + static::assertCorrectZipArchive($fileActual); $zipFile ->openFile($fileActual) - ->saveAsFile($fileExpected); - $this->assertCorrectZipArchive($fileExpected); + ->saveAsFile($fileExpected) + ; + static::assertCorrectZipArchive($fileExpected); $zipFileExpected = new ZipFile(); $zipFileExpected->openFile($fileExpected); - $this->assertEquals($zipFile->count(), $sourceCount); - $this->assertEquals($zipFileExpected->count(), $zipFile->count()); - $this->assertEquals($zipFileExpected->getListFiles(), $zipFile->getListFiles()); + static::assertSame($zipFile->count(), $sourceCount); + static::assertSame($zipFileExpected->count(), $zipFile->count()); + static::assertSame($zipFileExpected->getListFiles(), $zipFile->getListFiles()); foreach ($zipFile as $entryName => $content) { - $this->assertEquals($zipFileExpected[$entryName], $content); + static::assertSame($zipFileExpected[$entryName], $content); } $zipFileExpected->close(); @@ -296,18 +334,19 @@ class ZipFileTest extends ZipTestCase /** * Create archive and add files. * - * @see ZipOutputFile::addFromString() + * @throws ZipException + * * @see ZipOutputFile::addFromFile() * @see ZipOutputFile::addFromStream() * @see ZipFile::getEntryContents() - * @throws ZipException + * @see ZipOutputFile::addFromString() */ public function testCreateArchiveAndAddFiles() { $outputFromString = file_get_contents(__FILE__); - $outputFromString2 = file_get_contents(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'README.md'); - $outputFromFile = file_get_contents(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'phpunit.xml'); - $outputFromStream = file_get_contents(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'composer.json'); + $outputFromString2 = file_get_contents(\dirname(\dirname(__DIR__)) . \DIRECTORY_SEPARATOR . 'README.md'); + $outputFromFile = file_get_contents(\dirname(\dirname(__DIR__)) . \DIRECTORY_SEPARATOR . 'phpunit.xml'); + $outputFromStream = file_get_contents(\dirname(\dirname(__DIR__)) . \DIRECTORY_SEPARATOR . 'composer.json'); $filenameFromString = basename(__FILE__); $filenameFromString2 = 'test_file.txt'; @@ -323,44 +362,46 @@ class ZipFileTest extends ZipTestCase $tempStream = tmpfile(); fwrite($tempStream, $outputFromStream); - $zipFile = new ZipFile; + $zipFile = new ZipFile(); $zipFile ->addFromString($filenameFromString, $outputFromString) ->addFile($tempFile, $filenameFromFile) ->addFromStream($tempStream, $filenameFromStream) - ->addEmptyDir($emptyDirName); + ->addEmptyDir($emptyDirName) + ; $zipFile[$filenameFromString2] = $outputFromString2; $zipFile[$emptyDirName2] = null; $zipFile[$emptyDirName3] = 'this content ignoring'; - $this->assertEquals(count($zipFile), 7); + static::assertSame(\count($zipFile), 7); $zipFile ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; unlink($tempFile); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertEquals(count($zipFile), 7); - $this->assertEquals($zipFile[$filenameFromString], $outputFromString); - $this->assertEquals($zipFile[$filenameFromFile], $outputFromFile); - $this->assertEquals($zipFile[$filenameFromStream], $outputFromStream); - $this->assertEquals($zipFile[$filenameFromString2], $outputFromString2); - $this->assertTrue(isset($zipFile[$emptyDirName])); - $this->assertTrue(isset($zipFile[$emptyDirName2])); - $this->assertTrue(isset($zipFile[$emptyDirName3])); - $this->assertTrue($zipFile->isDirectory($emptyDirName)); - $this->assertTrue($zipFile->isDirectory($emptyDirName2)); - $this->assertTrue($zipFile->isDirectory($emptyDirName3)); + static::assertSame(\count($zipFile), 7); + static::assertSame($zipFile[$filenameFromString], $outputFromString); + static::assertSame($zipFile[$filenameFromFile], $outputFromFile); + static::assertSame($zipFile[$filenameFromStream], $outputFromStream); + static::assertSame($zipFile[$filenameFromString2], $outputFromString2); + static::assertTrue(isset($zipFile[$emptyDirName])); + static::assertTrue(isset($zipFile[$emptyDirName2])); + static::assertTrue(isset($zipFile[$emptyDirName3])); + static::assertTrue($zipFile->isDirectory($emptyDirName)); + static::assertTrue($zipFile->isDirectory($emptyDirName2)); + static::assertTrue($zipFile->isDirectory($emptyDirName3)); $listFiles = $zipFile->getListFiles(); - $this->assertEquals($listFiles[0], $filenameFromString); - $this->assertEquals($listFiles[1], $filenameFromFile); - $this->assertEquals($listFiles[2], $filenameFromStream); - $this->assertEquals($listFiles[3], $emptyDirName); - $this->assertEquals($listFiles[4], $filenameFromString2); - $this->assertEquals($listFiles[5], $emptyDirName2); - $this->assertEquals($listFiles[6], $emptyDirName3); + static::assertSame($listFiles[0], $filenameFromString); + static::assertSame($listFiles[1], $filenameFromFile); + static::assertSame($listFiles[2], $filenameFromStream); + static::assertSame($listFiles[3], $emptyDirName); + static::assertSame($listFiles[4], $filenameFromString2); + static::assertSame($listFiles[5], $emptyDirName2); + static::assertSame($listFiles[6], $emptyDirName3); $zipFile->close(); } @@ -376,22 +417,25 @@ class ZipFileTest extends ZipTestCase $zipFile->close(); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile['file'], ''); + static::assertSame($zipFile['file'], ''); $zipFile->close(); } /** * Test compression method from image file. + * * @throws ZipException */ public function testCompressionMethodFromImageMimeType() { - if (!function_exists('mime_content_type')) { - $this->markTestSkipped('Function mime_content_type not exists'); + if (!\function_exists('mime_content_type')) { + static::markTestSkipped('Function mime_content_type not exists'); + + return; } $outputFilename = $this->outputFilename; $this->outputFilename .= '.gif'; - $this->assertNotFalse( + static::assertNotFalse( file_put_contents( $this->outputFilename, base64_decode('R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw==') @@ -407,12 +451,13 @@ class ZipFileTest extends ZipTestCase $zipFile->openFile($this->outputFilename); $info = $zipFile->getEntryInfo($basename); - $this->assertEquals($info->getMethodName(), 'No compression'); + static::assertSame($info->getMethodName(), 'No compression'); $zipFile->close(); } /** * Rename zip entry name. + * * @throws ZipException */ public function testRename() @@ -425,7 +470,7 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); $zipFile->rename($oldName, $newName); @@ -438,49 +483,49 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFalse(isset($zipFile[$oldName])); - $this->assertTrue(isset($zipFile[$newName])); - $this->assertFalse(isset($zipFile['file1.txt'])); - $this->assertFalse(isset($zipFile['file2.txt'])); - $this->assertFalse(isset($zipFile['file3.txt'])); - $this->assertTrue(isset($zipFile['file_long_name.txt'])); - $this->assertTrue(isset($zipFile['file4.txt'])); - $this->assertTrue(isset($zipFile['fi.txt'])); + static::assertFalse(isset($zipFile[$oldName])); + static::assertTrue(isset($zipFile[$newName])); + static::assertFalse(isset($zipFile['file1.txt'])); + static::assertFalse(isset($zipFile['file2.txt'])); + static::assertFalse(isset($zipFile['file3.txt'])); + static::assertTrue(isset($zipFile['file_long_name.txt'])); + static::assertTrue(isset($zipFile['file4.txt'])); + static::assertTrue(isset($zipFile['fi.txt'])); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage name is null * @throws ZipException */ public function testRenameEntryNull() { + $this->setExpectedException(InvalidArgumentException::class, 'name is null'); + $zipFile = new ZipFile(); $zipFile->rename(null, 'new-file'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage name is null * @throws ZipException */ public function testRenameEntryNull2() { + $this->setExpectedException(InvalidArgumentException::class, 'name is null'); + $zipFile = new ZipFile(); $zipFile->rename('old-file', null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage is exists * @throws ZipException */ - public function testRenameEntryNewEntyExists() + public function testRenameEntryToExistsNewEntry() { + $this->setExpectedException(InvalidArgumentException::class, 'is exists'); + $zipFile = new ZipFile(); $zipFile['file'] = 'content'; $zipFile['file2'] = 'content 2'; @@ -493,11 +538,12 @@ class ZipFileTest extends ZipTestCase } /** - * @expectedException \PhpZip\Exception\ZipEntryNotFoundException * @throws ZipException */ public function testRenameEntryNotFound() { + $this->setExpectedException(ZipEntryNotFoundException::class); + $zipFile = new ZipFile(); $zipFile['file'] = 'content'; $zipFile['file2'] = 'content 2'; @@ -511,11 +557,12 @@ class ZipFileTest extends ZipTestCase /** * Delete entry from name. + * * @throws ZipException */ public function testDeleteFromName() { - $inputDir = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $inputDir = \dirname(\dirname(__DIR__)) . \DIRECTORY_SEPARATOR; $deleteEntryName = 'composer.json'; $zipFile = new ZipFile(); @@ -523,17 +570,17 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); $zipFile->deleteFromName($deleteEntryName); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFalse(isset($zipFile[$deleteEntryName])); + static::assertFalse(isset($zipFile[$deleteEntryName])); $zipFile->close(); } @@ -551,93 +598,93 @@ class ZipFileTest extends ZipTestCase $zipFile->close(); $zipFile->openFile($this->outputFilename); - $this->assertEquals(sizeof($zipFile), 1); - $this->assertTrue(isset($zipFile['entry1'])); - $this->assertFalse(isset($zipFile['entry2'])); + static::assertSame(\count($zipFile), 1); + static::assertTrue(isset($zipFile['entry1'])); + static::assertFalse(isset($zipFile['entry2'])); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\ZipEntryNotFoundException + * @throws ZipEntryNotFoundException */ public function testDeleteFromNameNotFoundEntry() { + $this->setExpectedException(ZipEntryNotFoundException::class); + $zipFile = new ZipFile(); $zipFile->deleteFromName('entry'); } /** - * Delete zip entries from glob pattern + * Delete zip entries from glob pattern. + * * @throws ZipException */ public function testDeleteFromGlob() { - $inputDir = dirname(dirname(__DIR__)); + $inputDir = \dirname(\dirname(__DIR__)); $zipFile = new ZipFile(); $zipFile->addFilesFromGlobRecursive($inputDir, '**.{xml,json,md}', '/'); - $this->assertTrue(isset($zipFile['composer.json'])); - $this->assertTrue(isset($zipFile['phpunit.xml'])); + static::assertTrue(isset($zipFile['composer.json'])); + static::assertTrue(isset($zipFile['phpunit.xml'])); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertTrue(isset($zipFile['composer.json'])); - $this->assertTrue(isset($zipFile['phpunit.xml'])); + static::assertTrue(isset($zipFile['composer.json'])); + static::assertTrue(isset($zipFile['phpunit.xml'])); $zipFile->deleteFromGlob('**.{xml,json}'); - $this->assertFalse(isset($zipFile['composer.json'])); - $this->assertFalse(isset($zipFile['phpunit.xml'])); + static::assertFalse(isset($zipFile['composer.json'])); + static::assertFalse(isset($zipFile['phpunit.xml'])); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertTrue($zipFile->count() > 0); + static::assertTrue($zipFile->count() > 0); foreach ($zipFile->getListFiles() as $name) { - $this->assertStringEndsWith('.md', $name); + static::assertStringEndsWith('.md', $name); } $zipFile->close(); } - /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The glob pattern is not specified - */ public function testDeleteFromGlobFailNull() { + $this->setExpectedException(InvalidArgumentException::class, 'The glob pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->deleteFromGlob(null); } - /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The glob pattern is not specified - */ public function testDeleteFromGlobFailEmpty() { + $this->setExpectedException(InvalidArgumentException::class, 'The glob pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->deleteFromGlob(''); } /** - * Delete entries from regex pattern + * Delete entries from regex pattern. + * * @throws ZipException */ public function testDeleteFromRegex() { - $inputDir = dirname(dirname(__DIR__)); + $inputDir = \dirname(\dirname(__DIR__)); $zipFile = new ZipFile(); $zipFile->addFilesFromRegexRecursive($inputDir, '~\.(xml|json)$~i', 'Path'); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); $zipFile->deleteFromRegex('~\.(json)$~i'); @@ -646,75 +693,73 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertFalse(isset($zipFile['Path/composer.json'])); - $this->assertFalse(isset($zipFile['Path/test.txt'])); - $this->assertTrue(isset($zipFile['Path/phpunit.xml'])); + static::assertFalse(isset($zipFile['Path/composer.json'])); + static::assertFalse(isset($zipFile['Path/test.txt'])); + static::assertTrue(isset($zipFile['Path/phpunit.xml'])); $zipFile->close(); } - /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The regex pattern is not specified - */ public function testDeleteFromRegexFailNull() { + $this->setExpectedException(InvalidArgumentException::class, 'The regex pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->deleteFromRegex(null); } - /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The regex pattern is not specified - */ public function testDeleteFromRegexFailEmpty() { + $this->setExpectedException(InvalidArgumentException::class, 'The regex pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->deleteFromRegex(''); } /** - * Delete all entries + * Delete all entries. + * * @throws ZipException */ public function testDeleteAll() { $zipFile = new ZipFile(); - $zipFile->addDirRecursive(dirname(dirname(__DIR__)) .DIRECTORY_SEPARATOR. 'src'); - $this->assertTrue($zipFile->count() > 0); + $zipFile->addDirRecursive(\dirname(\dirname(__DIR__)) . \DIRECTORY_SEPARATOR . 'src'); + static::assertTrue($zipFile->count() > 0); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertTrue($zipFile->count() > 0); + static::assertTrue($zipFile->count() > 0); $zipFile->deleteAll(); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectEmptyZip($this->outputFilename); + static::assertCorrectEmptyZip($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->count(), 0); + static::assertSame($zipFile->count(), 0); $zipFile->close(); } /** * Test zip archive comment. + * * @throws ZipException */ public function testArchiveComment() { - $comment = "This zip file comment" . PHP_EOL - . "Αυτό το σχόλιο αρχείο zip" . PHP_EOL - . "Это комментарий zip архива" . PHP_EOL - . "這個ZIP文件註釋" . PHP_EOL - . "ეს zip ფაილის კომენტარი" . PHP_EOL - . "このzipファイルにコメント" . PHP_EOL - . "ความคิดเห็นนี้ไฟล์ซิป"; + $comment = 'This zip file comment' . \PHP_EOL + . 'Αυτό το σχόλιο αρχείο zip' . \PHP_EOL + . 'Это комментарий zip архива' . \PHP_EOL + . '這個ZIP文件註釋' . \PHP_EOL + . 'ეს zip ფაილის კომენტარი' . \PHP_EOL + . 'このzipファイルにコメント' . \PHP_EOL + . 'ความคิดเห็นนี้ไฟล์ซิป'; $zipFile = new ZipFile(); $zipFile->setArchiveComment($comment); @@ -722,32 +767,32 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->getArchiveComment(), $comment); + static::assertSame($zipFile->getArchiveComment(), $comment); $zipFile->setArchiveComment(null); // remove archive comment $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); // check empty comment $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->getArchiveComment(), ""); + static::assertNull($zipFile->getArchiveComment()); $zipFile->close(); } /** * Test very long archive comment. - * - * @expectedException \PhpZip\Exception\InvalidArgumentException */ public function testVeryLongArchiveComment() { - $comment = "Very long comment" . PHP_EOL . - "Очень длинный комментарий" . PHP_EOL; - $comment = str_repeat($comment, ceil(0xffff / strlen($comment)) + strlen($comment) + 1); + $this->setExpectedException(InvalidArgumentException::class); + + $comment = 'Very long comment' . \PHP_EOL . + 'Очень длинный комментарий' . \PHP_EOL; + $comment = str_repeat($comment, ceil(0xffff / \strlen($comment)) + \strlen($comment) + 1); $zipFile = new ZipFile(); $zipFile->setArchiveComment($comment); @@ -755,39 +800,42 @@ class ZipFileTest extends ZipTestCase /** * Test zip entry comment. + * * @throws ZipException + * @throws \Exception */ public function testEntryComment() { $entries = [ '文件1.txt' => [ - 'data' => CryptoUtil::randomBytes(255), - 'comment' => "這是註釋的條目。", + 'data' => random_bytes(255), + 'comment' => '這是註釋的條目。', ], 'file2.txt' => [ - 'data' => CryptoUtil::randomBytes(255), - 'comment' => null + 'data' => random_bytes(255), + 'comment' => null, ], 'file3.txt' => [ - 'data' => CryptoUtil::randomBytes(255), - 'comment' => CryptoUtil::randomBytes(255), + 'data' => random_bytes(255), + 'comment' => random_bytes(255), ], 'file4.txt' => [ - 'data' => CryptoUtil::randomBytes(255), - 'comment' => "Комментарий файла" + 'data' => random_bytes(255), + 'comment' => 'Комментарий файла', ], 'file5.txt' => [ - 'data' => CryptoUtil::randomBytes(255), - 'comment' => "ไฟล์แสดงความคิดเห็น" + 'data' => random_bytes(255), + 'comment' => 'ไฟล์แสดงความคิดเห็น', ], 'file6 emoji 🙍🏼.txt' => [ - 'data' => CryptoUtil::randomBytes(255), - 'comment' => "Emoji comment file - 😀 ⛈ ❤️ 🤴🏽" + 'data' => random_bytes(255), + 'comment' => 'Emoji comment file - 😀 ⛈ ❤️ 🤴🏽', ], ]; // create archive with entry comments $zipFile = new ZipFile(); + foreach ($entries as $entryName => $item) { $zipFile->addFromString($entryName, $item['data']); $zipFile->setEntryComment($entryName, $item['comment']); @@ -795,15 +843,16 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); // check and modify comments $zipFile->openFile($this->outputFilename); + foreach ($zipFile->getListFiles() as $entryName) { $entriesItem = $entries[$entryName]; - $this->assertNotEmpty($entriesItem); - $this->assertEquals($zipFile[$entryName], $entriesItem['data']); - $this->assertEquals($zipFile->getEntryComment($entryName), (string)$entriesItem['comment']); + static::assertNotEmpty($entriesItem); + static::assertSame($zipFile[$entryName], $entriesItem['data']); + static::assertSame($zipFile->getEntryComment($entryName), (string) $entriesItem['comment']); } // modify comment $entries['file5.txt']['comment'] = mt_rand(1, 100000000); @@ -811,14 +860,15 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); // check modify comments $zipFile->openFile($this->outputFilename); + foreach ($entries as $entryName => $entriesItem) { - $this->assertTrue(isset($zipFile[$entryName])); - $this->assertEquals($zipFile->getEntryComment($entryName), (string)$entriesItem['comment']); - $this->assertEquals($zipFile[$entryName], $entriesItem['data']); + static::assertTrue(isset($zipFile[$entryName])); + static::assertSame($zipFile->getEntryComment($entryName), (string) $entriesItem['comment']); + static::assertSame($zipFile[$entryName], $entriesItem['data']); } $zipFile->close(); } @@ -826,14 +876,15 @@ class ZipFileTest extends ZipTestCase /** * Test zip entry very long comment. * - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Comment too long + * @throws ZipException */ public function testVeryLongEntryComment() { - $comment = "Very long comment" . PHP_EOL . - "Очень длинный комментарий" . PHP_EOL; - $comment = str_repeat($comment, ceil(0xffff / strlen($comment)) + strlen($comment) + 1); + $this->setExpectedException(ZipException::class, 'Comment too long'); + + $comment = 'Very long comment' . \PHP_EOL . + 'Очень длинный комментарий' . \PHP_EOL; + $comment = str_repeat($comment, ceil(0xffff / \strlen($comment)) + \strlen($comment) + 1); $zipFile = new ZipFile(); $zipFile->addFile(__FILE__, 'test'); @@ -841,98 +892,107 @@ class ZipFileTest extends ZipTestCase } /** - * @expectedException \PhpZip\Exception\ZipEntryNotFoundException * @throws ZipException */ public function testSetEntryCommentNotFoundEntry() { + $this->setExpectedException(ZipEntryNotFoundException::class); + $zipFile = new ZipFile(); $zipFile->setEntryComment('test', 'comment'); } /** * Test all available support compression methods. + * * @throws ZipException + * @throws \Exception */ public function testCompressionMethod() { $entries = [ '1' => [ - 'data' => CryptoUtil::randomBytes(255), - 'method' => ZipFileInterface::METHOD_STORED, + 'data' => random_bytes(255), + 'method' => ZipFile::METHOD_STORED, 'expected' => 'No compression', ], '2' => [ - 'data' => CryptoUtil::randomBytes(255), - 'method' => ZipFileInterface::METHOD_DEFLATED, + 'data' => random_bytes(255), + 'method' => ZipFile::METHOD_DEFLATED, 'expected' => 'Deflate', ], ]; - if (extension_loaded("bz2")) { + + if (\extension_loaded('bz2')) { $entries['3'] = [ - 'data' => CryptoUtil::randomBytes(255), - 'method' => ZipFileInterface::METHOD_BZIP2, + 'data' => random_bytes(255), + 'method' => ZipFile::METHOD_BZIP2, 'expected' => 'Bzip2', ]; } $zipFile = new ZipFile(); + foreach ($entries as $entryName => $item) { $zipFile->addFromString($entryName, $item['data'], $item['method']); } $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $zipFile->setCompressionLevel(ZipFileInterface::LEVEL_BEST_COMPRESSION); + $zipFile->setCompressionLevel(ZipFile::LEVEL_BEST_COMPRESSION); $zipAllInfo = $zipFile->getAllInfo(); foreach ($zipAllInfo as $entryName => $info) { - $this->assertEquals($zipFile[$entryName], $entries[$entryName]['data']); - $this->assertEquals($info->getMethodName(), $entries[$entryName]['expected']); + static::assertSame($zipFile[$entryName], $entries[$entryName]['data']); + static::assertSame($info->getMethodName(), $entries[$entryName]['expected']); $entryInfo = $zipFile->getEntryInfo($entryName); - $this->assertEquals($entryInfo, $info); + static::assertEquals($entryInfo, $info); } $zipFile->close(); } - /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid compression level. Minimum level -1. Maximum level 9 - */ public function testSetInvalidCompressionLevel() { + $this->setExpectedException( + InvalidArgumentException::class, + 'Invalid compression level. Minimum level -1. Maximum level 9' + ); + $zipFile = new ZipFile(); $zipFile->setCompressionLevel(-2); } - /** - * /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid compression level. Minimum level -1. Maximum level 9 - */ public function testSetInvalidCompressionLevel2() { + $this->setExpectedException( + InvalidArgumentException::class, + 'Invalid compression level. Minimum level -1. Maximum level 9' + ); + $zipFile = new ZipFile(); $zipFile->setCompressionLevel(10); } /** * Test extract all files. + * * @throws ZipException + * @throws \Exception */ public function testExtract() { $entries = [ - 'test1.txt' => CryptoUtil::randomBytes(255), - 'test2.txt' => CryptoUtil::randomBytes(255), - 'test/test 2/test3.txt' => CryptoUtil::randomBytes(255), + 'test1.txt' => random_bytes(255), + 'test2.txt' => random_bytes(255), + 'test/test 2/test3.txt' => random_bytes(255), 'test empty/dir' => null, ]; $zipFile = new ZipFile(); + foreach ($entries as $entryName => $value) { if ($value === null) { $zipFile->addEmptyDir($entryName); @@ -943,38 +1003,42 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertTrue(mkdir($this->outputDirname, 0755, true)); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); $zipFile->openFile($this->outputFilename); $zipFile->extractTo($this->outputDirname); + foreach ($entries as $entryName => $value) { - $fullExtractedFilename = $this->outputDirname . DIRECTORY_SEPARATOR . $entryName; + $fullExtractedFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . $entryName; + if ($value === null) { - $this->assertTrue(is_dir($fullExtractedFilename)); - $this->assertTrue(FilesUtil::isEmptyDir($fullExtractedFilename)); + static::assertTrue(is_dir($fullExtractedFilename)); + static::assertTrue(FilesUtil::isEmptyDir($fullExtractedFilename)); } else { - $this->assertTrue(is_file($fullExtractedFilename)); + static::assertTrue(is_file($fullExtractedFilename)); $contents = file_get_contents($fullExtractedFilename); - $this->assertEquals($contents, $value); + static::assertSame($contents, $value); } } $zipFile->close(); } /** - * Test extract some files + * Test extract some files. + * * @throws ZipException + * @throws \Exception */ public function testExtractSomeFiles() { $entries = [ - 'test1.txt' => CryptoUtil::randomBytes(255), - 'test2.txt' => CryptoUtil::randomBytes(255), - 'test3.txt' => CryptoUtil::randomBytes(255), - 'test4.txt' => CryptoUtil::randomBytes(255), - 'test5.txt' => CryptoUtil::randomBytes(255), - 'test/test/test.txt' => CryptoUtil::randomBytes(255), - 'test/test/test 2.txt' => CryptoUtil::randomBytes(255), + 'test1.txt' => random_bytes(255), + 'test2.txt' => random_bytes(255), + 'test3.txt' => random_bytes(255), + 'test4.txt' => random_bytes(255), + 'test5.txt' => random_bytes(255), + 'test/test/test.txt' => random_bytes(255), + 'test/test/test 2.txt' => random_bytes(255), 'test empty/dir/' => null, 'test empty/dir2/' => null, ]; @@ -984,10 +1048,10 @@ class ZipFileTest extends ZipTestCase 'test3.txt', 'test5.txt', 'test/test/test 2.txt', - 'test empty/dir2/' + 'test empty/dir2/', ]; - $this->assertTrue(mkdir($this->outputDirname, 0755, true)); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); $zipFile = new ZipFile(); $zipFile->addAll($entries); @@ -998,37 +1062,37 @@ class ZipFileTest extends ZipTestCase $zipFile->extractTo($this->outputDirname, $extractEntries); foreach ($entries as $entryName => $value) { - $fullExtractFilename = $this->outputDirname . DIRECTORY_SEPARATOR . $entryName; - if (in_array($entryName, $extractEntries)) { + $fullExtractFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . $entryName; + + if (\in_array($entryName, $extractEntries, true)) { if ($value === null) { - $this->assertTrue(is_dir($fullExtractFilename)); - $this->assertTrue(FilesUtil::isEmptyDir($fullExtractFilename)); + static::assertTrue(is_dir($fullExtractFilename)); + static::assertTrue(FilesUtil::isEmptyDir($fullExtractFilename)); } else { - $this->assertTrue(is_file($fullExtractFilename)); + static::assertTrue(is_file($fullExtractFilename)); $contents = file_get_contents($fullExtractFilename); - $this->assertEquals($contents, $value); + static::assertEquals($contents, $value); } + } elseif ($value === null) { + static::assertFalse(is_dir($fullExtractFilename)); } else { - if ($value === null) { - $this->assertFalse(is_dir($fullExtractFilename)); - } else { - $this->assertFalse(is_file($fullExtractFilename)); - } + static::assertFalse(is_file($fullExtractFilename)); } } - $this->assertFalse(is_file($this->outputDirname . DIRECTORY_SEPARATOR . 'test/test/test.txt')); + static::assertFalse(is_file($this->outputDirname . \DIRECTORY_SEPARATOR . 'test/test/test.txt')); $zipFile->extractTo($this->outputDirname, 'test/test/test.txt'); - $this->assertTrue(is_file($this->outputDirname . DIRECTORY_SEPARATOR . 'test/test/test.txt')); + static::assertTrue(is_file($this->outputDirname . \DIRECTORY_SEPARATOR . 'test/test/test.txt')); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage not found + * @throws ZipException */ public function testExtractFail() { + $this->setExpectedException(ZipException::class, 'not found'); + $zipFile = new ZipFile(); $zipFile['file'] = 'content'; $zipFile->saveAsFile($this->outputFilename); @@ -1039,11 +1103,12 @@ class ZipFileTest extends ZipTestCase } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Destination is not directory + * @throws ZipException */ public function testExtractFail2() { + $this->setExpectedException(ZipException::class, 'Destination is not directory'); + $zipFile = new ZipFile(); $zipFile['file'] = 'content'; $zipFile->saveAsFile($this->outputFilename); @@ -1054,14 +1119,17 @@ class ZipFileTest extends ZipTestCase } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Destination is not writable directory + * @throws ZipException */ public function testExtractFail3() { + $this->setExpectedException(ZipException::class, 'Destination is not writable directory'); + /** @noinspection PhpComposerExtensionStubsInspection */ if (posix_getuid() === 0) { - $this->markTestSkipped('Skip the test for a user with root privileges'); + static::markTestSkipped('Skip the test for a user with root privileges'); + + return; } $zipFile = new ZipFile(); @@ -1069,79 +1137,82 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertTrue(mkdir($this->outputDirname, 0444, true)); - $this->assertTrue(chmod($this->outputDirname, 0444)); + static::assertTrue(mkdir($this->outputDirname, 0444, true)); + static::assertTrue(chmod($this->outputDirname, 0444)); $zipFile->openFile($this->outputFilename); $zipFile->extractTo($this->outputDirname); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage entryName is null + * @noinspection OnlyWritesOnParameterInspection */ public function testAddFromArrayAccessNullName() { + $this->setExpectedException(InvalidArgumentException::class, 'entryName is null'); + $zipFile = new ZipFile(); $zipFile[null] = 'content'; } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage entryName is empty + * @noinspection OnlyWritesOnParameterInspection */ public function testAddFromArrayAccessEmptyName() { + $this->setExpectedException(InvalidArgumentException::class, 'entryName is empty'); + $zipFile = new ZipFile(); $zipFile[''] = 'content'; } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Contents is null * @throws ZipException */ public function testAddFromStringNullContents() { + $this->setExpectedException(InvalidArgumentException::class, 'Contents is null'); + $zipFile = new ZipFile(); $zipFile->addFromString('file', null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Entry name is null * @throws ZipException */ public function testAddFromStringNullEntryName() { + $this->setExpectedException(InvalidArgumentException::class, 'Entry name is null'); + $zipFile = new ZipFile(); $zipFile->addFromString(null, 'contents'); } /** - * @expectedException \PhpZip\Exception\ZipUnsupportMethodException - * @expectedExceptionMessage Unsupported compression method * @throws ZipException */ public function testAddFromStringUnsupportedMethod() { + $this->setExpectedException(ZipUnsupportMethodException::class, 'Unsupported compression method'); + $zipFile = new ZipFile(); $zipFile->addFromString('file', 'contents', ZipEntry::METHOD_WINZIP_AES); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Empty entry name * @throws ZipException */ public function testAddFromStringEmptyEntryName() { + $this->setExpectedException(InvalidArgumentException::class, 'Empty entry name'); + $zipFile = new ZipFile(); $zipFile->addFromString('', 'contents'); } /** * Test compression method from add string. + * * @throws ZipException */ public function testAddFromStringCompressionMethod() @@ -1149,8 +1220,8 @@ class ZipFileTest extends ZipTestCase $fileStored = sys_get_temp_dir() . '/zip-stored.txt'; $fileDeflated = sys_get_temp_dir() . '/zip-deflated.txt'; - $this->assertNotFalse(file_put_contents($fileStored, 'content')); - $this->assertNotFalse(file_put_contents($fileDeflated, str_repeat('content', 200))); + static::assertNotFalse(file_put_contents($fileStored, 'content')); + static::assertNotFalse(file_put_contents($fileDeflated, str_repeat('content', 200))); $zipFile = new ZipFile(); $zipFile->addFromString(basename($fileStored), file_get_contents($fileStored)); @@ -1164,43 +1235,43 @@ class ZipFileTest extends ZipTestCase $zipFile->openFile($this->outputFilename); $infoStored = $zipFile->getEntryInfo(basename($fileStored)); $infoDeflated = $zipFile->getEntryInfo(basename($fileDeflated)); - $this->assertEquals($infoStored->getMethodName(), 'No compression'); - $this->assertEquals($infoDeflated->getMethodName(), 'Deflate'); + static::assertSame($infoStored->getMethodName(), 'No compression'); + static::assertSame($infoDeflated->getMethodName(), 'Deflate'); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Stream is not resource * @throws ZipException */ public function testAddFromStreamInvalidResource() { + $this->setExpectedException(InvalidArgumentException::class, 'Stream is not resource'); + $zipFile = new ZipFile(); /** @noinspection PhpParamsInspection */ - $zipFile->addFromStream("invalid resource", "name"); + $zipFile->addFromStream('invalid resource', 'name'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Empty entry name * @throws ZipException */ public function testAddFromStreamEmptyEntryName() { + $this->setExpectedException(InvalidArgumentException::class, 'Empty entry name'); + $handle = fopen(__FILE__, 'rb'); $zipFile = new ZipFile(); - $zipFile->addFromStream($handle, ""); + $zipFile->addFromStream($handle, ''); } /** - * @expectedException \PhpZip\Exception\ZipUnsupportMethodException - * @expectedExceptionMessage Unsupported method * @throws ZipException */ public function testAddFromStreamUnsupportedMethod() { + $this->setExpectedException(ZipUnsupportMethodException::class, 'Unsupported method'); + $handle = fopen(__FILE__, 'rb'); $zipFile = new ZipFile(); @@ -1209,6 +1280,7 @@ class ZipFileTest extends ZipTestCase /** * Test compression method from add stream. + * * @throws ZipException */ public function testAddFromStreamCompressionMethod() @@ -1216,8 +1288,8 @@ class ZipFileTest extends ZipTestCase $fileStored = sys_get_temp_dir() . '/zip-stored.txt'; $fileDeflated = sys_get_temp_dir() . '/zip-deflated.txt'; - $this->assertNotFalse(file_put_contents($fileStored, 'content')); - $this->assertNotFalse(file_put_contents($fileDeflated, str_repeat('content', 200))); + static::assertNotFalse(file_put_contents($fileStored, 'content')); + static::assertNotFalse(file_put_contents($fileDeflated, str_repeat('content', 200))); $fpStored = fopen($fileStored, 'rb'); $fpDeflated = fopen($fileDeflated, 'rb'); @@ -1234,376 +1306,381 @@ class ZipFileTest extends ZipTestCase $zipFile->openFile($this->outputFilename); $infoStored = $zipFile->getEntryInfo(basename($fileStored)); $infoDeflated = $zipFile->getEntryInfo(basename($fileDeflated)); - $this->assertEquals($infoStored->getMethodName(), 'No compression'); - $this->assertEquals($infoDeflated->getMethodName(), 'Deflate'); + static::assertSame($infoStored->getMethodName(), 'No compression'); + static::assertSame($infoDeflated->getMethodName(), 'Deflate'); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage file is null * @throws ZipException */ public function testAddFileNullFileName() { + $this->setExpectedException(InvalidArgumentException::class, 'file is null'); + $zipFile = new ZipFile(); $zipFile->addFile(null); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage does not exist + * @throws ZipException */ public function testAddFileCantExists() { + $this->setExpectedException(ZipException::class, 'does not exist'); + $zipFile = new ZipFile(); $zipFile->addFile('path/to/file'); } /** - * @expectedException \PhpZip\Exception\ZipUnsupportMethodException - * @expectedExceptionMessage Unsupported compression method 99 * @throws ZipException */ public function testAddFileUnsupportedMethod() { + $this->setExpectedException(ZipUnsupportMethodException::class, 'Unsupported compression method 99'); + $zipFile = new ZipFile(); $zipFile->addFile(__FILE__, null, ZipEntry::METHOD_WINZIP_AES); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage file could not be read * @throws ZipException */ public function testAddFileCantOpen() { + $this->setExpectedException(ZipException::class, 'file could not be read'); + /** @noinspection PhpComposerExtensionStubsInspection */ if (posix_getuid() === 0) { - $this->markTestSkipped('Skip the test for a user with root privileges'); + static::markTestSkipped('Skip the test for a user with root privileges'); + + return; } - $this->assertNotFalse(file_put_contents($this->outputFilename, '')); - $this->assertTrue(chmod($this->outputFilename, 0244)); + static::assertNotFalse(file_put_contents($this->outputFilename, '')); + static::assertTrue(chmod($this->outputFilename, 0244)); $zipFile = new ZipFile(); $zipFile->addFile($this->outputFilename); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Input dir is null * @throws ZipException */ public function testAddDirNullDirname() { + $this->setExpectedException(InvalidArgumentException::class, 'Input dir is null'); + $zipFile = new ZipFile(); $zipFile->addDir(null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddDirEmptyDirname() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); - $zipFile->addDir(""); + $zipFile->addDir(''); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage does not exist * @throws ZipException */ public function testAddDirCantExists() { + $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); + $zipFile = new ZipFile(); - $zipFile->addDir(uniqid()); + $zipFile->addDir(uniqid('', true)); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Input dir is null * @throws ZipException */ public function testAddDirRecursiveNullDirname() { + $this->setExpectedException(InvalidArgumentException::class, 'Input dir is null'); + $zipFile = new ZipFile(); $zipFile->addDirRecursive(null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddDirRecursiveEmptyDirname() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); - $zipFile->addDirRecursive(""); + $zipFile->addDirRecursive(''); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage does not exist * @throws ZipException */ public function testAddDirRecursiveCantExists() { + $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); + $zipFile = new ZipFile(); - $zipFile->addDirRecursive(uniqid()); + $zipFile->addDirRecursive(uniqid('', true)); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Input dir is null * @throws ZipException */ public function testAddFilesFromGlobNull() { + $this->setExpectedException(InvalidArgumentException::class, 'Input dir is null'); + $zipFile = new ZipFile(); $zipFile->addFilesFromGlob(null, '*.png'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddFilesFromGlobEmpty() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromGlob("", '*.png'); + $zipFile->addFilesFromGlob('', '*.png'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage does not exist * @throws ZipException */ public function testAddFilesFromGlobCantExists() { + $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromGlob("path/to/path", '*.png'); + $zipFile->addFilesFromGlob('path/to/path', '*.png'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The glob pattern is not specified * @throws ZipException */ public function testAddFilesFromGlobNullPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The glob pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromGlob(__DIR__, null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The glob pattern is not specified * @throws ZipException */ public function testAddFilesFromGlobEmptyPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The glob pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromGlob(__DIR__, ''); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Input dir is null * @throws ZipException */ public function testAddFilesFromGlobRecursiveNull() { + $this->setExpectedException(InvalidArgumentException::class, 'Input dir is null'); + $zipFile = new ZipFile(); $zipFile->addFilesFromGlobRecursive(null, '*.png'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddFilesFromGlobRecursiveEmpty() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromGlobRecursive("", '*.png'); + $zipFile->addFilesFromGlobRecursive('', '*.png'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage does not exist * @throws ZipException */ public function testAddFilesFromGlobRecursiveCantExists() { + $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromGlobRecursive("path/to/path", '*.png'); + $zipFile->addFilesFromGlobRecursive('path/to/path', '*.png'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The glob pattern is not specified * @throws ZipException */ public function testAddFilesFromGlobRecursiveNullPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The glob pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromGlobRecursive(__DIR__, null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The glob pattern is not specified * @throws ZipException */ public function testAddFilesFromGlobRecursiveEmptyPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The glob pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromGlobRecursive(__DIR__, ''); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddFilesFromRegexDirectoryNull() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromRegex(null, '~\.png$~i'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddFilesFromRegexDirectoryEmpty() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromRegex("", '~\.png$~i'); + $zipFile->addFilesFromRegex('', '~\.png$~i'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage does not exist * @throws ZipException */ public function testAddFilesFromRegexCantExists() { + $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromRegex("path/to/path", '~\.png$~i'); + $zipFile->addFilesFromRegex('path/to/path', '~\.png$~i'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The regex pattern is not specified * @throws ZipException */ public function testAddFilesFromRegexNullPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The regex pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromRegex(__DIR__, null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The regex pattern is not specified * @throws ZipException */ public function testAddFilesFromRegexEmptyPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The regex pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromRegex(__DIR__, ''); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddFilesFromRegexRecursiveDirectoryNull() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromRegexRecursive(null, '~\.png$~i'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The input directory is not specified * @throws ZipException */ public function testAddFilesFromRegexRecursiveEmpty() { + $this->setExpectedException(InvalidArgumentException::class, 'The input directory is not specified'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromRegexRecursive("", '~\.png$~i'); + $zipFile->addFilesFromRegexRecursive('', '~\.png$~i'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage does not exist * @throws ZipException */ public function testAddFilesFromRegexRecursiveCantExists() { + $this->setExpectedException(InvalidArgumentException::class, 'does not exist'); + $zipFile = new ZipFile(); - $zipFile->addFilesFromGlobRecursive("path/to/path", '~\.png$~i'); + $zipFile->addFilesFromGlobRecursive('path/to/path', '~\.png$~i'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The regex pattern is not specified * @throws ZipException */ public function testAddFilesFromRegexRecursiveNullPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The regex pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromRegexRecursive(__DIR__, null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage The regex pattern is not specified * @throws ZipException */ public function testAddFilesFromRegexRecursiveEmptyPattern() { + $this->setExpectedException(InvalidArgumentException::class, 'The regex pattern is not specified'); + $zipFile = new ZipFile(); $zipFile->addFilesFromRegexRecursive(__DIR__, ''); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage handle is not resource * @throws ZipException */ public function testSaveAsStreamBadStream() { + $this->setExpectedException(InvalidArgumentException::class, 'handle is not resource'); + $zipFile = new ZipFile(); /** @noinspection PhpParamsInspection */ - $zipFile->saveAsStream("bad stream"); + $zipFile->saveAsStream('bad stream'); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage can not open from write * @throws ZipException */ public function testSaveAsFileNotWritable() { + $this->setExpectedException(InvalidArgumentException::class, 'can not open from write'); + /** @noinspection PhpComposerExtensionStubsInspection */ if (posix_getuid() === 0) { - $this->markTestSkipped('Skip the test for a user with root privileges'); + static::markTestSkipped('Skip the test for a user with root privileges'); + + return; } - $this->assertTrue(mkdir($this->outputDirname, 0444, true)); - $this->assertTrue(chmod($this->outputDirname, 0444)); + static::assertTrue(mkdir($this->outputDirname, 0444, true)); + static::assertTrue(chmod($this->outputDirname, 0444)); - $this->outputFilename = $this->outputDirname . DIRECTORY_SEPARATOR . basename($this->outputFilename); + $this->outputFilename = $this->outputDirname . \DIRECTORY_SEPARATOR . basename($this->outputFilename); $zipFile = new ZipFile(); $zipFile->saveAsFile($this->outputFilename); @@ -1611,42 +1688,47 @@ class ZipFileTest extends ZipTestCase /** * Test `ZipFile` implemented \ArrayAccess, \Countable and |iterator. + * * @throws ZipException + * @throws \Exception */ public function testZipFileArrayAccessAndCountableAndIterator() { $files = []; $numFiles = mt_rand(20, 100); for ($i = 0; $i < $numFiles; $i++) { - $files['file' . $i . '.txt'] = CryptoUtil::randomBytes(255); + $files['file' . $i . '.txt'] = random_bytes(255); } - $methods = [ZipFileInterface::METHOD_STORED, ZipFileInterface::METHOD_DEFLATED]; - if (extension_loaded("bz2")) { - $methods[] = ZipFileInterface::METHOD_BZIP2; + $methods = [ZipFile::METHOD_STORED, ZipFile::METHOD_DEFLATED]; + + if (\extension_loaded('bz2')) { + $methods[] = ZipFile::METHOD_BZIP2; } $zipFile = new ZipFile(); - $zipFile->setCompressionLevel(ZipFileInterface::LEVEL_BEST_SPEED); + $zipFile->setCompressionLevel(ZipFile::LEVEL_BEST_SPEED); + foreach ($files as $entryName => $content) { $zipFile->addFromString($entryName, $content, $methods[array_rand($methods)]); } $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); // Test \Countable - $this->assertEquals($zipFile->count(), $numFiles); - $this->assertEquals(count($zipFile), $numFiles); + static::assertSame($zipFile->count(), $numFiles); + static::assertSame(\count($zipFile), $numFiles); // Test \ArrayAccess reset($files); + foreach ($zipFile as $entryName => $content) { - $this->assertEquals($entryName, key($files)); - $this->assertEquals($content, current($files)); + static::assertSame($entryName, key($files)); + static::assertSame($content, current($files)); next($files); } @@ -1654,12 +1736,13 @@ class ZipFileTest extends ZipTestCase reset($files); $iterator = new \ArrayIterator($zipFile); $iterator->rewind(); + while ($iterator->valid()) { $key = $iterator->key(); $value = $iterator->current(); - $this->assertEquals($key, key($files)); - $this->assertEquals($value, current($files)); + static::assertSame($key, key($files)); + static::assertSame($value, current($files)); next($files); $iterator->next(); @@ -1673,25 +1756,25 @@ class ZipFileTest extends ZipTestCase $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertTrue(isset($zipFile['file1.txt'])); - $this->assertTrue(isset($zipFile['dir/file2.txt'])); - $this->assertTrue(isset($zipFile['dir/empty dir/'])); - $this->assertFalse(isset($zipFile['dir/empty dir/2/'])); + static::assertTrue(isset($zipFile['file1.txt'])); + static::assertTrue(isset($zipFile['dir/file2.txt'])); + static::assertTrue(isset($zipFile['dir/empty dir/'])); + static::assertFalse(isset($zipFile['dir/empty dir/2/'])); $zipFile['dir/empty dir/2/'] = null; unset($zipFile['dir/file2.txt'], $zipFile['dir/empty dir/']); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertTrue(isset($zipFile['file1.txt'])); - $this->assertFalse(isset($zipFile['dir/file2.txt'])); - $this->assertFalse(isset($zipFile['dir/empty dir/'])); - $this->assertTrue(isset($zipFile['dir/empty dir/2/'])); + static::assertTrue(isset($zipFile['file1.txt'])); + static::assertFalse(isset($zipFile['dir/file2.txt'])); + static::assertFalse(isset($zipFile['dir/empty dir/'])); + static::assertTrue(isset($zipFile['dir/empty dir/2/'])); $zipFile->close(); } @@ -1705,80 +1788,80 @@ class ZipFileTest extends ZipTestCase $zipFile = new ZipFile(); $zipFile[$entryName] = new \SplFileInfo(__FILE__); - $zipFile[$entryNameStream] = fopen(__FILE__, 'r'); + $zipFile[$entryNameStream] = fopen(__FILE__, 'rb'); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertEquals(sizeof($zipFile), 2); - $this->assertTrue(isset($zipFile[$entryName])); - $this->assertTrue(isset($zipFile[$entryNameStream])); - $this->assertEquals($zipFile[$entryName], file_get_contents(__FILE__)); - $this->assertEquals($zipFile[$entryNameStream], file_get_contents(__FILE__)); + static::assertSame(\count($zipFile), 2); + static::assertTrue(isset($zipFile[$entryName])); + static::assertTrue(isset($zipFile[$entryNameStream])); + static::assertSame($zipFile[$entryName], file_get_contents(__FILE__)); + static::assertSame($zipFile[$entryNameStream], file_get_contents(__FILE__)); $zipFile->close(); } /** * @throws Exception\ZipEntryNotFoundException * @throws ZipException + * @throws \Exception */ public function testUnknownCompressionMethod() { $zipFile = new ZipFile(); $zipFile->addFromString('file', 'content', ZipEntry::UNKNOWN); - $zipFile->addFromString('file2', base64_encode(CryptoUtil::randomBytes(512)), ZipEntry::UNKNOWN); + $zipFile->addFromString('file2', base64_encode(random_bytes(512)), ZipEntry::UNKNOWN); - $this->assertEquals($zipFile->getEntryInfo('file')->getMethodName(), 'Unknown'); - $this->assertEquals($zipFile->getEntryInfo('file2')->getMethodName(), 'Unknown'); + static::assertSame($zipFile->getEntryInfo('file')->getMethodName(), 'Unknown'); + static::assertSame($zipFile->getEntryInfo('file2')->getMethodName(), 'Unknown'); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->getEntryInfo('file')->getMethodName(), 'No compression'); - $this->assertEquals($zipFile->getEntryInfo('file2')->getMethodName(), 'Deflate'); + static::assertSame($zipFile->getEntryInfo('file')->getMethodName(), 'No compression'); + static::assertSame($zipFile->getEntryInfo('file2')->getMethodName(), 'Deflate'); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Dir name is null * @throws ZipException */ public function testAddEmptyDirNullName() { + $this->setExpectedException(InvalidArgumentException::class, 'Dir name is null'); + $zipFile = new ZipFile(); $zipFile->addEmptyDir(null); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Empty dir name * @throws ZipException */ public function testAddEmptyDirEmptyName() { + $this->setExpectedException(InvalidArgumentException::class, 'Empty dir name'); + $zipFile = new ZipFile(); - $zipFile->addEmptyDir(""); + $zipFile->addEmptyDir(''); } - /** - * @expectedException \PhpZip\Exception\ZipEntryNotFoundException - * @expectedExceptionMessage "bad entry name" - */ public function testNotFoundEntry() { + $this->setExpectedException(ZipEntryNotFoundException::class, '"bad entry name"'); + $zipFile = new ZipFile(); $zipFile['bad entry name']; } /** * Test rewrite input file. + * * @throws ZipException */ public function testRewriteFile() @@ -1786,31 +1869,33 @@ class ZipFileTest extends ZipTestCase $zipFile = new ZipFile(); $zipFile['file'] = 'content'; $zipFile['file2'] = 'content2'; - $this->assertEquals(count($zipFile), 2); + static::assertSame(\count($zipFile), 2); $zipFile ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; $md5file = md5_file($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertEquals(count($zipFile), 2); - $this->assertTrue(isset($zipFile['file'])); - $this->assertTrue(isset($zipFile['file2'])); + static::assertSame(\count($zipFile), 2); + static::assertTrue(isset($zipFile['file'])); + static::assertTrue(isset($zipFile['file2'])); $zipFile['file3'] = 'content3'; - $this->assertEquals(count($zipFile), 3); + static::assertSame(\count($zipFile), 3); $zipFile = $zipFile->rewrite(); - $this->assertEquals(count($zipFile), 3); - $this->assertTrue(isset($zipFile['file'])); - $this->assertTrue(isset($zipFile['file2'])); - $this->assertTrue(isset($zipFile['file3'])); + static::assertSame(\count($zipFile), 3); + static::assertTrue(isset($zipFile['file'])); + static::assertTrue(isset($zipFile['file2'])); + static::assertTrue(isset($zipFile['file3'])); $zipFile->close(); - $this->assertNotEquals(md5_file($this->outputFilename), $md5file); + static::assertNotSame(md5_file($this->outputFilename), $md5file); } /** * Test rewrite for string. + * * @throws ZipException */ public function testRewriteString() @@ -1822,24 +1907,25 @@ class ZipFileTest extends ZipTestCase $zipFile->close(); $zipFile->openFromString(file_get_contents($this->outputFilename)); - $this->assertEquals(count($zipFile), 2); - $this->assertTrue(isset($zipFile['file'])); - $this->assertTrue(isset($zipFile['file2'])); + static::assertSame(\count($zipFile), 2); + static::assertTrue(isset($zipFile['file'])); + static::assertTrue(isset($zipFile['file2'])); $zipFile['file3'] = 'content3'; $zipFile = $zipFile->rewrite(); - $this->assertEquals(count($zipFile), 3); - $this->assertTrue(isset($zipFile['file'])); - $this->assertTrue(isset($zipFile['file2'])); - $this->assertTrue(isset($zipFile['file3'])); + static::assertSame(\count($zipFile), 3); + static::assertTrue(isset($zipFile['file'])); + static::assertTrue(isset($zipFile['file2'])); + static::assertTrue(isset($zipFile['file3'])); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage input stream is null + * @throws ZipException */ public function testRewriteNullStream() { + $this->setExpectedException(ZipException::class, 'input stream is null'); + $zipFile = new ZipFile(); $zipFile->rewrite(); } @@ -1851,34 +1937,36 @@ class ZipFileTest extends ZipTestCase { $zipFile = new ZipFile(); $zipFile[0] = 0; - $this->assertTrue(isset($zipFile[0])); - $this->assertTrue(isset($zipFile['0'])); - $this->assertCount(1, $zipFile); + static::assertTrue(isset($zipFile[0])); + static::assertTrue(isset($zipFile['0'])); + static::assertCount(1, $zipFile); $zipFile ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertTrue(isset($zipFile[0])); - $this->assertTrue(isset($zipFile['0'])); - $this->assertEquals($zipFile['0'], '0'); - $this->assertCount(1, $zipFile); + static::assertTrue(isset($zipFile[0])); + static::assertTrue(isset($zipFile['0'])); + static::assertSame($zipFile['0'], '0'); + static::assertCount(1, $zipFile); $zipFile->close(); - $this->assertTrue(unlink($this->outputFilename)); + static::assertTrue(unlink($this->outputFilename)); $zipFile = new ZipFile(); $zipFile->addFromString(0, 0); - $this->assertTrue(isset($zipFile[0])); - $this->assertTrue(isset($zipFile['0'])); - $this->assertCount(1, $zipFile); + static::assertTrue(isset($zipFile[0])); + static::assertTrue(isset($zipFile['0'])); + static::assertCount(1, $zipFile); $zipFile ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); } /** @@ -1892,9 +1980,9 @@ class ZipFileTest extends ZipTestCase } $filename = 'file.jar'; $response = $zipFile->outputAsResponse(new Response(), $filename); - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals('application/java-archive', $response->getHeaderLine('content-type')); - $this->assertEquals('attachment; filename="file.jar"', $response->getHeaderLine('content-disposition')); + static::assertInstanceOf(ResponseInterface::class, $response); + static::assertSame('application/java-archive', $response->getHeaderLine('content-type')); + static::assertSame('attachment; filename="file.jar"', $response->getHeaderLine('content-disposition')); } /** @@ -1905,50 +1993,63 @@ class ZipFileTest extends ZipTestCase { $zipFile = new ZipFile(); $zipFile - ->addFromString('file', 'content', ZipFileInterface::METHOD_DEFLATED) - ->setCompressionLevelEntry('file', ZipFileInterface::LEVEL_BEST_COMPRESSION) - ->addFromString('file2', 'content', ZipFileInterface::METHOD_DEFLATED) - ->setCompressionLevelEntry('file2', ZipFileInterface::LEVEL_FAST) - ->addFromString('file3', 'content', ZipFileInterface::METHOD_DEFLATED) - ->setCompressionLevelEntry('file3', ZipFileInterface::LEVEL_SUPER_FAST) - ->addFromString('file4', 'content', ZipFileInterface::METHOD_DEFLATED) - ->setCompressionLevelEntry('file4', ZipFileInterface::LEVEL_DEFAULT_COMPRESSION) + ->addFromString('file', 'content', ZipFile::METHOD_DEFLATED) + ->setCompressionLevelEntry('file', ZipFile::LEVEL_BEST_COMPRESSION) + ->addFromString('file2', 'content', ZipFile::METHOD_DEFLATED) + ->setCompressionLevelEntry('file2', ZipFile::LEVEL_FAST) + ->addFromString('file3', 'content', ZipFile::METHOD_DEFLATED) + ->setCompressionLevelEntry('file3', ZipFile::LEVEL_SUPER_FAST) + ->addFromString('file4', 'content', ZipFile::METHOD_DEFLATED) + ->setCompressionLevelEntry('file4', ZipFile::LEVEL_DEFAULT_COMPRESSION) ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->getEntryInfo('file') - ->getCompressionLevel(), ZipFileInterface::LEVEL_BEST_COMPRESSION); - $this->assertEquals($zipFile->getEntryInfo('file2') - ->getCompressionLevel(), ZipFileInterface::LEVEL_FAST); - $this->assertEquals($zipFile->getEntryInfo('file3') - ->getCompressionLevel(), ZipFileInterface::LEVEL_SUPER_FAST); - $this->assertEquals($zipFile->getEntryInfo('file4') - ->getCompressionLevel(), ZipFileInterface::LEVEL_DEFAULT_COMPRESSION); + static::assertSame( + $zipFile->getEntryInfo('file') + ->getCompressionLevel(), + ZipFile::LEVEL_BEST_COMPRESSION + ); + static::assertSame( + $zipFile->getEntryInfo('file2') + ->getCompressionLevel(), + ZipFile::LEVEL_FAST + ); + static::assertSame( + $zipFile->getEntryInfo('file3') + ->getCompressionLevel(), + ZipFile::LEVEL_SUPER_FAST + ); + static::assertSame( + $zipFile->getEntryInfo('file4') + ->getCompressionLevel(), + ZipFile::LEVEL_DEFAULT_COMPRESSION + ); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid compression level * @throws ZipException */ public function testInvalidCompressionLevel() { + $this->setExpectedException(InvalidArgumentException::class, 'Invalid compression level'); + $zipFile = new ZipFile(); $zipFile->addFromString('file', 'content'); $zipFile->setCompressionLevel(15); } /** - * @expectedException \PhpZip\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid compression level * @throws ZipException */ public function testInvalidCompressionLevelEntry() { + $this->setExpectedException(InvalidArgumentException::class, 'Invalid compression level'); + $zipFile = new ZipFile(); $zipFile->addFromString('file', 'content'); $zipFile->setCompressionLevelEntry('file', 15); @@ -1961,20 +2062,24 @@ class ZipFileTest extends ZipTestCase { $zipFile = new ZipFile(); for ($i = 0; $i < 10; $i++) { - $zipFile->addFromString('file' . $i, 'content', ZipFileInterface::METHOD_DEFLATED); + $zipFile->addFromString('file' . $i, 'content', ZipFile::METHOD_DEFLATED); } $zipFile - ->setCompressionLevel(ZipFileInterface::LEVEL_BEST_SPEED) + ->setCompressionLevel(ZipFile::LEVEL_BEST_SPEED) ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); $zipFile->openFile($this->outputFilename); $infoList = $zipFile->getAllInfo(); - array_walk($infoList, function (ZipInfo $zipInfo) { - $this->assertEquals($zipInfo->getCompressionLevel(), ZipFileInterface::LEVEL_BEST_SPEED); - }); + array_walk( + $infoList, + function (ZipInfo $zipInfo) { + $this->assertSame($zipInfo->getCompressionLevel(), ZipFile::LEVEL_BEST_SPEED); + } + ); $zipFile->close(); } @@ -1985,28 +2090,28 @@ class ZipFileTest extends ZipTestCase public function testCompressionMethodEntry() { $zipFile = new ZipFile(); - $zipFile->addFromString('file', 'content', ZipFileInterface::METHOD_STORED); + $zipFile->addFromString('file', 'content', ZipFile::METHOD_STORED); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->getEntryInfo('file')->getMethodName(), 'No compression'); - $zipFile->setCompressionMethodEntry('file', ZipFileInterface::METHOD_DEFLATED); - $this->assertEquals($zipFile->getEntryInfo('file')->getMethodName(), 'Deflate'); + static::assertSame($zipFile->getEntryInfo('file')->getMethodName(), 'No compression'); + $zipFile->setCompressionMethodEntry('file', ZipFile::METHOD_DEFLATED); + static::assertSame($zipFile->getEntryInfo('file')->getMethodName(), 'Deflate'); $zipFile->rewrite(); - $this->assertEquals($zipFile->getEntryInfo('file')->getMethodName(), 'Deflate'); + static::assertSame($zipFile->getEntryInfo('file')->getMethodName(), 'Deflate'); } /** - * @expectedException \PhpZip\Exception\ZipUnsupportMethodException - * @expectedExceptionMessage Unsupported method * @throws ZipException */ public function testInvalidCompressionMethodEntry() { + $this->setExpectedException(ZipUnsupportMethodException::class, 'Unsupported method'); + $zipFile = new ZipFile(); - $zipFile->addFromString('file', 'content', ZipFileInterface::METHOD_STORED); + $zipFile->addFromString('file', 'content', ZipFile::METHOD_STORED); $zipFile->setCompressionMethodEntry('file', 99); } @@ -2020,29 +2125,29 @@ class ZipFileTest extends ZipTestCase $zipFile[$i] = $i; } $zipFile->setArchiveComment('comment'); - $this->assertCount(10, $zipFile); - $this->assertEquals($zipFile->getArchiveComment(), 'comment'); + static::assertCount(10, $zipFile); + static::assertSame($zipFile->getArchiveComment(), 'comment'); $zipFile->saveAsFile($this->outputFilename); $zipFile->unchangeAll(); - $this->assertCount(0, $zipFile); - $this->assertEquals($zipFile->getArchiveComment(), null); + static::assertCount(0, $zipFile); + static::assertNull($zipFile->getArchiveComment()); $zipFile->close(); $zipFile->openFile($this->outputFilename); - $this->assertCount(10, $zipFile); - $this->assertEquals($zipFile->getArchiveComment(), 'comment'); + static::assertCount(10, $zipFile); + static::assertSame($zipFile->getArchiveComment(), 'comment'); for ($i = 10; $i < 100; $i++) { $zipFile[$i] = $i; } $zipFile->setArchiveComment('comment 2'); - $this->assertCount(100, $zipFile); - $this->assertEquals($zipFile->getArchiveComment(), 'comment 2'); + static::assertCount(100, $zipFile); + static::assertSame($zipFile->getArchiveComment(), 'comment 2'); $zipFile->unchangeAll(); - $this->assertCount(10, $zipFile); - $this->assertEquals($zipFile->getArchiveComment(), 'comment'); + static::assertCount(10, $zipFile); + static::assertSame($zipFile->getArchiveComment(), 'comment'); $zipFile->close(); } @@ -2056,20 +2161,20 @@ class ZipFileTest extends ZipTestCase $zipFile[$i] = $i; } $zipFile->setArchiveComment('comment'); - $this->assertEquals($zipFile->getArchiveComment(), 'comment'); + static::assertSame($zipFile->getArchiveComment(), 'comment'); $zipFile->saveAsFile($this->outputFilename); $zipFile->unchangeArchiveComment(); - $this->assertEquals($zipFile->getArchiveComment(), null); + static::assertNull($zipFile->getArchiveComment()); $zipFile->close(); $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->getArchiveComment(), 'comment'); + static::assertSame($zipFile->getArchiveComment(), 'comment'); $zipFile->setArchiveComment('comment 2'); - $this->assertEquals($zipFile->getArchiveComment(), 'comment 2'); + static::assertSame($zipFile->getArchiveComment(), 'comment 2'); $zipFile->unchangeArchiveComment(); - $this->assertEquals($zipFile->getArchiveComment(), 'comment'); + static::assertSame($zipFile->getArchiveComment(), 'comment'); $zipFile->close(); } @@ -2084,55 +2189,27 @@ class ZipFileTest extends ZipTestCase $zipFile['file 2'] = 'content 2'; $zipFile ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; $zipFile->openFile($this->outputFilename); $zipFile['file 1'] = 'modify content 1'; $zipFile->setPasswordEntry('file 1', 'password'); - $this->assertEquals($zipFile['file 1'], 'modify content 1'); - $this->assertTrue($zipFile->getEntryInfo('file 1')->isEncrypted()); + static::assertSame($zipFile['file 1'], 'modify content 1'); + static::assertTrue($zipFile->getEntryInfo('file 1')->isEncrypted()); - $this->assertEquals($zipFile['file 2'], 'content 2'); - $this->assertFalse($zipFile->getEntryInfo('file 2')->isEncrypted()); + static::assertSame($zipFile['file 2'], 'content 2'); + static::assertFalse($zipFile->getEntryInfo('file 2')->isEncrypted()); $zipFile->unchangeEntry('file 1'); - $this->assertEquals($zipFile['file 1'], 'content 1'); - $this->assertFalse($zipFile->getEntryInfo('file 1')->isEncrypted()); + static::assertSame($zipFile['file 1'], 'content 1'); + static::assertFalse($zipFile->getEntryInfo('file 1')->isEncrypted()); - $this->assertEquals($zipFile['file 2'], 'content 2'); - $this->assertFalse($zipFile->getEntryInfo('file 2')->isEncrypted()); - $zipFile->close(); - } - - /** - * Test support ZIP64 ext (slow test - normal). - * Create > 65535 files in archive and open and extract to /dev/null. - * @throws ZipException - */ - public function testCreateAndOpenZip64Ext() - { - $countFiles = 0xffff + 1; - - $zipFile = new ZipFile(); - for ($i = 0; $i < $countFiles; $i++) { - $zipFile[$i . '.txt'] = $i; - } - $zipFile->saveAsFile($this->outputFilename); - $zipFile->close(); - - $this->assertCorrectZipArchive($this->outputFilename); - - $zipFile->openFile($this->outputFilename); - $this->assertEquals($zipFile->count(), $countFiles); - $i = 0; - foreach ($zipFile as $entry => $content) { - $this->assertEquals($entry, $i . '.txt'); - $this->assertEquals($content, $i); - $i++; - } + static::assertSame($zipFile['file 2'], 'content 2'); + static::assertFalse($zipFile->getEntryInfo('file 2')->isEncrypted()); $zipFile->close(); } } diff --git a/tests/PhpZip/ZipMatcherTest.php b/tests/PhpZip/ZipMatcherTest.php index 32da1b4..345fa3d 100644 --- a/tests/PhpZip/ZipMatcherTest.php +++ b/tests/PhpZip/ZipMatcherTest.php @@ -2,11 +2,16 @@ namespace PhpZip; +use PHPUnit\Framework\TestCase; use PhpZip\Model\ZipEntryMatcher; use PhpZip\Model\ZipInfo; -use PhpZip\Util\CryptoUtil; -class ZipMatcherTest extends \PHPUnit_Framework_TestCase +/** + * @internal + * + * @small + */ +class ZipMatcherTest extends TestCase { public function testMatcher() { @@ -16,50 +21,65 @@ class ZipMatcherTest extends \PHPUnit_Framework_TestCase } $matcher = $zipFile->matcher(); - $this->assertInstanceOf(ZipEntryMatcher::class, $matcher); + static::assertInstanceOf(ZipEntryMatcher::class, $matcher); - $this->assertTrue(is_array($matcher->getMatches())); - $this->assertCount(0, $matcher); + static::assertInternalType('array', $matcher->getMatches()); + static::assertCount(0, $matcher); $matcher->add(1)->add(10)->add(20); - $this->assertCount(3, $matcher); - $this->assertEquals($matcher->getMatches(), ['1', '10', '20']); + static::assertCount(3, $matcher); + static::assertEquals($matcher->getMatches(), ['1', '10', '20']); $matcher->delete(); - $this->assertCount(97, $zipFile); - $this->assertCount(0, $matcher); + static::assertCount(97, $zipFile); + static::assertCount(0, $matcher); $matcher->match('~^[2][1-5]|[3][6-9]|40$~s'); - $this->assertCount(10, $matcher); + static::assertCount(10, $matcher); $actualMatches = [ - '21', '22', '23', '24', '25', - '36', '37', '38', '39', - '40' + '21', + '22', + '23', + '24', + '25', + '36', + '37', + '38', + '39', + '40', ]; - $this->assertEquals($matcher->getMatches(), $actualMatches); + static::assertSame($matcher->getMatches(), $actualMatches); $matcher->setPassword('qwerty'); $info = $zipFile->getAllInfo(); - array_walk($info, function (ZipInfo $zipInfo) use ($actualMatches) { - $this->assertEquals($zipInfo->isEncrypted(), in_array($zipInfo->getName(), $actualMatches)); - }); + array_walk( + $info, + function (ZipInfo $zipInfo) use ($actualMatches) { + $this->assertSame($zipInfo->isEncrypted(), \in_array($zipInfo->getName(), $actualMatches, true)); + } + ); $matcher->all(); - $this->assertCount(count($zipFile), $matcher); + static::assertCount(\count($zipFile), $matcher); $expectedNames = []; - $matcher->invoke(function ($entryName) use (&$expectedNames) { - $expectedNames[] = $entryName; - }); - $this->assertEquals($expectedNames, $matcher->getMatches()); + $matcher->invoke( + static function ($entryName) use (&$expectedNames) { + $expectedNames[] = $entryName; + } + ); + static::assertSame($expectedNames, $matcher->getMatches()); $zipFile->close(); } + /** + * @throws \Exception + */ public function testDocsExample() { $zipFile = new ZipFile(); for ($i = 0; $i < 100; $i++) { - $zipFile['file_'.$i.'.jpg'] = CryptoUtil::randomBytes(100); + $zipFile['file_' . $i . '.jpg'] = random_bytes(100); } $renameEntriesArray = [ @@ -86,24 +106,26 @@ class ZipMatcherTest extends \PHPUnit_Framework_TestCase ]; foreach ($renameEntriesArray as $name) { - $this->assertTrue(isset($zipFile[$name])); + static::assertTrue(isset($zipFile[$name])); } $matcher = $zipFile->matcher(); $matcher->match('~^file_(1|5)\d+~'); - $this->assertEquals($matcher->getMatches(), $renameEntriesArray); + static::assertSame($matcher->getMatches(), $renameEntriesArray); - $matcher->invoke(function ($entryName) use ($zipFile) { - $newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName); - $zipFile->rename($entryName, $newName); - }); + $matcher->invoke( + static function ($entryName) use ($zipFile) { + $newName = preg_replace('~\.(jpe?g)$~i', '.no_optimize.$1', $entryName); + $zipFile->rename($entryName, $newName); + } + ); foreach ($renameEntriesArray as $name) { - $this->assertFalse(isset($zipFile[$name])); + static::assertFalse(isset($zipFile[$name])); $pathInfo = pathinfo($name); - $newName = $pathInfo['filename'].'.no_optimize.'.$pathInfo['extension']; - $this->assertTrue(isset($zipFile[$newName])); + $newName = $pathInfo['filename'] . '.no_optimize.' . $pathInfo['extension']; + static::assertTrue(isset($zipFile[$newName])); } $zipFile->close(); diff --git a/tests/PhpZip/ZipPasswordTest.php b/tests/PhpZip/ZipPasswordTest.php index a887126..7ab39d7 100644 --- a/tests/PhpZip/ZipPasswordTest.php +++ b/tests/PhpZip/ZipPasswordTest.php @@ -2,89 +2,103 @@ namespace PhpZip; +use PhpZip\Exception\RuntimeException; use PhpZip\Exception\ZipAuthenticationException; use PhpZip\Exception\ZipEntryNotFoundException; use PhpZip\Exception\ZipException; use PhpZip\Model\ZipInfo; -use PhpZip\Util\CryptoUtil; /** * Tests with zip password. + * + * @internal + * + * @small */ class ZipPasswordTest extends ZipFileAddDirTest { /** * Test archive password. + * * @throws ZipException + * @throws \Exception + * @noinspection PhpRedundantCatchClauseInspection */ public function testSetPassword() { - if (PHP_INT_SIZE === 4) { - $this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.'); + if (\PHP_INT_SIZE === 4) { // php 32 bit + $this->setExpectedException( + RuntimeException::class, + 'Traditional PKWARE Encryption is not supported in 32-bit PHP.' + ); } - $password = base64_encode(CryptoUtil::randomBytes(100)); - $badPassword = "bad password"; + $password = base64_encode(random_bytes(100)); + $badPassword = 'bad password'; // create encryption password with ZipCrypto $zipFile = new ZipFile(); $zipFile->addDir(__DIR__); - $zipFile->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL); + $zipFile->setPassword($password, ZipFile::ENCRYPTION_METHOD_TRADITIONAL); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename, $password); + static::assertCorrectZipArchive($this->outputFilename, $password); // check bad password for ZipCrypto $zipFile->openFile($this->outputFilename); $zipFile->setReadPassword($badPassword); + foreach ($zipFile->getListFiles() as $entryName) { try { $zipFile[$entryName]; - $this->fail("Expected Exception has not been raised."); + static::fail('Expected Exception has not been raised.'); } catch (ZipAuthenticationException $ae) { - $this->assertContains('Invalid password for zip entry', $ae->getMessage()); + static::assertContains('Invalid password for zip entry', $ae->getMessage()); } } // check correct password for ZipCrypto $zipFile->setReadPassword($password); + foreach ($zipFile->getAllInfo() as $info) { - $this->assertTrue($info->isEncrypted()); - $this->assertContains('ZipCrypto', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('ZipCrypto', $info->getMethodName()); $decryptContent = $zipFile[$info->getName()]; - $this->assertNotEmpty($decryptContent); - $this->assertContains('setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES); + $zipFile->setPassword($password, ZipFile::ENCRYPTION_METHOD_WINZIP_AES); $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename, $password); + static::assertCorrectZipArchive($this->outputFilename, $password); // check from WinZip AES encryption $zipFile->openFile($this->outputFilename); // set bad password WinZip AES $zipFile->setReadPassword($badPassword); + foreach ($zipFile->getListFiles() as $entryName) { try { $zipFile[$entryName]; - $this->fail("Expected Exception has not been raised."); + static::fail('Expected Exception has not been raised.'); } catch (ZipAuthenticationException $ae) { - $this->assertNotNull($ae); + static::assertNotNull($ae); } } // set correct password WinZip AES $zipFile->setReadPassword($password); + foreach ($zipFile->getAllInfo() as $info) { - $this->assertTrue($info->isEncrypted()); - $this->assertContains('WinZip', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('WinZip', $info->getMethodName()); $decryptContent = $zipFile[$info->getName()]; - $this->assertNotEmpty($decryptContent); - $this->assertContains('saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename); + static::assertCorrectZipArchive($this->outputFilename); // check remove password $zipFile->openFile($this->outputFilename); + foreach ($zipFile->getAllInfo() as $info) { - $this->assertFalse($info->isEncrypted()); + static::assertFalse($info->isEncrypted()); } $zipFile->close(); } /** * @throws ZipException + * @throws \Exception */ public function testTraditionalEncryption() { - if (PHP_INT_SIZE === 4) { - $this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.'); + if (\PHP_INT_SIZE === 4) { // php 32 bit + $this->setExpectedException( + RuntimeException::class, + 'Traditional PKWARE Encryption is not supported in 32-bit PHP.' + ); } - $password = base64_encode(CryptoUtil::randomBytes(50)); + $password = base64_encode(random_bytes(50)); $zip = new ZipFile(); $zip->addDirRecursive($this->outputDirname); - $zip->setPassword($password, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL); + $zip->setPassword($password, ZipFile::ENCRYPTION_METHOD_TRADITIONAL); $zip->saveAsFile($this->outputFilename); $zip->close(); - $this->assertCorrectZipArchive($this->outputFilename, $password); + static::assertCorrectZipArchive($this->outputFilename, $password); $zip->openFile($this->outputFilename); $zip->setReadPassword($password); - $this->assertFilesResult($zip, array_keys(self::$files)); + static::assertFilesResult($zip, array_keys(self::$files)); + foreach ($zip->getAllInfo() as $info) { if (!$info->isFolder()) { - $this->assertTrue($info->isEncrypted()); - $this->assertContains('ZipCrypto', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('ZipCrypto', $info->getMethodName()); } } $zip->close(); @@ -137,13 +157,16 @@ class ZipPasswordTest extends ZipFileAddDirTest /** * @dataProvider winZipKeyStrengthProvider + * * @param int $encryptionMethod * @param int $bitSize + * * @throws ZipException + * @throws \Exception */ public function testWinZipAesEncryption($encryptionMethod, $bitSize) { - $password = base64_encode(CryptoUtil::randomBytes(50)); + $password = base64_encode(random_bytes(50)); $zip = new ZipFile(); $zip->addDirRecursive($this->outputDirname); @@ -151,16 +174,17 @@ class ZipPasswordTest extends ZipFileAddDirTest $zip->saveAsFile($this->outputFilename); $zip->close(); - $this->assertCorrectZipArchive($this->outputFilename, $password); + static::assertCorrectZipArchive($this->outputFilename, $password); $zip->openFile($this->outputFilename); $zip->setReadPassword($password); - $this->assertFilesResult($zip, array_keys(self::$files)); + static::assertFilesResult($zip, array_keys(self::$files)); + foreach ($zip->getAllInfo() as $info) { if (!$info->isFolder()) { - $this->assertTrue($info->isEncrypted()); - $this->assertEquals($info->getEncryptionMethod(), $encryptionMethod); - $this->assertContains('WinZip AES-' . $bitSize, $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertSame($info->getEncryptionMethod(), $encryptionMethod); + static::assertContains('WinZip AES-' . $bitSize, $info->getMethodName()); } } $zip->close(); @@ -172,10 +196,10 @@ class ZipPasswordTest extends ZipFileAddDirTest public function winZipKeyStrengthProvider() { return [ - [ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128, 128], - [ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192, 192], - [ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES, 256], - [ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256, 256], + [ZipFile::ENCRYPTION_METHOD_WINZIP_AES_128, 128], + [ZipFile::ENCRYPTION_METHOD_WINZIP_AES_192, 192], + [ZipFile::ENCRYPTION_METHOD_WINZIP_AES, 256], + [ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256, 256], ]; } @@ -185,8 +209,11 @@ class ZipPasswordTest extends ZipFileAddDirTest */ public function testEncryptionEntries() { - if (PHP_INT_SIZE === 4) { - $this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.'); + if (\PHP_INT_SIZE === 4) { // php 32 bit + $this->setExpectedException( + RuntimeException::class, + 'Traditional PKWARE Encryption is not supported in 32-bit PHP.' + ); } $password1 = '353442434235424234'; @@ -194,31 +221,34 @@ class ZipPasswordTest extends ZipFileAddDirTest $zip = new ZipFile(); $zip->addDir($this->outputDirname); - $zip->setPasswordEntry('.hidden', $password1, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL); - $zip->setPasswordEntry('text file.txt', $password2, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES); + $zip->setPasswordEntry('.hidden', $password1, ZipFile::ENCRYPTION_METHOD_TRADITIONAL); + $zip->setPasswordEntry('text file.txt', $password2, ZipFile::ENCRYPTION_METHOD_WINZIP_AES); $zip->saveAsFile($this->outputFilename); $zip->close(); $zip->openFile($this->outputFilename); $zip->setReadPasswordEntry('.hidden', $password1); $zip->setReadPasswordEntry('text file.txt', $password2); - $this->assertFilesResult($zip, [ - '.hidden', - 'text file.txt', - 'Текстовый документ.txt', - 'empty dir/', - ]); + static::assertFilesResult( + $zip, + [ + '.hidden', + 'text file.txt', + 'Текстовый документ.txt', + 'empty dir/', + ] + ); $info = $zip->getEntryInfo('.hidden'); - $this->assertTrue($info->isEncrypted()); - $this->assertContains('ZipCrypto', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('ZipCrypto', $info->getMethodName()); $info = $zip->getEntryInfo('text file.txt'); - $this->assertTrue($info->isEncrypted()); - $this->assertContains('WinZip AES', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('WinZip AES', $info->getMethodName()); - $this->assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted()); - $this->assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted()); + static::assertFalse($zip->getEntryInfo('Текстовый документ.txt')->isEncrypted()); + static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted()); $zip->close(); } @@ -229,8 +259,11 @@ class ZipPasswordTest extends ZipFileAddDirTest */ public function testEncryptionEntriesWithDefaultPassword() { - if (PHP_INT_SIZE === 4) { - $this->markTestSkipped('Skip test for 32-bit system. Not support Traditional PKWARE Encryption.'); + if (\PHP_INT_SIZE === 4) { // php 32 bit + $this->setExpectedException( + RuntimeException::class, + 'Traditional PKWARE Encryption is not supported in 32-bit PHP.' + ); } $password1 = '353442434235424234'; @@ -240,8 +273,8 @@ class ZipPasswordTest extends ZipFileAddDirTest $zip = new ZipFile(); $zip->addDir($this->outputDirname); $zip->setPassword($defaultPassword); - $zip->setPasswordEntry('.hidden', $password1, ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL); - $zip->setPasswordEntry('text file.txt', $password2, ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES); + $zip->setPasswordEntry('.hidden', $password1, ZipFile::ENCRYPTION_METHOD_TRADITIONAL); + $zip->setPasswordEntry('text file.txt', $password2, ZipFile::ENCRYPTION_METHOD_WINZIP_AES); $zip->saveAsFile($this->outputFilename); $zip->close(); @@ -249,36 +282,40 @@ class ZipPasswordTest extends ZipFileAddDirTest $zip->setReadPassword($defaultPassword); $zip->setReadPasswordEntry('.hidden', $password1); $zip->setReadPasswordEntry('text file.txt', $password2); - $this->assertFilesResult($zip, [ - '.hidden', - 'text file.txt', - 'Текстовый документ.txt', - 'empty dir/', - ]); + static::assertFilesResult( + $zip, + [ + '.hidden', + 'text file.txt', + 'Текстовый документ.txt', + 'empty dir/', + ] + ); $info = $zip->getEntryInfo('.hidden'); - $this->assertTrue($info->isEncrypted()); - $this->assertContains('ZipCrypto', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('ZipCrypto', $info->getMethodName()); $info = $zip->getEntryInfo('text file.txt'); - $this->assertTrue($info->isEncrypted()); - $this->assertContains('WinZip AES', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('WinZip AES', $info->getMethodName()); $info = $zip->getEntryInfo('Текстовый документ.txt'); - $this->assertTrue($info->isEncrypted()); - $this->assertContains('WinZip AES', $info->getMethodName()); + static::assertTrue($info->isEncrypted()); + static::assertContains('WinZip AES', $info->getMethodName()); - $this->assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted()); + static::assertFalse($zip->getEntryInfo('empty dir/')->isEncrypted()); $zip->close(); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Invalid encryption method + * @throws ZipException */ public function testSetEncryptionMethodInvalid() { + $this->setExpectedException(ZipException::class, 'Invalid encryption method'); + $zipFile = new ZipFile(); $encryptionMethod = 9999; $zipFile->setPassword('pass', $encryptionMethod); @@ -295,35 +332,40 @@ class ZipPasswordTest extends ZipFileAddDirTest $zipFile = new ZipFile(); $zipFile->setPassword('pass'); $zipFile['file'] = 'content'; - $this->assertFalse($zipFile->getEntryInfo('file')->isEncrypted()); + static::assertFalse($zipFile->getEntryInfo('file')->isEncrypted()); for ($i = 1; $i <= 10; $i++) { $zipFile['file' . $i] = 'content'; + if ($i < 6) { $zipFile->setPasswordEntry('file' . $i, 'pass'); - $this->assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted()); + static::assertTrue($zipFile->getEntryInfo('file' . $i)->isEncrypted()); } else { - $this->assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted()); + static::assertFalse($zipFile->getEntryInfo('file' . $i)->isEncrypted()); } } $zipFile->disableEncryptionEntry('file3'); - $this->assertFalse($zipFile->getEntryInfo('file3')->isEncrypted()); - $this->asserttrue($zipFile->getEntryInfo('file2')->isEncrypted()); + static::assertFalse($zipFile->getEntryInfo('file3')->isEncrypted()); + static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted()); $zipFile->disableEncryption(); $infoList = $zipFile->getAllInfo(); - array_walk($infoList, function (ZipInfo $zipInfo) { - $this->assertFalse($zipInfo->isEncrypted()); - }); + array_walk( + $infoList, + function (ZipInfo $zipInfo) { + $this->assertFalse($zipInfo->isEncrypted()); + } + ); $zipFile->close(); } /** - * @expectedException \PhpZip\Exception\ZipException - * @expectedExceptionMessage Invalid encryption method + * @throws ZipException */ public function testInvalidEncryptionMethodEntry() { + $this->setExpectedException(ZipException::class, 'Invalid encryption method'); + $zipFile = new ZipFile(); - $zipFile->addFromString('file', 'content', ZipFileInterface::METHOD_STORED); + $zipFile->addFromString('file', 'content', ZipFile::METHOD_STORED); $zipFile->setPasswordEntry('file', 'pass', 99); } @@ -341,43 +383,46 @@ class ZipPasswordTest extends ZipFileAddDirTest $zipFile->saveAsFile($this->outputFilename); $zipFile->close(); - $this->assertCorrectZipArchive($this->outputFilename, 'password'); + static::assertCorrectZipArchive($this->outputFilename, 'password'); $zipFile->openFile($this->outputFilename); - $this->assertCount(3, $zipFile); + static::assertCount(3, $zipFile); + foreach ($zipFile->getAllInfo() as $info) { - $this->assertTrue($info->isEncrypted()); + static::assertTrue($info->isEncrypted()); } unset($zipFile['file3']); $zipFile['file4'] = 'content'; $zipFile->rewrite(); - $this->assertCorrectZipArchive($this->outputFilename, 'password'); + static::assertCorrectZipArchive($this->outputFilename, 'password'); - $this->assertCount(3, $zipFile); - $this->assertFalse(isset($zipFile['file3'])); - $this->assertTrue(isset($zipFile['file4'])); - $this->assertTrue($zipFile->getEntryInfo('file1')->isEncrypted()); - $this->assertTrue($zipFile->getEntryInfo('file2')->isEncrypted()); - $this->assertFalse($zipFile->getEntryInfo('file4')->isEncrypted()); - $this->assertEquals($zipFile['file4'], 'content'); + static::assertCount(3, $zipFile); + static::assertFalse(isset($zipFile['file3'])); + static::assertTrue(isset($zipFile['file4'])); + static::assertTrue($zipFile->getEntryInfo('file1')->isEncrypted()); + static::assertTrue($zipFile->getEntryInfo('file2')->isEncrypted()); + static::assertFalse($zipFile->getEntryInfo('file4')->isEncrypted()); + static::assertSame($zipFile['file4'], 'content'); $zipFile->extractTo($this->outputDirname, ['file4']); - $this->assertTrue(file_exists($this->outputDirname . DIRECTORY_SEPARATOR . 'file4')); - $this->assertEquals(file_get_contents($this->outputDirname . DIRECTORY_SEPARATOR . 'file4'), $zipFile['file4']); + static::assertFileExists($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4'); + static::assertStringEqualsFile($this->outputDirname . \DIRECTORY_SEPARATOR . 'file4', $zipFile['file4']); $zipFile->close(); } /** * @see https://github.com/Ne-Lexa/php-zip/issues/9 + * * @throws ZipException + * @throws \Exception */ public function testIssues9() { - $contents = str_pad('', 1000, 'test;test2;test3' . PHP_EOL, STR_PAD_RIGHT); - $password = base64_encode(CryptoUtil::randomBytes(20)); + $contents = str_pad('', 1000, 'test;test2;test3' . \PHP_EOL, \STR_PAD_RIGHT); + $password = base64_encode(random_bytes(20)); $encryptMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256; $zipFile = new ZipFile(); @@ -385,13 +430,14 @@ class ZipPasswordTest extends ZipFileAddDirTest ->addFromString('codes.csv', $contents) ->setPassword($password, $encryptMethod) ->saveAsFile($this->outputFilename) - ->close(); + ->close() + ; - $this->assertCorrectZipArchive($this->outputFilename, $password); + static::assertCorrectZipArchive($this->outputFilename, $password); $zipFile->openFile($this->outputFilename); $zipFile->setReadPassword($password); - $this->assertEquals($zipFile['codes.csv'], $contents); + static::assertSame($zipFile['codes.csv'], $contents); $zipFile->close(); } @@ -413,12 +459,13 @@ class ZipPasswordTest extends ZipFileAddDirTest $zipFile2 = new ZipFile(); $zipFile2->openFile($this->outputFilename); $zipFile2->setReadPassword($password); - $this->assertEquals($zipFile2->getListFiles(), $zipFile->getListFiles()); + static::assertSame($zipFile2->getListFiles(), $zipFile->getListFiles()); + foreach ($zipFile as $name => $contents) { - $this->assertNotEmpty($name); - $this->assertNotEmpty($contents); - $this->assertContains('test contents', $contents); - $this->assertEquals($zipFile2[$name], $contents); + static::assertNotEmpty($name); + static::assertNotEmpty($contents); + static::assertContains('test contents', $contents); + static::assertSame($zipFile2[$name], $contents); } $zipFile2->close(); diff --git a/tests/PhpZip/ZipRemoteFileTest.php b/tests/PhpZip/ZipRemoteFileTest.php index dbd5a59..5047d04 100644 --- a/tests/PhpZip/ZipRemoteFileTest.php +++ b/tests/PhpZip/ZipRemoteFileTest.php @@ -3,20 +3,16 @@ namespace PhpZip; use PhpZip\Exception\ZipException; -use PhpZip\Util\Iterator\IgnoreFilesFilterIterator; -use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator; /** - * Test add remote files to zip archive + * Test add remote files to zip archive. + * + * @internal + * + * @small */ class ZipRemoteFileTest extends ZipTestCase { - - protected function setUp() - { - parent::setUp(); - } - /** * @throws ZipException */ @@ -25,16 +21,28 @@ class ZipRemoteFileTest extends ZipTestCase $zipFile = new ZipFile(); $outputZip = $this->outputFilename; $fileUrl = 'https://raw.githubusercontent.com/Ne-Lexa/php-zip/master/README.md'; - $fp = @fopen($fileUrl, 'rb', false, stream_context_create([ - 'http' => [ - 'timeout' => 3, - ] - ])); + /** @noinspection PhpUsageOfSilenceOperatorInspection */ + $fp = @fopen( + $fileUrl, + 'rb', + false, + stream_context_create( + [ + 'http' => [ + 'timeout' => 3, + ], + ] + ) + ); + if ($fp === false) { - self::markTestSkipped(sprintf( - "Could not fetch remote file: %s", - $fileUrl - )); + static::markTestSkipped( + sprintf( + 'Could not fetch remote file: %s', + $fileUrl + ) + ); + return; } @@ -47,9 +55,8 @@ class ZipRemoteFileTest extends ZipTestCase $zipFile = new ZipFile(); $zipFile->openFile($outputZip); $files = $zipFile->getListFiles(); - self::assertCount(1, $files); - self::assertSame($fileName, $files[0]); + static::assertCount(1, $files); + static::assertSame($fileName, $files[0]); $zipFile->close(); } - } diff --git a/tests/PhpZip/ZipSlipVulnerabilityTest.php b/tests/PhpZip/ZipSlipVulnerabilityTest.php index 825dc5c..45d92e7 100644 --- a/tests/PhpZip/ZipSlipVulnerabilityTest.php +++ b/tests/PhpZip/ZipSlipVulnerabilityTest.php @@ -3,11 +3,14 @@ namespace PhpZip; /** - * Class ZipSlipVulnerabilityTest + * Class ZipSlipVulnerabilityTest. * - * @package PhpZip * @see https://github.com/Ne-Lexa/php-zip/issues/39 Issue#31 * @see https://snyk.io/research/zip-slip-vulnerability Zip Slip Vulnerability + * + * @internal + * + * @small */ class ZipSlipVulnerabilityTest extends ZipTestCase { @@ -19,7 +22,7 @@ class ZipSlipVulnerabilityTest extends ZipTestCase $localFile = '../dir/./../../file.txt'; $zipFile = new ZipFile(); $zipFile->addFromString($localFile, 'contents'); - self::assertContains($localFile, $zipFile->getListFiles()); + static::assertContains($localFile, $zipFile->getListFiles()); $zipFile->close(); } @@ -28,7 +31,7 @@ class ZipSlipVulnerabilityTest extends ZipTestCase */ public function testUnpack() { - $this->assertTrue(mkdir($this->outputDirname, 0755, true)); + static::assertTrue(mkdir($this->outputDirname, 0755, true)); $zipFile = new ZipFile(); $zipFile->addFromString('../dir/./../../file.txt', 'contents'); @@ -36,6 +39,6 @@ class ZipSlipVulnerabilityTest extends ZipTestCase $zipFile->close(); $expectedExtractedFile = $this->outputDirname . '/dir/file.txt'; - self::assertTrue(is_file($expectedExtractedFile)); + static::assertTrue(is_file($expectedExtractedFile)); } } diff --git a/tests/PhpZip/ZipTestCase.php b/tests/PhpZip/ZipTestCase.php index 91fd205..ab61665 100644 --- a/tests/PhpZip/ZipTestCase.php +++ b/tests/PhpZip/ZipTestCase.php @@ -2,32 +2,31 @@ namespace PhpZip; +use PHPUnit\Framework\TestCase; use PhpZip\Model\EndOfCentralDirectory; use PhpZip\Util\FilesUtil; /** * PHPUnit test case and helper methods. */ -class ZipTestCase extends \PHPUnit_Framework_TestCase +abstract class ZipTestCase extends TestCase { - /** - * @var string - */ + /** @var string */ protected $outputFilename; - /** - * @var string - */ + + /** @var string */ protected $outputDirname; /** - * Before test + * Before test. + * + * @noinspection PhpMissingParentCallCommonInspection */ protected function setUp() { - parent::setUp(); - $id = uniqid('phpzip', true); $tempDir = sys_get_temp_dir() . '/phpunit-phpzip'; + if (!is_dir($tempDir) && !mkdir($tempDir, 0755, true) && !is_dir($tempDir)) { throw new \RuntimeException('Dir ' . $tempDir . " can't created"); } @@ -36,7 +35,7 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase } /** - * After test + * After test. */ protected function tearDown() { @@ -45,6 +44,7 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase if ($this->outputFilename !== null && file_exists($this->outputFilename)) { unlink($this->outputFilename); } + if ($this->outputDirname !== null && is_dir($this->outputDirname)) { FilesUtil::removeDir($this->outputDirname); } @@ -53,25 +53,30 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase /** * Assert correct zip archive. * - * @param string $filename + * @param string $filename * @param string|null $password */ public static function assertCorrectZipArchive($filename, $password = null) { if (self::existsProgram('unzip')) { $command = 'unzip'; + if ($password !== null) { $command .= ' -P ' . escapeshellarg($password); } $command .= ' -t ' . escapeshellarg($filename); + $command .= ' 2>&1'; exec($command, $output, $returnCode); - $output = implode(PHP_EOL, $output); + $output = implode(\PHP_EOL, $output); if ($password !== null && $returnCode === 81) { if (self::existsProgram('7z')) { - // WinZip 99-character limit - // @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + /** + * WinZip 99-character limit. + * + * @see https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/ + */ $password = substr($password, 0, 99); $command = '7z t -p' . escapeshellarg($password) . ' ' . escapeshellarg($filename); @@ -79,31 +84,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase /** * @var array $output */ - $output = implode(PHP_EOL, $output); + $output = implode(\PHP_EOL, $output); - self::assertEquals($returnCode, 0); - self::assertNotContains(' Errors', $output); - self::assertContains(' Ok', $output); + static::assertSame($returnCode, 0); + static::assertNotContains(' Errors', $output); + static::assertContains(' Ok', $output); } else { - fwrite(STDERR, 'Program unzip cannot support this function.' . PHP_EOL); - fwrite(STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . PHP_EOL); + fwrite(\STDERR, 'Program unzip cannot support this function.' . \PHP_EOL); + fwrite(\STDERR, 'Please install 7z. For Ubuntu-like: sudo apt-get install p7zip-full' . \PHP_EOL); } } else { - self::assertEquals($returnCode, 0, $output); - self::assertNotContains('incorrect password', $output); - self::assertContains(' OK', $output); - self::assertContains('No errors', $output); + static::assertSame($returnCode, 0, $output); + static::assertNotContains('incorrect password', $output); + static::assertContains(' OK', $output); + static::assertContains('No errors', $output); } } } /** * @param string $program + * * @return bool */ - private static function existsProgram($program){ - if (DIRECTORY_SEPARATOR !== '\\') { + private static function existsProgram($program) + { + if (\DIRECTORY_SEPARATOR !== '\\') { exec('which ' . escapeshellarg($program), $output, $returnCode); + return $returnCode === 0; } // false for Windows @@ -120,30 +128,34 @@ class ZipTestCase extends \PHPUnit_Framework_TestCase if (self::existsProgram('zipinfo')) { exec('zipinfo ' . escapeshellarg($filename), $output, $returnCode); - $output = implode(PHP_EOL, $output); + $output = implode(\PHP_EOL, $output); - self::assertContains('Empty zipfile', $output); + static::assertContains('Empty zipfile', $output); } - $actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CENTRAL_DIRECTORY_RECORD_SIG, 0, 0, 0, 0, 0); - self::assertStringEqualsFile($filename, $actualEmptyZipData); + $actualEmptyZipData = pack('VVVVVv', EndOfCentralDirectory::END_OF_CD_SIG, 0, 0, 0, 0, 0); + static::assertStringEqualsFile($filename, $actualEmptyZipData); } /** * @param string $filename - * @param bool $showErrors - * @return bool|null If null - can not install zipalign + * @param bool $showErrors + * + * @return bool|null If null returned, then the zipalign program is not installed */ public static function assertVerifyZipAlign($filename, $showErrors = false) { if (self::existsProgram('zipalign')) { exec('zipalign -c -v 4 ' . escapeshellarg($filename), $output, $returnCode); + if ($showErrors && $returnCode !== 0) { - fwrite(STDERR, implode(PHP_EOL, $output)); + fwrite(\STDERR, implode(\PHP_EOL, $output)); } + return $returnCode === 0; } - fwrite(STDERR, 'Can not find program "zipalign" for test' . PHP_EOL); + fwrite(\STDERR, "Cannot find the program 'zipalign' for the test" . \PHP_EOL); + return null; } }