mirror of
https://github.com/bdnugget/kcgi.git
synced 2026-11-07 05:47:27 +00:00
Lots of documentation improvements, especially in www.
This commit is contained in:
1 parent
8d8ad02a75
commit
7408005f9f
14 files changed
+1240
-374
No files matched your search
@@ -1,4 +1,4 @@
|
||||
.SUFFIXES: .3 .3.html
|
||||
.SUFFIXES: .3 .3.html .dot .svg .gnuplot .png
|
||||
|
||||
# Comment if you don't need statically linked.
|
||||
# This is only for the sample program!
|
||||
@@ -126,6 +126,9 @@ REGRESS_SRCS = regress/regress.c \
|
||||
regress/test-file-get.c \
|
||||
regress/test-ping.c \
|
||||
regress/test-upload.c
|
||||
SVGS = figure1.svg \
|
||||
figure2.png \
|
||||
figure3.png
|
||||
|
||||
all: libkcgi.a libkcgihtml.a libkcgijson.a libkcgixml.a libkcgiregress.a
|
||||
|
||||
@@ -212,11 +215,11 @@ install: all
|
||||
sample: sample.o libkcgi.a libkcgihtml.a
|
||||
$(CC) -o $@ $(STATIC) sample.o -L. libkcgihtml.a libkcgi.a -lz
|
||||
|
||||
www: index.html kcgi.tgz kcgi.tgz.sha512 $(HTMLS)
|
||||
www: $(SVGS) index.html kcgi.tgz kcgi.tgz.sha512 $(HTMLS)
|
||||
|
||||
installwww: www
|
||||
mkdir -p $(PREFIX)/snapshots
|
||||
install -m 0444 index.html index.css $(HTMLS) $(PREFIX)
|
||||
install -m 0444 index.html index.css $(SVGS) $(HTMLS) $(PREFIX)
|
||||
install -m 0444 sample.c $(PREFIX)/sample.c.txt
|
||||
install -m 0444 kcgi.tgz kcgi.tgz.sha512 $(PREFIX)/snapshots/
|
||||
install -m 0444 kcgi.tgz $(PREFIX)/snapshots/kcgi-$(VERSION).tgz
|
||||
@@ -245,8 +248,14 @@ kcgi.tgz:
|
||||
(cd .dist && tar zcf ../$@ kcgi-$(VERSION))
|
||||
rm -rf .dist
|
||||
|
||||
.dot.svg:
|
||||
dot -Tsvg -o $@ $<
|
||||
|
||||
.gnuplot.png:
|
||||
gnuplot $<
|
||||
|
||||
clean:
|
||||
rm -f kcgi.tgz kcgi.tgz.sha512 index.html $(HTMLS) sample sample.o
|
||||
rm -f kcgi.tgz kcgi.tgz.sha512 index.html $(SVGS) $(HTMLS) sample sample.o
|
||||
rm -f libkcgi.a $(LIBOBJS)
|
||||
rm -f libkcgihtml.a kcgihtml.o
|
||||
rm -f libkcgijson.a kcgijson.o
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
digraph {
|
||||
node [shape="ellipse"; width="0"; height="0"; margin="0.05,0.05"];
|
||||
edge [arrowsize="0.5";];
|
||||
|
||||
subgraph cluster0 {
|
||||
color="black";
|
||||
client [label="application logic"];
|
||||
subgraph cluster1 {
|
||||
parser [shape="record"; label="{ HTTP parser | input validator }"];
|
||||
color="red";
|
||||
label="sandboxed child";
|
||||
labelloc="b";
|
||||
}
|
||||
label="CGI client";
|
||||
}
|
||||
evil [label="CGI server"];
|
||||
|
||||
parser -> client [label="validated pairs"];
|
||||
evil -> parser [label="CGI"];
|
||||
client -> evil [label="HTTP"];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/local/bin/gnuplot
|
||||
set terminal pngcairo crop enhanced font "Times,15"
|
||||
set output 'figure2.png'
|
||||
set border linewidth 1.5
|
||||
set style line 1 lc rgb '#800000' lt 1 lw 2
|
||||
set style line 2 lc rgb '#ff0000' lt 1 lw 2
|
||||
set style line 3 lc rgb '#ff4500' lt 1 lw 2
|
||||
set style line 4 lc rgb '#ffa500' lt 1 lw 2
|
||||
set style line 5 lc rgb '#006400' lt 1 lw 2
|
||||
set style line 6 lc rgb '#0000ff' lt 1 lw 2
|
||||
set style line 7 lc rgb '#9400d3' lt 1 lw 2
|
||||
set style line 11 lc rgb '#808080' lt 1
|
||||
set border 19 back ls 11
|
||||
set tics nomirror out scale 0.75
|
||||
set style line 12 lc rgb'#808080' lt 0 lw 1
|
||||
set grid back ls 12
|
||||
#########################################################
|
||||
set xlabel 'milliseconds'
|
||||
set size 0.8,0.8
|
||||
set key outside right
|
||||
set ytics 0.25
|
||||
unset title
|
||||
plot 'perf-time-openbsd-base.dat' u 2:($1/100) w l lw 3 ti '(1)', \
|
||||
'perf-time-openbsd-base-cgi.dat' u 2:($1/100) w l lw 3 ti '(2)', \
|
||||
'perf-time-openbsd-kcgi-nosandbox.dat' u 2:($1/100) w l lw 3 ti '(3)', \
|
||||
'perf-time-openbsd-kcgi-nocompress.dat' u 2:($1/100) w l lw 3 ti '(4)', \
|
||||
'perf-time-openbsd-kcgi.dat' u 2:($1/100) w l lw 3 ti '(5)'
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/local/bin/gnuplot
|
||||
set terminal pngcairo crop enhanced font "Times,15"
|
||||
set output 'figure3.png'
|
||||
set border linewidth 1.5
|
||||
set style line 1 lc rgb '#800000' lt 1 lw 2
|
||||
set style line 2 lc rgb '#ff0000' lt 1 lw 2
|
||||
set style line 3 lc rgb '#ff4500' lt 1 lw 2
|
||||
set style line 4 lc rgb '#ffa500' lt 1 lw 2
|
||||
set style line 5 lc rgb '#006400' lt 1 lw 2
|
||||
set style line 6 lc rgb '#0000ff' lt 1 lw 2
|
||||
set style line 7 lc rgb '#9400d3' lt 1 lw 2
|
||||
set style line 11 lc rgb '#808080' lt 1
|
||||
set border 19 back ls 11
|
||||
set tics nomirror out scale 0.75
|
||||
set style line 12 lc rgb'#808080' lt 0 lw 1
|
||||
set grid back ls 12
|
||||
#########################################################
|
||||
set size 0.8,0.8
|
||||
set ylabel "ms"
|
||||
set xtics rotate by -45
|
||||
set style fill solid border -1
|
||||
set xtics ("static" 4, "slowcgi(8)" 3, "kcgi(3)" 2, "no sandbox" 1, "no compress" 0)
|
||||
plot '-' w hist title ''
|
||||
2.947
|
||||
2.481
|
||||
2.916
|
||||
1.864
|
||||
0.22
|
||||
e
|
||||
@@ -1,77 +1,85 @@
|
||||
html, body { margin: 0;
|
||||
padding: 0; }
|
||||
body { color: #333;
|
||||
font-family: Times,serif;
|
||||
font-size: 14pt;
|
||||
line-height: 120%; }
|
||||
code, pre { font-size: 11pt; }
|
||||
a { text-decoration: none; }
|
||||
header { box-shadow: 0 1px 4px #888; }
|
||||
header nav ul { margin: auto 0;
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
header nav li + li { margin-top: 0.5em; }
|
||||
#header { background-color: #333;
|
||||
padding: 3em 2% 1ex 2%;
|
||||
color: #fff; }
|
||||
#header a { color: #ddd; }
|
||||
#header a:hover { color: #fff; }
|
||||
#header nav { padding: 0.5ex 0 1ex 0; }
|
||||
#version { background-color: #ddd;
|
||||
padding: 1em 2%; }
|
||||
html, body { margin: 0;
|
||||
padding: 0; }
|
||||
body { color: #333;
|
||||
font-family: Times,serif;
|
||||
font-size: 12pt;
|
||||
line-height: 120%; }
|
||||
code, pre { font-size: 11pt; }
|
||||
a { text-decoration: none; }
|
||||
header { box-shadow: 0 1px 4px #888; }
|
||||
header nav ul { margin: auto 0;
|
||||
padding: 0;
|
||||
list-style-type: none; }
|
||||
header nav li { margin: 0; }
|
||||
header nav li + li { margin-top: 0.5em; }
|
||||
#header { background-color: #333;
|
||||
padding: 3em 5% 1ex 5%;
|
||||
color: #fff; }
|
||||
#header a { color: #ddd; }
|
||||
#header a:hover { color: #fff; }
|
||||
#header nav { padding: 0.5ex 0 1ex 0; }
|
||||
#version { background-color: #ddd;
|
||||
padding: 1em 5% 0 5%; }
|
||||
#version a,
|
||||
#version time,
|
||||
#version span.version { font-style: normal; }
|
||||
h1 { font-size: 32pt;
|
||||
margin: 0 0 1ex 0;
|
||||
font-weight: normal; }
|
||||
h1 .nm { text-shadow: 1px 1px #666; }
|
||||
h1 .nd { font-size: 28pt;
|
||||
text-shadow: 1px 1px #333; }
|
||||
footer { margin-top: 1em;
|
||||
text-align: center;
|
||||
color: #666; }
|
||||
footer a { color: #000; }
|
||||
article h2 { font-size: x-large;
|
||||
font-weight: bolder;
|
||||
color: #333; }
|
||||
article h3 { font-size: large;
|
||||
color: #333; }
|
||||
figure { margin: 0;
|
||||
color: #444; }
|
||||
figcaption { padding: 0 1% 0.5ex 1%; }
|
||||
figcaption span { font-weight: bolder; }
|
||||
#intro { padding: 0 2%; }
|
||||
#intro figure { width: 100%;
|
||||
text-align: center; }
|
||||
#intro img { max-width: 480px;
|
||||
width: 100%; }
|
||||
#sample { padding: 1ex 1%; }
|
||||
#sample nav i { font-size: 16pt; }
|
||||
#sample nav { padding: 0.5ex 1em; }
|
||||
#sample nav a { color: #333; }
|
||||
#sample nav a:hover i { text-shadow: 1px 1px #aaa; }
|
||||
#comments { max-height: 0;
|
||||
padding: 0; }
|
||||
#nocomments { max-height: 100em;
|
||||
padding: 1em; }
|
||||
pre.prettyprint { border: none;
|
||||
line-height: 150%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 1ex;
|
||||
box-shadow: 1px 1px 2px #666;
|
||||
width: auto; }
|
||||
pre.prettyprint a { font-style: italic; }
|
||||
pre.prettyprint a:hover { font-style: italic; }
|
||||
pre.prettyprint ol.linenums > li
|
||||
{ list-style-type: decimal; }
|
||||
#features { padding: 1ex 2%; }
|
||||
footer { padding: 1ex;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em; }
|
||||
#version span.version { font-style: normal; }
|
||||
#version > a { font-size: 11pt;
|
||||
color: #888;
|
||||
display: block;
|
||||
margin-top: 1ex;
|
||||
text-shadow: 0 0 0px #000; }
|
||||
#version > a:hover { color: #666; }
|
||||
h1 { font-size: 32pt;
|
||||
margin: 0 0 1ex 0;
|
||||
font-weight: normal; }
|
||||
.nm, .file { font-style: italic; }
|
||||
h1 .nm { text-shadow: 1px 1px #666;
|
||||
font-style: inherit; }
|
||||
h1 .nd { font-size: 28pt;
|
||||
text-shadow: 1px 1px #333; }
|
||||
#intro { font-size: 14pt; }
|
||||
footer { margin-top: 1em;
|
||||
text-align: center;
|
||||
color: #666; }
|
||||
footer a { color: #000; }
|
||||
article h2 { font-size: x-large;
|
||||
font-weight: bolder;
|
||||
color: #333; }
|
||||
article h3 { font-size: large;
|
||||
color: #333; }
|
||||
article section,
|
||||
article nav,
|
||||
article figcaption { padding: 1ex 10%;
|
||||
margin: 0; }
|
||||
figure { margin: 0;
|
||||
text-align: center;
|
||||
color: #444; }
|
||||
figcaption span { font-weight: bolder; }
|
||||
dd { margin: 0 0 1em 0; }
|
||||
.sample { text-align: left; }
|
||||
.sample nav i { font-size: 16pt; }
|
||||
.sample nav a { color: #333; }
|
||||
.sample nav a:hover i { text-shadow: 1px 1px #aaa; }
|
||||
nav[data-sblg-nav] { overflow: hidden; }
|
||||
#longnews,
|
||||
#comments { max-height: 0; }
|
||||
#nolongnews,
|
||||
#nocomments { max-height: 100em; }
|
||||
pre.prettyprint { border: none;
|
||||
line-height: 150%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: 1px 1px 2px #666;
|
||||
width: auto; }
|
||||
pre.prettyprint a { font-style: italic; }
|
||||
pre.prettyprint a:hover { font-style: italic; }
|
||||
#sample pre.prettyprint ol { padding: 0 10%; }
|
||||
pre.prettyprint ol.linenums > li { list-style-type: decimal; }
|
||||
footer { padding: 1ex;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em; }
|
||||
@media only screen and (max-width: 480px), only screen and (max-device-width: 480px) {
|
||||
h1 { font-size: 16pt; }
|
||||
h1 .nd { font-size: 14pt; }
|
||||
pre { font-size: 8pt; }
|
||||
h1 { font-size: 16pt; }
|
||||
h1 .nd { font-size: 14pt; }
|
||||
pre { font-size: 8pt; }
|
||||
}
|
||||
@@ -8,25 +8,21 @@
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="index.css" type="text/css" />
|
||||
<script>
|
||||
function showComments()
|
||||
function showBits(name)
|
||||
{
|
||||
var show, nshow;
|
||||
|
||||
show = document.getElementById('comments');
|
||||
noshow = document.getElementById('nocomments');
|
||||
show = document.getElementById(name);
|
||||
noshow = document.getElementById('no' + name);
|
||||
|
||||
console.log('maxheight = ' + show.style.maxHeight);
|
||||
|
||||
if (show.style.maxHeight == '' || show.style.maxHeight == '0px') {
|
||||
show.style.maxHeight = '100em';
|
||||
show.style.padding = '1em';
|
||||
noshow.style.maxHeight = 0;
|
||||
noshow.style.padding = 0;
|
||||
} else {
|
||||
show.style.maxHeight = 0;
|
||||
show.style.padding = 0;
|
||||
noshow.style.maxHeight = '100em';
|
||||
noshow.style.padding = '1em';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -46,9 +42,15 @@
|
||||
</nav>
|
||||
</section>
|
||||
<section id="version">
|
||||
<nav data-sblg-nav="1" data-sblg-navsz="3" data-sblg-navcontent="1">
|
||||
Version <span class="version">${sblg-title}</span> (<time datetime="${sblg-date}">${sblg-date}</time>): ${sblg-aside}
|
||||
<nav data-sblg-nav="1" data-sblg-navsz="1" data-sblg-navcontent="1" id="nolongnews">
|
||||
<div>Version <span class="version">${sblg-title}</span> (<time datetime="${sblg-date}">${sblg-date}</time>)</div>
|
||||
<div>${sblg-aside}</div>
|
||||
</nav>
|
||||
<nav data-sblg-nav="1" data-sblg-navcontent="1" id="longnews">
|
||||
<div>Version <span class="version">${sblg-title}</span> (<time datetime="${sblg-date}">${sblg-date}</time>)</div>
|
||||
<div>${sblg-aside}</div>
|
||||
</nav>
|
||||
<a href="#version" onclick="showBits('longnews');">Toggle All News</a>
|
||||
</section>
|
||||
</header>
|
||||
<article>
|
||||
@@ -57,19 +59,17 @@
|
||||
<span class="nm">kcgi</span> is a minimal CGI library for web applications in <a
|
||||
href="http://opensource.org/licenses/ISC" rel="license">ISC licensed</a> ISO C.
|
||||
It was designed to be secure and auditable.
|
||||
To start, download <a href="snapshots/kcgi.tgz">kcgi.tgz</a> and run <code>make install</code> into your
|
||||
<code>PREFIX</code> of choice, then read <a href="kcgi.3.html">kcgi(3)</a>.
|
||||
The system also ships with the <a href="kcgihtml.3.html">kcgihtml(3)</a>, <a
|
||||
href="kcgijson.3.html">kcgijson(3)</a>, <a href="kcgixml.3.html">kcgixml(3)</a>, and <a
|
||||
href="kcgi_regress.3.html">kcgi_regress(3)</a> libraries.
|
||||
To start, <a href="#install">install</a> the library then read the <a href="#usage">usage guide</a>,
|
||||
which links to the canonical manpage documentation.
|
||||
Contact <a href="http://kristaps.bsd.lv">Kristaps</a> with questions or comments.
|
||||
<span class="nm">kcgi</span> is a <a href="http://www.bsd.lv">BSD.lv</a> project.
|
||||
</p>
|
||||
</section>
|
||||
<figure id="sample">
|
||||
<figure id="sample" class="sample">
|
||||
<figcaption>
|
||||
<span>Figure</span>: this simple example implements a server that just echoes <q>Hello, World!</q> as an HTTP
|
||||
The following simple example implements a server that just echoes <q>Hello, World!</q> as an HTTP
|
||||
response.
|
||||
Click on any italicitised fields to link to the documentation.
|
||||
</figcaption>
|
||||
<pre id="nocomments" class="prettyprint linenums">#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -129,36 +129,230 @@ int main(void) {
|
||||
return(EXIT_SUCCESS);
|
||||
}</pre>
|
||||
<nav>
|
||||
<a href="#" onclick="showComments(); return false;"><i class="fa fa-comments"></i> Toggle comments</a>
|
||||
<a href="#sample" onclick="showBits('comments');"><i class="fa fa-comments"></i> Toggle comments</a>
|
||||
</nav>
|
||||
</figure>
|
||||
<section id="features">
|
||||
<section id="install">
|
||||
<h2>
|
||||
How does it work?
|
||||
Installation
|
||||
</h2>
|
||||
<p>
|
||||
The meat of <span class="nm">kcgi</span> lies in the <a href="khttp_parse.3.html">khttp_parse(3)</a>
|
||||
function, which parses key-value input pairs from the HTTP request as well as determing the page
|
||||
request itself, MIME type, and so on.
|
||||
First, check if <span class="nm">kcgi</span> isn't already a third-part port for your system, such as
|
||||
for <a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/kcgi/">OpenBSD</a> or <a
|
||||
href="https://svnweb.freebsd.org/ports/head/www/kcgi/">FreeBSD</a>.
|
||||
If so, install using that system.
|
||||
</p>
|
||||
<p>
|
||||
If not, you'll need a modern UNIX system.
|
||||
To date, <span class="nm">kcgi</span> has been built and run on GNU/<a
|
||||
href="http://www.linux.org">Linux</a> machines, BSD (<a
|
||||
href="http://www.openbsd.org">OpenBSD</a>, <a href="http://www.freebsd.org">FreeBSD</a>), and <a
|
||||
href="http://www.apple.com/osx">Mac OSX</a> (Snow Leopard, Lion) on i386 and AMD64.
|
||||
It has been deployed under <a href="https://httpd.apache.org/">Apache</a>, <a
|
||||
href="http://nginx.org">nginx</a>, and OpenBSD's <a
|
||||
href="http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/httpd.8?query=httpd">httpd(8)</a>
|
||||
(the latter two via the <code>slowcgi</code> wrapper).
|
||||
Begin by downloading <a href="snapshots/kcgi.tgz">kcgi.tgz</a> and verify the archive with <a
|
||||
href="snapshots/kcgi.tgz.sha512">kcgi.tgz.sha512</a>.
|
||||
Once downloaded, compile the software with <code>make</code>, which will automatically run a
|
||||
configuration script to conditionally deploy portability glue.
|
||||
Finally, install the software using <code>make install</code>, optionally specifying the
|
||||
<code>PREFIX</code> if you don't intend to use <span class="file">/usr/local</span>.
|
||||
</p>
|
||||
<p>
|
||||
If <span class="nm">kcgi</span> doesn't compile, please send me the <span class="file">config.log</span>
|
||||
file and the output of the failed compilation.
|
||||
If you're running on an operating system with an unsupported sandbox, let me know and we can work
|
||||
together to fit it into the configuration and portability layer.
|
||||
</p>
|
||||
</section>
|
||||
<section id="usage">
|
||||
<h2>
|
||||
Usage
|
||||
</h2>
|
||||
<p>
|
||||
While this section describes a fairly typical setup, you'll want to read <a
|
||||
href="kcgi.3.html">kcgi(3)</a> and related for a fuller description.
|
||||
The most relevant component of <span class="nm">kcgi</span> is input parsing, described in <a
|
||||
href="khttp_parse.3.html">khttp_parse(3)</a>.
|
||||
All key-value pairs are parsed from input, as are non-key-value message bodies.
|
||||
Consider a sample application that wishes to process two named HTML fields, <code>string</code> (being a
|
||||
non-empty string) and <code>integer</code>, a signed integer.
|
||||
First, assign these fields to numeric identifiers.
|
||||
This simplifies later access of the field values.
|
||||
</p>
|
||||
<figure class="sample">
|
||||
<pre class="prettyprint linenums">enum key {
|
||||
KEY_STRING,
|
||||
KEY_INTEGER,
|
||||
KEY__MAX
|
||||
};</pre>
|
||||
</figure>
|
||||
<p>
|
||||
Next, connect the indices with validation functions and names.
|
||||
The validation function is run when the value is parsed; the name is the HTML form name for the given
|
||||
element.
|
||||
Validation applies to cookies as well as form and query-string data.
|
||||
You can provide your own validation functions instead of using the stock <span class="nm">kcgi</span>
|
||||
ones described in <a href="kvalid_string.3.html">kvalid_string(3)</a>, of course.
|
||||
An empty string for the name will be applied to non-key-value message bodies.
|
||||
</p>
|
||||
<figure class="sample">
|
||||
<pre class="prettyprint linenums">const struct kvalid keys[KEY__MAX] = {
|
||||
{ kvalid_stringne, "string" },
|
||||
{ kvalid_int, "integer" },
|
||||
};</pre>
|
||||
</figure>
|
||||
<p>
|
||||
Next, define a function that acts upon parsed fields.
|
||||
Note that this is <q>application logic</q>, and thus constitutes only an example: each application will
|
||||
handle its input differently.
|
||||
For simplicity, I focus only on the <code>string</code> input.
|
||||
If the value is found, it is assigned into the <code>fieldmap</code> array.
|
||||
If it was found but did not validate, it is assigned into the <code>fieldnmap</code> array.
|
||||
In this trivial example, the function emits the string values if found or indicates that they're not
|
||||
found (or not valid).
|
||||
There can be multiple inputs matching the same name, such as for HTML checkboxes.
|
||||
</p>
|
||||
<p>
|
||||
Beyond directly writing to the HTTP document with <a href="khttp_write.3.html">khttp_write(3)</a> and
|
||||
templating with <a href="khttp_template.3.html">khttp_template(3)</a>, <span class="nm">kcgi</span>
|
||||
provides the <a href="kcgihtml.3.html">kcgihtml(3)</a>, <a href="kcgijson.3.html">kcgijson(3)</a>, and
|
||||
<a href="kcgixml.3.html">kcgixml(3)</a> libraries for media-specific output functions.
|
||||
These are intended only for the simplest use: complex applications will probably use their own.
|
||||
</p>
|
||||
<figure class="sample">
|
||||
<pre class="prettyprint linenums">void process(struct kreq *r) {
|
||||
struct kpair *p;
|
||||
khttp_puts(r, "string = ");
|
||||
if ((p = r->fieldmap[KEY_STRING]))
|
||||
khttp_puts(r, p->parsed.s);
|
||||
else if (r->fieldnmap[KEY_STRING])
|
||||
khttp_puts(r, "failed parse");
|
||||
else
|
||||
khttp_puts(r, "not provided");
|
||||
}</pre>
|
||||
</figure>
|
||||
<p>
|
||||
Before doing any parsing, sanitise the HTTP context.
|
||||
To begin, I provide an array of indexed page identifiers.
|
||||
These define the page requests accepted by the application, in this case being only
|
||||
<code>/index.html</code>.
|
||||
</p>
|
||||
<figure class="sample">
|
||||
<pre class="prettyprint linenums">enum page {
|
||||
PAGE_INDEX,
|
||||
PAGE__MAX
|
||||
};
|
||||
const char *const pages[PAGE__MAX] = {
|
||||
"index",
|
||||
};</pre>
|
||||
</figure>
|
||||
<p>
|
||||
Now, validate the page request and HTTP context based upon pre-parsed components.
|
||||
This function checks the page request (it must be <code>/index</code>), MIME type (expanding to
|
||||
<code>/index.html</code>), and HTTP method (it must be an HTTP <code>GET</code>, such as
|
||||
<code>/index.html?string=foo</code>).
|
||||
</p>
|
||||
<figure class="sample">
|
||||
<pre class="prettyprint linenums">int sanitise(struct kreq *r) {
|
||||
if (PAGE_INDEX != r->page)
|
||||
return(0);
|
||||
else if (KMIME_TEXT_HTML != r->mime)
|
||||
return(0);
|
||||
return (KMETHOD_GET == r->method);
|
||||
}</pre>
|
||||
</figure>
|
||||
<p>
|
||||
Putting all of these together, parse the HTTP context, validate it, process it, then free the resources.
|
||||
This simple example emits an HTTP 404 error regardless of the cause.
|
||||
In reality, this will switch on HTTP 405 for unsupported methods and empty output for non-text media.
|
||||
Headers are output using <a href="khttp_head.3.html">khttp_head(3)</a>, with the document body started
|
||||
with <a href="khttp_body.3.html">khttp_body(3)</a>.
|
||||
The HTTP context is closed with <a href="khttp_free.3.html">khttp_free(3)</a>.
|
||||
</p>
|
||||
<figure class="sample">
|
||||
<pre class="prettyprint linenums">int main(void) {
|
||||
struct kreq r;
|
||||
if (KCGI_OK != khttp_parse(&r, keys, KEY__MAX,
|
||||
pages, PAGE__MAX, PAGE_INDEX))
|
||||
return(EXIT_FAILURE);
|
||||
if ( ! sanitise(&r)) {
|
||||
khttp_head(&r, kresps[KRESP_STATUS],
|
||||
"%s", khttps[KHTTP_404]);
|
||||
khttp_head(&r, kresps[KRESP_CONTENT_TYPE],
|
||||
"%s", kmimetypes[r.mime]);
|
||||
khttp_body(&r);
|
||||
khttp_puts(&r, "Page not found.");
|
||||
} else {
|
||||
khttp_head(&r, kresps[KRESP_STATUS],
|
||||
"%s", khttps[KHTTP_200]);
|
||||
khttp_head(&r, kresps[KRESP_CONTENT_TYPE],
|
||||
"%s", kmimetypes[r.mime]);
|
||||
khttp_body(&r);
|
||||
process(&r);
|
||||
}
|
||||
khttp_free(&r);
|
||||
return(EXIT_SUCCESS);
|
||||
};</pre>
|
||||
</figure>
|
||||
<p>
|
||||
Usually, web applications for HTML content will use the page and input maps quite heavily, switching on
|
||||
the page requested (which is usually known beforehand) and data sent along with the page.
|
||||
CMS-style web applications will not parse the page identifier, leaving that to a database lookup.
|
||||
Moreover, while HTML applications can generally disregard the HTTP method, a DAV implementation will
|
||||
focus much more on the method itself.
|
||||
</p>
|
||||
</section>
|
||||
<section id="features">
|
||||
<h2>
|
||||
Deploying
|
||||
</h2>
|
||||
<p>
|
||||
Applications using <span class="nm">kcgi</span> behave just like any other application.
|
||||
To compile <span class="nm">kcgi</span> applications, just include the <span class="nm">kcgi.h</span>
|
||||
header file and make sure it appears in the compiler inclusion path.
|
||||
(According to C99, you'll need to include <span class="nm">stdint.h</span> before it for the
|
||||
<code>int64_t</code> type used for parsing integers.)
|
||||
Linking is similarly normative: link to <span class="file">libkcgi</span> and, if your system has
|
||||
compression support, <span class="file">libz</span>.
|
||||
</p>
|
||||
<p>
|
||||
Well-deployed web servers, such as the default <a href="http://www.openbsd.org">OpenBSD</a> server, by
|
||||
default are deployed within a <a
|
||||
href="http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/chroot.2">chroot(2)</a>. If
|
||||
this is the case, you'll need to statically link your binary.
|
||||
If running within a <a
|
||||
href="http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/chroot.2">chroot(2)</a> and on
|
||||
OpenBSD, be aware that the sandbox method requires <span class="file">/dev/systrace</span> within the
|
||||
server root.
|
||||
By default, this file does not exist in the web server root.
|
||||
Moreover, the default web server root mount-point, <span class="file">/var</span>, is mounted
|
||||
<code>nodev</code>.
|
||||
This complication does not exist for the other sandboxes.
|
||||
</p>
|
||||
</section>
|
||||
<section id="features">
|
||||
<h2>
|
||||
Implementation Details
|
||||
</h2>
|
||||
<p>
|
||||
The bulk of <span class="nm">kcgi</span> lies in <a href="khttp_parse.3.html">khttp_parse(3)</a>, which
|
||||
fully parses the HTTP context.
|
||||
Application developers must invoke this function before all others.
|
||||
As mentioned in <a href="#security">security</a> section, the subroutines of
|
||||
<a href="khttp_parse.3.html">khttp_parse(3)</a> are invoked inside of a sandboxed child process –
|
||||
this isolates unvalidated, adversarial input.
|
||||
It must be matched by an <a href="khttp_free.3.html">khttp_free(3)</a>.
|
||||
It must be matched by an <a href="khttp_free.3.html">khttp_free(3)</a>, which frees all resources.
|
||||
</p>
|
||||
<p>
|
||||
Once <a href="khttp_parse.3.html">khttp_parse(3)</a> has been successfully invoked, the calling
|
||||
application is free to respond to the parsed and validated request.
|
||||
</p>
|
||||
<h3 id="security">
|
||||
Security
|
||||
</h3>
|
||||
<p>
|
||||
As a security precaution, the <span class="nm">kcgi</span> library parses and validates untrusted
|
||||
network data in a sandboxed child process by forking within <a
|
||||
href="khttp_parse.3.html">khttp_parse(3)</a>, where child process is responsible for reading and
|
||||
parsing form data from the web server.
|
||||
The <a href="khttp_parse.3.html">khttp_parse(3)</a> function isolates its parsing and validation of
|
||||
untrusted network data within a <i>sandboxed</i> child process.
|
||||
Sandboxes limit the environment available to a process, so exploitable errors in the parsing process (or
|
||||
validation with third-party libraries) cannot touch the system environment.
|
||||
This parsed data is returned to the parent process over a socket.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="figure1.svg" alt="Implementation Details" />
|
||||
</figure>
|
||||
<p>
|
||||
This method of sandboxing the untrusted child process follows <a href="http://www.openssh.org">OpenSSH</a>,
|
||||
and requires special handling for each operating system:
|
||||
</p>
|
||||
@@ -197,76 +391,11 @@ int main(void) {
|
||||
abruptly killed by the sandbox facility.
|
||||
If required, this kind of validation can take place after the parse validation sequence.
|
||||
</p>
|
||||
<h3 id="portability">
|
||||
Portability
|
||||
</h3>
|
||||
<p>
|
||||
<span class="nm">kcgi</span> should run on any modern UNIX systems and with any web server.
|
||||
To date, it has been built and run on GNU/<a href="http://www.linux.org">Linux</a> machines, BSD (<a
|
||||
href="http://www.openbsd.org">OpenBSD</a>, <a href="http://www.freebsd.org">FreeBSD</a>), and <a href="http://www.apple.com/osx">Mac OSX</a>
|
||||
(Snow Leopard, Lion) on i386 and AMD64.
|
||||
It has been deployed under <a href="https://httpd.apache.org/">Apache</a>, <a
|
||||
href="http://nginx.org">nginx</a>, and OpenBSD's <a
|
||||
href="http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/httpd.8?query=httpd">httpd(8)</a>
|
||||
(the latter two via the <code>slowcgi</code> wrapper).
|
||||
</p>
|
||||
<p>
|
||||
Portability across UNIX systems is made possible by a small <code>configure</code> script that checks
|
||||
for minor inconsistencies such as <code>strlcpy(3)</code>, the <a href="#security">Security</a> mechanisms,
|
||||
and for <a href="#compression">Compression</a> support.
|
||||
</p>
|
||||
<h3 id="extending">
|
||||
Extensibility
|
||||
</h3>
|
||||
<p>
|
||||
While page maps and input validation are entirely driven by the interfacing application, <span
|
||||
class="nm">kcgi</span> also allows for extension of the default HTTP headers, schemas, MIME
|
||||
types, and so on.
|
||||
Reasonable default have been provided for convenience.
|
||||
For specifics, see <a href="khttp_parse.3.html">khttp_parse(3)</a>.
|
||||
</p>
|
||||
<p>
|
||||
The library can also be extended for different output modes.
|
||||
Two such modes, <a href="kcgihtml.3.html">kcgihtml(3)</a> and <a href="kcgijson.3.html">kcgijson(3)</a>,
|
||||
are bundled with the system.
|
||||
It allows a mechanism for building HTML5 trees around the usual
|
||||
<a href="khttp_write.3.html">khttp_write(3)</a> family of functions.
|
||||
</p>
|
||||
<h3 id="compression">
|
||||
Compression
|
||||
</h3>
|
||||
<p>
|
||||
If <code>HAVE_ZLIB</code> is enabled during compilation (via the <a href="#portability">Portability</a>
|
||||
mechanism), <a href="khttp_body.3.html">khttp_body(3)</a> will signal use of <a
|
||||
href="http://www.zlib.net">zlib</a> to compress the HTTP body.
|
||||
Compression is only enabled if the client provides the correct (<q>gzip</q>) HTTP request header.
|
||||
</p>
|
||||
<h3 id="input">
|
||||
Input Processing
|
||||
</h3>
|
||||
<p>
|
||||
All common input methods—query string, cookie, and form (multipart form-data and mixed,
|
||||
urlencoded, and plain—are supported by <span class="nm">kcgi</span>.
|
||||
As described in the <a href="#security">Security</a> section, these fields are all parsed and validated
|
||||
from network data in a child process.
|
||||
Each input key-value pair can be matched (by key name) to a validator, which is run when fields are
|
||||
parsed.
|
||||
You can then look up key-value pairs constant-time in a table indexed by that key.
|
||||
</p>
|
||||
<h3 id="template">
|
||||
Templating
|
||||
</h3>
|
||||
<p>
|
||||
Many application will want just to fill in an output template instead of creating complex output trees
|
||||
(relegating most work to JavaScript and JSON).
|
||||
<span class="nm">kcgi</span> provides the <a href="khttp_template.3.html">khttp_template(3)</a> family
|
||||
of functions to fill in files or memory buffers with data.
|
||||
Templates are the most common usage of <span class="nm">kcgi</span>, as they allow for a strong
|
||||
disconnect between prsentation and logic.
|
||||
</p>
|
||||
<h3 id="testing">
|
||||
</section>
|
||||
<section>
|
||||
<h2 id="testing">
|
||||
Testing
|
||||
</h3>
|
||||
</h2>
|
||||
<p>
|
||||
<span class="nm">kcgi</span> is shipped with a fully automated testing framework executed with
|
||||
<code>make regress</code>.
|
||||
@@ -292,6 +421,49 @@ int main(void) {
|
||||
<code>afl-urlencoded</code> binaries using the test cases (and dictionaries, for the first) provided.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>
|
||||
Performance
|
||||
</h2>
|
||||
<p>
|
||||
Security comes at a price.
|
||||
By design, <span class="nm">kcgi</span> incurs overhead in three ways: first, spawning a child to
|
||||
process the untrusted network data; second, enacting the sandbox framework; and third, passing parsed
|
||||
pairs back to the parent context.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="figure2.png" />
|
||||
</figure>
|
||||
<p>
|
||||
This figure illustrates the cost of running <span class="nm">kcgi</span> against a baseline on OpenBSD
|
||||
5.5 running <a href="http://nginx.org">nginx</a> and <a
|
||||
href="http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/slowcgi.8">slowcgi(8)</a>, i386.
|
||||
It shows the empirical cumulative distribution of a statisically-significant number of page requests
|
||||
(>1000) as measured by <a href="https://httpd.apache.org/docs/2.2/programs/ab.html">ab(1)</a>.
|
||||
</p>
|
||||
<p>
|
||||
Line (1) shows a static file being served by the web server.
|
||||
The high speed is due to the file (most likely) being cached by the web server and/or kernel.
|
||||
Moving right, line (2) shows a basic CGI request producing no content.
|
||||
The CGI simply exits with an HTTP 200.
|
||||
This reflects the cost of invoking <a
|
||||
href="http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/slowcgi.8">slowcgi(8)</a>.
|
||||
Line (3) is a simple <span class="nm">kcgi</span> that simply emits an HTTP 200.
|
||||
This instance is neither sandboxed nor does it compress output.
|
||||
The overhead from line (2) is due to the additional child being spawned.
|
||||
Line (4) shows the addition of sandboxing without compression, in this case via
|
||||
<a href="http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man4/systrace.4">systrace(4)</a>.
|
||||
Lastly, line (5) shows both compression and sandboxing.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="figure3.png" />
|
||||
</figure>
|
||||
<p>
|
||||
In this bar chart, I show the same but for relative comparison of the distribution means.
|
||||
The similarity between the compressed and non-compressed versions are due to the small amount of data
|
||||
being transmitted in the response body.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
<footer>
|
||||
<div>© 2014 <a rel="author" href="http://kristaps.bsd.lv">Kristaps Dzonsons</a></div>
|
||||
|
||||
+67
@@ -95,6 +95,73 @@ library, link as follows:
|
||||
.Bd -literal
|
||||
% cc -o sample -static sample.o -lkcgihtml -lkcgi -lz
|
||||
.Ed
|
||||
.Sh EXAMPLES
|
||||
The following simple example assumes that
|
||||
.Pa kcgi.h
|
||||
is in the compiler's include path.
|
||||
It does nothing but emit
|
||||
.Dq Hello, world!
|
||||
to the output.
|
||||
.Bd -literal
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <kcgi.h>
|
||||
|
||||
int main(void) {
|
||||
struct kreq r;
|
||||
const char *page = "index";
|
||||
if (KCGI_OK != khttp_parse(&r, NULL, 0, &page, 1, 0))
|
||||
return(EXIT_FAILURE);
|
||||
khttp_head(&r, kresps[KRESP_STATUS],
|
||||
"%s", khttps[KHTTP_200]);
|
||||
khttp_head(&r, kresps[KRESP_CONTENT_TYPE],
|
||||
"%s", kmimetypes[r.mime]);
|
||||
khttp_body(&r);
|
||||
khttp_puts(&r, "Hello, world!");
|
||||
khttp_free(&r);
|
||||
return(EXIT_SUCCESS);
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
In a more complicated example,
|
||||
.Nm
|
||||
accepts a single parameter
|
||||
.Dq string
|
||||
to validate and display.
|
||||
This might be provided as part of an HTML form or directly as part of
|
||||
the URL query string.
|
||||
.Bd -literal
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <kcgi.h>
|
||||
|
||||
int main(void) {
|
||||
struct kreq r;
|
||||
struct kpair *p;
|
||||
const char *page = "index";
|
||||
struct kvalid key = { kvalid_stringne, "string" };
|
||||
if (KCGI_OK != khttp_parse(&r, &key, 1, &page, 1, 0))
|
||||
return(EXIT_FAILURE);
|
||||
khttp_head(&r, kresps[KRESP_STATUS],
|
||||
"%s", khttps[KHTTP_200]);
|
||||
khttp_head(&r, kresps[KRESP_CONTENT_TYPE],
|
||||
"%s", kmimetypes[r.mime]);
|
||||
khttp_body(&r);
|
||||
khttp_puts(&r, "Result: ");
|
||||
if ((p = r.fieldmap[0]))
|
||||
khttp_puts(&r, p->parsed.s);
|
||||
else if (r.fieldnmap[0])
|
||||
khttp_puts(&r, "bad parse");
|
||||
else
|
||||
khttp_puts(&r, "no value");
|
||||
khttp_free(&r);
|
||||
return(EXIT_SUCCESS);
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
Applications will usually specify an array of key-value pairs to
|
||||
validate; or in the event of web services, a default validator (empty
|
||||
string) for the full HTTP message body.
|
||||
.Sh SEE ALSO
|
||||
.Xr kcgi 3 ,
|
||||
.Xr kcgihtml 3 ,
|
||||
|
||||
+218
-190
@@ -56,49 +56,13 @@ and
|
||||
.Nm khttp_parsex
|
||||
functions parse and validate input and the HTTP environment
|
||||
(compression, paths, MIME types, and so on).
|
||||
It parses and validates data as key-value pairs for the
|
||||
.Li application/x-www-form-urlencoded ,
|
||||
.Li multipart/form-data ,
|
||||
and
|
||||
.Li text/plain
|
||||
.Pq if and only if the method is POST
|
||||
content types, and opaque message bodies (defaulting to
|
||||
.Li application/octet-stream )
|
||||
otherwise.
|
||||
Key-value pairs are also parsed from cookies and the query string.
|
||||
It is the central function in the
|
||||
.Xr kcgi 3
|
||||
library, parsing and validating key-value form (query string, message
|
||||
body, cookie) data and opaque message bodies.
|
||||
.Pp
|
||||
The collective arguments are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fa req
|
||||
Fill with input fields and HTTP context parsed from the CGI environment.
|
||||
This is the main structure carried around in a
|
||||
.Xr kcgi 3
|
||||
application.
|
||||
.It Fa keys
|
||||
An array of input and validation fields.
|
||||
.It Fa keysz
|
||||
The number of elements in
|
||||
.Fa keys .
|
||||
.It Fa pages
|
||||
An array of recognised pathnames.
|
||||
When pathnames are parsed, they're matched to indices in this array.
|
||||
.It Fa pagesz
|
||||
The number of pages in
|
||||
.Fa pages .
|
||||
Also used if the requested page was not in
|
||||
.Fa pages .
|
||||
.It Fa defmime
|
||||
If no MIME type is specified (that is, there's no suffix to the
|
||||
page request), use this index in the
|
||||
.Fa mimes
|
||||
array.
|
||||
.It Fa defpage
|
||||
If no page was specified (e.g., the default landing page), this is
|
||||
provided as the requested page index.
|
||||
.It Fa suffixes
|
||||
Define the MIME type (suffix) mapping.
|
||||
.It Fa maxmime
|
||||
The MIME index used if no MIME type was matched.
|
||||
.It Fa arg
|
||||
A pointer to private application data.
|
||||
It is not touched unless
|
||||
@@ -109,14 +73,48 @@ Function invoked with
|
||||
.Fa arg
|
||||
by the child process starting to parse untrusted network data. This
|
||||
makes sure that no unnecessary data is leaked into the child.
|
||||
.It Fa defmime
|
||||
If no MIME type is specified (that is, there's no suffix to the
|
||||
page request), use this index in the
|
||||
.Fa mimes
|
||||
array.
|
||||
.It Fa defpage
|
||||
If no page was specified (e.g., the default landing page), this is
|
||||
provided as the requested page index.
|
||||
.It Fa keys
|
||||
An array of input and validation fields.
|
||||
.It Fa keysz
|
||||
The number of elements in
|
||||
.Fa keys .
|
||||
.It Fa maxmime
|
||||
The MIME index used if no MIME type was matched.
|
||||
.It Fa pages
|
||||
An array of recognised pathnames.
|
||||
When pathnames are parsed, they're matched to indices in this array.
|
||||
.It Fa pagesz
|
||||
The number of pages in
|
||||
.Fa pages .
|
||||
Also used if the requested page was not in
|
||||
.Fa pages .
|
||||
.It Fa req
|
||||
Fill with input fields and HTTP context parsed from the CGI environment.
|
||||
This is the main structure carried around in a
|
||||
.Xr kcgi 3
|
||||
application.
|
||||
.It Fa suffixes
|
||||
Define the MIME type (suffix) mapping.
|
||||
.El
|
||||
.Pp
|
||||
The first form is for applications using the system-recognised MIME
|
||||
types, which should work well enough for most applications.
|
||||
It is equivalent to the following:
|
||||
The first form,
|
||||
.Nm khttp_parse ,
|
||||
is for applications using the system-recognised MIME types.
|
||||
This should work well enough for most applications.
|
||||
It is equivalent to invoking the second form,
|
||||
.Nm khttp_parsex ,
|
||||
as follows:
|
||||
.Bd -literal -offset indent
|
||||
khttp_parsex(req, ksuffixmap, KIME__MAX,
|
||||
keys, keysz, pages, pagesz, NULL, NULL);
|
||||
keys, keysz, pages, pagesz, NULL, NULL);
|
||||
.Ed
|
||||
.Pp
|
||||
The
|
||||
@@ -140,6 +138,76 @@ It consists of the following fields:
|
||||
Private application data.
|
||||
This is set during
|
||||
.Fn khttp_parse .
|
||||
.It Va auth
|
||||
Type of
|
||||
.Qq managed
|
||||
HTTP authorisation, if any.
|
||||
This is digest
|
||||
.Pq Dv KAUTH_DIGEST
|
||||
or basic
|
||||
.Pq Dv KAUTH_BASIC
|
||||
authorisation performed by the web server.
|
||||
See the
|
||||
.Va rawauth
|
||||
field for raw authorisation requests.
|
||||
If a managed authorisation is specified but with unknown type (i.e., not
|
||||
digest or basic authentiation), this is set to
|
||||
.Dv KAUTH_UNKNOWN .
|
||||
.It Va cookies
|
||||
All key-value pairs read from user cookies.
|
||||
.It Va cookiemap
|
||||
Entries in successfully-parsed (or un-parsed)
|
||||
.Va cookies
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
argument to
|
||||
.Fn khttp_parse .
|
||||
.It Va cookienmap
|
||||
Entries in unsuccessfully-parsed (but still attempted)
|
||||
.Fa cookies
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
argument to
|
||||
.Fn khttp_parse .
|
||||
.It Va cookiesz
|
||||
The size of the
|
||||
.Va cookies
|
||||
array.
|
||||
.It Va fields
|
||||
All key-value pairs read from the requests (query string, cookies,
|
||||
message body).
|
||||
.It Va fieldmap
|
||||
Entries in successfully-parsed (or un-parsed)
|
||||
.Fa fields
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
arguments to
|
||||
.Fn khttp_parse .
|
||||
.It Va fieldnmap
|
||||
Entries in unsuccessfully-parsed (but still attempted)
|
||||
.Fa fields
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
argument to
|
||||
.Fn khttp_parse .
|
||||
.It Va fieldsz
|
||||
The number of elements in the
|
||||
.Va fields
|
||||
array.
|
||||
.It Va fullpath
|
||||
The full path following the server name or
|
||||
.Dv NULL
|
||||
if there is no path following the server.
|
||||
For example, if
|
||||
.Pa foo.cgi/bar/baz
|
||||
is the
|
||||
.Dv PATH_INFO ,
|
||||
this would be
|
||||
.Pa /bar/baz .
|
||||
.It Va host
|
||||
The host-name (i.e., the host of the web application) request passed to
|
||||
the application.
|
||||
This shouldn't be confused with the application host's canonical name.
|
||||
.It Va method
|
||||
The
|
||||
.Dv KMETHOD_ACL ,
|
||||
@@ -176,67 +244,14 @@ and
|
||||
so be sure to emit a
|
||||
.Dv KHTTP_405
|
||||
status for non-conforming methods.
|
||||
.It Va auth
|
||||
Type of
|
||||
.Qq managed
|
||||
HTTP authorisation, if any.
|
||||
This is digest
|
||||
.Pq Dv KAUTH_DIGEST
|
||||
or basic
|
||||
.Pq Dv KAUTH_BASIC
|
||||
authorisation performed by the web server.
|
||||
See the
|
||||
.Va rawauth
|
||||
field for raw authorisation requests.
|
||||
If a managed authorisation is specified but with unknown type (i.e., not
|
||||
digest or basic authentiation), this is set to
|
||||
.Dv KAUTH_UNKNOWN .
|
||||
.It Va rawauth
|
||||
If the web server passes the
|
||||
.Qq Authorization
|
||||
header (which, for example, Apache doesn't by default), then the header
|
||||
is parsed into this field, which is of type
|
||||
.Vt struct khttpauth .
|
||||
.It Va cookies
|
||||
All key-value pairs read from user cookies.
|
||||
.It Va cookiesz
|
||||
The size of the
|
||||
.Va cookies
|
||||
array.
|
||||
.It Va cookiemap
|
||||
Entries in successfully-parsed (or un-parsed)
|
||||
.Va cookies
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
argument to
|
||||
.It Va kdata
|
||||
Internal data.
|
||||
Should not be touched.
|
||||
.It Va keys
|
||||
Value passed to
|
||||
.Fn khttp_parse .
|
||||
.It Va cookienmap
|
||||
Entries in unsuccessfully-parsed (but still attempted)
|
||||
.Fa cookies
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
argument to
|
||||
.Fn khttp_parse .
|
||||
.It Va fields
|
||||
All key-value pairs read from the requests (query string, cookies,
|
||||
message body).
|
||||
.It Va fieldsz
|
||||
The number of elements in the
|
||||
.Va fields
|
||||
array.
|
||||
.It Va fieldmap
|
||||
Entries in successfully-parsed (or un-parsed)
|
||||
.Fa fields
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
arguments to
|
||||
.Fn khttp_parse .
|
||||
.It Va fieldnmap
|
||||
Entries in unsuccessfully-parsed (but still attempted)
|
||||
.Fa fields
|
||||
mapped into field indices as defined by the
|
||||
.Fa keys
|
||||
argument to
|
||||
.It Va keysz
|
||||
Value passed to
|
||||
.Fn khttp_parse .
|
||||
.It Va mime
|
||||
The MIME type of the requested file as determined by its
|
||||
@@ -275,6 +290,8 @@ if the path failed lookup.
|
||||
.It Va pagename
|
||||
The string corresponding to
|
||||
.Va page .
|
||||
.It Va port
|
||||
The server's receiving TCP port.
|
||||
.It Va path
|
||||
The path (or empty string) following the parsed component regardless of
|
||||
whether it was located in the path array provided to
|
||||
@@ -286,6 +303,27 @@ is
|
||||
the path component would be
|
||||
.Pa baz
|
||||
.Pq with the leading slash stripped .
|
||||
.It Va pname
|
||||
The script name (which may be an empty string in degenerate cases)
|
||||
passed to the server.
|
||||
This may not reflect a file-system entity if re-written by the web
|
||||
server.
|
||||
.It Va rawauth
|
||||
If the web server passes the
|
||||
.Qq Authorization
|
||||
header (which, for example, Apache doesn't by default), then the header
|
||||
is parsed into this field, which is of type
|
||||
.Vt struct khttpauth .
|
||||
.It Va remote
|
||||
The string form of the client's IPV4 or IVP6 address.
|
||||
.It Va scheme
|
||||
The access scheme, which is either
|
||||
.Dv KSCHEME_HTTP
|
||||
or
|
||||
.Dv KSCHEME_HTTPS .
|
||||
The scheme defaults to
|
||||
.Dv KSCHEME_HTTP
|
||||
if not specified by the request.
|
||||
.It Va suffix
|
||||
The suffix part of the
|
||||
.Dv PATH_INFO
|
||||
@@ -301,53 +339,39 @@ the suffix would be
|
||||
See the
|
||||
.Va mime
|
||||
field for the MIME type parsed from the suffix.
|
||||
.It Va fullpath
|
||||
The full path following the server name or
|
||||
.Dv NULL
|
||||
if there is no path following the server.
|
||||
For example, if
|
||||
.Pa foo.cgi/bar/baz
|
||||
is the
|
||||
.Dv PATH_INFO ,
|
||||
this would be
|
||||
.Pa /bar/baz .
|
||||
.It Va scheme
|
||||
The access scheme, which is either
|
||||
.Dv KSCHEME_HTTP
|
||||
or
|
||||
.Dv KSCHEME_HTTPS .
|
||||
.It Va remote
|
||||
The string form of the client's IPV4 or IVP6 address.
|
||||
.It Va host
|
||||
The host-name request passed to the application.
|
||||
.It Va port
|
||||
The server's receiving TCP port.
|
||||
.It Va kdata
|
||||
Internal data.
|
||||
Should not be touched.
|
||||
.It Va keys
|
||||
Value passed to
|
||||
.Fn khttp_parse .
|
||||
.It Va keysz
|
||||
Value passed to
|
||||
.Fn khttp_parse .
|
||||
.It Va pname
|
||||
The script name (which may be an empty string in degenerate cases).
|
||||
.El
|
||||
.Pp
|
||||
The application must define the
|
||||
The application may optionally define
|
||||
.Vt keys
|
||||
provided to
|
||||
.Nm khttp_parse
|
||||
and
|
||||
.Nm khttp_parsex
|
||||
as an array of
|
||||
.Vt "struct kvalid" ,
|
||||
which consists of the following:
|
||||
.Vt "struct kvalid" .
|
||||
This structure is central to the validation of input data.
|
||||
It consists of the following fields:
|
||||
.Bl -tag -width Ds
|
||||
.It Va "int (*valid)(struct kpair *kp)"
|
||||
.It Va name
|
||||
The field name, i.e., how it appears in the HTML form input name.
|
||||
This cannot be
|
||||
.Dv NULL .
|
||||
If the field name is an empty string and the HTTP message consists of an
|
||||
opaque body (and not key-value pairs), then that field will be used to
|
||||
validate the HTTP message body.
|
||||
This is useful for
|
||||
.Dv KMETHOD_PUT
|
||||
style requests.
|
||||
.It Va valid
|
||||
Validating function.
|
||||
This can be
|
||||
.Dv NULL
|
||||
if anything goes.
|
||||
This function accepts a single
|
||||
.Ft "struct kpair *"
|
||||
argument and returns an
|
||||
.Ft int .
|
||||
If the function is
|
||||
.Dv NULL ,
|
||||
then no validation is performed and the data is considered as always
|
||||
valid.
|
||||
If you provide your own
|
||||
.Fa valid
|
||||
function, it must set the
|
||||
@@ -370,16 +394,6 @@ You should assume that you cannot invoke any
|
||||
.Qq invasive
|
||||
system calls such as opening files, sockets, etc.
|
||||
In other words, these must be pure computation.
|
||||
.It Va "const char *name"
|
||||
The field name, i.e., how it appears in the HTML form input name.
|
||||
This cannot be
|
||||
.Dv NULL .
|
||||
If the field name is an empty string and the HTTP message consists of an
|
||||
opaque body (and not key-value pairs), then that field will be used to
|
||||
validate the HTTP message body.
|
||||
This is useful for
|
||||
.Dv KMETHOD_PUT
|
||||
style requests.
|
||||
.El
|
||||
.Pp
|
||||
The
|
||||
@@ -388,26 +402,16 @@ structure presents the user with fields parsed from input and (possibly)
|
||||
matched to the
|
||||
.Fa keys
|
||||
variable passed to
|
||||
.Fn http_parse .
|
||||
.Nm khttp_parse
|
||||
and
|
||||
.Nm khttp_parsex .
|
||||
It is also passed to the validation function to be filled in.
|
||||
In this case, the MIME-related fields are already filled in and may be
|
||||
examined to determine the method of validation.
|
||||
This is useful when validating opaque message bodies.
|
||||
.Bl -tag -width Ds
|
||||
.It Va key
|
||||
The nil-terminated key (input) name.
|
||||
If the HTTP message body is opaque (e.g.,
|
||||
.Dv KMETHOD_PUT ) ,
|
||||
then an empty-string key is cooked up.
|
||||
.It Va val
|
||||
The (input) value, which is always nil-terminated, but if the data is
|
||||
binary, nil terminators may occur before the true data length of
|
||||
.Fa valsz .
|
||||
.It Va valsz
|
||||
The true length of
|
||||
.Fa val .
|
||||
.It Va file
|
||||
The value's source filename or
|
||||
.Dv NULL
|
||||
if not defined.
|
||||
.It Va ctype
|
||||
The value's content type (e.g.,
|
||||
The value's MIME content type (e.g.,
|
||||
.Li image/jpeg ) ,
|
||||
or
|
||||
.Dv NULL
|
||||
@@ -420,34 +424,46 @@ is not
|
||||
it is looked up in the
|
||||
.Fa mimes
|
||||
parameter passed to
|
||||
.Fn khttp_parsex
|
||||
.Nm khttp_parsex
|
||||
or
|
||||
.Va ksuffixmap
|
||||
if using
|
||||
.Fn khttp_parse .
|
||||
.Nm khttp_parse .
|
||||
If found, it is set to the appropriate index.
|
||||
Otherwise, it's
|
||||
.Fa mimesz .
|
||||
.It Va xcode
|
||||
The value's content transfer encoding (e.g.,
|
||||
.Li base64 ) ,
|
||||
or
|
||||
.It Va file
|
||||
The value's MIME source filename or
|
||||
.Dv NULL
|
||||
if not defined.
|
||||
.It Va key
|
||||
The nil-terminated key (input) name.
|
||||
If the HTTP message body is opaque (e.g.,
|
||||
.Dv KMETHOD_PUT ) ,
|
||||
then an empty-string key is cooked up.
|
||||
.It Va keypos
|
||||
If looked up in the
|
||||
.Fa keys
|
||||
variable passed to
|
||||
.Nm khttp_parse ,
|
||||
the index of the looked-up key.
|
||||
Otherwise
|
||||
.Fa keysz .
|
||||
.It Va next
|
||||
In a cookie or field map,
|
||||
.Fa next
|
||||
points to the next parsed key-value pair with the same
|
||||
.Fa key
|
||||
name.
|
||||
.It Va keypos
|
||||
If looked up in the
|
||||
.Fa keys
|
||||
variable passed to
|
||||
.Fn khttp_parse ,
|
||||
the index of the looked-up key.
|
||||
Otherwise
|
||||
.Fa keysz .
|
||||
This occurs most often in HTML checkbox forms, where many fields may
|
||||
have the same name.
|
||||
.It Va parsed
|
||||
The parsed, validated value.
|
||||
These may be integer, for a 64-bit signed integer; string, for a
|
||||
nil-termianted character string; or double, for a double-precision
|
||||
floating-point number.
|
||||
This is intentionally basic because the resulting data must be reliably
|
||||
passed from the parsing context back into the web application.
|
||||
.It Va state
|
||||
The validation state: whether validated by a parse, invalidated by a
|
||||
parse, or non-validated (unparsed).
|
||||
@@ -456,11 +472,19 @@ If parsed, the type of data in
|
||||
.Fa parsed ,
|
||||
otherwise
|
||||
.Dv KFIELD__MAX .
|
||||
.It Va parsed
|
||||
The parsed, validated value.
|
||||
These may be integer, for a 64-bit signed integer; string, for a
|
||||
nil-termianted character string; or double, for a double-precision
|
||||
floating-point number.
|
||||
.It Va val
|
||||
The (input) value, which is always nil-terminated, but if the data is
|
||||
binary, nil terminators may occur before the true data length of
|
||||
.Fa valsz .
|
||||
.It Va valsz
|
||||
The true length of
|
||||
.Fa val .
|
||||
.It Va xcode
|
||||
The value's MIME content transfer encoding (e.g.,
|
||||
.Li base64 ) ,
|
||||
or
|
||||
.Dv NULL
|
||||
if not defined.
|
||||
.El
|
||||
.Pp
|
||||
Lastly, the
|
||||
@@ -508,6 +532,10 @@ Opaque operating system error.
|
||||
.El
|
||||
.Pp
|
||||
On failure, the calling application should terminate as soon as possible.
|
||||
Applications should
|
||||
.Em not
|
||||
try to write an HTTP 505 error or similar, but allow the web server to
|
||||
handle the empty CGI response on its own.
|
||||
.Sh SEE ALSO
|
||||
.Xr kcgi 3 ,
|
||||
.Xr khttp_free 3
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Percentage served Time in ms
|
||||
0 1.754
|
||||
1 1.782
|
||||
2 1.787
|
||||
3 1.789
|
||||
4 1.793
|
||||
5 1.795
|
||||
6 1.798
|
||||
7 1.801
|
||||
8 1.802
|
||||
9 1.806
|
||||
10 1.808
|
||||
11 1.811
|
||||
12 1.812
|
||||
13 1.813
|
||||
14 1.815
|
||||
15 1.817
|
||||
16 1.819
|
||||
17 1.820
|
||||
18 1.821
|
||||
19 1.823
|
||||
20 1.824
|
||||
21 1.826
|
||||
22 1.827
|
||||
23 1.828
|
||||
24 1.829
|
||||
25 1.830
|
||||
26 1.830
|
||||
27 1.831
|
||||
28 1.832
|
||||
29 1.833
|
||||
30 1.834
|
||||
31 1.835
|
||||
32 1.836
|
||||
33 1.837
|
||||
34 1.839
|
||||
35 1.841
|
||||
36 1.842
|
||||
37 1.843
|
||||
38 1.845
|
||||
39 1.846
|
||||
40 1.847
|
||||
41 1.848
|
||||
42 1.850
|
||||
43 1.851
|
||||
44 1.853
|
||||
45 1.855
|
||||
46 1.857
|
||||
47 1.859
|
||||
48 1.861
|
||||
49 1.863
|
||||
50 1.864
|
||||
51 1.867
|
||||
52 1.869
|
||||
53 1.871
|
||||
54 1.872
|
||||
55 1.875
|
||||
56 1.879
|
||||
57 1.881
|
||||
58 1.882
|
||||
59 1.886
|
||||
60 1.888
|
||||
61 1.891
|
||||
62 1.892
|
||||
63 1.895
|
||||
64 1.897
|
||||
65 1.899
|
||||
66 1.901
|
||||
67 1.902
|
||||
68 1.904
|
||||
69 1.905
|
||||
70 1.907
|
||||
71 1.910
|
||||
72 1.911
|
||||
73 1.916
|
||||
74 1.917
|
||||
75 1.920
|
||||
76 1.923
|
||||
77 1.924
|
||||
78 1.926
|
||||
79 1.929
|
||||
80 1.931
|
||||
81 1.932
|
||||
82 1.933
|
||||
83 1.935
|
||||
84 1.937
|
||||
85 1.939
|
||||
86 1.940
|
||||
87 1.943
|
||||
88 1.945
|
||||
89 1.949
|
||||
90 1.953
|
||||
91 1.956
|
||||
92 1.960
|
||||
93 1.964
|
||||
94 1.967
|
||||
95 1.971
|
||||
96 1.975
|
||||
97 1.981
|
||||
98 1.990
|
||||
99 2.009
|
||||
@@ -0,0 +1,101 @@
|
||||
# Percentage served Time in ms
|
||||
0 0.215
|
||||
1 0.217
|
||||
2 0.217
|
||||
3 0.218
|
||||
4 0.218
|
||||
5 0.218
|
||||
6 0.218
|
||||
7 0.218
|
||||
8 0.218
|
||||
9 0.218
|
||||
10 0.218
|
||||
11 0.218
|
||||
12 0.218
|
||||
13 0.218
|
||||
14 0.219
|
||||
15 0.219
|
||||
16 0.219
|
||||
17 0.219
|
||||
18 0.219
|
||||
19 0.219
|
||||
20 0.219
|
||||
21 0.219
|
||||
22 0.219
|
||||
23 0.219
|
||||
24 0.219
|
||||
25 0.219
|
||||
26 0.219
|
||||
27 0.219
|
||||
28 0.219
|
||||
29 0.219
|
||||
30 0.219
|
||||
31 0.219
|
||||
32 0.219
|
||||
33 0.219
|
||||
34 0.219
|
||||
35 0.219
|
||||
36 0.219
|
||||
37 0.220
|
||||
38 0.220
|
||||
39 0.220
|
||||
40 0.220
|
||||
41 0.220
|
||||
42 0.220
|
||||
43 0.220
|
||||
44 0.220
|
||||
45 0.220
|
||||
46 0.220
|
||||
47 0.220
|
||||
48 0.220
|
||||
49 0.220
|
||||
50 0.220
|
||||
51 0.220
|
||||
52 0.220
|
||||
53 0.220
|
||||
54 0.220
|
||||
55 0.220
|
||||
56 0.220
|
||||
57 0.220
|
||||
58 0.220
|
||||
59 0.220
|
||||
60 0.221
|
||||
61 0.221
|
||||
62 0.221
|
||||
63 0.221
|
||||
64 0.221
|
||||
65 0.221
|
||||
66 0.221
|
||||
67 0.221
|
||||
68 0.221
|
||||
69 0.221
|
||||
70 0.222
|
||||
71 0.222
|
||||
72 0.222
|
||||
73 0.223
|
||||
74 0.223
|
||||
75 0.223
|
||||
76 0.223
|
||||
77 0.224
|
||||
78 0.224
|
||||
79 0.224
|
||||
80 0.225
|
||||
81 0.225
|
||||
82 0.226
|
||||
83 0.226
|
||||
84 0.227
|
||||
85 0.228
|
||||
86 0.230
|
||||
87 0.233
|
||||
88 0.234
|
||||
89 0.235
|
||||
90 0.237
|
||||
91 0.239
|
||||
92 0.240
|
||||
93 0.241
|
||||
94 0.245
|
||||
95 0.253
|
||||
96 0.255
|
||||
97 0.256
|
||||
98 0.265
|
||||
99 0.296
|
||||
@@ -0,0 +1,101 @@
|
||||
# Percentage served Time in ms
|
||||
0 2.764
|
||||
1 2.826
|
||||
2 2.836
|
||||
3 2.844
|
||||
4 2.849
|
||||
5 2.853
|
||||
6 2.857
|
||||
7 2.860
|
||||
8 2.863
|
||||
9 2.867
|
||||
10 2.869
|
||||
11 2.870
|
||||
12 2.872
|
||||
13 2.875
|
||||
14 2.876
|
||||
15 2.877
|
||||
16 2.879
|
||||
17 2.881
|
||||
18 2.883
|
||||
19 2.886
|
||||
20 2.888
|
||||
21 2.889
|
||||
22 2.890
|
||||
23 2.892
|
||||
24 2.894
|
||||
25 2.897
|
||||
26 2.899
|
||||
27 2.900
|
||||
28 2.901
|
||||
29 2.902
|
||||
30 2.906
|
||||
31 2.906
|
||||
32 2.908
|
||||
33 2.911
|
||||
34 2.913
|
||||
35 2.915
|
||||
36 2.918
|
||||
37 2.920
|
||||
38 2.922
|
||||
39 2.924
|
||||
40 2.927
|
||||
41 2.929
|
||||
42 2.931
|
||||
43 2.934
|
||||
44 2.936
|
||||
45 2.938
|
||||
46 2.939
|
||||
47 2.942
|
||||
48 2.944
|
||||
49 2.945
|
||||
50 2.947
|
||||
51 2.949
|
||||
52 2.951
|
||||
53 2.952
|
||||
54 2.955
|
||||
55 2.957
|
||||
56 2.959
|
||||
57 2.961
|
||||
58 2.963
|
||||
59 2.964
|
||||
60 2.968
|
||||
61 2.970
|
||||
62 2.972
|
||||
63 2.974
|
||||
64 2.975
|
||||
65 2.977
|
||||
66 2.978
|
||||
67 2.980
|
||||
68 2.981
|
||||
69 2.982
|
||||
70 2.984
|
||||
71 2.986
|
||||
72 2.987
|
||||
73 2.990
|
||||
74 2.991
|
||||
75 2.992
|
||||
76 2.994
|
||||
77 2.995
|
||||
78 2.997
|
||||
79 3.000
|
||||
80 3.002
|
||||
81 3.004
|
||||
82 3.005
|
||||
83 3.007
|
||||
84 3.009
|
||||
85 3.011
|
||||
86 3.013
|
||||
87 3.015
|
||||
88 3.017
|
||||
89 3.020
|
||||
90 3.023
|
||||
91 3.024
|
||||
92 3.028
|
||||
93 3.029
|
||||
94 3.035
|
||||
95 3.039
|
||||
96 3.042
|
||||
97 3.048
|
||||
98 3.054
|
||||
99 3.084
|
||||
@@ -0,0 +1,101 @@
|
||||
# Percentage served Time in ms
|
||||
0 2.292
|
||||
1 2.325
|
||||
2 2.345
|
||||
3 2.365
|
||||
4 2.373
|
||||
5 2.380
|
||||
6 2.385
|
||||
7 2.391
|
||||
8 2.393
|
||||
9 2.395
|
||||
10 2.397
|
||||
11 2.399
|
||||
12 2.402
|
||||
13 2.404
|
||||
14 2.408
|
||||
15 2.411
|
||||
16 2.412
|
||||
17 2.415
|
||||
18 2.417
|
||||
19 2.419
|
||||
20 2.423
|
||||
21 2.425
|
||||
22 2.426
|
||||
23 2.428
|
||||
24 2.430
|
||||
25 2.432
|
||||
26 2.433
|
||||
27 2.434
|
||||
28 2.437
|
||||
29 2.438
|
||||
30 2.439
|
||||
31 2.441
|
||||
32 2.442
|
||||
33 2.443
|
||||
34 2.445
|
||||
35 2.447
|
||||
36 2.449
|
||||
37 2.452
|
||||
38 2.454
|
||||
39 2.455
|
||||
40 2.457
|
||||
41 2.459
|
||||
42 2.461
|
||||
43 2.464
|
||||
44 2.465
|
||||
45 2.467
|
||||
46 2.471
|
||||
47 2.473
|
||||
48 2.477
|
||||
49 2.480
|
||||
50 2.481
|
||||
51 2.484
|
||||
52 2.486
|
||||
53 2.489
|
||||
54 2.490
|
||||
55 2.493
|
||||
56 2.495
|
||||
57 2.498
|
||||
58 2.500
|
||||
59 2.501
|
||||
60 2.504
|
||||
61 2.506
|
||||
62 2.508
|
||||
63 2.510
|
||||
64 2.512
|
||||
65 2.516
|
||||
66 2.518
|
||||
67 2.520
|
||||
68 2.522
|
||||
69 2.523
|
||||
70 2.525
|
||||
71 2.527
|
||||
72 2.528
|
||||
73 2.529
|
||||
74 2.531
|
||||
75 2.534
|
||||
76 2.535
|
||||
77 2.537
|
||||
78 2.538
|
||||
79 2.540
|
||||
80 2.542
|
||||
81 2.544
|
||||
82 2.547
|
||||
83 2.549
|
||||
84 2.551
|
||||
85 2.553
|
||||
86 2.556
|
||||
87 2.558
|
||||
88 2.561
|
||||
89 2.563
|
||||
90 2.566
|
||||
91 2.569
|
||||
92 2.572
|
||||
93 2.575
|
||||
94 2.579
|
||||
95 2.584
|
||||
96 2.589
|
||||
97 2.598
|
||||
98 2.605
|
||||
99 2.629
|
||||
@@ -0,0 +1,101 @@
|
||||
# Percentage served Time in ms
|
||||
0 2.768
|
||||
1 2.797
|
||||
2 2.808
|
||||
3 2.814
|
||||
4 2.818
|
||||
5 2.822
|
||||
6 2.824
|
||||
7 2.826
|
||||
8 2.828
|
||||
9 2.830
|
||||
10 2.832
|
||||
11 2.834
|
||||
12 2.836
|
||||
13 2.840
|
||||
14 2.843
|
||||
15 2.845
|
||||
16 2.847
|
||||
17 2.849
|
||||
18 2.852
|
||||
19 2.853
|
||||
20 2.855
|
||||
21 2.856
|
||||
22 2.857
|
||||
23 2.859
|
||||
24 2.861
|
||||
25 2.862
|
||||
26 2.865
|
||||
27 2.866
|
||||
28 2.867
|
||||
29 2.868
|
||||
30 2.870
|
||||
31 2.872
|
||||
32 2.875
|
||||
33 2.876
|
||||
34 2.879
|
||||
35 2.880
|
||||
36 2.883
|
||||
37 2.885
|
||||
38 2.887
|
||||
39 2.889
|
||||
40 2.891
|
||||
41 2.893
|
||||
42 2.896
|
||||
43 2.899
|
||||
44 2.903
|
||||
45 2.905
|
||||
46 2.907
|
||||
47 2.909
|
||||
48 2.911
|
||||
49 2.914
|
||||
50 2.916
|
||||
51 2.917
|
||||
52 2.919
|
||||
53 2.921
|
||||
54 2.922
|
||||
55 2.924
|
||||
56 2.926
|
||||
57 2.928
|
||||
58 2.930
|
||||
59 2.932
|
||||
60 2.934
|
||||
61 2.936
|
||||
62 2.938
|
||||
63 2.941
|
||||
64 2.942
|
||||
65 2.944
|
||||
66 2.944
|
||||
67 2.946
|
||||
68 2.948
|
||||
69 2.949
|
||||
70 2.951
|
||||
71 2.953
|
||||
72 2.955
|
||||
73 2.956
|
||||
74 2.959
|
||||
75 2.960
|
||||
76 2.962
|
||||
77 2.963
|
||||
78 2.964
|
||||
79 2.966
|
||||
80 2.968
|
||||
81 2.971
|
||||
82 2.973
|
||||
83 2.975
|
||||
84 2.977
|
||||
85 2.979
|
||||
86 2.982
|
||||
87 2.984
|
||||
88 2.986
|
||||
89 2.989
|
||||
90 2.990
|
||||
91 2.993
|
||||
92 2.996
|
||||
93 2.999
|
||||
94 3.005
|
||||
95 3.010
|
||||
96 3.016
|
||||
97 3.026
|
||||
98 3.035
|
||||
99 3.063
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<aside>
|
||||
Update the included sample file and correct the documentation regarding its compilation.
|
||||
Fix noted by Jan Schreiber — thanks!
|
||||
While doing so, add some documentation bits to the manpages.
|
||||
While doing so, add some documentation bits to the manpages and considerable documentation to the webpage.
|
||||
No functional changes.
|
||||
</aside>
|
||||
</article>
|
||||
Reference in new issue
Block a user