diff --git a/.github/cancelled_workflows/qodana_code_quality.yml b/.github/cancelled_workflows/qodana_code_quality.yml
new file mode 100644
index 0000000..e7c9bfe
--- /dev/null
+++ b/.github/cancelled_workflows/qodana_code_quality.yml
@@ -0,0 +1,25 @@
+name: Qodana
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - dev
+ - main
+
+jobs:
+ qodana:
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: write
+ pull-requests: write
+ checks: write
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+ - name: 'Qodana Scan'
+ uses: JetBrains/qodana-action@v2024.1
+ env:
+ QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml
index da14177..10a338f 100644
--- a/.github/workflows/cmake-multi-platform.yml
+++ b/.github/workflows/cmake-multi-platform.yml
@@ -3,158 +3,85 @@ name: CMake Build
on:
push:
branches: [ "main", "dev" ]
- pull_request:
- branches: [ "main" ]
jobs:
build:
runs-on: ${{ matrix.os }}
+ env:
+ GCC_MAJOR: 14
strategy:
fail-fast: false
matrix:
- os: [ ubuntu-latest, macos-13 ]
- build_type: [ Debug, Release ]
+ os: [ ubuntu-24.04, macos-latest ]
+ build_type: [ Debug ]
c_compiler: [ clang ]
include:
- - os: macos-13
+ - os: macos-latest
c_compiler: clang
- cpp_compiler: clang++
- env:
- LDFLAGS=: "-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
- CPPFLAGS: "-I/usr/local/opt/llvm/include I/usr/local/opt/llvm/include/c++/v1"
- LD_LIBRARY_PATH: "/usr/local/opt/llvm/lib"
- DYLD_LIBRARY_PATH: "/usr/local/opt/llvm/lib"
+ cpp_compiler: clang++-18
-# - os: macos-13
-# c_compiler: gcc
-# cpp_compiler: g++-13
-#
-# - os: ubuntu-latest
-# c_compiler: gcc
-# cpp_compiler: g++-13
-
- - os: ubuntu-latest
+ - os: ubuntu-24.04
c_compiler: clang
- cpp_compiler: clang++-17
-
- # Don't include the following configurations in the matrix
- exclude:
- - os: macos-13
- build_type: Debug
+ cpp_compiler: clang++-18
steps:
- # Install dependencies: cmake, ninja, gcc, libgcrypt, openssl, readline, and libsodium
- name: Install Dependencies
- if: matrix.os == 'macos-13'
+ if: matrix.os == 'macos-latest'
run: |
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE
brew update
- brew install llvm cmake ninja gcc libgcrypt openssl@3 readline libsodium
- echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.bash_profile
- echo 'export PATH="/usr/local/opt/gcc@13/bin:$PATH"' >> ~/.bash_profile
- echo 'export PATH="/usr/local/opt/gcc@13/lib/gcc/13:$PATH"' >> ~/.bash_profile
-
- # - name: Install Dependencies
-# if: matrix.os == 'ubuntu-latest'
-# run: |
-# wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
-# sudo add-apt-repository -y "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main"
-# sudo add-apt-repository -y ppa:ubuntu-toolchain-r/ppa
-# sudo apt update
-# sudo apt install -y cmake ninja-build gcc-13 g++-13 clang-17 lldb-17 lld-17 libc++-17-dev libc++abi-17-dev \
-# libomp-17-dev libgcrypt20 openssl libreadline8 libsodium23 libsodium-dev
-
+ brew install ninja cmake git gcc@${{ env.GCC_MAJOR }}
+ brew reinstall llvm
+ echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.bash_profile
+ echo 'export PATH="/opt/homebrew/opt/gcc/bin:$PATH"' >> ~/.bash_profile
+ echo 'export PATH="/opt/homebrew/opt/gcc/lib/gcc/${{ env.GCC_MAJOR }}:$PATH"' >> ~/.bash_profile
+ . ~/.bash_profile
- uses: actions/checkout@v4
- name: Set reusable strings
id: strings
shell: bash
+ working-directory: ${{ github.workspace }}
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
-
- # Build project
+ # Set the paths to the GCC include and lib directories on macOS
+ if [ "${{ matrix.os }}" == "macos-latest" ]; then
+ echo "gcc-include-dir=$(./scripts/search.sh "/opt/homebrew/Cellar/gcc/${{ env.GCC_MAJOR }}*/include/c++/${{ env.GCC_MAJOR }}")" >> "$GITHUB_OUTPUT"
+ echo "gcc-sys-include-dir=$(./scripts/search.sh "/opt/homebrew/Cellar/gcc/${{ env.GCC_MAJOR }}*/include/c++/${{ env.GCC_MAJOR }}/*-apple-darwin*")" >> "$GITHUB_OUTPUT"
+ echo "gcc-lib-dir=$(./scripts/search.sh "/opt/homebrew/Cellar/gcc/${{ env.GCC_MAJOR }}*/lib/gcc/current")" >> "$GITHUB_OUTPUT"
+ fi
+
+ # Build the project
- name: Build PrivacyShield
- if: matrix.os == 'ubuntu-latest'
+ if: matrix.os == 'ubuntu-24.04'
run: |
- sudo ./scripts/buildscript.sh
- # OS=${{ matrix.os }}
- # COMMAND="./scripts/install-blake3.sh ${{ matrix.c_compiler }}"
- # if [ "$OS" == "macos-13" ]; then
- # $COMMAND
- # elif [ "$OS" == "ubuntu-latest" ]; then
- # sudo $COMMAND
- # fi
- #
+ sudo ./scripts/build.sh
- name: Install Blake3
- if: matrix.os == 'macos-13'
+ if: matrix.os == 'macos-latest'
run: |
- ./scripts/install-blake3.sh ${{ matrix.c_compiler }}
+ sudo ./scripts/install-blake3.sh ${{ matrix.c_compiler }}
- name: Configure CMake
- if: matrix.os == 'macos-13'
+ if: matrix.os == 'macos-latest'
run: >
- export LDFLAGS="-L/usr/local/opt/gcc@13/lib/gcc/13 -Wl,-rpath,/usr/local/opt/gcc@13/lib/gcc/13";
- export CPPFLAGS="-I/usr/local/opt/gcc@13/include/c++/13 -I/usr/local/opt/gcc@13/include/c++/13/x86_64-apple-darwin22";
- export LD_LIBRARY_PATH="/usr/local/opt/gcc@13/lib/gcc/13";
- export DYLD_LIBRARY_PATH="/usr/local/opt/gcc@13/lib/gcc/13";
-
cmake -B ${{ steps.strings.outputs.build-output-dir }}
- -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++
- -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang
- -DCMAKE_CXX_FLAGS="-I/usr/local/opt/gcc@13/include/c++/13 -I/usr/local/opt/gcc@13/include/c++/13/x86_64-apple-darwin22 -L/usr/local/opt/gcc@13/lib/gcc/13 -Wl,-rpath,/usr/local/opt/gcc@13/lib/gcc/13 -stdlib=libstdc++"
+ -DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm/bin/clang++
+ -DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm/bin/clang
+ -DCMAKE_CXX_FLAGS="-stdlib++-isystem ${{ steps.strings.outputs.gcc-include-dir }} -cxx-isystem ${{ steps.strings.outputs.gcc-sys-include-dir }}"
+ -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libstdc++ -L ${{ steps.strings.outputs.gcc-lib-dir }} -Wl,-rpath,/opt/homebrew/opt/gcc/lib/gcc/current"
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-S ${{ github.workspace }} -G Ninja
+ # -DCMAKE_CXX_FLAGS="-stdlib=libstdc++ -stdlib++-isystem /opt/homebrew/Cellar/gcc/14.1.0_1/include/c++/14 -cxx-isystem /opt/homebrew/Cellar/gcc/14.1.0_1/include/c++/14/aarch64-apple-darwin23 -L /opt/homebrew/Cellar/gcc/14.1.0_1/lib/gcc/14 -Wl,-rpath,/opt/homebrew/opt/gcc/lib/gcc/current"
- name: Build
- if: matrix.os == 'macos-13'
+ if: matrix.os == 'macos-latest'
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} -j 4
-#
-# - name: Test
-# working-directory: ${{ steps.strings.outputs.build-output-dir }}
-# # Execute tests defined by the CMake configuration
-# run: ctest --build-config ${{ matrix.build_type }}
-#
- - name: Package
- if: matrix.os == 'macos-13' && matrix.build_type == 'Release'
- working-directory: ${{ steps.strings.outputs.build-output-dir }}
- run: |
- cpack -G DragNDrop
- - name: Package
- if: matrix.os == 'ubuntu-latest' && matrix.build_type == 'Release'
+ # Run Tests
+ - name: Test
working-directory: ${{ steps.strings.outputs.build-output-dir }}
- run: |
- sudo cpack
- sudo chown -R $USER:$USER "${{ github.workspace }}/Packages"
-
- - name: Import GPG Key
- if: matrix.build_type == 'Release'
- uses: crazy-max/ghaction-import-gpg@v6
- with:
- gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }}
- passphrase: ${{ secrets.GPG_PASS }}
- trust_level: 5
-
- - name: Sign Package
- if: matrix.build_type == 'Release'
- working-directory: ${{ github.workspace }}
- run: |
- for file in Packages/*; do
- gpg --batch --status-file ~/gpg_log.txt --passphrase ${{ secrets.GPG_PASS }} --default-key dr8co@duck.com \
- --pinentry-mode=loopback --detach-sign "$file" || (cat ~/gpg_log.txt && exit 1)
- done
-#
-# # Upload the built artifacts
- - name: Upload Artifacts
- if: matrix.build_type == 'Release'
- uses: actions/upload-artifact@v4
- with:
- name: "${{ matrix.os }}-${{ matrix.build_type }}"
- path: "${{ github.workspace }}/Packages"
- overwrite: true
- if-no-files-found: 'warn'
-
+ run: ctest -j 4
diff --git a/.github/workflows/cpack-multi-platform.yml b/.github/workflows/cpack-multi-platform.yml
new file mode 100644
index 0000000..c5d4608
--- /dev/null
+++ b/.github/workflows/cpack-multi-platform.yml
@@ -0,0 +1,121 @@
+name: CPack Multi-Platform
+
+on:
+ pull_request:
+ branches: [ "main" ]
+
+jobs:
+ build_then_package:
+ runs-on: ${{ matrix.os }}
+ env:
+ GCC_MAJOR: 14
+
+ strategy:
+ fail-fast: false
+
+ matrix:
+ os: [ ubuntu-24.04, macos-latest ]
+ build_type: [ Release ]
+ c_compiler: [ clang ]
+ include:
+ - os: macos-latest
+ c_compiler: clang
+ cpp_compiler: clang++-18
+
+ - os: ubuntu-24.04
+ c_compiler: clang
+ cpp_compiler: clang++-18
+
+ steps:
+ - name: Install Dependencies
+ if: matrix.os == 'macos-latest'
+ run: |
+ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE
+ brew update
+ brew install ninja cmake git gcc@${{ env.GCC_MAJOR }}
+ brew reinstall llvm
+ echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.bash_profile
+ echo 'export PATH="/opt/homebrew/opt/gcc/bin:$PATH"' >> ~/.bash_profile
+ echo 'export PATH="/opt/homebrew/opt/gcc/lib/gcc/${{ env.GCC_MAJOR }}:$PATH"' >> ~/.bash_profile
+ . ~/.bash_profile
+
+ - uses: actions/checkout@v4
+
+ - name: Set reusable strings
+ id: strings
+ shell: bash
+ working-directory: ${{ github.workspace }}
+ run: |
+ echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
+ # Set the paths to the GCC include and lib directories on macOS
+ if [ "${{ matrix.os }}" == "macos-latest" ]; then
+ echo "gcc-include-dir=$(./scripts/search.sh "/opt/homebrew/Cellar/gcc/*/include/c++/${{ env.GCC_MAJOR }}")" >> "$GITHUB_OUTPUT"
+ echo "gcc-sys-include-dir=$(./scripts/search.sh "/opt/homebrew/Cellar/gcc/*/include/c++/${{ env.GCC_MAJOR }}/*-apple-darwin*")" >> "$GITHUB_OUTPUT"
+ echo "gcc-lib-dir=$(./scripts/search.sh "/opt/homebrew/Cellar/gcc/${{ env.GCC_MAJOR }}*/lib/gcc/current")" >> "$GITHUB_OUTPUT"
+ fi
+
+ # Build the project
+ - name: Build PrivacyShield
+ if: matrix.os == 'ubuntu-24.04'
+ run: |
+ sudo ./scripts/build.sh
+
+ - name: Install Blake3
+ if: matrix.os == 'macos-latest'
+ run: |
+ sudo ./scripts/install-blake3.sh ${{ matrix.c_compiler }}
+
+ - name: Configure CMake
+ if: matrix.os == 'macos-latest'
+ run: >
+ cmake -B ${{ steps.strings.outputs.build-output-dir }}
+ -DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm/bin/clang++
+ -DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm/bin/clang
+ -DCMAKE_CXX_FLAGS="-stdlib++-isystem ${{ steps.strings.outputs.gcc-include-dir }} -cxx-isystem ${{ steps.strings.outputs.gcc-sys-include-dir }}"
+ -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libstdc++ -L ${{ steps.strings.outputs.gcc-lib-dir }} -Wl,-rpath,/opt/homebrew/opt/gcc/lib/gcc/current"
+ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
+ -S ${{ github.workspace }} -G Ninja
+
+ - name: Build
+ if: matrix.os == 'macos-latest'
+ run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} -j 4
+
+ - name: Package
+ if: matrix.os == 'macos-latest' && matrix.build_type == 'Release'
+ working-directory: ${{ steps.strings.outputs.build-output-dir }}
+ run: |
+ cpack
+
+ - name: Package
+ if: matrix.os == 'ubuntu-24.04' && matrix.build_type == 'Release'
+ working-directory: ${{ steps.strings.outputs.build-output-dir }}
+ run: |
+ sudo cpack
+ sudo chown -R $USER:$USER "${{ github.workspace }}/Packages"
+
+ - name: Import GPG Key
+ if: matrix.build_type == 'Release'
+ uses: crazy-max/ghaction-import-gpg@v6
+ with:
+ gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }}
+ passphrase: ${{ secrets.GPG_PASS }}
+ trust_level: 5
+
+ - name: Sign Package
+ if: matrix.build_type == 'Release'
+ working-directory: ${{ github.workspace }}
+ run: |
+ for file in Packages/*; do
+ gpg --batch --status-file ~/gpg_log.txt --passphrase ${{ secrets.GPG_PASS }} --default-key dr8co@duck.com \
+ --pinentry-mode=loopback --detach-sign "$file" || (cat ~/gpg_log.txt && exit 1)
+ done
+
+ # Upload the built artifacts
+ - name: Upload Artifacts
+ if: matrix.build_type == 'Release'
+ uses: actions/upload-artifact@v4
+ with:
+ name: "${{ matrix.os }}-${{ matrix.build_type }}"
+ path: "${{ github.workspace }}/Packages"
+ overwrite: true
+ if-no-files-found: 'warn'
diff --git a/.gitignore b/.gitignore
index a91432d..b664571 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@
# .idea/modules
# *.iml
# *.ipr
+.idea/**/*copilot*
# CMake
cmake-build-*/
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 79b3c94..0b76fe5 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aedd90d..a473ce9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,11 +18,10 @@
cmake_minimum_required(VERSION 3.28)
project(privacyShield
- VERSION 2.5.0
+ VERSION 3.0.0
DESCRIPTION "A suite of tools for privacy and security"
- LANGUAGES CXX)
-
-set(CMAKE_PROJECT_HOMEPAGE_URL "https://shield.boujee.tech")
+ HOMEPAGE_URL "https://shield.iandee.tech"
+ LANGUAGES C CXX)
# C++23 support is required for this project
set(CMAKE_CXX_STANDARD 23)
@@ -39,44 +38,156 @@ if (NOT CMAKE_BUILD_TYPE)
endif ()
# Set the path to additional CMake modules
-set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
+
+# Options
+include(CMakeDependentOption)
+# GCC does not support all sanitizers
+cmake_dependent_option(ENABLE_SANITIZERS
+ "Enable sanitizers (Ignored if not using Clang compiler)" OFF
+ "${CMAKE_CXX_COMPILER_ID} STREQUAL \"Clang\"" OFF)
+
+# Valgrind support
+option(VALGRIND_BUILD "Build with Valgrind support" OFF)
# Additional checks for the Debug build
-set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -Wpedantic")
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ add_compile_options(
+ -Wall
+ -Wextra
+ -Werror
+ -Wpedantic
+ )
+endif ()
+
+# Add the executable target
+add_executable(privacyShield)
+
+# Add sources for the target
+target_sources(privacyShield PRIVATE
+ src/encryption/encryptDecrypt.cpp
+ src/encryption/encryptFiles.cpp
+ src/encryption/encryptStrings.cpp
+ src/passwordManager/passwordManager.cpp
+ src/passwordManager/passwords.cpp
+ src/main.cpp
+)
+
+# C++20 Modules
+target_sources(privacyShield PRIVATE
+ FILE_SET CXX_MODULES FILES
+ src/duplicateFinder/duplicateFinder.cppm
+ src/encryption/cryptoCipher.cppm
+ src/encryption/encryption.cppm
+ src/fileShredder/fileShredder.cppm
+ src/passwordManager/FuzzyMatcher.cppm
+ src/passwordManager/passwordManager.cppm
+ src/privacyTracks/privacyTracks.cppm
+ src/utils/utils.cppm
+ src/secureAllocator.cppm
+ src/mimallocSTL.cppm
+)
+
+# Sanitizers for debugging and testing
+# Requires llvm-symbolizer and sanitizer libraries (asan, ubsan, msan, tsan)
+if (ENABLE_SANITIZERS)
+ # Common flags for all sanitizers
+ set(sanitizer_common_flags "-fno-omit-frame-pointer -g -O1")
+
+ # Address, leak, undefined, integer, nullability sanitizers
+ set(address_sanitizer_flags "-fsanitize=address,leak,undefined,integer,nullability")
+
+ # Thread sanitizer, cannot be used with address sanitizer
+ set(thread_sanitizer_flags "-fsanitize=thread -fPIE")
+
+ # Memory sanitizer, cannot be used with address sanitizer.
+ set(memory_sanitizer_flags "-fsanitize=memory -fPIE -fno-optimize-sibling-calls")
+
+ # Add compile options
+ add_compile_options(
+ "SHELL:${sanitizer_common_flags}"
+ "SHELL:${address_sanitizer_flags}"
+ )
+
+ # Track mimalloc allocations for AddressSanitizer
+ set(MI_TRACK_ASAN ON)
+
+ # Link the enabled sanitizers.
+ target_link_libraries(privacyShield PRIVATE asan ubsan)
+endif ()
+
+# Valgrind support
+if (VALGRIND_BUILD)
+ add_compile_options(-g) # Valgrind requires debug symbols
+ # target_link_libraries(privacyShield PRIVATE valgrind)
+ set(MI_TRACK_VALGRIND ON)
+endif ()
# Find the required packages
find_package(OpenSSL REQUIRED)
find_package(Sodium REQUIRED)
-find_package(Readline REQUIRED)
find_package(Gcrypt REQUIRED)
-find_package(BLAKE3 REQUIRED) # See https://github.com/BLAKE3-team/BLAKE3
-# Add the executable target
-add_executable(privacyShield)
+find_package(BLAKE3 QUIET) # See https://github.com/BLAKE3-team/BLAKE3
-# Add sources for the target
-file(GLOB_RECURSE PRIVACY_SHIELD_SOURCES
- "${CMAKE_SOURCE_DIR}/src/*.cpp")
+include(FetchContent)
-target_sources(privacyShield PRIVATE ${PRIVACY_SHIELD_SOURCES})
+# Fetch BLAKE3 from GitHub if it is not found
+if (NOT TARGET BLAKE3::blake3)
+ message(STATUS "BLAKE3 not found. Fetching from GitHub...")
-# C++20 Modules
-file(GLOB_RECURSE PRIVACY_SHIELD_MODULES
- "${CMAKE_SOURCE_DIR}/src/*.cppm")
+ FetchContent_Declare(
+ blake3
+ GIT_REPOSITORY https://github.com/BLAKE3-team/BLAKE3.git
+ GIT_TAG 454ee5a7c73583cb3060d1464a5d3a4e65f06062
+ SOURCE_SUBDIR c
+ EXCLUDE_FROM_ALL
+ )
-target_sources(privacyShield
- PRIVATE
- FILE_SET CXX_MODULES FILES
- ${PRIVACY_SHIELD_MODULES}
+ FetchContent_MakeAvailable(blake3)
+ target_include_directories(privacyShield PRIVATE "${blake3_SOURCE_DIR}")
+
+endif ()
+
+# Mimalloc allocator
+if (NOT TARGET mimalloc-static OR NOT TARGET mimalloc)
+ message(STATUS "mimalloc not found. Fetching from GitHub...")
+
+ FetchContent_Declare(
+ mimalloc
+ GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
+ GIT_TAG v2.1.7
+ EXCLUDE_FROM_ALL
+ )
+endif ()
+
+set(MI_BUILD_TESTS OFF) # Do not build tests
+
+FetchContent_MakeAvailable(mimalloc)
+target_include_directories(privacyShield PRIVATE "${mimalloc_SOURCE_DIR}/include")
+add_library(Mimalloc::mimalloc-static ALIAS mimalloc-static)
+add_library(Mimalloc::mimalloc ALIAS mimalloc)
+
+# Fetch Isocline from GitHub
+FetchContent_Declare(
+ isocline
+ GIT_REPOSITORY https://github.com/daanx/isocline.git
+ GIT_TAG c9310ae58941559d761fe5d2dd2713d245f18da6
+ EXCLUDE_FROM_ALL
)
+FetchContent_MakeAvailable(isocline)
+target_include_directories(privacyShield PRIVATE "${isocline_SOURCE_DIR}/include")
+add_library(ISOCline::isocline ALIAS isocline)
# Link libraries
-target_link_libraries(privacyShield
- PRIVATE OpenSSL::Crypto
- PRIVATE Readline::Readline
- PRIVATE Sodium::sodium
- PRIVATE Gcrypt::Gcrypt
- PRIVATE BLAKE3::blake3)
+target_link_libraries(privacyShield PRIVATE
+ OpenSSL::Crypto
+ Sodium::sodium
+ Gcrypt::Gcrypt
+ BLAKE3::blake3
+ ISOCline::isocline
+ Mimalloc::mimalloc-static
+)
# Install the binary (optional), with 0755 permissions
include(GNUInstallDirs)
diff --git a/CMakeModules/FindReadline.cmake b/CMakeModules/FindReadline.cmake
index b789688..a07eb80 100644
--- a/CMakeModules/FindReadline.cmake
+++ b/CMakeModules/FindReadline.cmake
@@ -48,8 +48,15 @@ if (READLINE_FOUND AND NOT APPLE)
# Find the actual location of the Readline library file
find_library(READLINE_LIBRARY
- NAMES libreadline.so libreadline.dylib libreadline.a
+ NAMES libreadline.so libreadline.a
HINTS ${READLINE_LIBRARY_DIRS}
+ PATHS
+ /usr/lib
+ /usr/lib/x86_64-linux-gnu
+ /opt/local/lib
+ /opt/homebrew/lib
+ /opt/homebrew/opt/readline/lib
+ /opt/homebrew/Cellar/readline/*/lib
)
# Set the imported location dynamically
@@ -69,7 +76,14 @@ if (NOT READLINE_FOUND AND APPLE)
# Find library manually
find_library(READLINE_LIBRARY REQUIRED
NAMES libreadline.dylib libreadline.a
- PATHS /usr/local/opt/readline/lib /usr/local/lib /opt/local/lib /usr/lib
+ PATHS
+ /usr/local/opt/readline/lib
+ /usr/local/lib
+ /opt/local/lib
+ /usr/lib
+ /opt/homebrew/lib
+ /opt/homebrew/opt/readline/lib
+ /opt/homebrew/Cellar/readline/*/lib
NO_DEFAULT_PATH
)
diff --git a/CMakeModules/Packing.cmake b/CMakeModules/Packing.cmake
index 1b1cfe2..6c07e51 100644
--- a/CMakeModules/Packing.cmake
+++ b/CMakeModules/Packing.cmake
@@ -22,12 +22,13 @@
# Set the CPack variables
set(CPACK_PACKAGE_NAME "PrivacyShield")
set(CPACK_PACKAGE_VENDOR "Ian Duncan")
-set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A suite of tools for privacy and security")
-set(CPACK_PACKAGE_VERSION "2.5.0")
-set(CPACK_PACKAGE_CONTACT "dr8co@duck.com")
-
-SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/Packages")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
+set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
+set(CPACK_PACKAGE_CONTACT "Ian Duncan ")
+set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}")
+set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/Packages")
+set(CPACK_STRIP_FILES YES)
set(CPACK_SOURCE_IGNORE_FILES
/.git
/.idea
@@ -56,11 +57,14 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
-# Set the type of installer you want to generate
-set(CPACK_GENERATOR "DEB;RPM")
-
-# Strip the executable from debug symbols
-set(CPACK_STRIP_FILES YES)
+# Set the package generator
+if (APPLE)
+ set(CPACK_GENERATOR "TGZ;DragNDrop")
+elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ set(CPACK_GENERATOR "TGZ;DEB;RPM")
+else ()
+ set(CPACK_GENERATOR "TGZ")
+endif ()
# Set the package dependencies
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.35), libstdc++6 (>= 13.2.0), openssl (>= 3.0.0), libsodium23 (>= 1.0.18), libreadline8 (>= 8.0), libgcrypt20 (>= 1.10.0), libgcc-s1 (>= 13.2.0)")
@@ -76,4 +80,4 @@ set(CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE ON)
set(CPACK_PACKAGE_CHECKSUM "SHA256")
-include(CPack)
\ No newline at end of file
+include(CPack)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f49c21a..c238610 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,6 +8,8 @@ to help and details about how this project handles them. Please make sure to rea
your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The
community looks forward to your contributions. 🎉
+For security issues, please follow the instructions in the [Security](./SECURITY.md) section.
+
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support
> the project and show your appreciation, which we would also be very happy about:
>
@@ -90,7 +92,7 @@ following steps in advance to help us fix any potential bug as fast as possible.
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue
-> tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
+> tracker, or elsewhere in public. Check the [Security](./SECURITY.md) section for more information.
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
diff --git a/README.md b/README.md
index 11ba8f9..1e15e06 100644
--- a/README.md
+++ b/README.md
@@ -128,6 +128,24 @@ and are not saved in the shell command history.
Any operation with any tool can be canceled at any time by pressing `Ctrl+C`,
and confirming the cancellation.
+**Note:**\
+The program uses ANSI escape codes for colors and formatting. If you experience issues with the colors,
+you can disable them by setting the `NO_COLOR` environment variable to `true` (or `1`),
+or by using the `--no-color` or `-nc` option.
+
+```bash
+export NO_COLOR=true && privacyShield
+```
+
+or
+
+```bash
+privacyShield --no-color
+```
+
+The program will automatically detect the `NO_COLOR` environment variable, and the terminal capabilities
+to determine if colors should be used.
+
### Password Manager
The password manager requires a primary password to encrypt/decrypt your passwords.
@@ -169,7 +187,7 @@ The process might be slow, and multithreading has been leveraged to speed up the
The [Serpent cipher](https://en.wikipedia.org/wiki/Serpent_(cipher))
is used for the first step because it is a
conservative and secure cipher with more rounds than [AES cipher](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
-(32 rounds vs 14 rounds, hence a larger security margin) that is resistant to cryptanalysis.
+(32 rounds vs. 14 rounds, hence a larger security margin) that is resistant to cryptanalysis.
The [counter mode (CTR)](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR))
is used for it because it is a fast and secure mode that is resistant to padding oracle attacks.
A non-deterministic random [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce)
@@ -332,14 +350,15 @@ operating system, such as [Linux](https://en.wikipedia.org/wiki/Linux),
* A C++ compiler with [C++23](https://en.cppreference.com/w/cpp/23) support,
and [C++20 Modules](https://en.cppreference.com/w/cpp/language/modules) support.
For this project, [GCC 14](https://gcc.gnu.org/gcc-14/) (or newer),
-or [LLVM Clang 17](https://clang.llvm.org/) (or newer) is required.
+or [LLVM Clang 18](https://clang.llvm.org/) (or newer) is required.
* [CMake](https://cmake.org/) 3.28+
* [Ninja](https://ninja-build.org/) 1.11+, or any other build system compatible with CMake and **C++20 Modules**.
* [OpenSSL](https://www.openssl.org/) 3+
* [Sodium](https://libsodium.org/) 1.0.18+
* [GCrypt](https://gnupg.org/software/libgcrypt/index.html) 1.10+
-* [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) 1.4+ (see the note below)
-* [GNU Readline](https://tiswww.case.edu/php/chet/readline/rltop.html) 8+
+* [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) 1.4+ (Fetched automatically by CMake, if not already installed)
+* [Isocline](https://github.com/daanx/isocline) (Fetched automatically by CMake)
+* [Mimalloc](https://github.com/microsoft/mimalloc) 2.17+ (Fetched automatically by CMake)
**Note:**\
This project utilizes the [C++20 Modules](https://en.cppreference.com/w/cpp/language/modules) feature,
@@ -411,28 +430,58 @@ You can then run the program from the build directory:
You can download a package for your platform from the
[releases page](https://github.com/dr8co/PrivacyShield/releases).
+The packages expect the dependencies to be installed on the system,
+except the ones that are fetched automatically by CMake (they are statically linked to the executable).
+
The package will contain the built executable, and you can install it using the package manager of your platform.
For the macOS package, you can simply drag the .dmg file to your Applications folder.
+The current macOS package was built on macOS 14.5 arm64 (M1 chip),
+and might not work on older versions of macOS.
+
For the Linux package, you can install the .deb or .rpm file using the package manager of your distribution.\
Internet connection might be required to install the dependencies.
For instance, on Ubuntu, you can install the .deb file using the following command:
```bash
-sudo dpkg -i privacyshield_2.5.0_amd64.deb # Replace with the actual file path
+sudo dpkg -i privacyshield_3.0.0_amd64.deb # Replace with the actual file path
# You can also use apt to install it:
-sudo apt install ./privacyshield_2.5.0_amd64.deb # Replace with the actual file path
+sudo apt install ./privacyshield_3.0.0_amd64.deb # Replace with the actual file path
```
On RPM-based distributions like Fedora, you can install the .rpm file using the following command:
```bash
-sudo rpm -i privacyshield-2.5.0-1.x86_64.rpm # Replace with the actual file path
+sudo rpm -i privacyshield-3.0.0-1.x86_64.rpm # Replace with the actual file path
```
The packages can be verified using the [GnuPG](https://gnupg.org/) signature files provided.
+To verify the packages, first import the [public GPG key](./security/privacyShield_pub_key.asc) provided:
+
+```bash
+gpg --import public_gpg_key.asc
+```
+
+The public key is provided in the [releases page](https://github.com/dr8co/PrivacyShield/releases) as well.
+Then verify the package using the signature file (which can also be found on the
+[releases page](https://github.com/dr8co/PrivacyShield/releases)):
+
+```bash
+gpg --verify signatures/privacyshield_3.0.0_amd64.deb.sig privacyshield_3.0.0_amd64.deb
+```
+
+The verification succeeds if the output says
+`gpg: Good signature from "Ian Duncan (Signing key for personal projects) ..."`.
+
+SHA256 checksums are also provided for the packages, and you can verify the integrity of the packages using them.
+
+```bash
+shasum -a 256 -c privacyshield_3.0.0_amd64.deb.sha256
+# Or, if you have the sha256sum command available:
+sha256sum -c privacyshield_3.0.0_amd64.deb.sha256
+```
#### Manual Installation
@@ -473,6 +522,8 @@ There is no need to remember commands or arguments, as the CLI will guide you th
To use the CLI, simply run the program by typing `privacyShield` in your terminal.
+**Tab completion is supported** for most input fields.
+
## Contributing
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct,
@@ -514,11 +565,15 @@ However, the feeling of empowered privacy protection is a strong possibility!
![""](./media/blank.svg)
-[![Readline](./media/Heckert_GNU_white.svg)](https://tiswww.case.edu/php/chet/readline/rltop.html)
+[![CMake](./media/Cmake.svg)](https://cmake.org/)
![""](./media/blank.svg)
-[![CMake](./media/Cmake.svg)](https://cmake.org/)
+[![Mimalloc](./media/mimalloc-logo.png)](https://github.com/microsoft/mimalloc)
+
+![""](./media/blank.svg)
+
+[![Isocline](./media/isocline.png)](https://github.com/daanx/isocline)
## License
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..c4acb41
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,29 @@
+# Security
+
+We take the security of Privacy Shield seriously.
+If you believe you have found a security vulnerability in the source code,
+please report it to us as described below.
+
+## Reporting Security Issues
+
+**Please do not report security vulnerabilities through public GitHub issues.**
+
+Instead, please send an encrypted email to [dr8co@duck.com](mailto:dr8co@duck.com).
+Encrypt your message with our PGP key; it can be found [here](./security/privacyShield_pub_key.asc).
+
+Please include the requested information listed below (as much as you can provide)
+to help us better understand the nature and scope of the possible issue:
+
+* Type of issue (e.g., buffer overflow, SQL injection, cross-site scripting, etc.)
+* Full paths of source file(s) related to the manifestation of the issue
+* The location of the affected source code (tag/branch/commit or direct URL)
+* Any special configuration required to reproduce the issue
+* Step-by-step instructions to reproduce the issue
+* Proof-of-concept or exploit code (if possible)
+* Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+## Preferred Languages
+
+We prefer all communications to be in English.
diff --git a/media/isocline.png b/media/isocline.png
new file mode 100644
index 0000000..f602444
Binary files /dev/null and b/media/isocline.png differ
diff --git a/media/mimalloc-logo.png b/media/mimalloc-logo.png
new file mode 100644
index 0000000..32b0953
Binary files /dev/null and b/media/mimalloc-logo.png differ
diff --git a/qodana.yaml b/qodana.yaml
new file mode 100644
index 0000000..52b97e8
--- /dev/null
+++ b/qodana.yaml
@@ -0,0 +1,29 @@
+#-------------------------------------------------------------------------------#
+# Qodana analysis is configured by qodana.yaml file #
+# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
+#-------------------------------------------------------------------------------#
+version: "1.0"
+
+#Specify inspection profile for code analysis
+profile:
+ name: qodana.recommended
+
+#Enable inspections
+#include:
+# - name:
+
+#Disable inspections
+#exclude:
+# - name:
+# paths:
+# -
+
+#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
+bootstrap: sudo ./scripts/prepare-qodana.sh
+
+#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
+#plugins:
+# - id: #(plugin id can be found at https://plugins.jetbrains.com)
+
+#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
+linter: jetbrains/qodana-clang:2024.1-eap
diff --git a/scripts/build-functions.sh b/scripts/build-functions.sh
new file mode 100755
index 0000000..172956a
--- /dev/null
+++ b/scripts/build-functions.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+set -e
+
+# This script is used to build the project on the Ubuntu Noble (24.04) distribution.
+# It is not intended to be used on other distributions, and must be run from the project root.
+
+PARALLELISM_LEVEL=4
+
+function check_root() {
+ # Root access is required to install the dependencies.
+ if [ "$EUID" -ne 0 ]; then
+ echo "Please run as root."
+ abort
+ fi
+}
+
+function install_dependencies() {
+# wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
+# add-apt-repository -y "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main"
+# add-apt-repository -y ppa:ubuntu-toolchain-r/ppa
+ apt update
+ export NEEDRESTART_SUSPEND=1
+ apt install -y wget unzip gcc-14 g++-14 clang-18 lldb-18 lld-18 libc++-18-dev libc++abi-18-dev libllvmlibc-18-dev clang-tools-18 libgcrypt20-dev openssl libsodium23 libsodium-dev
+
+ # Install CMake 3.29.3
+ if dpkg -s "cmake" >/dev/null 2>&1; then
+ apt remove -y --purge --auto-remove cmake
+ fi
+
+ wget -qO- "https://github.com/Kitware/CMake/releases/download/v3.29.3/cmake-3.29.3-linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local
+
+ # Install Ninja 1.12
+ if dpkg -s "ninja-build" >/dev/null 2>&1; then
+ apt remove -y --purge --auto-remove ninja-build
+ fi
+
+ wget -q "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip"
+ unzip ninja-linux.zip -d /usr/local/bin
+}
+
+function build_blake3() {
+ ./install-blake3.sh clang-18
+}
+
+function configure_cmake() {
+ cmake -B build -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_BUILD_TYPE=Debug -G Ninja
+}
+
+function build_project() {
+ cmake --build build --config Debug -j "$PARALLELISM_LEVEL"
+}
+
+function abort() {
+ echo "An unexpected error occurred. Program aborted."
+ exit 1
+}
+
+function build_install_gcc_14() {
+ apt update
+ apt install -y software-properties-common build-essential wget libgmp-dev libmpfr-dev libmpc-dev
+ wget -q https://ftp.gnu.org/gnu/gcc/gcc-14.1.0/gcc-14.1.0.tar.xz
+ tar -xf gcc-14.1.0.tar.xz
+ cd gcc-14.1.0 || abort
+ ./contrib/download_prerequisites
+ mkdir build
+ cd build || abort
+ ../configure --enable-languages=c,c++ --disable-multilib
+ make -j "$PARALLELISM_LEVEL"
+ make install
+ update-alternatives --install /usr/bin/gcc gcc /usr/local/bin/gcc 60 --slave /usr/bin/g++ g++ /usr/local/bin/g++
+ apt purge -y gcc cpp g++
+}
+
+trap "echo 'An unexpected error occurred. Program aborted.'" ERR
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000..c0fdbe7
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+# Run from this directory
+cd "${0%/*}" || abort
+
+# Include the build functions
+. ./build-functions.sh
+
+# Root access is required to install the dependencies.
+check_root
+
+# Install dependencies
+install_dependencies
+
+echo "Ninja: $(ninja --version), CMake: $(cmake --version)"
+
+# Build and install BLAKE3
+build_blake3
+
+# Configure CMake
+cd .. || abort
+configure_cmake
+
+# Build the project
+build_project
\ No newline at end of file
diff --git a/scripts/buildscript.sh b/scripts/buildscript.sh
deleted file mode 100755
index d1e3909..0000000
--- a/scripts/buildscript.sh
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/bin/env bash
-
-set -e
-
-# This script is used to build the project on the Ubuntu Jammy (22.04) distribution.
-# It is not intended to be used on other distributions, and must be run from the project root.
-
-PARALLELISM_LEVEL=4
-
-function check_root() {
- # Root access is required to install the dependencies.
- if [ "$EUID" -ne 0 ]; then
- echo "Please run as root."
- abort
- fi
-}
-
-function check_dependencies() {
- for cmd in wget add-apt-repository; do
- if ! command -v $cmd &>/dev/null; then
- echo "$cmd could not be found"
- exit
- fi
- done
-}
-
-function install_dependencies() {
- wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
- add-apt-repository -y "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main"
- add-apt-repository -y ppa:ubuntu-toolchain-r/ppa
- apt update
- apt install -y unzip gcc-13 g++-13 clang-18 lldb-18 lld-18 libc++-18-dev libc++abi-18-dev clang-tools-18 libgcrypt20 openssl libreadline8 libsodium23 libsodium-dev
-
- # Install CMake 3.28.3
- if dpkg -s "cmake" >/dev/null 2>&1; then
- apt remove -y --purge --auto-remove cmake
- fi
-
- wget -qO- "https://github.com/Kitware/CMake/releases/download/v3.28.3/cmake-3.28.3-linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local
-
- # Install Ninja 1.11
- if dpkg -s "ninja-build" >/dev/null 2>&1; then
- apt remove -y --purge --auto-remove ninja-build
- fi
-
- wget -q "https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-linux.zip"
- unzip ninja-linux.zip -d /usr/local/bin
-}
-
-function build_blake3() {
- ./install-blake3.sh clang-18
-}
-
-function configure_cmake() {
- cmake -B build -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_BUILD_TYPE=Debug -G Ninja
-}
-
-function build_project() {
- cmake --build build --config Debug -j "$PARALLELISM_LEVEL"
-}
-
-main() {
- trap "echo 'An unexpected error occurred. Program aborted.'" ERR
- check_root
- check_dependencies
- cd "${0%/*}" || abort
- install_dependencies
- echo "Ninja: $(ninja --version), CMake: $(cmake --version)"
- build_blake3
- cd .. || abort
- configure_cmake
- build_project
-}
-
-main
diff --git a/scripts/install-blake3.sh b/scripts/install-blake3.sh
index 9e81789..cd740d9 100755
--- a/scripts/install-blake3.sh
+++ b/scripts/install-blake3.sh
@@ -19,7 +19,7 @@ detect_os() {
# Function to check root access
check_root() {
- [[ "$CURRENT_OS" != "macos" && "$EUID" -ne 0 ]] && error_exit "This script must be run as root."
+ [[ "$CURRENT_OS" == "linux" && "$EUID" -ne 0 ]] && error_exit "This script must be run as root."
}
get_number_of_processors() {
@@ -36,35 +36,17 @@ install_blake3() {
cd ~ || error_exit "Failed to change to home directory."
# Download BLAKE3 and extract to current directory
- wget -qO- https://github.com/BLAKE3-team/BLAKE3/archive/refs/tags/1.5.0.tar.gz | tar -xz -C .
+ wget -qO- https://github.com/BLAKE3-team/BLAKE3/archive/refs/tags/1.5.1.tar.gz | tar -xz -C .
- cd BLAKE3-1.5.0/c || error_exit "Failed to navigate to BLAKE3/c directory."
+ cd BLAKE3-1.5.1/c || error_exit "Failed to navigate to BLAKE3/c directory."
- cmake -B build -DCMAKE_C_COMPILER="$C_COMPILER" -G Ninja || error_exit "Failed to run cmake."
+ cmake -B build -DCMAKE_C_COMPILER="$C_COMPILER" -DCMAKE_BUILD_TYPE=Release -G Ninja || error_exit "Failed to configure CMake."
get_number_of_processors
cmake --build build --config Release --target install -j "$NUMBER_OF_PROCESSORS" || error_exit "Failed to build and install."
}
-# Function to clone repository
-clone_repo() {
- git clone https://github.com/BLAKE3-team/BLAKE3.git || error_exit "Failed to clone BLAKE3 repository."
-}
-
-# Function to build and install BLAKE3
-build_install() {
- cd BLAKE3/c || error_exit "Failed to navigate to BLAKE3/c directory."
- cmake -B build -DCMAKE_C_COMPILER="$C_COMPILER" -G Ninja || error_exit "Failed to run cmake."
- get_number_of_processors
-
- cmake --build build --config Release --target install -j "$NUMBER_OF_PROCESSORS" || error_exit "Failed to build and install."
-
- # Cleanup
- cd ../..
- rm -rf BLAKE3
-}
-
# Set C compiler
C_COMPILER=${1:-gcc}
@@ -75,8 +57,4 @@ cd "${0%/*}" || error_exit "Failed to change directory to script location."
echo "Compiling BLAKE3 with $C_COMPILER compiler.."
-# Call functions
-# clone_repo
-# build_install
-
install_blake3
diff --git a/scripts/prepare-qodana.sh b/scripts/prepare-qodana.sh
new file mode 100755
index 0000000..96906cf
--- /dev/null
+++ b/scripts/prepare-qodana.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+# Run from this directory
+cd "$(dirname "$0")" || (echo "Running from $(pwd)" && exit 1)
+
+# Include the build functions
+. ./build-functions.sh
+
+# Root access is required to install the dependencies.
+check_root
+
+# Build and install GCC 14
+build_install_gcc_14
+
+# Install dependencies
+apt remove -y --purge --auto-remove llvm-16-dev clang-16 clang-tidy-16 clang-format-16 lld-16 libc++-16-dev libc++abi-16-dev
+apt update && apt install -y software-properties-common wget unzip build-essential openssl libsodium23 libsodium-dev libgcrypt20-dev
+wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
+add-apt-repository -y "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main"
+apt update
+export NEEDRESTART_SUSPEND=1
+apt install -y llvm-18-dev clang-18 lldb-18 lld-18 libc++-18-dev libc++abi-18-dev libllvmlibc-18-dev clang-tools-18 clang-tidy-18 clang-format-18
+
+for f in /usr/lib/llvm-18/bin/*; do
+ ln -sf "$f" /usr/bin;
+done
+
+# Install CMake 3.29.3
+if dpkg -s "cmake" >/dev/null 2>&1; then
+ apt remove -y --purge --auto-remove cmake
+fi
+
+wget -qO- "https://github.com/Kitware/CMake/releases/download/v3.29.3/cmake-3.29.3-linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local
+
+# Install Ninja 1.12
+if dpkg -s "ninja-build" >/dev/null 2>&1; then
+ apt remove -y --purge --auto-remove ninja-build
+fi
+
+wget -q "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip"
+unzip ninja-linux.zip -d /usr/local/bin
+
+echo "Ninja: $(ninja --version), CMake: $(cmake --version)"
+
+# Build and install BLAKE3
+build_blake3
+
+# Configure CMake
+cd .. || abort
+/usr/local/bin/cmake -S . -B build -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja
+
diff --git a/scripts/search.py b/scripts/search.py
new file mode 100755
index 0000000..433c486
--- /dev/null
+++ b/scripts/search.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+import sys
+import glob
+
+
+def search_filesystem(pattern: str) -> None:
+ """
+ Search the filesystem for files or directories that match the pattern
+ :param pattern: The pattern to search for
+ :return: None
+ """
+ # Find files or directories that match the pattern
+ matches = glob.glob(pattern, recursive=True)
+
+ # Check if there are any matches
+ if not matches:
+ print(f"No matches found for pattern: {pattern}")
+ else:
+ # Sort matches in reverse order
+ matches.sort(reverse=True)
+
+ # Select the first match (after sorting in reverse order)
+ print(matches[0])
+
+
+if __name__ == "__main__":
+ # Check if exactly one argument is provided
+ if len(sys.argv) != 2:
+ print("Usage: python3 search.py ")
+ sys.exit(1)
+
+ search_filesystem(sys.argv[1])
diff --git a/scripts/search.sh b/scripts/search.sh
new file mode 100755
index 0000000..b2848cc
--- /dev/null
+++ b/scripts/search.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# This script is used to search for files or directories that match a pattern.
+# It searches recursively and returns the first match found.
+
+# Check if exactly one argument is provided
+if [ "$#" -ne 1 ]; then
+ echo "Usage: $0 "
+ exit 1
+fi
+
+pattern=$1
+
+# Find files or directories that match the pattern and store them in an array
+IFS=$'\n' read -d '' -r -a matches < <(find $(dirname "$pattern") -name "$(basename "$pattern")" | sort -r 2>/dev/null)
+
+# Check if any matches were found
+if [ ${#matches[@]} -eq 0 ]; then
+ echo "No matches found."
+ exit 1
+else
+ # Print the first match
+ echo "${matches[0]}"
+fi
diff --git a/security/privacyShield_pub_key.asc b/security/privacyShield_pub_key.asc
new file mode 100644
index 0000000..c9eab7c
--- /dev/null
+++ b/security/privacyShield_pub_key.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGXUykUBEAD0nkmT6SgkJnlXTx2aEJ23bl87TZR3gW3v+uNQn5ISaUw1WJtE
+8XYYkibTBz88W8EeozXGfRn3UDs4g1UOvMcYlfibOxHUZm0MPrueJhFptaBs/x6h
+Y0zaODlX3ZpM3ISGu1o4mwLTOhHhP+15XkbT+b7Cbf1HFKplPyp8TS/s0cUfSq5f
+Thg8ngIbnkUp4S5cqKJMV3yyK0NyqYE8IJMNOMTot7szFA06phUp5XbOl+Jmhbr2
+I3YPWE/guFw0R4IX3klXOITLs5iJFkrLL5VbGseF0YQUkGVFTiTovuc8hcvmbS8x
+mU+lH0IkRnye1F9fogymhuiX8vL5Gf9C6gNr0mpWxaHtFAkVqmchhmWWRn3RGkHM
+6kdYrEj5yDrhwI8ZPVjh34XDQMg7pXIdiHZbTZMm/RA944VhdfjYs3VS/FZ3Oz9k
+Rf5M830x9ifG28dCMiVFhYn5jm4hOmh6KrfPxK7Muf2XFJP6DiBNAT07LDq/DeG8
+qk/VALH6vKElFADVsrn2QVq8LmapmYwMl9l2Bmb6isWsuu+RUGft3MvwygUfve5b
+9NQYITGGhmD/jH5IXiW/iU+6tIQx9UFaQTm9Plg4LChnVQpMqxp0YhX47QNZFyWB
+qJ+N47DTGcjDENQcXTidu4kgV0jf26y2MQ8N0GvufJXvA3NuHpcfJ1e7uwARAQAB
+tD9JYW4gRHVuY2FuIChTaWduaW5nIGtleSBmb3IgcGVyc29uYWwgcHJvamVjdHMp
+IDxkcjhjb0BkdWNrLmNvbT6JAk4EEwEKADgWIQRkiIHdBB6Z7skF/YG9mRSEoI6b
+xQUCZdTKRQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC9mRSEoI6bxXuU
+D/0eYT2v5b8E6Ws8rNh0hzglzC9BiZDG2NWoaz1O26ixXPdzymYFB1xvNFS4UONP
+1YDGGfFTBjhsseUwv11N6ZahwzGIkBnMUwH/Vbx2Rt4NTXkukfqeHaTM/jTPZUMn
+OzBcECaE9Buph18c05tlXGb60+40R4zB/uTPys5qzuVCs0Us4X5CIt7GHC/hwJiW
+hxEZyA/S0ardVyh1dQEpKV6PeWczwvfkqPBymsx5FLABC5N2epPq7OO6Uv/tMOfC
+UrM/7z1CEY/vmHcD+fpfsXK4Y4ixaUrs8BJQtmaYrVSH+wHlmlNumudI6VtPU7i1
+egTpJ4ViidqzzZLL075hGCEczsp3EqtBKpik+ZpHXHSpMYeuLvqz20PAP3jAYNHV
+k7vcv39mDsivf//GbfObQ43kKpWvfJ6lwb4EaopL8RLCFxGDrLM2bf1cb/WRULBw
+YAgiF7gfYpe4rPUlp1/xFkdH5lBZay1sFN8rVu0wPwung7czQHypNo0jYrvHiufD
+BDkr0sQznV6dKBY8uhMhkhiVhq2hast6I5rNkJSRx6ZpIROYVF5xnlAGaJ/C9SKK
+zInHVJRWW19RNDfVj9Ld9O6fgsnGTOiJDSmH1J6jbdNoqafNc1EyXnYT0Dl5rQ0f
+HDZMYssMgdIGIjh4gtNAuW/T8vE0rXl33+vwSvV7aJf1NLkCDQRl1MpFARAAv2hF
+AbLa0Q7YUQXVma6Dr72ML8bdF1q1xXC1vD+L7Aka+TFjzHmgeQxmDsP9LvAAkRAg
+K7XKLYJqT7nvHfr8g8flUiSImBRXHV7OBlVXiVmVJqKSMoFC6TOvGyrvlESDlBAB
+SZayNTihRZz/fIdiahZ+Iblwp3/9ZwufAqPsYlgfL51SLUZZnohw2jzDbNTToVsk
+W6qQKvjbDYOJk+/i4jNnmnoN3T39zRUqKiKudNHtspha424eRys6sBgTni4mSZ2b
+8FpWmC+ou7Ichkb4i0RU1sS+LgnwzP9yic9qEQDzhLcBqcFiSWJV1gwyzE+gbc3i
+KGAfXV6cIHpjA5ZrSoZyBIPsrDWHclWYJx7x4r39ddSORgOMCCtxiC57krjWh7oU
+a/KlWZR3MjeLI+ipVT0ltZeT9FArZzeSLXEqW2ENGPk1fWv0SgwPStyQla5244aZ
+BDaB3WUr4dVAdWWKEuxoPUQPwEVTgJKJ0/OtC2jPMbE9dFSBz1Iwh5+EuB7PLJTc
+PoTb4vMLkFmJntWJIytPDcUgFIpf0Cld8nXICFHUp085zJo182KBkQQWr05SG8p3
+5nNuzRE3V00CURrH/SSEa1D3wNlnOYS6klGYzmGLBlb8EoZusb+Lsrd+tLIzqTdV
+ciN+qUeEmPLCJEtRexaWVmbUfe7SGJH6VSB7JPEAEQEAAYkCNgQYAQoAIBYhBGSI
+gd0EHpnuyQX9gb2ZFISgjpvFBQJl1MpFAhsMAAoJEL2ZFISgjpvFEA8P/2DBEln/
+BGBhMyhGMTuLqSb7FqG3yJLD7YTlM2MTGOHinCroOxI3UlS6PQew9o4DkJHaLQw+
+ho4qcIol7wISnRHE5fYABcmbdPR3wSIT/KQRdSTVSOkQDFPFGOPRfKeCmKP4npB/
+x8LHcoJwG3ab/2axHNkJLWrJwFY6iYxpYIW364v+uSJJq22z3SJ8bmL0JJjr6JPK
+SU7Io4deRbnw7q0TNJqcgs4dzmIWaubaA1VdmYbZnq57F6WvTDVwz8vWT45sw4Sy
+RML3B2UPxnvHS8sJygnM2Vo+yijU1sYm3yYYAfB81AlbrjGyuGhISR7jnFgAtnuD
+HjpGXnDrYKcmjp+EpC88oJiIuuUD6E6AyIXGnXTs00fFCqhcXXwW6wRF7vzo47To
+eApHCc0y9A+3ZEfmkpdIsxXdcQNf65Eh0XOB4tEYtmm2hPgjJ3T+1iZRYfsr7dkV
+bE//dzBIom0VEr9I8vYlKsqlC0G98AplYvitS++4akCpYFuFXlcKlQErhSGL9qgo
+34xrQv//salNPGzDWbOpCDGUQ8DV01FFBkneZIlrz85rR63SPi7mp7SHIW6mGxuA
+ZSc8aL5X1m61d4uLTqYswRDJ1nE4ZT05Bk9bpDOzqbELBitU54oPCCNqWEAs+Usq
+dhCOLSVqS8XZKOgkOO54MKbmb3YZv5m46DfY
+=M91j
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/duplicateFinder/duplicateFinder.cppm b/src/duplicateFinder/duplicateFinder.cppm
index 11d733c..8b882b6 100644
--- a/src/duplicateFinder/duplicateFinder.cppm
+++ b/src/duplicateFinder/duplicateFinder.cppm
@@ -16,57 +16,64 @@
module;
-#include
+#include
#include
#include
#include
#include
#include
#include
-#include
#include
+#include
#include
#include
export module duplicateFinder;
import utils;
+import mimallocSTL;
namespace fs = std::filesystem;
-constexpr std::size_t CHUNK_SIZE = 4096; // Read and process files in chunks of 4 kB
+constexpr std::size_t CHUNK_SIZE = 4096; ///< Read and process files in chunks of 4 kB
/// \brief Represents a file by its path (canonical) and hash.
struct FileInfo {
- std::string path; // the path to the file.
- std::string hash; // the file's BLAKE3 hash
+ miSTL::string path{}; ///< the path to the file.
+ miSTL::string hash{}; ///< the file's BLAKE3 hash
};
/// \brief Calculates the 256-bit BLAKE3 hash of a file.
/// \param filePath path to the file.
/// \return Base64-encoded hash of the file.
/// \throws std::runtime_error if the file cannot be opened.
-std::string calculateBlake3(const std::string &filePath) {
+miSTL::string calculateBlake3(const miSTL::string &filePath) {
// Open the file
- std::ifstream file(filePath, std::ios::binary);
- if (!file)
- throw std::runtime_error(std::format("Failed to open '{}' for hashing.", filePath));
+ std::ifstream file(filePath.c_str(), std::ios::binary);
+ if (!file) {
+ if (std::error_code ec; fs::exists(filePath, ec))
+ throw std::runtime_error(std::format("Failed to open '{}' for hashing.", filePath));
+
+ printColoredError('b', "{} ", filePath);
+ printColoredErrorln('r', "existed during scan but was not found during hashing.");
+ return "";
+ }
// Initialize the BLAKE3 hasher
blake3_hasher hasher;
blake3_hasher_init(&hasher);
// Update the hasher with the file contents in chunks of 4 kB
- std::vector buffer(CHUNK_SIZE);
+ std::array buffer{};
while (file.read(buffer.data(), CHUNK_SIZE))
blake3_hasher_update(&hasher, buffer.data(), CHUNK_SIZE);
// Update the hasher with the last chunk of data
- std::size_t remainingBytes = file.gcount();
+ const std::size_t remainingBytes = file.gcount();
blake3_hasher_update(&hasher, buffer.data(), remainingBytes);
// Finalize the hash calculation
- std::vector digest(BLAKE3_OUT_LEN);
+ miSTL::vector digest(BLAKE3_OUT_LEN);
blake3_hasher_finalize(&hasher, digest.data(), BLAKE3_OUT_LEN);
return base64Encode(digest);
@@ -75,104 +82,99 @@ std::string calculateBlake3(const std::string &filePath) {
/// \brief handles file i/o errors during low-level file operations.
/// \param filename path to the file on which an error occurred.
inline void handleAccessError(const std::string_view filename) {
- std::string errMsg;
- errMsg.reserve(50);
-
- switch (errno) {
- case EACCES: // Permission denied
- errMsg = "You do not have permission to access this item";
- break;
- case EEXIST: // File exists
- errMsg = "already exists";
- break;
- case EISDIR: // Is a directory
- errMsg = "is a directory";
- break;
- case ELOOP: // Too many symbolic links encountered
- errMsg = "is a loop";
- break;
- case ENAMETOOLONG: // The filename is too long
- errMsg = "the path is too long";
- break;
- case ENOENT: // No such file or directory
- errMsg = "path does not exist";
- break;
- case EROFS: // Read-only file system
- errMsg = "the file system is read-only";
- break;
- default: // Success (most likely)
- return;
- }
+ if (errno) {
+ printColoredError('r', "Skipping ");
+ printColoredError('c', "{}", filename);
+ printColoredErrorln('r', ": {}", std::strerror(errno));
- printColor(std::format("Skipping '{}': {}.", filename, errMsg), 'r', true, std::cerr);
+ errno = 0;
+ }
}
/// \brief recursively traverses a directory and collects file information.
/// \param directoryPath the directory to process.
-/// \param files a vector to store the information from the files found in the directory.
-void traverseDirectory(const std::string_view directoryPath, std::vector &files) {
+/// \param candidateDuplicates a vector to store the information from the files found in the directory.
+std::size_t traverseDirectory(const fs::path &directoryPath, miSTL::vector &candidateDuplicates) {
std::error_code ec;
+ // Number of files processed
+ std::size_t filesProcessed{0};
+
+ // Map to store file sizes and their corresponding paths
+ miSTL::unordered_map > sizeToFileMap;
+
for (const auto &entry: fs::recursive_directory_iterator(directoryPath,
fs::directory_options::skip_permission_denied |
fs::directory_options::follow_directory_symlink)) {
+ ++filesProcessed;
if (entry.exists(ec)) {
// In case of broken symlinks
if (ec) {
- printColor(std::format("Skipping '{}': {}.",
- entry.path().string(), ec.message()), 'r', true, std::cerr);
+ printColoredError('r', "Skipping ");
+ printColoredError('c', "{}", entry.path().string());
+ printColoredErrorln('r', ": {}", ec.message());
ec.clear();
continue;
}
// Make sure we can read the entry
- if (isReadable(entry.path())) [[likely]] {
+ if (isReadable(entry.path().string().c_str())) [[likely]] {
// process only regular files
if (entry.is_regular_file()) [[likely]] {
- FileInfo fileInfo;
-
- // Update the file details
- fileInfo.path = entry.path().string();
- fileInfo.hash = ""; // the hash will be calculated later
- files.emplace_back(fileInfo);
- } else if (!entry.is_directory()) // Neither regular nor a directory
- printColor(std::format("Skipping '{}': Not a regular file.",
- entry.path().string()), 'r', true, std::cerr);
+ sizeToFileMap[fs::file_size(entry.path())].push_back(entry.path());
+ } else if (!entry.is_directory()) {
+ // Neither regular nor a directory
+ printColoredError('r', "Skipping ");
+ printColoredError('c', "{}", entry.path().string());
+ printColoredErrorln('r', ": Not a regular file.", ec.message());
+ }
} else handleAccessError(entry.path().string());
}
}
+ candidateDuplicates.reserve(filesProcessed);
+ // Report files with the same sizes
+ for (auto &files: sizeToFileMap | std::views::values) {
+ if (files.size() > 1) {
+ for (const auto &file: files) {
+ candidateDuplicates.emplace_back(FileInfo{file.string().c_str(), ""});
+ }
+ }
+ }
+
+ return filesProcessed;
}
/// \brief calculates hashes for a range of files.
/// \param files the files to process.
/// \param start the index where processing starts.
/// \param end the index where processing ends.
-void calculateHashes(std::vector &files, const std::size_t start, const std::size_t end) {
+void calculateHashes(miSTL::vector &files, const std::size_t start, const std::size_t end) {
// Check if the range is valid
if (start > end || end > files.size())
throw std::range_error("Invalid range.");
// Calculate hashes for the files in the range
for (std::size_t i = start; i < end; ++i)
- files[i].hash = calculateBlake3(files[i].path);
+ files[i].hash = calculateBlake3(files[i].path).c_str();
}
/// \brief finds duplicate files (by content) in a directory.
/// \param directoryPath the directory to process.
/// \return True if duplicates are found, else False.
-std::size_t findDuplicates(const std::string_view directoryPath) {
+std::size_t findDuplicates(const fs::path &directoryPath) {
// Collect file information
- std::vector files;
- traverseDirectory(directoryPath, files);
- const std::size_t filesProcessed = files.size();
- if (filesProcessed < 1) return 0;
+ miSTL::vector files;
+ const std::size_t filesProcessed = traverseDirectory(directoryPath, files);
+ const std::size_t numFiles = files.size();
+
+ if (filesProcessed < 2 || numFiles < 2) return 0;
// Number of threads to use
const unsigned int n{std::jthread::hardware_concurrency()};
const unsigned int numThreads{n ? n : 8}; // Use 8 threads if hardware_concurrency() fails
// Divide the files among the threads
- std::vector threads;
- const std::size_t filesPerThread = filesProcessed / numThreads;
+ miSTL::vector threads;
+ const std::size_t filesPerThread = numFiles / numThreads;
std::size_t start = 0;
// Calculate the files' hashes in parallel
@@ -187,7 +189,8 @@ std::size_t findDuplicates(const std::string_view directoryPath) {
for (auto &thread: threads) thread.join();
// A hash map to map the files to their corresponding hashes
- std::unordered_map > hashMap;
+ miSTL::unordered_map > hashMap;
+ hashMap.reserve(files.size());
// Iterate over files and identify duplicates
for (const auto &[filePath, hash]: files)
@@ -196,24 +199,24 @@ std::size_t findDuplicates(const std::string_view directoryPath) {
std::size_t duplicatesSet{0}, numDuplicates{0};
// Display duplicate files
- std::cout << "Duplicates found:" << std::endl;
+ std::println("Duplicates found:");
for (const auto &duplicates: hashMap | std::views::values) {
if (duplicates.size() > 1) {
++duplicatesSet;
// Show the duplicates in their sets
- printColor("Duplicate files set ", 'c');
- printColor(duplicatesSet, 'g');
- printColor(":", 'c', true);
+ printColoredOutput('c', "Duplicate files set ");
+ printColoredOutput('g', "{}", duplicatesSet);
+ printColoredOutputln('c', ":");
for (const auto &filePath: duplicates) {
- std::cout << " " << filePath << std::endl;
++numDuplicates;
+ std::println(" {}", filePath);
}
}
}
- printColor("\nFiles processed: ", 'c');
- printColor(filesProcessed, 'g', true);
+ printColoredOutput('c', "\nFiles processed: ");
+ printColoredOutputln('g', "{}", filesProcessed);
return numDuplicates;
}
@@ -221,68 +224,64 @@ std::size_t findDuplicates(const std::string_view directoryPath) {
/// \brief A simple duplicate file detective.
export void duplicateFinder() {
while (true) {
- std::cout << "\n-------------------";
- printColor(" Duplicate Finder ", 'm');
- std::cout << "-------------------\n";
- printColor("1. Scan for duplicate files\n", 'g');
- printColor("2. Exit\n", 'r');
- std::cout << "--------------------------------------------------------" << std::endl;
+ std::print("\n-------------------");
+ printColoredOutput('m', " Duplicate Finder ");
+ std::println("-------------------");
+ printColoredOutputln('g', "1. Scan for duplicate files");
+ printColoredOutputln('r', "2. Exit");
+ std::println("--------------------------------------------------------");
- printColor("Enter your choice:", 'b');
+ printColoredOutput('b', "Enter your choice:");
if (const int resp = getResponseInt(); resp == 1) {
try {
- printColor("Enter the path to the directory to scan:", 'b');
- std::string dirPath = getResponseStr();
-
- if (const auto len = dirPath.size(); len > 1 && (dirPath.ends_with('/') || dirPath.ends_with('\\')))
- dirPath.erase(len - 1);
+ printColoredOutput('b', "Enter the path to the directory to scan:");
+ fs::path dirPath = getFilesystemPath();
std::error_code ec;
const fs::file_status fileStatus = fs::status(dirPath, ec);
if (ec) {
- printColor("Unable to determine ", 'y', false, std::cerr);
- printColor(dirPath, 'b', false, std::cerr);
+ printColoredError('y', "Unable to determine ");
+ printColoredError('b', "{}", dirPath.string());
- printColor("'s status: ", 'y', false, std::cerr);
- printColor(ec.message(), 'r', true, std::cerr);
+ printColoredError('y', "'s status: ");
+ printColoredErrorln('r', "{}", ec.message());
ec.clear();
continue;
}
if (!exists(fileStatus)) {
- printColor(dirPath, 'c', false, std::cerr);
- printColor(" does not exist.", 'r', true, std::cerr);
+ printColoredError('c', "{}", dirPath.string());
+ printColoredErrorln('r', " does not exist.");
continue;
}
if (!is_directory(fileStatus)) {
- printColor(dirPath, 'c', false, std::cerr);
- printColor(" is not a directory.", 'r', true, std::cerr);
+ printColoredError('c', "{}", dirPath.string());
+ printColoredErrorln('r', " is not a directory.");
continue;
}
if (fs::is_empty(dirPath, ec)) {
if (ec) ec.clear();
else {
- printColor("The directory is empty.", 'r', true, std::cerr);
+ printColoredErrorln('r', "The directory is empty.");
continue;
}
}
- printColor("Scanning ", 'c');
- printColor(fs::canonical(dirPath).string(), 'g');
- printColor(" ...", 'c', true);
+ printColoredOutput('c', "Scanning ");
+ printColoredOutput('g', "{}", fs::canonical(dirPath).string());
+ printColoredOutputln('c', " ...");
const std::size_t duplicateFiles = findDuplicates(dirPath);
- std::cout << "Duplicates "
- << (duplicateFiles > 0 ? "found: " + std::to_string(duplicateFiles) : "not found.")
- << std::endl;
+ std::println("Duplicates {}",
+ duplicateFiles > 0 ? "found: " + std::to_string(duplicateFiles) : "not found.");
} catch (const std::exception &ex) {
- printColor("An error occurred: ", 'y', false, std::cerr);
- printColor(ex.what(), 'r', true, std::cerr);
+ printColoredError('y', "An error occurred: ");
+ printColoredErrorln('r', "{}", ex.what());
}
} else if (resp == 2) break;
else {
- printColor("Invalid option!", 'r', true, std::cerr);
+ printColoredErrorln('r', "Invalid option!");
}
}
}
diff --git a/src/encryption/encryptDecrypt.cpp b/src/encryption/encryptDecrypt.cpp
index ccdaa7d..f968b63 100644
--- a/src/encryption/encryptDecrypt.cpp
+++ b/src/encryption/encryptDecrypt.cpp
@@ -23,50 +23,19 @@ module;
#include
#include
#include
-#include
#include
#include
+#include
import utils;
import secureAllocator;
+import mimallocSTL;
import passwordManager;
module encryption;
namespace fs = std::filesystem;
-template
-/// \brief A concept describing a type convertible and comparable with uintmax_t.
-/// \tparam T - An integral type.
-concept Num = std::integral && std::convertible_to &&
- std::equality_comparable_with;
-
-
-/// \brief A class to make file sizes more readable.
-/// \details Adapted from https://en.cppreference.com/w/cpp/filesystem/file_size
-class FormatFileSize {
-public:
- explicit FormatFileSize(const Num auto &size) {
- // Default negative values to zero
- if (std::cmp_greater(size, size_))
- size_ = static_cast(size);
- }
-
-private:
- std::uintmax_t size_{0};
-
- friend
- std::ostream &operator<<(std::ostream &os, const FormatFileSize ffs) {
- int i{};
- auto mantissa = static_cast(ffs.size_);
- for (; mantissa >= 1024.; mantissa /= 1024., ++i) {
- }
- mantissa = std::ceil(mantissa * 10.) / 10.;
- os << mantissa << "BKMGTPE"[i];
- return i == 0 ? os : os << "B (" << ffs.size_ << ')';
- }
-};
-
/// \brief Available encryption/decryption ciphers.
enum class Algorithms : std::uint_fast8_t {
AES = 1 << 0,
@@ -91,13 +60,27 @@ constexpr struct {
const gcry_cipher_algos Twofish = GCRY_CIPHER_TWOFISH;
} AlgoSelection;
+
+/// \brief Formats a file size into a human-readable string.
+/// \param size The file size as an unsigned integer.
+/// \return A string representing the formatted file size.
+miSTL::string formatFileSize(const std::uintmax_t &size) {
+ int i{};
+ auto mantissa = static_cast(size);
+ for (; mantissa >= 1024.; mantissa /= 1024., ++i) {
+ }
+ mantissa = std::ceil(mantissa * 10.) / 10.;
+ miSTL::string result { std::to_string(mantissa) + "BKMGTPE"[i]};
+ return i == 0 ? result : result + "B (" + std::to_string(size).c_str() + ')';
+}
+
/// \brief Checks for issues with the input file, that may hinder encryption/decryption.
/// \param inFile the input file, to be encrypted/decrypted.
/// \param mode the mode of operation: encryption or decryption.
/// \throws std::invalid_argument if \p mode is invalid.
/// \throws std::runtime_error if the input file does not exist, is a directory,
/// is not a regular file, or is not readable.
-inline void checkInputFile(const fs::path &inFile, const OperationMode &mode) {
+void checkInputFile(const fs::path &inFile, const OperationMode &mode) {
if (mode != OperationMode::Encryption && mode != OperationMode::Decryption)
throw std::invalid_argument("Invalid mode of operation.");
@@ -111,7 +94,7 @@ inline void checkInputFile(const fs::path &inFile, const OperationMode &mode) {
if (!is_regular_file(inFile)) {
if (mode == OperationMode::Encryption) {
// Encryption
- std::cout << inFile.string() << " is not a regular file. \nDo you want to continue? (y/n): ";
+ std::print("{} is not a regular file.\nDo you want to continue? (y/n): ", inFile.string());
if (!validateYesNo())
throw std::runtime_error(std::format("{} is not a regular file.", inFile.string()));
} else
@@ -119,24 +102,22 @@ inline void checkInputFile(const fs::path &inFile, const OperationMode &mode) {
std::format("{} is not a regular file.", inFile.string())); // Encrypted files are regular
}
// Check if the input file is readable
- if (auto file = inFile.string(); !isReadable(file))
+ if (auto file = inFile.string(); !isReadable(file.c_str()))
throw std::runtime_error(std::format("{} is not readable.", file));
}
/// \brief Creates non-existing parent directories for a file.
/// \param filePath The file path for which the directory path needs to be created.
/// \return True if the directory path is created successfully or already exists, false otherwise.
-inline bool createPath(const fs::path &filePath) noexcept {
+bool createPath(const fs::path &filePath) noexcept {
if (filePath.string().empty()) return false; // Can't create empty paths
std::error_code ec;
-
auto absolutePath = weakly_canonical(filePath, ec);
if (ec) {
absolutePath = filePath;
ec.clear();
}
-
if (absolutePath.has_filename())
absolutePath.remove_filename();
@@ -167,15 +148,14 @@ inline void checkOutputFile(const fs::path &inFile, fs::path &outFile, const Ope
// If the output file is not specified, name it appropriately
if (equivalent(fs::current_path(), outFile)) {
outFile = inFile;
- if (inFile.extension() == ".enc") {
- outFile.replace_extension("");
- } else if (mode == OperationMode::Encryption) {
- outFile += ".enc";
- } else {
- outFile.replace_extension("");
- outFile += "_decrypted";
- outFile += inFile.extension();
- }
+ if (mode == OperationMode::Decryption) {
+ if (inFile.extension() == ".enc")
+ outFile.replace_extension("");
+ else {
+ outFile += "_decrypted";
+ outFile += inFile.extension();
+ }
+ } else outFile += ".enc";
} else if (is_directory(outFile)) {
// If the output file is a directory, rename it appropriately.
if (mode == OperationMode::Encryption) {
@@ -185,34 +165,34 @@ inline void checkOutputFile(const fs::path &inFile, fs::path &outFile, const Ope
}
// If the output file exists, ask for confirmation for overwriting
if (exists(outFile, ec)) {
- printColor(canonical(outFile).string(), 'b', false, std::cerr);
- printColor(" already exists. \nDo you want to overwrite it? (y/n): ", 'r', false, std::cerr);
+ printColoredError('b', "{}", canonical(outFile).string());
+ printColoredError('r', " already exists.\nDo you want to overwrite it? (y/n): ");
if (!validateYesNo())
throw std::runtime_error("Operation aborted.");
// Determine if the output file can be written if it exists
- if (auto file = weakly_canonical(outFile).string(); !(isWritable(file) && isReadable(file)))
+ if (auto file = weakly_canonical(outFile).string(); !(isWritable(file.c_str()) && isReadable(file.c_str())))
throw std::runtime_error(std::format("{} is not writable/readable.", file));
}
}
// Check if the input and output files are the same
- if (equivalent(inFile, outFile))
- throw std::runtime_error("The input and output files are the same.");
+ if (std::error_code ec; exists(outFile, ec) && equivalent(inFile, outFile))
+ throw std::runtime_error("The input and the output file both refer to the same object.");
// Check if there is enough space on the disk to save the output file.
const auto availableSpace = getAvailableSpace(weakly_canonical(outFile));
if (const auto fileSize = file_size(inFile); std::cmp_less(availableSpace, fileSize)) {
- printColor("Not enough space to save ", 'r', false, std::cerr);
- printColor(weakly_canonical(outFile).string(), 'c', true, std::cerr);
+ printColoredError('r', "Not enough space to save ");
+ printColoredError('c', "{}", weakly_canonical(outFile).string());
- printColor("Required: ", 'y', false, std::cerr);
- printColor(FormatFileSize(fileSize), 'g', true, std::cerr);
+ printColoredError('y', "Required: ");
+ printColoredError('g', "{}", formatFileSize(fileSize));
- printColor("Available: ", 'y', false, std::cerr);
- printColor(FormatFileSize(availableSpace), 'r', true, std::cerr);
+ printColoredError('y', "Available: ");
+ printColoredErrorln('r', "{}", formatFileSize(availableSpace));
- printColor("\nDo you still want to continue? (y/n):", 'b');
+ printColoredOutput('b', "\nDo you still want to continue? (y/n):");
if (!validateYesNo())
throw std::runtime_error("Insufficient storage space.");
}
@@ -232,17 +212,17 @@ inline void copyLastWrite(const std::string_view srcFile, const std::string_view
/// \param password the password to use for encryption/decryption.
/// \param algo the algorithm to use for encryption/decryption.
/// \param mode the mode of operation: encryption or decryption.
-void fileEncryptionDecryption(const std::string &inputFileName, const std::string &outputFileName,
+void fileEncryptionDecryption(const miSTL::string &inputFileName, const miSTL::string &outputFileName,
const privacy::string &password, const Algorithms &algo, const OperationMode &mode) {
// The mode must be valid: must be either encryption or decryption
if (mode != OperationMode::Encryption && mode != OperationMode::Decryption) [[unlikely]] {
- printColor("Invalid mode of operation.", 'r', true, std::cerr);
+ printColoredErrorln('r', "Invalid mode of operation.");
return;
}
try {
/// Encrypts/decrypts a file based on the passed mode and algorithm.
- auto encryptDecrypt = [&](const std::string &algorithm) -> void {
+ auto encryptDecrypt = [&](const miSTL::string &algorithm) -> void {
if (mode == OperationMode::Encryption) // Encryption
encryptFile(inputFileName, outputFileName, password, algorithm);
else // Decryption
@@ -278,25 +258,25 @@ void fileEncryptionDecryption(const std::string &inputFileName, const std::strin
// If we reach here, the operation was successful
auto pre = mode == OperationMode::Encryption ? "En" : "De";
- printColor(std::format("{}cryption completed successfully. \n{}crypted file saved as ", pre, pre), 'g');
- printColor(outputFileName, 'b', true);
+ printColoredOutput('g', "{}cryption completed successfully.\n{}crypted file saved as ", pre, pre);
+ printColoredOutputln('b', "{}", outputFileName);
// Preserve file permissions
if (!copyFilePermissions(inputFileName, outputFileName))
[[unlikely]]
- printColor(std::format("Check the permissions of the {}crypted file.", pre), 'm', true);
+ printColoredOutputln('m', "Check the permissions of the {}crypted file.", pre);
// Try to preserve the time of last modification
copyLastWrite(inputFileName, outputFileName);
} catch (const std::exception &ex) {
- printColor(std::format("Error: {}", ex.what()), 'r', true, std::cerr);
+ printColoredErrorln('r', "Error: {}", ex.what());
}
}
/// \brief Encrypts and decrypts files.
void encryptDecrypt() {
// I'm using hashmaps as an alternative to multiple if-else statements
- const std::unordered_map algoChoice = {
+ const miSTL::unordered_map algoChoice = {
{0, Algorithms::AES}, // Default
{1, Algorithms::AES},
{2, Algorithms::Camellia},
@@ -305,7 +285,7 @@ void encryptDecrypt() {
{5, Algorithms::Twofish}
};
- const std::unordered_map algoDescription = {
+ const miSTL::unordered_map algoDescription = {
{Algorithms::AES, "256-bit AES in CBC mode"},
{Algorithms::Camellia, "256-bit Camellia in CBC mode"},
{Algorithms::Aria, "256-bit Aria in CBC mode"},
@@ -314,18 +294,18 @@ void encryptDecrypt() {
};
while (true) {
- std::cout << "-------------";
- printColor(" file encryption/decryption utility ", 'c');
- std::cout << "-------------\n";
- printColor("1. Encrypt a file\n", 'g');
- printColor("2. Decrypt a file\n", 'm');
- printColor("3. Exit\n", 'r');
- std::cout << "--------------------------------------------------------------" << std::endl;
+ std::print("-------------");
+ printColoredOutput('c', " file encryption/decryption utility ");
+ std::println("-------------");
+ printColoredOutputln('g', "1. Encrypt a file");
+ printColoredOutputln('m', "2. Decrypt a file");
+ printColoredOutputln('r', "3. Exit");
+ std::println("--------------------------------------------------------------");
if (const int choice = getResponseInt("Enter your choice: "); choice == 1 || choice == 2) {
try {
- std::string pre = choice == 1 ? "En" : "De"; // the prefix string
- std::string pre_l{pre}; // the prefix in lowercase
+ miSTL::string pre = choice == 1 ? "En" : "De"; // the prefix string
+ miSTL::string pre_l{pre}; // the prefix in lowercase
// Transform the prefix to lowercase
std::ranges::transform(pre_l.begin(), pre_l.end(), pre_l.begin(),
@@ -333,44 +313,36 @@ void encryptDecrypt() {
return std::tolower(c);
});
- printColor(std::format("Enter the path to the file to {}crypt:", pre_l), 'c', true);
- std::string inputFile = getResponseStr();
-
- // Remove the trailing directory separator
- // ('\\' is considered as well in case the program is to be extended to Windows)
- if ((inputFile.ends_with('/') || inputFile.ends_with('\\')) && inputFile.size() > 1)
- inputFile.erase(inputFile.size() - 1);
+ printColoredOutputln('c', "Enter the path to the file to {}crypt:", pre_l);
+ fs::path inputPath = getFilesystemPath();
- fs::path inputPath(inputFile);
if (!inputPath.is_absolute()) // The path should be absolute
inputPath = fs::current_path() / inputPath;
checkInputFile(inputPath, static_cast(choice));
- printColor(std::format("Enter the path to save the {}crypted file "
- "\n(or leave it blank to save it in the same directory):",
- pre_l), 'c', true);
+ printColoredOutputln('c', "Enter the path to save the {}crypted file"
+ "\n(or leave it blank to save it in the same directory):", pre_l);
- fs::path outputPath{getResponseStr()};
+ fs::path outputPath = getFilesystemPath();
if (!outputPath.is_absolute()) // If the path is not absolute
outputPath = fs::current_path() / outputPath;
checkOutputFile(inputPath, outputPath, static_cast(choice));
- std::cout << "Choose a cipher (All are 256-bit):\n";
- printColor("1. Advanced Encryption Standard (AES)\n", 'b');
- printColor("2. Camellia\n", 'c');
- printColor("3. Aria\n", 'g');
- printColor("4. Serpent\n", 'y');
- printColor("5. Twofish\n", 'm');
+ std::println("Choose a cipher (All are 256-bit):");
+ printColoredOutputln('b', "1. Advanced Encryption Standard (AES)");
+ printColoredOutputln('c', "2. Camellia");
+ printColoredOutputln('g', "3. Aria");
+ printColoredOutputln('y', "4. Serpent");
+ printColoredOutputln('m', "5. Twofish");
- std::cout << "Leave blank to use the default (AES)" << std::endl;
+ std::println("Leave blank to use the default (AES)");
int algo = getResponseInt();
if (algo < 0 || algo > 5) {
// 0 is default (AES)
- printColor("Invalid choice!", 'r', true, std::cerr);
+ printColoredErrorln('r', "Invalid choice!");
continue;
}
-
const auto it = algoChoice.find(algo);
auto cipher = it != algoChoice.end() ? it->second : Algorithms::AES;
@@ -380,36 +352,33 @@ void encryptDecrypt() {
if (choice == 1) {
int tries{0};
while (password.empty() && ++tries < 3) {
- printColor("Please avoid empty or weak passwords. Please try again.", 'r', true, std::cerr);
+ printColoredErrorln('r', "Please avoid empty or weak passwords. Please try again.");
password = getSensitiveInfo("Enter the password: ");
}
-
if (tries >= 3)
throw std::runtime_error("Empty encryption password.");
- const privacy::string password2{getSensitiveInfo("Enter the password again: ")};
-
- if (!verifyPassword(password2, hashPassword(password, crypto_pwhash_OPSLIMIT_INTERACTIVE,
+ if (const privacy::string password2{getSensitiveInfo("Enter the password again: ")};
+ !verifyPassword(password2, hashPassword(password, crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE))) {
- printColor("Passwords do not match.", 'r', true, std::cerr);
+ printColoredErrorln('r', "Passwords do not match.");
continue;
}
}
- printColor(std::format("{}crypting ", pre), 'g');
- printColor(canonical(inputPath).string(), 'b');
- printColor(" with ", 'g');
- printColor(algoDescription.find(cipher)->second, 'c');
- printColor("...", 'g', true);
+ printColoredOutput('g', "{}crypting '", pre);
+ printColoredOutput('m', "{}", canonical(inputPath).string());
+ printColoredOutput('g', "' with ");
+ printColoredOutput('c', "{}", algoDescription.find(cipher)->second);
+ printColoredOutputln('g', "...");
- fileEncryptionDecryption(canonical(inputPath).string(), weakly_canonical(outputPath).string(),
+ fileEncryptionDecryption(canonical(inputPath).string().c_str(), weakly_canonical(outputPath).string().c_str(),
password, cipher, static_cast(choice));
- std::cout << std::endl;
+ std::println("");
} catch (const std::exception &ex) {
- printColor("Error: ", 'y', false, std::cerr);
- printColor(ex.what(), 'r', true, std::cerr);
- std::cerr << std::endl;
+ printColoredError('y', "Error: ");
+ printColoredErrorln('r', "{}", ex.what());
}
} else if (choice == 3) break;
- else printColor("Invalid choice!", 'r', true, std::cerr);
+ else printColoredErrorln('r', "Invalid choice!");
}
}
diff --git a/src/encryption/encryptFiles.cpp b/src/encryption/encryptFiles.cpp
index 49fcadf..b45b0c4 100644
--- a/src/encryption/encryptFiles.cpp
+++ b/src/encryption/encryptFiles.cpp
@@ -30,12 +30,13 @@ module;
import cryptoCipher;
import secureAllocator;
+import mimallocSTL;
module encryption;
-constexpr int MAX_KEY_SIZE = EVP_MAX_KEY_LENGTH; // For bounds checking
-constexpr std::size_t CHUNK_SIZE = 4096; // Read/Write files in chunks of 4 kB
-constexpr unsigned int PBKDF2_ITERATIONS = 100'000; // Iterations for PBKDF2 key derivation
+constexpr int MAX_KEY_SIZE = EVP_MAX_KEY_LENGTH; ///< Maximum length of a key
+constexpr std::streamsize CHUNK_SIZE = 4096; ///< Read/Write files in chunks of 4 kB
+constexpr unsigned int PBKDF2_ITERATIONS = 100'000; ///< Iterations for PBKDF2 key derivation
/// \brief Generates random bytes using a CSPRNG.
@@ -45,7 +46,7 @@ privacy::vector generateSalt(const int saltSize) {
std::mutex m;
privacy::vector salt(saltSize);
- if (std::scoped_lock lock(m); RAND_bytes(salt.data(), saltSize) != 1) {
+ if (std::scoped_lock lock(m); RAND_bytes(salt.data(), saltSize) != 1) {
std::cerr << "Failed to seed OpenSSL's CSPRNG properly."
"\nPlease check your system's randomness utilities." << std::endl;
@@ -134,15 +135,15 @@ deriveKey(const privacy::string &password, const privacy::vector
/// \details Encryption mode: CBC.
/// \details Key derivation function: PBKDF2 with BLAKE2b512 as the digest function (salted).
/// \details The IV is generated randomly with a CSPRNG and prepended to the encrypted file.
-void encryptFile(const std::string &inputFile, const std::string &outputFile, const privacy::string &password,
- const std::string &algo) {
+void encryptFile(const miSTL::string &inputFile, const miSTL::string &outputFile, const privacy::string &password,
+ const miSTL::string &algo) {
// Open the input file for reading
- std::ifstream inFile(inputFile, std::ios::binary);
+ std::ifstream inFile(inputFile.c_str(), std::ios::binary);
if (!inFile)
throw std::runtime_error(std::format("Failed to open '{}' for reading.", inputFile));
// Open the output file for writing
- std::ofstream outFile(outputFile, std::ios::binary | std::ios::trunc);
+ std::ofstream outFile(outputFile.c_str(), std::ios::binary | std::ios::trunc);
if (!outFile)
throw std::runtime_error(std::format("Failed to open '{}' for writing.", outputFile));
@@ -184,33 +185,40 @@ void encryptFile(const std::string &inputFile, const std::string &outputFile, co
outFile.write(reinterpret_cast(salt.data()), static_cast(salt.size()));
outFile.write(reinterpret_cast(iv.data()), static_cast(iv.size()));
- // Encrypt the file
- std::vector inBuf(CHUNK_SIZE);
- std::vector outBuf(CHUNK_SIZE + EVP_MAX_BLOCK_LENGTH);
- int bytesRead, bytesWritten;
+ // Buffers for file processing
+ unsigned char inBuf[CHUNK_SIZE];
+ unsigned char outBuf[CHUNK_SIZE + EVP_MAX_BLOCK_LENGTH];
+
+ // Lock the buffers
+ sodium_mlock(inBuf, CHUNK_SIZE);
+ sodium_mlock(outBuf, CHUNK_SIZE);
- while (true) {
+ int bytesRead, bytesWritten;
+ // The encryption loop
+ while (!inFile.eof()) {
// Read data from the file in chunks
- inFile.read(reinterpret_cast(inBuf.data()), static_cast(inBuf.size()));
+ inFile.read(reinterpret_cast(inBuf), CHUNK_SIZE);
bytesRead = static_cast(inFile.gcount());
- if (bytesRead <= 0)
- break;
- // Encrypt the data
- if (EVP_EncryptUpdate(cipher.getCtx(), outBuf.data(), &bytesWritten, inBuf.data(), bytesRead) != 1)
+ // Encrypt the chunk
+ if (EVP_EncryptUpdate(cipher.getCtx(), outBuf, &bytesWritten, inBuf, bytesRead) != 1)
throw std::runtime_error("Failed to encrypt the data.");
// Write the ciphertext (the encrypted data) to the output file
- outFile.write(reinterpret_cast(outBuf.data()), bytesWritten);
- outFile.flush(); // Ensure data is written immediately
+ outFile.write(reinterpret_cast(outBuf), bytesWritten);
+ // outFile.flush(); // Ensure data is written immediately
}
// Finalize the encryption operation
- if (EVP_EncryptFinal_ex(cipher.getCtx(), outBuf.data(), &bytesWritten) != 1)
+ if (EVP_EncryptFinal_ex(cipher.getCtx(), outBuf, &bytesWritten) != 1)
throw std::runtime_error("Failed to finalize encryption.");
// Write the last chunk
- outFile.write(reinterpret_cast(outBuf.data()), bytesWritten);
+ outFile.write(reinterpret_cast(outBuf), bytesWritten);
+
+ // Unlock and zeroize the buffers
+ sodium_munlock(inBuf, CHUNK_SIZE);
+ sodium_munlock(outBuf, CHUNK_SIZE);
}
/// \brief Decrypts a file encrypted by encryptFile() function.
@@ -220,15 +228,15 @@ void encryptFile(const std::string &inputFile, const std::string &outputFile, co
/// \param algo The cipher algorithm used to encrypt the file.
///
/// \throws std::runtime_error if the decryption fails, and for other (documented) errors.
-void decryptFile(const std::string &inputFile, const std::string &outputFile, const privacy::string &password,
- const std::string &algo) {
+void decryptFile(const miSTL::string &inputFile, const miSTL::string &outputFile, const privacy::string &password,
+ const miSTL::string &algo) {
// Open the input file for reading
- std::ifstream inFile(inputFile, std::ios::binary);
+ std::ifstream inFile(inputFile.c_str(), std::ios::binary);
if (!inFile)
throw std::runtime_error(std::format("Failed to open '{}' for reading.", inputFile));
// Open the output file for writing
- std::ofstream outFile(outputFile, std::ios::binary | std::ios::trunc);
+ std::ofstream outFile(outputFile.c_str(), std::ios::binary | std::ios::trunc);
if (!outFile)
throw std::runtime_error(std::format("Failed to open '{}' for writing.", outputFile));
@@ -278,33 +286,38 @@ void decryptFile(const std::string &inputFile, const std::string &outputFile, co
// Set automatic padding handling
EVP_CIPHER_CTX_set_padding(cipher.getCtx(), EVP_PADDING_PKCS7);
- // Decrypt the file
- privacy::vector inBuf(CHUNK_SIZE);
- privacy::vector outBuf(CHUNK_SIZE + EVP_MAX_BLOCK_LENGTH);
+ // Buffers for file processing
+ unsigned char inBuf[CHUNK_SIZE];
+ unsigned char outBuf[CHUNK_SIZE + EVP_MAX_BLOCK_LENGTH];
+
+ // Lock the buffers
+ sodium_mlock(inBuf, CHUNK_SIZE);
+ sodium_mlock(outBuf, CHUNK_SIZE);
int bytesRead, bytesWritten;
- while (true) {
+ while (!inFile.eof()) {
// Read the data in chunks
- inFile.read(reinterpret_cast(inBuf.data()), static_cast(inBuf.size()));
+ inFile.read(reinterpret_cast(inBuf), CHUNK_SIZE);
bytesRead = static_cast(inFile.gcount());
- if (bytesRead <= 0)
- break;
- // Decrypt the data
- if (EVP_DecryptUpdate(cipher.getCtx(), outBuf.data(), &bytesWritten, inBuf.data(), bytesRead) != 1)
+ // Decrypt the chunk
+ if (EVP_DecryptUpdate(cipher.getCtx(), outBuf, &bytesWritten, inBuf, bytesRead) != 1)
throw std::runtime_error("Failed to decrypt the data.");
// Write the decrypted data to the output file
- outFile.write(reinterpret_cast(outBuf.data()), bytesWritten);
- outFile.flush();
+ outFile.write(reinterpret_cast(outBuf), bytesWritten);
+ // outFile.flush();
}
// Finalize the decryption operation
- if (EVP_DecryptFinal_ex(cipher.getCtx(), outBuf.data(), &bytesWritten) != 1)
+ if (EVP_DecryptFinal_ex(cipher.getCtx(), outBuf, &bytesWritten) != 1)
throw std::runtime_error("Failed to finalize decryption.");
- outFile.write(reinterpret_cast(outBuf.data()), bytesWritten);
- outFile.flush();
+ outFile.write(reinterpret_cast(outBuf), bytesWritten);
+
+ // Unlock and zeroize the buffers
+ sodium_munlock(inBuf, CHUNK_SIZE);
+ sodium_munlock(outBuf, CHUNK_SIZE);
}
/// \brief Throws a thread-safe Gcrypt error.
@@ -331,15 +344,15 @@ inline void throwSafeError(const gcry_error_t &err, const std::string_view messa
/// using PBKDF2 with BLAKE2b-512 as the hash function.
/// \details The IV(nonce) is randomly generated and stored in the output file.
void
-encryptFileWithMoreRounds(const std::string &inputFilePath, const std::string &outputFilePath,
+encryptFileWithMoreRounds(const miSTL::string &inputFilePath, const miSTL::string &outputFilePath,
const privacy::string &password, const gcry_cipher_algos &algorithm) {
// Open the input file for reading
- std::ifstream inputFile(inputFilePath, std::ios::binary);
+ std::ifstream inputFile(inputFilePath.c_str(), std::ios::binary);
if (!inputFile)
throw std::runtime_error(std::format("Failed to open '{}' for reading.", inputFilePath));
// Open the output file for writing
- std::ofstream outputFile(outputFilePath, std::ios::binary | std::ios::trunc);
+ std::ofstream outputFile(outputFilePath.c_str(), std::ios::binary | std::ios::trunc);
if (!outputFile)
throw std::runtime_error(std::format("Failed to open '{}' for writing.", outputFilePath));
@@ -383,22 +396,27 @@ encryptFileWithMoreRounds(const std::string &inputFilePath, const std::string &o
outputFile.write(reinterpret_cast(salt.data()), static_cast(salt.size()));
outputFile.write(reinterpret_cast(ctr.data()), static_cast(ctr.size()));
+ unsigned char buffer[CHUNK_SIZE];
+ // Lock the buffer
+ sodium_mlock(buffer, CHUNK_SIZE);
+
// Encrypt the file in chunks
- privacy::vector buffer(CHUNK_SIZE);
while (!inputFile.eof()) {
- inputFile.read(reinterpret_cast(buffer.data()), CHUNK_SIZE);
+ inputFile.read(reinterpret_cast(buffer), CHUNK_SIZE);
const auto bytesRead = inputFile.gcount();
// Encrypt the chunk
- err = gcry_cipher_encrypt(cipherHandle, buffer.data(), buffer.size(), nullptr, 0);
+ err = gcry_cipher_encrypt(cipherHandle, buffer, CHUNK_SIZE, nullptr, 0);
if (err)
throwSafeError(err, "Failed to encrypt file");
// Write the encrypted chunk to the output file
- outputFile.write(reinterpret_cast(buffer.data()), bytesRead);
+ outputFile.write(reinterpret_cast(buffer), bytesRead);
}
- // Clean up
+ // Release the handle
gcry_cipher_close(cipherHandle);
+ // Unlock the buffer
+ sodium_munlock(buffer, CHUNK_SIZE);
}
/// \brief Decrypts a file encrypted by encryptFileWithMoreRounds() function.
@@ -409,15 +427,15 @@ encryptFileWithMoreRounds(const std::string &inputFilePath, const std::string &o
///
/// \throws std::runtime_error if the decryption fails, and for other (documented) errors.
void
-decryptFileWithMoreRounds(const std::string &inputFilePath, const std::string &outputFilePath,
+decryptFileWithMoreRounds(const miSTL::string &inputFilePath, const miSTL::string &outputFilePath,
const privacy::string &password, const gcry_cipher_algos &algorithm) {
// Open the input file for reading
- std::ifstream inputFile(inputFilePath, std::ios::binary);
+ std::ifstream inputFile(inputFilePath.c_str(), std::ios::binary);
if (!inputFile)
throw std::runtime_error(std::format("Failed to open '{}' for reading.", inputFilePath));
// Open the output file for writing
- std::ofstream outputFile(outputFilePath, std::ios::binary | std::ios::trunc);
+ std::ofstream outputFile(outputFilePath.c_str(), std::ios::binary | std::ios::trunc);
if (!outputFile)
throw std::runtime_error(std::format("Failed to open '{}' for writing.", outputFilePath));
@@ -466,20 +484,25 @@ decryptFileWithMoreRounds(const std::string &inputFilePath, const std::string &o
if (err)
throwSafeError(err, "Failed to set the decryption counter");
+ unsigned char buffer[CHUNK_SIZE];
+ // Lock the buffer
+ sodium_mlock(buffer, CHUNK_SIZE);
+
// Decrypt the file in chunks
- privacy::vector buffer(CHUNK_SIZE);
while (!inputFile.eof()) {
- inputFile.read(reinterpret_cast(buffer.data()), CHUNK_SIZE);
+ inputFile.read(reinterpret_cast(buffer), CHUNK_SIZE);
const auto bytesRead = inputFile.gcount();
// Decrypt the chunk in place
- err = gcry_cipher_decrypt(cipherHandle, buffer.data(), buffer.size(), nullptr, 0);
+ err = gcry_cipher_decrypt(cipherHandle, buffer, CHUNK_SIZE, nullptr, 0);
if (err)
throwSafeError(err, "Failed to decrypt the ciphertext");
// Write the decrypted chunk to the output file
- outputFile.write(reinterpret_cast(buffer.data()), bytesRead);
+ outputFile.write(reinterpret_cast(buffer), bytesRead);
}
// Release resources
gcry_cipher_close(cipherHandle);
+ // Unlock the buffer
+ sodium_munlock(buffer, CHUNK_SIZE);
}
diff --git a/src/encryption/encryptStrings.cpp b/src/encryption/encryptStrings.cpp
index d781706..b3b26f9 100644
--- a/src/encryption/encryptStrings.cpp
+++ b/src/encryption/encryptStrings.cpp
@@ -25,6 +25,7 @@ module;
import utils;
import secureAllocator;
+import mimallocSTL;
import cryptoCipher;
module encryption;
@@ -42,7 +43,7 @@ module encryption;
/// \details The key is derived from the password using PBKDF2 with 100,000 rounds (salted).
/// \details The IV is generated randomly using a CSPRNG and prepended to the ciphertext.
privacy::string
-encryptString(const privacy::string &plaintext, const privacy::string &password, const std::string &algo) {
+encryptString(const privacy::string &plaintext, const privacy::string &password, const miSTL::string &algo) {
CryptoCipher cipher;
// Create the cipher context
@@ -112,7 +113,7 @@ encryptString(const privacy::string &plaintext, const privacy::string &password,
///
/// \throws std::runtime_error if the decryption operation fails.
privacy::string
-decryptString(const std::string_view encodedCiphertext, const privacy::string &password, const std::string &algo) {
+decryptString(const std::string_view encodedCiphertext, const privacy::string &password, const miSTL::string &algo) {
CryptoCipher cipher;
// Create the cipher context
@@ -134,7 +135,7 @@ decryptString(const std::string_view encodedCiphertext, const privacy::string &p
privacy::vector encryptedText;
// Base64 decode the encoded ciphertext
- if (std::vector ciphertext = base64Decode(encodedCiphertext);
+ if (miSTL::vector ciphertext = base64Decode(encodedCiphertext);
ciphertext.size() > static_cast(SALT_SIZE) + ivSize) [[likely]] {
// Read the salt and IV from the ciphertext
salt.assign(ciphertext.begin(), ciphertext.begin() + SALT_SIZE);
@@ -277,7 +278,7 @@ decryptStringWithMoreRounds(const std::string_view encodedCiphertext, const priv
privacy::vector encryptedText;
// Base64-decode the encoded ciphertext
- if (std::vector ciphertext = base64Decode(encodedCiphertext);
+ if (miSTL::vector ciphertext = base64Decode(encodedCiphertext);
ciphertext.size() >= SALT_SIZE + ctrSize) [[likely]] {
// Read the salt and the counter from the ciphertext
salt.assign(ciphertext.begin(), ciphertext.begin() + SALT_SIZE);
diff --git a/src/encryption/encryption.cppm b/src/encryption/encryption.cppm
index fff6742..6d1284f 100644
--- a/src/encryption/encryption.cppm
+++ b/src/encryption/encryption.cppm
@@ -22,6 +22,7 @@ module;
export module encryption;
import secureAllocator;
+import mimallocSTL;
constexpr int SALT_SIZE = 32; // Default salt length (256 bits)
constexpr int KEY_SIZE_256 = 32; // Default key size (256 bits)
@@ -37,32 +38,32 @@ export {
deriveKey(const privacy::string &password, const privacy::vector &salt,
const int &keySize = KEY_SIZE_256);
- void encryptFile(const std::string &inputFile, const std::string &outputFile, const privacy::string &password,
- const std::string &algo = "AES-256-CBC");
+ void encryptFile(const miSTL::string &inputFile, const miSTL::string &outputFile, const privacy::string &password,
+ const miSTL::string &algo = "AES-256-CBC");
void
- encryptFileWithMoreRounds(const std::string &inputFilePath, const std::string &outputFilePath,
+ encryptFileWithMoreRounds(const miSTL::string &inputFilePath, const miSTL::string &outputFilePath,
const privacy::string &password,
const gcry_cipher_algos &algorithm = GCRY_CIPHER_SERPENT256);
- void decryptFile(const std::string &inputFile, const std::string &outputFile, const privacy::string &password,
- const std::string &algo = "AES-256-CBC");
+ void decryptFile(const miSTL::string &inputFile, const miSTL::string &outputFile, const privacy::string &password,
+ const miSTL::string &algo = "AES-256-CBC");
void
- decryptFileWithMoreRounds(const std::string &inputFilePath, const std::string &outputFilePath,
+ decryptFileWithMoreRounds(const miSTL::string &inputFilePath, const miSTL::string &outputFilePath,
const privacy::string &password,
const gcry_cipher_algos &algorithm = GCRY_CIPHER_SERPENT256);
privacy::string
encryptString(const privacy::string &plaintext, const privacy::string &password,
- const std::string &algo = "AES-256-CBC");
+ const miSTL::string &algo = "AES-256-CBC");
privacy::string encryptStringWithMoreRounds(const privacy::string &plaintext, const privacy::string &password,
const gcry_cipher_algos &algorithm = GCRY_CIPHER_SERPENT256);
privacy::string
decryptString(std::string_view encodedCiphertext, const privacy::string &password,
- const std::string &algo = "AES-256-CBC");
+ const miSTL::string &algo = "AES-256-CBC");
privacy::string decryptStringWithMoreRounds(std::string_view encodedCiphertext, const privacy::string &password,
const gcry_cipher_algos &algorithm = GCRY_CIPHER_SERPENT256);
diff --git a/src/fileShredder/fileShredder.cppm b/src/fileShredder/fileShredder.cppm
index 2541985..a866ace 100644
--- a/src/fileShredder/fileShredder.cppm
+++ b/src/fileShredder/fileShredder.cppm
@@ -17,7 +17,6 @@
module;
#include
-#include
#include
#include
#include
@@ -25,12 +24,15 @@ module;
#include
#include
#include
+#include
#include
+#include
using StatType = struct stat;
export module fileShredder;
import utils;
+import mimallocSTL;
namespace fs = std::filesystem;
constexpr std::streamoff BUFFER_SIZE = 4096;
@@ -55,7 +57,7 @@ void overwriteRandom(std::ofstream &file, const std::size_t fileSize, const int
// (Re)seed the Mersenne Twister engine in every pass
std::mt19937_64 gen(rd());
- std::vector buffer(BUFFER_SIZE);
+ miSTL::vector buffer(BUFFER_SIZE);
// Overwrite the file with random data
for (std::size_t pos = 0; pos < fileSize; pos += BUFFER_SIZE) {
@@ -90,7 +92,7 @@ void overwriteConstantByte(std::ofstream &file, T &byte, const auto &fileSize) {
// seek to the beginning of the file
file.seekp(0, std::ios::beg);
- std::vector buffer(BUFFER_SIZE, byte);
+ miSTL::vector buffer(BUFFER_SIZE, byte);
for (std::streamoff pos = 0; pos < fileSize; pos += BUFFER_SIZE) {
if (pos + BUFFER_SIZE > fileSize) {
@@ -124,10 +126,10 @@ inline void renameAndRemove(const std::string_view filename, int numTimes = 1) {
std::uniform_int_distribution numDist(minNameLength, maxNameLength);
// Get the file extension using std::filesystem
- const std::string fileExtension = fs::path(filename).extension().string();
+ const miSTL::string fileExtension = fs::path(filename).extension().string().c_str();
// Generate a random name using the safe characters (Not exhaustive)
- const std::string safeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ constexpr std::string_view safeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
std::uniform_int_distribution dist(0, safeChars.size() - 1);
fs::path path(filename);
@@ -141,7 +143,7 @@ inline void renameAndRemove(const std::string_view filename, int numTimes = 1) {
// Generate a random number of characters for the new name
const int numChars = numDist(gen);
- std::string newName;
+ miSTL::string newName;
// Generate a random name
for (int j = 0; j < numChars; ++j)
newName += safeChars[dist(gen)];
@@ -161,9 +163,9 @@ inline void renameAndRemove(const std::string_view filename, int numTimes = 1) {
fs::remove(path, ec);
if (ec) {
- printColor("Failed to delete ", 'r', false, std::cerr);
- printColor(filename, 'm', false, std::cerr);
- printColor(std::format(": {}", ec.message()), 'r', true, std::cerr);
+ printColoredError('r', "Failed to delete ");
+ printColoredError('m', "{}", filename);
+ printColoredErrorln('r', ": {}", ec.message());
}
}
@@ -176,9 +178,9 @@ inline void renameAndRemove(const std::string_view filename, int numTimes = 1) {
struct FileDescriptor {
int fd{-1};
- explicit FileDescriptor(const std::string &filename) : fd(open(filename.c_str(), O_RDWR)) {
+ explicit FileDescriptor(const miSTL::string &filename) : fd(open(filename.c_str(), O_RDWR)) {
if (fd == -1)
- throw std::runtime_error("Failed to open file: " + filename + " (" + std::strerror(errno) + ")");
+ throw std::runtime_error(std::format("Failed to open file: {} ({})", filename, std::strerror(errno)));
}
~FileDescriptor() { if (fd != -1) close(fd); }
@@ -202,7 +204,7 @@ struct FileStatInfo {
/// \brief wipes the cluster tips of a file.
/// \param fileName the path to the file to be wiped.
/// \throws std::runtime_error if zeroing the cluster tips fails.
-inline void wipeClusterTips(const std::string &fileName) {
+inline void wipeClusterTips(const miSTL::string &fileName) {
const FileDescriptor fileDescriptor(fileName);
const FileStatInfo fileInformation(fileDescriptor.fd);
@@ -213,14 +215,13 @@ inline void wipeClusterTips(const std::string &fileName) {
if (clusterTipSize >= fileInformation.fileStat.st_size) {
clusterTipSize = 0;
}
-
// Seek to the end of the file
if (lseek(fileDescriptor.fd, 0, SEEK_END) == -1) {
throw std::runtime_error(std::format("Failed to seek to end of file: ({})", std::strerror(errno)));
}
// Write zeros to the cluster tip
- const std::vector zeroBuffer(clusterTipSize, 0);
+ const miSTL::vector zeroBuffer(clusterTipSize, 0);
if (write(fileDescriptor.fd, zeroBuffer.data(), zeroBuffer.size()) == static_cast(-1)) {
throw std::runtime_error(std::format("Failed to write zeros: ({})", std::strerror(errno)));
@@ -233,10 +234,10 @@ inline void wipeClusterTips(const std::string &fileName) {
/// \param wipeClusterTip whether to wipe the cluster tips of the file.
///
/// \throws std::runtime_error if the file cannot be opened.
-void simpleShred(const std::string &filename, const int &nPasses = 3, const bool wipeClusterTip = false) {
- std::ofstream file(filename, std::ios::binary | std::ios::in);
+void simpleShred(const miSTL::string &filename, const int &nPasses = 3, const bool wipeClusterTip = false) {
+ std::ofstream file(filename.c_str(), std::ios::binary | std::ios::in);
if (!file)
- throw std::runtime_error("\nFailed to open file: " + filename);
+ throw std::runtime_error(std::format("\nFailed to open file: {}", filename));
std::error_code ec;
// Read last write time
@@ -268,10 +269,10 @@ void simpleShred(const std::string &filename, const int &nPasses = 3, const bool
/// \param wipeClusterTip whether to wipe the cluster tips of the file.
///
/// \throws std::runtime_error if the file cannot be opened, or if the number of passes is invalid.
-void dod5220Shred(const std::string &filename, const int &nPasses = 3, const bool wipeClusterTip = false) {
- std::ofstream file(filename, std::ios::binary | std::ios::in);
+void dod5220Shred(const miSTL::string &filename, const int &nPasses = 3, const bool wipeClusterTip = false) {
+ std::ofstream file(filename.c_str(), std::ios::binary | std::ios::in);
if (!file)
- throw std::runtime_error("\nFailed to open file: " + filename);
+ throw std::runtime_error(std::format("\nFailed to open file: {}", filename));
std::error_code ec;
// Read last write time
@@ -321,10 +322,10 @@ void dod5220Shred(const std::string &filename, const int &nPasses = 3, const boo
/// \enum ShredOptions
/// \brief Represents the different shredding options.
enum class ShredOptions : std::uint_fast8_t {
- Simple = 1 << 0, // Simple overwrite with random bytes
- Dod5220 = 1 << 1, // DoD 5220.22-M Standard algorithm
- Dod5220_7 = 1 << 2, // DoD 5220.22-M Standard algorithm with 7 passes
- WipeClusterTips = 1 << 3 // Wiping of the cluster tips
+ Simple = 1 << 0, ///< Simple overwrite with random bytes
+ Dod5220 = 1 << 1, ///< DoD 5220.22-M Standard algorithm
+ Dod5220_7 = 1 << 2, ///< DoD 5220.22-M Standard algorithm with 7 passes
+ WipeClusterTips = 1 << 3 ///< Wiping of the cluster tips
};
/// \brief Adds write and write permissions to a file, if the user has authority.
@@ -332,14 +333,14 @@ enum class ShredOptions : std::uint_fast8_t {
/// \return True if the operation succeeds, else false.
///
/// \details The actions of this function are similar to the unix command:
-/// \code chmod ugo+rw fileName \endcode or \code chmod a+rw fileName \endcode
+/// \code chmod ugo+rw fileName \endcode or \code chmod a+rw fileName \endcode. \n
/// The read/write permissions are added for everyone.
/// \note This function is meant for the file shredder ONLY, which might
/// need to modify a file's permissions (if and only if it has to) to successfully shred it.
///
/// \warning Modifying file permissions unnecessarily is a serious security risk,
/// and this program doesn't take that for granted.
-inline bool addReadWritePermissions(const std::string_view fileName) noexcept {
+static inline bool addReadWritePermissions(const std::string_view fileName) noexcept {
std::error_code ec;
permissions(fileName, fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read |
fs::perms::group_write | fs::perms::others_read | fs::perms::others_write,
@@ -359,21 +360,21 @@ inline bool addReadWritePermissions(const std::string_view fileName) noexcept {
///
/// \warning If the filePath is a directory, then all its files and subdirectories
/// are shredded without warning.
-bool shredFiles(const std::string &filePath, const std::uint_fast8_t &options, const int &simplePasses = 3) {
+bool shredFiles(const miSTL::string &filePath, const std::uint_fast8_t &options, const int &simplePasses = 3) {
std::error_code ec;
const fs::file_status fileStatus = fs::status(filePath, ec);
if (ec) {
- printColor("Unable to determine ", 'y', false, std::cerr);
- printColor(filePath, 'b', false, std::cerr);
+ printColoredError('y', "Unable to determine ");
+ printColoredError('b', "{}", filePath);
- printColor("'s status: ", 'y', false, std::cerr);
- printColor(ec.message(), 'r', true, std::cerr);
+ printColoredError('y', "'s status: ");
+ printColoredErrorln('r', "{}", ec.message());
return false;
}
// Check if the file exists and is a regular file.
if (!exists(fileStatus)) {
- printColor(filePath, 'c', false, std::cerr);
- printColor(" does not exist.", 'r', true, std::cerr);
+ printColoredError('c', "{}", filePath);
+ printColoredErrorln('r', " does not exist.");
return false;
}
// If the filepath is a directory, shred all the files in the directory and all its subdirectories
@@ -381,8 +382,8 @@ bool shredFiles(const std::string &filePath, const std::uint_fast8_t &options, c
if (fs::is_empty(filePath, ec)) {
if (ec) ec.clear();
else {
- printColor(filePath, 'c');
- printColor(" is an empty directory.", 'y', true);
+ printColoredOutput('c', "{}", filePath);
+ printColoredOutputln('y', " is an empty directory.");
return true;
}
}
@@ -392,48 +393,48 @@ bool shredFiles(const std::string &filePath, const std::uint_fast8_t &options, c
for (const auto &entry: fs::recursive_directory_iterator(filePath)) {
if (entry.exists(ec)) {
if (ec) {
- printColor(ec.message(), 'r', true, std::cerr);
+ printColoredErrorln('r', "{}", ec.message());
ec.clear();
continue;
}
if (!is_directory(entry.status())) {
- printColor("Shredding ", 'c');
- printColor(canonical(entry.path()).string(), 'b');
- printColor(" ...", 'c');
+ printColoredOutput('c', "Shredding ");
+ printColoredOutput('b', "{}", canonical(entry.path()).string());
+ printColoredOutput('c', " ...");
try {
- const bool shredded = shredFiles(entry.path().string(), options);
- printColor(shredded ? "\tshredded successfully." : "\tshredding failed.", shredded ? 'g' : 'r',
- true);
+ const bool shredded = shredFiles(entry.path().string().c_str(), options);
+ printColoredOutputln(shredded ? 'g' : 'r', "{}",
+ shredded ? "\tshredded successfully." : "\tshredding failed.");
++(shredded ? numShredded : numNotShredded);
} catch (const std::runtime_error &err) {
- printColor("Shredding failed: ", 'y', false, std::cerr);
- printColor(err.what(), 'r', true, std::cerr);
+ printColoredError('y', "Shredding failed: ");
+ printColoredErrorln('r', "{}", err.what());
}
}
}
}
if (numNotShredded == 0) // All files in the directory and all subdirectories were shredded successfully.
remove_all(fs::canonical(filePath));
- else printColor("Failed to shred some files.", 'r', true, std::cerr);
+ else printColoredErrorln('r', "Failed to shred some files.");
- std::cout << "\nProcessed " << numShredded + numNotShredded << " files." << std::endl;
+ std::println("\nProcessed {} files.", numShredded + numNotShredded);
if (numShredded) {
- printColor("Successfully shredded and deleted: ", 'g');
- printColor(numShredded, 'b', true);
+ printColoredOutput('g', "Successfully shredded and deleted: ");
+ printColoredOutputln('b', "{}", numShredded);
}
if (numNotShredded) {
- printColor("Failed to shred ", 'r', false, std::cerr);
- printColor(numNotShredded, 'b', false, std::cerr);
- printColor(" files.", 'r', true, std::cerr);
+ printColoredError('r', "Failed to shred ");
+ printColoredError('b', "{}", numNotShredded);
+ printColoredErrorln('r', " files.");
}
return true;
}
if (!is_regular_file(fileStatus)) {
- printColor(filePath, 'c', false, std::cerr);
- printColor(" is not a regular file.", 'r', true, std::cerr);
- printColor("Do you want to (try to) shred the file anyway? (y/n):", 'y', true);
+ printColoredError('c', "{}", filePath);
+ printColoredError('r', " is not a regular file.");
+ printColoredOutputln('y', "Do you want to (try to) shred the file anyway? (y/n):");
if (!validateYesNo()) return false;
}
@@ -441,8 +442,8 @@ bool shredFiles(const std::string &filePath, const std::uint_fast8_t &options, c
// Check file permissions
if (!isWritable(filePath) || !isReadable(filePath)) {
if (!addReadWritePermissions(filePath)) {
- printColor("\nInsufficient permissions to shred file: ", 'r', false, std::cerr);
- printColor(filePath, 'c', true, std::cerr);
+ printColoredError('r', "\nInsufficient permissions to shred file: ");
+ printColoredErrorln('c', "{}", filePath);
return false;
}
}
@@ -472,11 +473,10 @@ export void fileShredder() {
preferences |= std::to_underlying(ShredOptions::Simple) | wipeTips;
} else if (moreChoices1 == 2) {
// Configure shredding options
- const int alg = getResponseInt("\nChoose a shredding algorithm:\n"
+ if (const int alg = getResponseInt("\nChoose a shredding algorithm:\n"
"1. Overwrite with random bytes (default)\n"
"2. 3-pass DoD 5220.22-M Standard algorithm\n"
- "3. 7-pass DoD 5220.22-M Standard algorithm");
- if (alg == 1) {
+ "3. 7-pass DoD 5220.22-M Standard algorithm"); alg == 1) {
preferences |= std::to_underlying(ShredOptions::Simple) | wipeTips;
do {
@@ -500,7 +500,7 @@ export void fileShredder() {
} else if (simpleConfig == 4) {
// Abort
throw std::runtime_error("Operation aborted.");
- } else printColor("Invalid option", 'r', true, std::cerr);
+ } else printColoredErrorln('r', "Invalid option");
} while (true);
} else if (alg == 2 || alg == 3) {
// DoD 5220.22-M Standard algorithms
@@ -513,30 +513,26 @@ export void fileShredder() {
};
while (true) {
- printColor("\n------------------", 'g');
- printColor(" file shredder ", 'm');
- printColor("------------------", 'g', true);
+ printColoredOutput('g', "\n------------------");
+ printColoredOutput('m', " file shredder ");
+ printColoredOutputln('g', "------------------");
- printColor("1. Shred a file", 'y', true);
- printColor("2. Shred a directory", 'y', true);
- printColor("3. Exit", 'r', true);
+ printColoredOutputln('y', "1. Shred a file");
+ printColoredOutputln('y', "2. Shred a directory");
+ printColoredOutputln('r', "3. Exit");
- printColor("---------------------------------------------------", 'g', true);
+ printColoredOutputln('g', "---------------------------------------------------");
if (const int choice = getResponseInt("Enter your choice: "); choice == 1 || choice == 2) {
try {
// Get the path to the file or directory to shred
- std::string path = getResponseStr(std::format("Enter the path to the {} you would like to shred:",
- choice == 1 ? "file" : "directory"));
-
- // Remove trailing slashes
- if (const auto len = path.size(); len > 1 && (path.ends_with('/') || path.ends_with('\\')))
- path.erase(len - 1);
+ fs::path path = getFilesystemPath(std::format("Enter the path to the {} you would like to shred:",
+ choice == 1 ? "file" : "directory").c_str());
std::error_code ec;
const fs::file_status fileStatus = fs::status(path, ec);
if (ec) {
- printColor(ec.message(), 'r', true, std::cerr);
+ printColoredErrorln('r', "{}", ec.message());
ec.clear();
continue;
}
@@ -545,23 +541,23 @@ export void fileShredder() {
// Check if the file or directory exists
if (!exists(fileStatus)) {
- printColor(canonicalPath, 'c', false, std::cerr);
- printColor(" does not exist.", 'r', true, std::cerr);
+ printColoredError('c', "{}", canonicalPath);
+ printColoredErrorln('r', " does not exist.");
continue;
}
// If the path is a directory, shred all the files in the directory and all subdirectories (with confirmation)
if (choice == 1 && isDir) {
- printColor(canonicalPath, 'c');
- printColor(" is a directory.", 'r', true);
+ printColoredOutput('c', "{}", canonicalPath);
+ printColoredOutputln('r', " is a directory.");
- printColor("Shred all files in '", 'y');
- printColor(canonicalPath, 'c');
- printColor("'\nand all its subdirectories? (y/n):", 'y', true);
+ printColoredOutput('y', "Shred all files in '");
+ printColoredOutput('c', "{}", canonicalPath);
+ printColoredOutputln('y', "'\nand all its subdirectories? (y/n):");
if (!validateYesNo()) continue;
} else if (choice == 2 && !isDir) {
// If the path is a file, shred it without confirmation
- printColor(canonicalPath, 'c');
- printColor(" is not a directory.", 'r', true);
+ printColoredOutput('c', "{}", canonicalPath);
+ printColoredOutputln('r', " is not a directory.");
if (!validateYesNo("Shred it anyway? (y/n):")) continue;
}
std::uint_fast8_t preferences{0};
@@ -570,27 +566,27 @@ export void fileShredder() {
try {
selectPreferences(preferences, simpleNumPass);
} catch (const std::exception &ex) {
- printColor(std::format("Error: {}", ex.what()), 'r', true, std::cerr);
+ printColoredErrorln('r', "Error: {}", ex.what());
continue;
}
- printColor(std::format("The {} contents will be lost permanently.\nContinue? (y/n)",
- isDir ? "directory's (and all its subdirectories')" : "file"), 'r', true);
+ printColoredOutputln('r', "The {} contents will be lost permanently.\nContinue? (y/n)",
+ isDir ? "directory's (and all its subdirectories')" : "file");
if (validateYesNo()) {
std::cout << "Shredding '";
- printColor(canonicalPath, 'c');
+ printColoredOutput('c', "{}", canonicalPath);
std::cout << "'..." << std::endl;
- const bool shredded = shredFiles(path, preferences, simpleNumPass);
+ const bool shredded = shredFiles(path.string().c_str(), preferences, simpleNumPass);
if (!isDir) {
- printColor(shredded ? "Successfully shredded " : "Failed to shred ", shredded ? 'g' : 'r',
- false, shredded ? std::cout : std::cerr);
- printColor(canonicalPath, 'c', true, shredded ? std::cout : std::cerr);
+ printColoredOutput(shredded ? 'g' : 'r', "{}",
+ shredded ? "Successfully shredded " : "Failed to shred ");
+ printColoredOutputln('c', "{}", canonicalPath);
}
}
} catch (const std::exception &err) {
- printColor(std::format("Error: {}", err.what()), 'r', true, std::cerr);
+ printColoredErrorln('r', "Error: {}", err.what());
}
} else if (choice == 3) break;
- else printColor("Invalid choice.", 'r', true, std::cerr);
+ else printColoredErrorln('r', "Invalid choice.");
}
}
diff --git a/src/main.cpp b/src/main.cpp
index 11a7953..217db68 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,7 +17,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -29,37 +28,56 @@ import privacyTracks;
import encryption;
import passwordManager;
import fileShredder;
+import mimallocSTL;
import utils;
-constexpr const char *const MINIMUM_LIBGCRYPT_VERSION = "1.10.0";
+constexpr auto MINIMUM_LIBGCRYPT_VERSION = "1.10.0";
int main(const int argc, const char **argv) {
// The program should be launched in interactive mode
if (!isatty(STDIN_FILENO)) {
if (errno == ENOTTY) {
- printColor(std::format("{} is meant to be run interactively.", argv[0]), 'r', true, std::cerr);
+ printColoredErrorln('r', "{} is meant to be run interactively.", argv[0]);
return 1;
}
}
// Disable core dumping for security reasons
if (constexpr rlimit coreLimit{0, 0}; setrlimit(RLIMIT_CORE, &coreLimit) != 0) {
- printColor("Failed to disable core dumps.", 'r', true, std::cerr);
+ printColoredErrorln('r', "Failed to disable core dumps.");
return 1;
}
- // No arguments required
+ // Configure the color output, if necessary
+ configureColor();
+
+ // Only the first argument is considered
if (argc > 1) {
- printColor("Ignoring extra arguments: ", 'y');
- for (int i = 1; i < argc; printColor(std::format("{} ", argv[i++]), 'r')) {}
+ // Disable color output if requested
+ if (std::string_view(argv[1]) == "--no-color" || std::string_view(argv[1]) == "-nc") {
+ configureColor(true);
+ } else {
+ printColoredError('y', "The option ");
+ printColoredError('r', "{} ", argv[1]);
+ printColoredErrorln('y', "is not recognized.");
+
+ printColoredError('y', "Usage: ");
+ printColoredErrorln('r', "{} [--no-color | -nc]", argv[0]);
+ }
+ }
+
+ if (argc > 2) {
+ printColoredOutput('y', "Ignoring extra arguments: ", 'y');
+ for (int i = 2; i < argc; printColoredOutput('r', "{} ", argv[i++])) {
+ }
std::cout << std::endl;
}
// Handle the keyboard interrupt (SIGINT) signal (i.e., Ctrl+C)
struct sigaction act{};
act.sa_handler = [](int /* unused */) noexcept -> void {
- printColor("Keyboard interrupt detected. Unsaved data might be lost if you quit now."
- "\nDo you still want to quit? (y/n):", 'r');
+ printColoredOutput('r', "Keyboard interrupt detected.\nUnsaved data might be lost if you quit now."
+ "\nDo you still want to quit? (y/n):");
if (validateYesNo()) std::exit(1);
};
@@ -97,22 +115,22 @@ int main(const int argc, const char **argv) {
throw std::runtime_error("Failed to initialize libsodium.");
// Display information about the program
- printColor("\nPrivacy Shield 2.5.0\n", 'c');
- printColor("Copyright (C) 2024 Ian Duncan.\n", 'b');
+ printColoredOutputln('c', "\nPrivacy Shield 3.0.0");
+ printColoredOutputln('b', "Copyright (C) 2024 Ian Duncan.");
- printColor("This program comes with ", 'g');
- printColor("ABSOLUTELY NO WARRANTY.", 'r');
+ printColoredOutput('g', "This program comes with ");
+ printColoredOutputln('r', "ABSOLUTELY NO WARRANTY.");
- printColor("\nThis is a free software; you are free to change and redistribute it\n"
- "under the terms of the ", 'g');
- printColor("GNU General Public License v3 ", 'r');
- printColor("or later.", 'g');
+ printColoredOutput('g', "This is a free software; you are free to change and redistribute it\n"
+ "under the terms of the ");
+ printColoredOutput('r', "GNU General Public License v3 ");
+ printColoredOutputln('g', "or later.");
- printColor("\nFor more information, see ", 'g');
- printColor("https://www.gnu.org/licenses/gpl.html.\n", 'b', true);
+ printColoredOutput('g', "For more information, see ");
+ printColoredOutputln('b', "https://www.gnu.org/licenses/gpl.html.");
// All the available tools
- std::unordered_map > apps = {
+ miSTL::unordered_map > apps = {
{1, passwordManager},
{2, encryptDecrypt},
{3, fileShredder},
@@ -122,14 +140,14 @@ int main(const int argc, const char **argv) {
// Applications loop
while (true) {
- printColor("-------------------------------------\n", 'c');
- printColor("1. Manage passwords\n", 'b');
- printColor("2. Encrypt/decrypt files\n", 'g');
- printColor("3. Shred files\n", 'm');
- printColor("4. Clear browser privacy traces\n", 'y');
- printColor("5. Find duplicate files\n", 'b');
- printColor("6. Exit\n", 'r');
- printColor("-------------------------------------", 'c', true);
+ printColoredOutputln('c', "-------------------------------------");
+ printColoredOutputln('b', "1. Manage passwords");
+ printColoredOutputln('g', "2. Encrypt/decrypt files");
+ printColoredOutputln('m', "3. Shred files");
+ printColoredOutputln('y', "4. Clear browser privacy traces");
+ printColoredOutputln('b', "5. Find duplicate files");
+ printColoredOutputln('r', "6. Exit");
+ printColoredOutputln('c', "-------------------------------------");
const int choice = getResponseInt("What would you like to do? (Enter 1 or 2, 3..)");
@@ -138,24 +156,23 @@ int main(const int argc, const char **argv) {
iter->second();
else if (choice == 6)
break;
- else printColor("Invalid choice!", 'r', true, std::cerr);
+ else printColoredErrorln('r', "Invalid choice!");
} catch (const std::bad_function_call &bc) {
// In case the std::function objects are called inappropriately
- printColor(std::format("Bad function call: {}", bc.what()), 'r', true, std::cerr);
+ printColoredErrorln('r', "Bad function call: {}", bc.what());
} catch (const std::exception &ex) {
- printColor(std::format("Error: {}", ex.what()), 'r', true, std::cerr);
+ printColoredErrorln('r', "Error: {}", ex.what());
} catch (...) {
// All other exceptions, if any
- printColor("An error occurred.", 'r', true, std::cerr);
+ printColoredErrorln('r', "An error occurred.");
}
}
-
return 0;
} catch (const std::exception &ex) {
- printColor(std::format("Error: {}", ex.what()), 'r', true, std::cerr);
+ printColoredErrorln('r', "Error: {}", ex.what());
return 1;
} catch (...) {
- printColor("Something went wrong.", 'r', true, std::cerr);
+ printColoredErrorln('r', "Something went wrong.");
return 1;
}
}
diff --git a/src/mimallocSTL.cppm b/src/mimallocSTL.cppm
new file mode 100644
index 0000000..249f1b7
--- /dev/null
+++ b/src/mimallocSTL.cppm
@@ -0,0 +1,310 @@
+// Privacy Shield: A Suite of Tools Designed to Facilitate Privacy Management.
+// Copyright (C) 2024 Ian Duncan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://www.gnu.org/licenses.
+
+module;
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include