From a93a37715569165b8f399c2a4dfaf1030f6dabb6 Mon Sep 17 00:00:00 2001 From: niamtokik Date: Mon, 29 Mar 2021 20:04:07 +0000 Subject: [PATCH] first commit --- .formatter.exs | 4 + .gitignore | 27 +++++ README.md | 19 ++++ lib/dotzip.ex | 227 +++++++++++++++++++++++++++++++++++++++++++ mix.exs | 28 ++++++ test/dotzip_test.exs | 8 ++ test/test_helper.exs | 1 + 7 files changed, 314 insertions(+) create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 README.md create mode 100644 lib/dotzip.ex create mode 100644 mix.exs create mode 100644 test/dotzip_test.exs create mode 100644 test/test_helper.exs diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f2781f --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +dotzip-*.tar + + +# Temporary files for e.g. tests +/tmp diff --git a/README.md b/README.md new file mode 100644 index 0000000..9aa1c8d --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Dotzip + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `dotzip` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:dotzip, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/dotzip](https://hexdocs.pm/dotzip). + diff --git a/lib/dotzip.ex b/lib/dotzip.ex new file mode 100644 index 0000000..d026961 --- /dev/null +++ b/lib/dotzip.ex @@ -0,0 +1,227 @@ +defmodule Dotzip do + + @moduledoc """ + Elixir Implementation of ZIP File Format. https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT + """ + + def decode(file) do + {:ok, local, rest} = Dotzip.LocalFileHeader.decode(file) + {:ok, central, r} = Dotzip.CentralDirectoryHeader.decode(rest) + {:ok, e, rr} = Dotzip.EndCentralDirectory.decode(r) + {local, central, e, rr} + end + +end + +defmodule Dotzip.LocalFileHeader do + + defp signature(<< 0x50, 0x4b, 0x03, 0x04, rest::bitstring >>) do + {:ok, %{}, rest} + end + + defp version({:ok, data, << version::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :version, version), rest} + end + + defp purpose_flag({:ok, data, << purpose_flag::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :purpose_flag, purpose_flag), rest} + end + + defp compression_method({:ok, data, << compression_method::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :compression_method, compression_method), rest} + end + + defp last_modification_time({:ok, data, << last_modification::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :last_modification_time, last_modification), rest} + end + + defp last_modification_date({:ok, data, << last_modification_date::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :last_modification_date, last_modification_date), rest} + end + + defp crc32({:ok, data, << crc32::binary-size(4), rest::bitstring >>}) do + {:ok, Map.put(data, :crc32, crc32), rest} + end + + defp compressed_size({:ok, data, << compressed_size::little-size(32), rest::bitstring >>}) do + {:ok, Map.put(data, :compressed_size, compressed_size), rest} + end + + defp uncompressed_size({:ok, data, << uncompressed_size::little-size(32), rest::bitstring >>}) do + {:ok, Map.put(data, :uncompressed_size, uncompressed_size), rest} + end + + defp file_name_length({:ok, data, << file_name_length::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :file_name_length, file_name_length), rest } + end + + defp extra_field_length({:ok, data, << extra_field_length::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :extra_field_length, extra_field_length), rest} + end + + defp file_name({:ok, data, rest}) do + %{ :file_name_length => file_name_length } = data + <> = rest + {:ok, Map.put(data, :file_name, file_name), r} + end + + defp extra_field({:ok, data, rest}) do + %{ :extra_field_length => extra_field_length } = data + <> = rest + {:ok, Map.put(data, :extra_field, extra_field), r} + end + + defp content({:ok, data, rest}) do + %{ :compressed_size => compressed_size } = data + <> = rest + {:ok, Map.put(data, :content, content), r} + end + + def decode(file) do + {:ok, local_file_header, rest} = signature(file) + |> version() + |> purpose_flag() + |> compression_method() + |> last_modification_time() + |> last_modification_date() + |> crc32() + |> compressed_size() + |> uncompressed_size() + |> file_name_length() + |> extra_field_length() + |> file_name() + |> extra_field() + |> content() + end + +end + +defmodule Dotzip.CentralDirectoryHeader do + + defp signature(<< 0x50, 0x4b, 0x01, 0x02, rest::bitstring >>) do + {:ok, %{}, rest} + end + + defp version_made({:ok, data, << version::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :version_made, version), rest} + end + + defp version_needed({:ok, data, << version::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :version_needed, version), rest} + end + + defp purpose_flag({:ok, data, << purpose_flag::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :purpose_flag, purpose_flag), rest} + end + + defp compression_method({:ok, data, << compression_method::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :compression_method, compression_method), rest} + end + + defp last_modification_time({:ok, data, << last_modification::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :last_modification_time, last_modification), rest} + end + + defp last_modification_date({:ok, data, << last_modification_date::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :last_modification_date, last_modification_date), rest} + end + + defp crc32({:ok, data, << crc32::binary-size(4), rest::bitstring >>}) do + {:ok, Map.put(data, :crc32, crc32), rest} + end + + defp compressed_size({:ok, data, << compressed_size::little-size(32), rest::bitstring >>}) do + {:ok, Map.put(data, :compressed_size, compressed_size), rest} + end + + defp uncompressed_size({:ok, data, << uncompressed_size::little-size(32), rest::bitstring >>}) do + {:ok, Map.put(data, :uncompressed_size, uncompressed_size), rest} + end + + defp file_name_length({:ok, data, << file_name_length::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :file_name_length, file_name_length), rest } + end + + defp extra_field_length({:ok, data, << extra_field_length::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :extra_field_length, extra_field_length), rest} + end + + defp file_comment_length({:ok, data, << file_comment_length::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :file_comment_length, file_comment_length), rest} + end + + defp disk_number_start({:ok, data, << disk_number_start::little-size(16), rest::bitstring >>}) do + {:ok, Map.put(data, :disk_number_start, disk_number_start), rest} + end + + defp internal_file_attributes({:ok, data, << internal_file_attributes::binary-size(2), rest::bitstring >>}) do + {:ok, Map.put(data, :internal_file_attributes, internal_file_attributes), rest} + end + + defp external_file_attributes({:ok, data, << external_file_attributes::binary-size(4), rest::bitstring >>}) do + {:ok, Map.put(data, :external_file_attributes, external_file_attributes), rest} + end + + defp relative_offset({:ok, data, << relative_offset::little-size(32), rest::bitstring >>}) do + {:ok, Map.put(data, :relative_offset, relative_offset), rest} + end + + defp file_name({:ok, data, rest}) do + %{ :file_name_length => file_name_length } = data + <> = rest + {:ok, Map.put(data, :file_name, file_name), r} + end + + defp extra_field({:ok, data, rest}) do + %{ :extra_field_length => extra_field_length } = data + <> = rest + {:ok, Map.put(data, :extra_field, extra_field), r} + end + + defp file_comment({:ok, data, rest}) do + %{ :file_comment_length => file_comment_length } = data + <> = rest + {:ok, Map.put(data, :file_comment, file_comment), r} + end + + def decode(file) do + {:ok, central_directory_header, rest} = signature(file) + |> version_made() + |> version_needed() + |> purpose_flag() + |> compression_method() + |> last_modification_time() + |> last_modification_date() + |> crc32() + |> compressed_size() + |> uncompressed_size() + |> file_name_length() + |> extra_field_length() + |> file_comment_length() + |> disk_number_start() + |> internal_file_attributes() + |> external_file_attributes() + |> relative_offset() + |> file_name() + |> extra_field() + |> file_comment() + end + +end + +defmodule Dotzip.EndCentralDirectory do + + defp signature(<< 0x50, 0x4b, 0x05, 0x06, rest::bitstring >>) do + {:ok, %{}, rest} + end + + defp number_disk({:ok, data, << number_disk::little-size(32), rest::bitstring >>}) do + {:ok, Map.put(data, :number_disk, number_disk), rest} + end + + def decode(file) do + {:ok, end_central_directory, rest} = signature(file) + |> number_disk() + end + +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..8c14dd3 --- /dev/null +++ b/mix.exs @@ -0,0 +1,28 @@ +defmodule Dotzip.MixProject do + use Mix.Project + + def project do + [ + app: :dotzip, + version: "0.1.0", + elixir: "~> 1.11", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/test/dotzip_test.exs b/test/dotzip_test.exs new file mode 100644 index 0000000..e787674 --- /dev/null +++ b/test/dotzip_test.exs @@ -0,0 +1,8 @@ +defmodule DotzipTest do + use ExUnit.Case + doctest Dotzip + + test "local file header" do + assert :world == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()