New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
net/http: Filename from FormFile header does not contain slashes #15664
Comments
@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. $ go run server.go Then for the client $ go run client.go <paths....> However, I get Maybe that's a Windows thing? Code inlined
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)
}
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)
}
} |
Thanks odekm-em - I have actually used very similar code (to my original) on windows and Linux with go 1.5.2 and not had any problems. |
@alexbrainman Here is my code in a form that you should be able to just copy and run on windows - thanks. Scott 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, "validate: %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,sans-serif;margin-top:4em;margin-left:4em}
</style>
</head>
<body>
<h2>Validation</h2>
<p>Select and submit a file to validate.</p>
<form enctype="multipart/form-data" action="validate" method="post">
<input type="file" name="file" />
<input type="submit" value="Submit"/>
</form>
</body>
</html>
` |
@sdicker8 thank you for the code, but you're not telling me what is the problem with your code. I can compile your code, and run that program. But it outputs nothing. Do you expect your program to output something? What? Also you're calling url.dll,FileProtocolHandler from your program. Is that related to your problem? Does your problem goes away if you remove call into url.dll,FileProtocolHandler? Alex |
@alexbrainman When I run this code it opens a webpage with a form. When I then submit a file, which invokes FormFile, I get a filepath from Filename that does not contain slashes (e.g. C:UserssdrDesktoptest.csv). I am using 1.6.2 on windows. I use the call to exec.Command(...).Start() as a convenience so that the initial page opens automatically. Removing it does not change the problem. As an aside, I've been using Go for a couple of years now and I think it's awesome. Thank you very much for the time and effort you put into it. Scott |
It does that here.
I have this:
I run your program. It opens my browser. I click "Choose File" button. That opens "Open file" dialogue. I navigate into c:\tmp directory and select A.TXT file in that directory. Now I am back to my broser, and it says "A.TXT" next to "Choose File" button. Next I click Submit button, and browser displays My program says this:
Me too.
That is fine. I kept your code as is, but I still don't see how I can make form filename include full path. Wha am I doing wrong? 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 edit: Emmanuel didn't use a browser - sorry. |
@sdicker8 I can reproduce your problem with IE11. Unfortunately I don't have time to debug this - I will be travelling until mid June. But you can try it yourself. All you need to do is insert fmt.Printf into standard library code (and rebuild it) to find the culprit. Then we could decide what to do about it. I suspect the problem is in mime package, but you should start from the start net/http. Alex |
@alexbrainman Will do - thanks |
@alexbrainman @bradfitz The backslashes in the filepath are ultimately removed in mime.consumeValue. I can provide the full function call path from request.FormFile if needed. When IE is run in the 'Internet Zone', by using 127.0.0.1 instead of localhost, only the filename is returned, as expected, and not the (modified) filepath. So, it appears we have a bit of a corner case where developers who are working with, or intend to use, localhost on IE may get sidetracked a bit. A note in the docs might suffice - but you guys are in a better position to make that call. Thank you for your time. Scott |
Thank you for investigating. I will look into it when I get back from my travelling. Alex |
I tried debugging this. If I apply
against db82cf4, the program will output
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: Maybe we should try and return the last element of filename path. I am not sure. I will let @bradfitz decide here. Alex |
It seems OK to me to try to accommodate MSIE here. What MSIE is sending is pretty easy to distinguish from what other clients sends. That is, there's not really ambiguity here: nothing would go out of its way to put unnecessary backslashes into the encoding of "c:devgorobots.txt" to produce "c:\dev\go\robots.txt". The intention behind the latter seems quite unambiguous. I'll send a CL and we'll see. |
CL https://golang.org/cl/32175 mentions this issue. |
Please answer these questions before submitting your issue. Thanks!
go version
)?1.6.2 window/amd64
go env
)?windows 7 amd64
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 = `
<title>Validation</title> <style type="text/css"> body{font-family:arial;margin-top:4em;margin-left:4em} </style>Validation
Select and submit a file to validate.
` 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.csvThe text was updated successfully, but these errors were encountered: