diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go
index b54c904d21cf810a7d1304eb0346c55e660424fa..447ddd35af2465f10f76416d6013acf4ae712876 100644
--- a/httpbin/handlers_test.go
+++ b/httpbin/handlers_test.go
@@ -542,6 +542,7 @@ func testRequestWithBody(t *testing.T, verb, path string) {
 		testRequestWithBodyBodyTooBig,
 		testRequestWithBodyEmptyBody,
 		testRequestWithBodyFormEncodedBody,
+		testRequestWithBodyMultiPartBodyFiles,
 		testRequestWithBodyFormEncodedBodyNoContentType,
 		testRequestWithBodyInvalidFormEncodedBody,
 		testRequestWithBodyInvalidJSON,
@@ -816,6 +817,47 @@ func testRequestWithBodyMultiPartBody(t *testing.T, verb, path string) {
 	}
 }
 
+func testRequestWithBodyMultiPartBodyFiles(t *testing.T, verb, path string) {
+	var body bytes.Buffer
+	mw := multipart.NewWriter(&body)
+
+	// Add a file to the multipart request
+	part, _ := mw.CreateFormFile("fieldname", "filename")
+	part.Write([]byte("hello world"))
+	mw.Close()
+
+	r, _ := http.NewRequest(verb, path, bytes.NewReader(body.Bytes()))
+	r.Header.Set("Content-Type", mw.FormDataContentType())
+	w := httptest.NewRecorder()
+	app.ServeHTTP(w, r)
+
+	assertStatusCode(t, w, http.StatusOK)
+	assertContentType(t, w, jsonContentType)
+
+	var resp *bodyResponse
+	err := json.Unmarshal(w.Body.Bytes(), &resp)
+	if err != nil {
+		t.Fatalf("failed to unmarshal body %#v from JSON: %s", w.Body.String(), err)
+	}
+
+	if len(resp.Args) > 0 {
+		t.Fatalf("expected no query params, got %#v", resp.Args)
+	}
+
+	// verify that the file we added is present in the `files` attribute of the
+	// response, with the field as key and content as value
+	wantFiles := map[string][]string{
+		"fieldname": {"hello world"},
+	}
+	if !reflect.DeepEqual(resp.Files, wantFiles) {
+		t.Fatalf("want resp.Files = %#v, got %#v", wantFiles, resp.Files)
+	}
+
+	if resp.Method != verb {
+		t.Fatalf("expected method to be %s, got %s", verb, resp.Method)
+	}
+}
+
 func testRequestWithBodyInvalidFormEncodedBody(t *testing.T, verb, path string) {
 	r, _ := http.NewRequest(verb, path, strings.NewReader("%ZZ"))
 	r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
diff --git a/httpbin/helpers.go b/httpbin/helpers.go
index 71cc6d07d25dc5201527dedc1da0da613b9a832b..606983af1ba580f2cd97c35633cb5181e656a157 100644
--- a/httpbin/helpers.go
+++ b/httpbin/helpers.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"io"
 	"math/rand"
+	"mime/multipart"
 	"net/http"
 	"net/url"
 	"strconv"
@@ -109,6 +110,28 @@ func writeHTML(w http.ResponseWriter, body []byte, status int) {
 	writeResponse(w, status, htmlContentType, body)
 }
 
+// parseFiles handles reading the contents of files in a multipart FileHeader
+// and returning a map that can be used as the Files attribute of a response
+func parseFiles(fileHeaders map[string][]*multipart.FileHeader) (map[string][]string, error) {
+	files := map[string][]string{}
+	for k, fs := range fileHeaders {
+		files[k] = []string{}
+
+		for _, f := range fs {
+			fh, err := f.Open()
+			if err != nil {
+				return nil, err
+			}
+			contents, err := io.ReadAll(fh)
+			if err != nil {
+				return nil, err
+			}
+			files[k] = append(files[k], string(contents))
+		}
+	}
+	return files, nil
+}
+
 // parseBody handles parsing a request body into our standard API response,
 // taking care to only consume the request body once based on the Content-Type
 // of the request. The given bodyResponse will be modified.
@@ -175,6 +198,11 @@ func parseBody(w http.ResponseWriter, r *http.Request, resp *bodyResponse) error
 			return err
 		}
 		resp.Form = r.PostForm
+		files, err := parseFiles(r.MultipartForm.File)
+		if err != nil {
+			return err
+		}
+		resp.Files = files
 	case ct == "application/json":
 		err := json.NewDecoder(r.Body).Decode(&resp.JSON)
 		if err != nil && err != io.EOF {
diff --git a/httpbin/helpers_test.go b/httpbin/helpers_test.go
index 1057df464b70abfbc7db3e895714c5a9f0d6b9f9..a11c691d1b60d403da10fd7b0f7670bd6bb496db 100644
--- a/httpbin/helpers_test.go
+++ b/httpbin/helpers_test.go
@@ -4,6 +4,8 @@ import (
 	"crypto/tls"
 	"fmt"
 	"io"
+	"io/fs"
+	"mime/multipart"
 	"net/http"
 	"net/url"
 	"reflect"
@@ -293,3 +295,21 @@ func Test_getClientIP(t *testing.T) {
 		})
 	}
 }
+
+func TestParseFileDoesntExist(t *testing.T) {
+	// set up a headers map where the filename doesn't exist, to test `f.Open`
+	// throwing an error
+	headers := map[string][]*multipart.FileHeader{
+		"fieldname": {
+			{
+				Filename: "bananas",
+			},
+		},
+	}
+
+	// expect a patherror
+	_, err := parseFiles(headers)
+	if _, ok := err.(*fs.PathError); !ok {
+		t.Fatalf("Open(nonexist): error is %T, want *PathError", err)
+	}
+}