Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package jefferson for openSUSE:Factory 
checked in at 2022-10-25 11:20:06
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/jefferson (Old)
 and      /work/SRC/openSUSE:Factory/.jefferson.new.2275 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "jefferson"

Tue Oct 25 11:20:06 2022 rev:2 rq:1030940 version:0.4.1+git.20220705

Changes:
--------
--- /work/SRC/openSUSE:Factory/jefferson/jefferson.changes      2020-08-19 
18:52:12.479700002 +0200
+++ /work/SRC/openSUSE:Factory/.jefferson.new.2275/jefferson.changes    
2022-10-25 11:20:32.838208442 +0200
@@ -1,0 +2,42 @@
+Fri Oct 14 13:16:31 UTC 2022 - mar...@gmx.de
+
+- Update to version 0.4.1+git.20220705:
+  * Fix is_safe_path call to use absolute path rather than
+    relative path to execution directory.
+  * Fix extraction of files with size greater than one erase block.
+  * Remove unnecessary log call.
+  * Keep xattr, xref, and summary nodetypes in order to propely
+    identify unknown node types.
+  * Remove handling of xref nodes, xattr nodes, summary nodes.
+  * Use inode indexed dicts for inodes, dirent, and xref entries.
+  * Simplify filesystem structure.
+  * Fix duplicate inodes handling.
+  * Fix support for python 3.10 by pinning python-lzo to 1.14.
+  * Better handling of decompression error + simpler endianness
+    logging.
+  * Memory-mapped file support
+  * Add support for LZO compression.
+  * Revert the symlink path traversal check as it does not present
+    a direct risk to normal end users. Those checks can be
+    implemented by other tools where required.
+  * Fix path traversal security vulnerability by canonicalizing
+    path names of every inodes and discarding inodes with a path
+    pointing outside of the extraction directory.
+  * Autodetect endianness rather than scan the filesystem twice,
+    one for each possible endianness. We make the assumption that
+    a JFFS2 has always a fixed endianness and that nodes won't
+    switch between endianness in the middle of a filesystem.
+  * Add support for JFFS2 old magic signature (0x1984).
+  * pin cstruct version to 2.1 so we don't end up with breaking
+    API changes in the future. Moving to more recent versions
+    should be done manually once it's been tested that jefferson
+    still works with the newly released version of cstruct.
+  * Fix set_endianness to support cstruct version 2.1.
+  * Converted python2 encode("hex") to python3 hex()
+  * Switched to lzma in python stdlib
+  * Convert to python3.
+- Drop patches:
+  * 18.patch
+  * jefferson-use-pylzma.patch
+
+-------------------------------------------------------------------

Old:
----
  18.patch
  jefferson-0.3+git.20160616.tar.xz
  jefferson-use-pylzma.patch

New:
----
  jefferson-0.4.1+git.20220705.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ jefferson.spec ++++++
--- /var/tmp/diff_new_pack.adn8Tc/_old  2022-10-25 11:20:33.794210562 +0200
+++ /var/tmp/diff_new_pack.adn8Tc/_new  2022-10-25 11:20:33.798210571 +0200
@@ -1,7 +1,8 @@
 #
 # spec file for package jefferson
 #
-# Copyright (c) 2020, Martin Hauke <mar...@gmx.de>
+# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2020-2022, Martin Hauke <mar...@gmx.de>
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,23 +18,20 @@
 
 
 Name:           jefferson
-Version:        0.3+git.20160616
+Version:        0.4.1+git.20220705
 Release:        0
 Summary:        JFFS2 filesystem extraction tool
 License:        MIT
 Group:          Development/Tools/Other
 URL:            https://github.com/sviehb/jefferson
 Source:         %{name}-%{version}.tar.xz
-# Add support for python3
-Patch0:         https://github.com/sviehb/jefferson/pull/18.patch
-Patch1:         jefferson-use-pylzma.patch
 BuildRequires:  fdupes
 BuildRequires:  help2man
 BuildRequires:  python-rpm-macros
-BuildRequires:  python3-cstruct >= 1.5
-BuildRequires:  python3-setuptools
+BuildRequires:  python3-cstruct >= 2.1
 BuildRequires:  python3-pylzma
-Requires:       python3-cstruct >= 1.5
+BuildRequires:  python3-setuptools
+Requires:       python3-cstruct >= 2.1
 Requires:       python3-pylzma
 BuildArch:      noarch
 
@@ -52,8 +50,6 @@
 
 %prep
 %setup -q
-%patch0 -p1
-%patch1 -p1
 chmod -x README.md
 
 %build

++++++ _service ++++++
--- /var/tmp/diff_new_pack.adn8Tc/_old  2022-10-25 11:20:33.858210704 +0200
+++ /var/tmp/diff_new_pack.adn8Tc/_new  2022-10-25 11:20:33.862210712 +0200
@@ -5,7 +5,7 @@
     <param name="revision">master</param>
     <param name="scm">git</param>
     <param name="changesgenerate">enable</param>
-    <param name="versionformat">0.3+git.%cd</param>
+    <param name="versionformat">0.4.1+git.%cd</param>
   </service>
   <service mode="disabled" name="recompress">
     <param name="file">*.tar</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.adn8Tc/_old  2022-10-25 11:20:33.882210757 +0200
+++ /var/tmp/diff_new_pack.adn8Tc/_new  2022-10-25 11:20:33.886210766 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/sviehb/jefferson.git</param>
-              <param 
name="changesrevision">6f9169bad3ceb4e212fae62ad710eeca3350226b</param></service></servicedata>
+              <param 
name="changesrevision">ecc51d7ab500e6286c80154f89388e3173784081</param></service></servicedata>
 (No newline at EOF)
 

++++++ jefferson-0.3+git.20160616.tar.xz -> jefferson-0.4.1+git.20220705.tar.xz 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/FETCH_HEAD 
new/jefferson-0.4.1+git.20220705/.git/FETCH_HEAD
--- old/jefferson-0.3+git.20160616/.git/FETCH_HEAD      1970-01-01 
01:00:00.000000000 +0100
+++ new/jefferson-0.4.1+git.20220705/.git/FETCH_HEAD    2022-07-05 
10:56:07.000000000 +0200
@@ -0,0 +1,4 @@
+ecc51d7ab500e6286c80154f89388e3173784081               branch 'master' of 
https://github.com/sviehb/jefferson
+5d8e2f0b2f4e6d0999382614f37981d6ca6c3d9f       not-for-merge   branch 
'fix-inode-versioning' of https://github.com/sviehb/jefferson
+684069ea678c388d7fe58a61e671e4fa5a2a3c3f       not-for-merge   branch 
'fix-lzo-python310' of https://github.com/sviehb/jefferson
+da60d313db2be447b6f8c4ff2cdcca40292faf6d       not-for-merge   branch 
'fix-path-traversal' of https://github.com/sviehb/jefferson
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/ORIG_HEAD 
new/jefferson-0.4.1+git.20220705/.git/ORIG_HEAD
--- old/jefferson-0.3+git.20160616/.git/ORIG_HEAD       2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/ORIG_HEAD     2022-07-05 
10:56:07.000000000 +0200
@@ -1 +1 @@
-6f9169bad3ceb4e212fae62ad710eeca3350226b
+ecc51d7ab500e6286c80154f89388e3173784081
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/config 
new/jefferson-0.4.1+git.20220705/.git/config
--- old/jefferson-0.3+git.20160616/.git/config  2016-06-16 16:41:06.000000000 
+0200
+++ new/jefferson-0.4.1+git.20220705/.git/config        2022-07-05 
10:56:07.000000000 +0200
@@ -1,11 +1,15 @@
 [core]
-       repositoryformatversion = 0
+       repositoryformatversion = 1
        filemode = true
        bare = false
        logallrefupdates = true
 [remote "origin"]
        url = https://github.com/sviehb/jefferson.git
        fetch = +refs/heads/*:refs/remotes/origin/*
+       promisor = true
+       partialclonefilter = tree:0
 [branch "master"]
        remote = origin
        merge = refs/heads/master
+[extensions]
+       partialClone = origin
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jefferson-0.3+git.20160616/.git/hooks/fsmonitor-watchman.sample 
new/jefferson-0.4.1+git.20220705/.git/hooks/fsmonitor-watchman.sample
--- old/jefferson-0.3+git.20160616/.git/hooks/fsmonitor-watchman.sample 
2016-06-16 16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/hooks/fsmonitor-watchman.sample       
2022-07-05 10:56:07.000000000 +0200
@@ -86,12 +86,13 @@
        # recency index to select candidate nodes and "fields" to limit the
        # output to file names only. Then we're using the "expression" term to
        # further constrain the results.
+       my $last_update_line = "";
        if (substr($last_update_token, 0, 1) eq "c") {
                $last_update_token = "\"$last_update_token\"";
+               $last_update_line = qq[\n"since": $last_update_token,];
        }
        my $query = <<" END";
-               ["query", "$git_work_tree", {
-                       "since": $last_update_token,
+               ["query", "$git_work_tree", {$last_update_line
                        "fields": ["name"],
                        "expression": ["not", ["dirname", ".git"]]
                }]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jefferson-0.3+git.20160616/.git/hooks/pre-push.sample 
new/jefferson-0.4.1+git.20220705/.git/hooks/pre-push.sample
--- old/jefferson-0.3+git.20160616/.git/hooks/pre-push.sample   2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/hooks/pre-push.sample 2022-07-05 
10:56:07.000000000 +0200
@@ -14,7 +14,7 @@
 # Information about the commits which are being pushed is supplied as lines to
 # the standard input in the form:
 #
-#   <local ref> <local sha1> <remote ref> <remote sha1>
+#   <local ref> <local oid> <remote ref> <remote oid>
 #
 # This sample shows how to prevent push of commits where the log message starts
 # with "WIP" (work in progress).
@@ -22,27 +22,27 @@
 remote="$1"
 url="$2"
 
-z40=0000000000000000000000000000000000000000
+zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
 
-while read local_ref local_sha remote_ref remote_sha
+while read local_ref local_oid remote_ref remote_oid
 do
-       if [ "$local_sha" = $z40 ]
+       if test "$local_oid" = "$zero"
        then
                # Handle delete
                :
        else
-               if [ "$remote_sha" = $z40 ]
+               if test "$remote_oid" = "$zero"
                then
                        # New branch, examine all commits
-                       range="$local_sha"
+                       range="$local_oid"
                else
                        # Update to existing branch, examine new commits
-                       range="$remote_sha..$local_sha"
+                       range="$remote_oid..$local_oid"
                fi
 
                # Check for WIP commit
-               commit=`git rev-list -n 1 --grep '^WIP' "$range"`
-               if [ -n "$commit" ]
+               commit=$(git rev-list -n 1 --grep '^WIP' "$range")
+               if test -n "$commit"
                then
                        echo >&2 "Found WIP commit in $local_ref, not pushing"
                        exit 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jefferson-0.3+git.20160616/.git/hooks/push-to-checkout.sample 
new/jefferson-0.4.1+git.20220705/.git/hooks/push-to-checkout.sample
--- old/jefferson-0.3+git.20160616/.git/hooks/push-to-checkout.sample   
1970-01-01 01:00:00.000000000 +0100
+++ new/jefferson-0.4.1+git.20220705/.git/hooks/push-to-checkout.sample 
2022-07-05 10:56:07.000000000 +0200
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+# An example hook script to update a checked-out tree on a git push.
+#
+# This hook is invoked by git-receive-pack(1) when it reacts to git
+# push and updates reference(s) in its repository, and when the push
+# tries to update the branch that is currently checked out and the
+# receive.denyCurrentBranch configuration variable is set to
+# updateInstead.
+#
+# By default, such a push is refused if the working tree and the index
+# of the remote repository has any difference from the currently
+# checked out commit; when both the working tree and the index match
+# the current commit, they are updated to match the newly pushed tip
+# of the branch. This hook is to be used to override the default
+# behaviour; however the code below reimplements the default behaviour
+# as a starting point for convenient modification.
+#
+# The hook receives the commit with which the tip of the current
+# branch is going to be updated:
+commit=$1
+
+# It can exit with a non-zero status to refuse the push (when it does
+# so, it must not modify the index or the working tree).
+die () {
+       echo >&2 "$*"
+       exit 1
+}
+
+# Or it can make any necessary changes to the working tree and to the
+# index to bring them to the desired state when the tip of the current
+# branch is updated to the new commit, and exit with a zero status.
+#
+# For example, the hook can simply run git read-tree -u -m HEAD "$1"
+# in order to emulate git fetch that is run in the reverse direction
+# with git push, as the two-tree form of git read-tree -u -m is
+# essentially the same as git switch or git checkout that switches
+# branches while keeping the local changes in the working tree that do
+# not interfere with the difference between the branches.
+
+# The below is a more-or-less exact translation to shell of the C code
+# for the default behaviour for git's push-to-checkout hook defined in
+# the push_to_deploy() function in builtin/receive-pack.c.
+#
+# Note that the hook will be executed from the repository directory,
+# not from the working tree, so if you want to perform operations on
+# the working tree, you will have to adapt your code accordingly, e.g.
+# by adding "cd .." or using relative paths.
+
+if ! git update-index -q --ignore-submodules --refresh
+then
+       die "Up-to-date check failed"
+fi
+
+if ! git diff-files --quiet --ignore-submodules --
+then
+       die "Working directory has unstaged changes"
+fi
+
+# This is a rough translation of:
+#
+#   head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
+if git cat-file -e HEAD 2>/dev/null
+then
+       head=HEAD
+else
+       head=$(git hash-object -t tree --stdin </dev/null)
+fi
+
+if ! git diff-index --quiet --cached --ignore-submodules $head --
+then
+       die "Working directory has staged changes"
+fi
+
+if ! git read-tree -u -m "$commit"
+then
+       die "Could not update working tree to new HEAD"
+fi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/hooks/update.sample 
new/jefferson-0.4.1+git.20220705/.git/hooks/update.sample
--- old/jefferson-0.3+git.20160616/.git/hooks/update.sample     2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/hooks/update.sample   2022-07-05 
10:56:07.000000000 +0200
@@ -60,7 +60,7 @@
 
 # --- Check types
 # if $newrev is 0000...0000, it's a commit to delete a ref.
-zero="0000000000000000000000000000000000000000"
+zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
 if [ "$newrev" = "$zero" ]; then
        newrev_type=delete
 else
Binary files old/jefferson-0.3+git.20160616/.git/index and 
new/jefferson-0.4.1+git.20220705/.git/index differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/logs/HEAD 
new/jefferson-0.4.1+git.20220705/.git/logs/HEAD
--- old/jefferson-0.3+git.20160616/.git/logs/HEAD       2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/logs/HEAD     2022-07-05 
10:56:07.000000000 +0200
@@ -1,2 +1,4 @@
-0000000000000000000000000000000000000000 
6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mar...@gmx.de> 
1597349289 +0200        clone: from https://github.com/sviehb/jefferson.git
-6f9169bad3ceb4e212fae62ad710eeca3350226b 
6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mar...@gmx.de> 
1597349289 +0200        reset: moving to origin/master
+0000000000000000000000000000000000000000 
ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mar...@gmx.de> 
1665753391 +0200        clone: from https://github.com/sviehb/jefferson.git
+ecc51d7ab500e6286c80154f89388e3173784081 
ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mar...@gmx.de> 
1665753391 +0200        checkout: moving from master to master
+ecc51d7ab500e6286c80154f89388e3173784081 
ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mar...@gmx.de> 
1665753391 +0200        reset: moving to master
+ecc51d7ab500e6286c80154f89388e3173784081 
ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mar...@gmx.de> 
1665753450 +0200        reset: moving to master
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jefferson-0.3+git.20160616/.git/logs/refs/heads/master 
new/jefferson-0.4.1+git.20220705/.git/logs/refs/heads/master
--- old/jefferson-0.3+git.20160616/.git/logs/refs/heads/master  2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/logs/refs/heads/master        
2022-07-05 10:56:07.000000000 +0200
@@ -1 +1 @@
-0000000000000000000000000000000000000000 
6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mar...@gmx.de> 
1597349289 +0200        clone: from https://github.com/sviehb/jefferson.git
+0000000000000000000000000000000000000000 
ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mar...@gmx.de> 
1665753391 +0200        clone: from https://github.com/sviehb/jefferson.git
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jefferson-0.3+git.20160616/.git/logs/refs/remotes/origin/HEAD 
new/jefferson-0.4.1+git.20220705/.git/logs/refs/remotes/origin/HEAD
--- old/jefferson-0.3+git.20160616/.git/logs/refs/remotes/origin/HEAD   
2016-06-16 16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/logs/refs/remotes/origin/HEAD 
2022-07-05 10:56:07.000000000 +0200
@@ -1 +1 @@
-0000000000000000000000000000000000000000 
6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mar...@gmx.de> 
1597349289 +0200        clone: from https://github.com/sviehb/jefferson.git
+0000000000000000000000000000000000000000 
ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mar...@gmx.de> 
1665753391 +0200        clone: from https://github.com/sviehb/jefferson.git
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.idx
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.idx
 differ
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.pack
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.pack
 differ
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.idx
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.idx
 differ
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.pack
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.pack
 differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor
 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor
--- 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor
     1970-01-01 01:00:00.000000000 +0100
+++ 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor
   2022-07-05 10:56:07.000000000 +0200
@@ -0,0 +1,8 @@
+ecc51d7ab500e6286c80154f89388e3173784081 HEAD
+5d8e2f0b2f4e6d0999382614f37981d6ca6c3d9f refs/heads/fix-inode-versioning
+684069ea678c388d7fe58a61e671e4fa5a2a3c3f refs/heads/fix-lzo-python310
+da60d313db2be447b6f8c4ff2cdcca40292faf6d refs/heads/fix-path-traversal
+ecc51d7ab500e6286c80154f89388e3173784081 refs/heads/master
+3fafcf2c922aab5d72e2c65e862c6594e41dd700 refs/tags/v0.3
+dc8564fb90b54c71bd4c62eb71931fd5040fa308 refs/tags/v0.4
+93f4f7dce124fb4c9ffd1d497dc873013e86e355 refs/tags/v0.4.1
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.idx
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.idx
 differ
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.pack
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.pack
 differ
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.idx
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.idx
 differ
Binary files 
old/jefferson-0.3+git.20160616/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.pack
 and 
new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.pack
 differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/packed-refs 
new/jefferson-0.4.1+git.20220705/.git/packed-refs
--- old/jefferson-0.3+git.20160616/.git/packed-refs     2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/packed-refs   2022-07-05 
10:56:07.000000000 +0200
@@ -1,2 +1,10 @@
 # pack-refs with: peeled fully-peeled sorted 
-6f9169bad3ceb4e212fae62ad710eeca3350226b refs/remotes/origin/master
+5d8e2f0b2f4e6d0999382614f37981d6ca6c3d9f 
refs/remotes/origin/fix-inode-versioning
+684069ea678c388d7fe58a61e671e4fa5a2a3c3f refs/remotes/origin/fix-lzo-python310
+da60d313db2be447b6f8c4ff2cdcca40292faf6d refs/remotes/origin/fix-path-traversal
+ecc51d7ab500e6286c80154f89388e3173784081 refs/remotes/origin/master
+3fafcf2c922aab5d72e2c65e862c6594e41dd700 refs/tags/v0.3
+dc8564fb90b54c71bd4c62eb71931fd5040fa308 refs/tags/v0.4
+^361c6e38684bfb24e612e52726cf38c02bd2c9cc
+93f4f7dce124fb4c9ffd1d497dc873013e86e355 refs/tags/v0.4.1
+^ecc51d7ab500e6286c80154f89388e3173784081
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/refs/heads/master 
new/jefferson-0.4.1+git.20220705/.git/refs/heads/master
--- old/jefferson-0.3+git.20160616/.git/refs/heads/master       2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/.git/refs/heads/master     2022-07-05 
10:56:07.000000000 +0200
@@ -1 +1 @@
-6f9169bad3ceb4e212fae62ad710eeca3350226b
+ecc51d7ab500e6286c80154f89388e3173784081
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/README.md 
new/jefferson-0.4.1+git.20220705/README.md
--- old/jefferson-0.3+git.20160616/README.md    2016-06-16 16:41:06.000000000 
+0200
+++ new/jefferson-0.4.1+git.20220705/README.md  2022-07-05 10:56:07.000000000 
+0200
@@ -1,33 +1,31 @@
-# jefferson
-JFFS2 filesystem extraction tool
+## Jefferson
 
-Installation
-============
-```bash
-$ sudo python setup.py install
-```
+JFFS2 filesystem extraction tool
 
+### Installation
 
-Dependencies
-============
-- `cstruct`
-- `pyliblzma`
+Follow these steps on Debian based systems (Debian, Ubuntu, Kali, ...) to 
perform a system-wide installation of jefferon:
 
 ```bash
-$ sudo pip install cstruct
-$ sudo apt-get install python-lzma
+git clone https://github.com/sviehb/jefferson.git
+cd jefferson
+sudo apt update
+sudo apt install python3-pip liblzo2-dev
+sudo python3 -m pip install -r requirements.txt
+sudo python3 setup.py install
 ```
 
-Features
-============
-- Big/Little Endian support
-- `JFFS2_COMPR_ZLIB`, `JFFS2_COMPR_RTIME`, and `JFFS2_COMPR_LZMA` compression 
support
+
+### Features
+
+- big-endian and little-endian support with auto-detection
+- zlib, rtime, LZMA, and LZO compression support
 - CRC checks - for now only enforced on `hdr_crc`
-- Extraction of symlinks, directories, files, and device nodes
-- Detection/handling of duplicate inode numbers. Occurs if multiple JFFS2 
filesystems are found in one file and causes `jefferson` to treat segments as 
separate filesystems
+- extraction of symlinks, directories, files, and device nodes
+- detection/handling of duplicate inode numbers. Occurs if multiple JFFS2 
filesystems are found in one file and causes `jefferson` to treat segments as 
separate filesystems
+
+### Usage
 
-Usage
-============
 ```bash
 $ jefferson filesystem.img -d outdir
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/requirements.txt 
new/jefferson-0.4.1+git.20220705/requirements.txt
--- old/jefferson-0.3+git.20160616/requirements.txt     1970-01-01 
01:00:00.000000000 +0100
+++ new/jefferson-0.4.1+git.20220705/requirements.txt   2022-07-05 
10:56:07.000000000 +0200
@@ -0,0 +1,2 @@
+cstruct==2.1
+python-lzo==1.14
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/setup.py 
new/jefferson-0.4.1+git.20220705/setup.py
--- old/jefferson-0.3+git.20160616/setup.py     2016-06-16 16:41:06.000000000 
+0200
+++ new/jefferson-0.4.1+git.20220705/setup.py   2022-07-05 10:56:07.000000000 
+0200
@@ -1,20 +1,19 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # coding=utf-8
 
 from distutils.core import setup
 
-version = '0.2'
+version = "0.4.1"
 
 setup(
-    name='jefferson',
+    name="jefferson",
     version=version,
-    description='',
-    author='Stefan Viehb??ck',
-    url='https://github.com/sviehb/jefferson',
-    license='MIT',
-
+    description="",
+    author="Stefan Viehb??ck",
+    url="https://github.com/sviehb/jefferson";,
+    license="MIT",
     requires=['cstruct'],
-    packages=['jefferson'],
-    package_dir={'jefferson': 'src/jefferson'},
-    scripts=['src/scripts/jefferson'],
-)
\ No newline at end of file
+    packages=["jefferson"],
+    package_dir={"jefferson": "src/jefferson"},
+    scripts=["src/scripts/jefferson"],
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/src/jefferson/__init__.py 
new/jefferson-0.4.1+git.20220705/src/jefferson/__init__.py
--- old/jefferson-0.3+git.20160616/src/jefferson/__init__.py    2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/src/jefferson/__init__.py  2022-07-05 
10:56:07.000000000 +0200
@@ -1 +1 @@
-__author__ = 'stefan'
+__author__ = "stefan"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jefferson-0.3+git.20160616/src/jefferson/jffs2_lzma.py 
new/jefferson-0.4.1+git.20220705/src/jefferson/jffs2_lzma.py
--- old/jefferson-0.3+git.20160616/src/jefferson/jffs2_lzma.py  2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/src/jefferson/jffs2_lzma.py        
2022-07-05 10:56:07.000000000 +0200
@@ -15,7 +15,7 @@
 
 
 def decompress(data, outlen):
-    lzma_header = struct.pack('<BIQ', PROPERTIES, DICT_SIZE, outlen)
+    lzma_header = struct.pack("<BIQ", PROPERTIES, DICT_SIZE, outlen)
     lzma_data = lzma_header + data
     decompressed = lzma.decompress(lzma_data)
-    return decompressed
\ No newline at end of file
+    return decompressed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/src/jefferson/rtime.py 
new/jefferson-0.4.1+git.20220705/src/jefferson/rtime.py
--- old/jefferson-0.3+git.20160616/src/jefferson/rtime.py       2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/src/jefferson/rtime.py     2022-07-05 
10:56:07.000000000 +0200
@@ -4,11 +4,11 @@
     outpos = 0
     pos = 0
     while outpos < destlen:
-        value = ord(data_in[pos])
+        value = data_in[pos]
         pos += 1
         cpage_out[outpos] = value
         outpos += 1
-        repeat = ord(data_in[pos])
+        repeat = data_in[pos]
         pos += 1
 
         backoffs = positions[value]
@@ -21,6 +21,8 @@
                     backoffs += 1
                     repeat -= 1
             else:
-                cpage_out[outpos:outpos + repeat] = 
cpage_out[backoffs:backoffs + repeat]
+                cpage_out[outpos : outpos + repeat] = cpage_out[
+                    backoffs : backoffs + repeat
+                ]
                 outpos += repeat
-    return str(cpage_out)
\ No newline at end of file
+    return cpage_out
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jefferson-0.3+git.20160616/src/scripts/jefferson 
new/jefferson-0.4.1+git.20220705/src/scripts/jefferson
--- old/jefferson-0.3+git.20160616/src/scripts/jefferson        2016-06-16 
16:41:06.000000000 +0200
+++ new/jefferson-0.4.1+git.20220705/src/scripts/jefferson      2022-07-05 
10:56:07.000000000 +0200
@@ -1,20 +1,25 @@
-#! /usr/bin/python2
+#!/usr/bin/env python3
 
+import argparse
 import struct
 import stat
 import os
+import sys
 import zlib
 import binascii
-
+import lzo
+import mmap
+import contextlib
 import cstruct
 
 from jefferson import jffs2_lzma, rtime
 
 
 def PAD(x):
-    return (((x) + 3) & ~3)
+    return ((x) + 3) & ~3
 
 
+JFFS2_OLD_MAGIC_BITMASK = 0x1984
 JFFS2_MAGIC_BITMASK = 0x1985
 JFFS2_COMPR_NONE = 0x00
 JFFS2_COMPR_ZERO = 0x01
@@ -27,10 +32,10 @@
 JFFS2_COMPR_LZMA = 0x08
 
 # /* Compatibility flags. */
-JFFS2_COMPAT_MASK = 0xc000  # /* What do to if an unknown nodetype is found */
+JFFS2_COMPAT_MASK = 0xC000  # /* What do to if an unknown nodetype is found */
 JFFS2_NODE_ACCURATE = 0x2000
 # /* INCOMPAT: Fail to mount the filesystem */
-JFFS2_FEATURE_INCOMPAT = 0xc000
+JFFS2_FEATURE_INCOMPAT = 0xC000
 # /* ROCOMPAT: Mount read-only */
 JFFS2_FEATURE_ROCOMPAT = 0x8000
 # /* RWCOMPAT_COPY: Mount read/write, and copy the node when it's GC'd */
@@ -46,309 +51,289 @@
 JFFS2_NODETYPE_XATTR = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 8
 JFFS2_NODETYPE_XREF = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 9
 
-
 def mtd_crc(data):
-    return (binascii.crc32(data, -1) ^ -1) & 0xffffffff
-
+    return (binascii.crc32(data, -1) ^ -1) & 0xFFFFFFFF
 
-cstruct.typedef('uint8', 'uint8_t')
-cstruct.typedef('uint16', 'jint16_t')
-cstruct.typedef('uint32', 'jint32_t')
-cstruct.typedef('uint32', 'jmode_t')
+def is_safe_path(basedir, real_path):
+    basedir = os.path.realpath(basedir)
+    return basedir == os.path.commonpath((basedir, real_path))
+
+cstruct.typedef("uint8", "uint8_t")
+cstruct.typedef("uint16", "jint16_t")
+cstruct.typedef("uint32", "jint32_t")
+cstruct.typedef("uint32", "jmode_t")
 
 
 class Jffs2_unknown_node(cstruct.CStruct):
     __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        /* All start like this */
-        jint16_t magic;
-        jint16_t nodetype;
-        jint32_t totlen; /* So we can skip over nodes we don't grok */
-        jint32_t hdr_crc;
+    __def__ = """
+        struct {
+            /* All start like this */
+            jint16_t magic;
+            jint16_t nodetype;
+            jint32_t totlen; /* So we can skip over nodes we don't grok */
+            jint32_t hdr_crc;
+        }
     """
 
     def unpack(self, data):
-        cstruct.CStruct.unpack(self, data[:self.size])
-        comp_hrd_crc = mtd_crc(data[:self.size - 4])
+        cstruct.CStruct.unpack(self, data[: self.size])
+        comp_hrd_crc = mtd_crc(data[: self.size - 4])
 
         if comp_hrd_crc == self.hdr_crc:
             self.hdr_crc_match = True
         else:
-            #print 'hdr_crc does not match!'
+            # print("hdr_crc does not match!")
             self.hdr_crc_match = False
 
 
-class Jffs2_raw_xattr(cstruct.CStruct):
-    __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        jint16_t magic;
-        jint16_t nodetype;      /* = JFFS2_NODETYPE_XATTR */
-        jint32_t totlen;
-        jint32_t hdr_crc;
-        jint32_t xid;           /* XATTR identifier number */
-        jint32_t version;
-        uint8_t xprefix;
-        uint8_t name_len;
-        jint16_t value_len;
-        jint32_t data_crc;
-        jint32_t node_crc;
-        uint8_t data[0];
-    """
-
-
-class Jffs2_raw_summary(cstruct.CStruct):
-    __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        jint16_t magic;
-        jint16_t nodetype;      /* = JFFS2_NODETYPE_SUMMARY */
-        jint32_t totlen;
-        jint32_t hdr_crc;
-        jint32_t sum_num;       /* number of sum entries*/
-        jint32_t cln_mkr;       /* clean marker size, 0 = no cleanmarker */
-        jint32_t padded;        /* sum of the size of padding nodes */
-        jint32_t sum_crc;       /* summary information crc */
-        jint32_t node_crc;      /* node crc */
-        jint32_t sum[0];        /* inode summary info */
-    """
-
-
-class Jffs2_raw_xref(cstruct.CStruct):
-    __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        jint16_t magic;
-        jint16_t nodetype;      /* = JFFS2_NODETYPE_XREF */
-        jint32_t totlen;
-        jint32_t hdr_crc;
-        jint32_t ino;           /* inode number */
-        jint32_t xid;           /* XATTR identifier number */
-        jint32_t xseqno;        /* xref sequencial number */
-        jint32_t node_crc;
-    """
-
-
 class Jffs2_raw_dirent(cstruct.CStruct):
     __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        jint16_t magic;
-        jint16_t nodetype;      /* == JFFS2_NODETYPE_DIRENT */
-        jint32_t totlen;
-        jint32_t hdr_crc;
-        jint32_t pino;
-        jint32_t version;
-        jint32_t ino; /* == zero for unlink */
-        jint32_t mctime;
-        uint8_t nsize;
-        uint8_t type;
-        uint8_t unused[2];
-        jint32_t node_crc;
-        jint32_t name_crc;
-    /* uint8_t data[0]; -> name */
+    __def__ = """
+        struct {
+            jint16_t magic;
+            jint16_t nodetype;      /* == JFFS2_NODETYPE_DIRENT */
+            jint32_t totlen;
+            jint32_t hdr_crc;
+            jint32_t pino;
+            jint32_t version;
+            jint32_t ino; /* == zero for unlink */
+            jint32_t mctime;
+            uint8_t nsize;
+            uint8_t type;
+            uint8_t unused[2];
+            jint32_t node_crc;
+            jint32_t name_crc;
+        /* uint8_t data[0]; -> name */
+        }
     """
 
     def unpack(self, data, node_offset):
-        cstruct.CStruct.unpack(self, data[:self.size])
-        self.name = data[self.size:self.size + self.nsize]
+        cstruct.CStruct.unpack(self, data[: self.size])
+        self.name = data[self.size : self.size + self.nsize].tobytes()
         self.node_offset = node_offset
 
-        if mtd_crc(data[:self.size - 8]) == self.node_crc:
+        if mtd_crc(data[: self.size - 8]) == self.node_crc:
             self.node_crc_match = True
         else:
-            print 'node_crc does not match!'
+            print("node_crc does not match!")
             self.node_crc_match = False
 
         if mtd_crc(self.name) == self.name_crc:
             self.name_crc_match = True
         else:
-            print 'data_crc does not match!'
+            print("data_crc does not match!")
             self.name_crc_match = False
 
     def __str__(self):
         result = []
-        for field in self.__fields__ + ['name', 'node_offset']:
+        for field in self.__fields__ + ["name", "node_offset"]:
             result.append(field + "=" + str(getattr(self, field, None)))
         return type(self).__name__ + "(" + ", ".join(result) + ")"
 
 
 class Jffs2_raw_inode(cstruct.CStruct):
     __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        jint16_t magic;      /* A constant magic number.  */
-        jint16_t nodetype;   /* == JFFS2_NODETYPE_INODE */
-        jint32_t totlen;     /* Total length of this node (inc data, etc.) */
-        jint32_t hdr_crc;
-        jint32_t ino;        /* Inode number.  */
-        jint32_t version;    /* Version number.  */
-        jmode_t mode;       /* The file's type or mode.  */
-        jint16_t uid;        /* The file's owner.  */
-        jint16_t gid;        /* The file's group.  */
-        jint32_t isize;      /* Total resultant size of this inode (used for 
truncations)  */
-        jint32_t atime;      /* Last access time.  */
-        jint32_t mtime;      /* Last modification time.  */
-        jint32_t ctime;      /* Change time.  */
-        jint32_t offset;     /* Where to begin to write.  */
-        jint32_t csize;      /* (Compressed) data size */
-        jint32_t dsize;      /* Size of the node's data. (after decompression) 
*/
-        uint8_t compr;       /* Compression algorithm used */
-        uint8_t usercompr;   /* Compression algorithm requested by the user */
-        jint16_t flags;      /* See JFFS2_INO_FLAG_* */
-        jint32_t data_crc;   /* CRC for the (compressed) data.  */
-        jint32_t node_crc;   /* CRC for the raw inode (excluding data)  */
-        /* uint8_t data[0]; */
+    __def__ = """
+        struct {
+            jint16_t magic;      /* A constant magic number.  */
+            jint16_t nodetype;   /* == JFFS2_NODETYPE_INODE */
+            jint32_t totlen;     /* Total length of this node (inc data, etc.) 
*/
+            jint32_t hdr_crc;
+            jint32_t ino;        /* Inode number.  */
+            jint32_t version;    /* Version number.  */
+            jmode_t mode;       /* The file's type or mode.  */
+            jint16_t uid;        /* The file's owner.  */
+            jint16_t gid;        /* The file's group.  */
+            jint32_t isize;      /* Total resultant size of this inode (used 
for truncations)  */
+            jint32_t atime;      /* Last access time.  */
+            jint32_t mtime;      /* Last modification time.  */
+            jint32_t ctime;      /* Change time.  */
+            jint32_t offset;     /* Where to begin to write.  */
+            jint32_t csize;      /* (Compressed) data size */
+            jint32_t dsize;      /* Size of the node's data. (after 
decompression) */
+            uint8_t compr;       /* Compression algorithm used */
+            uint8_t usercompr;   /* Compression algorithm requested by the 
user */
+            jint16_t flags;      /* See JFFS2_INO_FLAG_* */
+            jint32_t data_crc;   /* CRC for the (compressed) data.  */
+            jint32_t node_crc;   /* CRC for the raw inode (excluding data)  */
+            /* uint8_t data[0]; */
+        }
     """
 
     def unpack(self, data):
-        cstruct.CStruct.unpack(self, data[:self.size])
+        cstruct.CStruct.unpack(self, data[: self.size])
 
-        node_data = data[self.size:self.size + self.csize]
-        if self.compr == JFFS2_COMPR_NONE:
-            self.data = node_data
-        elif self.compr == JFFS2_COMPR_ZERO:
-            self.data = '\x00' * self.dsize
-        elif self.compr == JFFS2_COMPR_ZLIB:
-            self.data = zlib.decompress(node_data)
-        elif self.compr == JFFS2_COMPR_RTIME:
-            self.data = rtime.decompress(node_data, self.dsize)
-        elif self.compr == JFFS2_COMPR_LZMA:
-            self.data = jffs2_lzma.decompress(node_data, self.dsize)
-        else:
-            print 'compression not implemented', self
-            print node_data.encode('hex')[:20]
-            self.data = node_data
+        node_data = data[self.size : self.size + self.csize].tobytes()
+        try:
+            if self.compr == JFFS2_COMPR_NONE:
+                self.data = node_data
+            elif self.compr == JFFS2_COMPR_ZERO:
+                self.data = b"\x00" * self.dsize
+            elif self.compr == JFFS2_COMPR_ZLIB:
+                self.data = zlib.decompress(node_data)
+            elif self.compr == JFFS2_COMPR_RTIME:
+                self.data = rtime.decompress(node_data, self.dsize)
+            elif self.compr == JFFS2_COMPR_LZMA:
+                self.data = jffs2_lzma.decompress(node_data, self.dsize)
+            elif self.compr == JFFS2_COMPR_LZO:
+                self.data = lzo.decompress(node_data, False, self.dsize)
+            else:
+                print("compression not implemented", self)
+                print(node_data.hex()[:20])
+                self.data = node_data
+        except Exception as e:
+            print("Decompression error on inode {}: {}".format(self.ino, e), 
file=sys.stderr)
+            self.data = b"\x00" * self.dsize
 
         if len(self.data) != self.dsize:
-            print 'data length mismatch!'
+            print("data length mismatch!")
 
-        if mtd_crc(data[:self.size - 8]) == self.node_crc:
+        if mtd_crc(data[: self.size - 8]) == self.node_crc:
             self.node_crc_match = True
         else:
-            print 'hdr_crc does not match!'
+            print("hdr_crc does not match!")
             self.node_crc_match = False
 
         if mtd_crc(node_data) == self.data_crc:
             self.data_crc_match = True
         else:
-            print 'data_crc does not match!'
+            print("data_crc does not match!")
             self.data_crc_match = False
 
+
 class Jffs2_device_node_old(cstruct.CStruct):
     __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        jint16_t old_id;
+    __def__ = """
+        struct {
+            jint16_t old_id;
+        }
     """
 
+
 class Jffs2_device_node_new(cstruct.CStruct):
     __byte_order__ = cstruct.LITTLE_ENDIAN
-    __struct__ = """
-        jint32_t new_id;
+    __def__ = """
+        struct {
+            jint32_t new_id;
+        }
     """
 
+
 NODETYPES = {
     JFFS2_FEATURE_INCOMPAT: Jffs2_unknown_node,
     JFFS2_NODETYPE_DIRENT: Jffs2_raw_dirent,
     JFFS2_NODETYPE_INODE: Jffs2_raw_inode,
-    JFFS2_NODETYPE_CLEANMARKER: 'JFFS2_NODETYPE_CLEANMARKER',
-    JFFS2_NODETYPE_SUMMARY: Jffs2_raw_summary,
-    JFFS2_NODETYPE_XATTR: Jffs2_raw_xattr,
-    JFFS2_NODETYPE_XREF: Jffs2_raw_xref,
-    JFFS2_NODETYPE_PADDING: 'JFFS2_NODETYPE_PADDING'
+    JFFS2_NODETYPE_CLEANMARKER: "JFFS2_NODETYPE_CLEANMARKER",
+    JFFS2_NODETYPE_PADDING: "JFFS2_NODETYPE_PADDING",
 }
 
 
 def set_endianness(endianness):
-    Jffs2_device_node_new.__fmt__ = endianness + 
Jffs2_device_node_new.__fmt__[1:]
-    Jffs2_device_node_old.__fmt__ = endianness + 
Jffs2_device_node_old.__fmt__[1:]
+    global Jffs2_device_node_new, Jffs2_device_node_old, Jffs2_unknown_node, 
Jffs2_raw_dirent, Jffs2_raw_inode, Jffs2_raw_summary, Jffs2_raw_xattr, 
Jffs2_raw_xref
 
-    for node in NODETYPES.values():
-        if isinstance(node, cstruct.CStructMeta):
-            node.__fmt__ = endianness + node.__fmt__[1:]
+    Jffs2_device_node_new = Jffs2_device_node_new.parse(
+        Jffs2_device_node_new.__def__,
+        __name__=Jffs2_device_node_new.__name__,
+        __byte_order__=endianness,
+    )
+
+    Jffs2_device_node_old = Jffs2_device_node_old.parse(
+        Jffs2_device_node_old.__def__,
+        __name__=Jffs2_device_node_old.__name__,
+        __byte_order__=endianness,
+    )
+
+    Jffs2_unknown_node = Jffs2_unknown_node.parse(
+        Jffs2_unknown_node.__def__,
+        __name__=Jffs2_unknown_node.__name__,
+        __byte_order__=endianness,
+    )
+
+    Jffs2_raw_dirent = Jffs2_raw_dirent.parse(
+        Jffs2_raw_dirent.__def__,
+        __name__=Jffs2_raw_dirent.__name__,
+        __byte_order__=endianness,
+    )
+
+    Jffs2_raw_inode = Jffs2_raw_inode.parse(
+        Jffs2_raw_inode.__def__,
+        __name__=Jffs2_raw_inode.__name__,
+        __byte_order__=endianness,
+    )
 
 
 def scan_fs(content, endianness, verbose=False):
-    set_endianness(endianness)
-    summaries = []
     pos = 0
-    jffs2_magic_bitmask_str = struct.pack(endianness + 'H', 
JFFS2_MAGIC_BITMASK)
-    fs_index = 0
+    jffs2_old_magic_bitmask_str = struct.pack(endianness + "H", 
JFFS2_OLD_MAGIC_BITMASK)
+    jffs2_magic_bitmask_str = struct.pack(endianness + "H", 
JFFS2_MAGIC_BITMASK)
+    content_mv = memoryview(content)
 
     fs = {}
-    fs[fs_index] = {}
-    fs[fs_index]["endianness"] = endianness
-    fs[fs_index][JFFS2_NODETYPE_INODE] = []
-    fs[fs_index][JFFS2_NODETYPE_DIRENT] = []
-    fs[fs_index][JFFS2_NODETYPE_XATTR] = []
-    fs[fs_index][JFFS2_NODETYPE_XREF] = []
-    fs[fs_index][JFFS2_NODETYPE_SUMMARY] = []
+    fs[JFFS2_NODETYPE_INODE] = {}
+    fs[JFFS2_NODETYPE_DIRENT] = {}
 
-    dirent_dict = {}
     while True:
-        find_result = content.find(jffs2_magic_bitmask_str, pos, len(content) 
- Jffs2_unknown_node.size)
-        if find_result == -1:
+        find_result = content.find(
+            jffs2_magic_bitmask_str, pos, len(content) - 
Jffs2_unknown_node.size
+        )
+        find_result_old = content.find(
+            jffs2_old_magic_bitmask_str, pos, len(content) - 
Jffs2_unknown_node.size
+        )
+        if find_result == -1 and find_result_old == -1:
             break
-        else:
+        if find_result != -1:
             pos = find_result
+        else:
+            pos = find_result_old
 
         unknown_node = Jffs2_unknown_node()
-        unknown_node.unpack(content[pos:pos + unknown_node.size])
+        unknown_node.unpack(content_mv[pos : pos + unknown_node.size])
         if not unknown_node.hdr_crc_match:
             pos += 1
             continue
         offset = pos
         pos += PAD(unknown_node.totlen)
 
-        if unknown_node.magic == JFFS2_MAGIC_BITMASK:
+        if unknown_node.magic in [
+             JFFS2_MAGIC_BITMASK,
+             JFFS2_OLD_MAGIC_BITMASK,
+        ]:
             if unknown_node.nodetype in NODETYPES:
                 if unknown_node.nodetype == JFFS2_NODETYPE_DIRENT:
                     dirent = Jffs2_raw_dirent()
-                    dirent.unpack(content[0 + offset:], offset)
-                    if dirent.ino in dirent_dict:
-                        print 'duplicate inode use detected!!!'
-                        fs_index += 1
-                        fs[fs_index] = {}
-                        fs[fs_index]["endianness"] = endianness
-                        fs[fs_index][JFFS2_NODETYPE_INODE] = []
-                        fs[fs_index][JFFS2_NODETYPE_DIRENT] = []
-                        fs[fs_index][JFFS2_NODETYPE_XATTR] = []
-                        fs[fs_index][JFFS2_NODETYPE_XREF] = []
-                        fs[fs_index][JFFS2_NODETYPE_SUMMARY] = []
-                        dirent_dict = {}
-
-                    dirent_dict[dirent.ino] = dirent
-
-                    fs[fs_index][JFFS2_NODETYPE_DIRENT].append(dirent)
+                    dirent.unpack(content_mv[0 + offset :], offset)
+                    if dirent.ino in fs[JFFS2_NODETYPE_DIRENT]:
+                        if dirent.version > 
fs[JFFS2_NODETYPE_DIRENT][dirent.ino].version:
+                            fs[JFFS2_NODETYPE_DIRENT][dirent.ino] = dirent
+                    else:
+                        fs[JFFS2_NODETYPE_DIRENT][dirent.ino] = dirent
                     if verbose:
-                        print '0x%08X:' % (offset), dirent
+                        print("0x%08X:" % (offset), dirent)
                 elif unknown_node.nodetype == JFFS2_NODETYPE_INODE:
                     inode = Jffs2_raw_inode()
-                    inode.unpack(content[0 + offset:])
-                    fs[fs_index][JFFS2_NODETYPE_INODE].append(inode)
-                    if verbose:
-                        print '0x%08X:' % (offset), inode
-                elif unknown_node.nodetype == JFFS2_NODETYPE_XREF:
-                    xref = Jffs2_raw_xref()
-                    xref.unpack(content[offset:offset + xref.size])
-                    fs[fs_index][JFFS2_NODETYPE_XREF].append(xref)
-                    if verbose:
-                        print '0x%08X:' % (offset), xref
-                elif unknown_node.nodetype == JFFS2_NODETYPE_XATTR:
-                    xattr = Jffs2_raw_xattr()
-                    xattr.unpack(content[offset:offset + xattr.size])
-                    fs[fs_index][JFFS2_NODETYPE_XREF].append(xattr)
-                    if verbose:
-                        print '0x%08X:' % (offset), xattr
-                elif unknown_node.nodetype == JFFS2_NODETYPE_SUMMARY:
-                    summary = Jffs2_raw_summary()
-                    summary.unpack(content[offset:offset + summary.size])
-                    summaries.append(summary)
-                    fs[fs_index][JFFS2_NODETYPE_SUMMARY].append(summary)
+                    inode.unpack(content_mv[0 + offset :])
+
+                    if inode.ino in fs[JFFS2_NODETYPE_INODE]:
+                        fs[JFFS2_NODETYPE_INODE][inode.ino].append(inode)
+                    else:
+                        fs[JFFS2_NODETYPE_INODE][inode.ino] = [inode]
                     if verbose:
-                        print '0x%08X:' % (offset), summary
+                        print("0x%08X:" % (offset), inode)
                 elif unknown_node.nodetype == JFFS2_NODETYPE_CLEANMARKER:
                     pass
                 elif unknown_node.nodetype == JFFS2_NODETYPE_PADDING:
                     pass
+                elif unknown_node.nodetype == JFFS2_NODETYPE_SUMMARY:
+                    pass
+                elif unknown_node.nodetype == JFFS2_NODETYPE_XATTR:
+                    pass
+                elif unknown_node.nodetype == JFFS2_NODETYPE_XREF:
+                    pass
                 else:
-                    print 'Unhandled node type', unknown_node.nodetype, 
unknown_node
-    return fs.values()
+                    print("Unknown node type", unknown_node.nodetype, 
unknown_node)
+    content_mv.release()
+    return fs
 
 
 def get_device(inode):
@@ -358,32 +343,34 @@
     if inode.dsize == len(Jffs2_device_node_new):
         node = Jffs2_device_node_new()
         node.unpack(inode.data)
-        return os.makedev((node.new_id & 0xfff00) >> 8, (node.new_id & 0xff) | 
((node.new_id >> 12) & 0xfff00))
-    elif inode.dsize == len(Jffs2_device_node_old):
+        return os.makedev(
+            (node.new_id & 0xFFF00) >> 8,
+            (node.new_id & 0xFF) | ((node.new_id >> 12) & 0xFFF00),
+        )
+
+    if inode.dsize == len(Jffs2_device_node_old):
         node = Jffs2_device_node_old()
         node.unpack(inode.data)
-        return os.makedev((node.old_id >> 8) & 0xff, node.old_id & 0xff)
+        return os.makedev((node.old_id >> 8) & 0xFF, node.old_id & 0xFF)
     return None
 
+def sort_version(item):
+    return item.version
 
 def dump_fs(fs, target):
     node_dict = {}
 
-    set_endianness(fs["endianness"])
-
-    for dirent in fs[JFFS2_NODETYPE_DIRENT]:
+    for dirent in fs[JFFS2_NODETYPE_DIRENT].values():
         dirent.inodes = []
-        for inode in fs[JFFS2_NODETYPE_INODE]:
-            if inode.ino == dirent.ino:
-                dirent.inodes.append(inode)
-        if dirent.ino in node_dict:
-            print 'duplicate dirent.ino use detected!!!', dirent
+        for ino, inodes in fs[JFFS2_NODETYPE_INODE].items():
+            if ino == dirent.ino:
+                dirent.inodes = sorted(inodes, key=sort_version)
         node_dict[dirent.ino] = dirent
 
-    for dirent in fs[JFFS2_NODETYPE_DIRENT]:
+    for dirent in fs[JFFS2_NODETYPE_DIRENT].values():
         pnode_pino = dirent.pino
         pnodes = []
-        for i in range(100):
+        for _ in range(100):
             if pnode_pino not in node_dict:
                 break
             pnode = node_dict[pnode_pino]
@@ -394,105 +381,110 @@
         node_names = []
 
         for pnode in pnodes:
-            node_names.append(pnode.name)
-        node_names.append(dirent.name)
-        path = '/'.join(node_names)
+            node_names.append(pnode.name.decode())
+        node_names.append(dirent.name.decode())
+        path = "/".join(node_names)
+
+        target_path = os.path.realpath(os.path.join(target, path))
+
+        if not is_safe_path(target, target_path):
+            print(f"Path traversal attempt to {target_path}, discarding.")
+            continue
 
-        target_path = os.path.join(os.getcwd(), target, path)
         for inode in dirent.inodes:
             try:
                 if stat.S_ISDIR(inode.mode):
-                    print 'writing S_ISDIR', path
+                    print("writing S_ISDIR", path)
                     if not os.path.isdir(target_path):
                         os.makedirs(target_path)
                 elif stat.S_ISLNK(inode.mode):
-                    print 'writing S_ISLNK', path
+                    print("writing S_ISLNK", path)
                     if not os.path.islink(target_path):
                         if os.path.exists(target_path):
-                            print 'file already exists as', inode.data
                             continue
                         os.symlink(inode.data, target_path)
                 elif stat.S_ISREG(inode.mode):
-                    print 'writing S_ISREG', path
+                    print("writing S_ISREG", path)
                     if not os.path.isfile(target_path):
                         if not os.path.isdir(os.path.dirname(target_path)):
                             os.makedirs(os.path.dirname(target_path))
-                        with open(target_path, 'wb') as fd:
+                        with open(target_path, "wb") as fd:
                             for inode in dirent.inodes:
                                 fd.seek(inode.offset)
                                 fd.write(inode.data)
                     os.chmod(target_path, stat.S_IMODE(inode.mode))
                     break
                 elif stat.S_ISCHR(inode.mode):
-                    print 'writing S_ISBLK', path
+                    print("writing S_ISBLK", path)
                     os.mknod(target_path, inode.mode, get_device(inode))
                 elif stat.S_ISBLK(inode.mode):
-                    print 'writing S_ISBLK', path
+                    print("writing S_ISBLK", path)
                     os.mknod(target_path, inode.mode, get_device(inode))
                 elif stat.S_ISFIFO(inode.mode):
-                    print 'skipping S_ISFIFO', path
+                    print("skipping S_ISFIFO", path)
                 elif stat.S_ISSOCK(inode.mode):
-                    print 'skipping S_ISSOCK', path
+                    print("skipping S_ISSOCK", path)
                 else:
-                    print 'unhandled inode.mode: %o' % inode.mode, inode, 
dirent
-
-            except IOError as e:
-                print "I/O error(%i): %s" % (e.errno, e.strerror), inode, 
dirent
+                    print("unhandled inode.mode: %o" % inode.mode, inode, 
dirent)
 
-            except OSError as e:
-                print "OS error(%i): %s" % (e.errno, e.strerror), inode, dirent
+            except OSError as error:
+                print("OS error(%i): %s" % (error.errno, error.strerror), 
inode, dirent)
 
 
 def main():
-    import argparse
 
     parser = argparse.ArgumentParser()
-    parser.add_argument('-v', '--verbose', help='increase output verbosity',
-                        action="store_true")
-    parser.add_argument('-f', '--force', help='overwrite destination 
directory',
-                        action="store_true")
-    parser.add_argument('filesystem', type=str,
-                        help="path to filesystem")
-    parser.add_argument('-d', '--dest', type=str, default='jffs2-root',
-                        help='destination directory (default: jffs-root)')
+    parser.add_argument(
+        "-v", "--verbose", help="increase output verbosity", 
action="store_true"
+    )
+    parser.add_argument(
+        "-f", "--force", help="overwrite destination directory", 
action="store_true"
+    )
+    parser.add_argument("filesystem", type=str, help="path to filesystem")
+    parser.add_argument(
+        "-d",
+        "--dest",
+        type=str,
+        default="jffs2-root",
+        help="destination directory (default: jffs-root)",
+    )
 
     args = parser.parse_args()
     dest_path = os.path.join(os.getcwd(), args.dest)
 
     if os.path.exists(dest_path):
         if not args.force:
-            print 'Destination path already exists!'
+            print("Destination path already exists!")
             return
     else:
         os.mkdir(dest_path)
 
-    content = open(args.filesystem, 'rb').read()
-    fs_list = scan_fs(content, cstruct.BIG_ENDIAN, verbose=args.verbose)
-    fs_list += scan_fs(content, cstruct.LITTLE_ENDIAN, verbose=args.verbose)
-
-    fs_index = 1
-    for fs in fs_list:
-        if not fs[JFFS2_NODETYPE_DIRENT]:
-            continue
+    with contextlib.ExitStack() as context_stack:
+        filesystem = context_stack.enter_context(open(args.filesystem, "rb"))
+        filesystem_len = os.fstat(filesystem.fileno()).st_size
+        if 0 == filesystem_len:
+            return
+        content = context_stack.enter_context(
+            mmap.mmap(filesystem.fileno(), filesystem_len, 
access=mmap.ACCESS_READ)
+        )
+        magic = struct.unpack("<H", content[0:2])[0]
+        if magic in [JFFS2_OLD_MAGIC_BITMASK, JFFS2_MAGIC_BITMASK]:
+            endianness = cstruct.LITTLE_ENDIAN
+        else:
+            endianness = cstruct.BIG_ENDIAN
+
+        set_endianness(endianness)
+
+        fs = scan_fs(content, endianness, verbose=args.verbose)
+        print("dumping fs to %s (endianness: %s)" % (dest_path, endianness))
+        for key, value in fs.items():
+            print("%s count: %i" % (NODETYPES[key].__name__, len(value)))
+
+        if not os.path.exists(dest_path):
+            os.mkdir(dest_path)
 
-        dest_path_fs = os.path.join(dest_path, 'fs_%i' % fs_index)
-        print 'dumping fs #%i to %s' % (fs_index, dest_path_fs)
-        for key, value in fs.iteritems():
-            if key == "endianness":
-                if value == cstruct.BIG_ENDIAN:
-                    print 'Endianness: Big'
-                elif value == cstruct.LITTLE_ENDIAN:
-                    print 'Endianness: Little'
-                continue
-
-            print '%s count: %i' % (NODETYPES[key].__name__, len(value))
-
-        if not os.path.exists(dest_path_fs):
-            os.mkdir(dest_path_fs)
-
-        dump_fs(fs, dest_path_fs)
-        print '-' * 10
-        fs_index += 1
+        dump_fs(fs, dest_path)
+        print("-" * 10)
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()

Reply via email to