I wasn't happy with how responses were generated. HTTP-headers were
handled by hand and it was duplicated in multiple parts of the code.
Due to the duplication, some functions like timestamp() had really
ugly semantics.
The HTTP requests are parsed much better: We have an enum of fields
we care about that are automatically read into our request struct. This
commit adapts this idea to the response: We have an enum of fields
we might put into our response, and a response-struct holds the
content of these fields. A function http_send_header() automatically
sends a header based on the entries in response. In case we don't
use a field, we just leave the field in the response-struct empty.
With this commit, some logical changes came with it:
- timestamp() now has a sane signature, TIMESTAMP_LEN is no more and
it can now return proper errors and is also reentrant by using
gmtime_r() instead of gmtime()
- No more use of a static timestamp-array, making all the methods
also reentrant
- Better internal-error-reporting: Because the fields are filled
before and not during sending the response-headers, we can better
report any internal errors as status 500 instead of sending a
partial non-500-header and then dying.
These improved data structures make it easier to read and hack the code
and implement new features, if desired.
Signed-off-by: Laslo Hunhold <dev@frign.de>
The method http_send_response() is already long enough and this
separation of concerns both helps shorten it a bit, improves
readability and reduces the chance of programming errors.
Signed-off-by: Laslo Hunhold <dev@frign.de>
Now that the range-support is actually working, we can send out
the Accept-Ranges-header so that clients know they can send
range-requests to the server.
This can be seen empirically when watching a video and skipping around
into unbuffered space. Previously, it would not be possible and the
time-selector would flip back to the furthest point the previous
buffering had progressed. Now it is working flawlessly.
Signed-off-by: Laslo Hunhold <dev@frign.de>
Quark previously didn't really handle suffix-range-requests
(those of the form "-num", asking for the last num bytes) properly
and also did not catch the error when the lower in the range
"lower-upper" was actually larger than or equal to the size of the
requested file.
I always planned to refactor the parsing but got the motivation by
Eric Radman <ericshane@eradman.com>, who kindly reported the latter bug
to me.
Signed-off-by: Laslo Hunhold <dev@frign.de>
This is recommended by the manual as strptime(), in principle,
might only touch the fields it parses from the string. Given
the struct tm implementations differ from operating system to
operating system, we make sure and set everything to zero
before passing it to strptime().
Signed-off-by: Laslo Hunhold <dev@frign.de>
The broken down time-representation tm generated earlier is in UTC,
and mktime() assumes that it's in local time instead, leading to
the problem that quark might not send a NOT_MODIFIED in a different
timezone.
timegm() instead correctly interprets the broken down
time-representation tm as UTC and returns the proper timestamp.
It might not be portable like mktime(), but it's complicated to
emulate it otherwise.
Thanks to Jeremy Bobbin <jer@jer.cx> for reporting the bug and
providing this fix, which is why I've added him to the LICENSE.
Thanks also to Hiltjo for his input.
Signed-off-by: Laslo Hunhold <dev@frign.de>
Stop immediately after responding with status code 304 "Not Modified".
This also solves missing log output for status 304.
If there is an error while sending a file, try to clean up and close the
file.
For me at least, the first valid configuration found by getaddrinfo
works fine most of the time. Obviously if this isn't the configuration
you want, you can specify the host explicitly.
Based on a patch by guysv. We now make sure that the valid
path-characters ", ', <, >, & can not be used for XSS on a target, for
example with a file called
"><img src="blabla" onerror="alert(1)"
by properly HTML-escaping these characters.
Signed-off-by: Laslo Hunhold <dev@frign.de>
Thanks to the feedback by z0lqLA! I forgot that unveil(NULL, NULL)
only locks further unveil calls when there has been at least _one_ prior
call to unveil!
To fix this, we reorder the calls and also make sure to call unveil()
before we disallow unveils via pledge.
Signed-off-by: Laslo Hunhold <dev@frign.de>
It has been on my todo-list for a long time. I tested it on
OpenBSD 6.5.
Thanks Richard Ulmer for the reminder.
Signed-off-by: Laslo Hunhold <dev@frign.de>
I didn't really like the use of a "yes"-variable for setsockopt().
A better way is to use compound literals (part of C99).
Another point are the structs. Instead of memsetting to zero we make
use of the standard which guarantees that "unmentioned" fields
are set to zero anyways. Just to note it here: The use of memset()
also sets padding to zero, which is not guaranteed with the method
of "unmentioned" fields.
Signed-off-by: Laslo Hunhold <dev@frign.de>
I wasn't happy with the tokenizer for the m- and v-flags, because it
was handling space-separated input and there was no way to have spaces
within the tokens themselves. This is a fine detail, but I didn't want
to impose this restriction where it could be solved (path prefixes or
folder names can very well contain spaces).
Given it's a bit quirky to handle multiple arguments to a single flag
in the command line, especially when parameters are optional, this
alternative wasn't further considered and I instead implemented a
tokenizer that allows escaping spaces with '\'.
While at it, I clarified the manual regarding this point.
Signed-off-by: Laslo Hunhold <dev@frign.de>
Put the chost-specification at the end and make it optional. This makes
more sense than having to give an arbitrary useless name in case you
weren't using virtual hosts in the first place.
While at it, clear up the wording in the manpage.
Signed-off-by: Laslo Hunhold <dev@frign.de>
After the initial report by Platon Ryzhikov, I couldn't validate this
behaviour with the given RFC 3986[0], which only speaks of percent encoding
for reserved characters.
[0]:https://tools.ietf.org/html/rfc3986
Signed-off-by: Laslo Hunhold <dev@frign.de>
If charset is unspecified, the encoding falls back to ISO 8859-1 or
something else that is defined in HTTP/1.1.
Given there is no reason not to use UTF-8 nowadays[0] and one can convert
legacy encodings to UTF-8 easily, if the case comes up, it is a sane
default to specify it in the config.def.h.
[0]: https://utf8everywhere.org/
Signed-off-by: Laslo Hunhold <dev@frign.de>
Don't append a forward slash if the length of a folder is PATH_MAX-1. This can
happen if HEADER_MAX is larger than PATH_MAX or if the `-m` option is used to
increase the path length.
This makes quark much more flexible when it is run behind a network
filter or other kind of tunnel. Only send an absolute redirection when
we are handling vhosts.
When cleaning up after a caught signal, quark forwards the signal to all
processes in the process group with `kill(0, ...)`. If we do not open up a new
process group in the parent process, quarks parent will be sent a SIG... too,
resulting it to shut down (especially considering that the parent process might
run as root).
As a result, if we set up the service with djb's excellent daemontools,
`svc -d quark` will terminate the svscan-process and tear all other services
down with it.
See also <https://cr.yp.to/daemontools/faq/create.html#pgrphack>.
We all agree that the IPv6 address format is a big clusterfuck and only
an insane person would've come up with it given the double colons
interfere with the way one actually appends a port to a normal IPv4 address.
To counteract in this issue, the RFC specifies that one should enclose
IPv6-addresses in square brackets to make the disctinction possible,
i.e.
host: ::1
port: 80
--> [::1]:80
The host field can contain both a port suffix and, of course by the RFC,
have the address enclosed in square brackets. Given I personally see
this as a "transport enclosure" I'd rather like to see it gone as soon
as possible and thus implement this cleanup in the http-header-parser so
the output is nice and clean and we don't have to deal with this garbage
later on.
Thanks to Josuah Demangeon <mail@josuah.net> for his wonderful input and
his dedication to read the RFCs 3986 and 2732 in such great detail.
The previous parsing of the -v vhosts made sure there were 4 tokens.
If there was no prefix specified, usage() is called. Now, it only
checks for the firsts 3, with .prefix set to null if there are only
3 tokens.
Since now config.def.h has been reduced we don't have any more unused
variables and thus the manual fiddling with error-levels is no longer
necessary.
To get a completely clean result though we have to still cast some
variables here and there.
The config.h-interface has proven to be very effective for a lot of
suckless tools, but it just does not make too much sense for a web
server like quark.
$ quark
If you run multiple instances of it, you want to see in the command line
(or top) what it does, and given the amount of options it's logical to
just express them as options given in the command line.
It also is a problem if you can modify quark via the config.h,
contradicting the manual. Just saying "Well, then don't touch config.h"
is also not good, as the vhost and map options were only exposed via
this interface.
What is left in config.h are mime-types and two constants relating to
the incoming HTTP-header-limits.
In order to introduce these changes, some structs and safe utility
functions were added and imported from OpenBSD respectively.
This makes quark's vhost-handling very powerful while still being
simple.
Imagine you have a website with a subdomain you really want
to move back to your main domain.
Say the subdomain is called "old.example.org" and you want to serve it
under "example.org" but in the subdirectory "old/", i.e. you want to
redirect a request "old.example.org/subdir/" to "example.org/old/subdir".
For a vhost-handler that only takes 4 arguments for each vhost this is
actually pretty powerful.
This ensures that quark really does not care if the incoming connection
is plain HTTP or relayed TLS-traffic from a proxy or tunnel. Depending
on the previous negotiation, the client will make the right decision on
which scheme to use in a given context.
To make the code a bit more flexible, let's get rid of the forking-code
in serve() and do it in main(). This way, we are more liberal in the
future to possibly handle it in a different way.