jczic / MicroWebSrv

A micro HTTP Web server that supports WebSockets, html/python language templating and routing handlers, for MicroPython (used on Pycom modules & ESP32)
https://github.com/jczic/MicroWebSrv
MIT License
641 stars 116 forks source link

Handling multipart/form-data requests #11

Closed ayoy closed 6 years ago

ayoy commented 6 years ago

First of all thanks a lot for your excellent work on the HTTP server!

I'm wondering whether you support multipart/form-data requests. I'm trying to upload a file using curl:

curl -v http://192.168.1.100/test -F "image=@test.txt"

but I'm getting an empty bytearray from a call to ReadRequestContent(). Am I doing something wrong or it's not supported yet?

Here's the output from curl:

*   Trying 192.168.1.100...
* TCP_NODELAY set
* Connected to 192.168.1.100 (192.168.1.100) port 80 (#0)
> POST /test HTTP/1.1
> Host: 192.168.1.100
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 90842
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------fa4b1f9e0d76e70b
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 500 Internal Server Error
< Content-Type: text/html; charset=UTF-8
< Content-Length: 250
< Server: MicroWebSrv by JC`zic
< Connection: close
< 
        <html>
            <head>
                <title>Error</title>
            </head>
            <body>
                <h1>500 Internal Server Error</h1>
                Server got itself in trouble
            </body>
        </html>
* Closing connection 0
jczic commented 6 years ago

Hello and thank you ! Exactly, sorry but MicroWebSrv doesn't support multipart/form-data POST at the moment :/ This subject stay again for future.

2018-02-04 22:16 GMT+01:00 Dominik Kapusta notifications@github.com:

First of all thanks a lot for your excellent work on the HTTP server!

I'm wondering whether you support multipart/form-data requests. I'm trying to upload a file using curl:

curl -v http://192.168.1.100/test -F "image=@test.txt"

but I'm getting an empty bytearray from a call to ReadRequestContent(). Am I doing something wrong or it's not supported yet?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jczic/MicroWebSrv/issues/11, or mute the thread https://github.com/notifications/unsubscribe-auth/AAegLOtQ5tp4SPE1-2XOeMw_tQ3Gp8Ntks5tRh44gaJpZM4R4umb .

--

Sincèrement,

nevercast commented 6 years ago

Is this now implemented?

jczic commented 6 years ago

nevercast : No for the moment ... But you can easily use AjAX, or WebSockets to transfer datas :)

nevercast commented 6 years ago

I've roughly implemented this myself.


        def _readRequestPostedMultipart(self, data, content_type_info):
            boundary = content_type_info['boundary']
            if isinstance(boundary, bytes):
                boundary = boundary.decode('utf8')
            if isinstance(data, bytes):
                data = data.decode('utf8')
            boundary_tokens = data.split(boundary)
            # First is empty, last is --
            boundary_tokens = boundary_tokens[1:-1]
            content = {}
            for token in boundary_tokens:
                # Starts with new line, strip that, 
                # Ends with two new lines, strip them
                token = token[2:-4]
                # Make a single split on the first new line.
                disposition, value = token.split('\r\n', 1)
                if len(value) > 2:
                    value = value[2:] # Excess leading new line.
                disposition_tokens = list(map(lambda clause: clause.strip(), disposition.split(';')))
                content_disposition = disposition_tokens[0]
                disposition_tokens = disposition_tokens[1:]
                for dis_tok in disposition_tokens:
                    field, field_value = dis_tok.split('=', 1)
                    field_value = field_value[1:-1]
                    if field == 'name':
                        content[field_value] = value
            return content
        # ------------------------------------------------------------------------
        def _readRequestPostedURLEncoded(self, data):
            if len(data) > 0:
                elements = data.decode().split('&')
                for s in elements:
                    param = s.split('=', 1)
                    if len(param) > 0:
                        value = MicroWebSrv._unquote_decode(
                            param[1]) if len(param) > 1 else ''
                        res[MicroWebSrv._unquote_decode(param[0])] = value
            return res
        # ------------------------------------------------------------------------

        def _parseContentType(self, contentTypeHeader):
            parts = list(map(lambda part: part.strip(), contentTypeHeader.split(';')))
            content_type = parts[0]
            content_type_info = {
                'type': content_type
            }
            if len(parts) > 1:
                for part in parts[1:]:
                    key, value = part.split('=', 1)
                    content_type_info[key] = value
            return content_type_info

        def ReadRequestPostedFormData(self):
            res = {}
            data = self.ReadRequestContent()
            print('ReadRequestPost')
            print(self._headers)
            print(data)
            if 'content-type' in self._headers:
                content_type = self._parseContentType(self._headers['content-type'])
                print(content_type)
                if content_type['type'] == 'multipart/form-data':
                    return self._readRequestPostedMultipart(data, content_type)
            return self._readRequestPostedURLEncoded(data)

        # ------------------------------------------------------------------------

It doesn't follow your style, is hastily written and has no tests. But I needed it working for an End of Sprint demo. I can make a PR possibly later this week. I've also added gzip "support" if the files are gzipped on flash.

jczic commented 4 years ago

Hello,

I released a fully new version (v2.0) of my web server here : github.com/jczic/MicroWebSrv2. Open source MIT, fully asynchronous, more robust, more fast and more efficient! It is delivered with a good documentation.

Thank you for your support and feedback. ☺️

Jean-Christophe Bos

Le mar. 24 juil. 2018 à 01:08, Josh Lloyd notifications@github.com a écrit :

I've roughly implemented this myself.

    def _readRequestPostedMultipart(self, data, content_type_info):
        boundary = content_type_info['boundary']
        if isinstance(boundary, bytes):
            boundary = boundary.decode('utf8')
        if isinstance(data, bytes):
            data = data.decode('utf8')
        boundary_tokens = data.split(boundary)
        # First is empty, last is --
        boundary_tokens = boundary_tokens[1:-1]
        content = {}
        for token in boundary_tokens:
            # Starts with new line, strip that,
            # Ends with two new lines, strip them
            token = token[2:-4]
            # Make a single split on the first new line.
            disposition, value = token.split('\r\n', 1)
            if len(value) > 2:
                value = value[2:] # Excess leading new line.
            disposition_tokens = list(map(lambda clause: clause.strip(), disposition.split(';')))
            content_disposition = disposition_tokens[0]
            disposition_tokens = disposition_tokens[1:]
            for dis_tok in disposition_tokens:
                field, field_value = dis_tok.split('=', 1)
                field_value = field_value[1:-1]
                if field == 'name':
                    content[field_value] = value
        return content
    # ------------------------------------------------------------------------
    def _readRequestPostedURLEncoded(self, data):
        if len(data) > 0:
            elements = data.decode().split('&')
            for s in elements:
                param = s.split('=', 1)
                if len(param) > 0:
                    value = MicroWebSrv._unquote_decode(
                        param[1]) if len(param) > 1 else ''
                    res[MicroWebSrv._unquote_decode(param[0])] = value
        return res
    # ------------------------------------------------------------------------

    def _parseContentType(self, contentTypeHeader):
        parts = list(map(lambda part: part.strip(), contentTypeHeader.split(';')))
        content_type = parts[0]
        content_type_info = {
            'type': content_type
        }
        if len(parts) > 1:
            for part in parts[1:]:
                key, value = part.split('=', 1)
                content_type_info[key] = value
        return content_type_info

    def ReadRequestPostedFormData(self):
        res = {}
        data = self.ReadRequestContent()
        print('ReadRequestPost')
        print(self._headers)
        print(data)
        if 'content-type' in self._headers:
            content_type = self._parseContentType(self._headers['content-type'])
            print(content_type)
            if content_type['type'] == 'multipart/form-data':
                return self._readRequestPostedMultipart(data, content_type)
        return self._readRequestPostedURLEncoded(data)

    # ------------------------------------------------------------------------

It doesn't follow your style, is hastily written and has no tests. But I needed it working for an End of Sprint demo. I can make a PR possibly later this week. I've also added gzip "support" if the files are gzipped on flash.

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/jczic/MicroWebSrv/issues/11#issuecomment-407228776, or mute the thread https://github.com/notifications/unsubscribe-auth/AAegLCjaB7ejOBCo_DbWNkFqaYX0_pLDks5uJldYgaJpZM4R4umb .

--

Sincèrement,