Skip to content
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

ErrNoMultipartForm error and adjacent comments do not match code #1606

Closed
mark-pictor-csec opened this issue Aug 15, 2023 · 2 comments
Closed

Comments

@mark-pictor-csec
Copy link

mark-pictor-csec commented Aug 15, 2023

See v1.48.0, starting at http.go:937 and continuing to line 955.

The error and the comments for the error and the following function indicate the error is returned for a wrong content type. However, this error is actually returned when the multipart form boundary length is zero (line 954-955). I don't see any content-type checks in the function, and the boundary length check is the only one returning this error.

Suggested fix:
Return a better error on line 955 (e.g. zero-length multipart form boundary), and either add a content type check from which to return ErrNoMultipartForm or remove the error and update the function's comment (lines 943-944) to match.

Found while writing a unit test; was very confused how the content-type header could get lost. Finally looked at the code and realized what was going on.

@mark-pictor-csec
Copy link
Author

Ugh... turns out I didn't fully understand what was going on. I'd forgotten the content-type header needed to include the boundary. Since the boundary is read from the content-type, in most situations a zero-len boundary does imply a missing content-type header.

I'd recommend changing the wording of the error a bit, perhaps

-var ErrNoMultipartForm = errors.New("request has no multipart/form-data Content-Type")
+var ErrNoMultipartForm = errors.New("request Content-Type has bad boundary or is not multipart/form-data")

Reproducer (could be cleaned up to remove fiber, but I assume it's a simple enough issue to not matter)

import (
	"bytes"
	"mime/multipart"
	"net/http"
	"testing"

	"github.com/gofiber/fiber/v2"
)

func multipartForm(t *testing.T, addr string) *http.Request {
	buf := &bytes.Buffer{}
	w := multipart.NewWriter(buf)
	if err := w.WriteField("field", "value"); err != nil {
		t.Fatal(err)
	}
	file, err := w.CreateFormFile("field2", "file1")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := file.Write([]byte("file content")); err != nil {
		t.Fatal(err)
	}
	if err := w.Close(); err != nil {
		t.Fatal(err)
	}
	req, err := http.NewRequest("POST", "http://"+addr+"/a/b/c?query=blah", buf)
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Content-Type", "multipart/form-data")   // causes error
	// req.Header.Set("Content-Type", w.FormDataContentType()) // no error
	return req
}

func TestMultipart(t *testing.T) {

	app := fiber.New(fiber.Config{
		DisableStartupMessage: true,
	})
	t.Cleanup(func() { _ = app.Shutdown() })
	app.All("/:param/:optional?/*", func(ctx *fiber.Ctx) error {
		form, err := ctx.MultipartForm() // calls fasthttp function of the same name
		if err != nil {
			t.Error(err)
			return nil
		}
		if len(form.File) == 0 {
			t.Error("setup error - no files")
			return nil
		}
		if len(form.Value) == 0 {
			t.Error("setup error - no values")
			return nil
		}
		return nil
	})
	ln := newLocalListener(t, "tcp")
	go func() {
		if err := app.Listener(ln); err != nil {
			t.Error(err)
		}
	}()
	addr := ln.Addr().String()

	req := multipartForm(t, addr)
	_, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatal(err)
	}
}

Toggle commenting on the two req.Header.Set(...) lines for a functional version.

@erikdubbelboer
Copy link
Collaborator

Thanks! I have copied your suggestion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants