I wrote a small utility in Go to zip a folder. It seems to work in many cases, but every now and then it produces a zip file that is coming up as corrupt when I open it in an unzip app (they all seem to complain about it).
Here is the code:
const (
singleFileByteLimit = 107374182400 // 1 GB
chunkSize = 1024 // 1 KB
)
// ZipFolder zips the given folder to the a zip file
// with the given name
func ZipFolder(srcFolder string, destFile string) error {
z := &zipper{
srcFolder: srcFolder,
destFile: destFile,
}
return z.zipFolder()
}
// We need a struct internally because the filepath WalkFunc
// doesn't allow custom params. So we save them here so it can
// access them
type zipper struct {
srcFolder string
destFile string
writer *zip.Writer
}
// internal function to zip a folder
func (z *zipper) zipFolder() error {
// create zip file
zipFile, err := os.Create(z.destFile)
if err != nil {
return err
}
defer zipFile.Close()
// create zip writer
z.writer = zip.NewWriter(zipFile)
// traverse the source folder
err = filepath.Walk(z.srcFolder, z.zipFile)
if err != nil {
return nil
}
// close the zip file
err = z.writer.Close()
if err != nil {
return err
}
return nil
}
// internal function to zip a file, called by filepath.Walk on each file
func (z *zipper) zipFile(path string, f os.FileInfo, err error) error {
// only zip files (directories are created by the files inside of them)
// TODO allow creating folder when no files are inside
if !f.IsDir() && f.Size() > 0 {
// open file
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// create new file in zip
fileName := strings.TrimPrefix(path, z.srcFolder+"/")
w, err := z.writer.Create(fileName)
if err != nil {
return err
}
// copy contents of the file to the zip writer
err = copyContents(file, w)
if err != nil {
return err
}
}
return nil
}
func copyContents(r io.Reader, w io.Writer) error {
var size int64
for {
b := make([]byte, chunkSize)
// we limit the size to avoid zip bombs
size += chunkSize
if size > singleFileByteLimit {
return errors.New("file too large, please contact us for assitance")
}
// read chunk into memory
length, err := r.Read(b)
if err == io.EOF {
break
} else if err != nil {
return err
}
// write chunk to zip file
_, err = w.Write(b[:length])
if err != nil {
return err
}
}
return nil
}
Reading through your code, I fixed things that didn't look right. Try the following:
const (
singleFileByteLimit = 107374182400 // 1 GB
chunkSize = 4096 // 4 KB
)
func copyContents(r io.Reader, w io.Writer) error {
var size int64
b := make([]byte, chunkSize)
for {
// we limit the size to avoid zip bombs
size += chunkSize
if size > singleFileByteLimit {
return errors.New("file too large, please contact us for assistance")
}
// read chunk into memory
length, err := r.Read(b[:cap(b)])
if err != nil {
if err != io.EOF {
return err
}
if length == 0 {
break
}
}
// write chunk to zip file
_, err = w.Write(b[:length])
if err != nil {
return err
}
}
return nil
}
// We need a struct internally because the filepath WalkFunc
// doesn't allow custom params. So we save them here so it can
// access them
type zipper struct {
srcFolder string
destFile string
writer *zip.Writer
}
// internal function to zip a file, called by filepath.Walk on each file
func (z *zipper) zipFile(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// only zip files (directories are created by the files inside of them)
// TODO allow creating folder when no files are inside
if !f.Mode().IsRegular() || f.Size() == 0 {
return nil
}
// open file
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// create new file in zip
fileName := strings.TrimPrefix(path, z.srcFolder+"/")
w, err := z.writer.Create(fileName)
if err != nil {
return err
}
// copy contents of the file to the zip writer
err = copyContents(file, w)
if err != nil {
return err
}
return nil
}
// internal function to zip a folder
func (z *zipper) zipFolder() error {
// create zip file
zipFile, err := os.Create(z.destFile)
if err != nil {
return err
}
defer zipFile.Close()
// create zip writer
z.writer = zip.NewWriter(zipFile)
err = filepath.Walk(z.srcFolder, z.zipFile)
if err != nil {
return nil
}
// close the zip file
err = z.writer.Close()
if err != nil {
return err
}
return nil
}
// ZipFolder zips the given folder to the a zip file
// with the given name
func ZipFolder(srcFolder string, destFile string) error {
z := &zipper{
srcFolder: srcFolder,
destFile: destFile,
}
return z.zipFolder()
}
Related
I am trying to write a buffer into my .log file to log what the buffer gets.
When I try a string in my logger, it works fine.
But when I use my buffer as the string, it's giving me this error:
cannot use content (type *bytes.Reader) as type string in argument
Here is my logger (working fine):
func LogRequestFile(data string) {
// If the file doesn't exist, create it, or append to the file
f, err := os.OpenFile("loggies.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
if _, err := f.Write([]byte(data)); err != nil {
f.Close() // ignore error; Write error takes precedence
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
Here is where I am calling the log:
func (p *SomeFunction) FunctionName(buffer []byte) []byte {
if len(buffer) > 0 && p.Payload != "" {
buffer = bytes.Replace(buffer, []byte("</body>"), []byte("<jamming>"+p.Payload), 1)
}
var content = bytes.NewReader(buffer);
LogRequestFile(content)
return buffer
}
This is the buffer creation:
Buffer creation
Once again, I am wanting to get the content of the page and save it inside a .log file.
As you see:
buffer = bytes.Replace(buffer, []byte("</body>"), []byte("<jamming>"+p.Payload), 1)
The above code works to replace a section of the html page.
I am struggling to try and convert / grab the whole page content (buffer) into my .log file.
Okay, so it appears it was my eyes being stupid.
I changed to this now it works.
func (p *SomeFunction) FunctionName(buffer []byte) []byte {
if len(buffer) > 0 && p.Payload != "" {
log.Debugf(" -- Injecting JS [%s] \n", p.Payload)
buffer = bytes.Replace(buffer, []byte("</body>"), []byte("<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js'></script><script>"+p.Payload+"</script></body>"), 1)
buffer = bytes.Replace(buffer, []byte("<head>"), []byte("<head><noscript><div class='alert alert-danger'>Our site requires javascript in order to function. Please enabled it and refresh the page.</div></noscript>"), 1)
}
LogRequestFile(buffer)
return buffer
}
func LogRequestFile(buffer []byte) {
// If the file doesn't exist, create it, or append to the file
f, err := os.OpenFile("loggies.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
if _, err := f.Write([]byte(buffer)); err != nil {
f.Close() // ignore error; Write error takes precedence
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
I know about ioutil.ReadDir and os.filePath but none of them traverse the directory in Breadth first fashion.
My approach is to call ioutil.ReadDir and append all the contents of the root dir into a slice. Then I am iterating over the contents and checking if it IsDir[] and calling the function recursively if true.
package main
import (
"io/ioutil"
"os"
)
var files []string
var path string
func appendFiles(root string) {
fileInfo, err := ioutil.ReadDir(root)
if err != nil {
return
}
for _, file := range fileInfo {
files = append(files, file.Name())
}
for _, file := range fileInfo {
fileStat, _ := os.Stat(file.Name())
if fileStat.Mode().IsDir() {
// path = path + "/" + file.Name()
appendFiles(file.Name())
}
}
}
func main() {
appendFiles(".")
}
The problem is that os.Stat() may return an error which you omit. When that happens, fileStat may be nil, so calling fileStat.Mode() in the next line panics.
And the reason os.Stat() fails is because file.Name() is relative to root, file.Name() by itself has little chance to exist, it must be joined with root. If os.Stat() is called with a file name that doesn't exist, it returns a nil file info and a non-nil error.
You may use filepath.Join() to construct a valid path for files that os.Stat() will work with. And it would be better to handle errors, e.g. return them, which you can inspect in main().
func appendFiles(root string) error {
fileInfo, err := ioutil.ReadDir(root)
if err != nil {
return fmt.Errorf("ReadDir error: %w", err)
}
for _, file := range fileInfo {
files = append(files, filepath.Join(root, file.Name()))
}
for _, file := range fileInfo {
fullName := filepath.Join(root, file.Name())
fileStat, err := os.Stat(fullName)
if err != nil {
return fmt.Errorf("Stat error: %w", err)
}
if fileStat.Mode().IsDir() {
if err := appendFiles(fullName); err != nil {
return fmt.Errorf("appendFiles error: %w", err)
}
}
}
return nil
}
func main() {
if err := appendFiles("."); err != nil {
fmt.Println(err)
}
}
There're examples about compressing a file to .zip in Go. However, the file they generate include the directory folder. When I decompress the .zip file, there will be a new folder.
So, how can I compress a file to .zip without getting the directory folder included?
An example:
https://golangcode.com/create-zip-files-in-go/
package main
import (
"archive/zip"
"fmt"
"io"
"os"
)
func main() {
// List of Files to Zip
files := []string{"example.csv", "data.csv"}
output := "done.zip"
if err := ZipFiles(output, files); err != nil {
panic(err)
}
fmt.Println("Zipped File:", output)
}
// ZipFiles compresses one or many files into a single zip archive file.
// Param 1: filename is the output zip file's name.
// Param 2: files is a list of files to add to the zip.
func ZipFiles(filename string, files []string) error {
newZipFile, err := os.Create(filename)
if err != nil {
return err
}
defer newZipFile.Close()
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
// Add files to zip
for _, file := range files {
if err = AddFileToZip(zipWriter, file); err != nil {
return err
}
}
return nil
}
func AddFileToZip(zipWriter *zip.Writer, filename string) error {
fileToZip, err := os.Open(filename)
if err != nil {
return err
}
defer fileToZip.Close()
// Get the file information
info, err := fileToZip.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// Using FileInfoHeader() above only uses the basename of the file. If we want
// to preserve the folder structure we can overwrite this with the full path.
header.Name = filename
// Change to deflate to gain better compression
// see http://golang.org/pkg/archive/zip/#pkg-constants
header.Method = zip.Deflate
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, fileToZip)
return err
}
Just use a base name of the file in the zip header.
header.Name = filepath.Base(filename)
^^^^^^^^^^^^^^
Here is a version that does the same thing
package main
import (
"archive/zip"
"io"
"log"
"os"
"path/filepath"
)
func createFlatZip(w io.Writer, files ...string) error {
z := zip.NewWriter(w)
for _, file := range files {
src, err := os.Open(file)
if err != nil {
return err
}
info, err := src.Stat()
if err != nil {
return err
}
hdr, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
hdr.Name = filepath.Base(file) // Write only the base name in the header
dst, err := z.CreateHeader(hdr)
if err != nil {
return err
}
_, err = io.Copy(dst, src)
if err != nil {
return err
}
src.Close()
}
return z.Close()
}
func main() {
if len(os.Args) < 3 {
log.Fatalf("archive name and at least one file are required")
}
a, err := os.Create(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer a.Close()
err = createFlatZip(a, os.Args[2:]...)
if err != nil {
log.Fatal(err)
}
}
Results:
~/src/gozip
➜ go build
~/src/gozip
➜ mkdir test && echo 1 > test/1.txt # create a test file in a subfolder
~/src/gozip
➜ ./gozip 1.zip test/1.txt
~/src/gozip
➜ unzip -l 1.zip
Archive: 1.zip
Length Date Time Name
--------- ---------- ----- ----
2 08-15-2019 01:29 1.txt
--------- -------
2 1 file
I have a task to copy directory, and paste it in an another folder with conditions using Go.
For example I have a directory tree such like that:
project
---app(where to copy)
---packages(from where copy)
------process
---------client01
------------build(folder)
---------------main.go
---------------config.json
---------------someFolder
------------someText.txt
---------client02
------------test4
---------------build
------------testProject
---------client04
------------projectX
------------test.go
The condition is copying only directory which only has child folder with name "build" and in the copied parent folder should be only build folder within its files.
package main
import (
"io"
"io/ioutil"
"os"
"path"
)
// File copies a single file from src to dst
func File(src, dst string) error {
var err error
var srcfd *os.File
var dstfd *os.File
var srcinfo os.FileInfo
if srcfd, err = os.Open(src); err != nil {
return err
}
defer srcfd.Close()
if dstfd, err = os.Create(dst); err != nil {
return err
}
defer dstfd.Close()
if _, err = io.Copy(dstfd, srcfd); err != nil {
return err
}
if srcinfo, err = os.Stat(src); err != nil {
return err
}
return os.Chmod(dst, srcinfo.Mode())
}
// Dir copies a whole directory recursively
func Dir(src string, dst string) error {
//var files Files
var err error
var fds []os.FileInfo
var srcinfo os.FileInfo
if srcinfo, err = os.Stat(src); err != nil {
return err
}
if fds, err = ioutil.ReadDir(src); err != nil {
return err
}
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
return err
}
for _, fd := range fds {
srcfp := path.Join(src, fd.Name())
dstfp := path.Join(dst, fd.Name())
if fd.IsDir() && srcfp == src + "/build" {
//files = append(files, dstfp)
println(srcfp)
if err = Dir(srcfp, dstfp); err != nil {
return err
}
if err = File(srcfp, dstfp); err != nil {
return err
}
} else if fd.IsDir() && srcfp == srcfp {
if err = Dir(srcfp, dstfp); err != nil {
return err
}
} else {
if err = File(srcfp, dstfp); err != nil {
return err
}
}
}
return nil
}
func main () {
err := Dir("./packages", "./app")
if err != nil {
println(err)
}
}
I expected a result directory tree in app:
project
---app(where to copy)
------client01
---------build
------------main.go
------------config.json
------------someFolder
------test4
---------build
For instance, test4 and client01 copied because it has "build" child-folder, and it copies only build folder
But I got that result directory tree:
project
---app
------process(that folder should not be copied)
---------client01
-----------build
--------------main.go
--------------config.json
So far i can tell, you only need to change this line : err := Dir("./packages", "./app") into : err := Dir("./packages/process", "./app")
If you want something else, let it know. Because i am guessing what the real problem is.
I wrote code that unzips a file in a particular location then copies the contents of the folder to outside where the folder is unzipped then it removes the folder.
This is the Code I wrote:
package main
import (
"os"
"flag"
"fmt"
"io"
"path/filepath"
"os/exec"
"archive/zip"
"time"
)
func RemoveContents(dir string) error {
d, err := os.Open(dir)
if err != nil {
return err
}
names, err := d.Readdirnames(-1)
if err != nil {
return err
}
for _, name := range names {
err = os.RemoveAll(filepath.Join(dir, name))
if err != nil {
return err
}
}
d.Close()
return nil
}
func CopyFile(source string, dest string) (err error) {
sourcefile, err := os.Open(source)
if err != nil {
return err
}
defer sourcefile.Close()
destfile, err := os.Create(dest)
if err != nil {
return err
}
defer destfile.Close()
_, err = io.Copy(destfile, sourcefile)
if err == nil {
sourceinfo, err := os.Stat(source)
if err != nil {
err = os.Chmod(dest, sourceinfo.Mode())
}
}
return
}
func CopyDir(source string, dest string) (err error) {
// get properties of source dir
sourceinfo, err := os.Stat(source)
if err != nil {
return err
}
// create dest dir
err = os.MkdirAll(dest, sourceinfo.Mode())
if err != nil {
return err
}
directory, _ := os.Open(source)
objects, err := directory.Readdir(-1)
for _, obj := range objects {
sourcefilepointer := source + "/" + obj.Name()
destinationfilepointer := dest + "/" + obj.Name()
if obj.IsDir() {
// create sub-directories - recursively
err = CopyDir(sourcefilepointer, destinationfilepointer)
if err != nil {
fmt.Println(err)
}
} else {
// perform copy
err = CopyFile(sourcefilepointer, destinationfilepointer)
if err != nil {
fmt.Println(err)
}
}
}
return
}
func main() {
flag.Parse() // get the source and destination directory
source_dir := flag.Arg(0) // get the source directory from 1st argument
dest_dir := flag.Arg(1) // get the destination directory from the 2nd argument
os.MkdirAll("E:\\go\\copyDirectory\\myFile.zip",0777)
zipFilePath := "E:\\go\\copyDirectory\\myFile.zip"
tempWrkDir := "E:\\go\\copyDirectory\\"
//Read zip file and get path handle.
fileHandleReader, err := zip.OpenReader(zipFilePath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
//open zip file and read all the folder and files inside
for _, fileReadHandler := range fileHandleReader.Reader.File {
//read the file or folder handle inside zip
fileOpenHandle, err := fileReadHandler.Open()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fileOpenHandle.Close()
targetUnZipPath := filepath.Join(tempWrkDir, fileReadHandler.Name)
if fileReadHandler.FileInfo().IsDir() {
os.MkdirAll(targetUnZipPath, fileReadHandler.Mode())
//fmt.Println("Creating directory", path)
}else {
// create new dummy file to copy original file.
newTempFileHandle, err := os.OpenFile(targetUnZipPath, os.O_WRONLY|os.O_CREATE, fileReadHandler.Mode())
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer newTempFileHandle.Close()
//copying original file to dummy file.
if _, err = io.Copy(newTempFileHandle, fileOpenHandle); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
}
time.Sleep(1000*time.Millisecond)
fmt.Println("Source :" + source_dir)
// check if the source dir exist
src, err := os.Stat(source_dir)
if err != nil {
panic(err)
}
if !src.IsDir() {
fmt.Println("Source is not a directory")
os.Exit(1)
}
// create the destination directory
fmt.Println("Destination :"+ dest_dir)
/*_, err = os.Open(dest_dir)
if !os.IsNotExist(err) {
fmt.Println("Destination directory already exists. Abort!")
os.Exit(1)
}*/
err = CopyDir(source_dir, dest_dir)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Directory copied")
}
err = RemoveContents("./myFiles")
if err != nil {
fmt.Println("ERRR:::",err)
}
//time.Sleep(10000*time.Millisecond)
}
The problem is that everything works fine except for deleting the folder. The folder has only one file in it. The location of the file is as follows:
E:\go\copyDirectory\myfile\mytextfile.txt
The Location of the zip file is as follows:
E:\go\copyDirectory\myfile.zip
The zip file has only one text file. The File inside the zip file is as follows:
E:\go\copyDirectory\myfile.zip\myfile\mytextfile.txt
The error I get is:
ERRR::: remove myfile\mytextfile.txt: The process cannot
access the file because it is being used by another process.
Thanks in advance.
You aren't closing the file. This:
defer newTempFileHandle.Close()
Is run when main finishes, which is after:
err = RemoveContents("./myFiles")
You can wrap that bit of code in an unnamed function:
func() {
//read the file or folder handle inside zip
fileOpenHandle, err := fileReadHandler.Open()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fileOpenHandle.Close()
targetUnZipPath := filepath.Join(tempWrkDir, fileReadHandler.Name)
if fileReadHandler.FileInfo().IsDir() {
os.MkdirAll(targetUnZipPath, fileReadHandler.Mode())
//fmt.Println("Creating directory", path)
} else {
// create new dummy file to copy original file.
newTempFileHandle, err := os.OpenFile(targetUnZipPath, os.O_WRONLY|os.O_CREATE, fileReadHandler.Mode())
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer newTempFileHandle.Close()
//copying original file to dummy file.
if _, err = io.Copy(newTempFileHandle, fileOpenHandle); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
}()
And then your defer will happen before you try and remove the files. I would recommend pulling this out into a named function though.