Skip to content

Conversation

@LeSpocky
Copy link
Contributor

Requirements for Contributing a Bug Fix

Identify the Bug

See: #255

Description of the Change

Adds a unit test highlighting the bug, no real fix so far.

Alternate Designs

This snippet at least fixes the webserver to return a 500 Internal Server Error:

diff --git a/src/webserver.cpp b/src/webserver.cpp
index a1392bd..4c4a034 100644
--- a/src/webserver.cpp
+++ b/src/webserver.cpp
@@ -623,6 +623,10 @@ MHD_Result webserver::finalize_answer(MHD_Connection* connection, struct details
     try {
         try {
             raw_response = mr->dhrs->get_raw_response();
+            if (raw_response == nullptr) {
+                mr->dhrs = internal_error_page(mr);
+                raw_response = mr->dhrs->get_raw_response();
+            }
         } catch(const std::invalid_argument& iae) {
             mr->dhrs = not_found_page(mr);
             raw_response = mr->dhrs->get_raw_response();

The unit test just fails different then, but that confirms the unit test itself is ok:

Running test (25): file_serving_resource_missing
[ASSERT FAILURE] (../../test/integ/basic.cpp:923) - error in "file_serving_resource_missing": 500!=404
- Time spent during "file_serving_resource_missing": 0.668945 ms

Possible Drawbacks

n.a.

Verification Process

Tested against multiple different version of libmicrohttpd.

Release Notes

n.a.

@LeSpocky
Copy link
Contributor Author

As discussed in #255 that diff could be a solution already. The unit test could test on HTTP status NOT equal to 200 instead? Or test equal to 500?

LeSpocky and others added 3 commits January 29, 2022 11:58
If `http_response::get_raw_response()` returns nullptr instead of a
valid `MHD_Response*` for whatever reason, that pointer would be passed
on to `http_response::decorate_response()` and
`http_response::enqueue_response()` eventually, leading to different API
calls to libmicrohttpd with NULL as argument `struct MHD_Response
*response`.  MHD does not guarantee any form of behaviour for invalid
input, so we have to consider it undefined behaviour and avoid passing
invalid input to MHD.

HTTP status 500 (Internal Server Error) is returned for consistency with
surrounding error handling, we don't know what caused
`http_response::get_raw_response()` to return nullptr, so we can not
give a better answer here.

Fixes: etr#255
Both `open()` and `lseek()` might fail depending on how `filename` is
set in object of class `httpserver::file_response()`.  In the case of a
missing file *fd* got -1 and lseek set *size* (which had the wrong type
btw.) to 0xffffffff aka (off_t) -1.  Passing an invalid file descriptor
and a massively huge size value on to `MHD_create_response_from_fd()`
might lead to unpredictable results depending how well libmicrohttpd
treats such invalid values.

Note: Before f9b7691 ("Use MHD_create_response_from_fd for files")
`httpserver::http::load_file()` was used, which throws an exception in
case a file can not be opened successfully.  That exception would have
lead to returning HTTP status 500 (Internal Server Error).

References: etr#255
The constructor of class `httpserver::file_response` can be called with
a `filename` pointing into the void, to a file which does not actually
exist.  The webserver should fail predictably in that case.

References: etr#255
@LeSpocky
Copy link
Contributor Author

I force pushed a possible solution as discussed in #255, but did not change description etc. of this PR, yet. Should I?

size_t size = lseek(fd, 0, SEEK_END);
if (fd == -1) return nullptr;

off_t size = lseek(fd, 0, SEEK_END);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type of size taking return value of lseek() should be off_t. That was introduced wrong with f9b7691. Could be fixed in a separate commit or just fixed here en passant.

@LeSpocky LeSpocky marked this pull request as ready for review January 30, 2022 11:04
@LeSpocky
Copy link
Contributor Author

I just discovered another problem: if that filename points to a directory instead of a file … 🙄

@LeSpocky LeSpocky marked this pull request as draft January 31, 2022 14:35
@etr
Copy link
Owner

etr commented Jan 31, 2022

I just discovered another problem: if that filename points to a directory instead of a file …

We should just manage this in the same way (direction = invalid) and express it in the documentation making it clear that only existing files (not directories) are acceptable.

Type of size taking return value of lseek() should be off_t. That was introduced wrong with f9b7691. Could be fixed in a separate commit or just fixed here en passant.

Given the broad changes that are going to happen in this method, I think we can just do it in this change.

It was possible before to pass a path to a directory, or a to a device
file, or to basically any path.  `open()` would happily open it.
In case of a directory, `lseek()` returns LONG_MAX (0x7FFFFFFFFFFFFFFF)
and `MHD_create_response_from_fd()` is invoked.  To avoid such nonsense,
we test the path now and allow regular files only.

References: etr#255
@LeSpocky
Copy link
Contributor Author

LeSpocky commented Feb 1, 2022

Those unit test are very repetitive. Maybe that can be improved later?

@LeSpocky LeSpocky marked this pull request as ready for review February 1, 2022 21:07
@etr
Copy link
Owner

etr commented Feb 1, 2022

Those unit test are very repetitive. Maybe that can be improved later?

Yeah, I think it would be worth keeping a change to those in an isolated change.

Copy link
Owner

@etr etr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add information about the method new failing path in the README file?

This documents a possible pitfall for users when passing filename of not
existing files.

References: etr#255
The constructor of class `httpserver::file_response` can be called with
a `filename` pointing to a directory instead of a regular file.
The webserver should fail predictably in that case.

References: etr#255
@LeSpocky
Copy link
Contributor Author

LeSpocky commented Feb 2, 2022

I updated the API doc to the constructor of the file_response class as suggested. However on that other remark of yours …

Can we also add information about the method new failing path in the README file?

Not sure in what direction that should go? There is this section Building responses to requests which describes the different flavors of http_response classes. The improved failure handling was in the get_raw_response() method and over where it is called in finalize_answer(). Instead of crashing or sending back potentially nonsense responses, libhttpserver returns 500 now, and explicitly expects the user to make sure a file used for the file_response must exist and be a regular file. It was a good idea to check that on user side before already. What do you have in mind should be added to the README and where? 🤔

@etr
Copy link
Owner

etr commented Feb 2, 2022

In the section called "Building responses to requests" there is piece dedicated to the file_response.

file_response(const std::string& filename, int response_code = 200, const std::string& content_type = "text/plain"): Uses the filename passed in construction as pointer to a file on disk. The body of the HTTP response will be set using the content of the file. The other two optional parameters are the response_code and the content_type. You can find constant definition for the various response codes within the http_utils library file.

Don't need to make it too complicated. I'd just change it as below (the diff is in bold):

file_response(const std::string& filename, int response_code = 200, const std::string& content_type = "text/plain"): Uses the filename passed in construction as pointer to a file on disk. The body of the HTTP response will be set using the content of the file. The file must be a regular file and exist on disk. Otherwise libhttpserver will return an error 500 (Internal Server Error). The other two optional parameters are the response_code and the content_type. You can find constant definition for the various response codes within the http_utils library file.

Suggested-by: Sebastiano Merlino <sebastiano@hey.com>
References: etr#255
@etr etr merged commit 5cb0be1 into etr:master Feb 3, 2022
@etr
Copy link
Owner

etr commented Feb 3, 2022

Thanks! This was a great piece of improvement for the library.

@LeSpocky LeSpocky deleted the file-response branch February 3, 2022 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants