源码学习:web server althttpd

8 篇文章 0 订阅
7 篇文章 0 订阅

源码路径:

https://sqlite.org/althttpd/dir?ci=tip (推荐)
https://github.com/jesrui/althttpd/tree/master (旧版本)

althttpd.md文件翻译

https://sqlite.org/althttpd/file?name=althttpd.md&ci=tip

althttpd简介:

轻量级 web 服务器,设计宗旨是追求简洁、安全和低资源占用。

设计理念 Design Philosophy

设计理念
Althttpd通常是通过xinetd、systemd或类似的工具启动的。针对每个传入的连接启动一个单独的进程,该进程完全专注于为该连接提供服务。单个althttpd进程将处理同一连接上的一个或多个HTTP请求。当连接关闭时,althttpd进程退出。

Althttpd还可以独立运行。Althttpd本身监听端口80以接收传入的HTTP请求(或端口443以接收传入的HTTPS请求),然后复制自身以处理每个传入的连接。每个连接仍然使用单独的进程处理。唯一的区别是连接处理程序进程现在是由主althttpd实例启动的,而不是由xinetd或systemd启动。

Althttpd没有配置文件。所有配置都使用一些命令行参数处理。这有助于保持配置的简单性,并减轻了通过错误配置的Web服务器引入安全漏洞的担忧。

由于每个althttpd进程只需为单个连接提供服务,althttpd是单线程的。此外,每个进程的生命周期仅为单个连接的持续时间,这意味着althttpd不需要过多担心内存泄漏。这些设计因素有助于保持althttpd源代码的简洁性,便于进行安全审计和分析。

用于服务TLS连接有两个选项:

  1. 可以使用定义了ENABLE_TLS宏并链接到-lssl -lcrypto的althttpd构建,并使用–cert fullchain.pem和–pkey privkey.pem标志启动。
  2. 可以通过外部连接服务(如stunnel4)启动althttpd,向althttpd传递-https 1标志,告诉它通过该服务“间接”以HTTPS模式运行。
    首选第一种选项(使用内置TLS)。

TLS:传输层安全性协议(TLS),是一种网络协议,用于保护在两个通信应用程序之间传输的数据的安全性和隐私性。TLS的前身是安全套接层协议(SSL)。TLS和SSL之间存在一些技术差异,但它们的基本目标相同。
TLS的主要功能包括:数据加密、身份验证、数据完整性。
HTTPS(超文本传输安全协议)是在HTTP协议上使用TLS的变体,用于加密和保护Web页面的数据传输。

源代码

althttpd的完整源代码包含在一个单独的C代码文件中,没有依赖于标准C库之外的任何内容,只有在选择ENABLE_TLS选项时才需要OpenSSL。源代码文件命名为"althttpd.c"。要构建和安装althttpd,请运行以下命令:

gcc -Os -o /usr/bin/althttpd althttpd.c

-Os 用于指定编译器优化代码大小而非执行速度。
althttpd的源代码有详细的注释,并且易于访问。对于特殊需求,定制化应该相对容易。
要使用libssl构建带有内置TLS支持的althttpd:

gcc -Os -o /usr/bin/althttpd -fPIC -DENABLE_TLS althttpd.c -lssl -lcrypto

SQLite网站使用静态构建,因此在服务器上不需要安装OpenSSL。

创建使用Xinetd的设置

以下是sqlite.org上/etc/xinetd.d/http文件的完整内容,配置althttpd以在IPv4和IPv6上提供未加密的HTTP请求。您可以将其用作创建自己安装的模板。

如果您更愿意使用systemd,有一个附近的单独教程介绍如何执行该操作。但是,即使只是为了更好地了解althttpd的期望,您仍然可以查看此处的说明。

service http
{
  port = 80
  flags = IPv4
  socket_type = stream
  wait = no
  user = root
  server = /usr/bin/althttpd
  server_args = -logfile /logs/http.log -root /home/www -user www-data
  bind = 45.33.6.223
}
service http
{
  port = 80
  flags = REUSE IPv6
  bind = 2600:3c00::f03c:91ff:fe96:b959
  socket_type = stream
  wait = no
  user = root
  server = /usr/bin/althttpd
  server_args = -logfile /logs/http.log -root /home/www -user www-data
}

关键观察点在于端口80上的每个传入TCP/IP连接都会启动一个/usr/bin/althttpd的副本,并带有一些额外的参数,这些参数等于web服务器的配置。

请注意,althttpd作为超级用户运行。这不是必需的,但如果这样做,然后althttpd将自身移动到Web文档层次结构的根(例如,示例中的/home/www)的chroot监狱中,然后在读取任何内容之前放弃所有超级用户权限。-user选项告诉althttpd在进入chroot监狱后变为用户www-data。

-root选项(始终应为绝对路径)告诉althttpd在哪里找到文档层次结构。在sqlite.org的情况下,所有内容都是从/home/www提供的。在此文档层次结构的顶级是一堆以".website"结尾的目录。每个这样的目录都是一个单独的网站。目录的选择基于传入HTTP请求的Host参数。sqlite.org的目录部分列表如下:

3dcanvas_tcl_lang_org.website
3dcanvas_tcl_tk.website
androwish_org.website
canvas3d_tcl_lang_org.website
canvas3d_tcl_tk.website
cvstrac_org.website
default.website
fossil_scm_com.website
fossil_scm_hwaci_com.website
fossil_scm_org.website
system_data_sqlite_org.website
wapp_tcl_lang_org.website
wapp_tcl_tk.website
www2_alt_mail_net.website
www_androwish_org.website
www_cvstrac_org.website
www_fossil_scm_com.website
www_fossil_scm_org.website
www_sqlite_org.website

对于每个传入的HTTP请求,althttpd获取请求头中Host参数的文本,将其转换为小写,并将所有ASCII字母数字之外的字符更改为"_"。结果确定用于内容的子目录。如果没有匹配项,则将使用"default.website"目录作为回退。

例如,如果Host参数是"www.SQLite.org",则将其名称转换为"www_sqlite_org.website",并且这是用于提供内容的目录。如果Host参数是"fossil-scm.org",则使用"fossil_scm_org.website"目录。通常,两个或更多名称指向同一个网站。例如,fossil-scm.org、www.fossil-scm.org、fossil-scm.com和www.fossil-scm.com都是同一个网站。在这种情况下,通常只有一个目录是真正的目录,其他目录是符号链接。

在仅托管单个网站的最小安装中,只需有一个名为"default.website"的子目录即可。
在*.website目录内,要提供的文件由HTTP请求URI选择。标记为可执行的文件将作为CGI运行。以".scgi"结尾并且内容形式为"SCGI hostname port"的非可执行文件将SCGI请求中继到hostname:port。所有其他非可执行文件都按原样提供。

如果请求URI指定了*.website内部的目录名称,则althttpd会附加"/home"、“/index.html"和”/index.cgi",按顺序查找匹配项。

如果URI的前缀与可执行文件的名称相匹配,那么该文件将作为CGI运行。对于按原样传递的内容,MIME类型是通过使用编译到althttpd中的表从文件扩展名推断出来的。

支持使用Xinetd的HTTPS

从版本2.0(2022-01-16)开始,althttpd可以选择支持TLS加密连接。使用Xinetd设置HTTPS网站与HTTP网站非常相似。xinetd的适当配置是/etc/xinetd.d目录中名为"https"的单个文件,内容如下:

service https
{
  port = 443
  flags = IPv4
  socket_type = stream
  wait = no
  user = root
  server = /usr/bin/althttpd
  server_args = -logfile /logs/http.log -root /home/www -user www-data -cert /etc/letsencrypt/live/sqlite.org/fullchain.pem -pkey /etc/letsencrypt/live/sqlite.org/privkey.pem
  bind = 45.33.6.223
}
service https
{
  port = 443
  flags = REUSE IPv6
  bind = 2600:3c00::f03c:91ff:fe96:b959
  socket_type = stream
  wait = no
  user = root
  server = /usr/bin/althttpd
  server_args = -logfile /logs/http.log -root /home/www -user www-data -cert /etc/letsencrypt/live/sqlite.org/fullchain.pem -pkey /etc/letsencrypt/live/sqlite.org/privkey.pem
}

当然,您需要调整路径名和IP地址,使其适合您的特定安装。

这个https配置文件与之前的http配置文件相同,只有一些变化:

将服务名称从 “http” 更改为 “https”
将端口号从80更改为443
添加-cert和-pkey选项给althttpd,以便它知道在哪里找到适当的证书和私钥。
创建新的https配置文件后,只需重新启动xinetd(通常使用命令 “/etc/init.d/xinetd restart”),您的现有网站将立即具有HTTPS版本。

设置HTTPS使用Stunnel4

早期版本的althttpd不支持加密。在althttpd上加密网站的推荐方式是使用stunnel4。这些建议现在已经改变。我们现在建议您将althttpd更新到2.0版本或更高版本,并使用前一节中描述的xinetd技术。此部分保留供历史参考。

在sqlite.org网站上,/etc/stunnel/stunnel.conf文件的相关行是:

cert = /etc/letsencrypt/live/sqlite.org/fullchain.pem
key = /etc/letsencrypt/live/sqlite.org/privkey.pem
[https]
accept       = :::443
TIMEOUTclose = 0
exec         = /usr/bin/althttpd
execargs     = /usr/bin/althttpd -logfile /logs/http.log -root /home/www -user www-data -https 1

这个设置与xinetd设置非常相似。一个关键的区别是使用"-https 1"选项告诉althttpd连接是加密的。这很重要,这样althttpd将知道为CGI程序设置HTTPS环境变量。

配置xinetd和stunnel4同时运行althttpd是可以的。事实上,这是SQLite.org网站的运行方式。对http://sqlite.org/的请求经过xinetd,对https://sqlite.org/的请求经过stunnel4。

独立运行

在作者的桌面工作站上,他的主目录下有一个子目录名为~/www/default.website。该子目录包含一组文件和CGI脚本。可以通过运行以下命令让althttpd在那里提供内容:

althttpd -root ~/www -port 8080

"-port 8080"选项告诉althttpd在独立模式下运行,监听端口8080。

althttpd的作者只在测试时使用独立模式。对于基于SSL的生产用途,建议使用内置的TLS支持或stunnel4。

带有HTTPS的独立运行

如果althttpd使用TLS支持构建,则可以使用以下选项告诉它以HTTPS模式运行:

althttpd -root ~/www --port 8043 --cert unsafe-builtin

此选项使用编译的自签名SSL证书,极不安全,仅用于测试目的。使用–cert选项指定自己的PEM格式SSL证书。–cert的参数可以是SSL私钥(通常命名为"privkey.pem")和证书链(通常命名为"fullchain.pem")的串联。或者,–cert可以指向只有fullchain.pem文件,而独立的–pkey选项可以指向privkey.pem文件。
使用自己的证书:

althttpd -root ~/www --port 8043 --cert fullchain.pem --pkey privkey.pem

请注意,证书在althttpd降低根权限之前就会被读取,因此证书可能存在于althttpd进程将在其下运行的非根用户无法访问的地方。

独立模式用于网站开发和测试

如果您在一个目录中有构成网站的各种HTML、JavaScript、CSS和其他资源文件,并且希望轻松测试这些文件,您可以输入以下命令:

althttpd --page index.html

在上述命令中,"index.html"是初始HTML页面的名称。此命令以独立模式启动althttpd,侦听它可以找到的第一个可用端口,并绑定到回环IP地址(127.0.0.1)。它还会自动在您的Web浏览器中打开一个新标签页,并指向"index.html"页面。

如果您在远程系统上开发网站,可以使用以下方式启动:

althttpd --popup

“–popup"选项与”–page"类似,但不限制IP地址为回环地址,并且不尝试启动新的Web浏览器标签页。

安全特性

为了防范恶意行为,althttpd对将提供的文件名称施加了一些限制。在请求URI中,除了字母数字和", -./:~“之外的所有字符都会转换为单个”_“。此外,如果请求URI的任何路径元素以”.“或”-“开头,althttpd都会始终返回404 Not Found错误。因此,只要文件名以”.“或”-"开头,就可以将辅助文件(例如由CGI使用的数据库或其他内容)放在文档层次结构中。

当althttpd返回404时,它会尝试确定请求是否恶意,如果它认为是,则可以选择临时阻止客户端的IP。

一个例外:尽管althttpd通常对任何以".“开头的路径元素的请求返回404 Not Found,但它允许以”/.well-known/“开头的URI请求。”/.well-known/“下面的文件或目录名称允许以”.“或”-“开头(但不允许以”…"开头)。此例外是为了允许LetsEncrypt验证对网站的拥有权。

基本身份验证

如果在内容层次结构的任何位置都存在名为"-auth"的文件,那么所有同级文件和所有低级目录中的文件都需要HTTP基本身份验证,其定义由"-auth"文件的内容确定。“-auth"文件是纯文本和面向行的。空行和以”#"开头的行将被忽略。其他行的含义如下:

  • http-redirect

    如果存在http-redirect行,则会导致所有HTTP请求重定向为HTTPS请求。"-auth"文件按顺序读取和处理,因此在"http-redirect"行下面的行对于http请求永远不会被看到或处理。

  • https-only

    如果存在https-only行,则表示仅允许HTTPS请求。任何HTTP请求都会导致404 Not Found错误。https-only行通常出现在http-redirect行之后。

  • realm NAME

    此形式的单行建立基本身份验证的 “realm”。Web浏览器通常将realm名称显示为要求用户名和密码的对话框的标题。

  • user NAME LOGIN:PASSWORD

    有多个用户行,每个有效用户对应一行。LOGIN:PASSWORD参数定义用户必须键入的用户名和密码以访问网站。密码是明文的 - HTTP基本身份验证不是最安全的身份验证机制。成功登录后,NAME被存储在REMOTE_USER环境变量中,以便可以被CGI脚本访问。NAME和LOGIN通常相同,但也可以不同。

  • anyone

    如果遇到"anyone"行,表示允许任何请求,即使没有提供用户名和密码。这行与"http-redirect"组合使用,可以使所有普通HTTP请求在不要求登录凭据的情况下重定向到HTTPS。

基本身份验证示例

http://www.sqlite.org/ 网站在顶级目录下包含一个"-auth"文件,内容如下:

http-redirect
anyone

这个"-auth"文件导致所有HTTP请求都被重定向到HTTPS,而无需进一步的登录。(试试:访问 http://sqlite.org/ 并验证是否被重定向到 https://sqlite.org/。)

在 https://fossil-scm.org/private/ 存在一个"-auth"文件,内容如下:

realm Access To All Fossil Repositories
http-redirect
user drh drh:xxxxxxxxxxxxxxxx

当然,密码不是一行 “x” 字符。这演示了"-auth"文件的典型用法。对于单个用户,只要用户通过HTTPS而不是HTTP进入,就授予对"private"子目录中内容的访问权限。强烈建议对所有基本身份验证使用 “http-redirect” 行,因为密码包含在请求头中,如果通过HTTP发送请求,可能会被坏人截取和窃取。

日志文件

如果在althttpd命令行上给出了"-logfile"选项,那么对于每个HTTP请求,都会向指定的文件追加一行。日志文件采用RFC4180规定的逗号分隔值(CSV)格式。在源代码中有一条注释解释输出行中每个字段的含义。

日志文件是CSV格式的,因此可以使用类似下面的脚本将其轻松导入到SQLite中进行分析:

CREATE TABLE log(
  date TEXT,             /* 时间戳 */
  ip TEXT,               /* 源IP地址 */
  url TEXT,              /* 请求URI */
  ref TEXT,              /* 引用者 */
  code INT,              /* 结果代码,例如:200,404 */
  nIn INT,               /* 请求中的字节数 */
  nOut INT,              /* 回复中的字节数 */
  t1 INT, t2 INT,        /* 处理时间(用户、系统)毫秒 */
  t3 INT, t4 INT,        /* CGI脚本时间(用户、系统)毫秒 */
  t5 INT,                /* 墙钟时间,毫秒 */
  nreq INT,              /* 此请求的序列号 */
  agent TEXT,            /* 用户代理 */
  user TEXT,             /* 远程用户 */
  n INT,                 /* 在SCRIPT_NAME中的URL字节数 */
  lineno INT             /* 生成日志条目的源代码行 */
);
.mode csv
.import httplog.csv log

在"-logfile"选项上的文件名可能包含strftime()扩展的基于时间的字符。因此,要使每天使用一个新的日志文件,可以使用类似以下的内容:

-logfile /var/logs/althttpd/httplog-%Y%m%d.csv

客户端IP阻止

如果在althttpd中包含了–ipshun DIRECTORY选项,DIRECTORY是从chroot jail内部访问的绝对路径(以"/"开头),并且客户端的IP地址作为该目录中的一个文件出现,那么althttpd可能返回503 Service Unavailable而不是处理请求。

如果文件大小为零字节,则始终返回503。因此,您可以"touch"一个文件,其名称是IP地址,永久封禁该客户端。

如果文件大小为N字节,则如果文件的修改时间在300*N秒之前,则在N秒内返回503。换句话说,每字节文件封禁客户端五分钟。

如果althttpd收到一个将导致404 Not Found的请求,并且在检查REQUEST_URI时该请求看起来很可疑,那么会自动创建封禁文件。例如,任何包含/…/的请求都被视为黑客企图。还检查其他常见的漏洞探测。可能会根据经验增加漏洞探测列表。

封禁文件在5分钟/字节后自动取消链接。

封禁文件最初大小为1字节。但如果封禁到期,然后在5分钟每字节的块文件大小之前到达新请求,则文件将增加一个字节,并且修改时间将被重置。

5分钟的封禁时间可以在构建时通过传递 -DBANISH_TIME=N 进行配置,其中 N 是默认为 300 的秒数。

GZip内容压缩

Althttpd对服务器端内容压缩有基本支持,通常将文件的传输成本减少一半以上。与将压缩库添加到althttpd不同,它依赖于网站开发人员提供压缩和未压缩形式的内容。

在提供文件时,如果客户端支持gzip压缩,并且找到了同名文件加上 .gz 扩展名,就会向客户端提供已gzip压缩的文件,并附带响应头表示已经gzip压缩。对于用户来说,原始请求的文件似乎是压缩的。但实际上,在幕后,提供的是不同的文件。

请注意,此功能仅适用于静态文件,而不适用于CGI。

static-build.md文件翻译

https://sqlite.org/althttpd/file?name=static-build.md&ci=tip

构建独立二进制文件

本文档描述了如何在Linux(或类似的类Unix操作系统)上构建一个完全自包含的、静态链接的 “althttpd” 二进制文件。

所需材料

  1. 从本网站获取althttpd.c源代码文件。
  2. 最新版的OpenSSL发布版的tar包。
  3. 常见的Unix构建工具,如 “make”、“gcc” 等。
  4. OpenSSL构建需要Perl。

构建步骤

  1. 编译静态的OpenSSL库。

    • 解压OpenSSL的tar包。
    • 将顶层目录重命名为 “openssl”。
    • 进入 openssl 目录。
    • 运行以下命令:
      ./config no-ssl3 no-weak-ssl-ciphers no-shared no-threads --openssldir=/usr/lib/ssl
      CFLAGS=-Os make -e
      
    • 在OpenSSL构建过程中,可以边喝茶边等待。
  2. 编译althttpd。

    • 返回到存放althttpd.c源文件的顶层目录。
    • 运行:
      gcc -I./openssl/include -Os -Wall -Wextra -DENABLE_TLS \
        -o althttpd althttpd.c -L./openssl -lssl -lcrypto -ldl
      
    • 上述命令构建了一个二进制文件,该文件静态链接到OpenSSL,但仍然动态链接到系统库。要使二进制文件完全静态,请添加 -static 选项。
    • 在上述gcc命令中,您还可以选择添加 -DALTHTTPD_VERSION=‘…’,引号中的文本是将报告给CGI脚本的althttpd版本号。

使用二进制文件

以上就是所有需要做的。编译后,只需将生成的二进制文件移动到服务器的 /usr/bin 目录即可。

linode-systemd.md文件翻译

https://sqlite.org/althttpd/file?name=linode-systemd.md&ci=tip
使用Althttpd、Linode和Systemd设置网站
以下是我最近(2024年1月16日)在廉价的Linode VPS上使用Alhttpd、Let’s Encrypt和systemd设置网站的笔记。
如果对本文档有建议或错误报告,请在论坛上发表留言。

1.0 创建Linode帐户并启动VPS

在Linode上创建帐户并启动新的VPS。您可以以每月5美元的价格获得它们。在这个示例中,我使用了一个每月12美元的Linode,这可能有点过剩。我可能会在某个时候降级到每月5美元的计划。

我选择了Ubuntu 23.10作为操作系统,因为那是当时最新的Ubuntu版本。使用您更熟悉的其他Linux发行版。

2.0 注册您的域名

我构建的系统使用了我已经拥有的域名的子域,因此我不必购买新的域名。如果不是这种情况,请立即购买域名。如何执行此操作的详细信息超出了本文档的范围,但网络上有许多好的教程。

将您的域名指向Linode名称服务器。根据您要执行的操作,进行所需的任何DNS条目。Linode提供了一个出色且易于使用的界面。

3.0 初始系统配置

以root身份登录到您的VPS。我需要升级和安装新软件,如下所示:

apt update
apt upgrade
apt install letsencrypt

4.0 创建Web服务器用户和主目录

应该已经有一个名为 “www-data” 的用户。(检查 /etc/passwd 文件。)这是我用于Web服务的用户。

我还手动编辑了 /etc/passwd 文件,将用户 www-data 的默认shell从 /usr/bin/nologin 更改为 /bin/bash。这个更改允许我运行 “su www-data” 命令以成为 www-data 用户,同时操作属于该用户的文件。但这一步是完全可选的。

为该用户在 /home/www 创建一个主目录。创建一个名为 /home/www/default.website 的子目录。将子目录的所有者更改为 www-data。命令序列如下:

mkdir -p /home/www/default.website
chown www-data /home/www/default.website

创建一个名为 /home/www/default.website/index.html 的文件,该文件由 www-data 用户可读,并放入一些HTML内容作为占位符。也许像这样:

<h1>Hello, World</h1>
<p>If you can see this, that means the web server is running.</p>

添加任何您想要的其他内容。但是,请注意,具有执行权限位的文件将被视为CGI运行。因此,请确保 *.website 文件夹中的任何文件或子文件夹中的文件都不可执行,除非您确实打算将它们作为CGI运行。

5.0 为althttpd构建二进制文件

我在我的桌面Linux机器上构建了一个静态链接的althttpd二进制文件(使用这些说明),并使用scp将静态二进制文件传输到VPS。在 /usr/bin/ 安装静态二进制文件。

6.0 用于HTTP服务的Systemd设置

首先,您需要启动简单的HTTP(未加密)服务,因为这是从Let’s Encrypt获取证书的先决条件。创建一个文件 /etc/systemd/system/http.socket,内容如下:

[Unit]
Description=HTTP socket

[Socket]
Accept=yes
ListenStream=80
NoDelay=true

[Install]
WantedBy=sockets.target

然后创建另一个文件,命名为 /etc/systemd/system/http@.service,内容如下:

[Unit]
Description=HTTP socket server
After=network-online.target

[Service]
WorkingDirectory=/home/www
ExecStart=/usr/bin/althttpd -root /home/www -

user www-data
StandardInput=socket

[Install]
WantedBy=multi-user.target

文件名中的 “@” 不是拼写错误。由于某种原因,systemd似乎需要它。我不知道细节。
最后,使用以下命令启动新服务:

systemctl daemon-reload
systemctl enable http.socket
systemctl start http.socket

此时,您应该能够将Web浏览器指向VPS的端口80,并看到在步骤4.0末尾安装的占位HTML。您还可以使用以下命令检查服务的状态或关闭服务:

systemctl status http.socket
systemctl stop http.socket

7.0 获取Let’s Encrypt证书

为了使用HTTPS,您需要一个证书。使用类似以下的命令获取证书:

letsencrypt certonly --webroot -w /home/www/default.website -d your-domain.org

当然,将您的实际域名替换为 “your-domain.org”。如果您希望Web服务器为多个域提供服务,可以使用多个 “-d” 选项。有关详细信息,请参阅letsencrypt文档。

我相信此命令设置了域将自动更新证书。您不应该再次运行此命令。如果以后发现我错了,我将回来更正这段文字。

您的证书将在以下文件中找到:

/etc/letsencrypt/live/your-domain.org/fullchain.pem
/etc/letsencrypt/live/your-domain.org/privkey.pem

确保 “privkey.pem” 文件保持安全。这是您的私钥。这是Web服务器用于向陌生人验证您服务器身份的内容。

8.0 激活HTTPS

现在您有了证书,您可以创建额外的systemd配置条目,以在端口443上接收TLS HTTPS请求。首先创建一个名为 /etc/systemd/system/https.socket 的文件,内容如下:

[Unit]
Description=HTTPS socket

[Socket]
Accept=yes
ListenStream=443
NoDelay=true

[Install]
WantedBy=sockets.target

然后创建另一个文件,命名为 /etc/systemd/system/https@.service,内容如下:

[Unit]
Description=HTTPS socket server
After=network-online.target

[Service]
WorkingDirectory=/home/www
ExecStart=/usr/bin/althttpd -root /home/www -user www-data -cert /etc/letsencrypt/live/your-domain.org/fullchain.pem -pkey /etc/letsencrypt/live/your-domain.org/privkey.pem
StandardInput=socket

[Install]
WantedBy=multi-user.target

这两个文件与步骤6.0中为HTTP服务创建的文件非常相似。关键区别:

  • 文件名中说 “https” 而不是 “http”。
  • TCP端口是443而不是80。
  • althttpd 命令有额外的 -cert 和 -pkey 参数来标识您的证书。

创建这两个文件后,运行:

systemctl daemon-reload
systemctl enable https.socket
systemctl start https.socket

执行完这些步骤后,您应该能够使用TLS加密的连接浏览您的网站。

9.0 记录Web流量

如果要记录Web流量(建议),请创建一个名为 /home/www/log 的新目录,并将所有者更改为 www-data。然后编辑步骤6.0和8.0中创建的 *.service 文件,将 “–log /log/http.log” 一词添加到 “ExecStart=…” 行中。

请注意,文件名为 “/log/http.log”,而不是 “/home/www/log/http.log”。这是因为althttpd在执行任何其他操作之前会对 /home/www 目录进行chroot。这是一个安全功能,可防止CGI脚本中的错误危害您的系统。由于chroot,从althttpd的角度来看, “/home/www/log/http.log” 文件实际上将被称为 “/log/http.log”。

在进行这些更改后,运行:

systemctl restart http.socket
systemctl restart https.socket

10.0 其他配置更改

现在,您可以尝试其他配置更改。添加像 “–ipshun /ipshun” 这样的选项是推荐的,以帮助消除恶意爬虫。您还可以在 “/home/www” 下添加特定域的新 “*.website” 文件夹。有关指导和建议,请参阅其他althttpd文档。本教程应足以帮助您入门。

Makefile文件解释

default: althttpd althttpsd  # 构建目标
SHELL = /bin/bash            # 指定shell
VERSION_NUMBER = 2.0
CC=cc                        # c编译器
CFLAGS=-Os -Wall -Wextra

# 检查是否安装了 fossil(版本控制工具),如果安装了,则使用 fossil 更新版本。
manifest:                    # 只是一个标签
	@if which fossil > /dev/null; then \
		fossil update --nosync current; \
	else \
	  echo "fossil binary not found. Version hash/time might be incorrect."
	fi
# 定义一个目标 manifest.uuid,它依赖于 manifest。这个目标的规则是空的
manifest.uuid: manifest

# We do the version-setting CPPFLAGS this way, instead of via
# $(shell...), for the sake of portability with BSD Make. Some hoops
# have to be jumped through to get the escaping "just right," though.
# 读取 manifest.uuid 文件中的版本信息和哈希,并将其写入名为 version 的文件中。
version: Makefile manifest.uuid
	@hash=`cut -c1-12 manifest.uuid`; \
	time=`sed -n 2p manifest | cut -d' ' -f2`; \
	{ echo -n "ALTHTTPD_VERSION=\""; \
		echo '$(VERSION_NUMBER)' "[$$hash] [$$time]\""; \
	} > $@

# 使用从 version 文件中读取的标志进行编译
althttpd:	althttpd.c version
	@flags="`cat version`"; set -x; \
	$(CC) $(CFLAGS) "-D$$flags" -o althttpd althttpd.c

# 相对于althttpd,加了TLS、SSL、crypto、-fPIC
althttpsd:	althttpd.c version
	@flags="`cat version`"; set -x; \
	$(CC) $(CFLAGS) "-D$$flags" -fPIC -o althttpsd -DENABLE_TLS althttpd.c -lssl -lcrypto

# 静态编译,避免运行时依赖问题。
static-althttpd:	althttpd.c version
	@flags="`cat version`"; set -x; \
	$(CC) $(CFLAGS) "-D$$flags" -static -o althttpd althttpd.c

# 静态编译,避免运行时依赖问题。
static-althttpsd:	althttpd.c version
	@flags="`cat version`"; set -x; \
	$(CC) $(CFLAGS) "-D$$flags" -static -fPIC -o althttpsd -DENABLE_TLS althttpd.c -lssl -lcrypto -lpthread -ldl

clean:
	rm -f althttpd althttpsd version

extract-error-numbers.tcl 文件解释

tcl脚本,用于从althttpd.c源文件中提取每个日志条目末尾的错误号。
比较类似python、bash脚本

#!/usr/bin/tclsh # 指定脚本的解释器,Tcl解释器。
# Run this script to extract the error numbers that appear at the end of each log file entry from the source text.
# 运行这个脚本可以从源文本中提取日志条目末尾的错误号。
set in [open althttpd.c r] # 打开名为 althttpd.c 的文件用于读取(r表示只读),文件句柄in。
while {![eof $in]} { # 直到文件结束
  set line [gets $in] # 从文件中读取一行,并将其存储在变量 line 中。
  # 使用正则表达式匹配来检查是否在当前行中存在特定的日志格式。
  # 如果匹配成功,将匹配到的错误号存储在变量 num 中,将日志消息存储在变量 msg 中。
  if {[regexp {(\d+)[^0-9]+/\* LOG: (.*) \*/} $line all num msg]} {
    # 如果匹配成功,打印一个SQL语句,将错误号和消息插入到名为 xref 的表中。
    puts "INSERT INTO xref VALUES($num,'$msg');"
  }
}
close $in # 关闭文件句柄,释放文件资源。

static-ssl.mk 文件解释

用于构建静态链接 OpenSSL 版本的 althttpd 的 Makefile 文件。

# 2022-02-16:
# This makefile is used by the author (drh) to build versions of
# althttpd that are statically linked against OpenSSL.  The resulting
# binaries power sqlite.org, fossil-scm.org, and other machines.
# 说明该 Makefile 由作者(drh)用于构建静态链接 OpenSSL 版本的 althttpd,用于驱动 sqlite.org、fossil-scm.org 等网站。
# This is not a general-purpose makefile.  But perhaps you can adapt it to your specific needs.
#
default: althttpd # 默认目标是构建 althttpd 可执行文件。

VERSION_NUMBER ?= 2.0 # 定义变量
# 定义变量 VERSION_HASH,使用 shell 命令从 manifest.uuid 文件中提取前12个字符。
VERSION_HASH ?= $(shell cut -c1-12 manifest.uuid)
# 定义变量 VERSION_TIME,使用 shell 命令从 manifest 文件的第二行提取时间。
VERSION_TIME ?= $(shell sed -n 2p manifest | cut -d' ' -f2)
# 定义变量 ALTHTTPD_VERSION,包含版本号、哈希和时间。
ALTHTTPD_VERSION ?= "$(VERSION_NUMBER) [$(VERSION_HASH)] $(VERSION_TIME)"
# 在预处理阶段添加一个宏定义,将 ALTHTTPD_VERSION 的值传递给 C 源文件。
CPPFLAGS += -DALTHTTPD_VERSION='$(ALTHTTPD_VERSION)'

# 如果安装了fossil ,则使用 fossil 更新版本。
manifest:
	@if which fossil > /dev/null; then \
		fossil update --nosync current; \
	else \
	  echo "fossil binary not found. Version hash/time might be incorrect."
	fi
manifest.uuid: manifest

# 指定 OpenSSL 的安装路径
OPENSSLDIR = /home/drh/fossil/release-build/compat/openssl
# 指定链接 OpenSSL 库时需要的库选项。
OPENSSLLIB = -L$(OPENSSLDIR) -lssl -lcrypto -ldl
# 启用 TLS 并指定 OpenSSL 头文件路径
CPPFLAGS += -I$(OPENSSLDIR)/include -DENABLE_TLS
CPPFLAGS += -Wall -Wextra
CFLAGS = -Os

althttpd:	althttpd.c manifest
	gcc $(CPPFLAGS) $(CFLAGS) -o althttpd althttpd.c $(OPENSSLLIB)

clean:	
	rm -f althttpd

源码中将ALTHTTPD_VERSION当作字符串使用

#define SERVER_SOFTWARE "althttpd " ALTHTTPD_VERSION
#define SERVER_SOFTWARE_TLS SERVER_SOFTWARE ", " OPENSSL_VERSION_TEXT

static char *zServerSoftware = 0;/* Software name and version info */
puts(SERVER_SOFTWARE_TLS);
zServerSoftware = useHttps==2 ? SERVER_SOFTWARE_TLS : SERVER_SOFTWARE;

althttpd.c 源码分析

althttpd.c 源码注释部分翻译

注释描述了一个小型、简单、独立的 HTTP 服务器的源代码文件。

/*
** 2001-09-15
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
**
** This source code file implements a small, simple, stand-alone HTTP server.
** 小型、简单、独立的 HTTP 服务器
** Features: 特点
**
**     * Launched from inetd/xinetd/systemd, or as a stand-alone server
**     * One process per TCP/IP connection
**     * Deliver static content or run CGI or SCGI
**     * Virtual sites based on the "Host:" property of the HTTP header
**     * Runs in a chroot jail
**     * Unified log file in a CSV format
**     * Small code base (this 1 file) to facilitate security auditing
**     * Simple setup - no configuration files to misconfigure
可以从 inetd/xinetd/systemd 启动,也可以作为独立服务器。
每个 TCP/IP 连接一个进程。
提供静态内容或运行 CGI 或 SCGI。
基于 HTTP 头的 "Host:" 属性的虚拟站点。
在 chroot jail 中运行。
统一的 CSV 格式日志文件。
小型代码库,有助于进行安全审计。
简单设置 - 无需配置文件。

**
** This file implements a small and simple but secure and effective web
** server.  There are no frills.  Anything that could be reasonably
** omitted has been.
   该文件实现了一个小巧简单、安全有效的 Web 服务器。没有多余的装饰,一切可以合理省略的都已省略。
**
** Setup rules: 设置规则
**
**    (1) Launch as root from inetd/systemd like this:
**        作为 root 从 inetd/systemd 启动,使用如下命令:
**            althttpd -logfile logfile -root /home/www -user nobody
**
**        It will automatically chroot to /home/www and become user "nobody".
**        The logfile name should be relative to the chroot jail.
**        它将自动进入 chroot jail,切换到 /home/www 目录,并成为用户 "nobody"。
          日志文件名应相对于 chroot jail。
**    (2) Directories of the form "*.website" (ex: www_sqlite_org.website)
**        contain content.  The directory is chosen based on the HTTP_HOST
**        request header.  If there is no HTTP_HOST header or if the
**        corresponding host directory does not exist, then the
**        "default.website" is used.
          形如 "*.website" 的目录(例如:www_sqlite_org.website)包含内容。
          目录的选择基于 HTTP_HOST 请求头。如果没有 HTTP_HOST 请求头,
          或者对应的主机目录不存在,则使用 "default.website"。
**
**        In stand-alone mode (when the --port, --page, or --popup options are
**        used) if neither the HTTP_HOST.website nor "default.website"
**        directories exist, then files are served directly from the 
**        directory from which althttpd was launched.  This makes it easy
**        to run a quick web-server for testing purposes using commands like:
**        在独立模式下(当使用 --port、--page 或 --popup 选项时),
          如果既不存在 HTTP_HOST.website 目录,也不存在 "default.website" 目录,
          那么文件将直接从启动 althttpd 的目录提供服务。
          这样可以轻松地通过诸如以下命令运行快速的用于测试目的的 Web 服务器:
**            althttpd --page fiddle.html
**            althttpd --popup
**            althttpd --port 8080..8090
**
**        In one-connection mode (when launched from xinetd or similar)
**        an error is raised if "default.website" does not exist.
          在单连接模式下(从 xinetd 或类似的工具启动时),如果 "default.website" 不存在,将引发错误。
**
**        If the HTTP_HOST header contains any charaters other than
**        [a-zA-Z0-9_.,*~/] then a 403 error is generated.
          如果 HTTP_HOST 请求头包含除 [a-zA-Z0-9_.,*~/] 之外的任何字符,将生成 403 错误。
**
**    (3) Any file or directory whose name begins with "." or "-" is ignored,
**        except if the URL begins with "/.well-known/" then initial "." and
**        "-" characters are allowed, but not initial "..".  The exception is
**        for RFC-5785 to allow letsencrypt or certbot to generate a TLS cert
**        using webroot.
          任何文件或目录名以 "." 或 "-" 开头的都会被忽略,但如果 URL 以 "/.well-known/" 开头,
          则允许使用初始的 "." 和 "-" 字符,但不允许使用初始的 ".."。
          这是为了 RFC-5785 的例外,以允许 letsencrypt 或 certbot 使用 webroot 生成 TLS 证书。
**
**    (4) Characters other than [0-9a-zA-Z,-./:_~] and any %HH characters
**        escapes in the filename are all translated into "_".  This is
**        a defense against cross-site scripting attacks and other mischief.
          文件名中除了 [0-9a-zA-Z,-./:_~] 和任何 %HH 字符转义之外的字符都会被翻译成 "_”。
          这是为了防范跨站脚本攻击和其他恶意行为。
**
**    (5) Executable files are run as CGI.  Files whose name ends with ".scgi"
**        trigger an SCGI request (see item 9 below).  All other files
**        are delivered as is.
          可执行文件会被作为 CGI 运行。
          文件名以 ".scgi" 结尾的会触发一个 SCGI 请求(参见下面的第9项)。
          所有其他文件将按原样传送。
**
**    (6) If a file named "-auth" exists in the same directory as the file to
**        be run as CGI/SCGI or to be delivered, then it contains information
**        for HTTP Basic authorization.  See file format details below.
          如果与将要作为 CGI/SCGI 运行或传送的文件位于同一目录中存在一个名为 "-auth" 的文件,
          那么它包含了 HTTP 基本授权的信息。请参阅下面的文件格式详细信息。
**
**    (7) To run as a stand-alone server, simply add the "-port N" command-line
**        option to define which TCP port to listen on.  If the argument is
**        "--port N1..N2" then TCP ports between N1 and N2 are scanned looking
**        for one that is open and the first open port is used.
          要作为独立服务器运行,只需添加 "-port N" 命令行选项来定义要监听的 TCP 端口。
          如果参数是 "--port N1..N2",则会扫描 N1 和 N2 之间的 TCP 端口,找到第一个开放的端口并使用。
**
**    (8) For static content, the mimetype is determined by the file suffix
**        using a table built into the source code below.  If you have
**        unusual content files, you might need to extend this table.
          对于静态内容,文件的 MIME 类型由下面源代码中内置的表格来确定,它使用文件后缀。
          如果你有不寻常的内容文件,可能需要扩展这个表格。
**
**    (9) Content files that end with ".scgi" and that contain text of the
**        form "SCGI hostname port" will format an SCGI request and send it
**        to hostname:port, then relay back the reply.  Error behavior is
**        determined by subsequent lines of the .scgi file.  See SCGI below
**        for details.
          以 ".scgi" 结尾的内容文件,如果包含形如 "SCGI hostname port" 的文本,
          将格式化一个 SCGI 请求并将其发送到 hostname:port,然后中继回响应。
          错误行为由 .scgi 文件的后续行决定。有关详细信息,请参阅下面的 SCGI 部分。
**
**   (10) If compiled with -DENABLE_TLS and linked against OpenSSL and
**        launched with a --cert option to identify a certificate file, then
**        TLS is used to encrypt the connection.
          如果使用 -DENABLE_TLS 进行编译,并链接到 OpenSSL,
          并使用 --cert 选项启动以确定证书文件,则会使用 TLS 加密连接。
**
**   (11) When requesting static file NAME and if the HTTP request includes
**        "Accept-Encoding: gzip" and if file "NAME.gz" exists on disk, then
**        return NAME.gz with an "Content-encoding: gzip" header
          在请求静态文件 NAME 时,如果 HTTP 请求包含 "Accept-Encoding: gzip",
          并且磁盘上存在文件 "NAME.gz",那么返回 NAME.gz 并带有 "Content-Encoding: gzip" 头。
**
** Command-line Options:命令行选项
**
**  --root DIR       Defines the directory that contains the various
**                   $HOST.website subdirectories, each containing web content
**                   for a single virtual host.  If launched as root and if
**                   "--user USER" also appears on the command-line and if
**                   "--jail 0" is omitted, then the process runs in a chroot
**                   jail rooted at this directory and under the userid USER.
**                   This option is required for xinetd launch but defaults
**                   to "." for a stand-alone web server. DIR should always
**                   be an absolute path, else child processes might misbehave.
      定义包含各种 $HOST.website 子目录的目录,每个子目录包含单个虚拟主机的 Web 内容。
**
**  --port N         Run in standalone mode listening on TCP port N, or from
**  --port N1..N2    the first available TCP port in the range from N1 to N2.
      以独立模式运行,监听 TCP 端口 N 或 N1 到 N2 范围内的第一个可用端口。
**
**  --user USER      Define the user under which the process should run if
**                   originally launched as root.  This process will refuse to
**                   run as root (for security).  If this option is omitted and
**                   the process is launched as root, it will abort without
**                   processing any HTTP requests.
      定义进程应以哪个用户身份运行。
**
**  --logfile FILE   Append a single-line, CSV-format, log file entry to FILE
**                   for each HTTP request.  FILE should be a full pathname.
**                   The FILE name is interpreted inside the chroot jail.  The
**                   FILE name is expanded using strftime() if it contains
**                   at least one '%' and is not too long.
        为每个 HTTP 请求在 FILE 中追加一行 CSV 格式的日志条目。
**
**  --ipshun DIR     If the remote IP address is also the name of a file
**                   in DIR that has size N bytes and where either N is zero
**                   or the m-time of the file is less than N time-units ago
**                   then that IP address is being shunned and no requests
**                   are processed.  The time-unit is a compile-time option
**                   (BANISH_TIME) that defaults to 300 seconds.  If this
**                   happens, the client gets a 503 Service Unavailable
**                   reply. Furthermore, althttpd will create ip-shunning
**                   files following a 404 Not Found error if the request
**                   URI is an obvious hack attempt.  The ip-shunning file
**                   will also be created if a CGI returns status code 418.
       如果远程 IP 地址在 DIR 中的文件名中,该文件大小为 N 字节,
       且 N 为零或文件的修改时间少于 N 时间单位,则该 IP 地址被屏蔽。
**
**  --https BOOLEAN  Indicates that input is coming over SSL and is being
**                   decoded upstream, perhaps by stunnel. This option
**                   does *not* activate built-in TLS support.  Use --cert
**                   for that.
         指示输入是否通过 SSL,但不激活内置 TLS 支持。
**
**  --page NAME      Come up in stand-alone mode, and then try to launch a
**                   web-browser pointing to the NAME document after the
**                   listening socket has been created.  This option
**                   implies --loopback and "--port 8080..8100".
      在独立模式下启动,并尝试在创建监听套接字后启动一个指向 NAME 文档的 Web 浏览器。
**
**  --popup          Launch a stand-alone web server to use for testing.
**                   This option implies "--port 8080..8100".  This option
**                   is similar to "--page NAME" except that it does not
**                   try to launch a web-browser and does not force the
**                   connection into --loopback mode.  Use this when
**                   running a test web-server on a remote host via ssh.
      启动一个用于测试的独立 Web 服务器。
**
**  --loopback       Only accept loop-back TCP connections (connections
**                   originating from the same host).  This is the
**                   default if --root is omitted.
       仅接受回环 TCP 连接。
**
**  --family ipv4    Only accept input from IPV4 or IPV6, respectively.
**  --family ipv6    These options are only meaningful if althttpd is run
**                   as a stand-alone server.
       仅接受 IPV4 或 IPV6 输入。
**
**  --jail BOOLEAN   Indicates whether or not to form a chroot jail if
**                   initially run as root.  The default is true, so the only
**                   useful variant of this option is "--jail 0" which prevents
**                   the formation of the chroot jail.
       指示是否形成 chroot jail。
**
**  --max-age SEC    The value for "Cache-Control: max-age=%d".  Defaults to
**                   120 seconds.
        "Cache-Control: max-age=%d" 的值,默认为 120 秒。
**
**  --max-cpu SEC    Maximum number of seconds of CPU time allowed per
**                   HTTP connection.  Default 30 (build option:
**                   -DMAX_CPU=integer). 0 means no limit.
         每个 HTTP 连接允许的最大 CPU 时间。
**
**  --debug BOOLEAN  Disables input timeouts.  This is useful for debugging
**                   when inputs are being typed in manually.
        禁用输入超时,用于调试。
**
**  --enable-sab     Add new lines to the HTTP reply header that are
**                   prerequisites for SharedArrayBuffer.  These are the lines:
**                     Cross-Origin-Embedder-Policy: require-corp
**                     Cross-Origin-Opener-Policy: same-origin
          为 SharedArrayBuffer 添加新行到 HTTP 回复头。
**
**
** Additional command-line options available when compiling with ENABLE_TLS:
**
**  --cert FILE      The TLS certificate, the "fullchain.pem" file
**
**  --pkey FILE      The TLS private key, the "privkey.pem" file.  May be
**                   omitted if the --cert file is the concatenation of
**                   the fullchain.pem and the privkey.pem.
**
**
** Command-line options can take either one or two initial "-" characters.
** So "--debug" and "-debug" mean the same thing, for example.
**
**
** Security Features: 安全特性
**
** (1)  This program automatically puts itself inside a chroot jail if
**      it can and if not specifically prohibited by the "--jail 0"
**      command-line option.  The root of the jail is the directory that
**      contains the various $HOST.website content subdirectories.
         自动将自身置于 chroot jail 中。
**
** (2)  No input is read while this process has root privileges.  Root
**      privileges are dropped prior to reading any input (but after entering
**      the chroot jail, of course).  If root privileges cannot be dropped
**      (for example because the --user command-line option was omitted or
**      because the user specified by the --user option does not exist),
**      then the process aborts with an error prior to reading any input.
         在具有 root 特权时不读取任何输入。
**
** (3)  The length of an HTTP request is limited to MAX_CONTENT_LENGTH bytes
**      (default: 250 million).  Any HTTP request longer than this fails
**      with an error. (Build option: -DMAX_CONTENT_LENGTH=integer)
        限制 HTTP 请求长度。
**
** (4)  There are hard-coded time-outs on each HTTP request.  If this process
**      waits longer than the timeout for the complete request, or for CGI
**      to finish running, then this process aborts.  (The timeout feature
**      can be disabled using the --debug command-line option.)
        对每个 HTTP 请求设置硬编码的超时。
**
** (5)  If the HTTP_HOST request header contains characters other than
**      [0-9a-zA-Z,-./:_~] then the entire request is rejected.
        针对 HTTP_HOST 请求头进行字符检查。
**
** (6)  Any characters in the URI pathname other than [0-9a-zA-Z,-./:_~]
**      are converted into "_".  This applies to the pathname only, not
**      to the query parameters or fragment.
        转换 URI 路径中的特殊字符。
**
** (7)  If the first character of any URI pathname component is "." or "-"
**      then a 404 Not Found reply is generated.  This prevents attacks
**      such as including ".." or "." directory elements in the pathname
**      and allows placing files and directories in the content subdirectory
**      that are invisible to all HTTP requests, by making the first
**      character of the file or subdirectory name "-" or ".".
        防止 URI 路径以 "." 或 "-" 开头。
**
** (8)  The request URI must begin with "/" or else a 404 error is generated.
        请求 URI 必须以 "/" 开头。
**
** (9)  This program never sets the value of an environment variable to a
**      string that begins with "() {".
**
** (10) If the --ipshun option is used, specific IP addresses can be
**      temporarily block for abusive behavior.
        不设置环境变量值以 "(){..." 开头。
**
** (11) If a CGI reports status code 418 ("I'm a teapot", rfc2324) and if
**      the --ipshun option is used, then the IP address is temporarily
**      blocked.
        使用 --ipshun 选项可临时阻止特定 IP 地址。
**
** Security Auditing:  安全审计
**
** This webserver mostly only serves static content.  Any security risk will
** come from CGI and SCGI.  To check an installation for security, then, it
** makes sense to focus on the CGI and SCGI scripts.
   此 Web 服务器主要用于提供静态内容。任何安全风险都将来自 CGI 和 SCGI。
   因此,要检查安装的安全性,重点应放在 CGI 和 SCGI 脚本上。
**
** To locate all CGI files: 查找所有 CGI 文件的方法:
**
**          find *.website -executable -type f -print
**     OR:  find *.website -perm +0111 -type f -print
**
** The first form of the "find" command is preferred, but is only supported
** by GNU find.  On a Mac, you'll have to use the second form.
   "find" 命令的第一种形式更受欢迎,但仅受 GNU find 支持。在 Mac 上,您必须使用第二种形式。
**
** To find all SCGI files: 查找所有 SCGI 文件的方法:
**
**          find *.website -name '*.scgi' -type f -print
**
** If any file is a security concern, it can be disabled on a live
** installation by turning off read permissions:
   如果任何文件引起安全担忧,可以通过关闭读取权限在生产环境中禁用该文件:
**
**          chmod 0000 file-of-concern
**
** SCGI Specification Files: SCGI 规范文件:
**
** Content files (files without the execute bit set) that end with ".scgi"
** specify a connection to an SCGI server.  The format of the .scgi file
** follows this template:
    不包含执行位的内容文件(以 ".scgi" 结尾的文件)指定与 SCGI 服务器的连接。 .scgi 文件的格式如下:
**
**      SCGI hostname port
**      fallback: fallback-filename
**      relight: relight-command
**
** The first line specifies the location and TCP/IP port of the SCGI
** server that will handle the request.  Subsequent lines determine
** what to do if the SCGI server cannot be contacted.  If the
** "relight:" line is present, then the relight-command is run using
** system() and the connection is retried after a 1-second delay.  Use
** "&" at the end of the relight-command to run it in the background.
** Make sure the relight-command does not generate output, or that
** output will become part of the SCGI reply.  Add a ">/dev/null"
** suffix (before the "&") to the relight-command if necessary to
** suppress output.  If there is no relight-command, or if the relight
** is attempted but the SCGI server still cannot be contacted, then
** the content of the fallback-filename file is returned as a
** substitute for the SCGI request.  The mimetype is determined by the
** suffix on the fallback-filename.  The fallback-filename would
** typically be an error message indicating that the service is
** temporarily unavailable.
    第一行指定 SCGI 服务器的位置和 TCP/IP 端口,随后的行确定如果无法联系 SCGI 服务器将采取什么措施。如果存在 "relight:" 行,则使用 system() 运行 relight-command,并在 1 秒延迟后重试连接。在 relight-command 的末尾使用 "&" 以在后台运行它。确保 relight-command 不会生成输出,否则输出将成为 SCGI 回复的一部分。如果没有 relight-command,或者尝试 relight 但 SCGI 服务器仍无法联系,则将 fallback-filename 文件的内容作为 SCGI 请求的替代返回。 mimetype 由 fallback-filename 的后缀确定。 fallback-filename 通常是指示服务暂时不可用的错误消息。
**
** Basic Authorization: 基本授权
**
** If the file "-auth" exists in the same directory as the content file
** (for both static content and CGI) then it contains the information used
** for basic authorization.  The file format is as follows:
   如果文件 "-auth" 存在于与内容文件(对于静态内容和 CGI 文件都是如此)相同的目录中,则其中包含用于基本授权的信息。文件格式如下:
**
**    *  Blank lines and lines that begin with '#' are ignored
**    *  "http-redirect" forces a redirect to HTTPS if not there already
**    *  "https-only" disallows operation in HTTP
**    *  "user NAME LOGIN:PASSWORD" checks to see if LOGIN:PASSWORD
**       authorization credentials are provided, and if so sets the
**       REMOTE_USER to NAME.
**    *  "realm TEXT" sets the realm to TEXT.
      空白行和以 '#' 开头的行将被忽略。
      "http-redirect" 强制将请求重定向到 HTTPS(如果尚未进行)。
      "https-only" 不允许在 HTTP 中操作。
      "user NAME LOGIN:PASSWORD" 检查是否提供了 LOGIN:PASSWORD 授权凭据,如果是,则将 REMOTE_USER 设置为 NAME。
      "realm TEXT" 将 realm 设置为 TEXT。
**
** There can be multiple "user" lines.  
   If no "user" line matches, the request fails with a 401 error.
   可以有多个 "user" 行。如果没有匹配的 "user" 行,则请求将以 401 错误失败。
** Because of security rule (7), there is no way for the content of the "-auth"
** file to leak out via HTTP request.
   由于安全规则(7),不存在通过 HTTP 请求泄漏 "-auth" 文件内容的途径。
*/

头文件部分

#include <stdio.h> //printf、scanf、fopen ...
#include <ctype.h> //isalpha、isdigit
#include <syslog.h> //向系统日志提交信息
#include <stdlib.h> //内存分配和释放的函数,随机数生成
#include <sys/stat.h> //文件状态信息获取函数,如 stat、chmod
#include <unistd.h> //UNIX系统调用,如 fork、pipe、read、write
#include <fcntl.h> //文件描述符进行操作的函数,如 open、fcntl
#include <string.h> //字符串操作函数,如 strcpy、strcat、strlen
#include <pwd.h> //对用户账户信息的访问,如 getpwnam、getpwuid
#include <sys/time.h> //对时间和时间间隔的处理函数,如 gettimeofday
#include <sys/types.h> //系统调用和库函数使用的基本数据类型
#include <sys/resource.h> //对进程资源限制的控制函数,如 getrlimit、setrlimit
#include <sys/socket.h> //套接字编程的函数,如 socket、bind、listen
#include <sys/wait.h> //进程等待和状态获取函数,如 wait、waitpid
#include <netinet/in.h> //与网络相关的结构和常量,如 struct sockaddr_in
#include <arpa/inet.h> //网络地址转换函数,如 inet_pton、inet_ntop
#include <stdarg.h> //可变数量参数的函数,如 va_start、va_arg
#include <time.h> //时间和日期函数,如 time、strftime
#include <sys/times.h> //处理进程时间的函数,如 times
#include <netdb.h> //网络数据库(如 /etc/hosts)查询函数,如 gethostbyname
#include <errno.h> //全局变量 errno,用于存储最近一次发生的错误码
#include <sys/resource.h> //
#include <signal.h> //信号处理函数,如 signal、kill
#include <dirent.h> //目录操作函数,如 opendir、readdir
#ifdef linux
#include <sys/sendfile.h> //高效的文件拷贝函数 sendfile
#endif
#include <assert.h> //提供 assert 宏,用于检查程序运行时的条件

常量部分

/*
** Configure the server by setting the following macros and recompiling.
   通过设置以下宏并重新编译来配置服务器。
*/
#ifndef DEFAULT_PORT
#define DEFAULT_PORT "80"             /* Default TCP port for HTTP ,HTTP 的默认 TCP 端口 */
#endif
#ifndef MAX_CONTENT_LENGTH
#define MAX_CONTENT_LENGTH 250000000  /* Max length of HTTP request content,HTTP 请求内容的最大长度 */
#endif
#ifndef MAX_CPU
#define MAX_CPU 30                    /* Max CPU cycles in seconds,CPU 循环的最大秒数 */
#endif

#ifndef ALTHTTPD_VERSION
#define ALTHTTPD_VERSION "2.0"
#endif

#ifndef BANISH_TIME
#define BANISH_TIME 300               /* How long to banish for abuse (sec) ,封禁滥用的时长(秒)*/
#endif

#ifndef SERVER_SOFTWARE
#  define SERVER_SOFTWARE "althttpd " ALTHTTPD_VERSION
#endif
#ifndef SERVER_SOFTWARE_TLS
#  ifdef ENABLE_TLS
#    define SERVER_SOFTWARE_TLS SERVER_SOFTWARE ", " OPENSSL_VERSION_TEXT
#  else
#    define SERVER_SOFTWARE_TLS SERVER_SOFTWARE
#  endif
#endif

/*
** Clock ID to use for clock_gettime(), for use in collecting the
** wall-clock processing time using clock_gettime() (which is
** signal-safe). We use clock_gettime(), rather than gmtime(), for
** measuring request wall-clock time because it's signal-safe.
** See discussion at:
** https://sqlite.org/althttpd/forumpost/4dc31619341ce947
   用于 clock_gettime() 的时钟标识,用于使用 clock_gettime()(它是信号安全的)收集墙钟处理时间。
   我们使用 clock_gettime() 而不是 gmtime() 来测量请求的墙钟时间,因为它是信号安全的。详见讨论:
*/
#ifdef _POSIX_MONOTONIC_CLOCK
#  define ALTHTTPD_CLOCK_ID CLOCK_MONOTONIC
#else
#  define ALTHTTPD_CLOCK_ID CLOCK_REALTIME
   /* noting that this can jump if the system time changes
      注意,如果系统时间更改,这可能会跳跃 */
#endif

程序数据集

/*
** We record most of the state information as global variables.  This
** saves having to pass information to subroutines as parameters, and
** makes the executable smaller...
   我们将大部分状态信息记录为全局变量。
   这样可以避免将信息作为参数传递给子程序,也使得可执行文件更小...
*/
static const char *zRoot = 0;    /* Root directory of the website 网站的根目录*/
static char *zPostData= 0;       /* POST data POST请求的数据*/
static int nPostData = 0;        /* Number of bytes of POST data POST数据的字节数*/
static char *zProtocol = 0;      /* The protocol being using by the browser 浏览器使用的协议*/
static char *zMethod = 0;        /* The method.  Must be GET HTTP请求的方法,必须为GET*/
static char *zScript = 0;        /* The object to retrieve 要获取的对象*/
static char *zRealScript = 0;    /* The object to retrieve.  Same as zScript except might have "/index.html" appended 
                                    要获取的对象,与zScript相同,可能附加了"/index.html"。*/
static char *zRequestUri = 0;    /* Sanitized request uri 经过处理的请求URI。*/
static char *zHome = 0;          /* The directory containing content 包含内容的目录。*/
static char *zQueryString = 0;   /* The query string on the end of the name 名称末尾的查询字符串。 */
static char *zFile = 0;          /* The filename of the object to retrieve 要获取的对象的文件名。*/
static int lenFile = 0;          /* Length of the zFile name, zFile名称的长度。*/
static char *zDir = 0;           /* Name of the directory holding zFile 包含zFile的目录的名称。*/
static char *zPathInfo = 0;      /* Part of the pathname past the file 文件之后路径的一部分。*/
static char *zAgent = 0;         /* What type if browser is making this query 发起此查询的浏览器类型。*/
static char *zServerName = 0;    /* The name after the http:// "http://"后的名称。*/
static char *zServerPort = 0;    /* The port number 端口号。*/
static char *zServerSoftware = 0;/* Software name and version info 软件名称和版本信息。*/
static char *zCookie = 0;        /* Cookies reported with the request 请求中报告的Cookies。*/
static char *zHttpHost = 0;      /* Name according to the web browser 根据Web浏览器的名称。*/
static char *zRealPort = 0;      /* The real TCP port when running as daemon 作为守护进程运行时的实际TCP端口。*/
static char *zRemoteAddr = 0;    /* IP address of the request 请求的IP地址。*/
static char *zReferer = 0;       /* Name of the page that refered to us 引用我们的页面的名称。*/
static char *zAccept = 0;        /* What formats will be accepted 将接受的格式。*/
static char *zAcceptEncoding =0; /* gzip or default 接受的编码格式,可以是gzip或默认。*/
static char *zContentLength = 0; /* Content length reported in the header 报头中报告的内容长度。*/
static char *zContentType = 0;   /* Content type reported in the header 报头中报告的内容类型。*/
static char *zQuerySuffix = 0;   /* The part of the URL after the first ? 第一个问号之后的URL的一部分。*/
static char *zAuthType = 0;      /* Authorization type (basic or digest) 授权类型(基本或摘要)。*/
static char *zAuthArg = 0;       /* Authorization values 授权值。*/
static char *zRemoteUser = 0;    /* REMOTE_USER set by authorization module 由授权模块设置的REMOTE_USER。 */
static char *zIfNoneMatch= 0;    /* The If-None-Match header value 表示If-None-Match头的值。*/
static char *zIfModifiedSince=0; /* The If-Modified-Since header value 表示If-Modified-Since头的值。*/
static char *zHttpScheme = "http";/* HTTP_SCHEME CGI variable 表示HTTP_SCHEME CGI变量,默认为"http"。*/
static char *zHttps = 0;         /* HTTPS CGI variable 表示HTTPS CGI变量。*/
static int nIn = 0;              /* Number of bytes of input 输入的字节数。*/
static int nOut = 0;             /* Number of bytes of output 输出的字节数。*/
static char zReplyStatus[4];     /* Reply status code 回复的状态码。*/
static int statusSent = 0;       /* True after status line is sent 在发送状态行后为真。*/
static const char *zLogFile = 0; /* Log to this file 记录日志到这个文件。*/
static char zExpLogFile[500] = {0}; /* %-expanded log file name 表示%扩展的日志文件名。*/
static const char *zIPShunDir=0; /* Directory containing hostile IP addresses 包含敌对IP地址的目录。*/
static int debugFlag = 0;        /* True if being debugged如果正在调试,则为真。 */
static struct timeval beginTime; /* Time when this process starts 进程启动的时间。*/
static struct timespec tsBeginTime; /* clock_gettime() when request processing starts 请求处理开始时的clock_gettime()。*/
static int closeConnection = 0;  /* True to send Connection: close in reply 为真时在回复中发送Connection: close。*/
static int nRequest = 0;         /* Number of requests processed 处理的请求数量。*/
static int omitLog = 0;          /* Do not make logfile entries if true 如果为真,则不生成日志文件条目。*/
static int useHttps = 0;         /* 0=HTTP, 1=external HTTPS (stunnel), 2=builtin TLS support 
                                    使用的协议,0表示HTTP,1表示外部HTTPS(通过stunnel),2表示内置的TLS支持。*/
static int useTimeout = 1;       /* True to use times 为真时使用时间。*/
static int nTimeoutLine = 0;     /* Line number where timeout was set 设置超时的行号。 */
static int standalone = 0;       /* Run as a standalone server (no inetd)为真时作为独立服务器运行(无inetd)。 */
static int ipv6Only = 0;         /* Use IPv6 only */
static int ipv4Only = 0;         /* Use IPv4 only */
static struct rusage priorSelf;  /* Previously report SELF time 表示之前报告的SELF时间。*/
static struct rusage priorChild; /* Previously report CHILD time 之前报告的CHILD时间。*/
/*static struct timespec tsSelf;*/
static int mxAge = 120;          /* Cache-control max-age 表示Cache-control的最大年龄。*/
static char *default_path = "/bin:/usr/bin";  /* Default PATH variable 默认的PATH变量。*/
static char *zScgi = 0;          /* Value of the SCGI env variable 表示SCGI环境变量的值*/
static int rangeStart = 0;       /* Start of a Range: request 表示Range请求的起始位置。*/
static int rangeEnd = 0;         /* End of a Range: request 表示Range请求的结束位置。*/
static int maxCpu = MAX_CPU;     /* Maximum CPU time per process 每个进程的最大CPU时间。 */
static int enableSAB = 0;        /* Add reply header to enable SharedArrayBuffer 
                                    是否添加响应头以启用SharedArrayBuffer。
                                    SharedArrayBuffer是一种在Web Workers之间共享数据的机制。*/
static int inSignalHandler = 0;  /* True if running a signal handler 是否正在运行信号处理程序。*/

cgienv CGI环境变量

CGI(Common Gateway Interface)环境变量是在 Web 服务器和 CGI 程序之间传递信息的一种标准机制。当客户端发起 HTTP 请求时,Web 服务器会将一组特定的环境变量传递给 CGI 程序,以提供有关请求和运行环境的信息。这些环境变量由 Web 服务器设置,而 CGI 程序可以读取和利用这些变量来执行相应的操作。

/* Mapping between CGI variable names and values stored in global variables. 
   映射 CGI 变量名和存储在全局变量中的值 */
static struct {
  char *zEnvName; // CGI 环境变量的名称
  char **pzEnvValue; //CGI 环境变量的值
} cgienv[] = {
  { "CONTENT_LENGTH",           &zContentLength }, /* Must be first for SCGI */
  { "AUTH_TYPE",                &zAuthType },
  { "AUTH_CONTENT",             &zAuthArg },
  { "CONTENT_TYPE",             &zContentType },
  { "DOCUMENT_ROOT",            &zHome },
  { "HTTP_ACCEPT",              &zAccept },
  { "HTTP_ACCEPT_ENCODING",     &zAcceptEncoding },
  { "HTTP_COOKIE",              &zCookie },
  { "HTTP_HOST",                &zHttpHost },
  { "HTTP_IF_MODIFIED_SINCE",   &zIfModifiedSince },
  { "HTTP_IF_NONE_MATCH",       &zIfNoneMatch },
  { "HTTP_REFERER",             &zReferer },
  { "HTTP_SCHEME",              &zHttpScheme },
  { "HTTP_USER_AGENT",          &zAgent },
  { "HTTPS",                    &zHttps },
  { "PATH",                     &default_path },
  { "PATH_INFO",                &zPathInfo },
  { "QUERY_STRING",             &zQueryString },
  { "REMOTE_ADDR",              &zRemoteAddr },
  { "REQUEST_METHOD",           &zMethod },
  { "REQUEST_URI",              &zRequestUri },
  { "REMOTE_USER",              &zRemoteUser },
  { "SCGI",                     &zScgi },
  { "SCRIPT_DIRECTORY",         &zDir },
  { "SCRIPT_FILENAME",          &zFile },
  { "SCRIPT_NAME",              &zRealScript },
  { "SERVER_NAME",              &zServerName },
  { "SERVER_PORT",              &zServerPort },
  { "SERVER_PROTOCOL",          &zProtocol },
  { "SERVER_SOFTWARE",          &zServerSoftware },
};

althttpd_printf、althttpd_vprintf 打印信息

/*
** A printf() proxy which outputs either to stdout or the outbound TLS
** connection, depending on connection state. It uses a
** statically-sized buffer for TLS output and will fail (via
** Malfunction()) if it's passed too much data. In non-TLS mode it has
** no such limitation. The buffer is generously sized, in any case, to
** be able to handle all of the headers output by althttpd as of the
** time of this writing.
** althttpd_vprintf 函数是 printf() 函数的代理,根据连接状态输出到 stdout 或者 TLS 连接。
** 在 TLS 模式下,它使用一个静态大小的缓冲区进行 TLS 输出,并且如果传递了过多的数据,
** 它将通过 Malfunction() 报错。在非 TLS 模式下,它没有这种限制。
** 无论如何,缓冲区的大小足够大,以处理截至代码编写时 althttpd 输出的所有头部。
*/
#ifdef ENABLE_TLS //TLS (Transport Layer Security) 
static int althttpd_vprintf(char const * fmt, va_list va){
  if( useHttps!=2 || NULL==tlsState.sslCon ){
    // 如果不是 TLS 模式或者 sslCon 为空,则调用 vprintf() 直接输出到 stdout
    return vprintf(fmt, va);
  }else{
    char pfBuffer[10000];
    // 使用 vsnprintf 将格式化的字符串写入 pfBuffer 中,不超过 pfBuffer 大小
    const int sz = vsnprintf(pfBuffer, sizeof(pfBuffer), fmt, va);
    if( sz<(int)sizeof(pfBuffer) ){
      // 如果写入的大小小于 pfBuffer 的大小,则通过 tls_write_server 输出到 TLS 连接
      return (int)tls_write_server(tlsState.sslCon, pfBuffer, sz);
    }else{
      // 如果写入大小超过 pfBuffer 的大小,通过 Malfunction() 报错
      Malfunction(529, /* LOG: Output buffer too small */
         "Output buffer is too small. Wanted %d bytes.", sz);
      return 0;
    }
  }
}
#else
#define althttpd_vprintf vprintf
#endif

#ifdef ENABLE_TLS
static int althttpd_printf(char const * fmt, ...){
  int rc;
  va_list va;
  va_start(va,fmt);
  rc = althttpd_vprintf(fmt, va);
  va_end(va);
  return rc;
}
static void *tls_new_server(int iSocket);
static void tls_close_server(void *pServerArg);
static void tls_atexit(void);
#else
#define althttpd_printf printf
#endif

time 转换

/* A structure for holding a single date and time. 用于保存单个日期和时间的结构体。 */
typedef struct DateTime DateTime;
struct DateTime {
  long long iJD;      /* The julian day number times 86400000,朱利安日数乘以 86400000 */
  int Y, M, D;        /* Year, month, and day */
  int h, m;           /* Hour and minutes */
  double s;           /* Seconds */
};

/* Populates p based on the time stored in t. 根据存储在 t 中的时间填充 p。 */
static void unixToDateTime(time_t t, DateTime *p){
  int Z, A, B, C, D, E, X1;
  int day_s;
  p->iJD = (long long)((t/86400.0 + 2440587.5) * 86400000);
  Z = (int)((p->iJD + 43200000)/86400000);
  A = (int)((Z - 1867216.25)/36524.25);
  A = Z + 1 + A - (A/4);
  B = A + 1524;
  C = (int)((B - 122.1)/365.25);
  D = (36525*(C&32767))/100;
  E = (int)((B-D)/30.6001);
  X1 = (int)(30.6001*E);
  p->D = B - D - X1;
  p->M = E<14 ? E-1 : E-13;
  p->Y = p->M>2 ? C - 4716 : C - 4715;

  day_s = t % 86400;
  p->h = day_s / 3600;
  p->m = day_s % 3600 / 60;
  p->s = day_s % 60;
#if 0
  fprintf(stderr,"day_s = %d, DateTime Y=%d M=%d D=%d h=%d m=%d s=%d\n",
          (int)day_s,
          p->Y, p->M, p->D,
          p->h, p->m, (int)p->s);
#endif
}

#ifdef COMBINED_LOG_FORMAT
/*
** Copies and converts _relevant_ fields of pD into pTm. Only those
** fields relevant for althttpd's own logging are handled and the rest
** are zeroed out, thus pTm is not necessarily suitable for all
** strftime() formats or libc time functions after calling this.
** 将 pD 的相关字段复制并转换为 pTm。仅处理 althttpd 自身日志记录所需的字段,
** 其余字段将被置零,因此调用此函数后,pTm 不一定适用于所有 strftime() 格式
** 或 libc 时间函数。
*/
static void DateTime_toTm(const DateTime *pD, struct tm *pTm){
  memset(pTm, 0, sizeof(*pTm));
  pTm->tm_isdst = -1;
  pTm->tm_sec = (int)pD->s;
  pTm->tm_min = pD->m;
  pTm->tm_hour = pD->h;
  pTm->tm_mday = pD->D;
  pTm->tm_mon = pD->M - 1;
  pTm->tm_year = pD->Y - 1900;
}
#endif

/* Convert a struct timeval into an integer number of microseconds
   将 struct timeval 转换为微秒数。 */
static long long int tvms(struct timeval *p){
  return ((long long int)p->tv_sec)*1000000 + (long long int)p->tv_usec;
}
/* Convert a struct timespec into an integer number of microseconds
    struct timespec 转换为微秒数。 */
static long long int tsms(struct timespec *p){
  return ((long long int)p->tv_sec)*1000000 + (long long int)(p->tv_nsec/1000);
}

log 日志

/*
** If (z+strlen(zIn)) < zEnd then this appends zIn to z, sets *zTail
** to the end of that string, and returns z. If (z+strlen(zIn)) >= zEnd
** then *zTail is set to 0 and 0 is returned. If z is NULL, *zTail
** is set to 0 but this is otherwise a no-op.
** 如果 (z+strlen(zIn)) < zEnd,则将 zIn 追加到 z 中,将 *zTail 设置为该字符串的末尾,并返回 z。
** 如果 (z+strlen(zIn)) >= zEnd,则将 *zTail 设置为 0 并返回 0。如果 z 为 NULL,则将 *zTail 设置为 0,但这是一个无操作。
*/
static char * log_str(char *z, const char *zEnd, const char *zIn, char **zTail){
  const size_t n = z ? strlen(zIn) : 0;
  if( !z || zEnd<=z+n ){
    *zTail = 0;
    return 0;
  }
  memcpy( z, zIn, n );
  *zTail = z+n;
  return z;
}

/*
** Works like log_str() but doubles all double-quote characters in the
** intput. If z is NULL or zIn is too long for the input buffer,
** *zTail is set to 0 and 0 is returned.
** This is used to quote strings for output into the CSV log file.
** 与 log_str() 类似,但是将输入中的所有双引号字符加倍。如果 z 为 NULL 或者 zIn 对于输入缓冲区来说太长,
** 则将 *zTail 设置为 0 并返回 0。
** 这用于为输出到 CSV 日志文件的字符串进行引用。
*/
static char * log_escstr(char *z, const char *zEnd, const char *zIn,
                         char **zTail){
  char * zOrig = z;
  char c = *zIn;
  if( !z || z>=zEnd ){
    goto too_short;
  }
  for( ; c && z<zEnd; c=*++zIn ){
    if( '"'==c ){
      if( z>=zEnd-1 ){
        goto too_short;
      }
      *z++ = c;
    }
    *z++ = c;
  }
  *zTail = z;
  return zOrig;
too_short:
  *zTail = 0;
  return 0;
}

/*
** Appends d to z, sets *zTail to the byte after the new end of z, and
** returns z. If appending d would extend past zEnd then *zTail is set
** to 0 and 0 is returned, but z is not modified.  If z is NULL, *zTail
** is set to 0 but this is otherwise a no-op.
**
** minDigits must be at least 1 and d will be left-padded with 0's to
** a minimum of that length.
** 将 d 追加到 z,将 *zTail 设置为新的 z 末尾的字节,并返回 z。
** 如果追加 d 会超过 zEnd,则将 *zTail 设置为 0 并返回 0,但 z 不会被修改。
** 如果 z 为 NULL,则将 *zTail 设置为 0,但这是一个无操作。
** minDigits 必须至少为 1,并且 d 将用 0 填充到至少该长度。
*/
static char * log_int(char *z, const char *zEnd, long long d,
                      int minDigits, char **zTail){
  char buf[128];
  int i = (int)sizeof(buf), n = 0;
  int neg = 0;

  assert( minDigits>0 );
  if( !z ){
    *zTail = 0;
    return 0;
  }else if( d<0 ){
    neg = 1;
    d = -d;
  }
  for( ; (minDigits-- > 0) || d>0; d/=10 ){
    buf[--i] = '0' + (d%10);
    assert(i>0);
    ++n;
  }
  if( neg ){
    buf[--i] = '-';
    ++n;
  }
  if( zEnd <= z+n ){
    return 0;
  }
  memcpy(z, &buf[i], n);
  *zTail = z+n;
  return z;
}

#ifndef COMBINED_LOG_FORMAT
/*
** A proxy for log_str() and log_int() which appends 19 bytes of
** pD's timestamp to z in the form "YYYY-MM-DD hh:mm:ss", assigns
** *zTail to the address one after the end of that string, and returns
** z. If (z+19 >= zEnd) then *zTail is set to 0 and 0 is returned, but
** this is otherwise a no-op.
** log_str() 和 log_int() 的代理,将 pD 的时间戳的 19 个字节追加到 z 中,格式为 "YYYY-MM-DD hh:mm:ss",
** 将 *zTail 分配给该字符串的结尾地址,并返回 z。
** 如果 (z+19 >= zEnd) 则将 *zTail 设置为 0 并返回 0,但这是一个无操作。
*/
static char * log_DateTime(char * z, const char *zEnd,
                           const struct DateTime *pD, char **zTail){
  char * zOut = z;
  if( z+19 >= zEnd ){
    *zTail = 0;
    return 0;
  }
#define aint(N,DIGITS,SUFFIX) log_int( zOut, zEnd, (int)(N), (DIGITS), &zOut ); \
  if( SUFFIX ) log_str( zOut, zEnd, SUFFIX, &zOut )
  aint(pD->Y, 4, "-");
  aint(pD->M, 2, "-");
  aint(pD->D, 2, " ");
  aint(pD->h, 2, ":");
  aint(pD->m, 2, ":");
  aint(pD->s, 2, 0);
#undef aint
  *zTail = zOut;
  return z;
}
#endif

/*
** Make an entry in the log file.  If the HTTP connection should be
** closed, then terminate this process.  Otherwise return.
** 在日志文件中创建一个条目。如果 HTTP 连接应该关闭,那么终止该进程。否则返回。
*/
static void MakeLogEntry(int exitCode, int lineNum){
  int logfd;
  if( zPostData ){// 如果存在 zPostData,则释放它
    if( inSignalHandler==0 ){
      free(zPostData);
    }
    zPostData = 0;
  }
  if( zLogFile && !omitLog ){// 如果需要记录日志且未被忽略,则进行日志记录
    struct rusage self, children;
    int waitStatus;
    const char *zRM = zRemoteUser ? zRemoteUser : "";
    const char *zFilename;

	// 设置一些可能为空的字符串
    if( zScript==0 ) zScript = "";
    if( zRealScript==0 ) zRealScript = "";
    if( zRemoteAddr==0 ) zRemoteAddr = "";
    if( zHttpHost==0 ) zHttpHost = "";
    if( zReferer==0 ) zReferer = "";
    if( zAgent==0 ) zAgent = "";
    if( zQuerySuffix==0 ) zQuerySuffix = "";
	
	// 确定日志文件名
    if( zExpLogFile[0]!=0 ){
      zFilename = zExpLogFile;
    }else{
      zFilename = zLogFile;
    }
    waitpid(-1, &waitStatus, WNOHANG);// 在非阻塞方式下等待子进程的退出

	// 如果在信号处理程序中,使用 getrusage() 时存在不安全性,因此将其置零
    if( inSignalHandler!=0 ){
      /* getrusage() is not signal-safe, so fall back to 0. 
                     不是信号安全的,因此回退到 0。*/
      memset(&self, 0, sizeof(self));
      memset(&children, 0, sizeof(children));
    }else{// 获取自身和子进程的资源使用情况
      getrusage(RUSAGE_SELF, &self);
      getrusage(RUSAGE_CHILDREN, &children);
    }
    // 打开日志文件,以追加、创建和写入方式,权限为 0640
    if( (logfd = open(zFilename,O_WRONLY | O_CREAT | O_APPEND, 0640)) > 0 ){
      time_t tNow;              /* current time 当前时间 */
      struct timespec tsNow;    /* current time (again! 再次获取) */
      struct DateTime dt;       /* high-level tNow 高级的 tNow */
      char msgbuf[5000];        /* message buffer 消息缓冲区*/
      const char * const zEnd = &msgbuf[0] + sizeof(msgbuf);
      char * zPos = &msgbuf[0]; /* current write pos 当前写入位置 */

#define astr( STR ) log_str( zPos, zEnd, (STR), &zPos )
#define acomma astr(",")
#define astr2( STR ) astr( STR ); acomma
#define aint( N ) log_int( zPos, zEnd, (N), 1, &zPos ); acomma

	  // 获取当前时间和时钟时间
      clock_gettime(ALTHTTPD_CLOCK_ID, &tsNow);
      time(&tNow);
      unixToDateTime(tNow, &dt);
#ifdef COMBINED_LOG_FORMAT // 如果使用 COMBINED_LOG_FORMAT,则记录日志的格式会稍有不同
      /* Potential TODO: eliminate use of strftime(). Default builds
      ** of althttpd do not use COMBINED_LOG_FORMAT, so this is a low-priority issue. */
       /* 潜在的 TODO: 消除对 strftime() 的使用。
          默认构建althttpd 不使用 COMBINED_LOG_FORMAT,因此这是一个低优先级的问题。*/
      {
        struct tm vTm/* to remove once we have a strftime() substitute */;//一旦我们有了 strftime() 替代品,就可以删除
        char zDate[200];
#if 0
        /* Older impl, for reference: 旧实现,供参考: */
        fprintf(log, "%s - - [%s] \"%s %s %s\" %s %d \"%s\" \"%s\"\n",
                zRemoteAddr, zDate, zMethod, zScript, zProtocol,
                zReplyStatus, nOut, zReferer, zAgent);
#endif
        DateTime_toTm(&dt, &vTm);
        strftime(zDate, sizeof(zDate), "%d/%b/%Y:%H:%M:%S %Z", &vTm);
        astr( zRemoteAddr );
        astr( " - - [" );
        astr( zDate );
        astr( "] \"" );
        astr( zMethod );
        astr( " " );
        astr( zScript );
        astr( " " );
        astr( zProtocol );
        astr( "\" " );
        astr( zReplyStatus );
        astr( " " );
        aint( nOut );
        astr( " \"" );
        astr( zReferer );
        astr( "\" \"" );
        astr( zAgent );
        astr( "\"\n" );
      }
#else // 否则,按照其他格式记录日志
      /* Log record files:
      **  (1) Date and time  日期和时间
      **  (2) IP address
      **  (3) URL being accessed  正在访问的 URL
      **  (4) Referer  引用者
      **  (5) Reply status  响应状态
      **  (6) Bytes received  接收到的字节数
      **  (7) Bytes sent
      **  (8) Self user time   自身用户时间
      **  (9) Self system time  自身系统时间
      ** (10) Children user time  子进程用户时间
      ** (11) Children system time   子进程系统时间
      ** (12) Total wall-clock time   总墙钟时间
      ** (13) Request number for same TCP/IP connection  相同 TCP/IP 连接的请求编号
      ** (14) User agent   用户代理
      ** (15) Remote user   远程用户
      ** (16) Bytes of URL that correspond to the SCRIPT_NAME  与 SCRIPT_NAME 相对应的 URL 字节数
      ** (17) Line number in source file  源文件中的行号
      */
#define escstr(X) log_escstr( zPos, zEnd, X, &zPos )
      /* (1) */ log_DateTime( zPos, zEnd, &dt, &zPos );
      astr(",");
      /* (2) */ astr2( zRemoteAddr );
      /* (3) */ astr( "\"" );
      astr( zHttpScheme );
      astr( "://" );
      escstr(zHttpHost);
      escstr(zScript);
      escstr(zQuerySuffix);
      astr2( "\"" );
      /* (4) */ astr( "\"" ); escstr(zReferer); astr2("\"");
      /* (5) */ astr2( zReplyStatus );
      /* (6) */ aint( nIn );
      /* (7) */ aint( nOut );
      /* (8) */ aint( tvms(&self.ru_utime) - tvms(&priorSelf.ru_utime) );
      /* (9) */ aint( tvms(&self.ru_stime) - tvms(&priorSelf.ru_stime) );
      /* (10) */ aint( tvms(&children.ru_utime) - tvms(&priorChild.ru_utime) );
      /* (11) */ aint( tvms(&children.ru_stime) - tvms(&priorChild.ru_stime) );
      /* (12) */ aint( tsms(&tsNow) - tsms(&tsBeginTime) );
      /* (13) */ aint( nRequest );
      /* (14) */ astr( "\"" ); escstr(zAgent); astr2("\"");
      /* (15) */ astr( "\"" ); escstr(zRM); astr2("\"");
      /* (16) */ aint( strlen(zHttpScheme)+strlen(zHttpHost)+strlen(zRealScript)+3 );
      /* (17) */ log_int( zPos, zEnd, lineNum, 1, &zPos );
#undef escstr
#ifdef ALTHTTPD_LOG_PID
      /* Appending of PID to the log is used only to assist in debugging of hanging althttpd processes: 将 PID 附加到日志仅用于协助调试挂起的 althttpd 进程:
      ** https://sqlite.org/althttpd/forumpost/4dc31619341ce947 */
      acomma;
      /* (18) */ log_int( zPos, zEnd, getpid(), 1, &zPos );
#endif
      astr( "\n" );
      priorSelf = self;
      priorChild = children;
#endif
      if( zPos!=0 ){
        size_t iStart = 0;
        size_t toSend;
        size_t nSent;
        assert( zPos<zEnd-1 );
        assert( zPos>&msgbuf[0] );
        *zPos = 0;
        toSend = zPos - &msgbuf[0];
        while( 1 /*exit-by-break*/ ){
          nSent = write( logfd, msgbuf+iStart, toSend);
          if( nSent<=0 ) break;
          if( nSent>=toSend ) break;
          iStart += nSent;
          toSend -= nSent;
        }
      }
      close(logfd);
      nIn = nOut = 0;
#undef astr
#undef astr2
#undef aint
#undef acomma
    }
  }
  if( closeConnection || inSignalHandler ){
    exit(exitCode);// 如果需要关闭连接或在信号处理程序中,则终止进程
  }
  statusSent = 0;
}

Malfunction 故障

用于在web服务器中通知客户端服务器发生了故障。

/* Forward reference 前向引用*/
static void Malfunction(int errNo, const char *zFormat, ...);

/* Print the first line of a response followed by the server type.
   用于开始构建 HTTP 响应的第一行 */
static void StartResponse(const char *zResultCode){
  time_t now;
  time(&now);
  if( statusSent ) return; 如果状态已发送,则直接返回,避免重复发送
   打印 HTTP 响应的第一行,格式为 "HTTP-Version Status-Code",例如 "HTTP/1.1 200 OK"
  nOut += althttpd_printf("%s %s\r\n",
                          zProtocol ? zProtocol : "HTTP/1.1",
                          zResultCode);
  strncpy(zReplyStatus, zResultCode, 3); 将响应状态码的前三个字符复制到 zReplyStatus
  zReplyStatus[3] = 0;
   如果响应状态码的第一个字符大于等于 '4',则表示客户端错误或服务器错误,设置 closeConnection 为 1
  if( zReplyStatus[0]>='4' ){
    closeConnection = 1;
  }
  if( closeConnection ){
    // 如果 closeConnection 为真,表示要关闭连接
    nOut += althttpd_printf("Connection: close\r\n");
  }else{
    // 否则,表示保持连接,打印 "Connection: keep-alive"
    nOut += althttpd_printf("Connection: keep-alive\r\n");
  }
  nOut += DateTag("Date", now);// 打印当前时间的 Date 标签
  statusSent = 1;// 设置状态已发送标志为真
}
/* Tell the client that there is an error in the script.
   告诉客户端脚本中存在错误。*/
static void CgiScriptWritable(void){
  StartResponse("500 CGI Configuration Error");
  nOut += althttpd_printf(
    "Content-type: text/plain; charset=utf-8\r\n"
    "\r\n"
    "The CGI program %s is writable by users other than its owner.\n",
    zRealScript);
  MakeLogEntry(0, 140);  /* LOG: CGI script is writable */
  althttpd_exit();
}
/* Tell the client that the server malfunctioned.
   通知客户端服务器发生了故障 */
void Malfunction(int linenum, const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  StartResponse("500 Server Malfunction");//向客户端发送响应头,表示服务器故障(500 Server Malfunction)
  nOut += althttpd_printf(
    "Content-type: text/plain; charset=utf-8\r\n"
    "\r\n"
    "Web server malfunctioned; error number %d\n\n", linenum);
  if( zFormat ){ 如果有额外的格式化字符串(zFormat),将其格式化后发送给客户端
    nOut += althttpd_vprintf(zFormat, ap);
    althttpd_printf("\n");
    nOut++;
  }
  va_end(ap);
  MakeLogEntry(0, linenum);
  althttpd_exit();
}

malloc with log

/* Allocate memory safely */
static char *SafeMalloc( size_t size ){
  char *p;
  p = (char*)malloc(size);
  if( p==0 ){
    strcpy(zReplyStatus, "998");
    MakeLogEntry(1,100);  /* LOG: Malloc() failed 主要是保证有个输出,方便调查问题*/
    exit(1);
  }
  return p;
}

TLS:tls_gets,tls_read_server,tls_write_server

使用 OpenSSL 库实现 TLS(Transport Layer Security)功能的一部分。
使用 OpenSSL 库来处理加密和解密通信。
OpenSSL 库(Open Source implementation of the SSL and TLS protocols)

#ifdef ENABLE_TLS
#include <openssl/bio.h> //BIO(Basic Input/Output) 文件、内存、套接字等数据源和目标之间进行灵活的数据传输
#include <openssl/ssl.h> //SSL(Secure Sockets Layer)用于安全地传输数据。
#include <openssl/err.h> //处理错误和异常的 OpenSSL 函数和宏
#include <openssl/x509.h> //处理数字证书的创建、解析和验证
typedef struct TlsServerConn {
  SSL *ssl;          /* The SSL codec , SSL 编解码器*/
  BIO *bio;          /* SSL BIO object ,SSL BIO 对象 */
  int iSocket;       /* The socket, 套接字 */
} TlsServerConn;// 定义 TLS 服务器连接的结构体

/*
** There can only be a single OpenSSL IO connection open at a time.
** State information about that IO is stored in the following global singleton:
// 全局变量,用于存储唯一的 OpenSSL IO 连接的状态信息
*/
static struct TlsState {
  int isInit;             /* 0: uninit 1: init as client 2: init as server */
  SSL_CTX *ctx;
  const char *zCertFile;  /* --cert CLI arg, --cert 命令行参数*/
  const char *zKeyFile;   /* --pkey CLI arg, --pkey 命令行参数*/
  TlsServerConn * sslCon;
} tlsState = {
  0,                      /* isInit */
  NULL,                   /* SSL_CTX *ctx */
  NULL,                   /* zCertFile */
  NULL,                   /* zKeyFile */
  NULL                    /* sslCon */
};

/*
** Read a single line of text from the client and stores it in zBuf
** (which must be at least nBuf bytes long). On error it
** calls Malfunction().
** If it reads anything, it returns zBuf.
// 从客户端读取一行文本并将其存储在 zBuf 中
*/
static char *tls_gets(void *pServerArg, char *zBuf, int nBuf){
  int n = 0, err = 0;
  int i;
  TlsServerConn * const pServer = (TlsServerConn*)pServerArg;
  if( BIO_eof(pServer->bio) ) return 0;// 如果在 BIO 中已经到达末尾,则返回 0
  for(i=0; i<nBuf-1; i++){
    n = SSL_read(pServer->ssl, &zBuf[i], 1);// 从 SSL 连接中读取一个字节
    err = SSL_get_error(pServer->ssl, n);
    if( err!=0 ){
      Malfunction(525,"SSL read error.");// 如果出现错误,则调用 Malfunction() 函数
    }else if( 0==n || zBuf[i]=='\n' ){
      break;
    }
  }
  zBuf[i+1] = 0;// 在末尾添加 null 字符
  return zBuf;
}

/*
** Reads up tp nBuf bytes of TLS-decoded bytes from the client and
** stores them in zBuf, which must be least nBuf bytes long.  Returns
** the number of bytes read. Fails fatally if nBuf is "too big" or if
** SSL_read() fails. Once pServerArg reaches EOF, this function simply
** returns 0 with no side effects.
// 从客户端读取最多 nBuf 字节的 TLS 解码字节
*/
static size_t tls_read_server(void *pServerArg, void *zBuf, size_t nBuf){
  int err = 0;
  size_t rc = 0;
  TlsServerConn * const pServer = (TlsServerConn*)pServerArg;
  if( nBuf>0x7fffffff ){
    // 如果 nBuf 大于 0x7fffffff,则调用 Malfunction() 函数,记录错误并返回
    Malfunction(526,"SSL read too big"); /* LOG: SSL read too big */
  }
  while( 0==err && nBuf!=rc && 0==BIO_eof(pServer->bio) ){
    const int n = SSL_read(pServer->ssl, zBuf + rc, (int)(nBuf - rc));
    if( n==0 ){// 如果读取到末尾,退出循环
      break;
    }
    err = SSL_get_error(pServer->ssl, n);// 获取 SSL 错误码
    if(0==err){
      rc += n;
    }else{// 如果出现错误,则调用 Malfunction() 函数
      Malfunction(527,"SSL read error."); /* LOG: SSL read error */
    }
  }
  return rc;
}

/*
** Write cleartext bytes into the SSL server codec so that they can
** be encrypted and sent back to the client. On success, returns
** the number of bytes written, else returns a negative value.
// 将明文字节写入 SSL 服务器编解码器,以便加密并发送回客户端
*/
static int tls_write_server(void *pServerArg, void const *zBuf,  size_t nBuf){
  int n;
  TlsServerConn * const pServer = (TlsServerConn*)pServerArg;
  if( nBuf<=0 ) return 0;// 如果 nBuf 小于等于 0,则返回 0
  if( nBuf>0x7fffffff ){// 如果 nBuf 大于 0x7fffffff,则调用 Malfunction() 函数,记录错误并返回
    Malfunction(528,"SSL write too big"); /* LOG: SSL write too big */
  }
  n = SSL_write(pServer->ssl, zBuf, (int)nBuf);// 调用 SSL_write() 函数将数据写入 SSL 连接
  if( n<=0 ){
    /* Do NOT call Malfunction() from here, as Malfunction()
    ** may output via this function. The current error handling
    ** is somewhat unsatisfactory, as it can lead to negative
    ** response length sizes in the althttpd log. */
    /* 不要从这里调用 Malfunction(),
       因为 Malfunction()可能通过此函数输出。当前的错误处理有些令人不满意,
       因为它可能导致 althttpd 日志中出现负响应长度。*/
    return -SSL_get_error(pServer->ssl, n);
  }else{
    return n;
  }
}
#endif /* ENABLE_TLS */

tls_new_server,tls_close_server,tls_atexit

#ifdef ENABLE_TLS
/*
** Create a new server-side codec.  The argument is the socket's file
** descriptor from which the codec reads and writes. The returned
** memory must eventually be passed to tls_close_server().
该函数用于创建一个新的服务器端编解码器。
接受一个套接字文件描述符作为参数,从该套接字读取和写入数据。
返回的内存必须最终传递给 tls_close_server 函数以释放资源。
*/
static void *tls_new_server(int iSocket){
  TlsServerConn *pServer = malloc(sizeof(*pServer));
  BIO *b = pServer ? BIO_new_socket(iSocket, 0) : NULL;
  if( NULL==b ){
    Malfunction(507,"Cannot allocate TlsServerConn."); /* LOG: TlsServerConn */
  }
  assert(NULL!=tlsState.ctx);
  pServer->ssl = SSL_new(tlsState.ctx);
  pServer->bio = b;
  pServer->iSocket = iSocket;
  SSL_set_bio(pServer->ssl, b, b);
  SSL_accept(pServer->ssl);//开始进行 SSL 握手过程,即服务器端与客户端之间的协商和验证。
  return (void*)pServer;
}

/*
** Close a server-side code previously returned from tls_new_server().
关闭先前由 tls_new_server 创建的服务器端编解码器。
*/
static void tls_close_server(void *pServerArg){
  TlsServerConn *pServer = (TlsServerConn*)pServerArg;
  SSL_free(pServer->ssl);
  memset(pServer, 0, sizeof(TlsServerConn));
  free(pServer);
}

static void tls_atexit(void){//在程序退出时执行
#if 0
  /*
  ** Shutting down TLS can lead to spurious hung processes on some platforms/builds.
  ** See the long discussion on this at:
  ** https://sqlite.org/althttpd/forumpost/4dc31619341ce947
  当前的实现被注释掉了,因为在某些平台和构建配置下,关闭 TLS 可能导致进程悬挂。
  详细讨论可以参考链接中的注释。
  */
  if( inSignalHandler==0 && tlsState.sslCon!=0 ){
    tls_close_server(tlsState.sslCon);
    tlsState.sslCon = NULL;
  }
#endif
}
#endif /* ENABLE_TLS */

tls_init_conn,tls_close_conn,althttpd_fflush

在内置 TLS 模式下初始化 SSL I/O 状态以及关闭 TLS 连接

/*
** If running in builtin TLS mode, initializes the SSL I/O
** state and returns 1, else does nothing and returns 0.
*/
static int tls_init_conn(int iSocket){
#ifdef ENABLE_TLS
  if( 2==useHttps ){
    /*assert(NULL==tlsState.sslCon);*/
    if( NULL==tlsState.sslCon ){
      tlsState.sslCon = (TlsServerConn *)tls_new_server(iSocket);
      if( NULL==tlsState.sslCon ){
        Malfunction(512, /* LOG: TLS context */ "Could not instantiate TLS context.");
      }
      atexit(tls_atexit);//注册 tls_atexit 函数,以确保在程序退出时关闭 TLS 连接。
    }
    return 1;//返回 1 表示成功初始化 TLS 连接。
  }
#else
  if( 0==iSocket ){/*unused arg*/}
#endif
  return 0;
}
static void tls_close_conn(void){
#ifdef ENABLE_TLS
  if( tlsState.sslCon ){
    tls_close_server(tlsState.sslCon);//关闭 TLS 连接
    tlsState.sslCon = NULL;
  }
#endif
}

在非内置 TLS 模式下刷新给定的 FILE 句柄

/* In non-builtin-TLS mode, fflush()es the given FILE handle, else this is a no-op.
用于在非内置 TLS 模式下刷新给定的 FILE 句柄,否则这个函数不执行任何操作。
*/
static void althttpd_fflush(FILE *f){
  if( useHttps!=2 ){//不是内置 TLS 模式
    fflush(f);//将缓冲区的数据写入文件
  }
}

althttpd_exit 程序退出时

/* Flush the buffer then exit. */
static void althttpd_exit(void){
  althttpd_fflush(stdout);
  tls_close_conn();
  exit(0);
}

BlockIPAddress,ServiceUnavailable

实现一个简单的 IP 阻止机制,用于防止来自特定 IP 地址的滥用请求。

/* Forward reference */
static void BlockIPAddress(void);
static void ServiceUnavailable(int lineno);
/*
** An abusive HTTP request has been submitted by the IP address zRemoteAddr.
** Block future requests coming from this IP address.
**
** This only happens if the zIPShunDir variable is set, which is only set
** by the --ipshun command-line option.  Without that setting, this routine
** is a no-op.
**
** If zIPShunDir is a valid directory, then this routine uses zRemoteAddr
** as the name of a file within that directory.  Cases:
**
** +  The file already exists and is not an empty file.  This will be the
**    case if the same IP was recently blocked, but the block has expired,
**    and yet the expiration was not so long ago that the blocking file has
**    been unlinked.  In this case, add one character to the file, which
**    will update its mtime (causing it to be active again) and increase
**    its expiration timeout.
**
** +  The file exists and is empty.  This happens if the administrator
**    uses "touch" to create the file.  An empty blocking file indicates
**    a permanent block.  Do nothing.
**
** +  The file does not exist.  Create it anew and make it one byte in size.
**
** The UnlinkExpiredIPBlockers() routine will run from time to time to
** unlink expired blocker files.  If the DisallowedRemoteAddr() routine finds
** an expired blocker file corresponding to zRemoteAddr, it might unlink
** that one blocker file if the file has been expired for long enough.
这个函数是用来阻止恶意的 HTTP 请求来自 IP 地址 zRemoteAddr。它会将来自该 IP 地址的未来请求阻止。关键的步骤如下:
检查是否启用了 IP 阻止,即 zIPShunDir 不为空。
构建文件名 zFullname,该文件名是由 IP 阻止目录和远程地址构成的。
检查文件是否存在以及文件大小的情况。
根据文件的情况执行不同的操作,例如更新文件的 mtime、增加文件的大小等。
*/
static void BlockIPAddress(void){
  size_t nIPShunDir;
  size_t nRemoteAddr;
  int rc;
  struct stat statbuf;
  char zFullname[1000];

  if( zIPShunDir==0 ) return;
  if( zRemoteAddr==0 ) return;
  if( zRemoteAddr[0]==0 ) return;

  /* If we reach this point, it means that a suspicious request was
  ** received and we want to activate IP blocking on the remote address.
  ** 如果到达此点,说明收到了一个可疑请求,我们希望在远程地址上激活 IP 阻止。
  */
  nIPShunDir = strlen(zIPShunDir);
  while( nIPShunDir>0 && zIPShunDir[nIPShunDir-1]=='/' ) nIPShunDir--;
  nRemoteAddr = strlen(zRemoteAddr);
  if( nIPShunDir + nRemoteAddr + 2 >= sizeof(zFullname) ){
    Malfunction(914, /* LOG: buffer overflow */
       "buffer overflow");
  }
  memcpy(zFullname, zIPShunDir, nIPShunDir);
  zFullname[nIPShunDir] = '/';
  memcpy(zFullname+nIPShunDir+1, zRemoteAddr, nRemoteAddr+1);
  rc = stat(zFullname, &statbuf);
  if( rc!=0 || statbuf.st_size>0 ){
    FILE *lock = fopen(zFullname, "a");
    if( lock ){
      fputc('X', lock);
      fclose(lock);
    }
  }
}

/*
** Send a service-unavailable reply.发送一个服务不可用的响应。
它会返回 HTTP 503 状态码和一条包含被滥用的 IP 地址信息的消息。
同时,它会关闭连接并记录日志。
*/
static void ServiceUnavailable(int lineno){
  StartResponse("503 Service Unavailable");
  nOut += althttpd_printf(
    "Content-type: text/plain; charset=utf-8\r\n"
    "\r\n"
    "Service to IP address %s temporarily blocked due to abuse\n",
    zRemoteAddr
  );
  closeConnection = 1;
  MakeLogEntry(0, lineno);
  althttpd_exit();
}

SetEnv

这个函数负责设置环境变量的值,并在检测到可能的攻击时采取相应的安全措施。

/* Set the value of environment variable zVar to zValue.
设置环境变量 zVar 的值为 zValue。*/
static void SetEnv(const char *zVar, const char *zValue){
  char *z;
  size_t len;
  if( zValue==0 ) zValue="";
  /* Disable an attempted bashdoor attack 禁用尝试的 bashdoor 攻击 */
  if( strncmp(zValue,"() {",4)==0 ){//检查 zValue 是否以 "()" 开头,这是一种 bashdoor 攻击的尝试。
    BlockIPAddress();
    ServiceUnavailable(902); /* LOG: 902 bashdoor attack */
    zValue = "";
  }
  len = strlen(zVar) + strlen(zValue) + 2;
  z = SafeMalloc(len);
  sprintf(z,"%s=%s",zVar,zValue);
  putenv(z);
}

GetFirstElement

从输入字符串中提取第一个空格分隔的标记,并返回指向该标记的指针。

/*
** Remove the first space-delimited token from a string and return
** a pointer to it.  Add a NULL to the string to terminate the token.
** Make *zLeftOver point to the start of the next token.
** 从字符串中移除第一个空格分隔的标记并返回指向它的指针。
** 在字符串末尾添加一个 NULL 以终止标记。
** 使 *zLeftOver 指向下一个标记的开始。
*/
static char *GetFirstElement(char *zInput, char **zLeftOver){
  char *zResult = 0;
  if( zInput==0 ){
    if( zLeftOver ) *zLeftOver = 0;
    return 0;
  }
  while( isspace(*(unsigned char*)zInput) ){ zInput++; }
  zResult = zInput;
  while( *zInput && !isspace(*(unsigned char*)zInput) ){ zInput++; }
  if( *zInput ){
    *zInput = 0;
    zInput++;
    while( isspace(*(unsigned char*)zInput) ){ zInput++; }
  }
  if( zLeftOver ){ *zLeftOver = zInput; }
  return zResult;
}

StrDup,StrAppend

/* Make a copy of a string into memory obtained from malloc.
   将字符串复制到使用 malloc 获得的内存中。*/
static char *StrDup(const char *zSrc){
  char *zDest;
  size_t size;

  if( zSrc==0 ) return 0;
  size = strlen(zSrc) + 1;
  zDest = (char*)SafeMalloc( size );
  strcpy(zDest,zSrc);
  return zDest;
}
//将字符串附加到先前的字符串中,使用指定的分隔符。
static char *StrAppend(char *zPrior, const char *zSep, const char *zSrc){
  char *zDest;
  size_t size;
  size_t n0, n1, n2;

  if( zSrc==0 ) return 0;
  if( zPrior==0 ) return StrDup(zSrc);
  n0 = strlen(zPrior);
  n1 = strlen(zSep);
  n2 = strlen(zSrc);
  size = n0+n1+n2+1;
  zDest = (char*)SafeMalloc( size );
  memcpy(zDest, zPrior, n0);
  free(zPrior);
  memcpy(&zDest[n0],zSep,n1);
  memcpy(&zDest[n0+n1],zSrc,n2+1);
  return zDest;
}

ComputeRequestUri,CompareEtags,RemoveNewline,Rfc822Date,DateTag

/*
** Construct the REQUEST_URI value from zString and zQueryString.
** 从 zString 和 zQueryString 构造 REQUEST_URI 值。

** REQUEST_URI is nominally the second field of the first line of the
** HTTP request.  But we might have done some sanitization on the
** SCRIPT_NAME and/or PATH_INFO and we want to capture that in the
** REQUEST_URI.  Hence, the REQUEST_URI is recomputed before being
** sent to CGI or SCGI.
** REQUEST_URI 名义上是 HTTP 请求的第一行的第二个字段。但我们可能对 SCRIPT_NAME 和/或 PATH_INFO 进行了一些净化,
** 我们希望在 REQUEST_URI 中捕获这些内容。因此,在发送到 CGI 或 SCGI 之前,会重新计算 REQUEST_URI。
*/
static void ComputeRequestUri(void){
  if( zQueryString==0 || zQueryString[0]==0 ){
    zRequestUri = zScript;
  }else{
    zRequestUri = StrAppend(zScript, "?", zQueryString);
  }
}

/* Compare two ETag values. Return 0 if they match and non-zero if they differ.
** The one on the left might be a NULL pointer and it might be quoted.
比较两个 ETag 值。如果它们匹配则返回 0,否则返回非零。
左边的值可能是 NULL 指针,也可能带引号。
*/
static int CompareEtags(const char *zA, const char *zB){
  if( zA==0 ) return 1;
  if( zA[0]=='"' ){
    int lenB = (int)strlen(zB);
    if( strncmp(zA+1, zB, lenB)==0 && zA[lenB+1]=='"' ) return 0;
  }
  return strcmp(zA, zB);
}

/* Break a line at the first \n or \r character seen.
在第一个看到的 \n 或 \r 字符处中断一行。*/
static void RemoveNewline(char *z){
  if( z==0 ) return;
  while( *z && *z!='\n' && *z!='\r' ){ z++; }
  *z = 0;
}

RFC822-formatted timestamp


/* Render seconds since 1970 as an RFC822 date string.  
** Return a pointer to that string in a static buffer.
将自1970年以来的秒数呈现为 RFC822 日期字符串。
返回指向静态缓冲区中该字符串的指针。
*/
static char *Rfc822Date(time_t t){
  struct tm *tm;
  static char zDate[100];
  tm = gmtime(&t);
  strftime(zDate, sizeof(zDate), "%a, %d %b %Y %H:%M:%S GMT", tm);
  return zDate;
}

/* Print a date tag in the header.  The name of the tag is zTag.
** The date is determined from the unix timestamp given.
** 在头部打印日期标签。标签的名称是 zTag。
** 日期是从给定的 Unix 时间戳确定的。
*/
static int DateTag(const char *zTag, time_t t){
  return althttpd_printf("%s: %s\r\n", zTag, Rfc822Date(t));
}
/*
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
** a Unix epoch time. <= zero is returned on failure.
*/
time_t ParseRfc822Date(const char *zDate){
  int mday, mon, year, yday, hour, min, sec;
  char zIgnore[4];
  char zMonth[4];
  static const char *const azMonths[] =
    {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
                       &mday, zMonth, &year, &hour, &min, &sec)){
    if( year > 1900 ) year -= 1900;
    for(mon=0; mon<12; mon++){
      if( !strncmp( azMonths[mon], zMonth, 3 )){
        int nDay;
        int isLeapYr;
        static int priorDays[] =
         {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
        isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
        yday = priorDays[mon] + mday - 1;
        if( isLeapYr && mon>1 ) yday++;
        nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
        return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
      }
    }
  }
  return 0;
}

/* Test procedure for ParseRfc822Date */
void TestParseRfc822Date(void){
  time_t t1, t2;
  for(t1=0; t1<0x7fffffff; t1 += 127){
    t2 = ParseRfc822Date(Rfc822Date(t1));
    assert( t1==t2 );
  }
}

StartResponse

/* Print the first line of a response followed by the server type.
打印响应的第一行,后面跟上服务器类型。*/
static void StartResponse(const char *zResultCode){
  time_t now;
  time(&now);
  if( statusSent ) return;
  nOut += althttpd_printf("%s %s\r\n",
                          zProtocol ? zProtocol : "HTTP/1.1",
                          zResultCode);
  strncpy(zReplyStatus, zResultCode, 3);
  zReplyStatus[3] = 0;
  if( zReplyStatus[0]>='4' ){
    closeConnection = 1;
  }
  if( closeConnection ){
    nOut += althttpd_printf("Connection: close\r\n");
  }else{
    nOut += althttpd_printf("Connection: keep-alive\r\n");
  }
  nOut += DateTag("Date", now);
  statusSent = 1;
}

UnlinkExpiredIPBlockers

/*
** Check all of the files in the zIPShunDir directory.  Unlink any
** files in that directory that have expired.
** 检查 zIPShunDir 目录中的所有文件。解除任何在该目录中已过期的文件。
**
** This routine might be slow if there are a lot of blocker files.
** So it only runs when we are not in a hurry, such as prior to sending
** a 404 Not Found reply.
** 如果有很多阻止文件,这个例程可能会很慢。因此,它只在我们不急于执行时运行,
** 例如在发送 404 Not Found 回复之前。
*/
static void UnlinkExpiredIPBlockers(void){
  DIR *pDir;
  struct dirent *pFile;
  size_t nIPShunDir;
  time_t now;
  char zFilename[2000];

  if( zIPShunDir==0 ) return;
  if( zIPShunDir[0]!='/' ) return;
  nIPShunDir = strlen(zIPShunDir);
  while( nIPShunDir>0 && zIPShunDir[nIPShunDir-1]=='/' ) nIPShunDir--;
  if( nIPShunDir > sizeof(zFilename)-100 ) return;
  memcpy(zFilename, zIPShunDir, nIPShunDir);
  zFilename[nIPShunDir] = 0;
  pDir = opendir(zFilename);
  if( pDir==0 ) return;
  zFilename[nIPShunDir] = '/';
  time(&now);
  while( (pFile = readdir(pDir))!=0 ){
    size_t nFile = strlen(pFile->d_name);
    int rc;
    struct stat statbuf;
    if( nIPShunDir+nFile >= sizeof(zFilename)-2 ) continue;
    if( strstr(pFile->d_name, "..") ) continue;
    memcpy(zFilename+nIPShunDir+1, pFile->d_name, nFile+1);
    memset(&statbuf, 0, sizeof(statbuf));
    rc = stat(zFilename, &statbuf);
    if( rc ) continue;
    if( !S_ISREG(statbuf.st_mode) ) continue;
    if( statbuf.st_size==0 ) continue;
    if( statbuf.st_size*5*BANISH_TIME + statbuf.st_mtime > now ) continue;
    unlink(zFilename);// 解除文件
  }
  closedir(pDir);
}

LikelyHackAttempt 判断黑客

/* Return true if the request URI contained in zScript[] seems like a hack attempt.
   如果包含在 zScript[] 中的请求 URI 看起来像是一次黑客尝试,则返回 true。
*/
static int LikelyHackAttempt(void){
  static const char *azHackUris[] = {
     "/../",
     "/./",
     "_SELECT_",
     "_select_",
     "_sleep_",
     "_OR_",
     "_AND_",
     "/etc/passwd",
     "/bin/sh",
     "/.git/",
     "/swagger.yaml",
     "/phpThumb.php",
     "/.htpasswd",
     "/.passwd",
     "/tomcat/manager/status/",
     "/WEB-INF/jboss-web.xml",
     "/phpMyAdmin/setup/index.php",
     "/examples/feed-viewer/feed-proxy.php",
  };
  unsigned int i;
  if( zScript==0 ) return 0;
  if( zScript[0]==0 ) return 0;
  if( zScript[0]!='/' ) return 1;
  for(i=0; i<sizeof(azHackUris)/sizeof(azHackUris[0]); i++){
    if( strstr(zScript, azHackUris[i])!=0 ) return 1;
  }
  return 0;
}

NotFound,Forbidden,NotAuthorized,CgiError

几种访问错误:404、403、401、500

/* Tell the client that there is no such document
   告诉客户端找不到此文档*/
static void NotFound(int lineno){
  if( LikelyHackAttempt() ){
    BlockIPAddress();
    ServiceUnavailable(lineno);
  }
  UnlinkExpiredIPBlockers();
  StartResponse("404 Not Found");
  nOut += althttpd_printf(
    "Content-type: text/html; charset=utf-8\r\n"
    "\r\n"
    "<head><title lineno=\"%d\">Not Found</title></head>\n"
    "<body><h1>Document Not Found</h1>\n"
    "The document %s is not available on this server\n"
    "</body>\n", lineno, zScript);
  MakeLogEntry(0, lineno);
  althttpd_exit();
}

/* Tell the client that they are not welcomed here.
   告诉客户端在这里不受欢迎。*/
static void Forbidden(int lineno){
  StartResponse("403 Forbidden");
  nOut += althttpd_printf(
    "Content-type: text/plain; charset=utf-8\r\n"
    "\r\n"
    "Access denied\n"
  );
  closeConnection = 1;
  MakeLogEntry(0, lineno);
  althttpd_exit();
}

/* Tell the client that authorization is required to access the document.
   告诉客户端访问文档需要授权。*/
static void NotAuthorized(const char *zRealm){
  StartResponse("401 Authorization Required");
  nOut += althttpd_printf(
    "WWW-Authenticate: Basic realm=\"%s\"\r\n"
    "Content-type: text/html; charset=utf-8\r\n"
    "\r\n"
    "<head><title>Not Authorized</title></head>\n"
    "<body><h1>401 Not Authorized</h1>\n"
    "A login and password are required for this document\n"
    "</body>\n", zRealm);
  MakeLogEntry(0, 110);  /* LOG: Not authorized */
}

/* Tell the client that there is an error in the script.
   告诉客户端脚本中存在错误。*/
static void CgiError(void){
  StartResponse("500 Error");
  nOut += althttpd_printf(
    "Content-type: text/html; charset=utf-8\r\n"
    "\r\n"
    "<head><title>CGI Program Error</title></head>\n"
    "<body><h1>CGI Program Error</h1>\n"
    "The CGI program %s generated an error\n"
    "</body>\n", zScript);
  MakeLogEntry(0, 120);  /* LOG: CGI Error */
  althttpd_exit();
  exit(0);
}

Timeout,SetTimeout

用于处理超时设置和处理信号

/* Set the timeout in seconds.  0 means no-timeout.
   设置超时时间,以秒为单位。0表示无超时。*/
static void SetTimeout(int nSec, int lineNum){
  if( useTimeout ){
    nTimeoutLine = lineNum;
    alarm(nSec);//设置闹钟
  }
}

/* This is called if we timeout or catch some other kind of signal.
** Log an error code which is 900+iSig and then quit.
** 如果发生超时或捕获其他类型的信号,则调用此函数。
** 记录错误代码,该错误代码为 900+iSig,然后退出。
*/
static void Timeout(int iSig){
  if( !debugFlag ){
    if( zScript && zScript[0] ){
      char zBuf[10];
      zBuf[0] = '9';
      zBuf[1] = '0' + (iSig/10)%10;
      zBuf[2] = '0' + iSig%10;
      zBuf[3] = 0;
      strcpy(zReplyStatus, zBuf);
      ++inSignalHandler;
      switch( iSig ){
        case SIGALRM:
          MakeLogEntry(0, nTimeoutLine);
          break;
        case SIGSEGV:
          MakeLogEntry(0, 131);  /* LOG: SIGSEGV */
          break;
        case SIGPIPE:
          MakeLogEntry(0, 132);  /* LOG: SIGPIPE */
          break;
        case SIGXCPU:
          MakeLogEntry(0, 133);  /* LOG: SIGXCPU */
          break;
        default:
          MakeLogEntry(0, 139);  /* LOG: Unknown signal */
          break;
      }
      --inSignalHandler;
    }
    exit(0);
  }
}

Redirect,Decode64

服务器重定向
base-64 解码

/*
** Do a server redirect to the document specified.  The document
** name not contain scheme or network location or the query string.
** It will be just the path.
执行服务器重定向到指定的文档。文档名不包含方案、网络位置或查询字符串。它只是路径。
*/
static void Redirect(const char *zPath, int iStatus, int finish, int lineno){
  switch( iStatus ){
    case 301:
      StartResponse("301 Permanent Redirect");
      break;
    case 308:
      StartResponse("308 Permanent Redirect");
      break;
    default:
      StartResponse("302 Temporary Redirect");
      break;
  }
  if( zServerPort==0 || zServerPort[0]==0 || strcmp(zServerPort,"80")==0 ){
    nOut += althttpd_printf("Location: %s://%s%s%s\r\n",
                   zHttpScheme, zServerName, zPath, zQuerySuffix);
  }else{
    nOut += althttpd_printf("Location: %s://%s:%s%s%s\r\n",
                   zHttpScheme, zServerName, zServerPort, zPath, zQuerySuffix);
  }
  if( finish ){
    nOut += althttpd_printf("Content-length: 0\r\n");
    nOut += althttpd_printf("\r\n");
    MakeLogEntry(0, lineno);
  }
  fflush(stdout);
}

/*
** This function treats its input as a base-64 string and returns the
** decoded value of that string.  Characters of input that are not
** valid base-64 characters (such as spaces and newlines) are ignored.
此函数将其输入视为 base-64 字符串,并返回该字符串的解码值。
忽略不是有效 base-64 字符(如空格和换行符)的输入字符。
*/
static void Decode64(char *z64){
  char *zData;
  int n64;
  int i, j;
  int a, b, c, d;
  static int isInit = 0;
  static int trans[128];
  static unsigned char zBase[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

  if( !isInit ){
    for(i=0; i<128; i++){ trans[i] = 0; }
    for(i=0; zBase[i]; i++){ trans[zBase[i] & 0x7f] = i; }
    isInit = 1;
  }
  n64 = strlen(z64);
  while( n64>0 && z64[n64-1]=='=' ) n64--;
  zData = z64;
  for(i=j=0; i+3<n64; i+=4){
    a = trans[z64[i] & 0x7f];
    b = trans[z64[i+1] & 0x7f];
    c = trans[z64[i+2] & 0x7f];
    d = trans[z64[i+3] & 0x7f];
    zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
    zData[j++] = ((c<<6) & 0xc0) | (d & 0x3f);
  }
  if( i+2<n64 ){
    a = trans[z64[i] & 0x7f];
    b = trans[z64[i+1] & 0x7f];
    c = trans[z64[i+2] & 0x7f];
    zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
    zData[j++] = ((b<<4) & 0xf0) | ((c>>2) & 0x0f);
  }else if( i+1<n64 ){
    a = trans[z64[i] & 0x7f];
    b = trans[z64[i+1] & 0x7f];
    zData[j++] = ((a<<2) & 0xfc) | ((b>>4) & 0x03);
  }
  zData[j] = 0;
}

TLS:ssl_init_server,sslctx_use_pkey_from_mem,sslctx_use_cert_from_mem

初始化SSL服务器
加载SSL证书和私钥等操作。

#ifdef ENABLE_TLS
/* This is a self-signed cert in the PEM format that can be used when
** no other certs are available.
**
** NB: Use of this self-signed cert is wildly insecure.  Use for testing
** purposes only.
*/
static const char sslSelfCert[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIDMTCCAhkCFGrDmuJkkzWERP/ITBvzwwI2lv0TMA0GCSqGSIb3DQEBCwUAMFQx\n"
"CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzESMBAGA1UEBwwJQ2hhcmxvdHRlMRMw\n"
"EQYDVQQKDApGb3NzaWwtU0NNMQ8wDQYDVQQDDAZGb3NzaWwwIBcNMjExMjI3MTEz\n"
"MTU2WhgPMjEyMTEyMjcxMTMxNTZaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJO\n"
"QzESMBAGA1UEBwwJQ2hhcmxvdHRlMRMwEQYDVQQKDApGb3NzaWwtU0NNMQ8wDQYD\n"
"VQQDDAZGb3NzaWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCCbTU2\n"
"6GRQHQqLq7vyZ0OxpAxmgfAKCxt6eIz+jBi2ZM/CB5vVXWVh2+SkSiWEA3UZiUqX\n"
"xZlzmS/CglZdiwLLDJML8B4OiV72oivFH/vJ7+cbvh1dTxnYiHuww7GfQngPrLfe\n"
"fiIYPDk1GTUJHBQ7Ue477F7F8vKuHdVgwktF/JDM6M60aSqlo2D/oysirrb+dlur\n"
"Tlv0rjsYOfq6bLAajoL3qi/vek6DNssoywbge4PfbTgS9g7Gcgncbcet5pvaS12J\n"
"avhFcd4JU4Ity49Hl9S/C2MfZ1tE53xVggRwKz4FPj65M5uymTdcxtjKXtCxIE1k\n"
"KxJxXQh7rIYjm+RTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFkdtpqcybAzJN8G\n"
"+ONuUm5sXNbWta7JGvm8l0BTSBcCUtJA3hn16iJqXA9KmLnaF2denC4EYk+KlVU1\n"
"QXxskPJ4jB8A5B05jMijYv0nzCxKhviI8CR7GLEEGKzeg9pbW0+O3vaVehoZtdFX\n"
"z3SsCssr9QjCLiApQxMzW1Iv3od2JXeHBwfVMFrWA1VCEUCRs8OSW/VOqDPJLVEi\n"
"G6wxc4kN9dLK+5S29q3nzl24/qzXoF8P9Re5KBCbrwaHgy+OEEceq5jkmfGFxXjw\n"
"pvVCNry5uAhH5NqbXZampUWqiWtM4eTaIPo7Y2mDA1uWhuWtO6F9PsnFJlQHCnwy\n"
"s/TsrXk=\n"
"-----END CERTIFICATE-----\n";

/* This is the private-key corresponding to the cert above
*/
static const char sslSelfPKey[] =
"-----BEGIN PRIVATE KEY-----\n"
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCCbTU26GRQHQqL\n"
"q7vyZ0OxpAxmgfAKCxt6eIz+jBi2ZM/CB5vVXWVh2+SkSiWEA3UZiUqXxZlzmS/C\n"
"glZdiwLLDJML8B4OiV72oivFH/vJ7+cbvh1dTxnYiHuww7GfQngPrLfefiIYPDk1\n"
"GTUJHBQ7Ue477F7F8vKuHdVgwktF/JDM6M60aSqlo2D/oysirrb+dlurTlv0rjsY\n"
"Ofq6bLAajoL3qi/vek6DNssoywbge4PfbTgS9g7Gcgncbcet5pvaS12JavhFcd4J\n"
"U4Ity49Hl9S/C2MfZ1tE53xVggRwKz4FPj65M5uymTdcxtjKXtCxIE1kKxJxXQh7\n"
"rIYjm+RTAgMBAAECggEANfTH1vc8yIe7HRzmm9lsf8jF+II4s2705y2H5qY+cvYx\n"
"nKtZJGOG1X0KkYy7CGoFv5K0cSUl3lS5FVamM/yWIzoIex/Sz2C1EIL2aI5as6ez\n"
"jB6SN0/J+XI8+Vt7186/rHxfdIPpxuzjHbxX3HTpScETNWcLrghbrPxakbTPPxwt\n"
"+x7QlPmmkFNuMfvkzToFf9NdwL++44TeBPOpvD/Lrw+eyqdth9RJPq9cM96plh9V\n"
"HuRqeD8+QNafaXBdSQs3FJK/cDK/vWGKZWIfFVSDbDhwYljkXGijreFjtXQfkkpF\n"
"rl1J87/H9Ee7z8fTD2YXQHl+0/rghAVtac3u54dpQQKBgQC2XG3OEeMrOp9dNkUd\n"
"F8VffUg0ecwG+9L3LCe7U71K0kPmXjV6xNnuYcNQu84kptc5vI8wD23p29LaxdNc\n"
"9m0lcw06/YYBOPkNphcHkINYZTvVJF10mL3isymzMaTtwDkZUkOjL1B+MTiFT/qp\n"
"ARKrTYGJ4HxY7+tUkI5pUmg4PQKBgQC3GA4d1Rz3Pb/RRpcsZgWknKsKhoN36mSn\n"
"xFJ3wPBvVv2B1ltTMzh/+the0ty6clzMrvoLERzRcheDsNrc/j/TUVG8sVdBYJwX\n"
"tMZyFW4NVMOErT/1ukh6jBqIMBo6NJL3EV/AKj0yniksgKOr0/AAduAccnGST8Jd\n"
"SHOdjwvHzwKBgGZBq/zqgNTDuYseHGE07CMgcDWkumiMGv8ozlq3mSR0hUiPOTPP\n"
"YFjQjyIdPXnF6FfiyPPtIvgIoNK2LVAqiod+XUPf152l4dnqcW13dn9BvOxGyPTR\n"
"lWCikFaAFviOWjY9r9m4dU1dslDmySqthFd0TZgPvgps9ivkJ0cdw30NAoGAMC/E\n"
"h1VvKiK2OP27C5ROJ+STn1GHiCfIFd81VQ8SODtMvL8NifgRBp2eFFaqgOdYRQZI\n"
"CGGYlAbS6XXCJCdF5Peh62dA75PdgN+y2pOJQzjrvB9cle9Q4++7i9wdCvSLOTr5\n"
"WDnFoWy+qVexu6crovOmR9ZWzYrwPFy1EOJ010ECgYBl7Q+jmjOSqsVwhFZ0U7LG\n"
"diN+vXhWfn1wfOWd8u79oaqU/Oy7xyKW2p3H5z2KFrBM/vib53Lh4EwFZjcX+jVG\n"
"krAmbL+M/hP7z3TD2UbESAzR/c6l7FU45xN84Lsz5npkR8H/uAHuqLgb9e430Mjx\n"
"YNMwdb8rChHHChNZu6zuxw==\n"
"-----END PRIVATE KEY-----\n";

/*
** Read a PEM certificate from memory and push it into an SSL_CTX.
** Return the number of errors.
*/
static int sslctx_use_cert_from_mem(
  SSL_CTX *ctx,
  const char *pData,
  int nData
){
  BIO *in;
  int rc = 1;
  X509 *x = 0;
  X509 *cert = 0;

  in = BIO_new_mem_buf(pData, nData);
  if( in==0 ) goto end_of_ucfm;
  x = X509_new();
  if( x==0 ) goto end_of_ucfm;
  cert = PEM_read_bio_X509(in, &x, 0, 0);
  if( cert==0 ) goto end_of_ucfm;
  rc = SSL_CTX_use_certificate(ctx, x)<=0;
end_of_ucfm:
  X509_free(x);
  BIO_free(in);
  return rc;
}

/*
** Read a PEM private key from memory and add it to an SSL_CTX.
** Return the number of errors.
*/
static int sslctx_use_pkey_from_mem(
  SSL_CTX *ctx,
  const char *pData,
  int nData
){
  int rc = 1;
  BIO *in;
  EVP_PKEY *pkey = 0;

  in = BIO_new_mem_buf(pData, nData);
  if( in==0 ) goto end_of_upkfm;
  pkey = PEM_read_bio_PrivateKey(in, 0, 0, 0);
  if( pkey==0 ) goto end_of_upkfm;
  rc = SSL_CTX_use_PrivateKey(ctx, pkey)<=0;
  EVP_PKEY_free(pkey);
end_of_upkfm:
  BIO_free(in);
  return rc;
}

/*
** Initialize the SSL library so that it is able to handle
** server-side connections.  Invokes Malfunction() if there are
** any problems (so does not return on error).
**
** If zKeyFile and zCertFile are not NULL, then they are the names
** of disk files that hold the certificate and private-key for the
** server.  If zCertFile is not NULL but zKeyFile is NULL, then
** zCertFile is assumed to be a concatenation of the certificate and
** the private-key in the PEM format.
**
** If zCertFile is "unsafe-builtin" then a built-in self-signed cert
** is used and zKeyFile is ignored.
**
** Error messages may contain the paths to the given files, but this
** function is called before the server starts listening for requests,
** so those will never be sent to clients.
*/
static void ssl_init_server(const char *zCertFile,
                            const char *zKeyFile){
  if( tlsState.isInit==0 ){
    const int useSelfSigned = zCertFile
      && 0==strcmp("unsafe-builtin", zCertFile);
    SSL_library_init();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();
    tlsState.ctx = SSL_CTX_new(SSLv23_server_method());
    if( tlsState.ctx==0 ){
      ERR_print_errors_fp(stderr);
      Malfunction(501,   /* LOG: Error initializing the SSL Server */
           "Error initializing the SSL server");
    }
    if( !useSelfSigned && zCertFile && zCertFile[0] ){
      if( SSL_CTX_use_certificate_chain_file(tlsState.ctx,
                                             zCertFile)!=1 ){
        ERR_print_errors_fp(stderr);
        Malfunction(502,  /* LOG: Error loading CERT file */
           "Error loading CERT file \"%s\"", zCertFile);
      }
      if( zKeyFile==0 ) zKeyFile = zCertFile;
      if( SSL_CTX_use_PrivateKey_file(tlsState.ctx, zKeyFile,
                                      SSL_FILETYPE_PEM)<=0 ){
        ERR_print_errors_fp(stderr);
        Malfunction(503,  /* LOG: Error loading private key file */
            "Error loading PRIVATE KEY from file \"%s\"",
            zKeyFile);
      }
    }else if( useSelfSigned ){
      if(sslctx_use_cert_from_mem(tlsState.ctx, sslSelfCert, -1)
         || sslctx_use_pkey_from_mem(tlsState.ctx, sslSelfPKey, -1) ){
        Malfunction(504,  /* LOG: Error loading self-signed cert */
           "Error loading self-signed CERT");
      }
    }else{
      Malfunction(505,"No certificate TLS specified"); /* LOG: No cert */
    }
    if( !SSL_CTX_check_private_key(tlsState.ctx) ){
      Malfunction(506,  /* LOG: private key does not match cert */
           "PRIVATE KEY \"%s\" does not match CERT \"%s\"",
           zKeyFile, zCertFile);
    }
    SSL_CTX_set_mode(tlsState.ctx, SSL_MODE_AUTO_RETRY);
    tlsState.isInit = 2;
  }else{
    assert( tlsState.isInit==2 );
  }
}
#endif /*ENABLE_TLS*/

CheckBasicAuthorization

验证用户是否具有访问权限。
从给定的文件(zAuthFile)中读取授权信息。支持的授权信息包括用户名和密码(“user NAME LOGIN:PASSWORD”),以及其他指令,如指定域(“realm TEXT”)、要求重定向至HTTPS(“http-redirect”)、仅允许HTTPS操作(“https-only”)等。

/*
** Check to see if basic authorization credentials are provided for
** the user according to the information in zAuthFile.  Return true
** if authorized.  Return false if not authorized.
**
** File format:
**
**    *  Blank lines and lines that begin with '#' are ignored
**    *  "http-redirect" forces a redirect to HTTPS if not there already
**    *  "https-only" disallows operation in HTTP
**    *  "user NAME LOGIN:PASSWORD" checks to see if LOGIN:PASSWORD 
**       authorization credentials are provided, and if so sets the
**       REMOTE_USER to NAME.
**    *  "realm TEXT" sets the realm to TEXT.
**    *  "anyone" bypasses authentication and allows anyone to see the
**       files.  Useful in combination with "http-redirect"
*/
static int CheckBasicAuthorization(const char *zAuthFile){
  FILE *in;
  char *zRealm = "unknown realm";
  char *zLoginPswd;
  char *zName;
  char zLine[2000];

  in = fopen(zAuthFile, "rb");
  if( in==0 ){
    NotFound(150);  /* LOG: Cannot open -auth file */
    return 0;
  }
  if( zAuthArg ) Decode64(zAuthArg);
  while( fgets(zLine, sizeof(zLine), in) ){
    char *zFieldName;
    char *zVal;

    zFieldName = GetFirstElement(zLine,&zVal);
    if( zFieldName==0 || *zFieldName==0 ) continue;
    if( zFieldName[0]=='#' ) continue;
    RemoveNewline(zVal);
    if( strcmp(zFieldName, "realm")==0 ){
      zRealm = StrDup(zVal);
    }else if( strcmp(zFieldName,"user")==0 ){
      if( zAuthArg==0 ) continue;
      zName = GetFirstElement(zVal, &zVal);
      zLoginPswd = GetFirstElement(zVal, &zVal);
      if( zLoginPswd==0 ) continue;
      if( zAuthArg && strcmp(zAuthArg,zLoginPswd)==0 ){
        zRemoteUser = StrDup(zName);
        fclose(in);
        return 1;
      }
    }else if( strcmp(zFieldName,"https-only")==0 ){
      if( !useHttps ){
        NotFound(160);  /* LOG:  http request on https-only page */
        fclose(in);
        return 0;
      }
    }else if( strcmp(zFieldName,"http-redirect")==0 ){
      if( !useHttps ){
        zHttpScheme = "https";
        Redirect(zScript, 301, 1, 170); /* LOG: -auth redirect */
        fclose(in);
        return 0;
      }
    }else if( strcmp(zFieldName,"anyone")==0 ){
      fclose(in);
      return 1;
    }else{
      NotFound(180);  /* LOG:  malformed entry in -auth file */
      fclose(in);
      return 0;
    }
  }
  fclose(in);
  NotAuthorized(zRealm);
  return 0;
}

mimetypes

根据文件名猜测文档的MIME类型
通过对文件名后缀进行二分查找,从硬编码的MIME类型映射表中获取与文件后缀对应的MIME类型。每个MIME类型由文件后缀、后缀长度、标志位以及MIME类型本身组成。
MIME类型(Multipurpose Internet Mail Extensions)是一种在互联网上标识文件类型的标准。它最初是为电子邮件附件设计的,但后来被广泛用于HTTP协议中,以指示在Web上传输的文件的类型和性质。

/*
** Type for mapping file extensions to mimetypes and type-specific
** internal flags.
*/
typedef struct MimeTypeDef {
  const char *zSuffix;       /* The file suffix */
  unsigned char size;        /* Length of the suffix */
  unsigned char flags;       /* See the MTF_xxx flags macros */
  const char *zMimetype;     /* The corresponding mimetype */
} MimeTypeDef;


/* Flags for mimetype flags. These MUST match the values hard-coded in
** GetMimeType(). That function avoids the macros for space reasons. */
#define MTF_NOCGI      0x1   /* Never treat as CGI */
#define MTF_NOCHARSET  0x2   /* Elide charset=... from Content-Type */

/*
** Guess the mime-type of a document based on its name.
*/
const MimeTypeDef *GetMimeType(const char *zName, int nName){
  const char *z;
  int i;
  int first, last;
  int len;
  char zSuffix[20];

  /* A table of mimetypes based on file suffixes.
  ** Suffixes must be in sorted order so that we can do a binary
  ** search to find the mime-type
  */
  static const MimeTypeDef aMime[] = {
  { "ai",         2, 0x00, "application/postscript"           },
  { "aif",        3, 0x00, "audio/x-aiff"                     },
  { "aifc",       4, 0x00, "audio/x-aiff"                     },
  { "aiff",       4, 0x00, "audio/x-aiff"                     },
  { "arj",        3, 0x00, "application/x-arj-compressed"     },
  { "asc",        3, 0x00, "text/plain"                       },
  { "asf",        3, 0x00, "video/x-ms-asf"                   },
  { "asx",        3, 0x00, "video/x-ms-asx"                   },
  { "au",         2, 0x00, "audio/ulaw"                       },
  { "avi",        3, 0x00, "video/x-msvideo"                  },
  { "bat",        3, 0x00, "application/x-msdos-program"      },
  { "bcpio",      5, 0x00, "application/x-bcpio"              },
  { "bin",        3, 0x00, "application/octet-stream"         },
  { "c",          1, 0x00, "text/plain"                       },
  { "cc",         2, 0x00, "text/plain"                       },
  { "ccad",       4, 0x00, "application/clariscad"            },
  { "cdf",        3, 0x00, "application/x-netcdf"             },
  { "class",      5, 0x00, "application/octet-stream"         },
  { "cod",        3, 0x00, "application/vnd.rim.cod"          },
  { "com",        3, 0x00, "application/x-msdos-program"      },
  { "cpio",       4, 0x00, "application/x-cpio"               },
  { "cpt",        3, 0x00, "application/mac-compactpro"       },
  { "csh",        3, 0x00, "application/x-csh"                },
  { "css",        3, 0x00, "text/css"                         },
  { "dcr",        3, 0x00, "application/x-director"           },
  { "deb",        3, 0x00, "application/x-debian-package"     },
  { "dir",        3, 0x00, "application/x-director"           },
  { "dl",         2, 0x00, "video/dl"                         },
  { "dms",        3, 0x00, "application/octet-stream"         },
  { "doc",        3, 0x00, "application/msword"               },
  { "drw",        3, 0x00, "application/drafting"             },
  { "dvi",        3, 0x00, "application/x-dvi"                },
  { "dwg",        3, 0x00, "application/acad"                 },
  { "dxf",        3, 0x00, "application/dxf"                  },
  { "dxr",        3, 0x00, "application/x-director"           },
  { "eps",        3, 0x00, "application/postscript"           },
  { "etx",        3, 0x00, "text/x-setext"                    },
  { "exe",        3, 0x00, "application/octet-stream"         },
  { "ez",         2, 0x00, "application/andrew-inset"         },
  { "f",          1, 0x00, "text/plain"                       },
  { "f90",        3, 0x00, "text/plain"                       },
  { "fli",        3, 0x00, "video/fli"                        },
  { "flv",        3, 0x00, "video/flv"                        },
  { "gif",        3, 0x02, "image/gif"                        },
  { "gl",         2, 0x00, "video/gl"                         },
  { "gtar",       4, 0x00, "application/x-gtar"               },
  { "gz",         2, 0x00, "application/x-gzip"               },
  { "hdf",        3, 0x00, "application/x-hdf"                },
  { "hh",         2, 0x00, "text/plain"                       },
  { "hqx",        3, 0x00, "application/mac-binhex40"         },
  { "h",          1, 0x00, "text/plain"                       },
  { "htm",        3, 0x00, "text/html"                        },
  { "html",       4, 0x00, "text/html"                        },
  { "ice",        3, 0x00, "x-conference/x-cooltalk"          },
  { "ief",        3, 0x00, "image/ief"                        },
  { "iges",       4, 0x00, "model/iges"                       },
  { "igs",        3, 0x00, "model/iges"                       },
  { "ips",        3, 0x00, "application/x-ipscript"           },
  { "ipx",        3, 0x00, "application/x-ipix"               },
  { "jad",        3, 0x00, "text/vnd.sun.j2me.app-descriptor" },
  { "jar",        3, 0x00, "application/java-archive"         },
  { "jpeg",       4, 0x02, "image/jpeg"                       },
  { "jpe",        3, 0x00, "image/jpeg"                       },
  { "jpg",        3, 0x02, "image/jpeg"                       },
  { "js",         2, 0x00, "text/x-javascript"                },
  /* application/javascript is commonly used for JS, but the
  ** HTML spec says text/javascript is correct:
  ** https://html.spec.whatwg.org/multipage/scripting.html
  ** #scriptingLanguages:javascript-mime-type */
  { "json",       4, 0x00, "application/json"                 },
  { "kar",        3, 0x00, "audio/midi"                       },
  { "latex",      5, 0x00, "application/x-latex"              },
  { "lha",        3, 0x00, "application/octet-stream"         },
  { "lsp",        3, 0x00, "application/x-lisp"               },
  { "lzh",        3, 0x02, "application/octet-stream"         },
  { "m",          1, 0x00, "text/plain"                       },
  { "m3u",        3, 0x00, "audio/x-mpegurl"                  },
  { "man",        3, 0x00, "application/x-troff-man"          },
  { "me",         2, 0x00, "application/x-troff-me"           },
  { "mesh",       4, 0x00, "model/mesh"                       },
  { "mid",        3, 0x00, "audio/midi"                       },
  { "midi",       4, 0x02, "audio/midi"                       },
  { "mif",        3, 0x00, "application/x-mif"                },
  { "mime",       4, 0x00, "www/mime"                         },
  { "mjs",        3, 0x00, "text/javascript" /*ES6 module*/   },
  { "movie",      5, 0x00, "video/x-sgi-movie"                },
  { "mov",        3, 0x02, "video/quicktime"                  },
  { "mp2",        3, 0x02, "audio/mpeg"                       },
  { "mp3",        3, 0x02, "audio/mpeg"                       },
  { "mpeg",       4, 0x02, "video/mpeg"                       },
  { "mpe",        3, 0x00, "video/mpeg"                       },
  { "mpga",       4, 0x00, "audio/mpeg"                       },
  { "mpg",        3, 0x02, "video/mpeg"                       },
  { "ms",         2, 0x00, "application/x-troff-ms"           },
  { "msh",        3, 0x00, "model/mesh"                       },
  { "nc",         2, 0x00, "application/x-netcdf"             },
  { "oda",        3, 0x00, "application/oda"                  },
  { "ogg",        3, 0x00, "application/ogg"                  },
  { "ogm",        3, 0x00, "application/ogg"                  },
  { "pbm",        3, 0x00, "image/x-portable-bitmap"          },
  { "pdb",        3, 0x00, "chemical/x-pdb"                   },
  { "pdf",        3, 0x00, "application/pdf"                  },
  { "pgm",        3, 0x00, "image/x-portable-graymap"         },
  { "pgn",        3, 0x00, "application/x-chess-pgn"          },
  { "pgp",        3, 0x00, "application/pgp"                  },
  { "pl",         2, 0x00, "application/x-perl"               },
  { "pm",         2, 0x00, "application/x-perl"               },
  { "png",        3, 0x02, "image/png"                        },
  { "pnm",        3, 0x00, "image/x-portable-anymap"          },
  { "pot",        3, 0x00, "application/mspowerpoint"         },
  { "ppm",        3, 0x00, "image/x-portable-pixmap"          },
  { "pps",        3, 0x00, "application/mspowerpoint"         },
  { "ppt",        3, 0x00, "application/mspowerpoint"         },
  { "ppz",        3, 0x00, "application/mspowerpoint"         },
  { "pre",        3, 0x00, "application/x-freelance"          },
  { "prt",        3, 0x00, "application/pro_eng"              },
  { "ps",         2, 0x00, "application/postscript"           },
  { "qt",         2, 0x00, "video/quicktime"                  },
  { "ra",         2, 0x00, "audio/x-realaudio"                },
  { "ram",        3, 0x00, "audio/x-pn-realaudio"             },
  { "rar",        3, 0x00, "application/x-rar-compressed"     },
  { "ras",        3, 0x00, "image/cmu-raster"                 },
  { "rgb",        3, 0x00, "image/x-rgb"                      },
  { "rm",         2, 0x00, "audio/x-pn-realaudio"             },
  { "roff",       4, 0x00, "application/x-troff"              },
  { "rpm",        3, 0x00, "audio/x-pn-realaudio-plugin"      },
  { "rtf",        3, 0x00, "text/rtf"                         },
  { "rtx",        3, 0x00, "text/richtext"                    },
  { "scm",        3, 0x00, "application/x-lotusscreencam"     },
  { "set",        3, 0x00, "application/set"                  },
  { "sgml",       4, 0x00, "text/sgml"                        },
  { "sgm",        3, 0x00, "text/sgml"                        },
  { "sh",         2, 0x00, "application/x-sh"                 },
  { "shar",       4, 0x00, "application/x-shar"               },
  { "silo",       4, 0x00, "model/mesh"                       },
  { "sit",        3, 0x00, "application/x-stuffit"            },
  { "skd",        3, 0x00, "application/x-koan"               },
  { "skm",        3, 0x00, "application/x-koan"               },
  { "skp",        3, 0x00, "application/x-koan"               },
  { "skt",        3, 0x00, "application/x-koan"               },
  { "smi",        3, 0x00, "application/smil"                 },
  { "smil",       4, 0x00, "application/smil"                 },
  { "snd",        3, 0x00, "audio/basic"                      },
  { "sol",        3, 0x00, "application/solids"               },
  { "spl",        3, 0x00, "application/x-futuresplash"       },
  { "src",        3, 0x00, "application/x-wais-source"        },
  { "step",       4, 0x00, "application/STEP"                 },
  { "stl",        3, 0x00, "application/SLA"                  },
  { "stp",        3, 0x00, "application/STEP"                 },
  { "sv4cpio",    7, 0x00, "application/x-sv4cpio"            },
  { "sv4crc",     6, 0x00, "application/x-sv4crc"             },
  { "svg",        3, 0x00, "image/svg+xml"                    },
  { "swf",        3, 0x00, "application/x-shockwave-flash"    },
  { "t",          1, 0x00, "application/x-troff"              },
  { "tar",        3, 0x00, "application/x-tar"                },
  { "tcl",        3, 0x00, "application/x-tcl"                },
  { "tex",        3, 0x00, "application/x-tex"                },
  { "texi",       4, 0x00, "application/x-texinfo"            },
  { "texinfo",    7, 0x00, "application/x-texinfo"            },
  { "tgz",        3, 0x00, "application/x-tar-gz"             },
  { "tiff",       4, 0x00, "image/tiff"                       },
  { "tif",        3, 0x00, "image/tiff"                       },
  { "tr",         2, 0x00, "application/x-troff"              },
  { "tsi",        3, 0x00, "audio/TSP-audio"                  },
  { "tsp",        3, 0x00, "application/dsptype"              },
  { "tsv",        3, 0x00, "text/tab-separated-values"        },
  { "txt",        3, 0x00, "text/plain"                       },
  { "unv",        3, 0x00, "application/i-deas"               },
  { "ustar",      5, 0x00, "application/x-ustar"              },
  { "vcd",        3, 0x00, "application/x-cdlink"             },
  { "vda",        3, 0x00, "application/vda"                  },
  { "viv",        3, 0x00, "video/vnd.vivo"                   },
  { "vivo",       4, 0x00, "video/vnd.vivo"                   },
  { "vrml",       4, 0x00, "model/vrml"                       },
  { "vsix",       4, 0x00, "application/vsix"                 },
  { "wasm",       4, 0x03, "application/wasm"                 },
  { "wav",        3, 0x00, "audio/x-wav"                      },
  { "wax",        3, 0x00, "audio/x-ms-wax"                   },
  { "wiki",       4, 0x00, "application/x-fossil-wiki"        },
  { "wma",        3, 0x00, "audio/x-ms-wma"                   },
  { "wmv",        3, 0x00, "video/x-ms-wmv"                   },
  { "wmx",        3, 0x00, "video/x-ms-wmx"                   },
  { "wrl",        3, 0x00, "model/vrml"                       },
  { "wvx",        3, 0x00, "video/x-ms-wvx"                   },
  { "xbm",        3, 0x00, "image/x-xbitmap"                  },
  { "xlc",        3, 0x00, "application/vnd.ms-excel"         },
  { "xll",        3, 0x00, "application/vnd.ms-excel"         },
  { "xlm",        3, 0x00, "application/vnd.ms-excel"         },
  { "xls",        3, 0x00, "application/vnd.ms-excel"         },
  { "xlw",        3, 0x00, "application/vnd.ms-excel"         },
  { "xml",        3, 0x00, "text/xml"                         },
  { "xpm",        3, 0x00, "image/x-xpixmap"                  },
  { "xwd",        3, 0x00, "image/x-xwindowdump"              },
  { "xyz",        3, 0x00, "chemical/x-pdb"                   },
  { "zip",        3, 0x00, "application/zip"                  },
  };

  for(i=nName-1; i>0 && zName[i]!='.'; i--){}
  z = &zName[i+1];
  len = nName - i;
  if( len<(int)sizeof(zSuffix)-1 ){
    strcpy(zSuffix, z);
    for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]);
    first = 0;
    last = sizeof(aMime)/sizeof(aMime[0]);
    while( first<=last ){
      int c;
      i = (first+last)/2;
      c = strcmp(zSuffix, aMime[i].zSuffix);
      if( c==0 ) return &aMime[i];
      if( c<0 ){
        last = i-1;
      }else{
        first = i+1;
      }
    }
  }
  return 0;
}

sanitizeString

URL处理和清理

/*
** The following table contains 1 for all characters that are permitted in
** the part of the URL before the query parameters and fragment.
**
** Allowed characters:  0-9a-zA-Z,-./:_~
**
** Disallowed characters include:  !"#$%&'()*+;<=>?@[\]^`{|}
*/
static const char allowedInName[] = {
      /*  x0  x1  x2  x3  x4  x5  x6  x7  x8  x9  xa  xb  xc  xd  xe  xf */
/* 0x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 1x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 2x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
/* 3x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,
/* 4x */   0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
/* 5x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  1,
/* 6x */   0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
/* 7x */   1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  1,  0,
/* 8x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 9x */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Ax */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Bx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Cx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Dx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Ex */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* Fx */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
};

/*
** Remove all disallowed characters in the input string z[].  Convert any
** disallowed characters into "_".
**
** Not that the three character sequence "%XX" where X is any byte is
** converted into a single "_" character.
**
** Return the number of characters converted.  An "%XX" -> "_" conversion
** counts as a single character.
*/
static int sanitizeString(char *z){//替换非法字符
  int nChange = 0;
  while( *z ){
    if( !allowedInName[*(unsigned char*)z] ){
      char cNew = '_';
      if( *z=='%' && z[1]!=0 && z[2]!=0 ){
        int i;
        if( z[1]=='2' ){
          if( z[2]=='e' || z[2]=='E' ) cNew = '.';
          if( z[2]=='f' || z[2]=='F' ) cNew = '/';
        }
        for(i=3; (z[i-2] = z[i])!=0; i++){}
      }
      *z = cNew;
      nChange++;
    }
    z++;
  }
  return nChange;
}

/*
** Count the number of "/" characters in a string.
*/
static int countSlashes(const char *z){
  int n = 0;
  while( *z ) if( *(z++)=='/' ) n++;
  return n;
}

tls_new_server,tls_close_server,tls_atexit

创建tls服务端

#ifdef ENABLE_TLS
/*
** Create a new server-side codec.  The argument is the socket's file
** descriptor from which the codec reads and writes. The returned
** memory must eventually be passed to tls_close_server().
*/
static void *tls_new_server(int iSocket){
  TlsServerConn *pServer = malloc(sizeof(*pServer));
  BIO *b = pServer ? BIO_new_socket(iSocket, 0) : NULL;
  if( NULL==b ){
    Malfunction(507,"Cannot allocate TlsServerConn."); /* LOG: TlsServerConn */
  }
  assert(NULL!=tlsState.ctx);
  pServer->ssl = SSL_new(tlsState.ctx);
  pServer->bio = b;
  pServer->iSocket = iSocket;
  SSL_set_bio(pServer->ssl, b, b);
  SSL_accept(pServer->ssl);
  return (void*)pServer;
}

/*
** Close a server-side code previously returned from tls_new_server().
*/
static void tls_close_server(void *pServerArg){
  TlsServerConn *pServer = (TlsServerConn*)pServerArg;
  SSL_free(pServer->ssl);
  memset(pServer, 0, sizeof(TlsServerConn));
  free(pServer);
}

static void tls_atexit(void){
#if 0
  /*
  ** Shutting down TLS can lead to spurious hung processes on some
  ** platforms/builds.  See the long discussion on this at:
  ** https://sqlite.org/althttpd/forumpost/4dc31619341ce947
  */
  if( inSignalHandler==0 && tlsState.sslCon!=0 ){
    tls_close_server(tlsState.sslCon);
    tlsState.sslCon = NULL;
  }
#endif
}
#endif /* ENABLE_TLS */

althttpd_fgets,althttpd_fread,althttpd_fwrite,althttpd_fflush

封装fgets、tls_gets为althttpd_fgets
fread、fwrite、fflush同理

/*
** Works like fgets():
**
** Read a single line of input into s[].  Ensure that s[] is zero-terminated.
** The s[] buffer is size bytes and so at most size-1 bytes will be read.
**
** Return a pointer to s[] on success, or NULL at end-of-input.
**
** If in TLS mode, the final argument is ignored and the TLS
** connection is read instead.
*/
static char *althttpd_fgets(char *s, int size, FILE *in){
  if( useHttps!=2 ){
    return fgets(s, size, in);
  }
#ifdef ENABLE_TLS
  assert(NULL!=tlsState.sslCon);
  return tls_gets(tlsState.sslCon, s, size);
#else
  Malfunction(508,"SSL not available"); /* LOG: SSL not available */
  return NULL;
#endif
}
/*
** Works like fread() but may, depending on connection state, use
** libssl to read the data (in which case the final argument is
** ignored). The target buffer must be at least (sz*nmemb) bytes.
*/
static size_t althttpd_fread(void *tgt, size_t sz, size_t nmemb, FILE *in){
  if( useHttps!=2 ){
    return fread(tgt, sz, nmemb, in);
  }
#ifdef ENABLE_TLS
  assert(NULL!=tlsState.sslCon);
  return tls_read_server(tlsState.sslCon, tgt, sz*nmemb);
#else
  Malfunction(509,"SSL not available"); /* LOG: SSL not available */
  return 0;
#endif
}

/*
** Works like fwrite() but may, depending on connection state, write to
** the active TLS connection (in which case the final argument is
** ignored).
** 
*/
static size_t althttpd_fwrite(
  void const *src,          /* Buffer containing content to write */
  size_t sz,                /* Size of each element in the buffer */
  size_t nmemb,             /* Number of elements to write */
  FILE *out                 /* Write on this stream */
){
  if( useHttps!=2 ){
    return fwrite(src, sz, nmemb, out);
  }
#ifdef ENABLE_TLS
  assert(NULL!=tlsState.sslCon);
  return tls_write_server(tlsState.sslCon, src, sz*nmemb);
#else
  Malfunction(510,"SSL not available"); /* LOG: SSL not available */
  return 0;
#endif
}

/*
** In non-builtin-TLS mode, fflush()es the given FILE handle, else
** this is a no-op.
*/
static void althttpd_fflush(FILE *f){
  if( useHttps!=2 ){
    fflush(f);
  }
}

xferBytes

/*
** Transfer nXfer bytes from in to out, after first discarding
** nSkip bytes from in.  Increment the nOut global variable
** according to the number of bytes transferred.
** 从输入流in中跳过nSkip字节后,将nXfer字节从in传输到out。根据传输的字节数,增加全局变量nOut。
**
** When running in built-in TLS mode the 2nd argument is ignored and
** output is instead sent via the TLS connection.
** 当以内建TLS模式运行时,第二个参数会被忽略,输出将通过TLS连接发送。
*/
static void xferBytes(FILE *in, FILE *out, int nXfer, int nSkip){
  size_t n;
  size_t got;
  char zBuf[16384];
  while( nSkip>0 ){
    n = nSkip;
    if( n>sizeof(zBuf) ) n = sizeof(zBuf);
    got = fread(zBuf, 1, n, in);
    if( got==0 ) break;
    nSkip -= got;
  }
  while( nXfer>0 ){
    n = nXfer;
    if( n>sizeof(zBuf) ) n = sizeof(zBuf);
    got = fread(zBuf, 1, n, in);
    if( got==0 ) break;
    althttpd_fwrite(zBuf, got, 1, out);
    nOut += got;
    nXfer -= got;
  }
}

SendFile


/*
** Send the text of the file named by zFile as the reply.  Use the
** suffix on the end of the zFile name to determine the mimetype.
** 将名为zFile的文件的文本内容作为回复发送。使用zFile名称末尾的后缀确定MIME类型。
**
** Return 1 to omit making a log entry for the reply.
** 返回1以避免为回复生成日志条目。
*/
static int SendFile(
  const char *zFile,      /* Name of the file to send */
  int lenFile,            /* Length of the zFile name in bytes */
  struct stat *pStat      /* Result of a stat() against zFile */
){
  const char *zContentType;
  time_t t;
  FILE *in;
  size_t szFilename;
  char zETag[100];
  const MimeTypeDef *pMimeType;//根据文件名的后缀确定MIME类型
  int bAddCharset = 1;
  const char *zEncoding = 0;
  struct stat statbuf;
  char zGzFilename[2000];

  pMimeType = GetMimeType(zFile, lenFile);
  zContentType = pMimeType
    ? pMimeType->zMimetype : "application/octet-stream";
  if( pMimeType && (MTF_NOCHARSET & pMimeType->flags) ){
    bAddCharset = 0;
  }
  if( zPostData ){ free(zPostData); zPostData = 0; }
  sprintf(zETag, "m%xs%x", (int)pStat->st_mtime, (int)pStat->st_size);
  if( CompareEtags(zIfNoneMatch,zETag)==0
   || (zIfModifiedSince!=0
        && (t = ParseRfc822Date(zIfModifiedSince))>0
        && t>=pStat->st_mtime)
  ){
    StartResponse("304 Not Modified");
    nOut += DateTag("Last-Modified", pStat->st_mtime);
    nOut += althttpd_printf("Cache-Control: max-age=%d\r\n", mxAge);
    nOut += althttpd_printf("ETag: \"%s\"\r\n", zETag);
    nOut += althttpd_printf("\r\n");
    fflush(stdout);
    MakeLogEntry(0, 470);  /* LOG: ETag Cache Hit */
    return 1;
  }
  if( rangeEnd<=0
   && zAcceptEncoding
   && strstr(zAcceptEncoding,"gzip")!=0
  ){
    szFilename = strlen(zFile);
    if( szFilename < sizeof(zGzFilename)-10 ){
      memcpy(zGzFilename, zFile, szFilename);
      memcpy(zGzFilename + szFilename, ".gz", 4);
      if( access(zGzFilename, R_OK)==0 ){
        memset(&statbuf, 0, sizeof(statbuf));
        if( stat(zGzFilename, &statbuf)==0 ){
          zEncoding = "gzip";
          zFile = zGzFilename;
          pStat = &statbuf;
        }
      }
    }
  }
  in = fopen(zFile,"rb");
  if( in==0 ) NotFound(480); /* LOG: fopen() failed for static content */
  if( rangeEnd>0 && rangeStart<pStat->st_size ){
    StartResponse("206 Partial Content");
    if( rangeEnd>=pStat->st_size ){
      rangeEnd = pStat->st_size-1;
    }
    nOut += althttpd_printf("Content-Range: bytes %d-%d/%d\r\n",
                    rangeStart, rangeEnd, (int)pStat->st_size);
    pStat->st_size = rangeEnd + 1 - rangeStart;
  }else{
    StartResponse("200 OK");
    rangeStart = 0;
  }
  nOut += DateTag("Last-Modified", pStat->st_mtime);
  if( enableSAB ){
    /* The following two HTTP reply headers are required if javascript is to make use of SharedArrayBuffer
       如果要使用SharedArrayBuffer,则以下两个HTTP回复标头是必需的 */
    nOut += althttpd_printf("Cross-Origin-Opener-Policy: same-origin\r\n");
    nOut += althttpd_printf("Cross-Origin-Embedder-Policy: require-corp\r\n");
  }
  nOut += althttpd_printf("Cache-Control: max-age=%d\r\n", mxAge);
  nOut += althttpd_printf("ETag: \"%s\"\r\n", zETag);
  nOut += althttpd_printf("Content-type: %s%s\r\n",zContentType,
                          bAddCharset ? "; charset=utf-8" : "");
  if( zEncoding ){
    nOut += althttpd_printf("Content-encoding: %s\r\n", zEncoding);
  }
  nOut += althttpd_printf("Content-length: %d\r\n\r\n",(int)pStat->st_size);
  fflush(stdout);
  if( strcmp(zMethod,"HEAD")==0 ){
    MakeLogEntry(0, 2); /* LOG: Normal HEAD reply */
    fclose(in);
    fflush(stdout);
    return 1;
  }
#ifdef linux
  if( 2!=useHttps ){
    off_t offset = rangeStart;
    nOut += sendfile(fileno(stdout), fileno(in), &offset, pStat->st_size);
  }else
#endif
  {
    xferBytes(in, stdout, (int)pStat->st_size, rangeStart);
  }
  fclose(in);
  return 0;
}

stream_file

所谓的流式文件读写

/*
** Streams all contents from in to out. If in TLS mode, the
** output stream is ignored and the output instead goes to the TLS channel.
** 从输入流in中流式传输所有内容到输出流out。如果处于TLS模式,则
** 忽略输出流,而是将输出发送到TLS通道。
*/
static void stream_file(FILE * const in, FILE * const out){
  enum { STREAMBUF_SIZE = 1024 * 4 };
  char streamBuf[STREAMBUF_SIZE];
  size_t n;
  while( (n = fread(streamBuf, 1,sizeof(STREAMBUF_SIZE),in)) ){
    althttpd_fwrite(streamBuf, 1, n, out);
  }
}

CgiHandleReply、SendScgiRequest


/*
** A CGI or SCGI script has run and is sending its reply back across
** the channel "in".  Process this reply into an appropriate HTTP reply.
** Close the "in" channel when done.
** 一个CGI或SCGI脚本已经运行,并正在通过通道“in”发送其回复。
** 处理此回复以生成适当的HTTP回复。完成后关闭“in”通道。
**
** If isNPH is true, the input is assumed to be from a
** non-parsed-header CGI and is passed on as-is to stdout or the TLS
** layer, depending on the connection state.
** 如果isNPH为真,假定输入来自未解析头部的CGI,将其原样传递到stdout或TLS
** 层,具体取决于连接状态。
*/
static void CgiHandleReply(FILE *in, int isNPH){
  int seenContentLength = 0;   /* True if Content-length: header seen 如果看到Content-length:头部则为真*/
  int contentLength = 0;       /* The content length 内容长度*/
  size_t nRes = 0;             /* Bytes of payload 载荷的字节数*/
  size_t nMalloc = 0;          /* Bytes of space allocated to aRes 分配给aRes的空间字节数*/
  char *aRes = 0;              /* Payload 载荷*/
  int c;                       /* Next character from in 来自in的下一个字符*/
  char *z;                     /* Pointer to something inside of zLine zLine内的某个指针*/
  int iStatus = 0;             /* Reply status code 回复状态码*/
  char zLine[1000];            /* One line of reply from the CGI script 来自CGI脚本的回复的一行*/

  /* Set a 1-hour timeout, so that we can implement Hanging-GET or
  ** long-poll style CGIs.  The RLIMIT_CPU will serve as a safety
  ** to help prevent a run-away CGI 
    设置1小时的超时,以便我们可以实现Hanging-GET或长轮询样式的CGI。
  ** RLIMIT_CPU将作为安全机制,帮助防止运行失控的CGI */
  SetTimeout(60*60, 800); /* LOG: CGI Handler timeout CGI处理程序超时*/

  if( isNPH ){
    /*
    ** Non-parsed-header output: simply pipe it out as-is. We
    ** need to go through this routine, instead of simply exec()'ing,
    ** in order to go through the TLS output channel.
    ** 未解析头部的输出:将其原样管道传输。我们
    ** 需要通过此例程进行,而不是简单地执行exec(),
    ** 以便通过TLS输出通道。
    */
    stream_file(in, stdout);
    fclose(in);
    return;
  }

  while( fgets(zLine,sizeof(zLine),in) && !isspace((unsigned char)zLine[0]) ){
    if( strncasecmp(zLine,"Location:",9)==0 ){
      StartResponse("302 Redirect");
      RemoveNewline(zLine);
      z = &zLine[10];
      while( isspace(*(unsigned char*)z) ){ z++; }
      nOut += althttpd_printf("Location: %s\r\n",z);
      rangeEnd = 0;
    }else if( strncasecmp(zLine,"Status:",7)==0 ){
      int i;
      for(i=7; isspace((unsigned char)zLine[i]); i++){}
      strncpy(zReplyStatus, &zLine[i], 3);
      zReplyStatus[3] = 0;
      iStatus = atoi(zReplyStatus);
      if( rangeEnd==0 || (iStatus!=200 && iStatus!=206) ){
        if( iStatus==418 ){
          /* If a CGI returns a status code of 418 ("I'm a teapot", rfc2324)
          ** that is a signal from the CGI to althttpd that the request was
          ** abuse - for example an attempted SQL injection attack or
          ** similar. */
          /* 如果CGI返回状态码418("I'm a teapot",rfc2324),
          ** 这是CGI向althttpd发送的信号,表明请求被滥用 -
          ** 例如尝试的SQL注入攻击或类似的情况。*/
          BlockIPAddress();
          ServiceUnavailable(903);  /* LOG: CGI reports abuse CGI报告滥用*/
        }
        nOut += althttpd_printf("%s %s", zProtocol, &zLine[i]);
        rangeEnd = 0;
        statusSent = 1;
      }
    }else if( strncasecmp(zLine, "Content-length:", 15)==0 ){
      seenContentLength = 1;
      contentLength = atoi(zLine+15);
    }else{
      size_t nLine = strlen(zLine);
      if( nRes+nLine >= nMalloc ){
        nMalloc += nMalloc + nLine*2;
        aRes = realloc(aRes, nMalloc+1);
        if( aRes==0 ){
          Malfunction(600, "Out of memory: %d bytes", nMalloc); /* LOG: OOM 内存不足*/
        }
      }
      memcpy(aRes+nRes, zLine, nLine);
      nRes += nLine;
    }
  }
  /* Copy everything else thru without change or analysis.其余内容直接复制而不更改或分析。
  */
  if( rangeEnd>0 && seenContentLength && rangeStart<contentLength ){
    StartResponse("206 Partial Content");
    if( rangeEnd>=contentLength ){
      rangeEnd = contentLength-1;
    }
    nOut += althttpd_printf("Content-Range: bytes %d-%d/%d\r\n",
                            rangeStart, rangeEnd, contentLength);
    contentLength = rangeEnd + 1 - rangeStart;
  }else{
    StartResponse("200 OK");
  }
  if( nRes>0 ){
    aRes[nRes] = 0;
    althttpd_fwrite(aRes, nRes, 1, stdout);
    nOut += nRes;
    nRes = 0;
  }
  if( iStatus==304 ){
    nOut += althttpd_printf("\r\n\r\n");
  }else if( seenContentLength ){
    nOut += althttpd_printf("Content-length: %d\r\n\r\n", contentLength);
    xferBytes(in, stdout, contentLength, rangeStart);
  }else{
    while( (c = getc(in))!=EOF ){
      if( nRes>=nMalloc ){
        nMalloc = nMalloc*2 + 1000;
        aRes = realloc(aRes, nMalloc+1);
        if( aRes==0 ){
           Malfunction(610, "Out of memory: %d bytes", nMalloc); /* LOG: OOM */
        }
      }
      aRes[nRes++] = c;
    }
    if( nRes ){
      aRes[nRes] = 0;
      nOut += althttpd_printf("Content-length: %d\r\n\r\n", (int)nRes);
      nOut += althttpd_fwrite(aRes, nRes, 1, stdout);
    }else{
      nOut += althttpd_printf("Content-length: 0\r\n\r\n");
    }
  }
  free(aRes);
  fclose(in);
}

/*
** Send an SCGI request to a host identified by zFile and process the reply.
** 发送一个SCGI请求到由zFile标识的主机,并处理回复。
*/
static void SendScgiRequest(const char *zFile, const char *zScript){
  FILE *in;
  FILE *s;
  char *z;
  char *zHost;
  char *zPort = 0;
  char *zRelight = 0;
  char *zFallback = 0;
  int rc;
  int iSocket = -1;
  struct addrinfo hints;
  struct addrinfo *ai = 0;
  struct addrinfo *p;
  char *zHdr;
  size_t nHdr = 0;
  size_t nHdrAlloc;
  int i;
  char zLine[1000];
  char zExtra[1000];
  in = fopen(zFile, "rb");//打开SCGI配置文件
  if( in==0 ){
    Malfunction(700, "cannot open \"%s\"\n", zFile); /* LOG: cannot open file */
  }
  if( fgets(zLine, sizeof(zLine)-1, in)==0 ){
    Malfunction(701, "cannot read \"%s\"\n", zFile); /* LOG: cannot read file */
  }
  if( strncmp(zLine,"SCGI ",5)!=0 ){
    Malfunction(702, /* LOG: bad SCGI spec */
       "misformatted SCGI spec \"%s\"\n", zFile);//SCGI规范格式错误
  }
  z = zLine+5;
  zHost = GetFirstElement(z,&z);
  zPort = GetFirstElement(z,0);
  if( zHost==0 || zHost[0]==0 || zPort==0 || zPort[0]==0 ){
    Malfunction(703, /* LOG: bad SCGI spec (2) */
       "misformatted SCGI spec \"%s\"\n", zFile);//SCGI规范格式错误
  }
  // 读取SCGI配置文件的额外行
  while( fgets(zExtra, sizeof(zExtra)-1, in) ){
    char *zCmd = GetFirstElement(zExtra,&z);
    if( zCmd==0 ) continue;
    if( zCmd[0]=='#' ) continue;
    RemoveNewline(z);
    if( strcmp(zCmd, "relight:")==0 ){ 处理"relight:"指令
      free(zRelight);
      zRelight = StrDup(z);
      continue;
    }
    if( strcmp(zCmd, "fallback:")==0 ){ 处理"fallback:"指令
      free(zFallback);
      zFallback = StrDup(z);
      continue;
    }
    Malfunction(704, /* LOG: Unrecognized line in SCGI spec */
       "unrecognized line in SCGI spec: \"%s %s\"\n",
                zCmd, z ? z : "");//在SCGI规范中无法识别的行
  }
  fclose(in);// 关闭SCGI配置文件
  memset(&hints, 0, sizeof(struct addrinfo));// 初始化地址信息结构体
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;
  rc = getaddrinfo(zHost,zPort,&hints,&ai);// 解析主机名和端口号
  if( rc ){
    Malfunction(705, /* LOG: Cannot resolve SCGI server name */
       "cannot resolve SCGI server name %s:%s\n%s\n",//无法解析SCGI服务器名
                zHost, zPort, gai_strerror(rc));
  }
  while(1){  /* Exit via break */ // 循环直到成功连接或退出
    for(p=ai; p; p=p->ai_next){
      iSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
      if( iSocket<0 ) continue;
      if( connect(iSocket,p->ai_addr,p->ai_addrlen)>=0 ) break;
      close(iSocket);
    }
    if( iSocket<0 || (s = fdopen(iSocket,"r+"))==0 ){
      if( iSocket>=0 ) close(iSocket);
      if( zRelight ){// 处理relight
        rc = system(zRelight);
        if( rc ){
          Malfunction(721, /* LOG: SCGI relight failed */
             "Relight failed with %d: \"%s\"\n",
                      rc, zRelight);
        }
        free(zRelight);
        zRelight = 0;
        sleep(1);
        continue;
      }
      if( zFallback ){// 处理fallback
        struct stat statbuf;
        int rc;
        memset(&statbuf, 0, sizeof(statbuf));
        if( chdir(zDir) ){// 更改工作目录
          char zBuf[1000];
          Malfunction(720, /* LOG: chdir() failed */
               "cannot chdir to [%s] from [%s]",
               zDir, getcwd(zBuf,999));
        }
        rc = stat(zFallback, &statbuf);// 检查fallback文件是否存在
        if( rc==0 && S_ISREG(statbuf.st_mode) && access(zFallback,R_OK)==0 ){
          closeConnection = 1;
          rc = SendFile(zFallback, (int)strlen(zFallback), &statbuf);// 发送fallback文件内容
          free(zFallback);
          althttpd_exit();
        }else{
          Malfunction(706, /* LOG: bad SCGI fallback */
             "bad fallback file: \"%s\"\n", zFallback);//fallback文件错误
        }
      }
      Malfunction(707, /* LOG: Cannot open socket to SCGI 无法打开SCGI服务器套接字*/
           "cannot open socket to SCGI server %s\n",
                  zScript);
    }
    break;
  }

  nHdrAlloc = 0;// 分配HTTP头部内存
  zHdr = 0;
  if( zContentLength==0 ) zContentLength = "0";
  ComputeRequestUri();// 计算请求URI
  zScgi = "1";
  for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
    int n1, n2;
    if( cgienv[i].pzEnvValue[0]==0 ) continue;
    n1 = (int)strlen(cgienv[i].zEnvName);
    n2 = (int)strlen(*cgienv[i].pzEnvValue);
    if( n1+n2+2+nHdr >= nHdrAlloc ){
      nHdrAlloc = nHdr + n1 + n2 + 1000;
      zHdr = realloc(zHdr, nHdrAlloc);
      if( zHdr==0 ){
        Malfunction(708, "out of memory"); /* LOG: OOM */
      }
    }
    memcpy(zHdr+nHdr, cgienv[i].zEnvName, n1);
    nHdr += n1;
    zHdr[nHdr++] = 0;
    memcpy(zHdr+nHdr, *cgienv[i].pzEnvValue, n2);
    nHdr += n2;
    zHdr[nHdr++] = 0;
  }
  zScgi = 0;
  fprintf(s,"%d:",(int)nHdr);
  fwrite(zHdr, 1, nHdr, s);
  fprintf(s,",");
  free(zHdr);
  if( nPostData>0 ){// 如果有POST数据,则将其写入套接字
    size_t wrote = 0;
    while( wrote<(size_t)nPostData ){
      size_t n = fwrite(zPostData+wrote, 1, nPostData-wrote, s);
      if( n<=0 ) break;
      wrote += n;
    }
    free(zPostData);
    zPostData = 0;
    nPostData = 0;
  }
  fflush(s);
  CgiHandleReply(s, 0);// 处理SCGI回复
}

tls_init_conn tls_close_conn

/*
** If running in builtin TLS mode, initializes the SSL I/O
** state and returns 1, else does nothing and returns 0.
如果运行在内置的TLS模式下,初始化SSL I/O状态并返回1,否则什么都不做并返回0。
*/
static int tls_init_conn(int iSocket){
#ifdef ENABLE_TLS
  if( 2==useHttps ){
    /*assert(NULL==tlsState.sslCon);*/
    if( NULL==tlsState.sslCon ){
      tlsState.sslCon = (TlsServerConn *)tls_new_server(iSocket);
      if( NULL==tlsState.sslCon ){
        Malfunction(512, /* LOG: TLS context */
          "Could not instantiate TLS context.");//无法实例化TLS上下文
      }
      atexit(tls_atexit);
    }
    return 1;
  }
#else
  if( 0==iSocket ){/*unused arg*/}
#endif
  return 0;
}
static void tls_close_conn(void){//关闭TLS连接。
#ifdef ENABLE_TLS
  if( tlsState.sslCon ){
    tls_close_server(tlsState.sslCon);
    tlsState.sslCon = NULL;
  }
#endif
}

DisallowedRemoteAddr


/*
** Check to see if zRemoteAddr is disallowed.  Return true if it is disallowed and false if not.
** 检查zRemoteAddr是否被禁止。如果被禁止,则返回true,否则返回false。
**
** zRemoteAddr is disallowed if:如果zRemoteAddr被禁止,满足以下条件:
**
**    *  The zIPShunDir variable is not NULL, zIPShunDir变量不为NULL
**
**    *  zIPShunDir is the name of a directory,zIPShunDir是一个目录的名称
**
**    *  There is a file in zIPShunDir whose name is exactly zRemoteAddr and that is N bytes in size.
         在zIPShunDir目录中有一个文件,其名称恰好为zRemoteAddr 并且其大小为N字节。
**
**    *  N==0 or the mtime of the file is less than N*BANISH_TIME seconds ago.
**    *  N==0或者文件的修改时间在N*BANISH_TIME秒之前。
**
** If N>0 and the mtime is greater than N*5*BANISH_TIME seconds
** (25 minutes per byte, by default) old, then the file is deleted.
** 如果N>0且修改时间大于N*5*BANISH_TIME秒(默认每字节25分钟),则删除该文件。
**
** The size of the file determines how long the embargo is suppose to
** last.  A zero-byte file embargos forever.  Otherwise, the embargo
** is for BANISH_TIME bytes for each byte in the file.
** 文件的大小决定了禁令应该持续多长时间。大小为零的文件永远被禁止。否则,禁令
** 将持续BANISH_TIME字节乘以文件中的每个字节的时间。
*/
static int DisallowedRemoteAddr(void){
  char zFullname[1000];
  size_t nIPShunDir;
  size_t nRemoteAddr;
  int rc;
  struct stat statbuf;
  time_t now;

  if( zIPShunDir==0 ) return 0;
  if( zRemoteAddr==0 ) return 0;
  if( zIPShunDir[0]!='/' ){
    Malfunction(910, /* LOG: argument to --ipshun should be absolute path, --ipshun参数应该是绝对路径*/
       "The --ipshun directory should have an absolute path");
  }
  nIPShunDir = strlen(zIPShunDir);
  while( nIPShunDir>0 && zIPShunDir[nIPShunDir-1]=='/' ) nIPShunDir--;
  nRemoteAddr = strlen(zRemoteAddr);
  if( nIPShunDir + nRemoteAddr + 2 >= sizeof(zFullname) ){
    Malfunction(912, /* LOG: RemoteAddr filename too big, RemoteAddr文件名太大*/
       "RemoteAddr filename too big");
  }
  if( zRemoteAddr[0]==0
   || zRemoteAddr[0]=='.'
   || strchr(zRemoteAddr,'/')!=0
  ){
    Malfunction(913, /* LOG: RemoteAddr contains suspicious characters,RemoteAddr包含可疑字符 */
       "RemoteAddr contains suspicious characters");
  }
  memcpy(zFullname, zIPShunDir, nIPShunDir);
  zFullname[nIPShunDir] = '/';
  memcpy(zFullname+nIPShunDir+1, zRemoteAddr, nRemoteAddr+1);
  memset(&statbuf, 0, sizeof(statbuf));
  rc = stat(zFullname, &statbuf);
  if( rc ) return 0;  /* No such file, hence no restrictions ,没有这个文件,因此没有限制*/
  if( statbuf.st_size==0 ) return 1;  /* Permanently banned ,永久禁止*/
  time(&now);
  if( statbuf.st_size*BANISH_TIME + statbuf.st_mtime >= now ){
    return 1;  /* Currently under a ban , 当前处于禁令状态*/
  }
  if( statbuf.st_size*5*BANISH_TIME + statbuf.st_mtime < now ){
    unlink(zFullname);
  }
  return 0;
}

ProcessOneRequest

处理单个HTTP请求

/*
** This routine processes a single HTTP request on standard input and
** sends the reply to standard output.  If the argument is 1 it means
** that we are should close the socket without processing additional
** HTTP requests after the current request finishes.  0 means we are
** allowed to keep the connection open and to process additional requests.
** This routine may choose to close the connection even if the argument is 0.
该例程处理标准输入上的单个HTTP请求,并将回复发送到标准输出。
如果参数是1,表示在当前请求完成后应关闭套接字而不处理其他HTTP请求;
如果是0,则允许保持连接并处理其他请求。即使参数为0,此例程也可能选择关闭连接。
**
** If the connection should be closed, this routine calls exit() and
** thus never returns.  If this routine does return it means that another
** HTTP request may appear on the wire.
如果应关闭连接,该例程调用exit()并因此永远不返回。
如果此例程返回,意味着可能会在连接中出现另一个HTTP请求。
**
** socketId must be 0 (if running via xinetd/etc) or the socket ID
** accept()ed by http_server(). It is only used for built-in TLS mode.
socketId必须为0(如果通过xinetd等方式运行)或由http_server()接受的accept()的套接字ID。
仅在内置TLS模式下使用。
*/
void ProcessOneRequest(int forceClose, int socketId){
  int i, j, j0;
  char *z;                  /* Used to parse up a string 用于解析字符串 */
  struct stat statbuf;      /* Information about the file to be retrieved 关于要检索的文件的信息*/
  FILE *in;                 /* For reading from CGI scripts 用于从CGI脚本中读取*/
#ifdef LOG_HEADER
  FILE *hdrLog = 0;         /* Log file for complete header content 完整标头内容的日志文件*/
#endif
  char zLine[10000];        /* A buffer for input lines or forming names */
  const MimeTypeDef *pMimeType = 0; /* URI's mimetype URI的MIME类型*/
  size_t sz = 0;
  struct tm vTm;            /* Timestamp for zExpLogFile */

  if( zLogFile ){
    assert(beginTime.tv_sec > 0);
    gmtime_r(&beginTime.tv_sec, &vTm);
    sz = strftime(zExpLogFile, sizeof(zExpLogFile), zLogFile, &vTm);
  }
  if( sz==0 || sz>=sizeof(zExpLogFile)-2 ){
    /* Invalid zExpLogFile name 无效的zExpLogFile名称*/
    zExpLogFile[0] = 0;
  }
  /* Must see a header within 10 seconds for the first request.
  ** Allow up to 5 more minutes for the follow-on requests
  在前10秒内必须看到第一个请求的标头。 对于后续的请求,最多允许额外5分钟
  */
  if( useTimeout ){
    if( nRequest>0 ){
      SetTimeout(60*5, 801);  /* LOG: Timeout request header (1+) */
    }else{
      SetTimeout(10, 802);    /* LOG: Timeout request header (0) */
    }
  }

  /* Change directories to the root of the HTTP filesystem
     更改目录到HTTP文件系统的根目录 */
  if( chdir(zRoot[0] ? zRoot : "/")!=0 ){
    char zBuf[1000];
    Malfunction(190,   /* LOG: chdir() failed */
         "cannot chdir to [%s] from [%s]",
         zRoot, getcwd(zBuf,sizeof(zBuf)-1));
  }
  nRequest++;
  tls_init_conn(socketId);

  /* Get the first line of the request and parse out the method, the script and the protocol.
  获取请求的第一行并解析方法、脚本和协议
  */
  omitLog = 1;
  if( althttpd_fgets(zLine,sizeof(zLine),stdin)==0 ){
    exit(0);
  }
  clock_gettime(ALTHTTPD_CLOCK_ID, &tsBeginTime);
  gettimeofday(&beginTime, 0);
  omitLog = 0;
  nIn += (i = (int)strlen(zLine));

  /* Parse the first line of the HTTP request 解析HTTP请求的第一行*/
  zMethod = StrDup(GetFirstElement(zLine,&z));
  zRealScript = zScript = StrDup(GetFirstElement(z,&z));
  zProtocol = StrDup(GetFirstElement(z,&z));
  if( zProtocol==0
   || strncmp(zProtocol,"HTTP/",5)!=0
   || strlen(zProtocol)!=8
   || i>9990
  ){
    zProtocol = 0;
    if( i<=9990 ){
      StartResponse("400 Bad Request");
      nOut += althttpd_printf(
        "Content-type: text/plain; charset=utf-8\r\n"
        "\r\n"
        "This server does not understand the requested protocol\n"//此服务器无法理解请求的协议
      );
      MakeLogEntry(0, 200); /* LOG: bad protocol in HTTP header 请求头中的错误协议*/
    }else{
      StartResponse("414 URI Too Long");
      nOut += althttpd_printf(
        "Content-type: text/plain; charset=utf-8\r\n"
        "\r\n"
        "URI too long\n"
      );
      MakeLogEntry(0, 201); /* LOG: bad protocol in HTTP header 请求头中的错误协议*/
    }
    althttpd_exit();
  }
  if( zScript[0]!='/' ) NotFound(210); /* LOG: Empty request URI */
  while( zScript[1]=='/' ){
    zScript++;
    zRealScript++;
  }
  if( forceClose ){
    closeConnection = 1;
  }else if( zProtocol[5]<'1' || zProtocol[7]<'1' ){
    closeConnection = 1;
  }

  /* This very simple server only understands the GET, POST and HEAD methods
     这个非常简单的服务器只理解GET、POST和HEAD方法  */
  if( strcmp(zMethod,"GET")!=0 && strcmp(zMethod,"POST")!=0
       && strcmp(zMethod,"HEAD")!=0 ){
    StartResponse("501 Not Implemented");//如果方法不是GET、POST或HEAD,则响应"501 Not Implemented" 
    nOut += althttpd_printf(
      "Content-type: text/plain; charset=utf-8\r\n"
      "\r\n"
      "The %s method is not implemented on this server.\n",
      zMethod);
    MakeLogEntry(0, 220); /* LOG: Unknown request method */
    althttpd_exit();
  }

  /* If there is a log file (if zLogFile!=0) and if the pathname in
  ** the first line of the http request contains the magic string
  ** "FullHeaderLog" then write the complete header text into the
  ** file %s(zLogFile)-hdr.  Overwrite the file.  This is for protocol
  ** debugging only and is only enabled if althttpd is compiled with
  ** the -DLOG_HEADER=1 option.
  如果存在日志文件(如果zLogFile!=0),并且HTTP请求的第一行中的路径名包含魔术字符串
"FullHeaderLog",则将完整的标头文本写入文件%s(zLogFile)-hdr。覆盖文件。
这仅用于协议调试,仅在编译althttpd时启用了-DLOG_HEADER=1选项时才有效。
  */
#ifdef LOG_HEADER
  if( zLogFile
   && strstr(zScript,"FullHeaderLog")!=0
   && strlen(zLogFile)<sizeof(zLine)-50
  ){
    sprintf(zLine, "%s-hdr", zLogFile);
    hdrLog = fopen(zLine, "wb");
  }
#endif


  /* Get all the optional fields that follow the first line.
     获取跟在第一行后面的所有可选字段  */
  zCookie = 0;
  zAuthType = 0;
  zRemoteUser = 0;
  zReferer = 0;
  zIfNoneMatch = 0;
  zIfModifiedSince = 0;
  zContentLength = 0;
  rangeEnd = 0;
  while( althttpd_fgets(zLine,sizeof(zLine),stdin) ){
    char *zFieldName;
    char *zVal;

#ifdef LOG_HEADER
    if( hdrLog ) fprintf(hdrLog, "%s", zLine);
#endif
    nIn += strlen(zLine);
    zFieldName = GetFirstElement(zLine,&zVal);
    if( zFieldName==0 || *zFieldName==0 ) break;
    RemoveNewline(zVal);
    if( strcasecmp(zFieldName,"User-Agent:")==0 ){
      zAgent = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"Accept:")==0 ){
      zAccept = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"Accept-Encoding:")==0 ){
      zAcceptEncoding = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"Content-length:")==0 ){
      zContentLength = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"Content-type:")==0 ){
      zContentType = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"Referer:")==0 ){
      zReferer = StrDup(zVal);
      if( strstr(zVal, "devids.net/")!=0 ){ zReferer = "devids.net.smut";
        Forbidden(230); /* LOG: Referrer is devids.net */
      }
    }else if( strcasecmp(zFieldName,"Cookie:")==0 ){
      zCookie = StrAppend(zCookie,"; ",zVal);
    }else if( strcasecmp(zFieldName,"Connection:")==0 ){
      if( strcasecmp(zVal,"close")==0 ){
        closeConnection = 1;
      }else if( !forceClose && strcasecmp(zVal, "keep-alive")==0 ){
        closeConnection = 0;
      }
    }else if( strcasecmp(zFieldName,"Host:")==0 ){
      int inSquare = 0;
      char c;
      if( sanitizeString(zVal) ){
        Forbidden(240);  /* LOG: Illegal content in HOST: parameter */
      }
      zHttpHost = StrDup(zVal);
      zServerPort = zServerName = StrDup(zHttpHost);
      while( zServerPort && (c = *zServerPort)!=0
              && (c!=':' || inSquare) ){
        if( c=='[' ) inSquare = 1;
        if( c==']' ) inSquare = 0;
        zServerPort++;
      }
      if( zServerPort && *zServerPort ){
        *zServerPort = 0;
        zServerPort++;
      }
      if( zRealPort ){
        zServerPort = StrDup(zRealPort);
      }
    }else if( strcasecmp(zFieldName,"Authorization:")==0 ){
      zAuthType = GetFirstElement(StrDup(zVal), &zAuthArg);
    }else if( strcasecmp(zFieldName,"If-None-Match:")==0 ){
      zIfNoneMatch = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"If-Modified-Since:")==0 ){
      zIfModifiedSince = StrDup(zVal);
    }else if( strcasecmp(zFieldName,"Range:")==0
           && strcmp(zMethod,"GET")==0 ){
      int x1 = 0, x2 = 0;
      int n = sscanf(zVal, "bytes=%d-%d", &x1, &x2);
      if( n==2 && x1>=0 && x2>=x1 ){
        rangeStart = x1;
        rangeEnd = x2;
      }else if( n==1 && x1>0 ){
        rangeStart = x1;
        rangeEnd = 0x7fffffff;
      }
    }
  }
#ifdef LOG_HEADER
  if( hdrLog ) fclose(hdrLog);
#endif

  /* Disallow requests from certain clients 禁止来自特定客户端的请求*/
  if( zAgent ){
    const char *azDisallow[] = {
      "Amazonbot",
      "Windows 9",
      "Download Master",
      "Ezooms/",
      "DotBot",
      "HTTrace",
      "AhrefsBot",
      "MicroMessenger",
      "OPPO A33 Build",
      "SemrushBot",
      "MegaIndex.ru",
      "MJ12bot",
      "Chrome/0.A.B.C",
      "Neevabot/",
      "BLEXBot/",
      "Synapse",
    };
    size_t ii;
    for(ii=0; ii<sizeof(azDisallow)/sizeof(azDisallow[0]); ii++){
      if( strstr(zAgent,azDisallow[ii])!=0 ){
        Forbidden(250);  /* LOG: Disallowed user agent 禁止用户*/
      }
    }
#if 0
    /* Spider attack from 2019-04-24 */
    if( strcmp(zAgent,
            "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 "
            "(KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36")==0 ){
      Forbidden(251);  /* LOG: Disallowed user agent (20190424) */
    }
#endif
  }
#if 0
  if( zReferer ){
    static const char *azDisallow[] = {
      "skidrowcrack.com",
      "hoshiyuugi.tistory.com",
      "skidrowgames.net",
    };
    int i;
    for(i=0; i<sizeof(azDisallow)/sizeof(azDisallow[0]); i++){
      if( strstr(zReferer, azDisallow[i])!=0 ){
        NotFound(260);  /* LOG: Disallowed referrer */
      }
    }
  }
#endif

  /* Make an extra effort to get a valid server name and port number.
  ** Only Netscape provides this information.  If the browser is
  ** Internet Explorer, then we have to find out the information for ourselves.
    进一步努力获取有效的服务器名称和端口号。
** 只有 Netscape 提供此信息。如果浏览器是
** Internet Explorer,则我们必须自己找到这些信息。
  */
  if( zServerName==0 ){
    zServerName = SafeMalloc( 100 );
    gethostname(zServerName,100);
  }
  if( zServerPort==0 || *zServerPort==0 ){
    zServerPort = DEFAULT_PORT;
  }

  /* Remove the query string from the end of the requested file.
     从请求的文件末尾删除查询字符串*/
  for(z=zScript; *z && *z!='?'; z++){}
  if( *z=='?' ){
    zQuerySuffix = StrDup(z);
    *z = 0;
    if( (zReferer==0 || zReferer[0]==0)
     && strncmp(zQuerySuffix+1, "w=%2", 4)==0
    ){
      NotFound(261);  /* LOG: Spider attack */
    }
  }else{
    zQuerySuffix = "";
  }
  zQueryString = *zQuerySuffix ? &zQuerySuffix[1] : zQuerySuffix;

  /* Create either a memory buffer to hold the POST query data */
  if( zMethod[0]=='P' && zContentLength!=0 ){
    size_t len = atoi(zContentLength);
    if( len>MAX_CONTENT_LENGTH ){
      StartResponse("500 Request too large");
      nOut += althttpd_printf(
        "Content-type: text/plain; charset=utf-8\r\n"
        "\r\n"
        "Too much POST data\n"
      );
      MakeLogEntry(0, 270); /* LOG: Request too large */
      althttpd_exit();
    }
    rangeEnd = 0;
    zPostData = SafeMalloc( len+1 );
    SetTimeout(15 + len/2000, 803);  /* LOG: Timeout POST data */
    nPostData = althttpd_fread(zPostData,1,len,stdin);
    nIn += nPostData;
  }

  /* Make sure the running time is not too great 设置超时*/
  SetTimeout(30, 804);  /* LOG: Timeout decode HTTP request */

  /* Convert all unusual characters in the script name into "_".
  ** This is a defense against various attacks, XSS attacks in particular.
     消除非法字符*/
  sanitizeString(zScript);

  /* Refuse to process the request if the IP address has been banished 
     如果IP地址被列入禁用列表,则拒绝处理请求*/
  if( zIPShunDir && DisallowedRemoteAddr() ){
    ServiceUnavailable(901); /* LOG: Prohibited remote IP address */
  }

  /* Do not allow "/." or "/-" to to occur anywhere in the entity name.
  ** This prevents attacks involving ".." and also allows us to create
  ** files and directories whose names begin with "-" or "." which are
  ** invisible to the webserver.
  **在实体名称的任何位置都不允许出现 "/." 或 "/-"。
** 这可以防止涉及 ".." 的攻击,并允许我们创建以 "-" 或 "." 开头的文件和目录,
** 这些文件和目录对Web服务器不可见。
  ** Exception:  Allow the "/.well-known/" prefix in accordance with RFC-5785.
** 例外情况:根据RFC-5785允许使用 "/.well-known/" 前缀。
  */
  for(z=zScript; *z; z++){
    if( *z=='/' && (z[1]=='.' || z[1]=='-') ){
      if( strncmp(zScript,"/.well-known/",13)==0 && (z[1]!='.' || z[2]!='.') ){
        /* Exception:  Allow "/." and "/-" for URLs that being with
        ** "/.well-known/".  But do not allow "/..". */
        continue;
      }
      NotFound(300); /* LOG: Path element begins with "." or "-" */
    }
  }

  /* Figure out what the root of the filesystem should be.  If the
  ** HTTP_HOST parameter exists (stored in zHttpHost) then remove the
  ** port number from the end (if any), convert all characters to lower
  ** case, and convert non-alphanumber characters (including ".") to "_".
  ** Then try to find a directory with that name and the extension .website.
  ** If not found, look for "default.website".
  确定文件系统的根目录应该是什么。如果存在HTTP_HOST参数(存储在zHttpHost中),
** 那么从末尾删除端口号(如果有的话),将所有字符转换为小写,并将非字母数字字符(包括 ".")转换为“”。
** 然后尝试查找具有该名称和扩展名.website的目录。如果找不到,则查找"default.website"。
  */
  if( zScript[0]!='/' ){
    NotFound(310); /* LOG: URI does not start with "/" */
  }
  if( strlen(zRoot)+40 >= sizeof(zLine) ){
    NotFound(320); /* LOG: URI too long */
  }
  if( zHttpHost==0 || zHttpHost[0]==0 ){
    NotFound(330);  /* LOG: Missing HOST: parameter */
  }else if( strlen(zHttpHost)+strlen(zRoot)+10 >= sizeof(zLine) ){
    NotFound(340);  /* LOG: HOST parameter too long */
  }else{
    sprintf(zLine, "%s/%s", zRoot, zHttpHost);
    for(i=strlen(zRoot)+1; zLine[i] && zLine[i]!=':'; i++){
      unsigned char c = (unsigned char)zLine[i];
      if( !isalnum(c) ){
        if( c=='.' && (zLine[i+1]==0 || zLine[i+1]==':') ){
          /* If the client sent a FQDN with a "." at the end
          ** (example: "sqlite.org." instead of just "sqlite.org") then
          ** omit the final "." from the document root directory name */
          break;
        }
        zLine[i] = '_';
      }else if( isupper(c) ){
        zLine[i] = tolower(c);
      }
    }
    strcpy(&zLine[i], ".website");
  }
  if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
    sprintf(zLine, "%s/default.website", zRoot);
    if( stat(zLine,&statbuf) || !S_ISDIR(statbuf.st_mode) ){
      if( standalone ){
        sprintf(zLine, "%s", zRoot);
      }else{
        NotFound(350);  /* LOG: *.website permissions */
      }
    }
  }
  zHome = StrDup(zLine);
  /* Change directories to the root of the HTTP filesystem
    切换到HTTP文件系统的根目录*/
  if( chdir(zHome)!=0 ){
    char zBuf[1000];
    Malfunction(360,  /* LOG: chdir() failed */
         "cannot chdir to [%s] from [%s]",
         zHome, getcwd(zBuf,999));
  }

  /* Locate the file in the filesystem.  We might have to append
  ** a name like "/home" or "/index.html" or "/index.cgi" in order
  ** to find it.  Any excess path information is put into the
  ** zPathInfo variable.
  在文件系统中定位文件。为了找到文件,我们可能需要附加类似于"/home"、"/index.html"或"/index.cgi"的名称。
  任何多余的路径信息都会存储在变量zPathInfo中。
  */
  j = j0 = (int)strlen(zLine);
  i = 0;
  while( zScript[i] ){
    while( zScript[i] && (i==0 || zScript[i]!='/') ){
      zLine[j] = zScript[i];
      i++; j++;
    }
    zLine[j] = 0;
    /* fprintf(stderr, "searching [%s]...\n", zLine); */
    if( stat(zLine,&statbuf)!=0 ){
      int stillSearching = 1;
      while( stillSearching && i>0 && j>j0 ){
        while( j>j0 && zLine[j-1]!='/' ){ j--; }
        strcpy(&zLine[j-1], "/not-found.html");
        if( stat(zLine,&statbuf)==0 && S_ISREG(statbuf.st_mode)
            && access(zLine,R_OK)==0 ){
          zRealScript = StrDup(&zLine[j0]);
          Redirect(zRealScript, 302, 1, 370); /* LOG: redirect to not-found */
          return;
        }else{
          j--;
        }
      }
      if( stillSearching ) NotFound(380); /* LOG: URI not found */
      break;
    }
    if( S_ISREG(statbuf.st_mode) ){
      if( access(zLine,R_OK) ){
        NotFound(390);  /* LOG: File not readable */
      }
      zRealScript = StrDup(&zLine[j0]);
      break;
    }
    if( zScript[i]==0 || zScript[i+1]==0 ){
      static const char *azIndex[] = {
        "/home", "/index", "/index.html", "/index.cgi"
      };
      int k = j>0 && zLine[j-1]=='/' ? j-1 : j;
      unsigned int jj;
      for(jj=0; jj<sizeof(azIndex)/sizeof(azIndex[0]); jj++){
        strcpy(&zLine[k],azIndex[jj]);
        if( stat(zLine,&statbuf)!=0 ) continue;
        if( !S_ISREG(statbuf.st_mode) ) continue;
        if( access(zLine,R_OK) ) continue;
        break;
      }
      if( jj>=sizeof(azIndex)/sizeof(azIndex[0]) ){
        NotFound(400); /* LOG: URI is a directory w/o index.html */
      }
      zRealScript = StrDup(&zLine[j0]);
      if( zScript[i]==0 ){
        /* If the requested URL does not end with "/" but we had to
        ** append "index.html", then a redirect is necessary.  Otherwise
        ** none of the relative URLs in the delivered document will be correct. 
        ** 如果请求的URL不以"/"结尾,但我们不得不附加"index.html",则需要进行重定向。
        否则,交付文档中的相对URL将全部不正确。*/
        Redirect(zRealScript,301,1,410); /* LOG: redirect to add trailing / */
        return;
      }
      break;
    }
    zLine[j] = zScript[i];
    i++; j++;
  }
  zFile = StrDup(zLine);
  zPathInfo = StrDup(&zScript[i]);
  lenFile = strlen(zFile);
  zDir = StrDup(zFile);
  for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){};
  if( i==0 ){
     strcpy(zDir,"/");
  }else{
     zDir[i] = 0;
  }

  /* Check to see if there is an authorization file.  If there is, process it.
     检查是否存在授权文件。如果有,进行处理。*/
  sprintf(zLine, "%s/-auth", zDir);
  if( access(zLine,R_OK)==0 && !CheckBasicAuthorization(zLine) ){
    tls_close_conn();
    return;
  }

  /* Take appropriate action  采取适当的行动。  */
  if( (statbuf.st_mode & 0100)==0100 && access(zFile,X_OK)==0
      && (!(pMimeType = GetMimeType(zFile, lenFile))
          || 0==(pMimeType->flags & MTF_NOCGI)) ){ /* CGI */
    char *zBaseFilename;       /* Filename without directory prefix */
    int px[2];                 /* CGI-1 to althttpd pipe */
    int py[2];                 /* zPostData to CGI-0 pipe */

    /* Abort with an error if the CGI script is writable by anyone other than its owner.
       如果 CGI 脚本可被除所有者以外的任何人写入,则中止并显示错误。*/
    if( statbuf.st_mode & 0022 ){
      CgiScriptWritable();
    }

    /* Compute the base filename of the CGI script 计算 CGI 脚本的基本文件名。*/
    for(i=strlen(zFile)-1; i>=0 && zFile[i]!='/'; i--){}
    zBaseFilename = &zFile[i+1];

    /* Create pipes used to communicate with the child CGI process 创建用于与子 CGI 进程通信的管道。*/
    if( pipe(px) ){
      Malfunction(440, /* LOG: pipe() failed */
                  "Unable to create a pipe for the CGI program");
    }
    if( pipe(py) ){
      Malfunction(441, /* LOG: pipe() failed */
                  "Unable to create a pipe for the CGI program");
    }

    /* Create the child process that will run the CGI. 创建将运行 CGI 的子进程。*/
    if( fork()==0 ){
      /* This code is run by the child CGI process only Begin by setting up the CGI-to-althttpd pipe
         此代码仅由子 CGI 进程运行。首先建立 CGI 到 althttpd 的管道。 */
      close(1);
      if( dup(px[1])<0 ){
        Malfunction(442, /* LOG: dup() failed */
                    "CGI cannot dup() to file descriptor 1");
      }

      /* Set up the althttpd-to-CGI link */
      close(0);
      if( dup(py[0])<0 ){
        Malfunction(444, /* LOG: dup() failed */
                  "CGI cannot dup() to file descriptor 0");
      }

      /* Close all surplus file descriptors */
      for(i=3; close(i)==0; i++){}

      /* Move into the directory holding the CGI program */
      if( chdir(zDir) ){
        char zBuf[1000];
        Malfunction(445, /* LOG: chdir() failed */
             "CGI cannot chdir to [%s] from [%s]",
             zDir, getcwd(zBuf,999));
      }

      /* Setup the CGI environment appropriately. */
      ComputeRequestUri();
      putenv("GATEWAY_INTERFACE=CGI/1.0");
      for(i=0; i<(int)(sizeof(cgienv)/sizeof(cgienv[0])); i++){
        if( *cgienv[i].pzEnvValue ){
          SetEnv(cgienv[i].zEnvName,*cgienv[i].pzEnvValue);
        }
      }

      /* Run the CGI program */
      execl(zBaseFilename, zBaseFilename, (char*)0);
      exit(0);  /* Not reached */
    }

    /* This parent process.  The child has been started.
    ** Set up the CGI-to-althttp pipe on which to receive the reply
    这是父进程。子进程已经启动。 建立 CGI 到 althttp 的管道,用于接收回复。
    */
    close(px[1]);
    in = fdopen(px[0], "rb");

    /* Set up the althttp-to-CGI pipe used to send POST data (if any) 
    建立 althttp 到 CGI 的管道,用于发送 POST 数据(如果有的话)。*/
    close(py[0]);
    if( nPostData>0 ){
      ssize_t wrote = 0, n;
      while( nPostData>wrote ){
        n = write(py[1], zPostData+wrote, nPostData-wrote);
        if( n<=0 ) break;
        wrote += n;
      }
    }
    if( zPostData ){
       free(zPostData);
       zPostData = 0;
       nPostData = 0;
    }
    close(py[1]);

    /* Wait for the CGI program to reply and process that reply
    等待 CGI 程序回复,并处理该回复。 */
    if( in==0 ){
      CgiError();
    }else{
      CgiHandleReply(in, strncmp(zBaseFilename,"nph-",4)==0);
    }
  }else if( lenFile>5 && strcmp(&zFile[lenFile-5],".scgi")==0 ){
    /* Any file that ends with ".scgi" is assumed to be text of the form: 
       任何以“.scgi”结尾的文件都被假定为以下形式的文本:
    **     SCGI hostname port
    ** Open a TCP/IP connection to that host and send it an SCGI request
       打开到该主机的TCP/IP连接并向其发送一个SCGI请求。
    */
    SendScgiRequest(zFile, zScript);
  }else if( countSlashes(zRealScript)!=countSlashes(zScript) ){
    /* If the request URI for static content contains material past the
    ** actual content file name, report that as a 404 error. 
    如果用于静态内容的请求URI包含超出实际内容文件名的部分,请将其报告为404错误。*/
    NotFound(460); /* LOG: Excess URI content past static file name 超出静态文件名的URI内容过多*/
  }else{
    /* If it isn't executable then it must be a simple file that needs to be copied to output.
       如果不是可执行文件,那么它必须是一个需要复制到输出的简单文件。*/
    SetTimeout(30 + statbuf.st_size/2000,
               805); /* LOG: Timeout send static file */
    if( SendFile(zFile, lenFile, &statbuf) ){
      return;
    }
  }
  //完成静态文件处理或CGI处理后,刷新输出并生成日志
  althttpd_fflush(stdout);
  MakeLogEntry(0, 0);  /* LOG: Normal reply */
  omitLog = 1;
}

launch_web_browser

/* Launch a web-browser pointing to zPage
   启动一个指向特定网页的网页浏览器*/
static void launch_web_browser(const char *zPath, int iPort){
  char zUrl[2000];
  static const char *const azBrowserProg[] = {
#if defined(__DARWIN__) || defined(__APPLE__) || defined(__HAIKU__)
       "open"
#else
       "xdg-open", "gnome-open", "firefox", "google-chrome"
#endif
  };
  size_t i;

  if( strlen(zPath)<=sizeof(zUrl)-1000 ){
    while( zPath[0]=='/' ) zPath++;
    sprintf(zUrl, "http://localhost:%d/%s", iPort, zPath);
    for(i=0; i<sizeof(azBrowserProg)/sizeof(azBrowserProg[0]); i++){
      execlp(azBrowserProg[i], azBrowserProg[i], zUrl, (char*)0);
    }
  }
  exit(1);
}

http_server

HTTP服务器守护进程,监听指定范围内的端口,处理HTTP请求。

#define MAX_PARALLEL 50  /* Number of simultaneous children 同时处理的子进程的最大数量*/

/* All possible forms of an IP address.  Needed to work around GCC strict aliasing rules.
   多种形式的IP地址结构*/
typedef union {
  struct sockaddr sa;              /* Abstract superclass */
  struct sockaddr_in sa4;          /* IPv4 */
  struct sockaddr_in6 sa6;         /* IPv6 */
  struct sockaddr_storage sas;     /* Should be the maximum of the above 3 */
} address;

/*
** Implement an HTTP server daemon listening on port zPort.
** HTTP服务器守护进程
** As new connections arrive, fork a child and let the child return
** out of this procedure call.  The child will handle the request.
** The parent never returns from this procedure.
**
** Return 0 to each child as it runs.  If unable to establish a
** listening socket, return non-zero.
**
** When it accept()s a connection, the socket ID is written to the
** final argument.
*/
int http_server(
  int mnPort, int mxPort,   /* Range of TCP ports to try 最小端口号mnPort、最大端口号mxPort*/
  int bLocalhost,           /* Listen on loopback sockets only 是否仅监听本地回环地址的标志bLocalhost*/
  const char *zPage,        /* Launch web browser on this document 要启动的网页路径zPage*/
  int *httpConnection       /* Socket over which HTTP request arrives, HTTP请求到达的套接字httpConnection*/
){
  int listener = -1;           /* The server socket 服务器套接字的文件描述符*/
  int connection;              /* A socket for each individual connection 每个单独连接的套接字的文件描述符*/
  fd_set readfds;              /* Set of file descriptors for select() 文件描述符集合readfds,用于select()系统调用*/
  socklen_t lenaddr;           /* Length of the inaddr structure 保存地址结构的长度。*/
  int child;                   /* PID of the child process 子进程的进程ID*/
  int nchildren = 0;           /* Number of child processes 当前活动的子进程数量。*/
  struct timeval delay;        /* How long to wait inside select() select()函数的超时时间*/
  struct sockaddr_in inaddr;   /* The socket address IPv4套接字地址*/
  int opt = 1;                 /* setsockopt flag 套接字选项*/
  int iPort = mnPort;

  while( iPort<=mxPort ){
    memset(&inaddr, 0, sizeof(inaddr));
    inaddr.sin_family = AF_INET;
    if( bLocalhost ){
      inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    }else{
      inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    inaddr.sin_port = htons(iPort);
    listener = socket(AF_INET, SOCK_STREAM, 0);//监听连接请求,并打印监听的端口信息。
    if( listener<0 ){
      iPort++;
      continue;
    }

    /* if we can't terminate nicely, at least allow the socket to be reused */
    setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){
      close(listener);
      iPort++;
      continue;
    }
    break;
  }
  if( iPort>mxPort ){
    if( mnPort==mxPort ){
      fprintf(stderr,"unable to open listening socket on port %d\n", mnPort);
    }else{
      fprintf(stderr,"unable to open listening socket on any"
                     " port in the range %d..%d\n", mnPort, mxPort);
    }
    exit(1);
  }
  if( iPort>mxPort ) return 1;
  listen(listener,10);
  printf("Listening for %s requests on TCP port %d\n",
         useHttps?"TLS-encrypted HTTPS":"HTTP",  iPort);
  fflush(stdout);
  //如果指定了要打开的网页路径zPage,则创建一个子进程,并在子进程中调用launch_web_browser()函数打开网页。
  if( zPage ){
    child = fork();
    if( child!=0 ){
      if( child>0 ) nchildren++;
    }else{
      launch_web_browser(zPage, iPort);
      /* NOT REACHED */
      exit(1);
    }
  }
  while( 1 ){
    if( nchildren>MAX_PARALLEL ){//子进程数量超过了最大并行处理数量MAX_PARALLEL,则主进程会等待一段时间。
      /* Slow down if connections are arriving too fast */
      sleep( nchildren-MAX_PARALLEL );
    }
    delay.tv_sec = 0;
    delay.tv_usec = 100000;
    FD_ZERO(&readfds);
    assert( listener>=0 );
    FD_SET( listener, &readfds);
    select( listener+1, &readfds, 0, 0, &delay);//使用select()函数监听套接字上是否有新的连接请求
    if( FD_ISSET(listener, &readfds) ){//接受连接并处理请求
      lenaddr = sizeof(inaddr);
      connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
      if( connection>=0 ){
        child = fork();
        if( child!=0 ){
          if( child>0 ) nchildren++;
          close(connection);
          /* printf("subprocess %d started...\n", child); fflush(stdout); */
        }else{
          int nErr = 0, fd;
          close(0);
          fd = dup(connection);
          if( fd!=0 ) nErr++;
          close(1);
          fd = dup(connection);
          if( fd!=1 ) nErr++;
          close(connection);
          *httpConnection = fd;
          return nErr;
        }
      }
    }
    /* Bury dead children */
    while( (child = waitpid(0, 0, WNOHANG))>0 ){
      /* printf("process %d ends\n", child); fflush(stdout); */
      nchildren--;
    }
  }
  /* NOT REACHED 永远不会被执行到*/
  exit(1);
}

main

int main(int argc, const char **argv){
  int i;                     /* Loop counter */
  const char *zPermUser = 0; /* Run daemon with this user's permissions 记录要以哪个用户的权限运行守护进程。*/
  int mnPort = 0;            /* Range of TCP ports for server mode, TCP端口范围*/
  int mxPort = 0;
  int useChrootJail = 1;     /* True to use a change-root jail 是否使用chroot环境*/
  struct passwd *pwd = 0;    /* Information about the user 保存关于用户的信息*/
  int httpConnection = 0;    /* Socket ID of inbound http connection 入站HTTP连接的套接字ID*/
  int bLocalhost = 0;        /* Bind to loop-back TCP ports only 是否仅绑定到本地回环TCP端口*/
  const char *zPage = 0;     /* Starting page 要启动的起始网页*/

  /* Record the time when processing begins. */
  gettimeofday(&beginTime, 0);

  /* Parse command-line arguments 命令行解析*/
  while( argc>1 && argv[1][0]=='-' ){
    int nTerm = 2;
    const char *z = argv[1];
    const char *zArg = argc>=3 ? argv[2] : "0";
    if( z[0]=='-' && z[1]=='-' ) z++;
    if( strcmp(z,"-root")==0 ){
      zRoot = zArg;
    }else
    if( strcmp(z,"-logfile")==0 ){
      zLogFile = zArg;
    }else
#ifdef ENABLE_TLS
    if( strcmp(z, "-cert")==0 ){
      useHttps = 2;
      zHttpScheme = "https";
      zHttps = "on";
      tlsState.zCertFile = zArg;
      if( tlsState.zKeyFile==0 ) tlsState.zKeyFile = zArg;
      if( standalone ){
        standalone = 2;
      }
    }else
    if( strcmp(z, "-pkey")==0 ){
      tlsState.zKeyFile = zArg;
    }else
#endif
    if( strcmp(z,"-user")==0 ){
      zPermUser = zArg;
    }else
    if( strcmp(z,"-ipshun")==0 ){
      zIPShunDir = zArg;
    }else
    if( strcmp(z,"-max-age")==0 ){
      mxAge = atoi(zArg);
    }else
    if( strcmp(z,"-max-cpu")==0 ){
      maxCpu = atoi(zArg);
    }else
    if( strcmp(z,"-loopback")==0 ){
      bLocalhost = 1;
      nTerm = 1;
    }else
    if( strcmp(z,"-enable-sab")==0 ){
      enableSAB = 1;
    }else
    if( strcmp(z,"-page")==0 ){
      zPage = zArg;
      bLocalhost = 1;
      if( mnPort==0 ){
        mnPort = 8080;
        mxPort = 8100;
      }
      standalone = 1 + (useHttps==2);
    }else
    if( strcmp(z,"-popup")==0 ){
      nTerm = 1;
      zRoot = ".";
      if( mnPort==0 ){
        mnPort = 8080;
        mxPort = 8100;
      }
      standalone = 1 + (useHttps==2);
    }else
    if( strcmp(z,"-https")==0 ){
      int const x = atoi(zArg);
      if( x<=0 ){
        useHttps = 0;
        zHttpScheme = "http";
        zHttps = 0;
      }else{
        zHttpScheme = "https";
        zHttps = "on";
        zRemoteAddr = getenv("REMOTE_HOST");
        useHttps = 1;
      }
    }else
    if( strcmp(z, "-port")==0 ){
      int ii;
      mnPort = mxPort = 0;
      for(ii=0; zArg[ii]>='0' && zArg[ii]<='9'; ii++){
        mnPort = mnPort*10 + zArg[ii] - '0';
      }
      if( zArg[ii]==0 ){
        mxPort = mnPort;
      }else if( zArg[ii]=='.' && zArg[ii+1]=='.' ){
        for(ii+=2; zArg[ii]>='0' && zArg[ii]<='9'; ii++){
          mxPort = mxPort*10 + zArg[ii] - '0';
        }
      }
      standalone = 1 + (useHttps==2);
    }else
    if( strcmp(z, "-family")==0 ){
      if( strcmp(zArg, "ipv4")==0 ){
        ipv4Only = 1;
      }else if( strcmp(zArg, "ipv6")==0 ){
        ipv6Only = 1;
      }else{
        Malfunction(513,  /* LOG: unknown IP protocol */
                    "unknown IP protocol: [%s]\n", zArg);
      }
    }else
    if( strcmp(z, "-jail")==0 ){
      if( atoi(zArg)==0 ){
        useChrootJail = 0;
      }
    }else
    if( strcmp(z, "-debug")==0 ){
      if( atoi(zArg) ){
        useTimeout = 0;
      }
    }else
    if( strcmp(z, "-input")==0 ){
      if( freopen(zArg, "rb", stdin)==0 || stdin==0 ){
        Malfunction(514, /* LOG: cannot open --input file */
                    "cannot open --input file \"%s\"\n", zArg);
      }
    }else
    if( strcmp(z, "-version")==0 ){
      puts(SERVER_SOFTWARE_TLS);
      return 0;
    }else
    if( strcmp(z, "-datetest")==0 ){
      TestParseRfc822Date();
      printf("Ok\n");
      exit(0);
    }else
    if( strcmp(z,"-remote-addr")==0 ){
      /* Used for testing purposes only - to simulate a remote IP address when
      ** input is really coming from a disk file. */
      zRemoteAddr = StrDup(zArg);
    }else
    {
      Malfunction(515, /* LOG: unknown command-line argument on launch */
                  "unknown argument: [%s]\n", z);
    }
    argv += nTerm;
    argc -= nTerm;
  }
  if( zRoot==0 ){
    if( !standalone ){
      mnPort = 8080;
      mxPort = 8100;
    }
    standalone = 1;
    bLocalhost = 1;
    zRoot = ".";
  }

  /* 10 seconds to get started */
  if( useTimeout ){//设置超时信号处理
    signal(SIGALRM, Timeout);
    signal(SIGSEGV, Timeout);
    signal(SIGPIPE, Timeout);
    signal(SIGXCPU, Timeout);
    if( !standalone ) SetTimeout(10, 806);  /* LOG: Timeout startup */
  }

#if ENABLE_TLS
  /* We "need" to read the cert before chroot'ing to allow that the
  ** cert is stored in space outside of the --root and not readable by the --user.
  */
  if( useHttps>=2 ){
    ssl_init_server(tlsState.zCertFile, tlsState.zKeyFile);//初始化SSL
  }
#endif

  /* Change directories to the root of the HTTP filesystem.  Then
  ** create a chroot jail there. */
  if( chdir(zRoot)!=0 ){、、切换到HTTP文件系统的根目录
    Malfunction(517, /* LOG: chdir() failed */
                "cannot change to directory [%s]", zRoot);
  }

  /* Get information about the user if available 获得用户信息*/
  if( zPermUser ) pwd = getpwnam(zPermUser);
  else if( getuid()==0 ){
    Malfunction(518, "Cannot run as root. Use the -user USER flag.");
    return 1;
  }

  /* Enter the chroot jail if requested 如果请求了使用 chroot 环境,则进入 chroot 环境*/
  if( zPermUser && useChrootJail && getuid()==0 ){
    if( chroot(".")<0 ){//尝试将当前目录设为 chroot 环境
      Malfunction(519, /* LOG: chroot() failed */
                  "unable to create chroot jail");
    }else{
      zRoot = "";
    }
  }

  /* Activate the server, if requested 如果请求了启动服务器,并且端口范围有效,则激活服务器*/
  if( mnPort>0 && mnPort<=mxPort
   && http_server(mnPort, mxPort, bLocalhost, zPage, &httpConnection)
  ){
    Malfunction(520, /* LOG: server startup failed */
                "failed to start server");
  }

#ifdef RLIMIT_CPU//对进程的CPU时间限制。
  if( maxCpu>0 ){
    struct rlimit rlim;
    rlim.rlim_cur = maxCpu;//所需的最大CPU时间。
    rlim.rlim_max = maxCpu;
    setrlimit(RLIMIT_CPU, &rlim);//调用 setrlimit 函数设置CPU时间限制
  }
#endif

  /* Drop root privileges.降低 root 权限。  */
  if( zPermUser ){
    if( pwd ){//如果指定了要切换的用户
      if( setgid(pwd->pw_gid) ){//如果获取到了指定用户的信息,则尝试切换组ID和用户ID 
        Malfunction(521, /* LOG: setgid() failed */
                    "cannot set group-id to %d", pwd->pw_gid);//切换组ID失败
      }
      if( setuid(pwd->pw_uid) ){
        Malfunction(522, /* LOG: setuid() failed */
                    "cannot set user-id to %d", pwd->pw_uid);//切换用户ID失败
      }
    }else{
      Malfunction(523, /* LOG: unknown user */
                  "no such user [%s]", zPermUser);//未找到指定的用户
    }
  }
  if( getuid()==0 ){
    Malfunction(524, /* LOG: cannot run as root */
                "cannot run as root");
  }

  /* Get the IP address from whence the request originates
     获取请求的来源IP地址*/
  if( zRemoteAddr==0 ){//如果未指定远程地址,则尝试获取连接的对端IP地址
    address remoteAddr;
    unsigned int size = sizeof(remoteAddr);
    char zHost[NI_MAXHOST];
    if( getpeername(0, &remoteAddr.sa, &size)>=0 ){
      //如果获取成功,则使用 getnameinfo 函数将对端IP地址转换为字符串形式
      getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0,
                  NI_NUMERICHOST);
      zRemoteAddr = StrDup(zHost);
    }
  }
  //对IPv6地址进行处理,去除前缀 '::ffff:' 并检查是否包含IPv4地址
  if( zRemoteAddr!=0
   && strncmp(zRemoteAddr, "::ffff:", 7)==0
   && strchr(zRemoteAddr+7, ':')==0
   && strchr(zRemoteAddr+7, '.')!=0
  ){
    zRemoteAddr += 7;//如果是IPv6转换的IPv4地址,则去除前缀 '::ffff:' 
  }
  //根据是否使用HTTPS设置服务器软件信息
  zServerSoftware = useHttps==2 ? SERVER_SOFTWARE_TLS : SERVER_SOFTWARE;

  /* Process the input stream 处理输入流*/
  for(i=0; i<100; i++){
    ProcessOneRequest(0, httpConnection);
  }
  ProcessOneRequest(1, httpConnection);// 处理最后一个请求
  tls_close_conn();//并关闭TLS连接
  exit(0);
}
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值