defmodule GitGud.Packages.Fdroid.ManifestTest do
@moduledoc """
The binary `AndroidManifest.xml` reader, checked against a real one:
`test/support/fixtures/apk/gitgud-AndroidManifest.xml` is lifted
verbatim out of this repo's own `app-debug.apk`, so the parser is
tested against what AAPT2 actually emits rather than against a
fixture written to match the parser.
The malformed cases matter as much as the happy path. An APK is
attacker-supplied input from the registry's point of view, so every
truncation has to come back as an error rather than as a raise or a
hang.
"""
use ExUnit.Case, async: true
alias GitGud.Packages.Formats.Fdroid.Manifest
@fixture "test/support/fixtures/apk/gitgud-AndroidManifest.xml"
defp fixture, do: File.read!(@fixture)
describe "a real AAPT2 manifest" do
test "reads the package and version" do
assert {:ok, info} = Manifest.parse(fixture())
assert info.package == "org.neiam.gitgud.app"
assert info.version_code == 1
assert info.version_name == "0.1.0"
end
test "reads the SDK levels declared in build.gradle.kts" do
assert {:ok, info} = Manifest.parse(fixture())
assert info.min_sdk == 26
assert info.target_sdk == 34
end
test "reads the permissions the app asks for" do
assert {:ok, info} = Manifest.parse(fixture())
assert "android.permission.INTERNET" in info.permissions
assert "android.permission.CAMERA" in info.permissions
end
test "returns permissions sorted and deduplicated" do
assert {:ok, info} = Manifest.parse(fixture())
assert info.permissions == Enum.sort(info.permissions)
assert info.permissions == Enum.uniq(info.permissions)
end
end
describe "rejecting input" do
test "refuses something that isn't a binary manifest" do
assert {:error, {422, _}} = Manifest.parse("<?xml version=\"1.0\"?><manifest/>")
assert {:error, {422, _}} = Manifest.parse(<<>>)
assert {:error, {422, _}} = Manifest.parse(<<0, 1, 2, 3>>)
end
test "refuses a file whose declared size overruns the data" do
<<head::binary-size(4), _size::little-32, rest::binary>> = fixture()
lying = <<head::binary, 999_999::little-32, rest::binary>>
assert {:error, {422, message}} = Manifest.parse(lying)
assert message =~ "truncated"
end
test "refuses an implausibly large file without parsing it" do
assert {:error, {422, message}} = Manifest.parse(:binary.copy(<<0>>, 5 * 1024 * 1024))
assert message =~ "implausibly large"
end
@tag timeout: 10_000
test "terminates on every truncation of a real manifest" do
data = fixture()
# A zero-length chunk or one whose size runs past the end are the
# two shapes that could loop or read out of bounds; walking every
# prefix is the cheap way to be sure neither does.
for len <- Enum.take_every(1..byte_size(data), 37) do
prefix = binary_part(data, 0, len)
case Manifest.parse(prefix) do
{:ok, info} -> assert is_map(info)
{:error, {422, _}} -> :ok
end
end
end
end
describe "a manifest with nothing in it" do
test "reports nil fields rather than failing" do
# A well-formed file chunk with an empty string pool and no
# elements: legal, useless, and it must not crash the reader.
pool =
<<0x0001::little-16, 28::little-16, 28::little-32, 0::little-32, 0::little-32,
0::little-32, 28::little-32, 0::little-32>>
size = 8 + byte_size(pool)
data = <<0x0003::little-16, 8::little-16, size::little-32, pool::binary>>
assert {:ok, info} = Manifest.parse(data)
assert info.package == nil
assert info.version_code == nil
assert info.permissions == []
end
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
3.7 KiB · text
History
504e101