Search code examples
erlangyawslarge-file-upload

Upload with progress on Yaws webserver


I'm testing the script upload2.yaws on Yaws 2.0 but is not working. The script was develeloped for Yaws 1.57.

The log from Yaws is:

POST /html/upload2.yaws HTTP/1.1
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Host: 192.168.26.10
Referer: http://192.168.26.10/html/upload2.yaws
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36
Cookie: gsScrollPos=0; gsScrollPos=0
Content-Length: 1587
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary5zKIyUtdiK82R4de
Accept-Language: pt,en-US;q=0.8,en;q=0.6,pt-BR;q=0.4
Accept-Encoding: gzip, deflate
Https: 1
Origin: http://192.168.26.10
Cache-Control: max-age=0


upload.yaws:multipart/3 State=filename=undefined rlist=undefined data=undefined
upload.yaws:multipart/3 result
upload.yaws:process_part/4d State=filename=undefined rlist=undefined data=undefined
upload.yaws:multipart/3 done
upload.yaws:result_ehtml/3 error Reason=Error: filename not found in header.
Worker: <0.78.0> 
[2015-07-24 13:50:54.795] ===== SRV -> CLI =====
HTTP/1.1 200 OK
Server: Yaws 2.0
Date: Fri, 24 Jul 2015 13:50:54 GMT
Content-Length: 532
Content-Type: text/html

When I changed the form action to an listen port with netcat, is received the full header with the filename:

The code:

show_form(A, Feedback) -> {ehtml,
        [       {form
                ,[{enctype,"multipart/form-data"},{action,"http://192.168.26.10:8800"},{method,"post"}]
                ,       [{p,[],"Choose a file and click Upload."}
                        ,{p,[],{input,[{type,"file"},{name,"file"}],[]}}
                        ,{p,[],{input,[{type,"submit"},{value,"Upload"}],[]}}
                        ]
                }
        ,{p,[],Feedback}
        ]}.

I started the nc

$ sudo nc -l 8800

Then is showed the followed header:

POST / HTTP/1.1
Host: 192.168.26.10:8800
Connection: keep-alive
Content-Length: 1587
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.26.10
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36
HTTPS: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9iHQOXBtRE9CyRAZ
Referer: http://192.168.26.10/html/upload2.yaws
Accept-Encoding: gzip, deflate
Accept-Language: pt,en-US;q=0.8,en;q=0.6,pt-BR;q=0.4
Cookie: gsScrollPos=0

------WebKitFormBoundary9iHQOXBtRE9CyRAZ
Content-Disposition: form-data; name="file"; filename="signing_debian_packages.txt"
Content-Type: text/plain

I suspect that the error is on multipart function, but I'm noob in Erlang :/


    %%% Process part of a multi-part form post
    %%% returns Ehtml | {get_more, Continuation, NewState}
    multipart(A, State) when is_record(State,upload) ->
            io:fwrite("upload.yaws:multipart/3 State=~s~n", [upload_to_string(State)]),
            case yaws_api:parse_multipart_post(A) of
            {cont, Cont, Part} ->
                    io:fwrite("upload.yaws:multipart/3 cont~n"),
                    case process_part(A, Part, State) of
                    {done, Result} ->
                            io:fwrite("upload.yaws:multipart/3 done~n"),
                            result_ehtml(A, Result);
                    {cont, NewState} ->
                            io:fwrite("upload.yaws:multipart/3 get_more NewState=~s~n", [upload_to_string(NewState)]),
                            {get_more, Cont, NewState}
                    end;
            {result, Part} ->
                    io:fwrite("upload.yaws:multipart/3 result~n"),
                    case process_part(A, Part, State#upload{last=true}) of
                    {done, Result} ->
                            io:fwrite("upload.yaws:multipart/3 done~n"),
                            result_ehtml(A, Result);
                    {cont, _} ->
                            io:fwrite("upload.yaws:multipart/3 error~n"),
                            result_ehtml(A, {error, "Error During Upload"})
                    end;
            [] -> result_ehtml(A, {error,"You must select a file to upload."})
            end.

Someone have a idea?


Solution

  • The problem is in the process_part function clause that handles the header:

    %%% Process header
    process_part(A, [{head, {"file", Opts}}|Tail], State ) ->
            io:fwrite("upload.yaws:process_part/4d State=~s~n", [upload_to_string(State)]),
            case lists:keysearch(filename, 1, Opts) of
            {value, {_, UncheckedFileName}} ->
                    io:fwrite("upload.yaws:process_part/4d UncheckedFileName=~s~n", [UncheckedFileName]),
                    FileName = sanitize_filename(UncheckedFileName),
                    io:fwrite("upload.yaws:process_part/4d FileName=~s~n", [FileName]),
                    process_part(A, Tail, State#upload{filename=FileName,rlist=[]});
            false ->
                    {done, {error, "Error: filename not found in header."}}
            end;
    

    The first argument to the call to lists:keysearch/3 is the atom filename, but it should be the string "filename" instead. The revised function is:

    %%% Process header
    process_part(A, [{head, {"file", Opts}}|Tail], State ) ->
            io:fwrite("upload.yaws:process_part/4d State=~s~n", [upload_to_string(State)]),
            case lists:keysearch("filename", 1, Opts) of
            {value, {_, UncheckedFileName}} ->
                    io:fwrite("upload.yaws:process_part/4d UncheckedFileName=~s~n", [UncheckedFileName]),
                    FileName = sanitize_filename(UncheckedFileName),
                    io:fwrite("upload.yaws:process_part/4d FileName=~s~n", [FileName]),
                    process_part(A, Tail, State#upload{filename=FileName,rlist=[]});
            false ->
                    {done, {error, "Error: filename not found in header."}}
            end;
    

    With that change, upload2.yaws works fine for me with Yaws 2.0 and Erlang 18.0.