Skip to content

Commit 4f7f337

Browse files
committed
net.http: fix windows: Failed to upload a file via content_type: multipart/form-data (fixes #19256)
1 parent d02eaf2 commit 4f7f337

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

vlib/net/http/http_proxy_test.v

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,138 @@ fn test_http_proxy_do() {
244244
println('Proxy env vars (HTTP_PROXY or HTTPS_PROXY) not set. Skipping test.')
245245
}
246246
}
247+
248+
const multipart_https_payload_len = 20 * 1024 + 137
249+
250+
fn test_https_multipart_form_preserves_large_binary_body() ! {
251+
mut port_listener := net.listen_tcp(.ip, '127.0.0.1:0')!
252+
port := port_listener.addr()!.port()!
253+
port_listener.close()!
254+
255+
payload := multipart_https_test_payload()
256+
form := {
257+
'alpha': 'beta'
258+
}
259+
files := {
260+
'file': [
261+
FileData{
262+
filename: 'payload.bin'
263+
content_type: 'application/octet-stream'
264+
data: payload
265+
},
266+
]
267+
}
268+
body, boundary := multipart_form_body(form, files)
269+
270+
mut listener := mbedtls.new_ssl_listener('127.0.0.1:${port}', mbedtls.SSLConnectConfig{
271+
cert: proxy_https_test_cert_path
272+
cert_key: proxy_https_test_key_path
273+
validate: false
274+
})!
275+
server := spawn multipart_https_serve_once(mut listener, body, boundary, form, files)
276+
277+
mut header := new_header()
278+
header.set(.content_type, 'multipart/form-data; boundary="${boundary}"')
279+
resp := fetch(
280+
method: .post
281+
url: 'https://127.0.0.1:${port}/upload'
282+
header: header
283+
data: body
284+
validate: false
285+
)!
286+
server.wait()
287+
288+
assert resp.status_code == 200
289+
assert resp.body == 'ok'
290+
}
291+
292+
fn multipart_https_test_payload() string {
293+
mut payload := []u8{len: multipart_https_payload_len, init: u8(((index * 17) % 250) + 1)}
294+
payload[127] = 0
295+
payload[4096] = 0
296+
payload[16 * 1024] = 0
297+
payload[payload.len - 1] = `!`
298+
return payload.bytestr()
299+
}
300+
301+
fn multipart_https_serve_once(mut listener mbedtls.SSLListener, expected_body string, boundary string, expected_form map[string]string, expected_files map[string][]FileData) {
302+
defer {
303+
listener.shutdown() or {}
304+
}
305+
mut conn := listener.accept() or { panic(err) }
306+
conn.set_read_timeout(5 * time.second)
307+
defer {
308+
conn.shutdown() or {}
309+
}
310+
311+
request_text := read_https_request(mut conn) or { panic(err) }
312+
req := parse_request_str(request_text) or { panic(err) }
313+
314+
assert req.method == .post
315+
assert req.url == '/upload'
316+
assert req.data == expected_body
317+
assert req.data.len == expected_body.len
318+
assert req.header.get(.content_length) or { panic(err) } == expected_body.len.str()
319+
assert req.header.get(.content_type) or { panic(err) } == 'multipart/form-data; boundary="${boundary}"'
320+
321+
form, files := parse_multipart_form(req.data, boundary)
322+
assert form == expected_form
323+
assert files == expected_files
324+
325+
conn.write_string('HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok') or {
326+
panic(err)
327+
}
328+
}
329+
330+
fn read_https_request(mut conn mbedtls.SSLConn) !string {
331+
mut request := []u8{}
332+
mut buf := []u8{len: 1024}
333+
mut content_length := -1
334+
mut headers_end := -1
335+
336+
for {
337+
n := conn.read(mut buf) or {
338+
if err.code() == net.err_timed_out_code {
339+
return error('timed out while reading HTTPS request')
340+
}
341+
return err
342+
}
343+
if n <= 0 {
344+
break
345+
}
346+
request << buf[..n]
347+
348+
request_str := request.bytestr()
349+
if headers_end == -1 {
350+
headers_end = request_str.index('\r\n\r\n') or { -1 }
351+
if headers_end != -1 {
352+
headers := request_str[..headers_end]
353+
for line in headers.split('\r\n') {
354+
if line.to_lower().starts_with('content-length:') {
355+
content_length = line.all_after(':').trim_space().int()
356+
break
357+
}
358+
}
359+
}
360+
}
361+
362+
if headers_end != -1 && content_length >= 0 {
363+
body_start := headers_end + 4
364+
if request.len - body_start >= content_length {
365+
break
366+
}
367+
}
368+
}
369+
370+
if headers_end == -1 {
371+
return error('HTTPS request did not include a full header block')
372+
}
373+
if content_length < 0 {
374+
return error('HTTPS request did not include Content-Length')
375+
}
376+
body_start := headers_end + 4
377+
if request.len - body_start < content_length {
378+
return error('HTTPS request body was truncated: expected ${content_length} bytes, got ${request.len - body_start}')
379+
}
380+
return request.bytestr()
381+
}

0 commit comments

Comments
 (0)