bmx-ng / archive.mod

Archives!
BSD 2-Clause "Simplified" License
3 stars 1 forks source link

Archive.Modules #1

Open WGZero05 opened 2 years ago

WGZero05 commented 2 years ago

Is it possible to rename the files that are already inside of my zip file?

Example:

Local entry:TArchiveEntry = New TArchiveEntry

Local ra:TReadArchive = New TReadArchive ra.SetFormat(EArchiveFormat.ZIP) ra.SetPassphrase("abc123")

ra.Open("data.zip")

While ra.ReadNextHeader(entry) = ARCHIVE_OK

Select entry.Pathname().Tolower()
case "myvideo.mp4"
    ra.RenameEntry (entry.Pathname(),"Newname.mp4")
case "mymusic.mp3"
    ra.RenameEntry (entry.Pathname(),"newmusicname.mp3")
endselect

Wend

ra.Free()

GWRon commented 2 years ago

do you want to alter file names in zipfiles or do you just want to "override" while extracting?

the first is doable with tools like zipnote or zip_rename but is not necessarily possible with normal libraries/tools without extracting and archiving again.

If filenames were the same length then you could simply "replace" the strings ("AAA.mp3" with "BBB.mp3") in the zip file .. but this is very hackish.

GWRon commented 2 years ago

With "SetPathname()" you might be able to do that here (renaming while extracting). will try and report back.

Edit: so if you just want to do while "extracting":

While ra.ReadNextHeader(entry) = ARCHIVE_OK
    Select entry.Pathname().Tolower()
        case "files/old_name.bmx"
            entry.SetPathname("files/new_name.bmx")
    End Select
woollybah commented 2 years ago

Is it possible to rename the files that are already inside of my zip file?

No. It doesn't work like that, I'm afraid. There is one flow for reading, and one flow for writing. It isn't designed for random access/updates of archive content.

GWRon commented 2 years ago

I am already trying out to createWrong

OpenWrong OpenNew IterateOverWrong WriteNew(corrected)

CloseFiles

For now archive.mod/core.mod does not contain functionality to read creation/modification time of an entry (archive_entry_mtime is not exposed). Edit: same for filetype ... you can set it but not read it.

GWRon commented 2 years ago
SuperStrict

Framework Archive.Zip
Import brl.standardio

'STEP 1: create "archive_rename_wrong.zip"
Local wa:TWriteArchive = New TWriteArchive
wa.SetFormat(EArchiveFormat.ZIP)
wa.Open("archive_rename_wrong.zip")
wa.AddEntry("archive_rename.bmx", "files/archive_rename_wrong_name.bmx")
wa.Close()
' file written now

'STEP 2: open it for reading
Local entry:TArchiveEntry = New TArchiveEntry
Local ra:TReadArchive = New TReadArchive
ra.SetFormat(EArchiveFormat.ZIP)
ra.Open("archive_rename_wrong.zip")

'STEP 3: create the new ("corrected") file
Local ca:TWriteArchive = New TWriteArchive
ca.SetFormat(EArchiveFormat.ZIP)
ca.Open("archive_rename_corrected.zip")

'STEP 4: read files from "wrong" archive - and save them in the "corrected"
'        but with adjusted file names
While ra.ReadNextHeader(entry) = ARCHIVE_OK
    Print "File : " + entry.Pathname()
    Print "Size : " + entry.Size()

    'correct names if needed
    Select entry.Pathname().Tolower()
        case "files/archive_rename_wrong_name.bmx"
            entry.SetPathname("files/archive_rename.bmx")
    End Select

    'write into corrected archive
    Local fTime:Long = 0 'entry.FTime())
    Local fType:EArchiveFileType = EArchiveFileType.FILE 'entry.FType()
    ca.AddEntry(ra.DataStream(), entry.Pathname(), entry.Size(), fTime, fType)
    print " -> wrote file ~q" + entry.Pathname() + "~q into new archive"
Wend

'STEP 5: Close/free "wrong" file and also set "corrected" as done
ra.Free()
ca.Close()
print "done."

This file streams the "wrong" archive and corrects the name - and finally adds the extracted archive content into the new one. So it "recompresses" not just renames.

Afterwards you could remove the old archive and rename the new one. To manipulate the zip files without recompress you might use one of the tools above.

Another option also requires the help of @woollybah you should be able to iterate over the entries in an archive .. and to set new names for them. Once done, you should be able to write the new header into the file and done. archive_entry_set_pathname to set the name of the currently iterated entry archive_write_header to write the new information hmm .... nah, seems it is not as easy to just manipulate the archive "header" (they only have entry-headers). So best thing is to use an approach like above (read file 1 + save file 2). Brucey might need to add FTime(), MTime() and FType() getters so you can recreate the entries accordingly

WGZero05 commented 2 years ago

@GWRon That makes it more complicated but still thanks for the idea. I can wait for all the new features of this module. Still waiting for the Extractzipfile function, and more. I just wanna thank you so much Mr.WollyBah for this effort. I wanna learn more from him. God Bless you, Mr.Brucey. πŸ™πŸ»

GWRon commented 2 years ago

Please provide a sample source file so we see what you are doing.

Also keep in mind that you can password secure the content...without password protecting the file list (you can look into zip files and list contained files...but extracting the files to see their content requires the password)

GWRon commented 2 years ago

the issue is the aes256 ... wa.SetFormatOption("encryption", "zipcrypt")

and it asks for a password

while this:

wa.SetFormatOption("encryption", "aes256") ' or zipcrypt, 
print wa.ErrorString()

results in:

encryption not supported

C code looks like this:

    } else if (strcmp(key, "encryption") == 0) {
        if (val == NULL) {
            zip->encryption_type = ENCRYPTION_NONE;
            ret = ARCHIVE_OK;
        } else if (val[0] == '1' || strcmp(val, "traditional") == 0
            || strcmp(val, "zipcrypt") == 0
            || strcmp(val, "ZipCrypt") == 0) {
            if (is_traditional_pkware_encryption_supported()) {
                zip->encryption_type = ENCRYPTION_TRADITIONAL;
                ret = ARCHIVE_OK;
            } else {
                archive_set_error(&a->archive,
                    ARCHIVE_ERRNO_MISC,
                    "encryption not supported");
            }
        } else if (strcmp(val, "aes128") == 0) {
            if (is_winzip_aes_encryption_supported(
                ENCRYPTION_WINZIP_AES128)) {
                zip->encryption_type = ENCRYPTION_WINZIP_AES128;
                ret = ARCHIVE_OK;
            } else {
                archive_set_error(&a->archive,
                    ARCHIVE_ERRNO_MISC,
                    "encryption not supported");
            }
        } else if (strcmp(val, "aes256") == 0) {
            if (is_winzip_aes_encryption_supported(
                ENCRYPTION_WINZIP_AES256)) {
                zip->encryption_type = ENCRYPTION_WINZIP_AES256;
                ret = ARCHIVE_OK;
            } else {
                archive_set_error(&a->archive,
                    ARCHIVE_ERRNO_MISC,
                    "encryption not supported");

So I made this here:

Local wa:TWriteArchive = New TWriteArchive
wa.SetFormat(EArchiveFormat.ZIP)
wa.SetFormatOption("encryption", "zipcrypt") ' or zipcrypt, 
print wa.Errno() + ": " + wa.ErrorString()
wa.ClearError()
wa.SetFormatOption("encryption", "aes128") ' or zipcrypt, 
print wa.Errno() + ": " + wa.ErrorString()
wa.ClearError()
wa.SetFormatOption("encryption", "aes256") ' or zipcrypt, 
print wa.Errno() + ": " + wa.ErrorString()
wa.ClearError()
wa.SetPassphrase("abc123")

which on my LINUX computer outputs:

0:
-1: encryption not supported
-1: encryption not supported
GWRon commented 2 years ago

Might be related to: https://github.com/libarchive/libarchive/issues/1607

GWRon commented 2 years ago

entry.IsEncrypted() entry.IsMetadataEncrypted() entry.IsDataEncrypted()

for the archive itself: ra.HasEncryptedEntries()

Edit: Seems you cannot "open" before setting the passphrase ... so to identify if it needs a password you open it without password, then check HasEncryptedEntries() ... then close/free the archive again ... create it anew, set the password and open. Sounds rather...complicated.

Local ra:TReadArchive = New TReadArchive
ra.SetFormat(EArchiveFormat.ZIP)
ra.Open("data.zip")
if ra.HasEncryptedEntries()
    ra.free()

    print "has password protection ... asking for password"
    'bla bla handling password request
    'password = abc123

    ra = new TReadArchive
    ra.SetFormat(EArchiveFormat.ZIP)
    ra.SetPassphrase("abc123")
    ra.Open("data.zip")
endif
GWRon commented 2 years ago

Think the corresponding functionality is not yet exposed directly... but I assume you set it via the format options:

wa.SetFormatOption("compression-level", "9")

https://www.mankier.com/3/archive_write_set_options#compression-level

GWRon commented 2 years ago

if you would have tried it ... you would have recognized that it seems to work :)

archive size is bigger with "0" than with "9" ... so seems to do what it should

GWRon commented 2 years ago

Regarding identifying zip files... the library offers: https://manpages.debian.org/testing/libarchive-dev/archive_read_add_passphrase.3.en.html

archive_read_add_passphrase() Register passphrases for reading an encryption archive. If passphrase is NULL or empty, this function will do nothing and ARCHIVE_FAILED will be returned. Otherwise, ARCHIVE_OK will be returned.

archive_read_set_passphrase_callback() Register a callback function that will be invoked to get a passphrase for decryption after trying all the passphrases registered by the archive_read_add_passphrase() function failed.

Dunno if one could add "empty" as password ... and in case it is failed, the callback would be executed ... the callback could ask for the password then.


The important thing to REMEMBER: "readArchive.SetPassphrase()" does not set a single password to use! ... it adds a password the archive tries out

    ra.SetPassphrase("abc1231234546464656")
    ra.SetPassphrase("abc123")

will add BOTH passwords -- and while the first fails the second succeeds. If it failed too, then above callback (if registered) would be called.

WGZero05 commented 2 years ago

What about the Append? What if I'm going to open the zip file and add more files to the already existing zip file? How can I do add more files to my zip?

@Yushkun24 All I can do is to wait patiently for Mr.Brucey for the Doc examples and functions. So be patient and wait, buddy. :)

GWRon commented 2 years ago

what is wrong with opening multiple issues ? what is wrong with pasting code here (in wrapped in 3 backticks ... ```code```) - better this than having people to write code from your snapshot

what about reading "line 10" and thinking about it ... zip zip rar zip zip ? :-)

GWRon commented 2 years ago

Exactly... there is no .rar support in archive.mod yet

WGZero05 commented 2 years ago

Hello Sir.Brucey. Can you please fix this wa.AddEntry("ν…ŒμŠ€νŠΈ_데이터.txt", "files/ν…ŒμŠ€νŠΈ_데이터.txt") How could I extract a file when the entry.Pathname() is empty? Wtf! πŸ˜‚ It is so strange how the mod can compress the name but it can't extract the name again. πŸ˜‚

And also how could I get out or leave without terminating my code in Blitzmax when I'm using SetPassphraseCallback? It keeps repeating again and again. 😩

GWRon commented 2 years ago

I now wanted to try out if you could "remove" the callback from the archive inside the callback (so it wont be called again) but ...

... simply running "example_03" does not ask me for a password :)

$ ./example_03 
File : files/testdata.txt
Size : 67
String size   : 67
First n chars : This is some data

File : files/ν…ŒμŠ€νŠΈ_데이터.txt
Size : 67
String size   : 67
First n chars : This is some data

Needed to move encryption to "zip" not "aes" ...

GWRon commented 2 years ago

I tried this here:

Function GetPass:String(archive:TReadArchive, data:Object)
    Global IGotAskedTimes:Int
    'remove this callback
    If IGotAskedTimes = 2 Then archive.SetPassphraseCallback(Null, Null)

    Local pass:String
    If IGotAskedTimes = 0
        pass = Input("Enter Password (abc123): ")
    Else
        pass = Input("Enter Password (abc123) attempt #" + (IGotAskedTimes + 1)+": ")
    EndIf

    IGotAskedTimes :+ 1
    Return pass
End Function

but it seems it did not handle it as I wanted:

$ ./example_03 

File : files/testdata.txt
Size : 67
Enter Password (abc123): hello
Enter Password (abc123) attempt #2: on
Enter Password (abc123) attempt #3: earth
Attempt to call uninitialized function pointer

Did not see a API function to remove that callback ... Also I do not see a way to abort the "open" action there. So Brucey might need to add a way to "null" the callback -- means you could set it inside your callback to "give up"

archive_read_add_passphrase.c:

int
archive_read_set_passphrase_callback(struct archive *_a, void *client_data,
    archive_passphrase_callback *cb)
{
    struct archive_read *a = (struct archive_read *)_a;

    archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW,
        "archive_read_set_passphrase_callback");

    a->passphrases.callback = cb;
    a->passphrases.client_data = client_data;
    return (ARCHIVE_OK);
}

....

/*
 * Get a passphrase for decryption.
 */
const char *
__archive_read_next_passphrase(struct archive_read *a)
{
...
    else if (a->passphrases.callback != NULL) {
        /* Get a passphrase through a call-back function
         * since we tried all passphrases out or we don't
         * have it. */
WGZero05 commented 2 years ago

I now wanted to try out if you could "remove" the callback from the archive inside the callback (so it wont be called again) but ...

... simply running "example_03" does not ask me for a password :)

$ ./example_03 
File : files/testdata.txt
Size : 67
String size   : 67
First n chars : This is some data

File : files/ν…ŒμŠ€νŠΈ_데이터.txt
Size : 67
String size   : 67
First n chars : This is some data

Needed to move encryption to "zip" not "aes" ...

@GWRon How you did it? I still can't figure it out. I've been so stressed Haha.

Untitled

GWRon commented 2 years ago

?? as said ... I tried to remove the callback. Raised an issue for it (as you need - for now - to check a field <> Null inside the callback function)

While this removes the callback it then will continue processing (and is not able to iterate over the files because of the missing password). So it requires a way to "identify" if a password was required - and the right one was passed in.

WGZero05 commented 2 years ago

@GWRon I did not use the Callback function and my zip is not encrypted either. Can you show me how could you revealed the Korean words? files/ν…ŒμŠ€νŠΈ_데이터.txt

GWRon commented 2 years ago

utf8 aware console ... dunno if maxide does it on windows.

I used Geany to edit the files and my simple compile-it-plugin to execute bmk with the appropriate commands.

If you "read" the filename it will be correct but once you write it to display it to you ... it might look "wrong". You could save the filename to a file (writestream("utf8::myfile.txt") and compare the content then.

woollybah commented 2 years ago

Thank you for the update of RAR, Mr.WoollyBah. By the way... Why can't I create a rar file? It terminated my code without warning. What did I miss?

Rar support is read-only, I'm afraid, since that's what comes with the library we are using for all of this.

GWRon commented 2 years ago

I assume rar write support is missing because of the licence for rar encoders/"archivers".

Simply use 7z...it is good enough as all purpose archiver

WGZero05 commented 2 years ago

Sir.Brucey can you please help me how to get the string or the title of files from my zip file? Some of my Korean mp3 songs from zip are not visible when I'm going to read the archive. The names are not visible. entry.pathname(). Can you please fix this issue. Thank you. πŸ™πŸ»

And how could i know when the data inside zip is a file or folder? Is there any way how to get the filetype inside of zip? for example:


SuperStrict

Framework Archive.Zip
Import brl.standardio

Local wa:TWriteArchive = New TWriteArchive
wa.SetFormat(EArchiveFormat.ZIP)
wa.SetCompressionLevel(9)

wa.Open("data.zip")

wa.AddEntry("testdata.txt", "files/testdata.txt")
wa.AddEntry("ν…ŒμŠ€νŠΈ_데이터.txt", "files/ν…ŒμŠ€νŠΈ_데이터.txt")
wa.AddEntry("", "empty", 0, 0, EArchiveFileType.Dir)

wa.Close()

Local entry:TArchiveEntry = New TArchiveEntry

Local ra:TReadArchive = New TReadArchive
ra.SetFormat(EArchiveFormat.ZIP)
ra.Open("data.zip")

While ra.ReadNextHeader(entry) = ARCHIVE_OK

       If Filetype (entry.Pathname())=1
                  print "This is a file: "+entry.Pathname()  'output: files/testdata.txt
        else
                  print "Folder: "+entry.Pathname()   'output: empty
       endif

Wend

ra.Free()
GWRon commented 2 years ago

Can you give some filenames which do not work for you? Paste them as text here ..or better: write a .bmx sample file, zip it (manually ;-)) and attach it to your post here. That way we see the actual encoding you use ...and this might help brucey to replicate an issue (or to check if it must fail elsewhere)

GWRon commented 2 years ago
SuperStrict

Framework Archive.Zip
Import brl.standardio

Local entry:TArchiveEntry = New TArchiveEntry
Local ra:TReadArchive = New TReadArchive
ra.SetFormat(EArchiveFormat.ZIP)
ra.Open("SampleMusic.zip")

While ra.ReadNextHeader(entry) = ARCHIVE_OK
    print entry.Pathname()
Wend

ra.Free()

output:

./bmk makeapp -g x64 -t console -quick -r -x "/home/ronny/Arbeit/Projekte/Testcodes/archive_test/MyData/reader.bmx" (im Verzeichnis: /home/ronny/Arbeit/Tools/BlitzMaxNG/bin)
[ 88%] Processing:reader.bmx
[ 94%] Compiling:reader.bmx.console.release.linux.x64.c
[100%] Linking:reader
Executing:reader

( Code Geass ) ORANGE RANGE - Shiawase Neiro.mp3
04 Who Am I.mp3
05. WAITING FOR YOUR LOVE - STEVIE B.mp3
SHINee μƒ€μ΄λ‹ˆ_Sherlockβ€’μ…œλ‘ (Clue   Note)_Music Video.mp3
GWRon commented 2 years ago

and after adjusting 2 lines of code ("playMusic" is not defined in your app - commented it out)

image

maxgui adjustment:

Function CreateCanvas:TGadget( x,y,w,h,group:TGadget,style=0 )
    Local t:TGadget=maxgui_driver.CreateGadget(GADGET_CANVAS,"",x,y,w,h,GetGroup(group),style)
    t.AttachGraphics int(DefaultGraphicsFlags())    'gfxFlags    '<----------- default is long, attach wants int
    Return t
End Function
WGZero05 commented 2 years ago

@GWRon Well, this is my output. The Korean song was not added. Both in my Win7 and Win10. The result is still the same. The entry.Pathname() is empty. Can you please try my code on your Windows and not on Linux?

WGMusicPlayer

WGZero05 commented 2 years ago

@woollybah Thank you Sir.Brucey for adding FileType()!. Can I use that method in all formats you created? Like Opening the .7z, .gz, etc.. ?

GWRon commented 2 years ago

Can you try to append this: Len(entry.pathname()) Or use it instead of the path name when adding the gadgetitem?

If the len is 0..then indeed it had issues to read in the filename. If the len is a two digit number (am not using on my computer) then it just has an issue displaying/adding it to the gui.

You can also print when adding an entry... But always prepend something as "print" ignores empty lines (you do not get a simple new line).

WGZero05 commented 2 years ago

image

WGZero05 commented 2 years ago

Ok... who deleted my comment? I can't see my comment!

WGZero05 commented 2 years ago

Can you see my comment with the photo? I can't see it!

image

GWRon commented 2 years ago

I can see it..i even received it twice via notification mail.

The screenshot shows that it had indeed issues to decode the name correct... Now this will be something for Brucey to hopefully fiddle out.

It works on linux but seems not on (his) windows.

WGZero05 commented 2 years ago

I can see it..i even received it twice via notification mail.

The screenshot shows that it had indeed issues to decode the name correct... Now this will be something for Brucey to hopefully fiddle out.

It works on linux but seems not on (his) windows.

I Hope Sir.brucey will fix this soon! πŸ™πŸ»

WGZero05 commented 2 years ago

Hello Sir.Brucey. I'm still waiting you to show me how to add files to already existing archive. Please provide examples. πŸ™ Thanks!

GWRon commented 2 years ago

Think I have shown a way - and with the newly added method for archiveEntry objects it should be working.

There might be another approach but I am not sure if it works for all archive types - which might explain, why it is not existing as "convenient function":

WGZero05 commented 2 years ago

Hello Sir.Brucey. Can you not use the 7-zip module? ( https://www.7-zip.org/ ) I found out that some paid softwares are using the module of 7-zip app bcoz the app is freeware. And they are also using all the commands from 7-zip Application. Can you not do cross the module of 7-zip with your archive.mod?

GWRon commented 2 years ago

You can simply use TProcess to execute the 7z.exe with the params you need.

WGZero05 commented 2 years ago

That shit idea is not what i want. What i want is how can I get all the names with Korean/Chinese language of the files from my zip file so that i could add them to AddGadgetItem (). Brucey Archive module is not satisfying and doesn't make me happy to use it anymore.