go: net/http: Filename from FormFile header does not contain slashes

Please answer these questions before submitting your issue. Thanks!

  1. What version of Go are you using (go version)? 1.6.2 window/amd64
  2. What operating system and processor architecture are you using (go env)? windows 7 amd64
  3. What did you do? If possible, provide a recipe for reproducing the error. A complete runnable program is good. A link on play.golang.org is best. package main

import ( “fmt” “net/http” “os/exec” )

func validate(w http.ResponseWriter, r *http.Request) { file, header, err := r.FormFile(“file”) if err != nil { fmt.Fprintf(w, “%s\n”, err) fmt.Println(err) return } defer file.Close() fmt.Fprintf(w, “%s\n”, header.Header) fmt.Fprintf(w, “%s\n”, header.Filename) fmt.Println(header.Header) fmt.Println(header.Filename) }

func index(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, html) }

func main() { http.HandleFunc(“/”, index) http.HandleFunc(“/validate”, validate) go exec.Command(“rundll32”, “url.dll,FileProtocolHandler”, “http://localhost:8090/”).Start() http.ListenAndServe(“:8090”, nil) }

var html = `<!DOCTYPE html>

<html> <head> <charset="utf-8"> <title>Validation</title> <style type="text/css"> body{font-family:arial;margin-top:4em;margin-left:4em} </style> </head> <body>

Validation

Select and submit a file to validate.

<form enctype="multipart/form-data" action="validate" method="post"> </form> </body> </html> ` 1. What did you expect to see? map[Content-Disposition:[form-data; name="file"; filename="C:\Users\sdr\Desktop\test.csv"] Content-Type:[application/vnd.ms-excel]] C:\Users\sdr\Desktop\test.csv 2. What did you see instead? map[Content-Disposition:[form-data; name="file"; filename="C:\Users\sdr\Desktop\test.csv"] Content-Type:[application/vnd.ms-excel]] C:UserssdrDesktoptest.csv

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (8 by maintainers)

Most upvoted comments

I tried debugging this. If I apply

diff --git a/src/mime/mediatype.go b/src/mime/mediatype.go
index 1845401..fa599ca 100644
--- a/src/mime/mediatype.go
+++ b/src/mime/mediatype.go
@@ -102,6 +102,7 @@ func checkMediaTypeDisposition(s string) error {
 // The returned map, params, maps from the lowercase
 // attribute to the attribute value with its case preserved.
 func ParseMediaType(v string) (mediatype string, params map[string]string, err error) {
+   fmt.Printf("ParseMediaType(%q)\n", v)
    i := strings.Index(v, ";")
    if i == -1 {
        i = len(v)
@@ -127,6 +128,7 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
            break
        }
        key, value, rest := consumeMediaParam(v)
+       fmt.Printf("key=%q value=%q\n", key, value)
        if key == "" {
            if strings.TrimSpace(rest) == ";" {
                // Ignore trailing semicolons.

against db82cf4e506938, the program will output

C:\tmp>u:\test
ParseMediaType("multipart/form-data; boundary=---------------------------7e0f124280332")
key="boundary" value="---------------------------7e0f124280332"
ParseMediaType("multipart/form-data; boundary=---------------------------7e0f124280332")
key="boundary" value="---------------------------7e0f124280332"
ParseMediaType("form-data; name=\"file\"; filename=\"C:\\dev\\go\\robots.txt\"")
key="name" value="file"
key="filename" value="C:devgorobots.txt"
map[Content-Disposition:[form-data; name="file"; filename="C:\dev\go\robots.txt"
] Content-Type:[text/plain]]
C:devgorobots.txt

So the problem is in mime/consumeValue. IE does not escape \ in the filename. And, unlike all other browsers I tried, IE sends full path - which is, probably, not secure.

I googled for solutions: https://java.net/jira/si/jira.issueviews:issue-html/JERSEY-759/JERSEY-759.html https://github.com/mscdex/busboy/issues/24 http://jersey.576304.n2.nabble.com/Jersey-truncating-the-slashes-from-the-uploaded-file-name-td5984041.html

Maybe we should try and return the last element of filename path. I am not sure.

I will let @bradfitz decide here.

Alex

@alexbrainman @odeke-em Argh - this appears to be a browser specific problem. When I use IE11 (in a corporate environment) I get ‘filepaths’ with the slashes problem. When I switch to Firefox I get just filenames like you Alex.

I just tested this on Linux/amd64(centos) - go1.6.2 - Firefox and got filenames. Emmanuel, on some flavor of *NIX and browser, got filepaths.

Thanks Scott

edit: Emmanuel didn’t use a browser - sorry.

@sdicker8 I improved your code to produce a working sample that could be used for others to reproduce your bug run in the form of a client and server at https://github.com/odeke-em/bugs/tree/master/golang/15664 or in one place https://gist.github.com/odeke-em/46a8deba3ded6bb4f2169e2e80928442, or inlined below. To run the server, just run

$ go run server.go

Then for the client

$ go run client.go <paths....>

However, I get filename to be contain the proper slashes when run on *NIX since I don’t have access to Windows machines. screen shot 2016-05-12 at 8 29 07 pm

Maybe that’s a Windows thing?

Code inlined

  • client.go
package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
)

func exitIfErr(err error) {
    if err == nil {
        return
    }
    fmt.Fprintf(os.Stderr, "%v\n", err)
    os.Exit(-1)
}

func main() {
    if len(os.Args) < 2 {
        exitIfErr(fmt.Errorf("expecting atleast one arg"))
    }

    rest := os.Args[1:]
    for _, filename := range rest {
        f, err := os.Open(filename)
        exitIfErr(err)

        fields := map[string]string{
            "filename": filename,
        }
        res, err := multipartUpload("http://localhost:8090/validate", f, fields)
        _ = f.Close()
        exitIfErr(err)

        io.Copy(os.Stdout, res.Body)
        _ = res.Body.Close()
    }
}

func createFormFile(mw *multipart.Writer, filename string) (io.Writer, error) {
    return mw.CreateFormFile("file", filename)
}

func multipartUpload(destURL string, f io.Reader, fields map[string]string) (*http.Response, error) {
    if f == nil {
        return nil, fmt.Errorf("bodySource cannot be nil")
    }
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    fw, err := createFormFile(writer, fields["filename"])
    if err != nil {
        return nil, fmt.Errorf("createFormFile %v", err)
    }

    n, err := io.Copy(fw, f)
    if err != nil && n < 1 {
        return nil, fmt.Errorf("copying fileWriter %v", err)
    }

    for k, v := range fields {
        _ = writer.WriteField(k, v)
    }

    err = writer.Close()
    if err != nil {
        return nil, fmt.Errorf("writerClose: %v", err)
    }

    req, err := http.NewRequest("POST", destURL, body)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Content-Type", writer.FormDataContentType())

    if req.Close && req.Body != nil {
        defer req.Body.Close()
    }

    return http.DefaultClient.Do(req)
}
  • server.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "os/exec"
)

const html = `
<html>
  Validation
  <form method="POST" action="/validate" enctype="multipart/form-data">
    <input type="file" name="file" />
    <br />
    <input type="submit" value="Send" />
  </form>
</html>
`

func validate(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseMultipartForm(1 << 20); err != nil {
        fmt.Fprintf(w, "parseForm: %v\n", err)
        return
    }
    file, header, err := r.FormFile("file")
    if err != nil {
        fmt.Fprintf(w, "formFile retrieval %s\n", err)
        fmt.Println(err)
        return
    }
    defer file.Close()
    fmt.Fprintf(w, "%s\n", header.Header)
    fmt.Fprintf(w, "%s\n", header.Filename)
    fmt.Println(header.Header)
    fmt.Println(header.Filename)
}

func index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, html)
}

func main() {
    addr := ":8090"
    http.HandleFunc("/", index)
    http.HandleFunc("/validate", validate)
    if false { // This is your specific command, not present on *NIX
        go exec.Command("rundll32", "url.dll,FileProtocolHandler",
            fmt.Sprintf("http://localhost%s", addr)).Start()
    }
    if err := http.ListenAndServe(addr, nil); err != nil {
        log.Fatal(err)
    }
}