pax_global_header00006660000000000000000000000064147754344360014533gustar00rootroot0000000000000052 comment=682958259133d2e0301e8adb0899c7ecc781e6c7 rifiuti2-0.8.2/000077500000000000000000000000001477543443600132775ustar00rootroot00000000000000rifiuti2-0.8.2/.editorconfig000066400000000000000000000002531477543443600157540ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 indent_style = space [*.{c,h,txt,cmake}] indent_size = 4 [*.{md,yml,json}] indent_size = 2 rifiuti2-0.8.2/.gitattribute000066400000000000000000000000161477543443600160040ustar00rootroot00000000000000* text eol=lf rifiuti2-0.8.2/.github/000077500000000000000000000000001477543443600146375ustar00rootroot00000000000000rifiuti2-0.8.2/.github/workflows/000077500000000000000000000000001477543443600166745ustar00rootroot00000000000000rifiuti2-0.8.2/.github/workflows/check.yml000066400000000000000000000041051477543443600204740ustar00rootroot00000000000000name: Push and PR on: push: paths-ignore: - '**.md' - 'docs/**' - 'LICENSE*' - '.*' - '!.github/**' branches: - master pull_request: paths-ignore: - '**.md' - 'docs/**' - 'LICENSE*' - '.*' - '!.github/**' jobs: check: strategy: fail-fast: false matrix: include: - os: ubuntu-24.04 shell: bash - os: macos-13 shell: bash - os: windows-2022 msystem: 'MINGW32' shell: msys2 - os: windows-2022 msystem: 'UCRT64' shell: msys2 runs-on: ${{ matrix.os }} defaults: run: shell: ${{ matrix.shell }} {0} steps: - uses: actions/checkout@v4 # cmake pulls ninja by default - name: Install dependencies (MSYS) if: matrix.os == 'windows-2022' uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} update: true install: >- git pacboy: >- toolchain:p glib2:p cmake:p libxml2:p - name: Install dependencies (Ubuntu) if: matrix.os == 'ubuntu-24.04' run: > sudo apt-get install -y build-essential cmake ninja-build libglib2.0-dev libxml2-utils - name: Install dependencies (MacOS) if: matrix.os == 'macos-13' run: | brew install cmake ninja - name: Pre-build run: | cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo - name: Build run: | cmake --build build -v - name: Test prefixed install run: | cmake --install build --prefix inst --strip -v - name: Basic CTest continue-on-error: true id: basic_ctest env: MSYS2_PATH_TYPE: inherit run: | cd build && ctest - name: Rerun failed tests if: steps.basic_ctest.outcome == 'failure' env: MSYS2_PATH_TYPE: inherit run: | cd build && ctest --rerun-failed --output-on-failure -V rifiuti2-0.8.2/.github/workflows/cmake_check.yml000066400000000000000000000023251477543443600216360ustar00rootroot00000000000000name: CMake compatibility check on: workflow_dispatch: jobs: check: strategy: fail-fast: false matrix: cmake_ver: - 3.31.5 - 3.30.7 - 3.29.9 - 3.28.6 - 3.27.9 - 3.26.6 - 3.25.3 - 3.24.4 - 3.23.5 - 3.22.6 - 3.21.7 - 3.20.6 - 3.19.8 - 3.18.6 - 3.17.5 # - 3.16.9 # - 3.15.7 # - 3.14.7 # - 3.13.5 # - 3.12.4 # - 3.11.4 # - 3.10.3 # - 3.9.6 # - 3.8.2 # - 3.7.2 # - 3.6.3 # - 3.5.2 # - 3.4.3 # - 3.3.2 # - 3.2.3 # - 3.1.3 # - 3.0.2 runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest with: cmakeVersion: ${{ matrix.cmake_ver }} ninjaVersion: latest - name: Install prerequisites run: | sudo apt-get -qy update sudo -E DEBIAN_FRONTEND=noninteractive apt-get -qy install libglib2.0-dev - name: Check cmake invocation run: | mkdir build cmake -S . -B build -G Ninja rifiuti2-0.8.2/.github/workflows/release.yml000066400000000000000000000046061477543443600210450ustar00rootroot00000000000000name: Release on: push: tags: - 0.* - 1.* jobs: binary: strategy: fail-fast: false matrix: msystem: - 'MINGW32' - 'UCRT64' runs-on: windows-2022 defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} update: true install: >- git pacboy: >- toolchain:p glib2:p cmake:p - name: Pre-build run: | cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo - name: Build run: | cmake --build build -v - name: Create binary archive run: | cmake --build build -v --target package - uses: actions/upload-artifact@v4 with: name: binary-${{ matrix.msystem }} path: 'build/rifiuti2-*.zip' if-no-files-found: error source: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Install dependencies run: > sudo apt-get install -y build-essential cmake ninja-build libglib2.0-dev libxml2-utils - name: Pre-build run: | cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo - name: Create source archive run: | cmake --build build -v --target package_source - uses: actions/upload-artifact@v4 with: name: source path: 'build/rifiuti2-*.xz' if-no-files-found: error release: needs: - binary - source runs-on: ubuntu-24.04 permissions: contents: write id-token: write attestations: write environment: release steps: - name: Get source artifacts uses: actions/download-artifact@v4 with: name: source - name: Get binary artifacts uses: actions/download-artifact@v4 with: pattern: binary-* merge-multiple: true - uses: actions/attest-build-provenance@v2 with: subject-path: "rifiuti2-*" - name: Create release notes fragment run: | echo ${{ vars.RELEASE_NOTES }} | base64 -d - > notes.md - uses: ncipollo/release-action@v1 with: artifacts: 'rifiuti2-*' artifactErrorsFailBuild: true draft: true bodyFile: 'notes.md' rifiuti2-0.8.2/.gitignore000066400000000000000000000001431477543443600152650ustar00rootroot00000000000000**/*.exe **/*.o **/Makefile # IDE or editor files /.vscode /.idea **/*.bak **/*~ # CMake /build* rifiuti2-0.8.2/CMakeLists.txt000066400000000000000000000077061477543443600160510ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. cmake_minimum_required(VERSION 3.17 FATAL_ERROR) # cmake -E rm project(rifiuti2 VERSION 0.8.2 HOMEPAGE_URL "https://github.com/abelcheung/rifiuti2/" DESCRIPTION "Windows recycle bin analysis tool" LANGUAGES C) set(PROJECT_BUG_REPORT_URL "${PROJECT_HOMEPAGE_URL}issues") set(PROJECT_TOOL_USAGE_URL "${PROJECT_HOMEPAGE_URL}wiki/Usage-and-Examples") set(PROJECT_GH_PAGE "https://abelcheung.github.io/rifiuti2/") include(GNUInstallDirs) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) if(DEFINED CMAKE_C_FLAGS_DEBUG) string(APPEND CMAKE_C_FLAGS_DEBUG " -O0 -Wextra") else() set(CMAKE_C_FLAGS_DEBUG "-O0 -Wextra") endif() if(NOT DEFINED CMAKE_C_FLAGS) set(CMAKE_C_FLAGS "") endif() string(APPEND CMAKE_C_FLAGS " -DG_LOG_DOMAIN=\\\"${PROJECT_NAME}\\\" -Wall -Werror") set(CMAKE_STATIC_LINKER_FLAGS "-static") configure_file(src/config.h.in config.h) configure_file(docs/rifiuti.1.in rifiuti.1) configure_file(docs/readme.txt.in readme.txt) find_package(PkgConfig REQUIRED) pkg_check_modules(GLIB REQUIRED "glib-2.0 >= 2.40.0") # Do static build in Windows, which require finding # extra libraries if (WIN32) pkg_check_modules(ICONV REQUIRED "iconv") list(APPEND GLIB_STATIC_CFLAGS_OTHER -DGLIB_STATIC_COMPILATION) endif() foreach(bin rifiuti rifiuti-vista) add_executable( ${bin} src/${bin}.c src/${bin}.h ) target_include_directories( ${bin} BEFORE PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ) target_sources( ${bin} PRIVATE src/utils.c src/utils.h src/utils-conv.c src/utils-conv.h src/utils-error.h src/utils-io.c src/utils-io.h src/utils-platform.h ) if(WIN32) target_sources(${bin} PRIVATE src/utils-win.c) endif() if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") target_sources(${bin} PRIVATE src/utils-linux.c) endif() if(WIN32) target_include_directories(${bin} PRIVATE ${GLIB_STATIC_INCLUDE_DIRS} ${ICONV_STATIC_INCLUDE_DIRS}) target_compile_options (${bin} PRIVATE ${GLIB_STATIC_CFLAGS_OTHER} ${ICONV_STATIC_CFLAGS_OTHER}) target_link_libraries (${bin} PRIVATE authz ${GLIB_STATIC_LIBRARIES} ${ICONV_STATIC_LIBRARIES}) target_link_directories (${bin} PRIVATE ${GLIB_STATIC_LIBRARY_DIRS} ${ICONV_STATIC_LIBRARY_DIRS}) target_link_options (${bin} BEFORE PRIVATE ${CMAKE_STATIC_LINKER_FLAGS}) else() target_include_directories(${bin} PRIVATE ${GLIB_INCLUDE_DIRS}) target_compile_options (${bin} PRIVATE ${GLIB_CFLAGS_OTHER}) target_link_libraries (${bin} PRIVATE ${GLIB_LIBRARIES}) target_link_directories (${bin} PRIVATE ${GLIB_LIBRARY_DIRS}) endif() endforeach() # Install: Windows use simplistic folder, # non-Windows follow FHS. if(WIN32) set(CMAKE_INSTALL_BINDIR .) set(CMAKE_INSTALL_DOCDIR doc) else() install( FILES ${CMAKE_CURRENT_BINARY_DIR}/rifiuti.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) endif() install( TARGETS rifiuti rifiuti-vista RUNTIME ) install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/docs/THANKS.txt ${CMAKE_CURRENT_BINARY_DIR}/readme.txt TYPE DOC ) include(CTest) add_subdirectory(test) set(CPACK_SOURCE_PACKAGE_FILE_NAME ${PROJECT_NAME}-${PROJECT_VERSION}) if(WIN32) set(CPACK_GENERATOR "ZIP") set(CPACK_SOURCE_GENERATOR "ZIP") else() set(CPACK_GENERATOR "TGZ") set(CPACK_SOURCE_GENERATOR "TXZ") endif() set(CPACK_SOURCE_IGNORE_FILES .editorconfig .git/ .gitattribute .gitignore .github/ CTestConfig.cmake ${CMAKE_BINARY_DIR}/ ${PROJECT_BINARY_DIR}/ ) include(CPack) rifiuti2-0.8.2/CTestConfig.cmake000066400000000000000000000003721477543443600164530ustar00rootroot00000000000000set(CTEST_PROJECT_NAME "rifiuti2") set(CTEST_NIGHTLY_START_TIME "00:00:00 UTC") set(CTEST_DROP_METHOD "http") set(CTEST_DROP_SITE "localhost:8080") set(CTEST_DROP_LOCATION "/submit.php?project=${CTEST_PROJECT_NAME}") set(CTEST_DROP_SITE_CDASH TRUE) rifiuti2-0.8.2/LICENSE000066400000000000000000000027371477543443600143150ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2007-2024, Abel Cheung Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rifiuti2-0.8.2/NEWS.md000066400000000000000000000143771477543443600144110ustar00rootroot00000000000000## 0.8.2 ### Bug Fix - ([#47](https://github.com/abelcheung/rifiuti2/issues/47), thanks to @polkornyipt) Fix certain INFO2 field size for some 64bit architectures, so INFO2 files won't be misidentified - Fix build on GNU/Hurd ### Other changes - Drop all `minisign` usage - Enable GitHub artifact attestation ## 0.8.1 ### Feature - ([#35](https://github.com/abelcheung/rifiuti2/issues/35)) `rifiuti2` under [Windows Subsystem for Linux][wsl] v2 can do live probation of Windows host recycle bins - ([#39](https://github.com/abelcheung/rifiuti2/issues/39)) JSON output format [wsl]: https://learn.microsoft.com/en-us/windows/wsl/ ### Bug Fix and minor change - All trash records can display its own error, instead of having a collective, generic error on termination of program - ([#42](https://github.com/abelcheung/rifiuti2/issues/42)) Display partial path if index file truncation happens but doesn't affect integrity of other data fields - Perform basic validation on trash file deletion time ## 0.8.0 ### Breaking Change - As a result of revamps below, package maintainers need to rewrite their package files. See [compile instructions](https://github.com/abelcheung/rifiuti2/wiki/Compile-From-Source) and [GitHub workflow file](.github/workflows/check.yml) for reference. - ([#21](https://github.com/abelcheung/rifiuti2/issues/21)) Adopts CMake as build system, and drop Autoconf/Automake completely. Document files have been restructured as well. - ([#18](https://github.com/abelcheung/rifiuti2/issues/18)) Gettext support is removed, its m4 macro versioning is placing a burden on maintainers and packagers. - Same for the translation. No contribution so far, probably this is unneeded. - 64 bit Windows binary requires [Universal C runtime][ucrt], which needs extra download for Windows 8.1 or below. [ucrt]: https://support.microsoft.com/en-us/topic/update-for-universal-c-runtime-in-windows-c0514201-7fe6-95a3-b0a5-287930f3560c ### Feature - ([#3](https://github.com/abelcheung/rifiuti2/issues/3)) Implement live system inspection on Windows - ([#32](https://github.com/abelcheung/rifiuti2/issues/32)) `$Recycle.bin` also shows extra field like old `INFO2` files do, displaying whether some trashed entries have been restored, leaving only the index file present inside `$Recycle.bin` ### Bug Fix and minor change - ([#28](https://github.com/abelcheung/rifiuti2/issues/28)) Fix crash on big endian platform due to incorrect string length check - ([#19](https://github.com/abelcheung/rifiuti2/issues/19), [#34](https://github.com/abelcheung/rifiuti2/issues/34)) Avoid printing garbage under Windows command prompt - ([#22](https://github.com/abelcheung/rifiuti2/issues/22)) Manpage is retired, referring users to GitHub repository and online docs - (Variation of [#17](https://github.com/abelcheung/rifiuti2/issues/17)) Program name shown as `(null)` in Windows GUI help dialog - ([#37](https://github.com/abelcheung/rifiuti2/issues/37)) XML output for 95/NT `INFO` now contains total entries ever existed field ## 0.7.0 #### Feature Addition * Support recycle bin from jurassic Windows: 95, NT4, ME (Issue #9) * Verified to work for recycle bin on network shared folder using UNC path (such thing is rare but does exist) * Display timezone in tab-delimited output header * Guess Windows version based on recycle bin artifacts * Distributed Windows binaries: * Copes better with Windows ACL, detecting folder with insufficient permissions * Attempts to detect Windows locale setting and automatically determine translation to use #### Change * Now **mandates UTF-8 locale except on Windows** * File output is also in UTF-8 encoding under Windows * `-8` option is rendered obsolete as a result * **Distributed Windows binaries can only run on Vista or above** * Windows XP/2003 support removed due to glib changes * Won't overwrite destination file if it already exists * `$Recycle.bin` version: * Not printing file size field if it is corrupt * Exit with error status whenever errors are found in any entry, not just the last entry * `INFO2` version: * Restricts the choice of legacy path character encoding; generally, all encodings not ASCII compatible are disallowed * Building requirement changes * Remove GNUism for part of build toolchain (`make`, `awk`) * Use external GNU gettext instead of obsolete `glib-gettextize` #### Bug fix * Fix unicode display on Windows console (Issue #12) * More robust handling of invalid or undecipherable characters, displaying escaped hex or unicode sequences in such cases (Issue #5) ---- ## 0.6.1 #### Bug fix * Restore old date/time format for tab-delimited output, in order to be more spreadsheet friendly (Issue #8) * Fix timezone offset for ISO8601-format date, to account for DST * Fix data retrieval on big endian systems * No more attempt to limit usage of TZ environment variable (which doesn't work anyway) ---- ## 0.6.0 #### Feature * Windows 10 recycle bin support (Issue #1) * Add GUI dialog to notify first time Windows users (Issue #2) * 8.3 path names can also be used in XML output now #### Bug fix * Win98 INFO2 trashed file size not retrieved correctly * Substantial rework on showing translation and file names in different lanuages, especially on Windows platform #### Change * Display file deletion time in UTC time zone by default * Vista version: * No more accepts multiple file arguments * Invalid file or dir in command argument treated as fatal error * Result is sorted by deletion time, instead of random order * Show version info in order to differentiate between Vista & Windows 10 formats * INFO2 version: * No more accepts standard input as input data ---- ## 0.5.1 * New manpage * Test cases added to repository * Debian packaging stuff added to repository ---- ## 0.5.0 * Complete rewrite, using glib for I18N support and unicode handling * This means INFO2 records from any localized version of Windows can be parsed correctly * Since Vista recycle bin format changed completely, there will be no INFO2 file. A new program, `rifiuti-vista`, handles such format. * Both program can output in XML format as well as tab-delimited plain text. * Can choose to output long path name or legacy one (like "Progra~1") * Some preliminary check to guard against specially crafted recycle bin files. rifiuti2-0.8.2/README.md000066400000000000000000000055701477543443600145650ustar00rootroot00000000000000## Introduction `Rifiuti2` is a for analyzing Windows Recycle Bin INFO2 file. Analysis of Windows Recycle Bin is usually carried out during Windows computer forensics. `Rifiuti2` can extract file deletion time, original path and size of deleted files and whether the trashed files have been permanently removed. For those interested in what it does, and what functionality it provides, please [check out official site][site] for more info. [site]: https://abelcheung.github.io/rifiuti2 ## Special notes Latest features and changes can be found in [NEWS file](NEWS.md). ### 0.8.2 - Bug fix release for a few less common architectures. - Enable GitHub artifact attestation. ### 0.8.1 JSON output format, WSL v2 support, and improve robustness when reading broken data. ### 0.8.0 - Windows binaries will be published via [MSYS2 GitHub workflow](https://github.com/msys2/setup-msys2). - Package maintainers would need to rewrite their package files, in light of multiple renovations: [CMake migration](https://github.com/abelcheung/rifiuti2/issues/21), [gettext removal](https://github.com/abelcheung/rifiuti2/issues/18), document restructuring etc. ## Usage `rifiuti2` is designed to be portable (just download and use without need for installation), and runs on command line environment. Although utilities provide `-h` option for brief help message, it is suggested to [consult Wiki page][wiki1] for full detail on all of the options; following are a few examples on how to use them: [wiki1]: https://github.com/abelcheung/rifiuti2/wiki/Usage-and-Examples - `rifiuti-vista.exe -x -z -o result.xml \case\S-1-2-3\` > Scan for index files under `\case\S-1-2-3\`, adjust all deletion time > for local time zone, and write XML output to `result.xml` - `rifiuti -l CP932 -t "\n" INFO2` > Assume INFO2 file is generated from Japanese Windows (codepage 932), > and display each field line by line, instead of separated by tab ## Download ### Supported platforms `rifiuti2` is guaranteed usable on Windows, Linux and FreeBSD, with success reports for MacOS (using `brew`). Some testing on big endian platforms are done with Qemu emulator. More compatibility fix for other architectures welcome. ### Windows Windows binaries are officially provided [on Github release page][rel]. Some info for ancient Windows version are [available on wiki][wiki_pkg_win]. [rel]: https://github.com/abelcheung/rifiuti2/releases/ [wiki_pkg_win]: https://github.com/abelcheung/rifiuti2/wiki/Packages#packages-for-windows ### Unix packages Most Linux and FreeBSD users can use pre-packaged software for convenience. Check out [the status here][wiki_pkg]. [wiki_pkg]: https://github.com/abelcheung/rifiuti2/wiki/Packages#packages-for-linux-and-bsd ### Others For OS where `rifiuti2` is not readily available, it is always possible to [compile from source][wiki2]. [wiki2]: https://github.com/abelcheung/rifiuti2/wiki/Compile-From-Source rifiuti2-0.8.2/docs/000077500000000000000000000000001477543443600142275ustar00rootroot00000000000000rifiuti2-0.8.2/docs/THANKS.txt000066400000000000000000000024411477543443600157610ustar00rootroot00000000000000Original version of rifiuti: Copyright (C) 2003, Keith J. Jones Major credits should go to Keith Jones and all the folks at Foundstone (which was acquired by McAfee in 2004). Without their analysis on Windows Recycle Bin it would be much more difficult to fully dissect the file format. Their elaborate white paper and original version of rifiuti are available from ODESSA project: http://odessa.sourceforge.net/ ---- There are more people to thank to: Anthony Wong (manpage & original Debian packaging) Hilda Chan (abnormal Vista recycle bin sample) ---- Finally, rifiuti2 won't have extensive testing without several OSS projects and software archives: 1. winworldpc.com library, hosting many obsolete and unpurchaseable versions of Windows, especially ancient beta versions which provide insight on how Windows has evolved. 2. VirtualBox and Qemu, without their excellent virtual machine emulation it is simply impossible to test program behavior on different platforms. 3. A few website hosting obsolete service packs and ancient IE installers, relieving some pain on network access and tools installation in virtual machines: - Folks at browser.evolt.org who mirrored many ancient IE versions - Mirror for all NT service packs in sdfox7.com/winntsp.htm rifiuti2-0.8.2/docs/readme.txt.in000066400000000000000000000006441477543443600166360ustar00rootroot00000000000000@PROJECT_NAME@ is designed to run as portable command line application, and no installation is required. Extract zip file onto any folder of your choice and programs are ready for use. It is released under Revised BSD License. All documents are available online: - Detailed usage help: @PROJECT_TOOL_USAGE_URL@ - Official GitHub repository: @PROJECT_HOMEPAGE_URL@ - Extra news and technical info: @PROJECT_GH_PAGE@ rifiuti2-0.8.2/docs/rifiuti.1.in000066400000000000000000000026041477543443600163730ustar00rootroot00000000000000.\"- .\" Copyright (c) 2008 Anthony Wong .\" Copyrgith (c) 2015-2024 Abel Cheung .\" .\" rifiuti2 is released under Revised BSD License. .TH "@PROJECT_NAME@" "1" "Jan 2024" "@PROJECT_VERSION@" "@PROJECT_DESCRIPTION@" .SH NAME @PROJECT_NAME@ \[em] @PROJECT_DESCRIPTION@ .SH SYNOPSIS .na .B "\fCrifiuti\/\fP \fRor\/\fP \fCrifiuti-vista\/\fP" .B "[\-h | \-v | \-\-help | \-\-version]" .br .B "\fCrifiuti\/\fP [\-l \fIcodepage\/\fP]" .B "[\-f xml | \-f json | [\-n] [\-t \fIdelim\/\fP]]" .B "[\-z] [\-o \fIoutfile\/\fP] [\-\-] \fIinfo2_file\/\fP" .br .B "\fCrifiuti-vista\/\fP" .B "[\-f xml | \-f json | [\-n] [\-t \fIdelim\/\fP]]" .B "[\-z] [\-o \fIoutfile\/\fP] [\-\-] \fIrecycle_dir_or_file\/\fP" .br (for Windows and WSL) .B "\fCrifiuti-vista\/\fP --live" .B "[\-f xml | \-f json | [\-n] [\-t \fIdelim\/\fP]]" .B "[\-z] [\-o \fIoutfile\/\fP] .ad n .SH DESCRIPTION .nr PI 2n This manpage is now obsolete. Please read corresponding usage help from GitHub Wiki instead. .IP \[bu] @PROJECT_TOOL_USAGE_URL@ .SH COPYRIGHT @PROJECT_NAME@ is released under Revised BSD license. .SH AUTHOR The main author is Abel Cheung .nh \fC\fP .hy .PP The original author of rifiuti is Keith J. Jones .nh \fC\fP .hy .PP Anthony Wong .nh \fC\fP .hy helped in Debian packaging and was author of the original manpage. rifiuti2-0.8.2/src/000077500000000000000000000000001477543443600140665ustar00rootroot00000000000000rifiuti2-0.8.2/src/config.h.in000066400000000000000000000007071477543443600161150ustar00rootroot00000000000000#pragma once #cmakedefine PROJECT_NAME "@PROJECT_NAME@" #cmakedefine PROJECT_VERSION "@PROJECT_VERSION@" #cmakedefine PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@" #cmakedefine PROJECT_HOMEPAGE_URL "@PROJECT_HOMEPAGE_URL@" #cmakedefine PROJECT_BUG_REPORT_URL "@PROJECT_BUG_REPORT_URL@" #cmakedefine PROJECT_TOOL_USAGE_URL "@PROJECT_TOOL_USAGE_URL@" #cmakedefine PROJECT_GH_PAGE "@PROJECT_GH_PAGE@" rifiuti2-0.8.2/src/rifiuti-vista.c000066400000000000000000000257151477543443600170430ustar00rootroot00000000000000/* * Copyright (C) 2007-2024, Abel Cheung * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include #include #include "utils-error.h" #include "utils-conv.h" #include "utils.h" #include "rifiuti-vista.h" extern metarecord *meta; /** * @brief Basic validation of index file * @param filename Full path of index file * @param filebuf Location of file buffer after reading * @param bufsize Location to store size of buffer * @param ver Location to store index file version * @param error Location to store error upon failure * @return `TRUE` if file is deemed usable, `FALSE` otherwise * @note This only checks if index file has sufficient amount * of data for sensible reading */ static bool _validate_index_file (const char *filename, void **outbuf, gsize *bufsize, uint64_t *ver, GError **error) { char *buf = NULL; g_return_val_if_fail (filename && *filename, false); g_return_val_if_fail (outbuf && ! *outbuf, false); g_return_val_if_fail (! error || ! *error , false); g_return_val_if_fail (bufsize , false); g_return_val_if_fail (ver , false); g_debug ("Start file validation for '%s'...", filename); if (! g_file_get_contents (filename, &buf, bufsize, error)) goto validate_fail; if (*bufsize <= VERSION1_FILENAME_OFFSET) { g_set_error_literal (error, R2_REC_ERROR, R2_REC_ERROR_IDX_SIZE_INVALID, _("File is not a $Recycle.bin index")); goto validate_fail; } copy_field (*ver, buf, VERSION_OFFSET, FILESIZE_OFFSET); *ver = GUINT64_FROM_LE (*ver); g_debug ("version = %" PRIu64, *ver); switch (*ver) { case VERSION_VISTA: break; // already handled above case VERSION_WIN10: // Version 2 adds a uint32 file name strlen before file name. // This presumably breaks the 260 char barrier in version 1. if (*bufsize <= VERSION2_FILENAME_OFFSET) { g_set_error_literal (error, R2_REC_ERROR, R2_REC_ERROR_IDX_SIZE_INVALID, _("File is not a $Recycle.bin index")); goto validate_fail; } break; default: if (*ver < 10) g_set_error (error, R2_REC_ERROR, R2_REC_ERROR_VER_UNSUPPORTED, _("Index file version %" PRIu64 " is unsupported"), *ver); else g_set_error (error, R2_REC_ERROR, R2_REC_ERROR_VER_UNSUPPORTED, "%s", _("File is not a $Recycle.bin index")); goto validate_fail; } *outbuf = buf; g_debug ("Finished file validation for '%s'", filename); return true; validate_fail: g_free (buf); return false; } static rbin_struct * _populate_record_data (void *buf, gsize bufsize, uint64_t version) { rbin_struct *record; uint32_t path_sz_expected, path_sz_actual; size_t null_terminator_offset; void *pathbuf_start = NULL; bool erraneous = false; GString *u; // shorthand switch (version) { case VERSION_VISTA: // In rare cases, the size of index file is one byte short of // (fixed) 544 bytes in Vista. Under such occasion, file size // only occupies 56 bit, not 64 bit as it ought to be. // Actually this 56-bit file size is very likely wrong after all. // This is observed during deletion of dd.exe from Forensic // Acquisition Utilities (by George M. Garner Jr) // in certain localized Vista. if (bufsize == VERSION1_FILE_SIZE - 1) erraneous = true; path_sz_expected = WIN_PATH_MAX * sizeof(gunichar2); path_sz_actual = bufsize + (int)erraneous - VERSION1_FILENAME_OFFSET; pathbuf_start = buf - (int)erraneous + VERSION1_FILENAME_OFFSET; break; case VERSION_WIN10: copy_field (path_sz_expected, buf, VERSION1_FILENAME_OFFSET, VERSION2_FILENAME_OFFSET); path_sz_expected = GUINT32_FROM_LE (path_sz_expected) * sizeof(gunichar2); path_sz_actual = bufsize - VERSION2_FILENAME_OFFSET; pathbuf_start = buf + VERSION2_FILENAME_OFFSET; break; default: g_assert_not_reached (); } record = g_malloc0 (sizeof (rbin_struct)); record->version = version; copy_field (record->filesize, buf, FILESIZE_OFFSET, FILETIME_OFFSET - (int) erraneous); if (erraneous) { g_debug ("filesize field broken, 56 bit only, val=0x%" PRIX64, record->filesize); /* not printing the value because it was wrong and misleading */ record->filesize = G_MAXUINT64; } else { record->filesize = GUINT64_FROM_LE (record->filesize); g_debug ("deleted file size = %" PRIu64, record->filesize); } /* File deletion time */ copy_field (record->winfiletime, buf - (int) erraneous, FILETIME_OFFSET, VERSION1_FILENAME_OFFSET); record->winfiletime = GINT64_FROM_LE (record->winfiletime); record->deltime = win_filetime_to_gdatetime (record->winfiletime); if (record->error == NULL) { GDateTime *now = g_date_time_new_now_utc (); if (g_date_time_difference (record->deltime, now) > 525600000LL || // 1y g_date_time_get_year (record->deltime) < 2007) g_set_error_literal (&record->error, R2_REC_ERROR, R2_REC_ERROR_DUBIOUS_TIME, _("File deletion time is suspicious or broken")); g_date_time_unref (now); } // Unicode path if (path_sz_actual > path_sz_expected) { g_set_error_literal (&record->error, R2_REC_ERROR, R2_REC_ERROR_DUBIOUS_PATH, _("Ignored dangling extraneous data after record")); } else if (path_sz_actual < path_sz_expected && ! erraneous) { g_set_error_literal (&record->error, R2_REC_ERROR, R2_REC_ERROR_DUBIOUS_PATH, _("Record is truncated, thus unicode path might be incomplete")); } u = g_string_new_len ((const char *) pathbuf_start, MIN(path_sz_actual, path_sz_expected)); record->raw_uni_path = u; null_terminator_offset = ucs2_bytelen (u->str, u->len); if (record->error == NULL) { char *s = g_convert (u->str, null_terminator_offset, "UTF-8", "UTF-16LE", NULL, NULL, NULL); if (s) g_free (s); else g_set_error_literal (&record->error, R2_REC_ERROR, R2_REC_ERROR_CONV_PATH, _("Path contains broken unicode character(s)")); } return record; } static void _parse_record_cb (const char *index_file, metarecord *meta) { rbin_struct *record = NULL; char *basename = NULL; uint64_t version = 0; gsize bufsize; void *buf = NULL; extern bool isolated_index; GError *error = NULL; basename = g_path_get_basename (index_file); if (! _validate_index_file (index_file, &buf, &bufsize, &version, &error)) { g_hash_table_replace (meta->invalid_records, g_strdup (basename), error); g_free (basename); return; } g_debug ("Start populating record for '%s'...", basename); record = _populate_record_data (buf, bufsize, version); g_free (buf); /* Check corresponding $R.... file existance and set record->gone */ if (isolated_index) record->gone = FILESTATUS_UNKNOWN; else { char *dirname = g_path_get_dirname (index_file); char *trash_basename = g_strdup (basename); trash_basename[1] = 'R'; /* $R... versus $I... */ char *trash_path = g_build_filename (dirname, trash_basename, NULL); record->gone = g_file_test (trash_path, G_FILE_TEST_EXISTS) ? FILESTATUS_EXISTS : FILESTATUS_GONE; g_free (dirname); g_free (trash_basename); g_free (trash_path); } record->index_s = basename; g_ptr_array_add (meta->records, record); g_debug ("Parsing done for '%s'", basename); } static int _sort_record_by_time (gconstpointer left, gconstpointer right) { const rbin_struct *a = *((rbin_struct **) left); const rbin_struct *b = *((rbin_struct **) right); /* sort by deletion time, then index file name */ return ((a->winfiletime < b->winfiletime) ? -1 : (a->winfiletime > b->winfiletime) ? 1 : strcmp (a->index_s, b->index_s)); } static void _compare_idx_versions (rbin_struct *record, metarecord *meta) { if (meta->version == VERSION_INCONSISTENT) return; if (meta->version != (int64_t) record->version) { g_debug ("Bad entry %s, meta ver = %" PRId64 ", rec ver = %" PRId64, record->index_s, meta->version, (int64_t)record->version); meta->version = VERSION_INCONSISTENT; } } /** * @brief Determine overall version from all `$Recycle.bin` index files * @param meta The metadata for recycle bin * @return `FALSE` if multiple versions are found, otherwise `TRUE` */ static bool _set_overall_rbin_version (metarecord *meta) { if (! meta->records->len) { meta->version = VERSION_NOT_FOUND; return true; } meta->version = ((rbin_struct *)(meta->records->pdata[0]))->version; g_ptr_array_foreach (meta->records, (GFunc) _compare_idx_versions, meta); return (meta->version != VERSION_INCONSISTENT); } int main (int argc, char **argv) { GError *error = NULL; UNUSED (argc); if (! rifiuti_init ( RECYCLE_BIN_TYPE_DIR, N_("DIR_OR_FILE"), N_("Parse index files in C:\\$Recycle.bin style " "folder and dump recycle bin data. " "Can also dump a single index file."), &argv, &error )) goto cleanup; do_parse_records (&_parse_record_cb); if (! meta->records->len && g_hash_table_size (meta->invalid_records)) { g_set_error_literal (&error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA, _("No valid recycle bin record found")); goto cleanup; } g_ptr_array_sort (meta->records, _sort_record_by_time); if (! _set_overall_rbin_version (meta)) { g_set_error_literal (&error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA, _("Index files from multiple Windows versions are mixed together." " Please check each file individually.")); goto cleanup; } if (! dump_content (&error)) { g_assert (error->domain == G_FILE_ERROR); GError *new_err = g_error_new_literal ( R2_FATAL_ERROR, R2_FATAL_ERROR_TEMPFILE, g_strdup (error->message)); g_error_free (error); error = new_err; } cleanup: return rifiuti_cleanup (&error); } rifiuti2-0.8.2/src/rifiuti-vista.h000066400000000000000000000007351477543443600170430ustar00rootroot00000000000000/* * Copyright (C) 2007-2024, Abel Cheung * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #pragma once #include "utils-conv.h" #define VERSION_OFFSET 0x0 #define FILESIZE_OFFSET 0x8 #define FILETIME_OFFSET 0x10 #define VERSION1_FILENAME_OFFSET 0x18 #define VERSION2_FILENAME_OFFSET 0x1C #define VERSION1_FILE_SIZE ((VERSION1_FILENAME_OFFSET) + (WIN_PATH_MAX) * 2) rifiuti2-0.8.2/src/rifiuti.c000066400000000000000000000304151477543443600157100ustar00rootroot00000000000000/* * Copyright (C) 2003, Keith J. Jones. * Copyright (C) 2007-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include #include #include "utils-error.h" #include "utils-conv.h" #include "utils.h" #include "rifiuti.h" extern char *legacy_encoding; extern metarecord *meta; /* 0-25 => A-Z, 26 => '\', 27 or above is erraneous */ unsigned char driveletters[28] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '?' }; /*! * Check if index file has sufficient amount of data for reading * 0 = success, all other return status = error * If success, infile will be set to file pointer and other args * will be filled, otherwise file pointer = NULL */ static bool _validate_index_file (const char *filename, FILE **infile, GError **error) { void *buf = NULL; FILE *fp = NULL; uint32_t ver; int e; g_return_val_if_fail (filename && *filename, false); g_return_val_if_fail (infile && ! *infile, false); g_debug ("Start file validation for '%s'...", filename); if (! (fp = g_fopen (filename, "rb"))) { e = errno; g_set_error (error, G_FILE_ERROR, g_file_error_from_errno(e), _("Can not open file: %s"), g_strerror(e)); return false; } /* empty recycle bin = 20 bytes */ buf = g_malloc (RECORD_START_OFFSET); if (1 > fread (buf, RECORD_START_OFFSET, 1, fp)) { g_set_error_literal (error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA, _("File is not an INFO2 index.")); goto validation_fail; } copy_field (ver, buf, VERSION_OFFSET, KEPT_ENTRY_OFFSET); ver = GUINT32_FROM_LE (ver); // total_entry only meaningful for 95 and NT4, on other versions // it's junk memory data, don't bother copying if ( ( ver == VERSION_NT4 ) || ( ver == VERSION_WIN95 ) ) { copy_field (meta->total_entry, buf, TOTAL_ENTRY_OFFSET, RECORD_SIZE_OFFSET); meta->total_entry = GUINT32_FROM_LE (meta->total_entry); } copy_field (meta->recordsize, buf, RECORD_SIZE_OFFSET, FILESIZE_SUM_OFFSET); meta->recordsize = GUINT32_FROM_LE (meta->recordsize); g_free (buf); buf = NULL; switch (meta->recordsize) { case LEGACY_RECORD_SIZE: if (( ver != VERSION_ME_03 ) && /* ME -> 280 byte record */ ( ver != VERSION_WIN98 ) && ( ver != VERSION_WIN95 )) { g_set_error (error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA, "Illegal INFO2 version %" PRIu32, ver); goto validation_fail; } if (!legacy_encoding) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "This INFO2 file was produced on a legacy system " "without Unicode file name (Windows ME or earlier). " "Please specify codepage of concerned system with " "'-l' option."); goto validation_fail; } break; case UNICODE_RECORD_SIZE: if (ver != VERSION_ME_03 && ver != VERSION_NT4) { g_set_error (error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA, "Illegal INFO2 version %" PRIu32, ver); goto validation_fail; } break; default: g_set_error (error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA, "Illegal INFO2 of record size %" PRIu32, meta->recordsize); goto validation_fail; } rewind (fp); *infile = fp; meta->version = ver; return true; validation_fail: g_free (buf); fclose (fp); return false; } static rbin_struct * _populate_record_data (void *buf, size_t bufsize) { rbin_struct *record; uint32_t drivenum; size_t null_terminator_offset; GString *l, *u; // shorthand for paths // Unicode records accept partial path truncation, // but no fault tolerance for Legacy records if (meta->recordsize == LEGACY_RECORD_SIZE && bufsize < LEGACY_RECORD_SIZE) return NULL; if (meta->recordsize == UNICODE_RECORD_SIZE && bufsize <= LEGACY_RECORD_SIZE) return NULL; record = g_malloc0 (sizeof (rbin_struct)); // Verbatim path in ANSI code page l = g_string_new_len (buf, WIN_PATH_MAX); record->raw_legacy_path = l; /* Index number associated with the record */ copy_field (record->index_n, buf, RECORD_INDEX_OFFSET, DRIVE_LETTER_OFFSET); record->index_n = GUINT32_FROM_LE (record->index_n); g_debug ("index=%u", record->index_n); /* Number representing drive letter, 'A:' = 0, etc */ copy_field (drivenum, buf, DRIVE_LETTER_OFFSET, FILETIME_OFFSET); drivenum = GUINT32_FROM_LE (drivenum); g_debug ("drive=%u", drivenum); if (drivenum >= sizeof (driveletters) - 1) { g_set_error (&record->error, R2_REC_ERROR, R2_REC_ERROR_DRIVE_LETTER, _("Drive number %" PRIu32 "does not represent " "a valid drive"), drivenum); } record->drive = driveletters[MIN (drivenum, sizeof (driveletters) - 1)]; record->gone = FILESTATUS_EXISTS; // If file is not in recycle bin (restored or permanently deleted), // first byte will be removed from filename if (l->str[0] == '\0') { record->gone = FILESTATUS_GONE; l->str[0] = record->drive; } /* File deletion time */ copy_field (record->winfiletime, buf, FILETIME_OFFSET, FILESIZE_OFFSET); record->winfiletime = GINT64_FROM_LE (record->winfiletime); record->deltime = win_filetime_to_gdatetime (record->winfiletime); if (record->error == NULL) { GDateTime *now = g_date_time_new_now_utc (); if (g_date_time_difference (record->deltime, now) > 525600000LL || // 1y g_date_time_get_year (record->deltime) < 1995) g_set_error_literal (&record->error, R2_REC_ERROR, R2_REC_ERROR_DUBIOUS_TIME, _("File deletion time is suspicious or broken")); g_date_time_unref (now); } /* File size or occupied cluster size */ /* BEWARE! This is 32bit data casted to 64bit struct member */ copy_field (record->filesize, buf, FILESIZE_OFFSET, UNICODE_FILENAME_OFFSET); record->filesize = GUINT64_FROM_LE (record->filesize); g_debug ("filesize=%" PRIu64, record->filesize); // Only bother checking legacy path when requested, // because otherwise we don't know which encoding to use if (legacy_encoding) { char *s = g_convert (l->str, -1, "UTF-8", legacy_encoding, NULL, NULL, NULL); if (s) g_free (s); else g_set_error (&record->error, R2_REC_ERROR, R2_REC_ERROR_CONV_PATH, _("Path contains character(s) that could not be " "interpreted in %s encoding"), legacy_encoding); } if (bufsize == LEGACY_RECORD_SIZE) return record; // Part below deals with unicode path only if (bufsize < UNICODE_RECORD_SIZE && record->error == NULL) { g_set_error_literal (&record->error, R2_REC_ERROR, R2_REC_ERROR_DUBIOUS_PATH, _("Record is truncated, thus unicode path might be incomplete")); } u = g_string_new_len ((const char *) (buf + UNICODE_FILENAME_OFFSET), bufsize - UNICODE_FILENAME_OFFSET); record->raw_uni_path = u; null_terminator_offset = ucs2_bytelen (u->str, u->len); if (record->error == NULL) { char *s = g_convert (u->str, null_terminator_offset, "UTF-8", "UTF-16LE", NULL, NULL, NULL); if (s) g_free (s); else g_set_error_literal (&record->error, R2_REC_ERROR, R2_REC_ERROR_CONV_PATH, _("Path contains broken unicode character(s)")); } /* * We check for junk memory filling the padding area after * unicode path, using it as the indicator of OS generating this * INFO2 file. (server 2000 / 2003) * * The padding area after legacy path is no good; experiment * shows that legacy path *always* contain non-zero bytes after * null terminator if path contains double-byte character, * regardless of OS. * * Those non-zero bytes resemble partial end of full path. * Looks like an ANSI codepage full path is filled in * legacy path field, then overwritten in place by a 8.3 * version of path whenever applicable (which was always shorter). * * The 8.3 path generated from non-ascii seems to follow certain * ruleset, but the exact detail is unknown: * - accented latin chars transliterated to pure ASCII * - first DBCS char converted to UCS2 codepoint */ if (! meta->fill_junk && u->len > null_terminator_offset) { char *p = u->str + null_terminator_offset; while (p < u->str + u->len) { if (*p != '\0') { g_debug ("Junk detected at offset 0x%tx of unicode path", p - u->str); meta->fill_junk = true; break; } p++; } if (meta->fill_junk) hexdump (u->str, u->len); } return record; } static void _parse_record_cb (const char *index_file, metarecord *meta) { rbin_struct *record = NULL; FILE *infile = NULL; size_t read_sz, prev_pos, curr_pos; void *buf = NULL; GError *error = NULL; char *segment_id; if (! _validate_index_file (index_file, &infile, &error)) { g_hash_table_replace (meta->invalid_records, g_strdup (index_file), error); return; } g_debug ("Start populating record for '%s'...", index_file); fseek (infile, RECORD_START_OFFSET, SEEK_SET); prev_pos = curr_pos = ftell (infile); buf = g_malloc0 (meta->recordsize); while ((read_sz = fread (buf, 1, meta->recordsize, infile)) > 0) { prev_pos = curr_pos; curr_pos = ftell (infile); g_debug ("Read byte range %zu-%zu %s", prev_pos, curr_pos, (read_sz < meta->recordsize ? "" : " (!!!)")); if (NULL != (record = _populate_record_data (buf, read_sz))) g_ptr_array_add (meta->records, record); } g_free (buf); segment_id = g_strdup_printf ("|%zu|%zu", prev_pos, curr_pos); if (feof (infile)) { if (read_sz > 0 && record == NULL) g_set_error_literal (&error, R2_REC_ERROR, R2_REC_ERROR_IDX_SIZE_INVALID, _("Premature end of file encountered, and " "the last segment is not recoverable.")); } else if (ferror (infile)) // other generic error { g_set_error_literal (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Failed to read record for unknown reason")); } if (error) { g_hash_table_replace (meta->invalid_records, g_strdup (segment_id), error); } g_free (segment_id); fclose (infile); } int main (int argc, char **argv) { GError *error = NULL; UNUSED (argc); if (! rifiuti_init ( RECYCLE_BIN_TYPE_FILE, N_("INFO2"), N_("Parse INFO2 file and dump recycle bin data."), &argv, &error )) goto cleanup; do_parse_records (&_parse_record_cb); if (! meta->records->len && g_hash_table_size (meta->invalid_records)) { g_set_error_literal (&error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA, _("No valid recycle bin record found")); goto cleanup; } if (! dump_content (&error)) { g_assert (error->domain == G_FILE_ERROR); GError *new_err = g_error_new_literal ( R2_FATAL_ERROR, R2_FATAL_ERROR_TEMPFILE, g_strdup (error->message)); g_error_free (error); error = new_err; } cleanup: return rifiuti_cleanup (&error); } rifiuti2-0.8.2/src/rifiuti.h000066400000000000000000000017361477543443600157210ustar00rootroot00000000000000/* * Copyright (C) 2003, Keith J. Jones. * Copyright (C) 2007-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #pragma once #include "utils-conv.h" /* These offsets are relative to file start */ #define VERSION_OFFSET 0 #define KEPT_ENTRY_OFFSET 4 #define TOTAL_ENTRY_OFFSET 8 #define RECORD_SIZE_OFFSET 12 #define FILESIZE_SUM_OFFSET 16 #define RECORD_START_OFFSET 20 /* Following offsets are relative to start of each record */ #define LEGACY_FILENAME_OFFSET 0x0 #define RECORD_INDEX_OFFSET WIN_PATH_MAX #define DRIVE_LETTER_OFFSET ((WIN_PATH_MAX) + 4) #define FILETIME_OFFSET ((WIN_PATH_MAX) + 8) #define FILESIZE_OFFSET ((WIN_PATH_MAX) + 16) #define UNICODE_FILENAME_OFFSET ((WIN_PATH_MAX) + 20) #define LEGACY_RECORD_SIZE ((WIN_PATH_MAX) + 20) /* 280 bytes */ #define UNICODE_RECORD_SIZE ((WIN_PATH_MAX) * 3 + 20) /* 800 bytes */ rifiuti2-0.8.2/src/utils-conv.c000066400000000000000000000344331477543443600163440ustar00rootroot00000000000000/* * Copyright (C) 2023-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include #include #include #include #include "utils-error.h" #include "utils-conv.h" struct _fmt_data fmt[] = { // must match out_fmt enum order { .friendly_name = "TSV format", .fallback_tmpl = {"<\\u%04X>", "<\\%02X>", "<\\u%04X>"}, .gone_outtext = {"???", "FALSE", "TRUE"}, }, { .friendly_name = "XML format", // All paths are placed inside CDATA, using entities // can be confusing .fallback_tmpl = {"<\\u%04X>", "<\\%02X>", "<\\u%04X>"}, .gone_outtext = {"unknown", "false", "true"}, }, { .friendly_name = "JSON format", .fallback_tmpl = { "", // Unused, see json_escape() // JSON doesn't allow encoding raw byte data in strings // (must be proper characters) "<\\%02X>", // HACK \u sequence collides with path separator, which // will be processed in json escaping routine. Use a temp // char to avoid collision and convert it back later "*u%04X" }, .gone_outtext = {"null", "false", "true"}, }, }; /** * @brief Try out if encoding is compatible to ASCII * @param enc The encoding to test * @param error Location to store error during trial * @return `true` if compatible, `false` otherwise * (including the case where encoding doesn't exist) */ bool enc_is_ascii_compatible (const char *enc, GError **error) { bool equal; char *s; g_return_val_if_fail (enc && *enc, false); s = g_convert ("C:\\", -1, "UTF-8", enc, NULL, NULL, error); equal = (0 == g_strcmp0 ("C:\\", (const char *)s)); g_free (s); if (equal) return true; if (*error == NULL) // Encoding is ASCII incompatible (e.g. EBCDIC). Even if trial // convert doesn't fail, it would cause application error // later on. Treat that as conversion error for convenience. g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, ""); return false; } /** * @brief Find null terminator position in UCS2 string * @param str The string to check (in `char *` !) * @param max_sz Maximum byte length to check, or use -1 to * denote the string should be nul-terminated * @return Byte position where null terminator (double \\0) * is found, or `max_sz` otherwise * @note Being different from standard C funcs like `wcsnlen()` * or `strnlen()`, it returns bytes, not chars. And it would * take care of odd bytes when UCS2 strings are expecting * even number of bytes. */ size_t ucs2_bytelen (const char *str, ssize_t max_sz) { char *p = (char *) str; if (str == NULL || max_sz == 0) return 0; if (max_sz == 1) return 1; while (*p || *(p+1)) { p += 2; if (max_sz >= 0 && p - str + 1 >= max_sz) return max_sz; } return p - str; } /** * @brief Move character pointer for specified bytes * @param sz Must be either 1 or 2, denoting broken byte or broken UCS2 character * @param ptr Location of char pointer to string to be converted * @param bytes_left Location to number of remaining bytes to read * @param s Broken byte(s) will be formatted and appended to this `GString` * @param fmt_type Type of output format; see `fmt[]` for detail * @note This is the core of `conv_path_to_utf8_with_tmpl()` doing * error fallback, converting a single broken char to `printf` output. */ static void _advance_octet (size_t sz, char **ptr, gsize *bytes_left, GString *s, out_fmt fmt_type) { int c = 0; g_return_if_fail (*bytes_left > 0); g_return_if_fail (sz == 1 || sz == 2); g_return_if_fail (*ptr != NULL); if (*bytes_left == 1) sz = 1; if (sz == 1) c = *(uint8_t *) (*ptr); else c = GUINT16_FROM_LE (*(uint16_t *) (*ptr)); g_string_append_printf (s, fmt[fmt_type].fallback_tmpl[sz], c); *ptr += sz; *bytes_left -= sz; return; } /** * @brief Convert non-printable characters to escape sequences * @param str The original string to be converted * @param fmt_type Type of output format; see `fmt[]` for detail * @return Converted string, maybe containing escape sequences * @attention Caller is responsible for using correct template, no * error checking is performed. This template should handle a single * Windows unicode path character, which is in UTF-16LE encoding. */ static char * _filter_printable_char (const char *str, out_fmt fmt_type) { char *p, *np; gunichar c; GString *s; s = g_string_sized_new (strlen (str) * 2); p = (char *) str; while (*p) { c = g_utf8_get_char (p); np = g_utf8_next_char (p); // ASCII space is common (e.g. "Program Files"), but not // for any other kinds of space or invisible char if (g_unichar_isgraph (c) || (c == 0x20)) s = g_string_append_len (s, p, (size_t) (np - p)); else g_string_append_printf (s, fmt[fmt_type].fallback_tmpl[0], c); p = np; } return g_string_free (s, FALSE); } static void _sync_pos (GString *str, gsize *bytes_left, char **chr_ptr, bool from_gstring) { if (from_gstring) { *bytes_left = str->allocated_len - str->len - 1; *chr_ptr = str->str + str->len; } else { str->len = str->allocated_len - *bytes_left - 1; g_assert (*chr_ptr == str->str + str->len); str->str[str->len] = '\0'; } } /** * @brief Convert path to UTF-8 encoding with customizable fallback * @param path The path string to be converted * @param from_enc Either a legacy Windows ANSI encoding, or use * `NULL` to represent Windows wide char encoding (UTF-16LE) * @param fmt_type Type of output format; see `fmt[]` for detail * @param func String transform func for post processing; can be * `NULL`, which still does some internal filtering * @param error Location to store error upon problem * @return UTF-8 encoded path, or `NULL` if conversion error happens * @note This is very similar to `g_convert_with_fallback()`, but the * fallback is a `printf`-style string instead of a fixed string, * so that different fallback sequence can be used with various output * format. * @attention 1. This routine is not for generic charset conversion. * Extra transformation is intended for path display only. * @attention 1. Caller is responsible for using correct template, * no error checking is performed. */ char * conv_path_to_utf8_with_tmpl (const GString *path, const char *from_enc, out_fmt fmt_type, StrTransformFunc func, GError **error) { char *i_ptr, *o_ptr, *result; gsize i_size, i_left, o_left, char_sz, status; GIConv conv; GPtrArray *err_offsets; GString *s; // For unicode path, the first char must be ASCII drive letter // or slash. And since it is in little endian, first byte is // always non-null g_return_val_if_fail (path != NULL, NULL); g_return_val_if_fail (! from_enc || *from_enc, NULL); if (from_enc) { char_sz = sizeof (char); i_left = i_size = strnlen (path->str, WIN_PATH_MAX); } else { char_sz = sizeof (gunichar2); i_left = i_size = ucs2_bytelen (path->str, path->len); } i_ptr = path->str; // Ballpark figure, GString decides alloc size on its own s = g_string_sized_new (i_size + 1); _sync_pos (s, &o_left, &o_ptr, true); // Shouldn't fail, encoding already tested upon start of prog conv = g_iconv_open ("UTF-8", from_enc ? from_enc : "UTF-16LE"); g_debug ("Initial : r=%02zu, w=%02zu/%02zu", i_left, o_left, s->allocated_len - 1); err_offsets = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); // Pass 1: Convert to UTF-8, all illegal seq become escaped hex while (i_left > 0) { if (*i_ptr == '\0') { if (from_enc != NULL) break; if (*(i_ptr+1) == '\0') break; /* utf-16: check "\0\0" */ } // When non-reversible char are converted to \uFFFD, there // is nothing we can do. Just accept the status quo. status = g_iconv (conv, &i_ptr, &i_left, &o_ptr, &o_left); _sync_pos (s, &o_left, &o_ptr, false); if (status != (gsize) -1) break; int e = errno; g_debug ("Progress: r=%02zu, w=%02zu/%02zu, status=%zd (%s), str=%s", i_left, o_left, s->allocated_len - 1, status, g_strerror(e), s->str); switch (e) { case EINVAL: case EILSEQ: { size_t *processed = g_malloc (sizeof (size_t)); *processed = i_size - i_left; g_ptr_array_add (err_offsets, processed); } _advance_octet (char_sz, &i_ptr, &i_left, s, fmt_type); _sync_pos (s, &o_left, &o_ptr, true); g_debug ("Progress: r=%02zu, w=%02zu/%02zu, str=%s", i_left, o_left, s->allocated_len - 1, s->str); g_iconv (conv, NULL, NULL, &o_ptr, &o_left); // reset state _sync_pos (s, &o_left, &o_ptr, false); break; case E2BIG: s = g_string_set_size (s, s->allocated_len * 2); _sync_pos (s, &o_left, &o_ptr, true); break; } } g_debug ("Finally : r=%02zu, w=%02zu/%02zu, status=%zd, str=%s", i_left, o_left, s->allocated_len - 1, status, s->str); g_iconv_close (conv); if (error && g_error_matches ((const GError *) (*error), R2_REC_ERROR, R2_REC_ERROR_CONV_PATH) && err_offsets->len > 0) { // More detailed error message showing offsets char *old = (*error)->message; GString *dbg_str = g_string_new ((const char *) old); dbg_str = g_string_append (dbg_str, ", at offset:"); for (size_t i = 0; i < err_offsets->len; i++) { g_string_append_printf (dbg_str, " %zu", *((size_t *) (err_offsets->pdata[i]))); } (*error)->message = g_string_free (dbg_str, FALSE); g_free (old); } g_ptr_array_free (err_offsets, TRUE); // Pass 2: Post processing, e.g. convert non-printable chars to hex g_return_val_if_fail (g_utf8_validate (s->str, -1, NULL), NULL); if (func == NULL) result = _filter_printable_char (s->str, fmt_type); else result = func (s->str); g_string_free (s, TRUE); return result; } /** * @brief Convert escape sequences in delimiters * @param str The original delimiter string * @return Escaped delimiter string * @note Similar to `g_strcompress()`, but only process a few * characters, unlike glib routine which converts all 8bit chars. * Currently handles `\\r`, `\\n`, `\\t` and `\\e`. */ char * filter_escapes (const char *str) { GString *result, *debug_str; char *i = (char *) str; g_return_val_if_fail ( (str != NULL) && (*str != '\0'), NULL); result = g_string_new (NULL); do { if ( *i != '\\' ) { result = g_string_append_c (result, *i); continue; } switch ( *(++i) ) { case 'r': result = g_string_append_c (result, '\r'); break; case 'n': result = g_string_append_c (result, '\n'); break; case 't': result = g_string_append_c (result, '\t'); break; case 'e': result = g_string_append_c (result, '\x1B'); break; default: result = g_string_append_c (result, '\\'); i--; } } while ( *(++i) ); debug_str = g_string_new ("filtered delimiter = "); i = result->str; do { if ( *i >= 0x20 && *i <= 0x7E ) /* problem during linking with g_ascii_isprint */ debug_str = g_string_append_c (debug_str, *i); else g_string_append_printf (debug_str, "\\x%02X", *(unsigned char *) i); } while ( *(++i) ); g_debug ("%s", debug_str->str); g_string_free (debug_str, TRUE); return g_string_free (result, FALSE); } char * json_escape (const char *src) { // TODO g_string_replace from glib 2.68 does it all char *p = (char *) src; GString *s = g_string_sized_new (strlen (src)); while (*p) { gunichar c = g_utf8_get_char (p); switch (c) { // JSON does not need to escape asterisk. This is for // workaround in format template case '*' : s = g_string_append_c (s, '\\'); break; case '\\': // For all other chars below, they are actually disallowed // in Windows path. This is for the mischievous who // move data to other OS and rename case 0x22: case 0x27: s = g_string_append_c (s, '\\'); s = g_string_append_c (s, c); break; case 0x08: s = g_string_append (s, "\\b"); break; case 0x09: s = g_string_append (s, "\\t"); break; case 0x0A: s = g_string_append (s, "\\n"); break; case 0x0B: s = g_string_append (s, "\\v"); break; case 0x0C: s = g_string_append (s, "\\f"); break; case 0x0D: s = g_string_append (s, "\\r"); break; default : if (g_unichar_isgraph (c) || c == 0x20) s = g_string_append_unichar (s, c); else if (c < 0x10000) g_string_append_printf (s, "\\u%04X", c); else // calculate surrogate { uint16_t high, low; high = 0xD800 + ((c - 0x10000) >> 10 ); low = 0xDC00 + ((c - 0x10000) & 0x3FF); g_string_append_printf (s, "\\u%04X\\u%04X", high, low); } break; } p = g_utf8_next_char (p); } return g_string_free (s, FALSE); } rifiuti2-0.8.2/src/utils-conv.h000066400000000000000000000036341477543443600163500ustar00rootroot00000000000000/* * Copyright (C) 2023-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #pragma once #include #include // All versions of recycle bin prior to Windows 10 use full PATH_MAX // or FILENAME_MAX (260 char) to store file paths in either ANSI or // Unicode variations. However it is impossible to reuse any similar // constant as it is totally platform dependent. #define WIN_PATH_MAX 260 // Minimum bytes needed to guarantee writing a utf8 character #define MIN_WRITEBUF_SPACE 4 typedef enum { FORMAT_UNKNOWN = -1, FORMAT_TEXT, FORMAT_XML, FORMAT_JSON, } out_fmt; typedef struct _fmt_data { const char *friendly_name; // tmpl[0]=utf8 (max 32bit), 1=char (8bit), 2=ucs2 (16bit) // templates should use numeric printf format since // they are not proper characters, or non-printable // chars in case of UTF-8 // namely `%u`, `%o`, `%d`, `%i`, `%x` and `%X` const char *fallback_tmpl[3]; // The output for file deletion status const char *gone_outtext[3]; } _fmt_data; typedef char * (*StrTransformFunc) (const char *src); bool enc_is_ascii_compatible (const char *enc, GError **error); size_t ucs2_bytelen (const char *str, ssize_t max_sz); char * conv_path_to_utf8_with_tmpl (const GString *path, const char *from_enc, out_fmt fmt_type, StrTransformFunc func, GError **error); char * filter_escapes (const char *str); char * json_escape (const char *src); rifiuti2-0.8.2/src/utils-error.h000066400000000000000000000024001477543443600165220ustar00rootroot00000000000000/* * Copyright (C) 2023-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #pragma once #include typedef enum { R2_FATAL_ERROR_LIVE_UNSUPPORTED, /* Can't detect live system env */ R2_FATAL_ERROR_ILLEGAL_DATA, /* all data broken, not empty bin */ R2_FATAL_ERROR_TEMPFILE, } R2FatalError; /** * @brief Per record non-fatal error * @note Some error may indicate the whole record is invalidated, * but there also exists very minor error that doesn't. */ typedef enum { R2_REC_ERROR_DRIVE_LETTER, R2_REC_ERROR_DUBIOUS_TIME, R2_REC_ERROR_DUBIOUS_PATH, R2_REC_ERROR_CONV_PATH, R2_REC_ERROR_IDX_SIZE_INVALID, R2_REC_ERROR_VER_UNSUPPORTED, /* ($Recycle.bin) bad version */ } R2RecordError; typedef enum { R2_MISC_ERROR_GET_SID, // problem getting Security Identifier R2_MISC_ERROR_ENUMERATE_MNT, // can't get usable drive list } R2MiscError; // our own error domains #define R2_FATAL_ERROR (rifiuti_fatal_error_quark ()) GQuark rifiuti_fatal_error_quark (void); #define R2_REC_ERROR (rifiuti_record_error_quark ()) GQuark rifiuti_record_error_quark (void); #define R2_MISC_ERROR (rifiuti_misc_error_quark ()) GQuark rifiuti_misc_error_quark (void); rifiuti2-0.8.2/src/utils-io.c000066400000000000000000000061741477543443600160070ustar00rootroot00000000000000/* * Copyright (C) 2023-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include #include #include #include "utils-io.h" #include "utils-platform.h" static FILE *out_fh = NULL; static FILE *err_fh = NULL; static FILE *prev_fh = NULL; static char *tmpfile_path = NULL; static void _local_print (const char *str, bool is_stdout) { FILE *fh; if (!g_utf8_validate (str, -1, NULL)) { g_critical (_("String not in UTF-8 encoding: %s"), str); return; } fh = is_stdout ? out_fh : err_fh; #ifdef G_OS_WIN32 if (fh == NULL) { wchar_t *wstr = g_utf8_to_utf16 (str, -1, NULL, NULL, NULL); puts_wincon (is_stdout, wstr); g_free (wstr); } else #endif fputs (str, fh); } static void _local_printout (const char *str) { _local_print (str, true); } static void _local_printerr (const char *str) { _local_print (str, false); } /** * @brief Wrapper of `g_mkstemp()` that manages file handle and * output behind the scene * @param error Location of `GError` pointer to store potential problem * @return `true` if temp file is created successfully. Upon problem, * returns `false` and `error` is set. */ bool get_tempfile (GError **error) { int fd, e = 0; FILE *tmp_fh; // segfaults if string is pre-allocated in stack tmpfile_path = g_strdup ("rifiuti-XXXXXX"); if (-1 == (fd = g_mkstemp(tmpfile_path))) { e = errno; g_set_error (error, G_FILE_ERROR, g_file_error_from_errno(e), _("Can not create temp file: %s"), g_strerror(e)); return false; } if (NULL == (tmp_fh = fdopen (fd, "wb"))) { e = errno; g_set_error (error, G_FILE_ERROR, g_file_error_from_errno(e), _("Can not open temp file: %s"), g_strerror(e)); g_close (fd, NULL); return false; } prev_fh = out_fh; out_fh = tmp_fh; return true; } bool clean_tempfile (char *dest, GError **error) { int result; if (tmpfile_path == NULL) return true; if (prev_fh) { fclose (out_fh); out_fh = prev_fh; } if (0 != (result = g_rename (tmpfile_path, dest))) { int e = errno; g_set_error (error, G_FILE_ERROR, g_file_error_from_errno(e), _("%s. Temp file '%s' can't be moved to destination."), g_strerror(e), tmpfile_path); } g_free (tmpfile_path); return (result == 0); } void init_handles (void) { #ifdef G_OS_WIN32 if (! init_wincon_handle (false)) #endif err_fh = stderr; g_set_printerr_handler (_local_printerr); #ifdef G_OS_WIN32 if (! init_wincon_handle (true)) #endif out_fh = stdout; g_set_print_handler (_local_printout); } /** * @brief Close all output / error file handles before exit */ void close_handles (void) { if (out_fh != NULL) fclose (out_fh); if (err_fh != NULL) fclose (err_fh); return; } rifiuti2-0.8.2/src/utils-io.h000066400000000000000000000007571477543443600160150ustar00rootroot00000000000000/* * Copyright (C) 2023-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #pragma once #include #include void init_handles (void); void close_handles (void); bool get_tempfile (GError **error); bool clean_tempfile (char *dest, GError **error); rifiuti2-0.8.2/src/utils-linux.c000066400000000000000000000154721477543443600165400ustar00rootroot00000000000000/* * Copyright (C) 2015-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include "utils-conv.h" #include "utils-error.h" #include "utils-platform.h" G_DEFINE_QUARK (rifiuti-misc-error-quark, rifiuti_misc_error) /** * @brief The result of not wishing to use GIOChannel * @param haystack Content to be search * @param needle Substring to look for * @param separator Field separator in desired line * @param needle_pos Field number where needle is supposed to be * @param result_pos Field number of data we want * @return Desired search result in `GPtrArray` * @note This routine does not require all lines to have same * separator; result could be extracted as long as the * particular matching line has all the right conditions. */ static GPtrArray * _search_delimited_text (const char *haystack, const char *needle, const char *sep, gsize needle_pos, gsize result_pos) { GPtrArray *result = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); char **lines = g_strsplit (haystack, "\n", 0); for (gsize i = 0; i < g_strv_length (lines); i++) { if (NULL == g_strstr_len (lines[i], -1, needle)) continue; g_debug ("Found potential match '%s' in line '%s'", needle, lines[i]); char **fields = g_strsplit (g_strchomp (lines[i]), sep, 0); gsize nfields = g_strv_length (fields); if (nfields > needle_pos && nfields > result_pos && g_strcmp0 (fields[needle_pos], needle) == 0) { g_ptr_array_add (result, g_strdup (fields[result_pos])); } g_strfreev (fields); } g_strfreev (lines); return result; } /** * @brief Probe Windows SID from inside WSL Linux * @return SID string "S-1-...", or `NULL` if failure */ static char * _get_user_sid (GError **error) { const char *cmd = "whoami.exe /user /fo csv"; char *cmd_out = NULL, *cmd_err = NULL, *result = NULL; int exit_code; GError *cmd_error = NULL; if (FALSE == g_spawn_command_line_sync( cmd, &cmd_out, &cmd_err, &exit_code, &cmd_error)) { g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "Error running whoami: %s", cmd_error->message); goto sid_cleanup; } else if (exit_code != 0) // e.g. whoami.exe from MSYS2 { if (g_utf8_validate (cmd_err, 10, NULL)) g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "Error running whoami: %s", cmd_err); else // When running under valgrind, the stderr is set to // contain binary data of whoami.exe g_set_error_literal (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "Error running whoami with unknown reason"); goto sid_cleanup; } g_debug ("whoami output: %s", cmd_out); // No full fledged CSV parsing. Sample output: // "User Name","SID" // "machine\user","S-1-5-21-..." { char **lines = g_strsplit (cmd_out, "\r\n", 0); char **fields = g_strsplit (lines[1], ",", 0); glong len = g_utf8_strlen (fields[1], -1); result = g_utf8_substring (fields[1], 1, len - 1); g_strfreev (fields); g_strfreev (lines); if (result[0] != 'S') { g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "Invalid format '%s'", result); g_free (result); result = NULL; } } sid_cleanup: g_free (cmd_out); g_free (cmd_err); g_clear_error (&cmd_error); return result; } /** * @brief Check mount points for potential Windows drive * @param error Location to store `GError` upon problem * @return `GPtrArray` containing found mount points, * or `NULL` if problem arises */ static GPtrArray * _probe_mounts (GError **error) { GPtrArray *result; GError *read_error = NULL; gsize len; char *mounts_data = NULL; const char *fstype = "9p"; const char *proc = "/proc/self/mounts"; if (! g_file_get_contents (proc, &mounts_data, &len, &read_error)) { g_set_error_literal (error, R2_MISC_ERROR, R2_MISC_ERROR_ENUMERATE_MNT, read_error->message); g_clear_error (&read_error); return NULL; } result = _search_delimited_text ( (const char *) mounts_data, fstype, " ", 2, 1); g_free (mounts_data); return result; } /** * @brief Probe for possible Windows Recycle Bin under WSL Linux * @param error Location to store `GError` when problem arises * @return List of possible Windows paths in `GPtrArray` */ GPtrArray * enumerate_drive_bins (GError **error) { GPtrArray *mnt_pts, *result; char *sid; if (NULL == (sid = _get_user_sid (error))) return NULL; mnt_pts = _probe_mounts (error); if (mnt_pts == NULL) return NULL; if (mnt_pts->len == 0) { g_ptr_array_free (mnt_pts, TRUE); return NULL; } result = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); for (gsize i = 0; i < mnt_pts->len; i++) { char *full_rbin_path = g_build_filename ( (char *) (mnt_pts->pdata[i]), "$Recycle.bin", sid, NULL); if (g_file_test (full_rbin_path, G_FILE_TEST_EXISTS)) g_ptr_array_add (result, full_rbin_path); else g_free (full_rbin_path); } g_ptr_array_free (mnt_pts, TRUE); if (result->len == 0) { g_set_error_literal (error, R2_MISC_ERROR, R2_MISC_ERROR_ENUMERATE_MNT, "No recycle bin found on system"); g_ptr_array_free (result, TRUE); result = NULL; } return result; } /** * @brief Get Windows product name via registry * @return Windows product name as ASCII string */ char * windows_product_name (void) { const char *cmd = "reg.exe query \"HKLM\\software\\microsoft\\windows nt\\currentversion\" /v ProductName"; char *cmd_out = NULL, *cmd_err = NULL, *result = NULL; int exit_code; GError *error = NULL; GPtrArray *search_result; if (FALSE == g_spawn_command_line_sync( cmd, &cmd_out, &cmd_err, &exit_code, &error)) { g_debug ("Error running reg.exe: %s", error->message); goto prod_cleanup; } if (exit_code != 0) { g_debug ("reg.exe error: %s", cmd_err); goto prod_cleanup; } g_debug ("reg.exe output: %s", cmd_out); search_result = _search_delimited_text ( (const char *)cmd_out, "ProductName", " ", 1, 3); g_assert (search_result->len == 1); result = g_strdup ((char *) (search_result->pdata[0])); g_ptr_array_free (search_result, TRUE); prod_cleanup: g_free (cmd_out); g_free (cmd_err); g_clear_error (&error); return result; } rifiuti2-0.8.2/src/utils-platform.h000066400000000000000000000015371477543443600172270ustar00rootroot00000000000000/* * Copyright (C) 2007-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #pragma once #include #include #include #ifdef G_OS_WIN32 void gui_message (const char *message); char * get_win_timezone_name (void); bool can_list_win32_folder (const char *path, GError **error); bool init_wincon_handle (bool is_stdout); void puts_wincon (bool is_stdout, const wchar_t *wstr); void cleanup_windows_res (void); #endif #if (defined G_OS_WIN32 || defined __linux__) GPtrArray *enumerate_drive_bins (GError **error); char * windows_product_name (void); #endif rifiuti2-0.8.2/src/utils-win.c000066400000000000000000000320711477543443600161700ustar00rootroot00000000000000/* * Copyright (C) 2015-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include #include #include #include #include #include #include "utils-error.h" #include "utils-platform.h" static HANDLE wincon_fh = NULL; static HANDLE winerr_fh = NULL; static PSID sid = NULL; G_DEFINE_QUARK (rifiuti-misc-error-quark, rifiuti_misc_error) /* GUI message box */ void gui_message (const char *message) { wchar_t *title = L"This is a command line application"; wchar_t *body = (wchar_t *) g_utf8_to_utf16 ( message, -1, NULL, NULL, NULL); if (body) { MessageBoxW (NULL, body, title, MB_OK | MB_ICONINFORMATION | MB_TOPMOST); g_free (body); return; } body = L"(Failure to display help)"; MessageBoxW (NULL, body, title, MB_OK | MB_ICONINFORMATION | MB_TOPMOST); return; } /*! * `strftime()` on Windows can show garbage timezone name, because its * encoding does not match console codepage. For example, strftime %Z result * is in CP936 encoding for zh-HK, while console codepage is CP950. * * OTOH, `wcsftime() %Z` would not return anything if console codepage is * set to any non-default codepage for current system. * * `GetTimeZoneInformation()` returns sensible result regardless of * console codepage setting. */ char * get_win_timezone_name (void) { TIME_ZONE_INFORMATION tzinfo; WCHAR *name = NULL; DWORD id; char *result = NULL; GError *error = NULL; id = GetTimeZoneInformation (&tzinfo); g_debug ("%s(): GetTimeZoneInformation() = %lu", __func__, id); switch (id) { case TIME_ZONE_ID_UNKNOWN: case TIME_ZONE_ID_STANDARD: name = tzinfo.StandardName; break; case TIME_ZONE_ID_DAYLIGHT: name = tzinfo.DaylightName; break; default: { char *msg = g_win32_error_message (GetLastError ()); g_critical ("%s(): %s", __func__, msg); g_free (msg); } break; } if (name) { result = g_utf16_to_utf8 ((const gunichar2 *) name, -1, NULL, NULL, &error); if (error) { g_critical ("%s(): %s", __func__, error->message); g_clear_error (&error); } } if (result) return result; else return g_strdup (_("(Failed to retrieve timezone name)")); } /** * @brief Get SID of current user * @return Pointer to SID structure, or `NULL` upon failure * @note Code is derived from example of * [GetEffectiveRightsFromAcl()](https://learn.microsoft.com/en-us/windows/win32/api/aclapi/nf-aclapi-geteffectiverightsfromacla), */ static PSID _get_user_sid (GError **error) { static bool tried = false; wchar_t username[UNLEN + 1], *domainname = NULL; char *errmsg = NULL; DWORD e = 0, bufsize = UNLEN + 1, sidsize = 0, domainsize = 0; SID_NAME_USE sidtype; if (tried) return sid; tried = true; if (! GetUserNameW (username, &bufsize)) { errmsg = g_win32_error_message (GetLastError()); g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "GetUserName() failure: %s", errmsg); g_free (errmsg); return NULL; } if (! LookupAccountNameW (NULL, username, NULL, &sidsize, NULL, &domainsize, &sidtype)) { e = GetLastError(); if ( e != ERROR_INSUFFICIENT_BUFFER ) { errmsg = g_win32_error_message (e); g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "LookupAccountName() failure (1): %s", errmsg); g_free (errmsg); return NULL; } } // XXX Don't use standard Windows way (AllocateAndInitializeSid). // Random unpredictable test failures would result, even when // tests are run serially. sid = g_malloc (sidsize); // Unused but still needed, otherwise LookupAccountName call // would fail domainname = g_malloc (domainsize * sizeof (wchar_t)); if (LookupAccountNameW (NULL, username, sid, &sidsize, domainname, &domainsize, &sidtype)) { g_free (domainname); return sid; } errmsg = g_win32_error_message (GetLastError()); g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "LookupAccountName() failure (2): %s", errmsg); g_free (errmsg); g_free (sid); g_free (domainname); return NULL; } /** * @brief Probe for possible Windows Recycle Bin paths * @param error Location to store `GError` when problem arises * @return List of possible Windows paths in `GPtrArray` */ GPtrArray * enumerate_drive_bins (GError **error) { DWORD drive_bitmap; PSID sid = NULL; char *errmsg = NULL, *sid_str = NULL; static char drive_root[4] = "A:\\"; GPtrArray *result = NULL; if (NULL == (sid = _get_user_sid (error))) return NULL; if (! ConvertSidToStringSidA(sid, &sid_str)) { errmsg = g_win32_error_message (GetLastError()); g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_GET_SID, "ConvertSidToStringSidA() failure: %s", errmsg); goto enumerate_cleanup; } if (! (drive_bitmap = GetLogicalDrives())) { errmsg = g_win32_error_message (GetLastError()); g_set_error (error, R2_MISC_ERROR, R2_MISC_ERROR_ENUMERATE_MNT, "GetLogicalDrives() failure: %s", errmsg); goto enumerate_cleanup; } result = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); for (gsize i = 0; i < sizeof(DWORD) * CHAR_BIT; i++) { if (! (drive_bitmap & (1 << i))) continue; drive_root[0] = 'A' + i; UINT type = GetDriveTypeA(drive_root); if ( (type == DRIVE_NO_ROOT_DIR) || (type == DRIVE_UNKNOWN ) || (type == DRIVE_REMOTE ) || (type == DRIVE_CDROM )) { g_debug ("%s unwanted, type = %u", drive_root, type); continue; } char *full_rbin_path = g_build_filename (drive_root, "$Recycle.bin", sid_str, NULL); if (g_file_test (full_rbin_path, G_FILE_TEST_EXISTS)) g_ptr_array_add (result, full_rbin_path); else g_free (full_rbin_path); } // Might be possible that even C:\ is network drive // (e.g. thin client), but still report that as // error for live mode if (result->len == 0) { g_set_error_literal (error, R2_MISC_ERROR, R2_MISC_ERROR_ENUMERATE_MNT, _("No recycle bin found on system")); g_ptr_array_free (result, TRUE); result = NULL; } enumerate_cleanup: if (sid_str != NULL) LocalFree (sid_str); g_free (errmsg); return result; } /* * Get Windows product name via registry */ char * windows_product_name (void) { LSTATUS status; DWORD str_size; gunichar2 *buf; gunichar2 *subkey = L"software\\microsoft\\windows nt\\currentversion"; gunichar2 *keyvalue = L"ProductName"; char *result; status = RegGetValueW( HKEY_LOCAL_MACHINE, subkey, keyvalue, RRF_RT_REG_SZ, NULL, NULL, &str_size ); g_debug ("1st RegGetValueW(ProductName): status = %li, str_size = %lu", status, str_size); if (status != ERROR_SUCCESS) return NULL; buf = g_malloc ((gsize) str_size); status = RegGetValueW( HKEY_LOCAL_MACHINE, subkey, keyvalue, RRF_RT_REG_SZ, NULL, buf, &str_size ); g_debug ("2nd RegGetValueW(ProductName): status = %li", status); if (status != ERROR_SUCCESS) { g_free (buf); return NULL; } result = g_utf16_to_utf8(buf, -1, NULL, NULL, NULL); g_free (buf); return result; } /*! * Fetch ACL access mask using Authz API */ bool can_list_win32_folder (const char *path, GError **error) { char *errmsg = NULL; gunichar2 *wpath; bool ret = false; PSID sid; DWORD dw, dw2; PSECURITY_DESCRIPTOR sec_desc; ACCESS_MASK mask; AUTHZ_RESOURCE_MANAGER_HANDLE authz_manager; AUTHZ_CLIENT_CONTEXT_HANDLE authz_ctxt = NULL; AUTHZ_ACCESS_REQUEST authz_req = { MAXIMUM_ALLOWED, NULL, NULL, 0, NULL }; AUTHZ_ACCESS_REPLY authz_reply; if (NULL == (sid = _get_user_sid (error))) return false; wpath = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL); if (wpath == NULL) return false; if ( !AuthzInitializeResourceManager (AUTHZ_RM_FLAG_NO_AUDIT, NULL, NULL, NULL, NULL, &authz_manager) ) { errmsg = g_win32_error_message (GetLastError()); g_printerr (_("AuthzInitializeResourceManager() failed: %s"), errmsg); g_printerr ("\n"); goto traverse_fail; } dw = GetNamedSecurityInfoW ((wchar_t *)wpath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, NULL, NULL, NULL, NULL, &sec_desc); if ( dw != ERROR_SUCCESS ) { errmsg = g_win32_error_message (dw); g_printerr (_("Failed to retrieve Discretionary ACL info for '%s': %s"), path, errmsg); g_printerr ("\n"); goto traverse_getacl_fail; } if ( !AuthzInitializeContextFromSid (0, sid, authz_manager, NULL, (LUID) {0} /* unused */, NULL, &authz_ctxt) ) { errmsg = g_win32_error_message (GetLastError()); g_printerr (_("AuthzInitializeContextFromSid() failed: %s"), errmsg); g_printerr ("\n"); goto traverse_getacl_fail; } authz_reply = (AUTHZ_ACCESS_REPLY) { 1, &mask, &dw2, &dw }; /* last 2 param unused */ if ( !AuthzAccessCheck (0, authz_ctxt, &authz_req, NULL, sec_desc, NULL, 0, &authz_reply, NULL ) ) { errmsg = g_win32_error_message (GetLastError()); g_printerr (_("AuthzAccessCheck() failed: %s"), errmsg); g_printerr ("\n"); } else { /* * We only need permission to list directory; even directory traversal is * not needed, because we are going to access the files directly later. * Unlike Unix, no read permission on parent folder is needed to list * files within. */ if ( (mask & FILE_LIST_DIRECTORY) == FILE_LIST_DIRECTORY && (mask & FILE_READ_EA) == FILE_READ_EA ) ret = true; else { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES, _("Error listing dir '%s': disallowed under Windows ACL."), path); } g_debug ("Access Mask hex for '%s': 0x%lX", path, mask); } AuthzFreeContext (authz_ctxt); traverse_getacl_fail: LocalFree (sec_desc); AuthzFreeResourceManager (authz_manager); traverse_fail: g_free (errmsg); g_free (wpath); return ret; } /*! * Initialize console handle under Windows * * Used only when output is Windows native console. For all other cases * unix-style file stream is used. */ bool init_wincon_handle (bool is_stdout) { HANDLE h; DWORD console_type; if (is_stdout) h = GetStdHandle (STD_OUTPUT_HANDLE); else h = GetStdHandle (STD_ERROR_HANDLE); console_type = GetFileType (h); /* * FILE_TYPE_CHAR only happens when output is a native Windows * command prompt or powershell 5.x. This is true even for latest * Windows 10 / Server, despite console revamp since 1809. Those * need to be dealt with using wide char API. * * For most 3rd party terminals and for output redirection (file * or pipe), GetFileType() would return FILE_TYPE_PIPE, which * handles UTF-8 properly. Powershell core also supports UTF-8 by * default. */ switch (console_type) { case FILE_TYPE_CHAR: g_debug ("GetFileType() = %s", "FILE_TYPE_CHAR"); if (is_stdout) wincon_fh = h; else winerr_fh = h; return true; case FILE_TYPE_PIPE: g_debug ("GetFileType() = %s", "FILE_TYPE_PIPE"); return false; default: g_debug ("GetFileType() = %lu", console_type); return false; } } void puts_wincon (bool is_stdout, const wchar_t *wstr) { HANDLE h = is_stdout ? wincon_fh : winerr_fh; g_return_if_fail (wstr != NULL); g_return_if_fail (h != NULL); WriteConsoleW (h, wstr, wcslen (wstr), NULL, NULL); } static void _close_wincon_handles (void) { if (wincon_fh != NULL) CloseHandle (wincon_fh); if (winerr_fh != NULL) CloseHandle (winerr_fh); return; } void cleanup_windows_res (void) { _close_wincon_handles (); g_free (sid); } rifiuti2-0.8.2/src/utils.c000066400000000000000000001207021477543443600153740ustar00rootroot00000000000000/* * Copyright (C) 2007-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include "config.h" #include #include #include "utils-conv.h" #include "utils-error.h" #include "utils-io.h" #include "utils.h" #include "utils-platform.h" /* Our own error domain */ G_DEFINE_QUARK (rifiuti-fatal-error-quark, rifiuti_fatal_error) G_DEFINE_QUARK (rifiuti-record-error-quark, rifiuti_record_error) /* Common function signature for option callbacks */ #define DECL_OPT_CALLBACK(func) \ static gboolean func ( \ const gchar *opt_name, \ const gchar *value, \ gpointer data, \ GError **error) DECL_OPT_CALLBACK(_check_legacy_encoding); DECL_OPT_CALLBACK(_set_output_path); DECL_OPT_CALLBACK(_option_deprecated); DECL_OPT_CALLBACK(_set_opt_delim); DECL_OPT_CALLBACK(_set_opt_noheading); DECL_OPT_CALLBACK(_set_opt_format); DECL_OPT_CALLBACK(_show_ver_and_exit); /* pre-declared out of laziness */ static int _check_file_args (const char *path, GPtrArray *list, rbin_type type, bool *isolated_index, GError **error); /** * @brief More detailed OS version guess from artifacts * @note This is different from `detected_os_ver`, which only checks for * first few bytes. It is a more detailed breakdown, and for detection of * exact Windows version from various recycle bin artifacts. * @warning MUST match order of `os_strings` array except * the `UNKNOWN` entry */ typedef enum { OS_GUESS_UNKNOWN = -1, OS_GUESS_95, OS_GUESS_NT4, OS_GUESS_98, OS_GUESS_ME, OS_GUESS_2K, OS_GUESS_XP_03, OS_GUESS_2K_03, /* Empty recycle bin, full detection impossible */ OS_GUESS_VISTA, /* includes everything up to 8.1 */ OS_GUESS_10 } _os_guess; /** * @brief Outputed string for OS detection from artifacts * @warning MUST match order of `_os_guess` enum */ static char *os_strings[] = { N_("Windows 95"), N_("Windows NT 4.0"), N_("Windows 98"), N_("Windows ME"), N_("Windows 2000"), N_("Windows XP or 2003"), N_("Windows 2000, XP or 2003"), N_("Windows Vista - 8.1"), N_("Windows 10 or above") }; static out_fmt output_format = FORMAT_UNKNOWN; static bool no_heading = false; static gboolean use_localtime = FALSE; static gboolean live_mode = FALSE; static char *delim = NULL; static char *output_loc = NULL; static char **fileargs = NULL; GPtrArray *allidxfiles = NULL; bool isolated_index = false; char *legacy_encoding = NULL; /*!< INFO2 only, or upon request */ metarecord *meta = NULL; /* Options controlling output format */ static const GOptionEntry out_options[] = { { "delimiter", 't', 0, G_OPTION_ARG_CALLBACK, _set_opt_delim, N_("Field delimiter for TSV ['\\t' (TAB) if not given]"), N_("STRING") }, { "no-heading", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, _set_opt_noheading, N_("Don't show TSV column header and metadata"), NULL }, { "xml", 'x', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, _option_deprecated, N_("Deprecated, use '-f xml' in future"), NULL }, { "format", 'f', 0, G_OPTION_ARG_CALLBACK, _set_opt_format, N_("'text' (default), 'xml' or 'json'"), N_("FORMAT") }, { 0 } }; /* Global options for program */ static const GOptionEntry main_options[] = { { "output", 'o', 0, G_OPTION_ARG_CALLBACK, _set_output_path, N_("Write output to FILE"), N_("FILE") }, { "localtime", 'z', 0, G_OPTION_ARG_NONE, &use_localtime, N_("Present deletion time in time zone of local system (default is UTC)"), NULL }, { "version", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, _show_ver_and_exit, N_("Print version information and exit"), NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &fileargs, N_("INFO2 file name"), NULL }, { 0 } }; /* Options only intended for INFO2 reader */ static const GOptionEntry rbinfile_options[] = { { "legacy-filename", 'l', 0, G_OPTION_ARG_CALLBACK, _check_legacy_encoding, N_("Show legacy (8.3) path if available and specify its CODEPAGE"), N_("CODEPAGE") }, { 0 } }; /* Options only intended for live system probation */ static const GOptionEntry live_options[] = { { "live", 0, 0, G_OPTION_ARG_NONE, &live_mode, N_("Inspect live system"), NULL }, { 0 } }; /* Following routines are command argument handling related */ static gboolean _set_out_format (out_fmt desired_format, GError **error) { extern struct _fmt_data fmt[]; if (output_format == desired_format) return TRUE; if (output_format == FORMAT_UNKNOWN) { output_format = desired_format; return TRUE; } g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Output was already set in %s, but later argument " "attempts to change to %s", fmt[output_format].friendly_name, fmt[desired_format].friendly_name); return FALSE; } static gboolean _set_opt_format (const gchar *opt_name, const gchar *format, gpointer data, GError **error) { UNUSED(opt_name); UNUSED(data); if (g_strcmp0 (format, "text") == 0) return _set_out_format (FORMAT_TEXT, error); else if (g_strcmp0 (format, "tsv") == 0) // aliases return _set_out_format (FORMAT_TEXT, error); else if (g_strcmp0 (format, "csv") == 0) return _set_out_format (FORMAT_TEXT, error); else if (g_strcmp0 (format, "xml") == 0) return _set_out_format (FORMAT_XML, error); else if (g_strcmp0 (format, "json") == 0) return _set_out_format (FORMAT_JSON, error); else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Illegal output format '%s'", format); return FALSE; } } /** * @brief Option callback for setting TSV header visibility * @return `FALSE` if option conflict exists, `TRUE` otherwise */ static gboolean _set_opt_noheading (const gchar *opt_name, const gchar *value, gpointer data, GError **error) { UNUSED(opt_name); UNUSED(value); UNUSED(data); no_heading = true; return _set_out_format (FORMAT_TEXT, error); } /** * @brief Option callback for setting field delimiter in TSV output * @return `FALSE` if duplicate options are found, `TRUE` otherwise */ static gboolean _set_opt_delim (const gchar *opt_name, const gchar *value, gpointer data, GError **error) { UNUSED(opt_name); UNUSED(data); static bool seen = false; if (seen) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("Multiple delimiter options disallowed.")); return FALSE; } seen = true; delim = (*value) ? filter_escapes (value) : g_strdup (""); return _set_out_format (FORMAT_TEXT, error); } /** * @brief Option callback to set output file location * @return `FALSE` if duplicate options are found, or * output file location already exists. `TRUE` otherwise. */ static gboolean _set_output_path (const gchar *opt_name, const gchar *value, gpointer data, GError **error) { UNUSED(opt_name); UNUSED(data); static bool seen = false; if (seen) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("Multiple output destinations disallowed.")); return FALSE; } seen = true; if ( *value == '\0' ) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("Empty output filename disallowed.")); return FALSE; } if (g_file_test (value, G_FILE_TEST_EXISTS)) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("Output destinations already exists.")); return FALSE; } output_loc = g_strdup (value); return TRUE; } /** * @brief Emits warning when an argument is marked as deprecated * @return Always `TRUE` */ static gboolean _option_deprecated (const gchar *opt_name, const gchar *value, gpointer data, GError **error) { UNUSED(value); UNUSED(data); if (strcmp (opt_name, "-x") == 0 || strcmp (opt_name, "--xml") == 0) { g_warning(_("Option '%s' is deprecated. Use '-f xml' in future."), opt_name); return _set_out_format (FORMAT_XML, error); } return TRUE; } /** * @brief Check if supplied legacy ANSI code page is valid * @return `TRUE` if supplied code page is usable, `FALSE` otherwise. * @note Code page is not validated against actual recycle bin record. */ static gboolean _check_legacy_encoding (const gchar *opt_name, const gchar *enc, gpointer data, GError **error) { UNUSED(opt_name); UNUSED(data); static bool seen = false; GError *conv_err = NULL; if (seen) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("Multiple encoding options disallowed.")); return FALSE; } seen = true; if ( *enc == '\0' ) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("Empty encoding option disallowed.")); return FALSE; } if (enc_is_ascii_compatible (enc, &conv_err)) { legacy_encoding = g_strdup (enc); return TRUE; } /* everything below is error handling */ if (g_error_matches (conv_err, G_CONVERT_ERROR, G_CONVERT_ERROR_NO_CONVERSION)) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("'%s' encoding is not supported by glib library " "on this system. If iconv program is present on " "system, use 'iconv -l' for a list of possible " "alternatives; otherwise check out following site for " "a list of probable encodings to use:\n\n\t%s"), enc, #ifdef G_OS_WIN32 "https://github.com/win-iconv/win-iconv/blob/master/win_iconv.c" #else "https://www.gnu.org/software/libiconv/" #endif ); } else if ( g_error_matches (conv_err, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE) || g_error_matches (conv_err, G_CONVERT_ERROR, G_CONVERT_ERROR_PARTIAL_INPUT) ) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("'%s' is incompatible to any Windows code page."), enc); } else g_assert_not_reached (); g_clear_error (&conv_err); return FALSE; } /** * @brief Print program version with some text, then exit */ static gboolean _show_ver_and_exit (const gchar *opt_name, const gchar *value, gpointer data, GError **error) { UNUSED (opt_name); UNUSED (value); UNUSED (data); UNUSED (error); g_print ("%s %s\n", PROJECT_NAME, PROJECT_VERSION); g_print ("%s\n\n", PROJECT_DESCRIPTION); /* TRANSLATOR COMMENT: %s is software name */ g_print (_("%s is released under Revised BSD License.\n"), PROJECT_NAME); /* TRANSLATOR COMMENT: 1st argument is software name, 2nd is official URL */ g_print (_("More information can be found on\n\n\t%s\n"), PROJECT_HOMEPAGE_URL); // OK I cheated, it is not returning at all. exit (EXIT_OK); } /** * @brief File argument check callback, after handling all arguments * @return `TRUE` if a unique file argument is used under common scenario, * or no file argument is provided in live mode. `FALSE` otherwise. */ static gboolean _fileargs_handler (GOptionContext *context, GOptionGroup *group, metarecord *meta, GError **error) { UNUSED (context); UNUSED (group); gsize fileargs_len = fileargs ? g_strv_length (fileargs) : 0; if (!live_mode) { if (fileargs_len != 1) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("Must specify exactly one file or folder argument.")); return FALSE; } meta->filename = g_strdup (fileargs[0]); return _check_file_args (meta->filename, allidxfiles, meta->type, &isolated_index, error); } if (fileargs_len) { g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, _("Live system probation must not be used together " "with file arguments.")); return FALSE; } #if (defined G_OS_WIN32 || defined __linux__) { meta->filename = g_strdup ("(current system)"); GPtrArray *bindirs = enumerate_drive_bins (error); if (!bindirs) { char *reason = g_strdup ((*error)->message); g_clear_error (error); g_set_error (error, R2_FATAL_ERROR, R2_FATAL_ERROR_LIVE_UNSUPPORTED, _("Live probation unsupported under this system; " "requires running under Windows or WSL distribution.\n" "Failure reason: %s"), reason); g_free (reason); return FALSE; } for (gsize i = 0; i < bindirs->len; i++) { // Ignore errors, pretty common that some folders don't // exist or are empty. _check_file_args ((const char *)(bindirs->pdata[i]), allidxfiles, meta->type, NULL, NULL); } g_ptr_array_free (bindirs, TRUE); } #endif return TRUE; } /** * @brief post-callback after handling all output related args * @return Always `TRUE`, denoting success. It never fails. */ static gboolean _set_def_output_opts (GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { UNUSED (context); UNUSED (group); UNUSED (data); UNUSED (error); /* Fallback values after successful option parsing */ if (delim == NULL) delim = g_strdup ("\t"); if (output_format == FORMAT_UNKNOWN) output_format = FORMAT_TEXT; return TRUE; } /** * @brief Converts Windows FILETIME number to glib counterpart * @param win_filetime The FILETIME integer to be converted * @return `GDateTime` with UTC timezone */ GDateTime * win_filetime_to_gdatetime (int64_t win_filetime) { int64_t t; /* Let's assume we don't need subsecond time resolution */ t = (win_filetime - 116444736000000000LL) / 10000000; g_debug ("FileTime -> Epoch: %" PRId64 " -> %" PRId64, win_filetime, t); return g_date_time_new_from_unix_utc (t); } /** * @brief Prepare for glib option group setup * @param context Pointer to option context to be modified * @param type Recycle bin type; some options may or may not be available depending on type * @param meta Pointer to metadata structure */ static void _opt_ctxt_setup (GOptionContext **context, rbin_type type) { char *desc_str; GOptionGroup *main_group, *output_group; /* FIXME Sneaky metadata modification! Think about cleaner way */ meta->type = type; desc_str = g_strdup_printf ( _("Usage help: %s\nBug report: %s\nMore info : %s"), PROJECT_TOOL_USAGE_URL, PROJECT_BUG_REPORT_URL, PROJECT_GH_PAGE); g_option_context_set_description (*context, desc_str); g_free (desc_str); /* main group */ main_group = g_option_group_new (NULL, NULL, NULL, meta, NULL); g_option_group_add_entries (main_group, main_options); switch (type) { case RECYCLE_BIN_TYPE_FILE: g_option_group_add_entries (main_group, rbinfile_options); break; case RECYCLE_BIN_TYPE_DIR: #if (defined G_OS_WIN32 || defined __linux__) g_option_group_add_entries (main_group, live_options); #else UNUSED (live_options); #endif break; default: break; } g_option_group_set_parse_hooks (main_group, NULL, (GOptionParseFunc) _fileargs_handler); g_option_context_set_main_group (*context, main_group); /* output format arg group */ output_group = g_option_group_new ("format", _("Output format options:"), N_("Show output formatting options"), NULL, NULL); g_option_group_add_entries (output_group, out_options); g_option_group_set_parse_hooks ( output_group, NULL, _set_def_output_opts); g_option_context_add_group (*context, output_group); g_option_context_set_help_enabled (*context, TRUE); } /** * @brief Process command line arguments * @param context Reference of option context pointer * @param argv Reference of command line `argv` * @param error Reference of `GError` pointer to store errors * @return `TRUE` if argument parsing succeeds. * `FALSE` on failure, and sets `error` as well. */ static bool _opt_ctxt_parse (GOptionContext **context, char ***argv, GError **error) { gsize argc; char **argv_u8; #ifdef G_OS_WIN32 argv_u8 = g_win32_get_command_line (); UNUSED (argv); #else argv_u8 = g_strdupv (*argv); #endif argc = g_strv_length (argv_u8); if (argc == 1) { argv_u8 = g_realloc_n (argv_u8, argc + 2, sizeof(gpointer)); argv_u8[argc++] = "--help-all"; argv_u8[argc] = (void *) NULL; #ifdef G_OS_WIN32 g_set_print_handler (gui_message); #endif } { char *args_str = g_strjoinv("|", argv_u8); g_debug("Calling argv_u8 (%zu): %s", argc, args_str); g_free(args_str); } g_option_context_parse_strv (*context, &argv_u8, error); g_option_context_free (*context); g_strfreev (argv_u8); return (*error == NULL); } /** * @brief Free all fields used in a single recycle bin record * @param record Pointer to the record structure */ static void _free_record_cb (rbin_struct *record) { g_free (record->index_s); g_date_time_unref (record->deltime); if (record->raw_uni_path) g_string_free (record->raw_uni_path, TRUE); if (record->raw_legacy_path) g_string_free (record->raw_legacy_path, TRUE); g_clear_error (&record->error); g_free (record); } /** * @brief Initialize program setup */ bool rifiuti_init (rbin_type type, char *usage_param, char *usage_summary, char ***argv, GError **error) { GOptionContext *context; setlocale (LC_ALL, ""); init_handles (); /* Initialize metadata struct */ meta = g_malloc0 (sizeof (metarecord)); meta->records = g_ptr_array_new (); g_ptr_array_set_free_func (meta->records, (GDestroyNotify) _free_record_cb); meta->invalid_records = g_hash_table_new_full ( g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_error_free ); // Other global structures allidxfiles = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); /* Parse command line arguments and generate help */ context = g_option_context_new (usage_param); g_option_context_set_summary (context, usage_summary); _opt_ctxt_setup (&context, type); return _opt_ctxt_parse (&context, argv, error); } /** * @brief Scan folder and add all index files for parsing * @param list Pointer to file list to be modified * @param path The folder to scan * @param error Pointer to `GError` for error reporting * @return `TRUE` on success, `FALSE` if folder can't be opened */ static bool _populate_index_file_list (GPtrArray *list, const char *path, GError **error) { GDir *dir; const char *direntry; GPatternSpec *pattern1, *pattern2; // g_dir_open() returns cryptic error message or even succeeds on Windows, // when in fact the directory content is inaccessible. #ifdef G_OS_WIN32 if ( !can_list_win32_folder (path, error) ) { return false; } #endif if (NULL == (dir = g_dir_open (path, 0, error))) return false; pattern1 = g_pattern_spec_new ("$I??????.*"); pattern2 = g_pattern_spec_new ("$I??????"); while ((direntry = g_dir_read_name (dir)) != NULL) { #if GLIB_CHECK_VERSION (2, 70, 0) if (!g_pattern_spec_match_string (pattern1, direntry) && !g_pattern_spec_match_string (pattern2, direntry)) continue; #else /* glib < 2.70 */ if (!g_pattern_match_string (pattern1, direntry) && !g_pattern_match_string (pattern2, direntry)) continue; #endif g_ptr_array_add (list, g_build_filename (path, direntry, NULL)); } g_dir_close (dir); g_pattern_spec_free (pattern1); g_pattern_spec_free (pattern2); return true; } /** * @brief Search for desktop.ini in folder for hint of recycle bin * @param path The searched path * @return `TRUE` if `desktop.ini` found to contain recycle bin * identifier, `FALSE` otherwise */ static bool _found_desktop_ini (const char *path) { char *filename = NULL, *content = NULL, *found = NULL; filename = g_build_filename (path, "desktop.ini", NULL); if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { g_free (filename); return false; } if (g_file_get_contents (filename, &content, NULL, NULL)) /* Don't bother parsing, we don't use the content at all */ found = strstr (content, RECYCLE_BIN_CLSID); g_free (content); g_free (filename); return (found != NULL); } /** * @brief Guess Windows version which generated recycle bin index file * @param meta Pointer to metadata structure * @return Enum constant representing approximate Windows version range */ static _os_guess _guess_windows_ver (const metarecord *meta) { if (meta->type == RECYCLE_BIN_TYPE_DIR) { /* * No attempt is made to distinguish difference for Vista - 8.1. * The corrupt filesize artifact on Vista can't be reproduced, * therefore must be very rare. */ switch (meta->version) { case VERSION_VISTA: return OS_GUESS_VISTA; case VERSION_WIN10: return OS_GUESS_10; default: return OS_GUESS_UNKNOWN; } } /* INFO2 only below */ switch (meta->version) { case VERSION_WIN95: return OS_GUESS_95; case VERSION_WIN98: return OS_GUESS_98; case VERSION_NT4 : return OS_GUESS_NT4; case VERSION_ME_03: /* TODO use symbolic name when 2 versions are merged */ if (meta->recordsize == 280) return OS_GUESS_ME; if (meta->records->len == 0) return OS_GUESS_2K_03; return meta->fill_junk ? OS_GUESS_2K : OS_GUESS_XP_03; /* Not using OS_GUESS_UNKNOWN, INFO2 ceased to be used so detection logic won't change in future */ default: g_assert_not_reached(); } // Should be impossible to reach here, but for shutting up // diagnostics warning... return OS_GUESS_UNKNOWN; } /** * @brief Add potentially valid file(s) to list * @param path The file or folder to be checked * @param list A `GPtrArray` to store potential index files * @param type Recycle bin type * @param isolated_index Pointer to `gboolean`, indicating whether * the concerned `path` is a single `$Recycle.bin` type index * taken out of its original folder. Can be `NULL`, which means * this check is not performed. * @param error A `GError` pointer to store potential problems * @return `TRUE` if input file/dir is valid, `FALSE` otherwise * @attention Successful result does not imply files are appended * to list, which is the case for empty recycle bin */ static gboolean _check_file_args (const char *path, GPtrArray *list, rbin_type type, bool *isolated_index, GError **error) { g_debug ("Start checking path '%s'...", path); g_return_val_if_fail (path != NULL, FALSE); g_return_val_if_fail (list != NULL, FALSE); if (!g_file_test (path, G_FILE_TEST_EXISTS)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, _("'%s' does not exist."), path); return FALSE; } if ((type == RECYCLE_BIN_TYPE_DIR) && g_file_test (path, G_FILE_TEST_IS_DIR)) { if ( ! _populate_index_file_list (list, path, error) ) return FALSE; /* * last ditch effort: search for desktop.ini. Just print empty content * representing empty recycle bin if found. */ if (list->len == 0 && ! _found_desktop_ini (path)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, _("No files with name pattern '%s' " "are found in directory."), "$Ixxxxxx.*"); return FALSE; } } else if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) { if (isolated_index && (type == RECYCLE_BIN_TYPE_DIR)) { char *parent_dir = g_path_get_dirname (path); *isolated_index = ! _found_desktop_ini (parent_dir); g_free (parent_dir); } g_ptr_array_add (list, g_strdup (path)); } else { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, (type == RECYCLE_BIN_TYPE_DIR) ? _("'%s' is not a normal file or directory.") : _("'%s' is not a normal file."), path); return FALSE; } return TRUE; } void do_parse_records (ParseIdxFunc func) { g_ptr_array_foreach (allidxfiles, (GFunc) func, meta); } /** * @brief Print preamble and column header for TSV output * @param meta Pointer to metadata structure */ static void _print_text_header (const metarecord *meta) { { char *rbin_path = g_filename_display_name (meta->filename); g_print (_("Recycle bin path: '%s'\n"), rbin_path); g_free (rbin_path); } if (meta->version == VERSION_NOT_FOUND) { g_print ("%s\n", _("Version: ??? (empty folder)")); } else { g_print (_("Version: %" PRIu64 "\n"), meta->version); } if (( meta->type == RECYCLE_BIN_TYPE_FILE ) && meta->total_entry) { g_print (_("Total entries ever existed: %d"), meta->total_entry); g_print ("\n"); } #if (defined G_OS_WIN32 || defined __linux__) if (live_mode) { char *product_name = windows_product_name(); if (product_name) { g_print (_("OS: %s"), product_name); g_free (product_name); } else { g_print ("%s", _("OS detection failed")); } } else #endif { _os_guess g = _guess_windows_ver (meta); if (g == OS_GUESS_UNKNOWN) g_print ("%s", _("OS detection failed")); else g_print (_("OS Guess: %s"), gettext (os_strings[g]) ); } g_print ("\n"); // Deletion time for each entry may or may not be under DST. // Results have not been verified. { GDateTime *now; char *tzname = NULL, *tznumeric = NULL; now = use_localtime ? g_date_time_new_now_local (): g_date_time_new_now_utc (); #ifdef G_OS_WIN32 if (use_localtime) tzname = get_win_timezone_name (); #endif if (tzname == NULL) tzname = g_date_time_format (now, "%Z"); tznumeric = g_date_time_format (now, "%z"); g_print (_("Time zone: %s [%s]"), tzname, tznumeric); g_print ("\n"); g_date_time_unref (now); g_free (tzname); g_free (tznumeric); } g_print ("\n"); { char *fields[] = { /* TRANSLATOR COMMENT: appears in column header */ N_("Index"), N_("Deleted Time"), N_("Gone?"), N_("Size"), N_("Path"), NULL }; char *headerline = g_strjoinv (delim, fields); g_print ("%s\n", headerline); g_free (headerline); } } /** * @brief Print preamble for XML output * @param meta Pointer to metadata structure */ static void _print_xml_header (const metarecord *meta) { GString *result; result = g_string_new ("\n"); g_string_append_printf (result, "type == RECYCLE_BIN_TYPE_FILE ) ? "file" : "dir"); if (meta->version >= 0) /* can be found and not error */ g_string_append_printf (result, " version=\"%" PRId64 "\"", meta->version); if (meta->type == RECYCLE_BIN_TYPE_FILE && meta->total_entry > 0) g_string_append_printf (result, " ever_existed=\"%" PRIu32 "\"", meta->total_entry); result = g_string_append (result, ">\n"); { char *rbin_path = g_filename_display_name (meta->filename); g_string_append_printf (result, " \n", rbin_path); g_free (rbin_path); } g_print ("%s", result->str); g_string_free (result, TRUE); } /** * @brief Print preamble for JSON output * @param meta Pointer to metadata structure */ static void _print_json_header (const metarecord *meta) { g_print ("{\n \"format\": \"%s\",\n", (meta->type == RECYCLE_BIN_TYPE_FILE) ? "file" : "dir"); if (meta->version >= 0) /* can be found and not error */ g_print (" \"version\": %" PRId64 ",\n", meta->version); else g_print (" \"version\": null,\n"); if (meta->type == RECYCLE_BIN_TYPE_FILE && meta->total_entry > 0) g_print (" \"ever_existed\": %" PRIu32 ",\n", meta->total_entry); { char *s = g_filename_display_name (meta->filename); char *rbin_path = json_escape (s); g_print (" \"path\": \"%s\",\n", rbin_path); g_free (s); g_free (rbin_path); } g_print (" \"records\": [\n"); } static void _print_text_record (rbin_struct *record, const metarecord *meta) { char *output, **header; GString *src; GDateTime *dt; extern struct _fmt_data fmt[]; g_return_if_fail (record != NULL); header = (char **) g_malloc0_n (6, sizeof(gpointer)); header[0] = (meta->type == RECYCLE_BIN_TYPE_FILE) ? g_strdup_printf ("%" PRIu32, record->index_n) : g_strdup (record->index_s); dt = use_localtime ? g_date_time_to_local (record->deltime): g_date_time_ref (record->deltime); header[1] = g_date_time_format (dt, "%F %T"); header[2] = g_strdup(fmt[FORMAT_TEXT].gone_outtext[record->gone]); header[3] = (record->filesize == G_MAXUINT64) ? // faulty g_strdup ("???") : g_strdup_printf ("%" PRIu64, record->filesize); src = legacy_encoding ? record->raw_legacy_path : record->raw_uni_path ; header[4] = conv_path_to_utf8_with_tmpl (src, legacy_encoding, FORMAT_TEXT, NULL, &record->error); if (! header[4]) header[4] = g_strdup ("???"); output = g_strjoinv (delim, header); g_print ("%s\n", output); g_free (output); g_date_time_unref (dt); g_strfreev (header); } static void _print_xml_record (rbin_struct *record, const metarecord *meta) { extern struct _fmt_data fmt[]; char *path, *dt_str; GDateTime *dt; GString *s, *src; g_return_if_fail (record != NULL); s = g_string_new (" type == RECYCLE_BIN_TYPE_FILE) g_string_append_printf (s, " index=\"%" PRIu32 "\"", record->index_n); else g_string_append_printf (s, " index=\"%s\"", record->index_s); if (use_localtime) { dt = g_date_time_to_local (record->deltime); dt_str = g_date_time_format (dt, "%FT%T%z"); } else { dt = g_date_time_ref (record->deltime); dt_str = g_date_time_format (dt, "%FT%TZ"); } g_string_append_printf (s, " time=\"%s\"", dt_str); g_string_append_printf (s, " gone=\"%s\"", fmt[FORMAT_XML].gone_outtext[record->gone]); if (record->filesize == G_MAXUINT64) // faulty g_string_append_printf (s, " size=\"-1\""); else g_string_append_printf (s, " size=\"%" PRIu64 "\"", record->filesize); // Still need to be converted despite using CDATA, // otherwise could be writing garbage output src = legacy_encoding ? record->raw_legacy_path : record->raw_uni_path ; path = conv_path_to_utf8_with_tmpl (src, legacy_encoding, FORMAT_XML, NULL, &record->error); if (path) g_string_append_printf (s, ">\n" " \n" " \n", path); else s = g_string_append (s, ">\n \n \n"); g_print ("%s", s->str); g_string_free (s, TRUE); g_date_time_unref (dt); g_free (path); g_free (dt_str); } static void _print_json_record (rbin_struct *record, const metarecord *meta) { extern struct _fmt_data fmt[]; char *path, *dt_str; GDateTime *dt; GString *src, *s; g_return_if_fail (record != NULL); s = g_string_new (" {"); if (meta->type == RECYCLE_BIN_TYPE_FILE) g_string_append_printf (s, "\"index\": %" PRIu32, record->index_n); else g_string_append_printf (s, "\"index\": \"%s\"", record->index_s); if (use_localtime) { dt = g_date_time_to_local (record->deltime); dt_str = g_date_time_format (dt, "%FT%T%z"); } else { dt = g_date_time_ref (record->deltime); dt_str = g_date_time_format (dt, "%FT%TZ"); } g_string_append_printf (s, ", \"time\": \"%s\"", dt_str); g_string_append_printf (s, ", \"gone\": %s", fmt[FORMAT_JSON].gone_outtext[record->gone]); if (record->filesize == G_MAXUINT64) // faulty g_string_append_printf (s, ", \"size\": null"); else g_string_append_printf (s, ", \"size\": %" PRIu64, record->filesize); src = legacy_encoding ? record->raw_legacy_path : record->raw_uni_path ; path = conv_path_to_utf8_with_tmpl (src, legacy_encoding, FORMAT_JSON, &json_escape, &record->error); if (path) g_string_append_printf (s, ", \"path\": \"%s\"},\n", path); else s = g_string_append (s, ", \"path\": null},\n"); g_print ("%s", s->str); g_date_time_unref (dt); g_free (path); g_free (dt_str); g_string_free (s, TRUE); } static void _print_xml_footer (void) { g_print ("%s", "\n"); } static void _print_json_footer (void) { g_print (" ]\n}\n"); } /** * @brief Dump all results to screen or designated output file * @param error Reference of `GError` pointer to store potential problem * @return `TRUE` if output writing is successful, `FALSE` otherwise */ bool dump_content (GError **error) { void (*print_header_func)(const metarecord *); void (*print_record_func)(rbin_struct *, const metarecord *); void (*print_footer_func)(); // TODO use g_file_set_contents_full in glib 2.66 if (output_loc && ! get_tempfile (error)) return false; switch (output_format) { case FORMAT_TEXT: print_header_func = no_heading ? NULL : &_print_text_header; print_record_func = &_print_text_record; print_footer_func = NULL; break; case FORMAT_XML: print_header_func = &_print_xml_header; print_record_func = &_print_xml_record; print_footer_func = &_print_xml_footer; break; case FORMAT_JSON: print_header_func = &_print_json_header; print_record_func = &_print_json_record; print_footer_func = &_print_json_footer; break; default: g_assert_not_reached(); } if (print_header_func != NULL) (*print_header_func) (meta); g_ptr_array_foreach (meta->records, (GFunc) print_record_func, meta); if (print_footer_func != NULL) (*print_footer_func) (); if (output_loc) return clean_tempfile (output_loc, error); else return true; } static void _dump_rec_error (rbin_struct *record, bool *flag) { g_return_if_fail (record); if (! record->error) return; if (! *flag) { *flag = true; g_printerr ("\n%s\n", _("Error occurred in following record:")); } if (record->index_n) g_printerr ("%2u: %s\n", record->index_n, record->error->message); else g_printerr ("%s: %s\n", record->index_s, record->error->message); return; } /** * @brief Handle global and record errors before quitting * @param error The global `GError` to process * @return program exit code */ exitcode _get_exit_code (const GError *error) { exitcode code = EXIT_OK; if (error == NULL) return code; g_printerr ("Fatal error: %s\n", error->message); if (error->domain == G_OPTION_ERROR) code = EXIT_ERR_ARG; else if (error->domain == G_FILE_ERROR) code = EXIT_ERR_OPEN_FILE; else if (g_error_matches (error, R2_FATAL_ERROR, R2_FATAL_ERROR_ILLEGAL_DATA)) code = EXIT_ERR_ILLEGAL_DATA; else if (g_error_matches (error, R2_FATAL_ERROR, R2_FATAL_ERROR_TEMPFILE)) code = EXIT_ERR_WRITE_FILE; else if (g_error_matches (error, R2_FATAL_ERROR, R2_FATAL_ERROR_LIVE_UNSUPPORTED)) code = EXIT_ERR_NO_LIVE; else { g_critical ("Error not handled: quark = %s, code = %d", g_quark_to_string (error->domain), error->code); code = EXIT_ERR_UNHANDLED; } return code; } bool _has_record_error (void) { bool flag = false; // Determine occasion to print headline GHashTableIter iter; gpointer key, val; if (g_hash_table_size (meta->invalid_records)) { flag = true; g_hash_table_iter_init (&iter, meta->invalid_records); g_printerr ("%s\n", _("Error occurred in following record:")); while (g_hash_table_iter_next (&iter, &key, &val)) { char *record_id = (char *) key; if (*record_id == '|') { char **frags = g_strsplit (record_id, "|", 0); record_id = g_strdup_printf ("byte range %s - %s", frags[1], frags[2]); g_strfreev (frags); } else record_id = g_strdup (record_id); g_printerr ("%s: %s\n", record_id, ((GError *)val)->message); g_free (record_id); } } g_ptr_array_foreach (meta->records, (GFunc) _dump_rec_error, &flag); return flag; } /** * @brief Dump error and perform final cleanup * @param error The global `GError` to process * @return program exit code */ exitcode rifiuti_cleanup (GError **error) { exitcode code = EXIT_OK; g_return_val_if_fail (error != NULL, EXIT_ERR_UNHANDLED); code = _get_exit_code ((const GError *) (*error)); g_clear_error (error); if (_has_record_error () && code == EXIT_OK) code = EXIT_ERR_DUBIOUS_DATA; g_debug ("Final cleanup..."); g_ptr_array_unref (meta->records); g_hash_table_destroy (meta->invalid_records); g_free (meta->filename); g_free (meta); g_ptr_array_free (allidxfiles, TRUE); g_strfreev (fileargs); g_free (output_loc); g_free (legacy_encoding); g_free (delim); close_handles (); #ifdef G_OS_WIN32 cleanup_windows_res (); #endif return code; } void hexdump (void *start, size_t size) { GString *s = g_string_new (""); size_t i = 0; while (true) { if (i % 16 == 0) { if (s->len > 0) { g_debug ("%s", s->str); s = g_string_assign (s, ""); } g_string_append_printf (s, "%04zX ", i); } if (i >= size) break; g_string_append_printf (s, "%02" PRIX8 " ", *(uint8_t *) (start+i)); i++; } g_string_free (s, TRUE); } rifiuti2-0.8.2/src/utils.h000066400000000000000000000162551477543443600154100ustar00rootroot00000000000000/* * Copyright (C) 2007-2024, Abel Cheung. * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #pragma once /* * Rifiuti itself only need _POSIX_C_SOURCE == 1 for usage of * localtime_r(); however glib2's usage of siginfo_t pushes * the requirement further. It's undefined in some Unices. */ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 199309L #endif #include #include #include #include // https://stackoverflow.com/a/3599170 #define UNUSED(x) (void)(x) /* exit status */ typedef enum { EXIT_OK = EXIT_SUCCESS, EXIT_ERR_ARG, /* Command argument parsing error */ EXIT_ERR_OPEN_FILE, /* Fail when searching and opening index file */ EXIT_ERR_WRITE_FILE, /* Error writing output, includes manipulation of temp file */ EXIT_ERR_ILLEGAL_DATA, /* serious file format validation failure */ EXIT_ERR_DUBIOUS_DATA, /* affect some record(s) only */ EXIT_ERR_NO_LIVE, // live mode requested but failed EXIT_ERR_UNHANDLED = 64, } exitcode; typedef enum { RECYCLE_BIN_TYPE_UNKNOWN = 0, RECYCLE_BIN_TYPE_FILE, RECYCLE_BIN_TYPE_DIR, } rbin_type; /* The first 4 or 8 bytes of recycle bin index files */ typedef enum { /* negative number = error */ VERSION_INCONSISTENT = -2, /* Mixed versions in same folder */ VERSION_NOT_FOUND, /* Empty $Recycle.bin */ /* $Recycle.bin */ VERSION_VISTA = 1, VERSION_WIN10, /* INFO / INFO2 */ VERSION_WIN95 = 0, VERSION_NT4 = 2, VERSION_WIN98 = 4, VERSION_ME_03, } detected_os_ver; /** * @brief Whether original trashed file still exists */ typedef enum { FILESTATUS_UNKNOWN = 0, FILESTATUS_EXISTS, FILESTATUS_GONE } trash_file_status; /** * @brief Metadata for recycle bin * @note This is a merge of `INFO2` and `$Recycle.bin` elements. */ typedef struct _rbin_meta { rbin_type type; /* `INFO2` or `$Recycle.bin` format */ char *filename; /* File or dir name of trash can itself */ /** * @brief The global recycle bin version * @note For `INFO2`, the value is stored in certain bytes of `INFO2` index file. * For `$Recycle.bin`, it is determined collectively from all index files within * the folder. */ int64_t version; /** * @brief Size of each trash record within index file * @note It is either 280 or 800 bytes, depending on Windows version * @attention For `INFO2` only. `$Recycle.bin` has only one record per file. */ uint32_t recordsize; /** * @brief Total entry ever existed in `INFO2` file * @note On Windows 95 and NT 4.x, `INFO2` keeps a field for counting number * of trashed entries. The field is unused afterwards. * @attention For `INFO2` only */ uint32_t total_entry; /** * @brief Whether empty spaces in index file was padded with junk data * @note For Windows 98, ME and 2000, paths and fields are not padded with * zero filled memory, but with arbitrary random data, presumably memory * segments due to sloppy programming practice. * @attention For `INFO2` only */ bool fill_junk; /** * @brief List of trash file records pointer */ GPtrArray *records; /** * @brief List of invalid records and their errors */ GHashTable *invalid_records; } metarecord; /** * @brief Structure for single recycle bin item * @note This is a merge of `INFO2` and `$Recycle.bin` elements. */ typedef struct _rbin_struct { /** * @brief version of each index file * @note `meta.version` keeps the global status of whole dir, * while this one keeps individual version of index file. * @attention For `$Recycle.bin` only */ uint64_t version; /** * @brief Chronological index number for INFO2 * @attention For `INFO2` only */ uint32_t index_n; /** * @brief Index file name * @attention For `$Recyle.bin` only */ char *index_s; GDateTime *deltime; /* Item trashing time */ /** * @brief Trashed time (`deltime`) stored as Windows datetime integer * @note For internal entry sorting in `$Recycle.bin`. `INFO2` records sort using `index_n` field. */ int64_t winfiletime; /** * @brief Trashed file size * @note Can mean cluster size or actual file/folder size, * depending on Recycle bin version. Not invertigated * thoroughly yet. */ uint64_t filesize; /** * @brief Original path of trashed file, in unicode * @note Original path was stored in index file in UTF-16 * encoding since Windows 2000. The raw UTF-16 data is * stored here. `GString` structure is chosen for * convenience in storing buffer length, which can't be * easily determined from null termination when path data * is truncated (due to broken file) */ GString *raw_uni_path; /** * @brief Original path of trashed file, in ANSI code page * @note Until Windows 2003, index file preserves trashed file * path in ANSI code page. The raw path is stored here. * @attention For `INFO2` only. Can be either full path or * 8.3 format, depending on Windows version and code page used. */ GString *raw_legacy_path; /** * @brief Whether original trashed file is gone * @note Trash file can be detected if it still exists, but via very * different mechanisms on different formats. For `INFO2`, one can * only deduce if it is either permanently removed, or restored on * filesystem. For `$Recycle.bin`, it is guaranteed to be restored * if `$R...` named trashed file doesn't exist in folder. */ trash_file_status gone; /** * @brief Drive letter for removed trash entry * @note If `INFO2` entry is marked as gone, first letter of original * path is removed and stored elsewhere, which corresponds to drive letter. * @attention For `INFO2` only */ unsigned char drive; /** * @brief Error associated with this trash entry */ GError *error; } rbin_struct; /* convenience macro */ #define copy_field(field, buf, off1, off2) \ memcpy(&(field), (buf) + (off1), (off2) - (off1)) /*! Every Windows use this GUID in recycle bin desktop.ini */ #define RECYCLE_BIN_CLSID "645FF040-5081-101B-9F08-00AA002F954E" typedef void (*ParseIdxFunc) (const char *path, metarecord *meta); /* shared functions */ bool rifiuti_init (rbin_type type, char *usage_param, char *usage_summary, char ***argv, GError **error); GDateTime * win_filetime_to_gdatetime (int64_t win_filetime); bool dump_content (GError **error); exitcode rifiuti_cleanup (GError **error); void hexdump (void *start, size_t size); void do_parse_records (ParseIdxFunc func); rifiuti2-0.8.2/test/000077500000000000000000000000001477543443600142565ustar00rootroot00000000000000rifiuti2-0.8.2/test/CMakeLists.txt000066400000000000000000000172251477543443600170250ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # Shorthands set(sample_dir ${CMAKE_CURRENT_SOURCE_DIR}/samples) set(bindir ${CMAKE_CURRENT_BINARY_DIR}) set( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ) # Required by XML tests find_program(XMLLINT xmllint) # Util functions function(add_test_using_shell name command) if(WIN32) add_test( NAME ${name} COMMAND pwsh -NonInteractive -NoProfile -Command "${command}" ${ARGN}) else() add_test( NAME ${name} COMMAND sh -c "${command}" ${ARGN}) endif() endfunction() function(startswith haystack needle) string(LENGTH ${needle} needle_len) string(SUBSTRING ${haystack} 0 ${needle_len} substr) if(${substr} STREQUAL ${needle}) set(startswith_match 1 PARENT_SCOPE) else() set(startswith_match 0 PARENT_SCOPE) endif() endfunction() function(add_bintype_label testname) foreach(tname ${testname} ${ARGN}) if(NOT TEST ${tname}) message(WARNING "Test name ${tname} does not exist") continue() endif() startsWith(${tname} "f_") if(startswith_match) set(bintype "info2") endif() startsWith(${tname} "d_") if(startswith_match) set(bintype "recycledir") endif() if(NOT DEFINED bintype) message(WARNING "Unable to determine bin type from name ${tname}") continue() endif() get_property(tlabels TEST ${tname} PROPERTY LABELS) list(FIND tlabels ${bintype} pos) if (pos EQUAL -1) list(APPEND tlabels ${bintype}) set_tests_properties(${tname} PROPERTIES LABELS "${tlabels}") endif() endforeach() endfunction() # # Set fixture properties automatically for a fixed test name pattern. # Fixture dependencies are set up automatically too. # Using preparation step with test prefix "myid" as example: # # - "myid_Prep" (mandatory) test must exist beforehand. # - "myid_PrepAlt" (optional) is treated as another independent setup step # - "myid_PrepPre" (optional) is a prerequisite for "myid_Prep" # - "myid_PrepPost" (optional) depends on "myid_Prep" # # Same applies to ${prefix}_Clean steps. # function(set_fixture_with_dep prefix) set(fixture $) set_tests_properties(${prefix} PROPERTIES FIXTURES_REQUIRED ${fixture}) set(prepname ${prefix}_Prep) set_tests_properties(${prepname} PROPERTIES FIXTURES_SETUP ${fixture}) set(prepalt ${prepname}Alt) if(TEST ${prepalt}) set_tests_properties(${prepalt} PROPERTIES FIXTURES_SETUP ${fixture}) endif() set(preppre ${prepname}Pre) if(TEST ${preppre}) set_tests_properties(${preppre} PROPERTIES FIXTURES_SETUP ${fixture}) set_tests_properties(${prepname} PROPERTIES DEPENDS ${preppre}) endif() set(preppost ${prepname}Post) if(TEST ${preppost}) set_tests_properties(${preppost} PROPERTIES FIXTURES_SETUP ${fixture}) set_tests_properties(${preppost} PROPERTIES DEPENDS ${prepname}) endif() set(cleanname ${prefix}_Clean) set_tests_properties(${cleanname} PROPERTIES FIXTURES_CLEANUP ${fixture}) set(cleanalt ${cleanname}Alt) if(TEST ${cleanalt}) set_tests_properties(${cleanalt} PROPERTIES FIXTURES_CLEANUP ${fixture}) endif() set(cleanpre ${cleanname}Pre) if(TEST ${cleanpre}) set_tests_properties(${cleanpre} PROPERTIES FIXTURES_CLEANUP ${fixture}) set_tests_properties(${cleanname} PROPERTIES DEPENDS ${cleanpre}) endif() set(cleanpost ${cleanname}Post) if(TEST ${cleanpost}) set_tests_properties(${cleanpost} PROPERTIES FIXTURES_CLEANUP ${fixture}) set_tests_properties(${cleanpost} PROPERTIES DEPENDS ${cleanname}) endif() endfunction() # # Simplistic comparison involving 3 steps: # 1. Geneate output from test recycle bin file (use -o output, no redirection used) # 2. Compare output with reference test result # 3. Delete output file # # This function create tests with add_test(), fixtures and label properties. Other properties should be added manually. Output file name is detemined automatically from test prefix. Extra arguments are appended to program command line arguments. # # Parameters: # id (string): unique test ID fragment, will be prepended with "f_" or "d_" to form full test prefix name, which is determined by 'is_info2' param below. # is_info2 (bool): See 'id' param. # input (path): Recycle bin file/dir name or full path, to be read by rifiuti2. # - If it is a relative path, add_test() calls would set WORKING_DIRECTORY to sample folder in source dir (i.e. ${sample_dir} above). # - If it is empty or falsy, the whole preparation step is skipped. User is responsible to create their own step BEFORE calling this function (not after, otherwise fixture automation won't work). # Note that the path is reflected in output file content, thus can potentially make tests fail if not careful. # ref (path): Reference test result file name or path. If this is relative path, it is taken as under ${sample_dir}. # label (str): A '|'-separated list of labels, to prevent automatic expansion. The labels are only applied to main test, not other fixture steps. # function(generate_simple_comparison_test id is_info2 input ref labels) if(is_info2) set(prefix f_${id}) set(progname rifiuti) else() set(prefix d_${id}) set(progname rifiuti-vista) endif() set(out ${bindir}/${prefix}.output) set(remnant ${out}) if(NOT IS_ABSOLUTE ${ref}) set(ref ${sample_dir}/${ref}) else() startswith(${ref} ${bindir}) if(startswith_match) list(APPEND remnant ${ref}) endif() endif() if(input) if(IS_ABSOLUTE ${input}) add_test(NAME ${prefix}_Prep COMMAND ${progname} -o ${out} ${ARGN} ${input} COMMAND_EXPAND_LISTS) else() add_test(NAME ${prefix}_Prep COMMAND ${progname} -o ${out} ${ARGN} ${input} WORKING_DIRECTORY ${sample_dir} COMMAND_EXPAND_LISTS) endif() endif() add_test(NAME ${prefix} COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol ${out} ${ref}) add_test(NAME ${prefix}_Clean COMMAND ${CMAKE_COMMAND} -E rm ${remnant} COMMAND_EXPAND_LISTS) set_fixture_with_dep(${prefix}) string(REPLACE "|" ";" labels "${labels}") if(is_info2) list(APPEND labels "info2") else() list(APPEND labels "recycledir") endif() set_tests_properties(${prefix} PROPERTIES LABELS "${labels}") endfunction() # # For some systems, glib may or may not be using system iconv # (e.g. Solaris and FreeBSD), therefore simply finding out # supported encoding from iconv program is not enough. # # In general, there is no cross platform way of specifying # encoding names. To rub salt into wounds, even glib itself # appears to append different encoding aliases on different # OSes... (such as using win_iconv on Windows) # Have to resort to checking runtime behavior instead. # add_executable(test_glib_iconv test_glib_iconv.c) target_include_directories(test_glib_iconv PRIVATE ${GLIB_INCLUDE_DIRS}) target_compile_options (test_glib_iconv PRIVATE ${GLIB_CFLAGS_OTHER}) target_link_libraries (test_glib_iconv PRIVATE ${GLIB_LIBRARIES}) target_link_directories (test_glib_iconv PRIVATE ${GLIB_LIBRARY_DIRS}) # # The real tests # include(cli-option) include(crafted) include(encoding) include(json) include(parse-info2) include(parse-rdir) include(read-write) include(xml) rifiuti2-0.8.2/test/cmake/000077500000000000000000000000001477543443600153365ustar00rootroot00000000000000rifiuti2-0.8.2/test/cmake/_try_encoding.cmake000066400000000000000000000023751477543443600211720ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # # Execute encoding tests as external cmake script # # 1. $CMAKE_CURRENT_BINARY_DIR etc in cmake script aren't # stable; they would change into wherever WORKING_DIRECTORY # is set, so program paths must be supplied externally. # 2. CHOICES (encoding list) is supplied as '|' separated # list, in order to not collide with semicolon handling # in cmake. # string(REPLACE "|" ";" CHOICES "${CHOICES}") list(LENGTH CHOICES len) if(len EQUAL 1) set(encoding ${CHOICES}) else() execute_process( COMMAND ${TEST_GLIB_ICONV} ${CHOICES} RESULT_VARIABLE status OUTPUT_VARIABLE encoding # COMMAND_ECHO STDOUT OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT status EQUAL 0) message(FATAL_ERROR "No suitable encoding found in glib") endif() endif() set(args -l ${encoding} ${INFO2}) if(DEFINED OUTFILE) list(APPEND args -o ${OUTFILE}) endif() if(DEFINED EXTRA_ARGS) string(REPLACE "|" ";" EXTRA_ARGS "${EXTRA_ARGS}") list(APPEND args ${EXTRA_ARGS}) endif() execute_process( COMMAND ${RIFIUTI} ${args} # COMMAND_ECHO STDOUT ERROR_VARIABLE err_result ECHO_ERROR_VARIABLE) rifiuti2-0.8.2/test/cmake/cli-option.cmake000066400000000000000000000164451477543443600204270ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. function(addBareOptTest name) add_test(NAME d_InvokeOpt${name} COMMAND rifiuti-vista ${ARGV1}) add_test(NAME f_InvokeOpt${name} COMMAND rifiuti ${ARGV1}) set_tests_properties(d_InvokeOpt${name} f_InvokeOpt${name} PROPERTIES LABELS "arg") add_bintype_label(d_InvokeOpt${name} f_InvokeOpt${name}) if(NOT DEFINED ARGV1 AND WIN32) set_tests_properties(d_InvokeOpt${name} f_InvokeOpt${name} PROPERTIES DISABLED True) endif() endfunction() addBareOptTest(None ) addBareOptTest(ShortHelp1 -h ) addBareOptTest(ShortHelp2 -? ) addBareOptTest(ShortVer -v ) addBareOptTest(LongHelp --help-all ) addBareOptTest(LongVer --version ) function(addWithFileOptTest name) add_test(NAME d_WithFileOpt${name} COMMAND rifiuti-vista ${ARGN} ${sample_dir}/dir-sample1) add_test(NAME f_WithFileOpt${name} COMMAND rifiuti ${ARGN} ${sample_dir}/INFO2-sample1) set_tests_properties(d_WithFileOpt${name} f_WithFileOpt${name} PROPERTIES LABELS "arg") add_bintype_label(d_WithFileOpt${name} f_WithFileOpt${name}) endfunction() addWithFileOptTest(LongHead --no-heading ) addWithFileOptTest(LongSep --delimiter=: ) addWithFileOptTest(LongTime --localtime ) addWithFileOptTest(LongXml --format xml ) addWithFileOptTest(ShortHead -n ) addWithFileOptTest(ShortSep -t : ) addWithFileOptTest(ShortTime -z ) addWithFileOptTest(ShortXml -f xml ) function(addBadBareOptTest name) add_test(NAME d_BadBareOpt${name} COMMAND rifiuti-vista ${ARGN}) add_test(NAME f_BadBareOpt${name} COMMAND rifiuti ${ARGN}) set_tests_properties(d_BadBareOpt${name} f_BadBareOpt${name} PROPERTIES LABELS "arg;xfail" PASS_REGULAR_EXPRESSION "Unknown option") add_bintype_label(d_BadBareOpt${name} f_BadBareOpt${name}) endfunction() addBadBareOptTest(Short -/) addBadBareOptTest(Long --invalid) function(addDupOptTest name) add_test(NAME d_DupOpt${name} COMMAND rifiuti-vista ${ARGN} ${sample_dir}/dir-sample1) add_test(NAME f_DupOpt${name} COMMAND rifiuti ${ARGN} ${sample_dir}/INFO2-sample1) set_tests_properties(d_DupOpt${name} f_DupOpt${name} PROPERTIES LABELS "arg;xfail" PASS_REGULAR_EXPRESSION "Multiple .+ disallowed") add_bintype_label(d_DupOpt${name} f_DupOpt${name}) endfunction() addDupOptTest(ShortSep -t ":" -t "," ) addDupOptTest(LongSep --delimiter=: --delimiter=/ ) addDupOptTest(MixSep --delimiter=: -t / ) addDupOptTest(ShortOut -o file1 -o file2 ) addDupOptTest(LongOut --output=file1 --output=file2 ) addDupOptTest(MixOut --output=file1 -o file2 ) add_test(NAME f_DupOptShortEnc COMMAND rifiuti -l ASCII -l CP1252 ${sample_dir}/INFO2-sample2) add_test(NAME f_DupOptLongEnc COMMAND rifiuti --legacy-filename=ASCII --legacy-filename=CP1252 ${sample_dir}/INFO2-sample2) add_test(NAME f_DupOptMixEnc COMMAND rifiuti -l ASCII --legacy-filename=CP1252 ${sample_dir}/INFO2-sample2) set_tests_properties( f_DupOptShortEnc f_DupOptLongEnc f_DupOptMixEnc PROPERTIES LABELS "info2;arg;xfail" PASS_REGULAR_EXPRESSION "Multiple .+ disallowed") add_test(NAME d_NullArgOptTestOut COMMAND rifiuti-vista -o "" ${sample_dir}/dir-sample1) add_test(NAME f_NullArgOptTestOut COMMAND rifiuti -o "" ${sample_dir}/INFO2-sample1) add_test(NAME f_NullArgOptTestEnc COMMAND rifiuti -l "" ${sample_dir}/INFO2-sample1) set_tests_properties(d_NullArgOptTestOut f_NullArgOptTestOut f_NullArgOptTestEnc PROPERTIES LABELS "arg;xfail" PASS_REGULAR_EXPRESSION "Empty .+ disallowed") add_bintype_label(d_NullArgOptTestOut f_NullArgOptTestOut f_NullArgOptTestEnc) function(addBadComboOptTest id) add_test(NAME d_BadComboOptTest${id} COMMAND rifiuti-vista ${ARGN} ${sample_dir}/dir-sample1) add_test(NAME f_BadComboOptTest${id} COMMAND rifiuti ${ARGN} ${sample_dir}/INFO2-sample1) set_tests_properties(d_BadComboOptTest${id} f_BadComboOptTest${id} PROPERTIES LABELS "arg;xfail" PASS_REGULAR_EXPRESSION "Output was already set in .+ format, but later argument attempts to change to .+ format") add_bintype_label(d_BadComboOptTest${id} f_BadComboOptTest${id}) endfunction() # implicit text options addBadComboOptTest(1 -f xml -t:) addBadComboOptTest(2 -n -f xml) # explicit option conflict addBadComboOptTest(3 -f tsv -f json) addBadComboOptTest(4 -f xml -f text) function(addMultiInputTest name) add_test(NAME d_MultiInputTest${name} COMMAND rifiuti-vista ${ARGN}) add_test(NAME f_MultiInputTest${name} COMMAND rifiuti ${ARGN}) set_tests_properties(d_MultiInputTest${name} f_MultiInputTest${name} PROPERTIES LABELS "arg;xfail" PASS_REGULAR_EXPRESSION "Must specify exactly one") add_bintype_label(d_MultiInputTest${name} f_MultiInputTest${name}) endfunction() addMultiInputTest(1 a a) addMultiInputTest(2 foo bar baz) function(addMissingInputTest name) add_test(NAME d_MissingInputTest${name} COMMAND rifiuti-vista ${ARGN}) add_test(NAME f_MissingInputTest${name} COMMAND rifiuti ${ARGN}) set_tests_properties(d_MissingInputTest${name} f_MissingInputTest${name} PROPERTIES LABELS "arg;xfail" PASS_REGULAR_EXPRESSION "Must specify exactly one") add_bintype_label(d_MissingInputTest${name} f_MissingInputTest${name}) endfunction() addMissingInputTest(1 -f xml) addMissingInputTest(2 -t :) addMissingInputTest(3 -z -o file1 -n) function(SepCompareTest testid input sep) if(IS_DIRECTORY ${sample_dir}/${input}) set(is_info2 0) set(prefix d_${testid}) else() set(is_info2 1) set(prefix f_${testid}) endif() set(ref ${bindir}/${prefix}_ref.txt) if(WIN32) string(REPLACE \\ ` pssep ${sep}) add_test_using_shell(${prefix}_PrepAlt "(Get-Content ${sample_dir}/${input}.txt).Replace(\"`t\", \"${pssep}\") | Set-Content ${ref}") else() add_test_using_shell(${prefix}_PrepAlt "awk '{gsub(\"\\t\",\"${sep}\");print;}' ${sample_dir}/${input}.txt > ${ref}") endif() generate_simple_comparison_test(${testid} ${is_info2} "${input}" "${ref}" "arg" -t "${sep}") endfunction() function(addSepCompareTests) # "\\\\" converts to "\;" in cmake, can't do test on backslashes set(seps "|" "\\n\\t" "%s") list(LENGTH seps len) math(EXPR len "${len} - 1") foreach(i RANGE ${len}) list(GET seps ${i} sep) SepCompareTest(SepCompare${i} "INFO2-sample1" "${sep}") SepCompareTest(SepCompare${i} "dir-sample1" "${sep}") endforeach() endfunction() addSepCompareTests() # Live option # TODO think about possibility to configure recycle bin # specifically for GitHub Windows Server runner add_test(NAME d_LiveProbeOpt COMMAND rifiuti-vista --live) set_tests_properties(d_LiveProbeOpt PROPERTIES LABELS "recycledir;arg" SKIP_REGULAR_EXPRESSION "No such file or directory;Unknown option --live" PASS_REGULAR_EXPRESSION "\\(current system\\)") rifiuti2-0.8.2/test/cmake/crafted.cmake000066400000000000000000000046511477543443600177560ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # # Mix different file versions into a single dir # add_test( NAME d_CraftedMixVer COMMAND rifiuti-vista ${sample_dir}/dir-mixed ) set_tests_properties( d_CraftedMixVer PROPERTIES LABELS "recycledir;crafted;xfail" PASS_REGULAR_EXPRESSION "Index files from multiple Windows versions") # # Create dir with bad permission # add_test( NAME d_BadPermDir_Prep COMMAND ${CMAKE_COMMAND} -E copy_directory ${sample_dir}/dir-win10-01 dir-BadPerm ) if(WIN32) add_test(NAME d_BadPermDir_PrepPost COMMAND icacls.exe dir-BadPerm /q /inheritance:r /grant:r "Users:(OI)(CI)(S,REA)") add_test(NAME d_BadPermDir_CleanPre COMMAND icacls.exe dir-BadPerm /q /reset) else() add_test(NAME d_BadPermDir_PrepPost COMMAND chmod u= dir-BadPerm) add_test(NAME d_BadPermDir_CleanPre COMMAND chmod u=rwx dir-BadPerm) endif() add_test(NAME d_BadPermDir_Clean COMMAND ${CMAKE_COMMAND} -E rm -r dir-BadPerm) add_test(NAME d_BadPermDir COMMAND rifiuti-vista dir-BadPerm) set_tests_properties( d_BadPermDir PROPERTIES LABELS "recycledir;crafted;xfail" PASS_REGULAR_EXPRESSION "Permission denied;disallowed under Windows ACL") set_fixture_with_dep("d_BadPermDir") # # Simulate bad UTF-16 path entries # generate_simple_comparison_test("BadUniEnc" 0 "dir-bad-uni" "dir-bad-uni.txt" "encoding|crafted|xfail") set_tests_properties(d_BadUniEnc_Prep PROPERTIES PASS_REGULAR_EXPRESSION "Path contains broken unicode character\\(s\\)") # # Bad record, including bad time / path and truncated file # add_test(NAME f_BadRecords COMMAND rifiuti INFO2-trunc WORKING_DIRECTORY ${sample_dir}) set_tests_properties(f_BadRecords PROPERTIES LABELS "info2;crafted" PASS_REGULAR_EXPRESSION [=[ 4: File deletion time is suspicious or broken 5: Record is truncated]=]) # # Ditto for $Recycle.bin # # TODO different tests for stdout and stderr add_test(NAME d_BadRecords COMMAND rifiuti-vista dir-badfiles WORKING_DIRECTORY ${sample_dir}) set_tests_properties(d_BadRecords PROPERTIES LABELS "recycledir;crafted" PASS_REGULAR_EXPRESSION [=[ \$IF47Q09: File is not a \$Recycle\.bin index \$IW0RYW0\.rtf: File deletion time is suspicious or broken \$IX1JBL3\.djvu: Record is truncated]=]) rifiuti2-0.8.2/test/cmake/encoding.cmake000066400000000000000000000120711477543443600201270ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. function(add_encoding_test name) set(args ${ARGN}) list(PREPEND args -V) list(APPEND args -DTEST_GLIB_ICONV=$) list(APPEND args -DRIFIUTI=$) list(APPEND args -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/_try_encoding.cmake) add_test( NAME ${name} COMMAND ${CMAKE_COMMAND} ${args} ) endfunction() function(add_encoding_test_with_cwd name cwd) set(args ${ARGN}) list(PREPEND args -V) list(APPEND args -DTEST_GLIB_ICONV=$) list(APPEND args -DRIFIUTI=$) list(APPEND args -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/_try_encoding.cmake) add_test( NAME ${name} COMMAND ${CMAKE_COMMAND} ${args} WORKING_DIRECTORY ${cwd} ) endfunction() # # Encoding does not exist # add_encoding_test(f_EncNotExist -DCHOICES=xxx -DINFO2=dummy) set_tests_properties( f_EncNotExist PROPERTIES LABELS "encoding;info2;xfail" PASS_REGULAR_EXPRESSION "'xxx' encoding is not supported" ) # # Legacy file (95-ME) requires encoding option # add_test( NAME f_EncNeeded COMMAND rifiuti INFO-95-ja-1 WORKING_DIRECTORY ${sample_dir} ) set_tests_properties( f_EncNeeded PROPERTIES LABELS "encoding;info2;xfail" PASS_REGULAR_EXPRESSION "produced on a legacy system without Unicode" ) # # ASCII incompatible encoding # if(APPLE) # iconv on Mac lacks many encodings add_encoding_test(f_IncompatEnc -DCHOICES=UTF-32 -DINFO2=dummy) else() add_encoding_test(f_IncompatEnc -DCHOICES=IBM-037|IBM037|CP037 -DINFO2=dummy) endif() set_tests_properties( f_IncompatEnc PROPERTIES LABELS "encoding;info2;xfail" PASS_REGULAR_EXPRESSION "incompatible to any Windows code page" ) # # Legacy path encoding - correct (2 cases) # add_encoding_test_with_cwd(f_LegacyEncOK1_Prep ${sample_dir} -DINFO2=INFO2-sample1 -DCHOICES=CP936|MS936|Windows-936|GBK|csGBK -DOUTFILE=${bindir}/f_LegacyEncOK1.output ) generate_simple_comparison_test("LegacyEncOK1" 1 "" "INFO2-sample1-alt.txt" "encoding") add_encoding_test_with_cwd(f_LegacyEncOK2_Prep ${sample_dir} -DINFO2=INFO-95-ja-1 -DCHOICES=CP932|Windows-932|IBM-943|SJIS|JIS_X0208|SHIFT_JIS|SHIFT-JIS -DOUTFILE=${bindir}/f_LegacyEncOK2.output ) generate_simple_comparison_test("LegacyEncOK2" 1 "" "INFO-95-ja-1.txt" "encoding") # # Legacy path encoding - wrong but still managed to get result # # Original file is in Windows ANSI (CP1252), but intentionally # treat it as Shift-JIS, and got hex escapes # add_encoding_test_with_cwd(f_LegacyEncWrong_Prep ${sample_dir} -DINFO2=INFO2-sample2 -DCHOICES=CP932|Windows-932|IBM-943|SJIS|JIS_X0208|SHIFT_JIS|SHIFT-JIS -DOUTFILE=${bindir}/f_LegacyEncWrong.output ) set_tests_properties(f_LegacyEncWrong_Prep PROPERTIES PASS_REGULAR_EXPRESSION "could not be interpreted in .+ encoding") generate_simple_comparison_test("LegacyEncWrong" 1 "" "INFO2-sample2-wrong-enc.txt" "encoding|xfail") # # Legacy UNC entries # add_encoding_test_with_cwd(f_LegacyUNC_Prep ${sample_dir} -DINFO2=INFO2-2k-tw-uncpath -DCHOICES=CP950|Windows-950|BIG5 -DOUTFILE=${bindir}/f_LegacyUNC.output ) generate_simple_comparison_test("LegacyUNC" 1 "" "INFO2-2k-tw-uncpath.txt" "encoding") # # JSON output # add_encoding_test_with_cwd(f_JsonInfo2Win95_Prep ${sample_dir} -DINFO2=INFO-95-ja-1 -DCHOICES=CP932|Windows-932|IBM-943|SJIS|JIS_X0208|SHIFT_JIS|SHIFT-JIS -DOUTFILE=${bindir}/f_JsonInfo2Win95.output -DEXTRA_ARGS=-f|json ) generate_simple_comparison_test("JsonInfo2Win95" 1 "" "INFO-95-ja-1.json" "encoding|json") add_encoding_test_with_cwd(f_JsonWrongEnc_Prep ${sample_dir} -DINFO2=INFO-95-ja-1 -DCHOICES=CP1255|MS-HEBR|WINDOWS-1255|HEBREW|ISO-8859-8|ISO-IR-138|ISO8859-8|ISO_8859-8|ISO_8859-8:1988|CSISOLATINHEBREW -DOUTFILE=${bindir}/f_JsonWrongEnc.output -DEXTRA_ARGS=-f|json ) set_tests_properties(f_JsonWrongEnc_Prep PROPERTIES PASS_REGULAR_EXPRESSION "could not be interpreted in .+ encoding") generate_simple_comparison_test("JsonWrongEnc" 1 "" "INFO-95-ja-1-in-cp1255.json" "encoding|xfail|json") # It turns out different iconv implemention may have # different behavior even for the same code page. Take this # for example, GNU iconv marks 0x90 illegal for CP1255, but # winiconv converts that to U+0090. if(WIN32) set_tests_properties(f_JsonWrongEnc PROPERTIES WILL_FAIL true) endif() add_encoding_test_with_cwd(f_XmlWrongEnc_Prep ${sample_dir} -DINFO2=INFO-95-ja-1 -DCHOICES=CP949|UHC|ISO-IR-149|KOREAN|KSC_5601|KS_C_5601-1987|KS_C_5601-1989|CSKSC56011987 -DOUTFILE=${bindir}/f_XmlWrongEnc.output -DEXTRA_ARGS=-f|xml ) set_tests_properties(f_XmlWrongEnc_Prep PROPERTIES PASS_REGULAR_EXPRESSION "could not be interpreted in .+ encoding") generate_simple_comparison_test("XmlWrongEnc" 1 "" "INFO-95-ja-1-in-cp949.xml" "encoding|xfail|xml") rifiuti2-0.8.2/test/cmake/json.cmake000066400000000000000000000015621477543443600173150ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # # Verify JSON output works as intended # function(createJsonOutputTests) set(ids "JsonInfo2Empty" "JsonInfo2WinXP" "JsonInfo2Win98" "JsonRdirVista" "JsonRdirWin10" "JsonRdirUNC19" ) set(files "INFO2-empty" "INFO2-sample1" "INFO2-sample2" "dir-sample1" "dir-win10-01" "dir-2019-uncpath" ) set(encs "" "" "CP1252" "" "" ) foreach(id file enc IN ZIP_LISTS ids files encs) if (IS_DIRECTORY ${sample_dir}/${file}) set(is_info2 0) else() set(is_info2 1) endif() set(args -f json) if(enc) list(APPEND args -l ${enc}) endif() generate_simple_comparison_test(${id} ${is_info2} ${file} ${file}.json "parse|json" ${args}) endforeach() endfunction() createJsonOutputTests() rifiuti2-0.8.2/test/cmake/parse-info2.cmake000066400000000000000000000020041477543443600204610ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # # Verify INFO2 results match existing golden files # function(createINFO2ParseTests) # Keep variables scoped set(ids "Info2Empty" "Info2WinNT" "Info2Win98" "Info2WinME" "Info2Win2K" "Info2WinXP" "Info2UNCA1" "Info2UNCU1") set(files "INFO2-empty" "INFO-NT-en-1" "INFO2-sample2" "INFO2-ME-en-1" "INFO2-2k-cht-1" "INFO2-sample1" "INFO2-me-en-uncpath" "INFO2-03-tw-uncpath") set(encs "" "" "CP1252" "CP1252" "" "" "ASCII" "") foreach(id file enc IN ZIP_LISTS ids files encs) if(enc) generate_simple_comparison_test(${id} 1 ${file} ${file}.txt "parse" -l ${enc}) else() generate_simple_comparison_test(${id} 1 ${file} ${file}.txt "parse") endif() endforeach() endfunction() createINFO2ParseTests() # In encoding.cmake now # (Info2Win95 INFO-95-ja-1 -l ${cp932}) # (Info2UNCA2 INFO2-2k-tw-uncpath -l ${cp950}) rifiuti2-0.8.2/test/cmake/parse-rdir.cmake000066400000000000000000000022341477543443600204110ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # # Verify $Recycle.bin results match existing golden files # function(createRDirParseTests) set(ids DirEmpty DirVista DirWin10 DirUNC19) set(files dir-empty dir-sample1 dir-win10-01 dir-2019-uncpath) foreach(id file IN ZIP_LISTS ids files) generate_simple_comparison_test(${id} 0 ${file} ${file}.txt "parse") endforeach() endfunction() createRDirParseTests() # # Similar to above, but only test single index file # generate_simple_comparison_test(DirOneIdx 0 "dir-win10-01/$IKEGS1G" "dir-single-idx.txt" "parse") # # Similar to previous test, but copy index file elsewhere # to test the isolated file behavior # add_test(NAME d_DirIsolatedIdx_PrepPre COMMAND ${CMAKE_COMMAND} -E copy ${sample_dir}/dir-win10-01/$IKEGS1G .) add_test(NAME d_DirIsolatedIdx_Prep COMMAND rifiuti-vista $IKEGS1G -o d_DirIsolatedIdx.output) add_test(NAME d_DirIsolatedIdx_CleanAlt COMMAND ${CMAKE_COMMAND} -E rm $IKEGS1G) generate_simple_comparison_test(DirIsolatedIdx 0 "" "dir-isolated-idx.txt" "parse") rifiuti2-0.8.2/test/cmake/read-write.cmake000066400000000000000000000045221477543443600204060ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # # Non-existant input file arg # add_test(NAME d_InputNotExist COMMAND rifiuti-vista dUmMy) add_test(NAME f_InputNotExist COMMAND rifiuti dUmMy) set_tests_properties(d_InputNotExist f_InputNotExist PROPERTIES LABELS "xfail" PASS_REGULAR_EXPRESSION "does not exist") add_bintype_label(d_InputNotExist f_InputNotExist) # # Special file # # g_file_test() is unuseful in Windows; NUL, CON etc are # treated as regular files, therefore can't be determined # ahead as unusable. They have to be actually opened. # if(WIN32) add_test(NAME d_InputSpecialFile COMMAND rifiuti-vista nul) add_test(NAME f_InputSpecialFile COMMAND rifiuti nul) set_tests_properties(d_InputSpecialFile f_InputSpecialFile PROPERTIES LABELS "xfail" PASS_REGULAR_EXPRESSION "File is not .+ index") else() add_test(NAME d_InputSpecialFile COMMAND rifiuti-vista /dev/null) add_test(NAME f_InputSpecialFile COMMAND rifiuti /dev/null) set_tests_properties(d_InputSpecialFile f_InputSpecialFile PROPERTIES LABELS "xfail" PASS_REGULAR_EXPRESSION "not a normal file") endif() add_bintype_label(d_InputSpecialFile f_InputSpecialFile) # # File output == Console output? # function(FileStdoutCompareTest testid input) if(IS_DIRECTORY ${sample_dir}/${input}) set(is_info2 0) set(prog rifiuti-vista) set(prefix d_${testid}) else() set(is_info2 1) set(prog rifiuti) set(prefix f_${testid}) endif() set(con_output ${bindir}/${prefix}_c.txt) add_test_using_shell(${prefix}_PrepAlt "$ ${input} > ${con_output}" WORKING_DIRECTORY ${sample_dir}) generate_simple_comparison_test(${testid} ${is_info2} "${input}" "${con_output}" "write") endfunction() FileStdoutCompareTest("FileConDiffA" "dir-sample1") FileStdoutCompareTest("FileConDiffU" "dir-win10-01") FileStdoutCompareTest("FileConDiffF" "INFO2-03-tw-uncpath") # # Unicode filename / dir name should work # generate_simple_comparison_test("UnicodePathName" 1 "./ごみ箱/INFO2-empty" "japanese-path-file.txt" "encoding") generate_simple_comparison_test("UnicodePathName" 0 "./ごみ箱/dir-empty" "japanese-path-dir.txt" "encoding") rifiuti2-0.8.2/test/cmake/xml.cmake000066400000000000000000000072571477543443600171530ustar00rootroot00000000000000# Copyright (C) 2023-2024, Abel Cheung # rifiuti2 is released under Revised BSD License. # Please see LICENSE file for more info. # # XML well-formed, DTD validation, normalization checks # function(createXmlTestSet id input) # $ARGN as extra rifiuti args set(dtd ${CMAKE_CURRENT_SOURCE_DIR}/rifiuti.dtd) if(NOT IS_ABSOLUTE ${input}) set(inputchk ${sample_dir}/${input}) else() set(inputchk ${input}) endif() if(IS_DIRECTORY ${inputchk}) set(is_info2 0) set(prog rifiuti-vista) set(prefix "d_Xml${id}") else() set(is_info2 1) set(prog rifiuti) set(prefix "f_Xml${id}") endif() set(wellform_pfx "${prefix}WellForm") set(dtdvalid_pfx "${prefix}DTDValidate") set(xmlequal_pfx "${prefix}Equal") set(wellform_out "${bindir}/${wellform_pfx}.output") set(wellform_fxt $) set(xmlequal_out "${bindir}/${xmlequal_pfx}.output") set(xmlequal_ref "${bindir}/${xmlequal_pfx}.refout") set(xmlequal_fxt $) # XML/DTD validation part add_test(NAME ${wellform_pfx}_Prep COMMAND ${prog} -o ${wellform_out} ${ARGN} -f xml ${input} WORKING_DIRECTORY ${sample_dir}) add_test(NAME ${wellform_pfx} COMMAND ${XMLLINT} --noout ${wellform_out}) add_test(NAME ${dtdvalid_pfx} COMMAND ${XMLLINT} --noout ${wellform_out} --dtdvalid ${dtd}) add_test(NAME ${wellform_pfx}_Clean COMMAND ${CMAKE_COMMAND} -E rm ${wellform_out}) # XmlWellForm fixtures are used in multiple fixtures, # properties set further down below, not here set_tests_properties(${wellform_pfx} PROPERTIES FIXTURES_REQUIRED ${wellform_fxt}) set_tests_properties(${dtdvalid_pfx} PROPERTIES FIXTURES_REQUIRED ${wellform_fxt}) set_tests_properties(${wellform_pfx} ${dtdvalid_pfx} PROPERTIES LABELS "xml") add_bintype_label(${wellform_pfx} ${dtdvalid_pfx}) # XML normalization and comparison # xmllint has a long known history of broken --output option, # have to use redirection instead. # https://unix.stackexchange.com/q/492116 add_test_using_shell(${xmlequal_pfx}_PrepAlt "${XMLLINT} --c14n ${wellform_out} > ${xmlequal_out}") set_tests_properties(${xmlequal_pfx}_PrepAlt PROPERTIES DEPENDS ${wellform_pfx}_Prep) add_test_using_shell(${xmlequal_pfx}_Prep "${XMLLINT} --c14n ${input}.xml > ${xmlequal_ref}" WORKING_DIRECTORY ${sample_dir}) generate_simple_comparison_test("Xml${id}Equal" ${is_info2} "" "${xmlequal_ref}" "xml") set_fixture_with_dep("${xmlequal_pfx}") set_tests_properties(${wellform_pfx}_Prep PROPERTIES FIXTURES_SETUP "${wellform_fxt};${xmlequal_fxt}") set_tests_properties(${wellform_pfx}_Clean PROPERTIES FIXTURES_CLEANUP "${wellform_fxt};${xmlequal_fxt}") if("${XMLLINT}" STREQUAL "XMLLINT-NOTFOUND") set_tests_properties( ${dtdvalid_pfx} ${xmlequal_pfx} ${xmlequal_pfx}_Clean ${xmlequal_pfx}_PrepAlt ${xmlequal_pfx}_Prep ${wellform_pfx} ${wellform_pfx}_Clean ${wellform_pfx}_Prep PROPERTIES DISABLED true) endif() endfunction() createXmlTestSet(1 INFO2-sample1) createXmlTestSet(2 dir-sample1) createXmlTestSet(3 INFO-95-ja-1 -l CP932) # Make sure no version is printed if $Recycle.bin # is empty because no index can be found; but empty # INFO2 still contains version info generate_simple_comparison_test (NoVerIfDirEmpty 0 dir-empty dir-empty.xml "xml" -f xml) generate_simple_comparison_test (HasVerIfInfo2Empty 1 INFO2-empty INFO2-empty.xml "xml" -f xml) rifiuti2-0.8.2/test/rifiuti-schema.json000066400000000000000000000036271477543443600200720ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://raw.githubusercontent.com/abelcheung/rifiuti2/0.8.1/test/rifiuti-schema.json", "title": "rifiuti", "description": "JSON schema for rifiuti json formatted output", "type": "object", "definitions": { "nonNegativeInteger": { "type": "integer", "minimum": 0 } }, "properties": { "format": { "description": "Recycle bin format", "type": "string" }, "version": { "allOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "description": "Version embedded in index file header" } ] }, "ever_existed": { "allOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "description": "Total items ever existed in recycle bin" } ] }, "path": { "description": "Location of recycle bin", "type": "string" }, "records": { "description": "All recycle bin records", "type": "array", "uniqueItems": true, "minItems": 0, "items": { "type": "object", "properties": { "index": { "anyOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "type": "string" } ] }, "time": { "type": "string" }, "gone": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ] }, "size": { "anyOf": [ { "$ref": "#/definitions/nonNegativeInteger" }, { "type": "null" } ] }, "path": { "type": "string" } }, "required": [ "index", "time", "gone", "size", "path" ] } } }, "required": [ "format", "version", "path", "records" ] } rifiuti2-0.8.2/test/rifiuti.dtd000066400000000000000000000005601477543443600164270ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/000077500000000000000000000000001477543443600157225ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/INFO-95-ja-1000066400000000000000000000060341477543443600173640ustar00rootroot00000000000000 ,D:\WINDOWS\޽į\The Microsoft Network ̾ı.lnk ְD:\WINDOWS\޽į\VKޯϯ Ұ.bmpOƯD:\WINDOWS\޽į\VK÷ĕ.txtsϺD:\My Documents\DirectX-V8.0a\bda.cabϋ D:\My Documents\DirectX-V8.0a\bdant.cabϋ D:\My Documents\DirectX-V8.0a\cfgmgr32.dll?ϋD:\My Documents\DirectX-V8.0a\dxsetup.exe 9ϋD:\My Documents\DirectX-V8.0a\setupapi.dll 9ϋD:\WINDOWS\޽į\Connect to the Internet.LNK  %ыD:\WINDOWS\޽į\Outlook Express.lnk'ыD:\WINDOWS\޽į\VK÷ĕ.txtIrifiuti2-0.8.2/test/samples/INFO-95-ja-1-in-cp1255.json000066400000000000000000000034121477543443600216520ustar00rootroot00000000000000{ "format": "file", "version": 0, "ever_existed": 16, "path": "INFO-95-ja-1", "records": [ {"index": 1, "time": "2015-05-11T05:59:49Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\ֳ<\\DE>½¸ִ¯ּ<\\DF>\\The Microsoft Network ‚ּ¾¯ִ±¯ּ<\\DF>.lnk"}, {"index": 2, "time": "2015-05-11T06:00:25Z", "gone": false, "size": 950272, "path": "D:\\WINDOWS\\ֳ<\\DE>½¸ִ¯ּ<\\DF>\\<\\90>V‹Kֻ<\\DE>¯ִֿ¯ּ<\\DF> ²ׂ°¼<\\DE>.bmp"}, {"index": 3, "time": "2015-05-11T07:19:25Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\ֳ<\\DE>½¸ִ¯ּ<\\DF>\\<\\90>V‹Kֳ·½ִ•¶<\\8F>‘.txt"}, {"index": 4, "time": "2015-05-11T09:48:21Z", "gone": false, "size": 589824, "path": "D:\\My Documents\\DirectX-V8.0a\\bda.cab"}, {"index": 5, "time": "2015-05-11T09:48:21Z", "gone": false, "size": 589824, "path": "D:\\My Documents\\DirectX-V8.0a\\bdant.cab"}, {"index": 6, "time": "2015-05-11T09:48:21Z", "gone": false, "size": 65536, "path": "D:\\My Documents\\DirectX-V8.0a\\cfgmgr32.dll"}, {"index": 11, "time": "2015-05-11T09:48:23Z", "gone": false, "size": 163840, "path": "D:\\My Documents\\DirectX-V8.0a\\dxsetup.exe"}, {"index": 12, "time": "2015-05-11T09:48:23Z", "gone": false, "size": 360448, "path": "D:\\My Documents\\DirectX-V8.0a\\setupapi.dll"}, {"index": 13, "time": "2015-05-11T09:59:19Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\ֳ<\\DE>½¸ִ¯ּ<\\DF>\\Connect to the Internet.LNK"}, {"index": 14, "time": "2015-05-11T09:59:22Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\ֳ<\\DE>½¸ִ¯ּ<\\DF>\\Outlook Express.lnk"}, {"index": 15, "time": "2015-05-18T00:45:09Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\ֳ<\\DE>½¸ִ¯ּ<\\DF>\\<\\90>V‹Kֳ·½ִ•¶<\\8F>‘.txt"}, ] } rifiuti2-0.8.2/test/samples/INFO-95-ja-1-in-cp949.xml000066400000000000000000000036661477543443600214450ustar00rootroot00000000000000 璟.lnk]]> 켓<\AF>璟 꾀갸<\DE>.bmp]]> rifiuti2-0.8.2/test/samples/INFO-95-ja-1.json000066400000000000000000000033651477543443600203400ustar00rootroot00000000000000{ "format": "file", "version": 0, "ever_existed": 16, "path": "INFO-95-ja-1", "records": [ {"index": 1, "time": "2015-05-11T05:59:49Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\デスクトップ\\The Microsoft Network のセットアップ.lnk"}, {"index": 2, "time": "2015-05-11T06:00:25Z", "gone": false, "size": 950272, "path": "D:\\WINDOWS\\デスクトップ\\新規ビットマップ イメージ.bmp"}, {"index": 3, "time": "2015-05-11T07:19:25Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\デスクトップ\\新規テキスト文書.txt"}, {"index": 4, "time": "2015-05-11T09:48:21Z", "gone": false, "size": 589824, "path": "D:\\My Documents\\DirectX-V8.0a\\bda.cab"}, {"index": 5, "time": "2015-05-11T09:48:21Z", "gone": false, "size": 589824, "path": "D:\\My Documents\\DirectX-V8.0a\\bdant.cab"}, {"index": 6, "time": "2015-05-11T09:48:21Z", "gone": false, "size": 65536, "path": "D:\\My Documents\\DirectX-V8.0a\\cfgmgr32.dll"}, {"index": 11, "time": "2015-05-11T09:48:23Z", "gone": false, "size": 163840, "path": "D:\\My Documents\\DirectX-V8.0a\\dxsetup.exe"}, {"index": 12, "time": "2015-05-11T09:48:23Z", "gone": false, "size": 360448, "path": "D:\\My Documents\\DirectX-V8.0a\\setupapi.dll"}, {"index": 13, "time": "2015-05-11T09:59:19Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\デスクトップ\\Connect to the Internet.LNK"}, {"index": 14, "time": "2015-05-11T09:59:22Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\デスクトップ\\Outlook Express.lnk"}, {"index": 15, "time": "2015-05-18T00:45:09Z", "gone": false, "size": 32768, "path": "D:\\WINDOWS\\デスクトップ\\新規テキスト文書.txt"}, ] } rifiuti2-0.8.2/test/samples/INFO-95-ja-1.txt000066400000000000000000000022211477543443600201740ustar00rootroot00000000000000Recycle bin path: 'INFO-95-ja-1' Version: 0 Total entries ever existed: 16 OS Guess: Windows 95 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 1 2015-05-11 05:59:49 FALSE 32768 D:\WINDOWS\デスクトップ\The Microsoft Network のセットアップ.lnk 2 2015-05-11 06:00:25 FALSE 950272 D:\WINDOWS\デスクトップ\新規ビットマップ イメージ.bmp 3 2015-05-11 07:19:25 FALSE 32768 D:\WINDOWS\デスクトップ\新規テキスト文書.txt 4 2015-05-11 09:48:21 FALSE 589824 D:\My Documents\DirectX-V8.0a\bda.cab 5 2015-05-11 09:48:21 FALSE 589824 D:\My Documents\DirectX-V8.0a\bdant.cab 6 2015-05-11 09:48:21 FALSE 65536 D:\My Documents\DirectX-V8.0a\cfgmgr32.dll 11 2015-05-11 09:48:23 FALSE 163840 D:\My Documents\DirectX-V8.0a\dxsetup.exe 12 2015-05-11 09:48:23 FALSE 360448 D:\My Documents\DirectX-V8.0a\setupapi.dll 13 2015-05-11 09:59:19 FALSE 32768 D:\WINDOWS\デスクトップ\Connect to the Internet.LNK 14 2015-05-11 09:59:22 FALSE 32768 D:\WINDOWS\デスクトップ\Outlook Express.lnk 15 2015-05-18 00:45:09 FALSE 32768 D:\WINDOWS\デスクトップ\新規テキスト文書.txt rifiuti2-0.8.2/test/samples/INFO-95-ja-1.xml000066400000000000000000000040321477543443600201570ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/INFO-NT-en-1000066400000000000000000000113241477543443600174560ustar00rootroot00000000000000 ,C:\WINNT\Profiles\Administrator\Desktop\IE 5.5 SP2 Full tSC:\WINNT\Profiles\Administrator\Desktop\IE 5.5 SP2 FullC:\WINNT\Profiles\Administrator\Desktop\Firefox Setup 2[1].0.0.20.exe J\C:\WINNT\Profiles\Administrator\Desktop\Firefox Setup 2[1].0.0.20.exeC:\WINNT\Profiles\Administrator\Desktop\coreftplite[1].ansi.exe'C:\WINNT\Profiles\Administrator\Desktop\coreftplite[1].ansi.exeC:\WINNT\Profiles\Administrator\Desktop\ie55sp2_nt.zip28C:\WINNT\Profiles\Administrator\Desktop\ie55sp2_nt.zipC:\TEMP\ie6@O=C:\TEMP\ie6C:\TEMP\ie6-standaloneռ΃C:\TEMP\ie6-standalonerifiuti2-0.8.2/test/samples/INFO-NT-en-1.txt000066400000000000000000000012271477543443600202750ustar00rootroot00000000000000Recycle bin path: 'INFO-NT-en-1' Version: 2 Total entries ever existed: 18 OS Guess: Windows NT 4.0 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 12 2015-05-23 01:50:28 FALSE 89355264 C:\WINNT\Profiles\Administrator\Desktop\IE 5.5 SP2 Full 13 2015-05-23 01:50:31 FALSE 6048256 C:\WINNT\Profiles\Administrator\Desktop\Firefox Setup 2[1].0.0.20.exe 14 2015-05-23 01:50:31 FALSE 2615296 C:\WINNT\Profiles\Administrator\Desktop\coreftplite[1].ansi.exe 15 2015-05-23 01:50:31 FALSE 3682816 C:\WINNT\Profiles\Administrator\Desktop\ie55sp2_nt.zip 16 2015-05-23 01:50:49 FALSE 20809216 C:\TEMP\ie6 17 2015-05-23 01:50:49 FALSE 8637952 C:\TEMP\ie6-standalone rifiuti2-0.8.2/test/samples/INFO2-03-tw-uncpath000066400000000000000000000062241477543443600207760ustar00rootroot00000000000000 \\Vm-2003-r2-tw\Temp\\DOWNLO~1\HxDSetup.zip4e 0\\Vm-2003-r2-tw\Temp\eN\Downloads\HxDSetup.zip\Vm-2003-r2-tw\Temp\\DOWNLO~1\README~1.HTMe@\\Vm-2003-r2-tw\Temp\eN\Downloads\README.html\Vm-2003-r2-tw\Temp\\DOWNLO~1\____PC~1.BINS.binqJf`\\Vm-2003-r2-tw\Temp\eN\Downloads\____PC__FONTS.bin\\Vm-2003-r2-tw\Temp\\DOWNLO~1\ws_ftple.exePd) f \\Vm-2003-r2-tw\Temp\eN\Downloads\ws_ftple.exerifiuti2-0.8.2/test/samples/INFO2-03-tw-uncpath.txt000066400000000000000000000007441477543443600216150ustar00rootroot00000000000000Recycle bin path: 'INFO2-03-tw-uncpath' Version: 5 OS Guess: Windows XP or 2003 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 1 2019-05-05 17:08:49 FALSE 3153920 \\Vm-2003-r2-tw\Temp\文件\Downloads\HxDSetup.zip 2 2019-05-05 17:12:32 TRUE 16384 \\Vm-2003-r2-tw\Temp\文件\Downloads\README.html 3 2019-05-05 17:14:38 TRUE 24576 \\Vm-2003-r2-tw\Temp\文件\Downloads\____PC__FONTS.bin 4 2019-05-05 17:14:46 FALSE 708608 \\Vm-2003-r2-tw\Temp\文件\Downloads\ws_ftple.exe rifiuti2-0.8.2/test/samples/INFO2-2k-cht-1000066400000000000000000000076641477543443600177230ustar00rootroot00000000000000 C:\DOCUME~1\Nobody\ୱ\ABCs~1.TXT\ABCsWr.txtf|ld~wH1w|wH  @wpgC:\Documents and Settings\Nobody\Lhb\ABCeXeW[eN.txtd~wX1wx·wx ʶwwwx@ IoyJoyE wf@@d~wxwPYw ww$ ww Ccxpx jH jH  f @~w @Tww8 f{w|FyC:\DOCUME~1\Nobody\ୱ\MOZILL~1.LNK\Mozilla Firefox.lnkh |IlId~wH1w|IwH  @w@2tC:\Documents and Settings\Nobody\Lhb\Mozilla Firefox.lnkxI·wxM ʶwwwxI@M IIoyJoyEI w I@I@Id~wxwPIYwM ww ww CcxpxI vvM h  III@~w @IIITwwҋ 0S f{w|FyC:\Documents and Settings\Nobody\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.bmpjC:\Documents and Settings\Nobody\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.bmpC:\temp\ODULLU~1.DOCr ?????.doc0R ,wH  RRd~wH1wRwH  @wC:\temp\dll mT-NeW[ *4CJD.doc`R,wJaBsÑ rifiuti2-0.8.2/test/samples/INFO2-ME-en-1.txt000066400000000000000000000010261477543443600203340ustar00rootroot00000000000000Recycle bin path: 'INFO2-ME-en-1' Version: 5 OS Guess: Windows ME Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 1 2015-05-10 12:43:36 FALSE 4096 C:\WINDOWS\Desktop\Windows Media Player.lnk 2 2015-05-10 12:45:41 FALSE 0 C:\My Documents\Temp Folder é à ä ç 3 2015-05-18 22:15:32 TRUE 495616 C:\My Documents\Copy of My Music 3 2015-05-18 23:38:34 TRUE 4096 C:\My Documents\bin-me.zip 4 2015-05-18 23:38:53 TRUE 4096 C:\My Documents\bin-me.zip 5 2015-05-18 23:39:31 FALSE 8192 C:\WINDOWS\Desktop\New WordPad Document.doc rifiuti2-0.8.2/test/samples/INFO2-empty000066400000000000000000000000241477543443600176120ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/INFO2-empty.json000066400000000000000000000001241477543443600205630ustar00rootroot00000000000000{ "format": "file", "version": 5, "path": "INFO2-empty", "records": [ ] } rifiuti2-0.8.2/test/samples/INFO2-empty.txt000066400000000000000000000002111477543443600204260ustar00rootroot00000000000000Recycle bin path: 'INFO2-empty' Version: 5 OS Guess: Windows 2000, XP or 2003 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path rifiuti2-0.8.2/test/samples/INFO2-empty.xml000066400000000000000000000002131477543443600204110ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/INFO2-me-en-uncpath000066400000000000000000000015341477543443600211240ustar00rootroot00000000000000\\Vm-me\temp\Documents\langastas.xpi#D#DފpD?xܸ`DFo'APG@$x1?-8p8Dxu?xxdxvĐFxB'l\\Vm-me\temp\Documents\3DPacmanSetupTrial.exeD#DފpD?~ܸ̗Fo'APGW$~1?-8p8D~u?~~d~vF~BhH\Vm-me\temp\Documents\fgabida.lst#D#DފpD?ܸ0OIo'APGU$1?-8p8Du?dvaFB7#rifiuti2-0.8.2/test/samples/INFO2-me-en-uncpath.txt000066400000000000000000000005401477543443600217360ustar00rootroot00000000000000Recycle bin path: 'INFO2-me-en-uncpath' Version: 5 OS Guess: Windows ME Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 1 2019-05-02 16:31:45 FALSE 420864 \\Vm-me\temp\Documents\langastas.xpi 2 2019-05-02 16:38:56 FALSE 4773888 \\Vm-me\temp\Documents\3DPacmanSetupTrial.exe 3 2019-05-02 16:43:15 TRUE 4608 \\Vm-me\temp\Documents\fgabida.lst rifiuti2-0.8.2/test/samples/INFO2-sample1000066400000000000000000000310241477543443600200220ustar00rootroot00000000000000 D )C:\DOCUME~1\ALLUSE~1\Desktop\е~1.LNKp\еʵ.lnk,J Z9C:\Documents and Settings\All Users\Desktop\ gSLhb͋xQ.lnkC:\Documents and Settings\Administrator\Desktop\wongsir_url.txt-@L=C:\Documents and Settings\Administrator\Desktop\wongsir_url.txtC:\Documents and Settings\Administrator\Desktop\dd-wrt.v24_mini_wrt54g.bin.@Č?p,C:\Documents and Settings\Administrator\Desktop\dd-wrt.v24_mini_wrt54g.binC:\Documents and Settings\Administrator\Desktop\theme\.svn/ fE C:\Documents and Settings\Administrator\Desktop\theme\.svnC:\Documents and Settings\Administrator\Desktop\Config Client0EXC:\Documents and Settings\Administrator\Desktop\Config ClientC:\Documents and Settings\Administrator\Desktop\Config Client.7z1E0C:\Documents and Settings\Administrator\Desktop\Config Client.7zC:\Documents and Settings\All Users\Desktop\Wireshark.lnk2r+JC:\Documents and Settings\All Users\Desktop\Wireshark.lnkC:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline.rar9FJ)C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline.rar:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline@[J)C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConlineC:\DOCUME~1\ADMINI~1\Desktop\360~1.LNKesktop\360.lnkAJC:\Documents and Settings\Administrator\Desktop\360Oi{.lnkC:\Documents and Settings\Administrator\Desktop\gdbB`HJ)C:\Documents and Settings\Administrator\Desktop\gdbC:\Documents and Settings\Administrator\Desktop\gdb.zipC`HJ)C:\Documents and Settings\Administrator\Desktop\gdb.zipC:\Documents and Settings\Administrator\Desktop\recovered filesD 7:JC:\Documents and Settings\Administrator\Desktop\recovered filesC:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConlineEwJ)C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConlineC:\Documents and Settings\Administrator\Desktop\Uneraser_Setup(2).exeFpwJNC:\Documents and Settings\Administrator\Desktop\Uneraser_Setup(2).exeC:\Documents and Settings\Administrator\Desktop\Uneraser_Setup.exeGpwJNC:\Documents and Settings\Administrator\Desktop\Uneraser_Setup.exerifiuti2-0.8.2/test/samples/INFO2-sample1-alt.txt000066400000000000000000000033031477543443600214150ustar00rootroot00000000000000Recycle bin path: 'INFO2-sample1' Version: 5 OS Guess: Windows XP or 2003 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 44 2008-10-28 15:53:42 FALSE 4096 C:\DOCUME~1\ALLUSE~1\Desktop\有道桌~1.LNK 45 2008-11-03 15:01:59 FALSE 4096 C:\Documents and Settings\Administrator\Desktop\wongsir_url.txt 46 2008-11-06 09:20:58 FALSE 2912256 C:\Documents and Settings\Administrator\Desktop\dd-wrt.v24_mini_wrt54g.bin 47 2008-11-13 12:08:39 FALSE 765952 C:\Documents and Settings\Administrator\Desktop\theme\.svn 48 2008-11-13 12:11:33 FALSE 5812224 C:\Documents and Settings\Administrator\Desktop\Config Client 49 2008-11-13 12:11:36 FALSE 1847296 C:\Documents and Settings\Administrator\Desktop\Config Client.7z 50 2008-11-19 04:42:04 FALSE 4096 C:\Documents and Settings\All Users\Desktop\Wireshark.lnk 57 2008-11-19 05:07:15 FALSE 2727936 C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline.rar 64 2008-11-19 05:07:35 TRUE 2727936 C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline 65 2008-11-19 05:17:12 FALSE 4096 C:\DOCUME~1\ADMINI~1\Desktop\360保~1.LNK 66 2008-11-19 05:21:37 FALSE 2732032 C:\Documents and Settings\Administrator\Desktop\gdb 67 2008-11-19 05:21:37 FALSE 2723840 C:\Documents and Settings\Administrator\Desktop\gdb.zip 68 2008-11-19 11:34:23 FALSE 0 C:\Documents and Settings\Administrator\Desktop\recovered files 69 2008-11-19 18:51:45 FALSE 2727936 C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline 70 2008-11-19 18:51:45 FALSE 5169152 C:\Documents and Settings\Administrator\Desktop\Uneraser_Setup(2).exe 71 2008-11-19 18:51:45 FALSE 5169152 C:\Documents and Settings\Administrator\Desktop\Uneraser_Setup.exe rifiuti2-0.8.2/test/samples/INFO2-sample1.json000066400000000000000000000052221477543443600207730ustar00rootroot00000000000000{ "format": "file", "version": 5, "path": "INFO2-sample1", "records": [ {"index": 44, "time": "2008-10-28T15:53:42Z", "gone": false, "size": 4096, "path": "C:\\Documents and Settings\\All Users\\Desktop\\有道桌面词典.lnk"}, {"index": 45, "time": "2008-11-03T15:01:59Z", "gone": false, "size": 4096, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\wongsir_url.txt"}, {"index": 46, "time": "2008-11-06T09:20:58Z", "gone": false, "size": 2912256, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\dd-wrt.v24_mini_wrt54g.bin"}, {"index": 47, "time": "2008-11-13T12:08:39Z", "gone": false, "size": 765952, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\theme\\.svn"}, {"index": 48, "time": "2008-11-13T12:11:33Z", "gone": false, "size": 5812224, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\Config Client"}, {"index": 49, "time": "2008-11-13T12:11:36Z", "gone": false, "size": 1847296, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\Config Client.7z"}, {"index": 50, "time": "2008-11-19T04:42:04Z", "gone": false, "size": 4096, "path": "C:\\Documents and Settings\\All Users\\Desktop\\Wireshark.lnk"}, {"index": 57, "time": "2008-11-19T05:07:15Z", "gone": false, "size": 2727936, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\GetDataBackforFAT-v3.63_PConline.rar"}, {"index": 64, "time": "2008-11-19T05:07:35Z", "gone": true, "size": 2727936, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\GetDataBackforFAT-v3.63_PConline"}, {"index": 65, "time": "2008-11-19T05:17:12Z", "gone": false, "size": 4096, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\360保险箱.lnk"}, {"index": 66, "time": "2008-11-19T05:21:37Z", "gone": false, "size": 2732032, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\gdb"}, {"index": 67, "time": "2008-11-19T05:21:37Z", "gone": false, "size": 2723840, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\gdb.zip"}, {"index": 68, "time": "2008-11-19T11:34:23Z", "gone": false, "size": 0, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\recovered files"}, {"index": 69, "time": "2008-11-19T18:51:45Z", "gone": false, "size": 2727936, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\GetDataBackforFAT-v3.63_PConline"}, {"index": 70, "time": "2008-11-19T18:51:45Z", "gone": false, "size": 5169152, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\Uneraser_Setup(2).exe"}, {"index": 71, "time": "2008-11-19T18:51:45Z", "gone": false, "size": 5169152, "path": "C:\\Documents and Settings\\Administrator\\Desktop\\Uneraser_Setup.exe"}, ] } rifiuti2-0.8.2/test/samples/INFO2-sample1.txt000066400000000000000000000033601477543443600206420ustar00rootroot00000000000000Recycle bin path: 'INFO2-sample1' Version: 5 OS Guess: Windows XP or 2003 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 44 2008-10-28 15:53:42 FALSE 4096 C:\Documents and Settings\All Users\Desktop\有道桌面词典.lnk 45 2008-11-03 15:01:59 FALSE 4096 C:\Documents and Settings\Administrator\Desktop\wongsir_url.txt 46 2008-11-06 09:20:58 FALSE 2912256 C:\Documents and Settings\Administrator\Desktop\dd-wrt.v24_mini_wrt54g.bin 47 2008-11-13 12:08:39 FALSE 765952 C:\Documents and Settings\Administrator\Desktop\theme\.svn 48 2008-11-13 12:11:33 FALSE 5812224 C:\Documents and Settings\Administrator\Desktop\Config Client 49 2008-11-13 12:11:36 FALSE 1847296 C:\Documents and Settings\Administrator\Desktop\Config Client.7z 50 2008-11-19 04:42:04 FALSE 4096 C:\Documents and Settings\All Users\Desktop\Wireshark.lnk 57 2008-11-19 05:07:15 FALSE 2727936 C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline.rar 64 2008-11-19 05:07:35 TRUE 2727936 C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline 65 2008-11-19 05:17:12 FALSE 4096 C:\Documents and Settings\Administrator\Desktop\360保险箱.lnk 66 2008-11-19 05:21:37 FALSE 2732032 C:\Documents and Settings\Administrator\Desktop\gdb 67 2008-11-19 05:21:37 FALSE 2723840 C:\Documents and Settings\Administrator\Desktop\gdb.zip 68 2008-11-19 11:34:23 FALSE 0 C:\Documents and Settings\Administrator\Desktop\recovered files 69 2008-11-19 18:51:45 FALSE 2727936 C:\Documents and Settings\Administrator\Desktop\GetDataBackforFAT-v3.63_PConline 70 2008-11-19 18:51:45 FALSE 5169152 C:\Documents and Settings\Administrator\Desktop\Uneraser_Setup(2).exe 71 2008-11-19 18:51:45 FALSE 5169152 C:\Documents and Settings\Administrator\Desktop\Uneraser_Setup.exe rifiuti2-0.8.2/test/samples/INFO2-sample1.xml000066400000000000000000000060301477543443600206200ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/INFO2-sample2000066400000000000000000000036741477543443600200350ustar00rootroot00000000000000 YYC:\WINDOWS\All Users\Desktop\Connect to the Internet.LNKternet.LNKYCC0C&CY0CYbCwY0LCCCYdY0Y\JYCHC4YtCzC:\WINDOWS\Desktop\Online Servicese Services1WS\Desktop\Online Services\AYCC0C&CY0CYbCwY0LCCCYdY0Y\JYCHC4Y Csz:\WINDOWS\Desktop\IE9-WindowsVista-x64-enu.exe64-enu.exe,Y4CC0C&CY04CYbCwY0LCC8CYdY0Y\JY8CH8C4Y8C?NMzC:\My Documents\Rsum.txt.txtm.txt.txtD\DC3.TXTz<v=hWEC0C&C0WEbCw㐁0LCCWE|0\J<WEHWE?D`A{C:\WINDOWS\Desktop\winzip100.exezip100.exeDC4.EXE| @Y̺CC0C&C0̺CYbCw㐁0LCCкCYdY0Y\JYкCHкC4YC {_:\WINDOWS\Desktop\111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111CF{C:\WINDOWS\Desktop\1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345C`F1{rifiuti2-0.8.2/test/samples/INFO2-sample2-wrong-enc.txt000066400000000000000000000020531477543443600225360ustar00rootroot00000000000000Recycle bin path: 'INFO2-sample2' Version: 4 OS Guess: Windows 98 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 0 2015-04-20 00:07:36 FALSE 32768 C:\WINDOWS\All Users\Desktop\Connect to the Internet.LNK 1 2015-04-20 00:07:42 FALSE 32768 C:\WINDOWS\Desktop\Online Services 2 2015-04-20 00:09:43 TRUE 524288 C:\WINDOWS\Desktop\IE9-WindowsVista-x64-enu.exe 3 2015-04-20 01:04:33 FALSE 32768 C:\My Documents\R駸um<\E9>.txt.txt 4 2015-04-20 01:05:01 FALSE 6258688 C:\WINDOWS\Desktop\winzip100.exe 5 2015-04-20 01:05:41 TRUE 32768 C:\WINDOWS\Desktop\111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 6 2015-04-20 01:06:12 FALSE 32768 C:\WINDOWS\Desktop\1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 rifiuti2-0.8.2/test/samples/INFO2-sample2.json000066400000000000000000000026451477543443600210020ustar00rootroot00000000000000{ "format": "file", "version": 4, "path": "INFO2-sample2", "records": [ {"index": 0, "time": "2015-04-20T00:07:36Z", "gone": false, "size": 32768, "path": "C:\\WINDOWS\\All Users\\Desktop\\Connect to the Internet.LNK"}, {"index": 1, "time": "2015-04-20T00:07:42Z", "gone": false, "size": 32768, "path": "C:\\WINDOWS\\Desktop\\Online Services"}, {"index": 2, "time": "2015-04-20T00:09:43Z", "gone": true, "size": 524288, "path": "C:\\WINDOWS\\Desktop\\IE9-WindowsVista-x64-enu.exe"}, {"index": 3, "time": "2015-04-20T01:04:33Z", "gone": false, "size": 32768, "path": "C:\\My Documents\\Résumé.txt.txt"}, {"index": 4, "time": "2015-04-20T01:05:01Z", "gone": false, "size": 6258688, "path": "C:\\WINDOWS\\Desktop\\winzip100.exe"}, {"index": 5, "time": "2015-04-20T01:05:41Z", "gone": true, "size": 32768, "path": "C:\\WINDOWS\\Desktop\\111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"}, {"index": 6, "time": "2015-04-20T01:06:12Z", "gone": false, "size": 32768, "path": "C:\\WINDOWS\\Desktop\\1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"}, ] } rifiuti2-0.8.2/test/samples/INFO2-sample2.txt000066400000000000000000000020501477543443600206360ustar00rootroot00000000000000Recycle bin path: 'INFO2-sample2' Version: 4 OS Guess: Windows 98 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path 0 2015-04-20 00:07:36 FALSE 32768 C:\WINDOWS\All Users\Desktop\Connect to the Internet.LNK 1 2015-04-20 00:07:42 FALSE 32768 C:\WINDOWS\Desktop\Online Services 2 2015-04-20 00:09:43 TRUE 524288 C:\WINDOWS\Desktop\IE9-WindowsVista-x64-enu.exe 3 2015-04-20 01:04:33 FALSE 32768 C:\My Documents\Résumé.txt.txt 4 2015-04-20 01:05:01 FALSE 6258688 C:\WINDOWS\Desktop\winzip100.exe 5 2015-04-20 01:05:41 TRUE 32768 C:\WINDOWS\Desktop\111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 6 2015-04-20 01:06:12 FALSE 32768 C:\WINDOWS\Desktop\1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 rifiuti2-0.8.2/test/samples/INFO2-sample2.xml000066400000000000000000000031661477543443600206300ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/INFO2-trunc000066400000000000000000000076571477543443600176320ustar00rootroot00000000000000 C:\DOCUME~1\Nobody\ୱ\ABCs~1.TXT\ABCsWr.txtf|ld~wH1w|wH  @wpgC:\Documents and Settings\Nobody\Lhb\ABCeXeW[eN.txtd~wX1wx·wx ʶwwwx@ IoyJoyE wf@@d~wxwPYw ww$ ww Ccxpx jH jH  f @~w @Tww8 f{w|FyC:\DOCUME~1\Nobody\ୱ\MOZILL~1.LNK\Mozilla Firefox.lnkh |IlId~wH1w|IwH  @w@2tC:\Documents and Settings\Nobody\Lhb\Mozilla Firefox.lnkxI·wxM ʶwwwxI@M IIoyJoyEI w I@I@Id~wxwPIYwM ww ww CcxpxI vvM h  III@~w @IIITwwҋ 0S f{w|FyC:\Documents and Settings\Nobody\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.bmpjC:\Documents and Settings\Nobody\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.bmpC:\temp\ODULLU~1.DOCr ?????.doc0R ,wH  RRd~wH1wRwH  @w,0u C:\temp\dll mT-NeW[ *4CJD.doc`R,w.<\uDEE2>.ahk $I77T7B2.ahk 2019-04-14 11:44:43 TRUE 324 D:\<\uDC82><\uD800>𐌰𐎅𐠔𨋢.ahk $I77T7B3.ahk 2019-04-14 11:44:43 TRUE 324 D:\𐂂𐌰<\u05FF>𐎅𐠔𨋢.ahk rifiuti2-0.8.2/test/samples/dir-bad-uni/000077500000000000000000000000001477543443600200155ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-bad-uni/$I77T7B0.ahk000066400000000000000000000010401477543443600215040ustar00rootroot00000000000000D0QsD:\؂0؅`.ahkrifiuti2-0.8.2/test/samples/dir-bad-uni/$I77T7B1.ahk000066400000000000000000000010401477543443600215050ustar00rootroot00000000000000D0QsD:\؂0؅`..ahkrifiuti2-0.8.2/test/samples/dir-bad-uni/$I77T7B2.ahk000066400000000000000000000010401477543443600215060ustar00rootroot00000000000000D0QsD:\0؅`.ahkrifiuti2-0.8.2/test/samples/dir-bad-uni/$I77T7B3.ahk000066400000000000000000000010401477543443600215070ustar00rootroot00000000000000D0QsD:\؂0؅`.ahkrifiuti2-0.8.2/test/samples/dir-bad-uni/desktop.ini000066400000000000000000000002011477543443600221600ustar00rootroot00000000000000[.ShellClassInfo] CLSID={645FF040-5081-101B-9F08-00AA002F954E} LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-8964 rifiuti2-0.8.2/test/samples/dir-badfiles/000077500000000000000000000000001477543443600202475ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-badfiles/$I4OZLXW.bmp000066400000000000000000000001541477543443600221330ustar00rootroot00000000000000) (\\WIN-163RLA0PH3N\somewhere\ .bmprifiuti2-0.8.2/test/samples/dir-badfiles/$IF47Q09000066400000000000000000000000341477543443600211760ustar00rootroot00000000000000P,z'rifiuti2-0.8.2/test/samples/dir-badfiles/$IW0RYW0.rtf000066400000000000000000000001521477543443600221070ustar00rootroot00000000000000@*'\\WIN-163RLA0PH3N\somewhere\hahaha.rtfrifiuti2-0.8.2/test/samples/dir-badfiles/$IX1JBL3.djvu000066400000000000000000000001331477543443600222160ustar00rootroot00000000000000) (\\WIN-163RLA0PH3N\somewhere\ rifiuti2-0.8.2/test/samples/dir-empty.txt000066400000000000000000000002111477543443600203670ustar00rootroot00000000000000Recycle bin path: 'dir-empty' Version: ??? (empty folder) OS detection failed Time zone: UTC [+0000] Index Deleted Time Gone? Size Path rifiuti2-0.8.2/test/samples/dir-empty.xml000066400000000000000000000001741477543443600203600ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/dir-empty/000077500000000000000000000000001477543443600176345ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-empty/desktop.ini000066400000000000000000000002011477543443600217770ustar00rootroot00000000000000[.ShellClassInfo] CLSID={645FF040-5081-101B-9F08-00AA002F954E} LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-8964 rifiuti2-0.8.2/test/samples/dir-isolated-idx.txt000066400000000000000000000006331477543443600216270ustar00rootroot00000000000000Recycle bin path: '$IKEGS1G' Version: 2 OS Guess: Windows 10 or above Time zone: UTC [+0000] Index Deleted Time Gone? Size Path $IKEGS1G 2015-04-04 17:19:52 ??? 0 C:\Users\tester\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 rifiuti2-0.8.2/test/samples/dir-mixed/000077500000000000000000000000001477543443600176045ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-mixed/$IDNLPD4.exe000066400000000000000000000001221477543443600214040ustar00rootroot00000000000000P `N zC:\Temp\FAU\FAU.x86\dd.exerifiuti2-0.8.2/test/samples/dir-mixed/$IEQWWMF.exe000066400000000000000000000010401477543443600214650ustar00rootroot00000000000000` `{*C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\fmdata.exerifiuti2-0.8.2/test/samples/dir-sample1.json000066400000000000000000000056211477543443600207370ustar00rootroot00000000000000{ "format": "dir", "version": 1, "path": "dir-sample1", "records": [ {"index": "$IUVFB0M.rtf", "time": "2007-09-21T06:32:46Z", "gone": false, "size": 155, "path": "C:\\Users\\student\\Desktop\\New Rich Text Document.rtf"}, {"index": "$I0JGHX7", "time": "2007-09-21T06:47:49Z", "gone": true, "size": 0, "path": "C:\\Users\\student\\Desktop\\New Folder 1"}, {"index": "$I1IS2OK.txt", "time": "2007-09-21T06:48:13Z", "gone": false, "size": 0, "path": "C:\\Users\\student\\Desktop\\New Text Document blah.txt"}, {"index": "$IYAR1YY.exe", "time": "2007-09-21T07:54:23Z", "gone": true, "size": null, "path": "C:\\dd.exe"}, {"index": "$I95CUKU", "time": "2007-09-21T08:02:59Z", "gone": true, "size": 4096, "path": "C:\\Users\\student\\Downloads\\fau-1.3.0.2355(rc3)\\fau\\FAU.x86\\sparsefile"}, {"index": "$IHMU3NR.zip", "time": "2007-09-21T08:17:19Z", "gone": true, "size": 5025829, "path": "C:\\Users\\student\\Downloads\\fau-1.3.0.2355(rc3).zip"}, {"index": "$I7FV8IY.exe", "time": "2007-09-21T08:23:18Z", "gone": true, "size": 153478296, "path": "C:\\Users\\student\\Downloads\\VMware-server-installer-1.0.4-56528.exe"}, {"index": "$IMG2SSB", "time": "2007-09-21T08:28:57Z", "gone": true, "size": 0, "path": "C:\\Users\\student\\Desktop\\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012"}, {"index": "$IZK01YL.txt", "time": "2007-09-21T08:31:35Z", "gone": true, "size": 11, "path": "C:\\Users\\student\\Desktop\\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012\\1234567.txt"}, {"index": "$I1TDH1G.exe", "time": "2007-09-21T08:38:30Z", "gone": true, "size": 704512, "path": "C:\\Users\\student\\Downloads\\fau-1.3.0.2355(rc3)\\fau\\FAU.x86\\nc.exe"}, {"index": "$IEQWWMF.exe", "time": "2007-09-21T08:38:30Z", "gone": true, "size": 679936, "path": "C:\\Users\\student\\Downloads\\fau-1.3.0.2355(rc3)\\fau\\FAU.x86\\fmdata.exe"}, {"index": "$IFRN1CZ.exe", "time": "2007-09-21T08:38:30Z", "gone": true, "size": 110592, "path": "C:\\Users\\student\\Downloads\\fau-1.3.0.2355(rc3)\\fau\\FAU.x86\\wipe.exe"}, {"index": "$IW527XU.exe", "time": "2007-09-21T08:38:30Z", "gone": true, "size": 331776, "path": "C:\\Users\\student\\Downloads\\fau-1.3.0.2355(rc3)\\fau\\FAU.x86\\volume_dump.exe"}, {"index": "$IC6GEAW.exe", "time": "2007-09-21T08:50:16Z", "gone": true, "size": null, "path": "C:\\Users\\student\\Downloads\\fau-1.3.0.2355(rc3)\\fau\\FAU.x86\\dd.exe"}, {"index": "$IZUFRX4.vmdk", "time": "2007-09-21T09:22:25Z", "gone": true, "size": 10737418240, "path": "C:\\Virtual Machines\\Windows XP Professional\\Windows XP Professional-flat.vmdk"}, ] } rifiuti2-0.8.2/test/samples/dir-sample1.txt000066400000000000000000000040001477543443600205730ustar00rootroot00000000000000Recycle bin path: 'dir-sample1' Version: 1 OS Guess: Windows Vista - 8.1 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path $IUVFB0M.rtf 2007-09-21 06:32:46 FALSE 155 C:\Users\student\Desktop\New Rich Text Document.rtf $I0JGHX7 2007-09-21 06:47:49 TRUE 0 C:\Users\student\Desktop\New Folder 1 $I1IS2OK.txt 2007-09-21 06:48:13 FALSE 0 C:\Users\student\Desktop\New Text Document blah.txt $IYAR1YY.exe 2007-09-21 07:54:23 TRUE ??? C:\dd.exe $I95CUKU 2007-09-21 08:02:59 TRUE 4096 C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\sparsefile $IHMU3NR.zip 2007-09-21 08:17:19 TRUE 5025829 C:\Users\student\Downloads\fau-1.3.0.2355(rc3).zip $I7FV8IY.exe 2007-09-21 08:23:18 TRUE 153478296 C:\Users\student\Downloads\VMware-server-installer-1.0.4-56528.exe $IMG2SSB 2007-09-21 08:28:57 TRUE 0 C:\Users\student\Desktop\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012 $IZK01YL.txt 2007-09-21 08:31:35 TRUE 11 C:\Users\student\Desktop\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012\1234567.txt $I1TDH1G.exe 2007-09-21 08:38:30 TRUE 704512 C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\nc.exe $IEQWWMF.exe 2007-09-21 08:38:30 TRUE 679936 C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\fmdata.exe $IFRN1CZ.exe 2007-09-21 08:38:30 TRUE 110592 C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\wipe.exe $IW527XU.exe 2007-09-21 08:38:30 TRUE 331776 C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\volume_dump.exe $IC6GEAW.exe 2007-09-21 08:50:16 TRUE ??? C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\dd.exe $IZUFRX4.vmdk 2007-09-21 09:22:25 TRUE 10737418240 C:\Virtual Machines\Windows XP Professional\Windows XP Professional-flat.vmdk rifiuti2-0.8.2/test/samples/dir-sample1.xml000066400000000000000000000063221477543443600205650ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/dir-sample1/000077500000000000000000000000001477543443600200405ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-sample1/$I0JGHX7000066400000000000000000000010401477543443600210420ustar00rootroot00000000000000SC:\Users\student\Desktop\New Folder 1rifiuti2-0.8.2/test/samples/dir-sample1/$I1IS2OK.txt000066400000000000000000000010401477543443600216610ustar00rootroot00000000000000`daC:\Users\student\Desktop\New Text Document blah.txtrifiuti2-0.8.2/test/samples/dir-sample1/$I1TDH1G.exe000066400000000000000000000010401477543443600216030ustar00rootroot00000000000000 `{*C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\nc.exerifiuti2-0.8.2/test/samples/dir-sample1/$I7FV8IY.exe000066400000000000000000000010401477543443600216470ustar00rootroot00000000000000% N>(C:\Users\student\Downloads\VMware-server-installer-1.0.4-56528.exerifiuti2-0.8.2/test/samples/dir-sample1/$I95CUKU000066400000000000000000000010401477543443600210600ustar00rootroot00000000000000%C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\sparsefilerifiuti2-0.8.2/test/samples/dir-sample1/$IC6GEAW.exe000066400000000000000000000010371477543443600216350ustar00rootroot00000000000000P/Tn,C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\dd.exerifiuti2-0.8.2/test/samples/dir-sample1/$IEQWWMF.exe000066400000000000000000000010401477543443600217210ustar00rootroot00000000000000` `{*C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\fmdata.exerifiuti2-0.8.2/test/samples/dir-sample1/$IFRN1CZ.exe000066400000000000000000000010401477543443600216560ustar00rootroot00000000000000`{*C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\wipe.exerifiuti2-0.8.2/test/samples/dir-sample1/$IHMU3NR.zip000066400000000000000000000010401477543443600217100ustar00rootroot00000000000000%L'C:\Users\student\Downloads\fau-1.3.0.2355(rc3).ziprifiuti2-0.8.2/test/samples/dir-sample1/$IMG2SSB000066400000000000000000000010401477543443600210700ustar00rootroot00000000000000Ps)C:\Users\student\Desktop\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012rifiuti2-0.8.2/test/samples/dir-sample1/$IUVFB0M.rtf000066400000000000000000000010401477543443600216640ustar00rootroot00000000000000,29C:\Users\student\Desktop\New Rich Text Document.rtfrifiuti2-0.8.2/test/samples/dir-sample1/$IW527XU.exe000066400000000000000000000010401477543443600216340ustar00rootroot00000000000000PZ}*C:\Users\student\Downloads\fau-1.3.0.2355(rc3)\fau\FAU.x86\volume_dump.exerifiuti2-0.8.2/test/samples/dir-sample1/$IYAR1YY.exe000066400000000000000000000010371477543443600217170ustar00rootroot00000000000000Pp$C:\dd.exerifiuti2-0.8.2/test/samples/dir-sample1/$IZK01YL.txt000066400000000000000000000010401477543443600217030ustar00rootroot00000000000000 bt)C:\Users\student\Desktop\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012\1234567.txtrifiuti2-0.8.2/test/samples/dir-sample1/$IZUFRX4.vmdk000066400000000000000000000010401477543443600220750ustar00rootroot00000000000000`p0C:\Virtual Machines\Windows XP Professional\Windows XP Professional-flat.vmdkrifiuti2-0.8.2/test/samples/dir-sample1/$R1IS2OK.txt000066400000000000000000000000001477543443600216650ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-sample1/$RUVFB0M.rtf000066400000000000000000000002301477543443600216750ustar00rootroot00000000000000{\rtf1\ansi\deff0{\fonttbl{\f0\fswiss\fcharset0 Arial;}} {\*\generator Msftedit 5.41.21.2506;}\viewkind4\uc1\pard\lang1033\f0\fs20 lalala hahaha\par } rifiuti2-0.8.2/test/samples/dir-single-idx.txt000066400000000000000000000006521477543443600213050ustar00rootroot00000000000000Recycle bin path: 'dir-win10-01/$IKEGS1G' Version: 2 OS Guess: Windows 10 or above Time zone: UTC [+0000] Index Deleted Time Gone? Size Path $IKEGS1G 2015-04-04 17:19:52 FALSE 0 C:\Users\tester\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 rifiuti2-0.8.2/test/samples/dir-win10-01.json000066400000000000000000000023141477543443600205450ustar00rootroot00000000000000{ "format": "dir", "version": 2, "path": "dir-win10-01", "records": [ {"index": "$IKEGS1G", "time": "2015-04-04T17:19:52Z", "gone": false, "size": 0, "path": "C:\\Users\\tester\\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"}, {"index": "$IQ7LAXT.png", "time": "2015-04-04T17:20:01Z", "gone": false, "size": 6455, "path": "C:\\Users\\tester\\Pictures\\web-canvas.png"}, {"index": "$I7R52EG.txt", "time": "2015-04-04T17:24:09Z", "gone": false, "size": 14, "path": "C:\\Temp\\foobat.txt.txt"}, {"index": "$IBBFODN", "time": "2015-04-07T23:19:35Z", "gone": true, "size": 7, "path": "C:\\Temp\\𨳊𨶙閪邨鰂"}, {"index": "$IHO61YT", "time": "2015-04-07T23:32:07Z", "gone": true, "size": 12884901888, "path": "C:\\Temp\\largesparsefile"}, {"index": "$IROMPZ0.exe", "time": "2015-04-19T10:49:59Z", "gone": true, "size": 1761792, "path": "C:\\Temp\\FAU\\FAU.x64\\dd.exe"}, {"index": "$IDNLPD4.exe", "time": "2015-04-19T10:50:51Z", "gone": true, "size": 872448, "path": "C:\\Temp\\FAU\\FAU.x86\\dd.exe"}, ] } rifiuti2-0.8.2/test/samples/dir-win10-01.txt000066400000000000000000000015131477543443600204130ustar00rootroot00000000000000Recycle bin path: 'dir-win10-01' Version: 2 OS Guess: Windows 10 or above Time zone: UTC [+0000] Index Deleted Time Gone? Size Path $IKEGS1G 2015-04-04 17:19:52 FALSE 0 C:\Users\tester\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 $IQ7LAXT.png 2015-04-04 17:20:01 FALSE 6455 C:\Users\tester\Pictures\web-canvas.png $I7R52EG.txt 2015-04-04 17:24:09 FALSE 14 C:\Temp\foobat.txt.txt $IBBFODN 2015-04-07 23:19:35 TRUE 7 C:\Temp\𨳊𨶙閪邨鰂 $IHO61YT 2015-04-07 23:32:07 TRUE 12884901888 C:\Temp\largesparsefile $IROMPZ0.exe 2015-04-19 10:49:59 TRUE 1761792 C:\Temp\FAU\FAU.x64\dd.exe $IDNLPD4.exe 2015-04-19 10:50:51 TRUE 872448 C:\Temp\FAU\FAU.x86\dd.exe rifiuti2-0.8.2/test/samples/dir-win10-01.xml000066400000000000000000000026171477543443600204020ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/dir-win10-01/000077500000000000000000000000001477543443600176525ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-win10-01/$I7R52EG.txt000066400000000000000000000001121477543443600214350ustar00rootroot000000000000008)nC:\Temp\foobat.txt.txtrifiuti2-0.8.2/test/samples/dir-win10-01/$IBBFODN000066400000000000000000000000741477543443600207050ustar00rootroot00000000000000OqC:\Temp\ccؙݪrifiuti2-0.8.2/test/samples/dir-win10-01/$IDNLPD4.exe000066400000000000000000000001221477543443600214520ustar00rootroot00000000000000P `N zC:\Temp\FAU\FAU.x86\dd.exerifiuti2-0.8.2/test/samples/dir-win10-01/$IHO61YT000066400000000000000000000001141477543443600207000ustar00rootroot00000000000000NqC:\Temp\largesparsefilerifiuti2-0.8.2/test/samples/dir-win10-01/$IKEGS1G000066400000000000000000000010121477543443600206650ustar00rootroot00000000000000Z;nC:\Users\tester\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890rifiuti2-0.8.2/test/samples/dir-win10-01/$IQ7LAXT.png000066400000000000000000000001541477543443600215150ustar00rootroot000000000000007nn(C:\Users\tester\Pictures\web-canvas.pngrifiuti2-0.8.2/test/samples/dir-win10-01/$IROMPZ0.exe000066400000000000000000000001221477543443600215140ustar00rootroot00000000000000@ݔzC:\Temp\FAU\FAU.x64\dd.exerifiuti2-0.8.2/test/samples/dir-win10-01/$R7R52EG.txt000066400000000000000000000000161477543443600214510ustar00rootroot00000000000000Blah blah blahrifiuti2-0.8.2/test/samples/dir-win10-01/$RKEGS1G000066400000000000000000000000001477543443600206720ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/dir-win10-01/$RQ7LAXT.png000066400000000000000000000144671477543443600215420ustar00rootroot00000000000000PNG  IHDRl~QgAMA asRGBPLTE rxwDFH Z[[-/189<ABDG{{UVY[]a7:=㬰kmp58;<>Bmorefjwm69="$&37:(+-9}h9AAwꌎpãrbi%-"ø~uEIIϼTlN5WW,j9{)}޿[(,WA8DulblgaJe̷CB&MÂ`mpgWI쉌¤Z,M|ZOϓ[N(/CB'2_tHkߧb6a|<\BY73I{}p֣|5PYc;{^j콻X[VM8Ī鞞-?Ve13lrCv=u.N.ރF|n~d;_+ sZ$@QABEqjQ/p=SO,{~$F«TJHZFk\(mD-TT#*!X0I dH(1~-窔T.gD-HEBxDOT ,~ڰ-(8Gggδ9axUH*}O^pa}kBT$GGӯ0f P*E"t?T_p9s=]> /\$x uP"a3lwg$},(&uܝ3{ $l<61{->a ܽz?g-+H%`ľ{2a;tw׮ぁ N ُ} >[b?]rE&l0&@?25Cp)$ ɔX$<#/#G_7ߘx18@(gdz '~]}xwO/T( )lg[Aў={1~͚5 W&>! Ӎ AٱpOuw4JuG/,1%l^m=V@[ }i֬wf&B-! j ~!pGêǷÉ<d>Z2Z\#;amIrEl*i*lK <:24npӳe:e2п}ǎ_@m8PW%%[<vV$t۶,˘'}!/@k/_b6?hB!'x:Lg߸?4G_ӟxvAQQ[ׯ_/0\I$Fq$g?4h^~Pq(}ڹs=u}jR 9HU&J$=^'?Dş_cۆ1DZώيB')a3`!G(303^wnl\V 1ށw~ܹoӳIu\'L-NS*0,㶊%<n*m =ysmr4!&1;ҺO0ܮ?bG"=& BY6`b/NV)~ c~++aw{fg鼜 T~x&FiggݷV~"[cif[߮rvo*$Z6lTڑOnn۝ =Bw:uXvZ1lDG~YETi45][>1 qꅭ==Y8nBB"MLVKLcz `M|uDؕM5o]y9^L8mlyzZgϡ{*B:L*`7WG̸؃-j |||bDTT%,ys7jzϗĆ{3믕h

>KDh4⪬G?_SU$ɿ҃w\{ӦlB@/C JۉDPDD͒kЎ5Wh4%ĝ q\</^ ./U0}Z}äb(t˙L`#<#~δ}J|@UJPvFxn6{`l8:`cIA6ܸ熍6IЭX)=v%=tqٔYDI-#t0().FolL^w߾}i4P GDz)qw(2O@;ڕ;pCXp0=lPll0^_{Id^dɵҬPh:7Ŏސ Rym #…K{o .YU 2fiS}Ig@% ؇2|eQ[˖5-&B!)z:]ḏbUJ٥rO :&1`&vDv8|Vq]P޴Tx _xȑ+S+Z_;{)4*!!+3 3S2[%^6}2861|!ƕ[qUz*7A} @9ꎋ?={6^Y~6!~玫"M؛Ur2B§%2>Zqn{퇗£XKrݩ{pa0gZ;KB%3 p6fC1L􌽧M |d-(zJ^RV5. XʰK/--3]ZVLw>p|\of& 8`lxDrA#Ტy-g6nnvjiy{DN زӳBC҄Qe%=DG 7FRRL,{ueA/G+*߼y!1'޻ 'ZtM&LPG)K`C%m`#۳'Om(Xa0 )5e*P-AFv:"Jr#(sOERc7Jj)RVˏ.]ҖG-t|oGGkDz3їqtC,ݤ?Ѥp'U".s|q,S%%7.z;Յ G<ǒ~+, q&z5'NCMj2RP,"cC. <7() -Ym\A$KaR|AIBo@ 𚛈M.Rll0Uf|CWr@KBooG<Oۖ' ?LQQ[S)a5f Ą$ƉVgffxxk[r?U___I1k%lo~G/hL!Lt߂+iZC(} $HTLvΔN|حanij ~DIX3lbR%pG?n3'ΞMEFGnҍߊWtBC3^)E{>-T#?YrwҲ.\TI5R~OԩDqHȌ/?3Qw:W'M1}k. _WsR̷ y2(w|z{YGmkxY=|6l-rys$땴T_MZCr^;zO#8oW/-T 52/ὄn! 6͵}1"H__ԺG'SN{TJj?ecŰ7DR5kkE}XCrnvrVSRflˢ=7 kfoقmr2z[5/OHL(C)cwƔI řzj @_sgF|aR3g ܣB(q,#oL9ߝg??v2 Nh8b1,Ͼ1e;9>qBR Çgq1nDh5C j5%͉cR1,8c2O)oN0&OS?~$!EaԫZ&N N2ᯍ}.L%St1ɘ3dž9ZZ ?PI:U7?Jgho3;?O?(K?+I>IENDB`rifiuti2-0.8.2/test/samples/dir-win10-01/desktop.ini000066400000000000000000000002011477543443600220150ustar00rootroot00000000000000[.ShellClassInfo] CLSID={645FF040-5081-101B-9F08-00AA002F954E} LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-8964 rifiuti2-0.8.2/test/samples/japanese-path-dir.txt000066400000000000000000000002251477543443600217560ustar00rootroot00000000000000Recycle bin path: './ごみ箱/dir-empty' Version: ??? (empty folder) OS detection failed Time zone: UTC [+0000] Index Deleted Time Gone? Size Path rifiuti2-0.8.2/test/samples/japanese-path-file.txt000066400000000000000000000002251477543443600221170ustar00rootroot00000000000000Recycle bin path: './ごみ箱/INFO2-empty' Version: 5 OS Guess: Windows 2000, XP or 2003 Time zone: UTC [+0000] Index Deleted Time Gone? Size Path rifiuti2-0.8.2/test/samples/ごみ箱/000077500000000000000000000000001477543443600211425ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/ごみ箱/INFO2-empty000066400000000000000000000000241477543443600230320ustar00rootroot00000000000000 rifiuti2-0.8.2/test/samples/ごみ箱/dir-empty/000077500000000000000000000000001477543443600230545ustar00rootroot00000000000000rifiuti2-0.8.2/test/samples/ごみ箱/dir-empty/desktop.ini000066400000000000000000000002011477543443600252170ustar00rootroot00000000000000[.ShellClassInfo] CLSID={645FF040-5081-101B-9F08-00AA002F954E} LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-8964 rifiuti2-0.8.2/test/test_glib_iconv.c000066400000000000000000000012151477543443600175730ustar00rootroot00000000000000/* * Copyright (C) 2019-2024, Abel Cheung * rifiuti2 is released under Revised BSD License. * Please see LICENSE file for more info. */ #include #include bool conv_established (char *enc) { GIConv cd = g_iconv_open ("UTF-8", enc); if (cd != (GIConv) -1) { g_iconv_close (cd); return true; } return false; } int main (int argc, char **argv) { char *enc; for (int i = 1; i < argc; i++) { enc = argv[i]; if (*enc == '\0') continue; if (conv_established (enc)) { g_print("%s\n", enc); return 0; } } return 1; }