LeGEC
LeGEC

Reputation: 51840

http Request.FormFile : handle zip files?

I'm writing a web server in go.

On one of the pages, the user can upload a file.

I would like to be able to handle zip files.

In the archive/zip package, I only see two functions which allow me to read from a zip archive :

  1. func OpenReader(name string) (*ReadCloser, error)
  2. func NewReader(r io.ReaderAt, size int64) (*Reader, error)

I would like to avoid writing and reading back from the disk,
if I want to use the second function, I need to know the size of the uploaded file before calling the function.

Question

I will split my question in two parts :

  1. What would be the idiomatic way to read the unzipped content of a zip file uploaded through a standard multipart/form-data html form ?

  2. How can I get the actual size of a file uploaded through a html form ?

    func(req *http.Request) {
        f, h, err := req.FormFile("fileTag")
        if err != nil {
             panic(err)
        }
        var fileSize int = ??
    
        unzipper, err := zip.NewReader(f, fileSize)
    }
    

Upvotes: 7

Views: 4408

Answers (4)

trunov
trunov

Reputation: 11

You can take size from the header:

file, header, err := r.FormFile("zipfile")
// do something with error
zipReader, err := zip.NewReader(file, header.Size)
// do something with error

Upvotes: 0

Mr_Pink
Mr_Pink

Reputation: 109347

You can look for the file size in the FormFile's Header (which is a MIMEHEader).

h.Header.Get("Content-Length")

If there is no content length for the file, you can read it into a buffer first to get the size.

var buff bytes.Buffer
fileSize, err := buff.ReadFrom(f)

Other options are to seek to the end, as you put in your answer, or get the concrete Reader out of the interface. A multipart File will be an *io.SectionReader if it's in memory, or an *os.File if it was written to a temp file:

switch f := f.(type) {
case *io.SectionReader:
    fileSize = r.Size()
case *os.File:
    if s, err := f.Stat(); err == nil {
        fileSize = s.Size()
    }
}

Upvotes: 3

fabmilo
fabmilo

Reputation: 48330

I would use an in-memory buffer and make sure to limit the max upload size of a file (~100MB?) Here it is using io.Copy

import (
    "archive/zip"
    "bytes"
    "io"
    "net/http"
)

func HttHandler(req *http.Request) {

    f, _, err := req.FormFile("fileTag")

    if err != nil {
        panic(err)
    }

    buf := new(bytes.Buffer)

    fileSize, err := io.Copy(buf, f)
    if err != nil {
        panic(err)
    }

    zip.NewReader(bytes.NewReader(buf.Bytes()), fileSize)

}

Upvotes: 1

LeGEC
LeGEC

Reputation: 51840

Here is a way I found to get the size :

func(req *http.Request) {
    f, h, err := req.FormFile("fileTag")
    if err != nil {
         panic(err)
    }
    fileSize, err := f.Seek(0, 2) //2 = from end
    if err != nil {
         panic(err)
    }
    _, err = f.Seek(0, 0)
    if err != nil {
         panic(err)
    }

    unzipper, err := zip.NewReader(f, fileSize)
}

I don't find this solution very elegant or idiomatic.

Isn't there some cleaner way to handle this case ?

Upvotes: 2

Related Questions