MCPcopy
hub / github.com/dosco/graphjin / parseMultipartGraphQL

Function parseMultipartGraphQL

serv/upload.go:74–184  ·  view source on GitHub ↗

parseMultipartGraphQL parses a graphql-multipart-request-spec body and returns a populated gqlReq with file values injected at the paths declared in the `map` part. When `backend` is nil, files are inlined as base64 (legacy mode). When `backend` is non-nil, files are streamed to the backend and the

(r *http.Request, conf UploadsConfig, backend fstable.Backend)

Source from the content-addressed store, hash-verified

72//
73// Spec: https://github.com/jaydenseric/graphql-multipart-request-spec
74func parseMultipartGraphQL(r *http.Request, conf UploadsConfig, backend fstable.Backend) (gqlReq, error) {
75 maxSize := conf.MaxSize
76 if maxSize <= 0 {
77 maxSize = defaultMaxUploadSize
78 }
79
80 // Cap the raw body before any parsing; protects against zip-bomb
81 // style abuse (large declared length, slow trickle).
82 r.Body = http.MaxBytesReader(nil, r.Body, maxSize)
83
84 if err := r.ParseMultipartForm(maxSize); err != nil {
85 return gqlReq{}, fmt.Errorf("multipart: parse failed: %w", err)
86 }
87
88 // 1. operations: { query, operationName, variables }
89 opsRaw := r.FormValue("operations")
90 if opsRaw == "" {
91 return gqlReq{}, errors.New("multipart: missing 'operations' field")
92 }
93 var ops gqlReq
94 if err := json.Unmarshal([]byte(opsRaw), &ops); err != nil {
95 return gqlReq{}, fmt.Errorf("multipart: 'operations' is not valid JSON: %w", err)
96 }
97
98 // 2. map: { "0": ["variables.file"], "1": ["variables.files.0"] }
99 mapRaw := r.FormValue("map")
100 if mapRaw == "" {
101 // Spec mandates `map`; without it, no file injection happens.
102 // Treat as a structural error rather than silently dropping uploads.
103 return gqlReq{}, errors.New("multipart: missing 'map' field")
104 }
105 var fileMap map[string][]string
106 if err := json.Unmarshal([]byte(mapRaw), &fileMap); err != nil {
107 return gqlReq{}, fmt.Errorf("multipart: 'map' is not valid JSON: %w", err)
108 }
109
110 // Decode variables to a generic structure so we can write file
111 // values at arbitrary paths.
112 var vars map[string]any
113 if len(ops.Vars) > 0 {
114 if err := json.Unmarshal(ops.Vars, &vars); err != nil {
115 return gqlReq{}, fmt.Errorf("multipart: 'variables' is not valid JSON: %w", err)
116 }
117 } else {
118 vars = make(map[string]any)
119 }
120 root := map[string]any{"variables": vars}
121
122 allowed := buildMIMEAllowlist(conf.AllowedMIME)
123 ctx := r.Context()
124
125 for partName, paths := range fileMap {
126 fhs, ok := r.MultipartForm.File[partName]
127 if !ok || len(fhs) == 0 {
128 return gqlReq{}, fmt.Errorf("multipart: 'map' references file %q which is missing from the request", partName)
129 }
130 fh := fhs[0]
131

Calls 7

buildMIMEAllowlistFunction · 0.85
mimeAllowedFunction · 0.85
streamToBackendFunction · 0.85
setAtPathFunction · 0.85
GetMethod · 0.65
CloseMethod · 0.65
OpenMethod · 0.45