Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F13522246
safe_zip_test.exs
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
safe_zip_test.exs
View Options
defmodule
Pleroma.SafeZipTest
do
# Not making this async because it creates and deletes files
use
ExUnit.Case
alias
Pleroma.SafeZip
@fixtures_dir
"test/fixtures"
@tmp_dir
"test/zip_tmp"
setup
do
# Ensure tmp directory exists
File
.
mkdir_p!
(
@tmp_dir
)
on_exit
(
fn
->
# Clean up any files created during tests
File
.
rm_rf!
(
@tmp_dir
)
File
.
mkdir_p!
(
@tmp_dir
)
end
)
:ok
end
describe
"list_dir_file/1"
do
test
"lists files in a valid zip"
do
{
:ok
,
files
}
=
SafeZip
.
list_dir_file
(
Path
.
join
(
@fixtures_dir
,
"emojis.zip"
))
assert
is_list
(
files
)
assert
length
(
files
)
>
0
end
test
"returns an empty list for empty zip"
do
{
:ok
,
files
}
=
SafeZip
.
list_dir_file
(
Path
.
join
(
@fixtures_dir
,
"empty.zip"
))
assert
files
==
[]
end
test
"returns error for non-existent file"
do
assert
{
:error
,
_
}
=
SafeZip
.
list_dir_file
(
Path
.
join
(
@fixtures_dir
,
"nonexistent.zip"
))
end
test
"only lists regular files, not directories"
do
# Create a zip with both files and directories
zip_path
=
create_zip_with_directory
()
# List files with SafeZip
{
:ok
,
files
}
=
SafeZip
.
list_dir_file
(
zip_path
)
# Verify only regular files are listed, not directories
assert
"file_in_dir/test_file.txt"
in
files
assert
"root_file.txt"
in
files
# Directory entries should not be included in the list
refute
"file_in_dir/"
in
files
end
end
describe
"contains_all_data?/2"
do
test
"returns true when all files are in the archive"
do
# For this test, we'll create our own zip file with known content
# to ensure we can test the contains_all_data? function properly
zip_path
=
create_zip_with_directory
()
archive_data
=
File
.
read!
(
zip_path
)
# Check if the archive contains the root file
# Note: The function expects charlists (Erlang strings) in the MapSet
assert
SafeZip
.
contains_all_data?
(
archive_data
,
MapSet
.
new
([
~c"root_file.txt"
]))
end
test
"returns false when files are missing"
do
archive_path
=
Path
.
join
(
@fixtures_dir
,
"emojis.zip"
)
archive_data
=
File
.
read!
(
archive_path
)
# Create a MapSet with non-existent files
fset
=
MapSet
.
new
([
~c"nonexistent.txt"
])
refute
SafeZip
.
contains_all_data?
(
archive_data
,
fset
)
end
test
"returns false for invalid archive data"
do
refute
SafeZip
.
contains_all_data?
(
"invalid data"
,
MapSet
.
new
([
~c"file.txt"
]))
end
test
"only checks for regular files, not directories"
do
# Create a zip with both files and directories
zip_path
=
create_zip_with_directory
()
archive_data
=
File
.
read!
(
zip_path
)
# Check if the archive contains a directory (should return false)
refute
SafeZip
.
contains_all_data?
(
archive_data
,
MapSet
.
new
([
~c"file_in_dir/"
]))
# For this test, we'll manually check if the file exists in the archive
# by extracting it and verifying it exists
extract_dir
=
Path
.
join
(
@tmp_dir
,
"extract_check"
)
File
.
mkdir_p!
(
extract_dir
)
{
:ok
,
files
}
=
SafeZip
.
unzip_file
(
zip_path
,
extract_dir
)
# Verify the root file was extracted
assert
Enum
.
any?
(
files
,
fn
file
->
Path
.
basename
(
file
)
==
"root_file.txt"
end
)
# Verify the file exists on disk
assert
File
.
exists?
(
Path
.
join
(
extract_dir
,
"root_file.txt"
))
end
end
describe
"zip/4"
do
test
"creates a zip file on disk"
do
# Create a test file
test_file_path
=
Path
.
join
(
@tmp_dir
,
"test_file.txt"
)
File
.
write!
(
test_file_path
,
"test content"
)
# Create a zip file
zip_path
=
Path
.
join
(
@tmp_dir
,
"test.zip"
)
assert
{
:ok
,
^
zip_path
}
=
SafeZip
.
zip
(
zip_path
,
[
"test_file.txt"
],
@tmp_dir
,
false
)
# Verify the zip file exists
assert
File
.
exists?
(
zip_path
)
end
test
"creates a zip file in memory"
do
# Create a test file
test_file_path
=
Path
.
join
(
@tmp_dir
,
"test_file.txt"
)
File
.
write!
(
test_file_path
,
"test content"
)
# Create a zip file in memory
zip_name
=
Path
.
join
(
@tmp_dir
,
"test.zip"
)
assert
{
:ok
,
{
^
zip_name
,
zip_data
}}
=
SafeZip
.
zip
(
zip_name
,
[
"test_file.txt"
],
@tmp_dir
,
true
)
# Verify the zip data is binary
assert
is_binary
(
zip_data
)
end
test
"returns error for unsafe paths"
do
# Try to zip a file with path traversal
assert
{
:error
,
_
}
=
SafeZip
.
zip
(
Path
.
join
(
@tmp_dir
,
"test.zip"
),
[
"../fixtures/test.txt"
],
@tmp_dir
,
false
)
end
test
"can create zip with directories"
do
# Create a directory structure
dir_path
=
Path
.
join
(
@tmp_dir
,
"test_dir"
)
File
.
mkdir_p!
(
dir_path
)
file_in_dir_path
=
Path
.
join
(
dir_path
,
"file_in_dir.txt"
)
File
.
write!
(
file_in_dir_path
,
"file in directory"
)
# Create a zip file
zip_path
=
Path
.
join
(
@tmp_dir
,
"dir_test.zip"
)
assert
{
:ok
,
^
zip_path
}
=
SafeZip
.
zip
(
zip_path
,
[
"test_dir/file_in_dir.txt"
],
@tmp_dir
,
false
)
# Verify the zip file exists
assert
File
.
exists?
(
zip_path
)
# Extract and verify the directory structure is preserved
extract_dir
=
Path
.
join
(
@tmp_dir
,
"extract"
)
{
:ok
,
files
}
=
SafeZip
.
unzip_file
(
zip_path
,
extract_dir
)
# Check if the file path is in the list, accounting for possible full paths
assert
Enum
.
any?
(
files
,
fn
file
->
String
.
ends_with?
(
file
,
"file_in_dir.txt"
)
end
)
# Verify the file exists in the expected location
assert
File
.
exists?
(
Path
.
join
([
extract_dir
,
"test_dir"
,
"file_in_dir.txt"
]))
end
end
describe
"unzip_file/3"
do
test
"extracts files from a zip archive"
do
archive_path
=
Path
.
join
(
@fixtures_dir
,
"emojis.zip"
)
# Extract the archive
assert
{
:ok
,
files
}
=
SafeZip
.
unzip_file
(
archive_path
,
@tmp_dir
)
# Verify files were extracted
assert
is_list
(
files
)
assert
length
(
files
)
>
0
# Verify at least one file exists
first_file
=
List
.
first
(
files
)
# Simply check that the file exists in the tmp directory
assert
File
.
exists?
(
first_file
)
end
test
"extracts specific files from a zip archive"
do
archive_path
=
Path
.
join
(
@fixtures_dir
,
"emojis.zip"
)
# Get list of files in the archive
{
:ok
,
all_files
}
=
SafeZip
.
list_dir_file
(
archive_path
)
file_to_extract
=
List
.
first
(
all_files
)
# Extract only one file
assert
{
:ok
,
[
extracted_file
]}
=
SafeZip
.
unzip_file
(
archive_path
,
@tmp_dir
,
[
file_to_extract
])
# Verify only the specified file was extracted
assert
Path
.
basename
(
extracted_file
)
==
Path
.
basename
(
file_to_extract
)
# Check that the file exists in the tmp directory
assert
File
.
exists?
(
Path
.
join
(
@tmp_dir
,
Path
.
basename
(
file_to_extract
)))
end
test
"returns error for invalid zip file"
do
invalid_path
=
Path
.
join
(
@tmp_dir
,
"invalid.zip"
)
File
.
write!
(
invalid_path
,
"not a zip file"
)
assert
{
:error
,
_
}
=
SafeZip
.
unzip_file
(
invalid_path
,
@tmp_dir
)
end
test
"creates directories when extracting files in subdirectories"
do
# Create a zip with files in subdirectories
zip_path
=
create_zip_with_directory
()
# Extract the archive
assert
{
:ok
,
files
}
=
SafeZip
.
unzip_file
(
zip_path
,
@tmp_dir
)
# Verify files were extracted - handle both relative and absolute paths
assert
Enum
.
any?
(
files
,
fn
file
->
Path
.
basename
(
file
)
==
"test_file.txt"
&&
String
.
contains?
(
file
,
"file_in_dir"
)
end
)
assert
Enum
.
any?
(
files
,
fn
file
->
Path
.
basename
(
file
)
==
"root_file.txt"
end
)
# Verify directory was created
dir_path
=
Path
.
join
(
@tmp_dir
,
"file_in_dir"
)
assert
File
.
exists?
(
dir_path
)
assert
File
.
dir?
(
dir_path
)
# Verify file in directory was extracted
file_path
=
Path
.
join
(
dir_path
,
"test_file.txt"
)
assert
File
.
exists?
(
file_path
)
end
end
describe
"unzip_data/3"
do
test
"extracts files from zip data"
do
archive_path
=
Path
.
join
(
@fixtures_dir
,
"emojis.zip"
)
archive_data
=
File
.
read!
(
archive_path
)
# Extract the archive from data
assert
{
:ok
,
files
}
=
SafeZip
.
unzip_data
(
archive_data
,
@tmp_dir
)
# Verify files were extracted
assert
is_list
(
files
)
assert
length
(
files
)
>
0
# Verify at least one file exists
first_file
=
List
.
first
(
files
)
# Simply check that the file exists in the tmp directory
assert
File
.
exists?
(
first_file
)
end
test
"extracts specific files from zip data"
do
archive_path
=
Path
.
join
(
@fixtures_dir
,
"emojis.zip"
)
archive_data
=
File
.
read!
(
archive_path
)
# Get list of files in the archive
{
:ok
,
all_files
}
=
SafeZip
.
list_dir_file
(
archive_path
)
file_to_extract
=
List
.
first
(
all_files
)
# Extract only one file
assert
{
:ok
,
extracted_files
}
=
SafeZip
.
unzip_data
(
archive_data
,
@tmp_dir
,
[
file_to_extract
])
# Verify only the specified file was extracted
assert
Enum
.
any?
(
extracted_files
,
fn
path
->
Path
.
basename
(
path
)
==
Path
.
basename
(
file_to_extract
)
end
)
# Simply check that the file exists in the tmp directory
assert
File
.
exists?
(
Path
.
join
(
@tmp_dir
,
Path
.
basename
(
file_to_extract
)))
end
test
"returns error for invalid zip data"
do
assert
{
:error
,
_
}
=
SafeZip
.
unzip_data
(
"not a zip file"
,
@tmp_dir
)
end
test
"creates directories when extracting files in subdirectories from data"
do
# Create a zip with files in subdirectories
zip_path
=
create_zip_with_directory
()
archive_data
=
File
.
read!
(
zip_path
)
# Extract the archive from data
assert
{
:ok
,
files
}
=
SafeZip
.
unzip_data
(
archive_data
,
@tmp_dir
)
# Verify files were extracted - handle both relative and absolute paths
assert
Enum
.
any?
(
files
,
fn
file
->
Path
.
basename
(
file
)
==
"test_file.txt"
&&
String
.
contains?
(
file
,
"file_in_dir"
)
end
)
assert
Enum
.
any?
(
files
,
fn
file
->
Path
.
basename
(
file
)
==
"root_file.txt"
end
)
# Verify directory was created
dir_path
=
Path
.
join
(
@tmp_dir
,
"file_in_dir"
)
assert
File
.
exists?
(
dir_path
)
assert
File
.
dir?
(
dir_path
)
# Verify file in directory was extracted
file_path
=
Path
.
join
(
dir_path
,
"test_file.txt"
)
assert
File
.
exists?
(
file_path
)
end
end
# Security tests
describe
"security checks"
do
test
"prevents path traversal in zip extraction"
do
# Create a malicious zip file with path traversal
malicious_zip_path
=
create_malicious_zip_with_path_traversal
()
# Try to extract it with SafeZip
assert
{
:error
,
_
}
=
SafeZip
.
unzip_file
(
malicious_zip_path
,
@tmp_dir
)
# Verify the file was not extracted outside the target directory
refute
File
.
exists?
(
Path
.
join
(
Path
.
dirname
(
@tmp_dir
),
"traversal_attempt.txt"
))
end
test
"prevents directory traversal in zip listing"
do
# Create a malicious zip file with path traversal
malicious_zip_path
=
create_malicious_zip_with_path_traversal
()
# Try to list files with SafeZip
assert
{
:error
,
_
}
=
SafeZip
.
list_dir_file
(
malicious_zip_path
)
end
test
"prevents path traversal in zip data extraction"
do
# Create a malicious zip file with path traversal
malicious_zip_path
=
create_malicious_zip_with_path_traversal
()
malicious_data
=
File
.
read!
(
malicious_zip_path
)
# Try to extract it with SafeZip
assert
{
:error
,
_
}
=
SafeZip
.
unzip_data
(
malicious_data
,
@tmp_dir
)
# Verify the file was not extracted outside the target directory
refute
File
.
exists?
(
Path
.
join
(
Path
.
dirname
(
@tmp_dir
),
"traversal_attempt.txt"
))
end
test
"handles zip bomb attempts"
do
# Create a zip bomb (a zip with many files or large files)
zip_bomb_path
=
create_zip_bomb
()
# The SafeZip module should handle this gracefully
# Either by successfully extracting it (if it's not too large)
# or by returning an error (if it detects a potential zip bomb)
result
=
SafeZip
.
unzip_file
(
zip_bomb_path
,
@tmp_dir
)
case
result
do
{
:ok
,
_
}
->
# If it successfully extracts, make sure it didn't fill up the disk
# This is a simple check to ensure the extraction was controlled
assert
File
.
exists?
(
@tmp_dir
)
{
:error
,
_
}
->
# If it returns an error, that's also acceptable
# The important thing is that it doesn't crash or hang
assert
true
end
end
test
"handles deeply nested directory structures"
do
# Create a zip with deeply nested directories
deep_nest_path
=
create_deeply_nested_zip
()
# The SafeZip module should handle this gracefully
result
=
SafeZip
.
unzip_file
(
deep_nest_path
,
@tmp_dir
)
case
result
do
{
:ok
,
files
}
->
# If it successfully extracts, verify the files were extracted
assert
is_list
(
files
)
assert
length
(
files
)
>
0
{
:error
,
_
}
->
# If it returns an error, that's also acceptable
# The important thing is that it doesn't crash or hang
assert
true
end
end
end
# Helper functions to create test fixtures
# Creates a zip file with a path traversal attempt
defp
create_malicious_zip_with_path_traversal
do
malicious_zip_path
=
Path
.
join
(
@tmp_dir
,
"path_traversal.zip"
)
# Create a file to include in the zip
test_file_path
=
Path
.
join
(
@tmp_dir
,
"test_file.txt"
)
File
.
write!
(
test_file_path
,
"malicious content"
)
# Use Erlang's zip module directly to create a zip with path traversal
{
:ok
,
charlist_path
}
=
:zip
.
create
(
String
.
to_charlist
(
malicious_zip_path
),
[{
String
.
to_charlist
(
"../traversal_attempt.txt"
),
File
.
read!
(
test_file_path
)}]
)
to_string
(
charlist_path
)
end
# Creates a zip file with directory entries
defp
create_zip_with_directory
do
zip_path
=
Path
.
join
(
@tmp_dir
,
"with_directory.zip"
)
# Create files to include in the zip
root_file_path
=
Path
.
join
(
@tmp_dir
,
"root_file.txt"
)
File
.
write!
(
root_file_path
,
"root file content"
)
# Create a directory and a file in it
dir_path
=
Path
.
join
(
@tmp_dir
,
"file_in_dir"
)
File
.
mkdir_p!
(
dir_path
)
file_in_dir_path
=
Path
.
join
(
dir_path
,
"test_file.txt"
)
File
.
write!
(
file_in_dir_path
,
"file in directory content"
)
# Use Erlang's zip module to create a zip with directory structure
{
:ok
,
charlist_path
}
=
:zip
.
create
(
String
.
to_charlist
(
zip_path
),
[
{
String
.
to_charlist
(
"root_file.txt"
),
File
.
read!
(
root_file_path
)},
{
String
.
to_charlist
(
"file_in_dir/test_file.txt"
),
File
.
read!
(
file_in_dir_path
)}
]
)
to_string
(
charlist_path
)
end
# Creates a zip bomb (a zip with many small files)
defp
create_zip_bomb
do
zip_path
=
Path
.
join
(
@tmp_dir
,
"zip_bomb.zip"
)
# Create a small file to duplicate many times
small_file_path
=
Path
.
join
(
@tmp_dir
,
"small_file.txt"
)
File
.
write!
(
small_file_path
,
String
.
duplicate
(
"A"
,
100
))
# Create a list of many files to include in the zip
file_entries
=
for
i
<-
1
..
100
do
{
String
.
to_charlist
(
"file_
#{
i
}
.txt"
),
File
.
read!
(
small_file_path
)}
end
# Use Erlang's zip module to create a zip with many files
{
:ok
,
charlist_path
}
=
:zip
.
create
(
String
.
to_charlist
(
zip_path
),
file_entries
)
to_string
(
charlist_path
)
end
# Creates a zip with deeply nested directories
defp
create_deeply_nested_zip
do
zip_path
=
Path
.
join
(
@tmp_dir
,
"deep_nest.zip"
)
# Create a file to include in the zip
file_content
=
"test content"
# Create a list of deeply nested files
file_entries
=
for
i
<-
1
..
10
do
nested_path
=
Enum
.
reduce
(
1
..
i
,
"nested"
,
fn
j
,
acc
->
"
#{
acc
}
/level_
#{
j
}
"
end
)
{
String
.
to_charlist
(
"
#{
nested_path
}
/file.txt"
),
file_content
}
end
# Use Erlang's zip module to create a zip with deeply nested directories
{
:ok
,
charlist_path
}
=
:zip
.
create
(
String
.
to_charlist
(
zip_path
),
file_entries
)
to_string
(
charlist_path
)
end
end
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Dec 7, 8:49 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
700269
Default Alt Text
safe_zip_test.exs (16 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment