LocalWebserver Data Flow

From ScummVM :: Wiki
Jump to navigation Jump to search
Data Flow Diagram

Creating a Client

LocalWebserver accepts client connection. There is a MAX_CONNECTIONS limit, and if there is no more room, connection is dropped.

Otherwise, Client::open() is called. Client connection's socket is stored in that Client object. open() makes Client in READING_HEADERS state and creates a MemoryReadWriteStream.


While Client is in this state, LocalWebserver calls Client::readHeaders(). Client uses inner Reader object to parse headers in that method.

Client reads data from socket, and only reads more data from socket when stream gets empty.

When Reader signals that headers are read, Client checks Reader::badRequest() method. If it returns true, Client moves to BAD_REQUEST state, if not - to READ_HEADERS.

How Reader reads first headers

Reader always reads passed content stream byte by byte.

For first headers it has a special MemoryReadWriteStream inside. It reads content into that stream until "\r\n\r\n" is found.

If stream size is more than SUSPICIOUS_HEADERS_SIZE (which is set to 1 MB), _badRequest flag is set and no more content is read.

_badRequest is also set if some unknown method is passed and on other strange data in the first line of HTTP request.


For a Client in such state LocalWebserver tries to find a BaseHandler. BaseHandlers process Client's path, query, etc and set a ClientHandler for a Client.

If Client's in BAD_REQUEST state, no BaseHandler is found or it was unable to set a ClientHandler, LocalWebserver sets a special ClientHandler with "BAD REQUEST" error in it.

When ClientHandler is set, Client moves to BEING_HANDLED state.


LocalWebserver calls Client::handle() in that state, and Client passes that to ClientHandler.

Usually, GetClientHandler is used. Its task is to pass the response data through Client's socket.

When data is passed, Client::close() is called - Client closes the connection and gets INVALID.


This one skips the content passed after first headers, and then starts processing POST/form-multipart blocks. These blocks consist of headers and content.

Using Client (and Reader within it), it reads headers data into own MemoryReadWriteStream. Again, if stream size is more that SUSPICIOUS_HEADERS_SIZE, it stops and returns an error.

Otherwise, it parses these and tries to find filename. If it's found, it opens a file with DumpFile, otherwise sets stream pointer to nullptr.

Block's content is channeled directly to that stream (when pointer's nullptr, it's ignored).

This ClientHandler works until Reader says there is no more content. In that case it replaces itself with GetClientHandler which either returns an error message or redirects user to a page, indicating a successful upload.

Complete Data Flow

Thus, data comes from socket into Client's MemoryReadWriteStream, which is being processed by Reader.

First headers are stored within Reader's own MemoryReadWriteStream, but it's limited to 1 MB. Contents of this stream are passed into Common::String.

First line (until "\r\n") is being processed char by char and is distributed in separate String fields, like _method, _path, _query, etc. "Content-Length: " and "boundary=" are being searched within that string's c_str() with strstr() and its contents are copied into separate Strings char by char until '\r', '\n' or '\0' is found.

Other headers and contents are being read directly to WriteStreams.

First content block is being ignored by both ClientHandlers - one doesn't even read it, the other skips it in order to start processing form-multipart blocks. So, from Client's MemoryReadWriteStream it goes to the void.

Headers block is channeled from Client's MemoryReadWriteStream into UploadFileClientHandler's one - but it is, again, limited to 1 MB. Similarly to Reader, it passes its contents into Common::String and searches for "name=\"" and "filename=\"" (reading contents char by char until '"' or '\0' is found then).

Content block is either ignored or is channeled to DumpFile.