Failed to parse multipart message: EOF reading delimiter
codenoid opened this issue · comments
Description
Exception: Failed to parse multipart message: EOF reading delimiter (HTTP::Multipart::Error)
Steps to Reproduce
- puts env.redirect with HTTP::FormData.parse
require "kemal"
post "/" do |env|
HTTP::FormData.parse(env.request) do |upload|
filename = upload.filename
if !filename.is_a?(String)
"No filename included in upload"
else
file_path = ::File.join [Kemal.config.public_folder, "uploads/", filename]
File.open(file_path, "w") do |f|
IO.copy(upload.body, f)
end
end
end
env.redirect "/"
end
Kemal.run
Actual behavior: [What actually happens]
2018-09-09 02:25:24 +07:00 302 POST / 8.56ms
Exception: Failed to parse multipart message: EOF reading delimiter (HTTP::Multipart::Error)
from /usr/share/crystal/src/http/multipart/parser.cr:122:7 in 'fail'
from /usr/share/crystal/src/http/multipart/parser.cr:0:7 in 'close_delimiter?'
from src/routes/content-management.cr:69:33 in '->'
from lib/kemal/src/kemal/route.cr:255:3 in '->'
from lib/kemal/src/kemal/route_handler.cr:255:3 in 'process_request'
from lib/kemal/src/kemal/route_handler.cr:15:7 in 'call'
from /usr/share/crystal/src/http/server/handler.cr:24:7 in 'call_next'
from lib/kemal/src/kemal/websocket_handler.cr:13:14 in 'call'
from /usr/share/crystal/src/http/server/handler.cr:24:7 in 'call_next'
from lib/kemal/src/kemal/static_file_handler.cr:12:11 in 'call'
from /usr/share/crystal/src/http/server/handler.cr:24:7 in 'call_next'
from lib/kemal/src/kemal/exception_handler.cr:8:7 in 'call'
from /usr/share/crystal/src/http/server/handler.cr:24:7 in 'call_next'
from lib/kemal/src/kemal/log_handler.cr:10:35 in 'call'
from /usr/share/crystal/src/http/server/handler.cr:24:7 in 'call_next'
from lib/kemal/src/kemal/init_handler.cr:12:7 in 'call'
from /usr/share/crystal/src/http/server/request_processor.cr:39:11 in 'process'
from /usr/share/crystal/src/http/server/request_processor.cr:16:3 in 'process'
from /usr/share/crystal/src/http/server.cr:402:5 in 'handle_client'
from /usr/share/crystal/src/http/server.cr:368:13 in '->'
from /usr/share/crystal/src/fiber.cr:255:3 in 'run'
from /usr/share/crystal/src/fiber.cr:29:34 in '->'
from ???
Versions
Ubuntu 16.04, Crystal 0.26.1, Kemal 0.24.0
To reproduce this, you need to provide the HTTP request sent to the server.
done @straight-shoota
I get that particular message when my form submission is genuinely messed up. What we need to know is what data is being sent by your client to see if it looks good.
Dunno if it's the same problem but maybe also:
require "kemal"
post "/upload" do |env|
HTTP::FormData.parse(env.request) do |upload|
end
end
Kemal.run
with this client: curl -s http://127.0.0.1:3000/upload -X POST --data 'x=1'
results in Exception: Cannot extract form-data from HTTP request: could not find boundary in Content-Type (HTTP::FormData::Error)
and with curl -s http://127.0.0.1:3000/upload -X POST
get
Exception: Cannot extract form-data from HTTP request: body is empty (HTTP::FormData::Error)
@rdp Your curl
requests don't send multipart/form-data
content. So obviously, HTTP::FormData
can't parse it and the error messages are very explicit about that.
@codenoid The exact HTTP request is not reproducible from your screenshot (it doesn't even say which program UI it shows). Could you please provide some code that I can run to send the same request? Preferably a curl
command or Crystal source code.
I wouldn't expect it to crash unless there's some way to first check if there is formdata or not perhaps? Anyway I suppose it's part of the ongoing conversation for #465 for now...
Well, you're using HTTP::FormData
on you own account. So you need to ensure it's safe. A minimal precaution would be to check if the request's content type is actually multipart/form-data
. This would already detect your example requests.
Apart from that, it's obviously always possible that the message format is compromised, so to be safe you can wrap the parsing in a begin / rescue
.
As @straight-shoota already explained, Kemal does not make any assumptions about the message format. It's the developers' responsibility to actually make it safe.
I believe this may be related to a bug I opened a PR for: #567
For followers, this error message meant the following, I was parsing params twice:
post "/some_path" do |env|
some_parameter = env.params.body["my_form_parameter_name"] # get a POST param
HTTP::FormData.parse(env.request) do |part| # parse file data
...
end
It seems that using params.body as well as FormData.parse causes it to "parse twice" and blow up. I'm guessing that params.body discards file input (?)
Workaround is to only use FormData.parse like
post "/some_path" do |env|
HTTP::FormData.parse(env.request) do |part|
if part.name == "file_upload"
file_contents = part.body.gets_to_end
filename = part.headers.filename
elsif part.name == "my_form_parameter_name"
some_parameter = part.body.gets_to_end
end
end
end
Which works.
As a side note it'd be really nice if the FormData crystal docs described file uploads better, but unrelated. Thanks!
Yeah, HTTP::Server::Context#params
is a Kemal extension which parses parameters from the URL and body, which results in consuming the body. This might not be evident from the looks of it.
That's one reason why this is not part of the stdlib implementation.