Prechádzať zdrojové kódy

Merge tag '3.1.13' into develop

improved compatibility with php 7.4 and php-32 bit, minor bug fixes
Ne-Lexa 6 rokov pred
rodič
commit
36b1af0263
63 zmenil súbory, kde vykonal 5531 pridanie a 2647 odobranie
  1. 1500 0
      .php_cs
  2. 3 6
      .travis.yml
  3. 15 4
      composer.json
  4. 314 59
      src/PhpZip/Crypto/TraditionalPkwareEncryptionEngine.php
  5. 98 62
      src/PhpZip/Crypto/WinZipAesEngine.php
  6. 6 2
      src/PhpZip/Crypto/ZipEncryptionEngine.php
  7. 3 4
      src/PhpZip/Exception/Crc32Exception.php
  8. 8 7
      src/PhpZip/Exception/ZipEntryNotFoundException.php
  9. 1 0
      src/PhpZip/Exception/ZipException.php
  10. 0 14
      src/PhpZip/Exception/ZipNotFoundEntry.php
  11. 0 14
      src/PhpZip/Exception/ZipUnsupportMethod.php
  12. 3 0
      src/PhpZip/Exception/ZipUnsupportMethodException.php
  13. 2 0
      src/PhpZip/Extra/ExtraField.php
  14. 100 62
      src/PhpZip/Extra/ExtraFieldsCollection.php
  15. 52 28
      src/PhpZip/Extra/ExtraFieldsFactory.php
  16. 14 10
      src/PhpZip/Extra/Fields/ApkAlignmentExtraField.php
  17. 7 8
      src/PhpZip/Extra/Fields/DefaultExtraField.php
  18. 4 1
      src/PhpZip/Extra/Fields/JarMarkerExtraField.php
  19. 13 8
      src/PhpZip/Extra/Fields/NtfsExtraField.php
  20. 50 24
      src/PhpZip/Extra/Fields/WinZipAesEntryExtraField.php
  21. 25 19
      src/PhpZip/Extra/Fields/Zip64ExtraField.php
  22. 0 43
      src/PhpZip/Mapper/OffsetPositionMapper.php
  23. 0 30
      src/PhpZip/Mapper/PositionMapper.php
  24. 62 25
      src/PhpZip/Model/EndOfCentralDirectory.php
  25. 6 7
      src/PhpZip/Model/Entry/OutputOffsetEntry.php
  26. 335 183
      src/PhpZip/Model/Entry/ZipAbstractEntry.php
  27. 10 6
      src/PhpZip/Model/Entry/ZipChangesEntry.php
  28. 12 30
      src/PhpZip/Model/Entry/ZipNewEntry.php
  29. 12 8
      src/PhpZip/Model/Entry/ZipNewFileEntry.php
  30. 19 19
      src/PhpZip/Model/Entry/ZipSourceEntry.php
  31. 160 54
      src/PhpZip/Model/ZipEntry.php
  32. 88 54
      src/PhpZip/Model/ZipEntryMatcher.php
  33. 184 145
      src/PhpZip/Model/ZipInfo.php
  34. 77 55
      src/PhpZip/Model/ZipModel.php
  35. 101 65
      src/PhpZip/Stream/ResponseStream.php
  36. 357 299
      src/PhpZip/Stream/ZipInputStream.php
  37. 13 5
      src/PhpZip/Stream/ZipInputStreamInterface.php
  38. 174 100
      src/PhpZip/Stream/ZipOutputStream.php
  39. 1 1
      src/PhpZip/Stream/ZipOutputStreamInterface.php
  40. 9 21
      src/PhpZip/Util/CryptoUtil.php
  41. 16 12
      src/PhpZip/Util/DateTimeConverter.php
  42. 63 32
      src/PhpZip/Util/FilesUtil.php
  43. 10 5
      src/PhpZip/Util/Iterator/IgnoreFilesFilterIterator.php
  44. 12 7
      src/PhpZip/Util/Iterator/IgnoreFilesRecursiveFilterIterator.php
  45. 16 9
      src/PhpZip/Util/PackUtil.php
  46. 8 30
      src/PhpZip/Util/StringUtil.php
  47. BIN
      src/PhpZip/Util/encodings/cp866-utf8.php
  48. 339 237
      src/PhpZip/ZipFile.php
  49. 237 157
      src/PhpZip/ZipFileInterface.php
  50. 72 0
      tests/PhpZip/Internal/DummyFileSystemStream.php
  51. 18 0
      tests/PhpZip/Internal/ZipFileExtended.php
  52. 14 67
      tests/PhpZip/Issue24Test.php
  53. 62 34
      tests/PhpZip/PhpZipExtResourceTest.php
  54. 44 0
      tests/PhpZip/Zip64Test.php
  55. 50 38
      tests/PhpZip/ZipAlignTest.php
  56. 20 24
      tests/PhpZip/ZipEventTest.php
  57. 155 97
      tests/PhpZip/ZipFileAddDirTest.php
  58. 263 213
      tests/PhpZip/ZipFileTest.php
  59. 54 32
      tests/PhpZip/ZipMatcherTest.php
  60. 157 110
      tests/PhpZip/ZipPasswordTest.php
  61. 28 21
      tests/PhpZip/ZipRemoteFileTest.php
  62. 8 5
      tests/PhpZip/ZipSlipVulnerabilityTest.php
  63. 47 35
      tests/PhpZip/ZipTestCase.php

+ 1500 - 0
.php_cs

@@ -0,0 +1,1500 @@
+<?php
+
+/**
+ * PHP Code Style Fixer (config created for version 2.16.1 (Yellow Bird)).
+ *
+ * Use one of the following console commands to just see the
+ * changes that will be made.
+ * - `php-cs-fixer fix --config='.php_cs' --diff-format udiff --dry-run`
+ * - `php '.php_cs'`
+ *
+ * Use one of the following console commands to fix PHP code:
+ * - `php-cs-fixer fix --config='.php_cs' --diff-format udiff`
+ * - `php '.php_cs' --force`
+ *
+ * @see https://cs.symfony.com/
+ */
+$rules = [
+    /*
+     * Each line of multi-line DocComments must have an asterisk [PSR-5]
+     * and must be aligned with the first one.
+     */
+    'align_multiline_comment' => 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 `<?php` tags or short-echo `<?=` tags
+     * and not other tag variations.
+     */
+    'full_opening_tag' => 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 `<?php echo` syntax.
+    'no_short_echo_tag' => 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__)
+    )
+;

+ 3 - 6
.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

+ 15 - 4
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"
+        }
+    }
 }

+ 314 - 59
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;
     }
 }

+ 98 - 62
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 */
+            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);
-        } else {
-            throw new RuntimeException('Extension openssl or mcrypt not loaded');
+            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 */
+            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);
-        } else {
-            throw new RuntimeException('Extension openssl or mcrypt not loaded');
+            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);
     }
 }

+ 6 - 2
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);

+ 3 - 4
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;
     }

+ 8 - 7
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;
     }
 

+ 1 - 0
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

+ 0 - 14
src/PhpZip/Exception/ZipNotFoundEntry.php

@@ -1,14 +0,0 @@
-<?php
-
-namespace PhpZip\Exception;
-
-/**
- * Thrown if entry not found.
- *
- * @author Ne-Lexa alexey@nelexa.ru
- * @license MIT
- * @deprecated Rename class exception, using ZipEntryNotFoundException
- */
-class ZipNotFoundEntry extends ZipEntryNotFoundException
-{
-}

+ 0 - 14
src/PhpZip/Exception/ZipUnsupportMethod.php

@@ -1,14 +0,0 @@
-<?php
-
-namespace PhpZip\Exception;
-
-/**
- * Thrown if entry unsupport compression method.
- *
- * @author Ne-Lexa alexey@nelexa.ru
- * @license MIT
- * @deprecated Rename exception class, using ZipUnsupportMethodException
- */
-class ZipUnsupportMethod extends ZipUnsupportMethodException
-{
-}

+ 3 - 0
src/PhpZip/Exception/ZipUnsupportMethodException.php

@@ -2,6 +2,9 @@
 
 namespace PhpZip\Exception;
 
+/**
+ * Class ZipUnsupportMethodException.
+ */
 class ZipUnsupportMethodException extends RuntimeException
 {
 }

+ 2 - 0
src/PhpZip/Extra/ExtraField.php

@@ -23,12 +23,14 @@ interface ExtraField
 
     /**
      * Serializes a Data Block.
+     *
      * @return string
      */
     public function serialize();
 
     /**
      * Initializes this Extra Field by deserializing a Data Block.
+     *
      * @param string $data
      */
     public function deserialize($data);

+ 100 - 62
src/PhpZip/Extra/ExtraFieldsCollection.php

@@ -31,51 +31,60 @@ class ExtraFieldsCollection implements \Countable, \ArrayAccess, \Iterator
      */
     public function count()
     {
-        return sizeof($this->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 <p>
-     * An offset to check for.
-     * </p>
-     * @return boolean true on success or false on failure.
-     * </p>
-     * <p>
-     * The return value will be casted to boolean if non-boolean was returned.
+     *                      An offset to check for.
+     *                      </p>
+     *
+     * @return bool true on success or false on failure.
+     *              </p>
+     *              <p>
+     *              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 <p>
-     * The offset to retrieve.
-     * </p>
-     * @return mixed Can return all value types.
-     * @since 5.0.0
+     *                      The offset to retrieve.
+     *                      </p>
+     *
      * @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 <p>
-     * The offset to assign the value to.
-     * </p>
-     * @param mixed $value <p>
-     * The value to set.
-     * </p>
-     * @return void
+     *                      The offset to assign the value to.
+     *                      </p>
+     * @param mixed $value  <p>
+     *                      The value to set.
+     *                      </p>
+     *
      * @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 <p>
-     * The offset to unset.
-     * </p>
-     * @return void
+     *                      The offset to unset.
+     *                      </p>
+     *
      * @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()

+ 52 - 28
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;
     }
 }

+ 14 - 10
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;

+ 7 - 8
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)

+ 4 - 1
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");
         }
     }

+ 13 - 8
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;
     }
 }

+ 50 - 24
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 <em>do</em> 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 <em>not</em> 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

+ 25 - 19
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)));
         }
     }

+ 0 - 43
src/PhpZip/Mapper/OffsetPositionMapper.php

@@ -1,43 +0,0 @@
-<?php
-
-namespace PhpZip\Mapper;
-
-/**
- * Adds a offset value to the given position.
- *
- * @author Ne-Lexa alexey@nelexa.ru
- * @license MIT
- */
-class OffsetPositionMapper extends PositionMapper
-{
-    /**
-     * @var int
-     */
-    private $offset;
-
-    /**
-     * @param int $offset
-     */
-    public function __construct($offset)
-    {
-        $this->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;
-    }
-}

+ 0 - 30
src/PhpZip/Mapper/PositionMapper.php

@@ -1,30 +0,0 @@
-<?php
-
-namespace PhpZip\Mapper;
-
-/**
- * Maps a given position.
- *
- * @author Ne-Lexa alexey@nelexa.ru
- * @license MIT
- */
-class PositionMapper
-{
-    /**
-     * @param int $position
-     * @return int
-     */
-    public function map($position)
-    {
-        return $position;
-    }
-
-    /**
-     * @param int $position
-     * @return int
-     */
-    public function unmap($position)
-    {
-        return $position;
-    }
-}

+ 62 - 25
src/PhpZip/Model/EndOfCentralDirectory.php

@@ -3,7 +3,7 @@
 namespace PhpZip\Model;
 
 /**
- * Read End of Central Directory
+ * End of Central Directory.
  *
  * @author Ne-Lexa alexey@nelexa.ru
  * @license MIT
@@ -11,11 +11,14 @@ namespace PhpZip\Model;
 class EndOfCentralDirectory
 {
     /** Zip64 End Of Central Directory Record. */
-    const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06064B50;
+    const ZIP64_END_OF_CD_RECORD_SIG = 0x06064B50;
+
     /** Zip64 End Of Central Directory Locator. */
-    const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG = 0x07064B50;
+    const ZIP64_END_OF_CD_LOCATOR_SIG = 0x07064B50;
+
     /** End Of Central Directory Record signature. */
-    const END_OF_CENTRAL_DIRECTORY_RECORD_SIG = 0x06054B50;
+    const END_OF_CD_SIG = 0x06054B50;
+
     /**
      * The minimum length of the End Of Central Directory Record.
      *
@@ -34,6 +37,7 @@ class EndOfCentralDirectory
      * zipfile comment length          2
      */
     const END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 22;
+
     /**
      * The length of the Zip64 End Of Central Directory Locator.
      * zip64 end of central dir locator
@@ -43,9 +47,10 @@ class EndOfCentralDirectory
      * central directory               4
      * relative offset of the zip64
      * end of central directory record 8
-     * total number of disks           4
+     * total number of disks           4.
      */
-    const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LEN = 20;
+    const ZIP64_END_OF_CD_LOCATOR_LEN = 20;
+
     /**
      * The minimum length of the Zip64 End Of Central Directory Record.
      *
@@ -68,38 +73,46 @@ class EndOfCentralDirectory
      * the starting disk number         8
      */
     const ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_MIN_LEN = 56;
-    /**
-     * @var string|null The archive comment.
-     */
-    private $comment;
-    /**
-     * @var int
-     */
+
+    /** @var int Count files. */
     private $entryCount;
-    /**
-     * @var bool
-     */
-    private $zip64 = false;
+
+    /** @var int Central Directory Offset. */
+    private $cdOffset;
+
+    /** @var int */
+    private $cdSize;
+
+    /** @var string|null The archive comment. */
+    private $comment;
+
+    /** @var bool Zip64 extension */
+    private $zip64;
 
     /**
      * EndOfCentralDirectory constructor.
-     * @param int $entryCount
-     * @param null|string $comment
-     * @param bool $zip64
+     *
+     * @param int        $entryCount
+     * @param int        $cdOffset
+     * @param int        $cdSize
+     * @param bool       $zip64
+     * @param mixed|null $comment
      */
-    public function __construct($entryCount, $comment, $zip64 = false)
+    public function __construct($entryCount, $cdOffset, $cdSize, $zip64, $comment = null)
     {
         $this->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
      */

+ 6 - 7
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)

+ 335 - 183
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
-     * @var int
-     */
-    private $encryptionMethod = ZipFileInterface::ENCRYPTION_METHOD_TRADITIONAL;
-    /**
+     *
+     * @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 $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()
     {

+ 10 - 6
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()
     {

+ 12 - 30
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)
-                )
-            );
+        return $this->content;
     }
 
     /**
-     * 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;
         }

+ 12 - 8
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);
     }
 }

+ 19 - 19
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);
         }
     }

+ 160 - 54
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,24 +106,76 @@ 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
+     *
+     * @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 setPlatform($platform);
+    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 external file attributes.
+     * Sets the internal file attributes.
+     *
+     * @param int $attributes the internal file attributes
+     *
+     * @throws ZipException
      *
-     * @param int $externalAttributes the external file attributes.
      * @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
+     *
      * @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

+ 88 - 54
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.
-     * </p>
-     * <p>
-     * The return value is cast to an integer.
+     *             </p>
+     *             <p>
+     *             The return value is cast to an integer.
+     *
      * @since 5.1.0
      */
     public function count()
     {
-        return count($this->matches);
+        return \count($this->matches);
     }
 }

+ 184 - 145
src/PhpZip/Model/ZipInfo.php

@@ -1,14 +1,15 @@
-<?php /** @noinspection PhpMissingBreakStatementInspection */
+<?php
 
 namespace PhpZip\Model;
 
+use PhpZip\Exception\ZipException;
 use PhpZip\Extra\Fields\NtfsExtraField;
 use PhpZip\Extra\Fields\WinZipAesEntryExtraField;
 use PhpZip\Util\FilesUtil;
-use PhpZip\ZipFileInterface;
+use PhpZip\ZipFile;
 
 /**
- * Zip info
+ * Zip info.
  *
  * @author Ne-Lexa alexey@nelexa.ru
  * @license MIT
@@ -17,53 +18,96 @@ class ZipInfo
 {
     // made by constants
     const MADE_BY_MS_DOS = 0;
+
     const MADE_BY_AMIGA = 1;
+
     const MADE_BY_OPEN_VMS = 2;
+
     const MADE_BY_UNIX = 3;
+
     const MADE_BY_VM_CMS = 4;
+
     const MADE_BY_ATARI = 5;
+
     const MADE_BY_OS_2 = 6;
+
     const MADE_BY_MACINTOSH = 7;
+
     const MADE_BY_Z_SYSTEM = 8;
+
     const MADE_BY_CP_M = 9;
+
     const MADE_BY_WINDOWS_NTFS = 10;
+
     const MADE_BY_MVS = 11;
+
     const MADE_BY_VSE = 12;
+
     const MADE_BY_ACORN_RISC = 13;
+
     const MADE_BY_VFAT = 14;
+
     const MADE_BY_ALTERNATE_MVS = 15;
+
     const MADE_BY_BEOS = 16;
+
     const MADE_BY_TANDEM = 17;
+
     const MADE_BY_OS_400 = 18;
+
     const MADE_BY_OS_X = 19;
+
     const MADE_BY_UNKNOWN = 20;
 
-    const UNX_IFMT = 0170000;    /* Unix file type mask */
-    const UNX_IFREG = 0100000;    /* Unix regular file */
-    const UNX_IFSOCK = 0140000;     /* Unix socket (BSD, not SysV or Amiga) */
-    const UNX_IFLNK = 0120000;    /* Unix symbolic link (not SysV, Amiga) */
-    const UNX_IFBLK = 0060000;    /* Unix block special       (not Amiga) */
-    const UNX_IFDIR = 0040000;    /* Unix directory */
-    const UNX_IFCHR = 0020000;    /* Unix character special   (not Amiga) */
-    const UNX_IFIFO = 0010000;    /* Unix fifo    (BCC, not MSC or Amiga) */
-    const UNX_ISUID = 04000;      /* Unix set user id on execution */
-    const UNX_ISGID = 02000;      /* Unix set group id on execution */
-    const UNX_ISVTX = 01000;      /* Unix directory permissions control */
-    const UNX_ENFMT = self::UNX_ISGID;  /* Unix record locking enforcement flag */
-    const UNX_IRWXU = 00700;      /* Unix read, write, execute: owner */
-    const UNX_IRUSR = 00400;      /* Unix read permission: owner */
-    const UNX_IWUSR = 00200;      /* Unix write permission: owner */
-    const UNX_IXUSR = 00100;      /* Unix execute permission: owner */
-    const UNX_IRWXG = 00070;      /* Unix read, write, execute: group */
-    const UNX_IRGRP = 00040;      /* Unix read permission: group */
-    const UNX_IWGRP = 00020;      /* Unix write permission: group */
-    const UNX_IXGRP = 00010;      /* Unix execute permission: group */
-    const UNX_IRWXO = 00007;      /* Unix read, write, execute: other */
-    const UNX_IROTH = 00004;      /* Unix read permission: other */
-    const UNX_IWOTH = 00002;      /* Unix write permission: other */
-    const UNX_IXOTH = 00001;      /* Unix execute permission: other */
-
-    private static $valuesMadeBy = [
+    const UNX_IFMT = 0170000;    // Unix file type mask
+
+    const UNX_IFREG = 0100000;    // Unix regular file
+
+    const UNX_IFSOCK = 0140000;     // Unix socket (BSD, not SysV or Amiga)
+
+    const UNX_IFLNK = 0120000;    // Unix symbolic link (not SysV, Amiga)
+
+    const UNX_IFBLK = 0060000;    // Unix block special       (not Amiga)
+
+    const UNX_IFDIR = 0040000;    // Unix directory
+
+    const UNX_IFCHR = 0020000;    // Unix character special   (not Amiga)
+
+    const UNX_IFIFO = 0010000;    // Unix fifo    (BCC, not MSC or Amiga)
+
+    const UNX_ISUID = 04000;      // Unix set user id on execution
+
+    const UNX_ISGID = 02000;      // Unix set group id on execution
+
+    const UNX_ISVTX = 01000;      // Unix directory permissions control
+
+    const UNX_ENFMT = self::UNX_ISGID;  // Unix record locking enforcement flag
+
+    const UNX_IRWXU = 00700;      // Unix read, write, execute: owner
+
+    const UNX_IRUSR = 00400;      // Unix read permission: owner
+
+    const UNX_IWUSR = 00200;      // Unix write permission: owner
+
+    const UNX_IXUSR = 00100;      // Unix execute permission: owner
+
+    const UNX_IRWXG = 00070;      // Unix read, write, execute: group
+
+    const UNX_IRGRP = 00040;      // Unix read permission: group
+
+    const UNX_IWGRP = 00020;      // Unix write permission: group
+
+    const UNX_IXGRP = 00010;      // Unix execute permission: group
+
+    const UNX_IRWXO = 00007;      // Unix read, write, execute: other
+
+    const UNX_IROTH = 00004;      // Unix read permission: other
+
+    const UNX_IWOTH = 00002;      // Unix write permission: other
+
+    const UNX_IXOTH = 00001;      // Unix execute permission: other
+
+    private static $platformNames = [
         self::MADE_BY_MS_DOS => '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()]);
+
+            $return .= ' ';
+        }
+
+        if (isset(self::$compressionMethodNames[$compressionMethod])) {
+            $return .= ucfirst(self::$compressionMethodNames[$compressionMethod]);
         } else {
-            $return = 'unknown';
+            $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()) . ', ' : '')

+ 77 - 55
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.
-     * </p>
-     * <p>
-     * The return value is cast to an integer.
+     *             </p>
+     *             <p>
+     *             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);
     }

+ 101 - 65
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.
      *
-     * @return string
      * @throws \RuntimeException if unable to read or an error occurs while
-     *     reading.
+     *                           reading
+     *
+     * @return string
      */
     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;
     }
 }

+ 357 - 299
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)
-            );
+            return true;
+        }
 
-            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);
-            }
+        return false;
+    }
 
-            // 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);
-        }
-        // Start recovering file entries from min.
-        $this->preamble = $min;
-        $this->postamble = $size - $min;
-        return new EndOfCentralDirectory(0, $comment);
+    /**
+     * 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;
-            }
-            $entries[$entry->getName()] = $entry;
-        }
+        fseek($this->in, $endOfCentralDirectory->getCdOffset());
 
-        if (($numEntries % 0x10000) !== 0) {
-            throw new ZipException("Expected " . abs($numEntries) .
-                ($numEntries > 0 ? " more" : " less") .
-                " entries in the Central Directory!");
+        if (!($cdStream = fopen('php://temp', 'w+b'))) {
+            throw new ZipException('Temp resource can not open from write');
         }
-
-        if ($this->preamble + $this->postamble >= fstat($this->in)['size']) {
-            $this->checkZipFileSignature();
+        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;
         }
+        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.");
-        }
-
-        // 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
+        if (unpack('V', fread($stream, 4))[1] !== ZipOutputStreamInterface::CENTRAL_FILE_HEADER_SIG) {
+            throw new ZipException('Corrupt zip file. Cannot read central dir entry.');
+        }
+
         $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;
+
+        $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;
 
-        // 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;
+        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()));
+        }
+
+        $nameLength = \strlen($entry->getName());
 
-        fseek($this->in, $pos + ZipEntry::LOCAL_FILE_HEADER_MIN_LEN - 2, SEEK_SET);
+        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());
     }

+ 13 - 5
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);

+ 174 - 100
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);

+ 1 - 1
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

+ 9 - 21
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);
     }
 }

+ 16 - 12
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;
     }
 }

+ 63 - 32
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 !== '..';
                 }

+ 10 - 5
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;
     }
 }

+ 12 - 7
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);
     }
 }

+ 16 - 9
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;
     }
 }

+ 8 - 30
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);
-        }
-    }
 }

BIN
src/PhpZip/Util/encodings/cp866-utf8.php


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 339 - 237
src/PhpZip/ZipFile.php


+ 237 - 157
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();
 

+ 72 - 0
tests/PhpZip/Internal/DummyFileSystemStream.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace PhpZip\Internal;
+
+/**
+ * Try to load using dummy stream.
+ */
+class DummyFileSystemStream
+{
+    /** @var resource */
+    private $fp;
+
+    /**
+     * @param $path
+     * @param $mode
+     * @param $options
+     * @param $opened_path
+     *
+     * @return bool
+     */
+    public function stream_open($path, $mode, $options, &$opened_path)
+    {
+        $parsedUrl = parse_url($path);
+        $path = $parsedUrl['path'];
+        $this->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);
+    }
+}

+ 18 - 0
tests/PhpZip/Internal/ZipFileExtended.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace PhpZip\Internal;
+
+use PhpZip\ZipFile;
+
+/**
+ * Class ZipFileExtended.
+ */
+class ZipFileExtended extends ZipFile
+{
+    protected function onBeforeSave()
+    {
+        parent::onBeforeSave();
+        $this->setZipAlign(4);
+        $this->deleteFromRegex('~^META\-INF/~i');
+    }
+}

+ 14 - 67
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);
-    }
-}

+ 62 - 34
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();
     }
 }

+ 44 - 0
tests/PhpZip/Zip64Test.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace PhpZip;
+
+use PhpZip\Exception\ZipException;
+
+/**
+ * @internal
+ *
+ * @large
+ */
+class Zip64Test extends ZipTestCase
+{
+    /**
+     * 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'] = (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();
+    }
+}

+ 50 - 38
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);
     }
 }

+ 20 - 24
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();
     }
 }

+ 155 - 97
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();
     }
 }

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 263 - 213
tests/PhpZip/ZipFileTest.php


+ 54 - 32
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();

+ 157 - 110
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('<?php', $decryptContent);
+            static::assertNotEmpty($decryptContent);
+            static::assertContains('<?php', $decryptContent);
         }
 
         // change encryption method to WinZip Aes and update file
-        $zipFile->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('<?php', $decryptContent);
+            static::assertNotEmpty($decryptContent);
+            static::assertContains('<?php', $decryptContent);
         }
 
         // clear password
@@ -94,42 +108,48 @@ class ZipPasswordTest extends ZipFileAddDirTest
         $zipFile->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();
 

+ 28 - 21
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();
     }
-
 }

+ 8 - 5
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));
     }
 }

+ 47 - 35
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;
     }
 }

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov