mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-03 07:29:23 +00:00
Add support for serving directly from filesystem
You can now pass `-D directory` to redbean which will serve assets from the local filesystem. This is useful for development since it allows us to skip needing to shut down the server and run InfoZIP when testing an iteration of a lua server page script. See #97
This commit is contained in:
parent
a1677d605a
commit
3c19b6e352
7 changed files with 465 additions and 101 deletions
|
@ -125,6 +125,8 @@ char *DecodeLatin1(const char *, size_t, size_t *);
|
|||
bool IsValidHttpToken(const char *, size_t);
|
||||
char *EncodeHttpHeaderValue(const char *, size_t, size_t *);
|
||||
char *VisualizeControlCodes(const char *, size_t, size_t *);
|
||||
char *IndentLines(const char *, size_t, size_t *, size_t);
|
||||
bool IsAcceptableHttpRequestPath(const char *, size_t);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
|
|
57
net/http/indentlines.c
Normal file
57
net/http/indentlines.c
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/x/x.h"
|
||||
#include "net/http/http.h"
|
||||
|
||||
/**
|
||||
* Inserts spaces before lines.
|
||||
*
|
||||
* @param data is input value
|
||||
* @param size if -1 implies strlen
|
||||
* @param out_size if non-NULL receives output length on success
|
||||
* @param amt is number of spaces to use
|
||||
* @return allocated NUL-terminated buffer, or NULL w/ errno
|
||||
*/
|
||||
char *IndentLines(const char *data, size_t size, size_t *out_size, size_t amt) {
|
||||
char *r;
|
||||
const char *p;
|
||||
size_t i, n, m, a;
|
||||
if (size == -1) size = strlen(data);
|
||||
r = 0;
|
||||
n = 0;
|
||||
do {
|
||||
if ((p = memchr(data, '\n', size))) {
|
||||
m = p + 1 - data;
|
||||
a = *data != '\r' && *data != '\n' ? amt : 0;
|
||||
} else {
|
||||
m = size;
|
||||
a = size ? amt : 0;
|
||||
}
|
||||
r = xrealloc(r, n + a + m + 1);
|
||||
memset(r + n, ' ', a);
|
||||
memcpy(r + n + a, data, m);
|
||||
n += a + m;
|
||||
data += m;
|
||||
size -= m;
|
||||
} while (p);
|
||||
if (out_size) *out_size = n;
|
||||
r[n] = '\0';
|
||||
return r;
|
||||
}
|
75
net/http/isacceptablehttprequestpath.c
Normal file
75
net/http/isacceptablehttprequestpath.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/str/thompike.h"
|
||||
#include "net/http/http.h"
|
||||
|
||||
/**
|
||||
* Returns true if request path seems legit.
|
||||
*
|
||||
* 1. Request path must start with '/'.
|
||||
* 2. The substring "//" is disallowed.
|
||||
* 3. We won't serve hidden files (segment starts with '.').
|
||||
* 4. We won't serve paths with segments equal to "." or "..".
|
||||
*
|
||||
* It is assumed that the URI parser already took care of percent
|
||||
* escape decoding as well as ISO-8859-1 decoding. The input needs
|
||||
* to be a UTF-8 string.
|
||||
*/
|
||||
bool IsAcceptableHttpRequestPath(const char *data, size_t size) {
|
||||
bool t;
|
||||
size_t i;
|
||||
unsigned n;
|
||||
wint_t x, y, a, b;
|
||||
const char *p, *e;
|
||||
if (!size || *data != '/') return false;
|
||||
t = 0;
|
||||
p = data;
|
||||
e = p + size;
|
||||
while (p < e) {
|
||||
x = *p++ & 0xff;
|
||||
if (x >= 0300) {
|
||||
a = ThomPikeByte(x);
|
||||
n = ThomPikeLen(x) - 1;
|
||||
if (p + n <= e) {
|
||||
for (i = 0;;) {
|
||||
b = p[i] & 0xff;
|
||||
if (!ThomPikeCont(b)) break;
|
||||
a = ThomPikeMerge(a, b);
|
||||
if (++i == n) {
|
||||
x = a;
|
||||
p += i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (x == '\\') {
|
||||
x = '/';
|
||||
}
|
||||
if (!t) {
|
||||
t = true;
|
||||
} else {
|
||||
if ((x == '/' || x == '.') && y == '/') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
y = x;
|
||||
}
|
||||
return true;
|
||||
}
|
61
test/net/http/indentlines_test.c
Normal file
61
test/net/http/indentlines_test.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/gc.internal.h"
|
||||
#include "libc/testlib/ezbench.h"
|
||||
#include "libc/testlib/hyperion.h"
|
||||
#include "libc/testlib/testlib.h"
|
||||
#include "net/http/http.h"
|
||||
|
||||
TEST(IndentLines, testEmpty) {
|
||||
char *p;
|
||||
size_t n;
|
||||
static const char kInput[] = "";
|
||||
static const char kOutput[] = "";
|
||||
p = gc(IndentLines(kInput, -1, &n, 2));
|
||||
EXPECT_STREQ(kOutput, p);
|
||||
EXPECT_EQ(strlen(kOutput), n);
|
||||
}
|
||||
|
||||
TEST(IndentLines, test) {
|
||||
char *p;
|
||||
size_t n;
|
||||
static const char kInput[] = "\
|
||||
HTTP/1.1 405 Method Not Allowed\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\
|
||||
Date: Sun, 28 Mar 2021 10:47:47 GMT\r\n\
|
||||
Server: redbean/0.2\r\n\
|
||||
Content-Length: 20\r\n\
|
||||
\r\n";
|
||||
static const char kOutput[] = "\
|
||||
HTTP/1.1 405 Method Not Allowed\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\
|
||||
Date: Sun, 28 Mar 2021 10:47:47 GMT\r\n\
|
||||
Server: redbean/0.2\r\n\
|
||||
Content-Length: 20\r\n\
|
||||
\r\n";
|
||||
p = gc(IndentLines(kInput, -1, &n, 2));
|
||||
EXPECT_STREQ(kOutput, p);
|
||||
EXPECT_EQ(strlen(kOutput), n);
|
||||
}
|
||||
|
||||
BENCH(IndentLines, bench) {
|
||||
EZBENCH2("IndentLines", donothing,
|
||||
free(IndentLines(kHyperion, kHyperionSize, 0, 2)));
|
||||
}
|
61
test/net/http/isacceptablehttprequestpath_test.c
Normal file
61
test/net/http/isacceptablehttprequestpath_test.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/runtime/gc.internal.h"
|
||||
#include "libc/testlib/ezbench.h"
|
||||
#include "libc/testlib/testlib.h"
|
||||
#include "net/http/escape.h"
|
||||
#include "net/http/http.h"
|
||||
|
||||
TEST(IsAcceptableHttpRequestPath, test) {
|
||||
EXPECT_TRUE(IsAcceptableHttpRequestPath("/", 1));
|
||||
EXPECT_TRUE(IsAcceptableHttpRequestPath("/index.html", 11));
|
||||
}
|
||||
|
||||
TEST(IsAcceptableHttpRequestPath, testDoubleSlash_notAllowed) {
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("//", 2));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("/foo//bar", 9));
|
||||
}
|
||||
|
||||
TEST(IsAcceptableHttpRequestPath, testDoesntStartWithSlash_notAllowed) {
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath(NULL, 0));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("*", 1));
|
||||
}
|
||||
|
||||
TEST(IsAcceptableHttpRequestPath, testNoncanonicalDirectories_areForbidden) {
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("/.", 2));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("/./", 3));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("/../", 4));
|
||||
}
|
||||
|
||||
TEST(IsAcceptableHttpRequestPath, testNoncanonicalWindowsDirs_areForbidden) {
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("\\.", 2));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("\\.\\", 3));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("\\..\\", 4));
|
||||
}
|
||||
|
||||
TEST(IsAcceptableHttpRequestPath, testOverlongSlashDot_isDetected) {
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("/\300\256", 3));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("/\300\257", 3));
|
||||
EXPECT_FALSE(IsAcceptableHttpRequestPath("\300\256\300\256", 4));
|
||||
}
|
||||
|
||||
BENCH(IsAcceptableHttpRequestPath, bench) {
|
||||
EZBENCH2("IsAcceptableHttpRequestPath", donothing,
|
||||
IsAcceptableHttpRequestPath("/index.html", 11));
|
||||
}
|
|
@ -98,6 +98,8 @@ FLAGS\n\
|
|||
-u uniprocess\n\
|
||||
-z print port\n\
|
||||
-m log messages\n\
|
||||
-b log message bodies\n\
|
||||
-D DIR serve assets from directory\n\
|
||||
-c INT cache seconds\n\
|
||||
-r /X=/Y redirect X to Y\n\
|
||||
-l ADDR listen ip [default 0.0.0.0]\n\
|
||||
|
@ -215,6 +217,8 @@ static const struct ContentTypeExtension {
|
|||
{"js", "application/javascript"}, //
|
||||
{"json", "application/json"}, //
|
||||
{"m4a", "audio/mpeg"}, //
|
||||
{"markdown", "text/plain"}, //
|
||||
{"md", "text/plain"}, //
|
||||
{"mp2", "audio/mpeg"}, //
|
||||
{"mp3", "audio/mpeg"}, //
|
||||
{"mp4", "video/mp4"}, //
|
||||
|
@ -239,6 +243,11 @@ struct Buffer {
|
|||
char *p;
|
||||
};
|
||||
|
||||
struct Strings {
|
||||
size_t n;
|
||||
char **p;
|
||||
};
|
||||
|
||||
struct Parser {
|
||||
int i;
|
||||
int c;
|
||||
|
@ -285,6 +294,10 @@ static struct Assets {
|
|||
uint32_t hash;
|
||||
int64_t lastmodified;
|
||||
char *lastmodifiedstr;
|
||||
struct File {
|
||||
char *path;
|
||||
struct stat st;
|
||||
} * file;
|
||||
} * p;
|
||||
} assets;
|
||||
|
||||
|
@ -318,14 +331,15 @@ static unsigned httpversion;
|
|||
static uint32_t clientaddrsize;
|
||||
|
||||
static lua_State *L;
|
||||
static size_t zsize;
|
||||
static void *content;
|
||||
static uint8_t *zdir;
|
||||
static uint8_t *zmap;
|
||||
static size_t hdrsize;
|
||||
static size_t msgsize;
|
||||
static size_t amtread;
|
||||
static size_t zsize;
|
||||
static char *luaheaderp;
|
||||
struct Strings stagedirs;
|
||||
static const char *pidpath;
|
||||
static const char *logpath;
|
||||
static int64_t programtime;
|
||||
|
@ -514,9 +528,27 @@ static wontreturn void PrintUsage(FILE *f, int rc) {
|
|||
exit(rc);
|
||||
}
|
||||
|
||||
static char *RemoveTrailingSlashes(char *s) {
|
||||
size_t n;
|
||||
n = strlen(s);
|
||||
while (n && (s[n - 1] == '/' || s[n - 1] == '\\')) s[--n] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
static void AddStagingDirectory(const char *dirpath) {
|
||||
char *s;
|
||||
s = RemoveTrailingSlashes(strdup(dirpath));
|
||||
if (!isdirectory(s)) {
|
||||
fprintf(stderr, "error: not a directory: %s\n", s);
|
||||
exit(1);
|
||||
}
|
||||
stagedirs.p = xrealloc(stagedirs.p, ++stagedirs.n * sizeof(*stagedirs.p));
|
||||
stagedirs.p[stagedirs.n - 1] = s;
|
||||
}
|
||||
|
||||
static void GetOpts(int argc, char *argv[]) {
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:c:L:P:U:G:B:")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:c:L:P:U:G:B:D:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'v':
|
||||
__log_level++;
|
||||
|
@ -539,6 +571,9 @@ static void GetOpts(int argc, char *argv[]) {
|
|||
case 'r':
|
||||
AddRedirect(optarg);
|
||||
break;
|
||||
case 'D':
|
||||
AddStagingDirectory(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
cacheseconds = strtol(optarg, NULL, 0);
|
||||
break;
|
||||
|
@ -662,7 +697,9 @@ static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) {
|
|||
}
|
||||
} while (wrote);
|
||||
} else if (errno == EINTR) {
|
||||
if (killed) return -1;
|
||||
if (killed || (meltdown && nowl() - startread > 2)) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
@ -735,7 +772,8 @@ static int64_t GetLastModifiedZip(const uint8_t *cfile) {
|
|||
}
|
||||
|
||||
static bool IsCompressed(struct Asset *a) {
|
||||
return ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate;
|
||||
return !a->file &&
|
||||
ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate;
|
||||
}
|
||||
|
||||
static bool IsNotModified(struct Asset *a) {
|
||||
|
@ -754,6 +792,22 @@ static char *FormatUnixHttpDateTime(char *s, int64_t t) {
|
|||
return s;
|
||||
}
|
||||
|
||||
static void *FreeLater(void *p) {
|
||||
if (p) {
|
||||
freelist.p = xrealloc(freelist.p, ++freelist.n * sizeof(*freelist.p));
|
||||
freelist.p[freelist.n - 1] = p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void CollectGarbage(void) {
|
||||
size_t i;
|
||||
for (i = 0; i < freelist.n; ++i) free(freelist.p[i]);
|
||||
freelist.n = 0;
|
||||
free(request.params.p);
|
||||
DestroyHttpRequest(&msg);
|
||||
}
|
||||
|
||||
static void IndexAssets(void) {
|
||||
int64_t lm;
|
||||
struct Asset *p;
|
||||
|
@ -812,7 +866,7 @@ static struct Asset *FindAsset(const char *path, size_t pathlen) {
|
|||
}
|
||||
}
|
||||
|
||||
static struct Asset *LocateAsset(const char *path, size_t pathlen) {
|
||||
static struct Asset *LocateAssetZip(const char *path, size_t pathlen) {
|
||||
char *path2;
|
||||
struct Asset *a;
|
||||
if (pathlen && path[0] == '/') ++path, --pathlen;
|
||||
|
@ -830,20 +884,43 @@ static struct Asset *LocateAsset(const char *path, size_t pathlen) {
|
|||
return a;
|
||||
}
|
||||
|
||||
static void *FreeLater(void *p) {
|
||||
if (p) {
|
||||
freelist.p = xrealloc(freelist.p, ++freelist.n * sizeof(*freelist.p));
|
||||
freelist.p[freelist.n - 1] = p;
|
||||
static struct Asset *LocateAssetFile(const char *path, size_t pathlen) {
|
||||
size_t i;
|
||||
char *path2;
|
||||
struct Asset *a;
|
||||
if (stagedirs.n) {
|
||||
a = FreeLater(xcalloc(1, sizeof(struct Asset)));
|
||||
a->file = FreeLater(xmalloc(sizeof(struct File)));
|
||||
for (i = 0; i < stagedirs.n; ++i) {
|
||||
if (stat((a->file->path = FreeLater(xasprintf(
|
||||
"%s%.*s", stagedirs.p[i], request.path.n, request.path.p))),
|
||||
&a->file->st) != -1 &&
|
||||
(S_ISREG(a->file->st.st_mode) ||
|
||||
(S_ISDIR(a->file->st.st_mode) &&
|
||||
((stat((a->file->path = FreeLater(
|
||||
xasprintf("%s%s", a->file->path, "index.lua"))),
|
||||
&a->file->st) != -1 &&
|
||||
S_ISREG(a->file->st.st_mode)) ||
|
||||
(stat((a->file->path = FreeLater(
|
||||
xasprintf("%s%s", a->file->path, "index.html"))),
|
||||
&a->file->st) != -1 &&
|
||||
S_ISREG(a->file->st.st_mode)))))) {
|
||||
a->lastmodifiedstr = FormatUnixHttpDateTime(
|
||||
FreeLater(xmalloc(30)),
|
||||
(a->lastmodified = a->file->st.st_mtim.tv_sec));
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
||||
return p;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void CollectGarbage(void) {
|
||||
size_t i;
|
||||
for (i = 0; i < freelist.n; ++i) free(freelist.p[i]);
|
||||
freelist.n = 0;
|
||||
free(request.params.p);
|
||||
DestroyHttpRequest(&msg);
|
||||
static struct Asset *LocateAsset(const char *path, size_t pathlen) {
|
||||
struct Asset *a;
|
||||
if (!(a = LocateAssetFile(path, pathlen))) {
|
||||
a = LocateAssetZip(path, pathlen);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static void EmitParamKey(struct Parser *u, struct Params *h) {
|
||||
|
@ -944,25 +1021,21 @@ static void ParseFragment(struct Parser *u, struct Buffer *h) {
|
|||
u->q = u->p;
|
||||
}
|
||||
|
||||
static bool IsForbiddenPath(struct Buffer *b) {
|
||||
return !!memmem(b->p, b->n, "/.", 2);
|
||||
}
|
||||
|
||||
static bool ParseRequestUri(void) {
|
||||
struct Parser u;
|
||||
u.i = 0;
|
||||
u.c = '/';
|
||||
u.c = 0;
|
||||
u.isform = false;
|
||||
u.islatin1 = true;
|
||||
u.data = inbuf.p + msg.uri.a;
|
||||
u.size = msg.uri.b - msg.uri.a;
|
||||
memset(&request, 0, sizeof(request));
|
||||
if (!u.size || *u.data != '/') return false;
|
||||
u.q = u.p = FreeLater(xmalloc(u.size * 2));
|
||||
if (u.c == '/') ParsePath(&u, &request.path);
|
||||
ParsePath(&u, &request.path);
|
||||
if (u.c == '?') ParseParams(&u, &request.params);
|
||||
if (u.c == '#') ParseFragment(&u, &request.fragment);
|
||||
return u.i == u.size && !IsForbiddenPath(&request.path);
|
||||
return u.i == u.size &&
|
||||
IsAcceptableHttpRequestPath(request.path.p, request.path.n);
|
||||
}
|
||||
|
||||
static void ParseFormParams(void) {
|
||||
|
@ -1054,8 +1127,7 @@ static char *AppendExpires(char *p, int64_t t) {
|
|||
struct tm tm;
|
||||
gmtime_r(&t, &tm);
|
||||
p = AppendHeaderName(p, "Expires");
|
||||
FormatHttpDateTime(p, &tm);
|
||||
p += 29;
|
||||
p = FormatHttpDateTime(p, &tm);
|
||||
return AppendCrlf(p);
|
||||
}
|
||||
|
||||
|
@ -1144,6 +1216,7 @@ static void *Deflate(const void *data, size_t size, size_t *out_size) {
|
|||
static void *LoadAsset(struct Asset *a, size_t *out_size) {
|
||||
size_t size;
|
||||
uint8_t *data;
|
||||
if (a->file) return FreeLater(xslurp(a->file->path, out_size));
|
||||
size = ZIP_LFILE_UNCOMPRESSEDSIZE(zmap + a->lf);
|
||||
data = xmalloc(size + 1);
|
||||
if (ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate) {
|
||||
|
@ -1178,8 +1251,13 @@ static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) {
|
|||
pathlen, path);
|
||||
p = SetStatus(304, "Not Modified");
|
||||
} else {
|
||||
content = ZIP_LFILE_CONTENT(zmap + a->lf);
|
||||
contentlength = ZIP_LFILE_COMPRESSEDSIZE(zmap + a->lf);
|
||||
if (!a->file) {
|
||||
content = ZIP_LFILE_CONTENT(zmap + a->lf);
|
||||
contentlength = ZIP_LFILE_COMPRESSEDSIZE(zmap + a->lf);
|
||||
} else {
|
||||
/* TODO(jart): Use sendfile(). */
|
||||
content = FreeLater(xslurp(a->file->path, &contentlength));
|
||||
}
|
||||
if (IsCompressed(a)) {
|
||||
if (ClientAcceptsGzip()) {
|
||||
gzipped = true;
|
||||
|
@ -1283,12 +1361,13 @@ static int LuaLoadAsset(lua_State *L) {
|
|||
const char *path;
|
||||
size_t size, pathlen;
|
||||
path = luaL_checklstring(L, 1, &pathlen);
|
||||
if (!(a = LocateAsset(path, pathlen))) {
|
||||
return luaL_argerror(L, 1, "not found");
|
||||
if ((a = LocateAsset(path, pathlen))) {
|
||||
data = LoadAsset(a, &size);
|
||||
lua_pushlstring(L, data, size);
|
||||
free(data);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
data = LoadAsset(a, &size);
|
||||
lua_pushlstring(L, data, size);
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -1317,22 +1396,26 @@ static int LuaGetFragment(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static void LuaPushLatin1(lua_State *L, const char *s, size_t n) {
|
||||
char *t;
|
||||
size_t m;
|
||||
t = DecodeLatin1(s, n, &m);
|
||||
lua_pushlstring(L, t, m);
|
||||
free(t);
|
||||
}
|
||||
|
||||
static int LuaGetUri(lua_State *L) {
|
||||
lua_pushlstring(L, inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a);
|
||||
LuaPushLatin1(L, inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int LuaFormatDate(lua_State *L) {
|
||||
int64_t t;
|
||||
static int LuaFormatHttpDateTime(lua_State *L) {
|
||||
char buf[30];
|
||||
struct tm tm;
|
||||
t = luaL_checkinteger(L, 1);
|
||||
gmtime_r(&t, &tm);
|
||||
lua_pushstring(L, FormatHttpDateTime(buf, &tm));
|
||||
lua_pushstring(L, FormatUnixHttpDateTime(buf, luaL_checkinteger(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int LuaParseDate(lua_State *L) {
|
||||
static int LuaParseHttpDateTime(lua_State *L) {
|
||||
size_t n;
|
||||
const char *s;
|
||||
s = luaL_checklstring(L, 1, &n);
|
||||
|
@ -1355,14 +1438,6 @@ static int LuaGetPayload(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static void LuaPushLatin1(lua_State *L, const char *s, size_t n) {
|
||||
char *t;
|
||||
size_t m;
|
||||
t = DecodeLatin1(s, n, &m);
|
||||
lua_pushlstring(L, t, m);
|
||||
free(t);
|
||||
}
|
||||
|
||||
static int LuaGetHeader(lua_State *L) {
|
||||
int h;
|
||||
const char *key;
|
||||
|
@ -1579,17 +1654,6 @@ static int LuaDecodeBase64(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int LuaVisualizeControlCodes(lua_State *L) {
|
||||
char *p;
|
||||
size_t size, n;
|
||||
const char *data;
|
||||
data = luaL_checklstring(L, 1, &size);
|
||||
p = VisualizeControlCodes(data, size, &n);
|
||||
lua_pushlstring(L, p, n);
|
||||
free(p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int LuaPopcnt(lua_State *L) {
|
||||
lua_pushinteger(L, popcnt(luaL_checkinteger(L, 1)));
|
||||
return 1;
|
||||
|
@ -1615,6 +1679,26 @@ static int LuaBsf(lua_State *L) {
|
|||
}
|
||||
}
|
||||
|
||||
static int LuaCrc32(lua_State *L) {
|
||||
long i;
|
||||
size_t n;
|
||||
const char *p;
|
||||
i = luaL_checkinteger(L, 1);
|
||||
p = luaL_checklstring(L, 2, &n);
|
||||
lua_pushinteger(L, crc32_z(i, p, n));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int LuaCrc32c(lua_State *L) {
|
||||
long i;
|
||||
size_t n;
|
||||
const char *p;
|
||||
i = luaL_checkinteger(L, 1);
|
||||
p = luaL_checklstring(L, 2, &n);
|
||||
lua_pushinteger(L, crc32c(i, p, n));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void LuaRun(const char *path) {
|
||||
struct Asset *a;
|
||||
const char *code;
|
||||
|
@ -1630,39 +1714,41 @@ static void LuaRun(const char *path) {
|
|||
}
|
||||
|
||||
static const luaL_Reg kLuaFuncs[] = {
|
||||
{"DecodeBase64", LuaDecodeBase64}, //
|
||||
{"EncodeBase64", LuaEncodeBase64}, //
|
||||
{"EscapeFragment", LuaEscapeFragment}, //
|
||||
{"EscapeHtml", LuaEscapeHtml}, //
|
||||
{"EscapeLiteral", LuaEscapeLiteral}, //
|
||||
{"EscapeParam", LuaEscapeParam}, //
|
||||
{"EscapePath", LuaEscapePath}, //
|
||||
{"EscapeSegment", LuaEscapeSegment}, //
|
||||
{"FormatDate", LuaFormatDate}, //
|
||||
{"GetClientAddr", LuaGetClientAddr}, //
|
||||
{"GetDate", LuaGetDate}, //
|
||||
{"GetFragment", LuaGetFragment}, //
|
||||
{"GetHeader", LuaGetHeader}, //
|
||||
{"GetHeaders", LuaGetHeaders}, //
|
||||
{"GetMethod", LuaGetMethod}, //
|
||||
{"GetParam", LuaGetParam}, //
|
||||
{"GetParams", LuaGetParams}, //
|
||||
{"GetPath", LuaGetPath}, //
|
||||
{"GetPayload", LuaGetPayload}, //
|
||||
{"GetServerAddr", LuaGetServerAddr}, //
|
||||
{"GetUri", LuaGetUri}, //
|
||||
{"GetVersion", LuaGetVersion}, //
|
||||
{"HasParam", LuaHasParam}, //
|
||||
{"LoadAsset", LuaLoadAsset}, //
|
||||
{"ParseDate", LuaParseDate}, //
|
||||
{"ServeAsset", LuaServeAsset}, //
|
||||
{"ServeError", LuaServeError}, //
|
||||
{"SetHeader", LuaSetHeader}, //
|
||||
{"SetStatus", LuaSetStatus}, //
|
||||
{"Write", LuaWrite}, //
|
||||
{"bsf", LuaBsf}, //
|
||||
{"bsr", LuaBsr}, //
|
||||
{"popcnt", LuaPopcnt}, //
|
||||
{"DecodeBase64", LuaDecodeBase64}, //
|
||||
{"EncodeBase64", LuaEncodeBase64}, //
|
||||
{"EscapeFragment", LuaEscapeFragment}, //
|
||||
{"EscapeHtml", LuaEscapeHtml}, //
|
||||
{"EscapeLiteral", LuaEscapeLiteral}, //
|
||||
{"EscapeParam", LuaEscapeParam}, //
|
||||
{"EscapePath", LuaEscapePath}, //
|
||||
{"EscapeSegment", LuaEscapeSegment}, //
|
||||
{"FormatHttpDateTime", LuaFormatHttpDateTime}, //
|
||||
{"GetClientAddr", LuaGetClientAddr}, //
|
||||
{"GetDate", LuaGetDate}, //
|
||||
{"GetFragment", LuaGetFragment}, //
|
||||
{"GetHeader", LuaGetHeader}, //
|
||||
{"GetHeaders", LuaGetHeaders}, //
|
||||
{"GetMethod", LuaGetMethod}, //
|
||||
{"GetParam", LuaGetParam}, //
|
||||
{"GetParams", LuaGetParams}, //
|
||||
{"GetPath", LuaGetPath}, //
|
||||
{"GetPayload", LuaGetPayload}, //
|
||||
{"GetServerAddr", LuaGetServerAddr}, //
|
||||
{"GetUri", LuaGetUri}, //
|
||||
{"GetVersion", LuaGetVersion}, //
|
||||
{"HasParam", LuaHasParam}, //
|
||||
{"LoadAsset", LuaLoadAsset}, //
|
||||
{"ParseHttpDateTime", LuaParseHttpDateTime}, //
|
||||
{"ServeAsset", LuaServeAsset}, //
|
||||
{"ServeError", LuaServeError}, //
|
||||
{"SetHeader", LuaSetHeader}, //
|
||||
{"SetStatus", LuaSetStatus}, //
|
||||
{"Write", LuaWrite}, //
|
||||
{"bsf", LuaBsf}, //
|
||||
{"bsr", LuaBsr}, //
|
||||
{"crc32", LuaCrc32}, //
|
||||
{"crc32c", LuaCrc32c}, //
|
||||
{"popcnt", LuaPopcnt}, //
|
||||
};
|
||||
|
||||
static void LuaSetArgv(void) {
|
||||
|
@ -1723,6 +1809,7 @@ static char *ServeLua(struct Asset *a) {
|
|||
}
|
||||
|
||||
static bool IsLua(struct Asset *a) {
|
||||
if (a->file) return endswith(a->file->path, ".lua");
|
||||
return ZIP_LFILE_NAMESIZE(zmap + a->lf) >= 4 &&
|
||||
!memcmp(ZIP_LFILE_NAME(zmap + a->lf) +
|
||||
ZIP_LFILE_NAMESIZE(zmap + a->lf) - 4,
|
||||
|
@ -1764,13 +1851,16 @@ static char *HandleRedirect(struct Redirect *r) {
|
|||
}
|
||||
|
||||
static void LogMessage(const char *d, const char *s, size_t n) {
|
||||
char *s2, *s3;
|
||||
size_t n2, n3;
|
||||
size_t n2, n3, n4;
|
||||
char *s2, *s3, *s4;
|
||||
if (!logmessages) return;
|
||||
while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n;
|
||||
if ((s2 = DecodeLatin1(s, n, &n2))) {
|
||||
if ((s3 = VisualizeControlCodes(s2, n2, &n3))) {
|
||||
LOGF("%s %s %,ld byte message\n%.*s", clientaddrstr, d, n, n3, s3);
|
||||
if ((s4 = IndentLines(s3, n3, &n4, 1))) {
|
||||
LOGF("%s %s %,ld byte message\n%.*s", clientaddrstr, d, n, n4, s4);
|
||||
free(s4);
|
||||
}
|
||||
free(s3);
|
||||
}
|
||||
free(s2);
|
||||
|
@ -1778,12 +1868,15 @@ static void LogMessage(const char *d, const char *s, size_t n) {
|
|||
}
|
||||
|
||||
static void LogBody(const char *d, const char *s, size_t n) {
|
||||
char *s2;
|
||||
size_t n2;
|
||||
char *s2, *s3;
|
||||
size_t n2, n3;
|
||||
if (!n || !logbodies) return;
|
||||
while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n;
|
||||
if ((s2 = VisualizeControlCodes(s, n, &n2))) {
|
||||
LOGF("%s %s %,ld byte payload\n%.*s", clientaddrstr, d, n, n2, s2);
|
||||
if ((s3 = IndentLines(s2, n2, &n3, 1))) {
|
||||
LOGF("%s %s %,ld byte payload\n%.*s", clientaddrstr, d, n, n3, s3);
|
||||
free(s3);
|
||||
}
|
||||
free(s2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,8 +74,8 @@ local function main()
|
|||
Write([[
|
||||
<h3>xmlhttprequest request demo</h3>
|
||||
<input id="x" value="lâtìn1">
|
||||
<label for="x">name</label><br>
|
||||
<button id="send">send (via http header)</button><br>
|
||||
<label for="x">X-Custom-Header</label><br>
|
||||
<button id="send">send</button><br>
|
||||
<div id="result"></div>
|
||||
<script>
|
||||
function OnSend() {
|
||||
|
@ -90,6 +90,21 @@ local function main()
|
|||
document.getElementById('send').onclick = OnSend;
|
||||
</script>
|
||||
]])
|
||||
|
||||
Write('<h3>extra information</h3>\n')
|
||||
Write('<dt>GetClientAddr()\n')
|
||||
Write('<dd>')
|
||||
Write(GetClientAddr())
|
||||
Write('\n')
|
||||
Write('<dt>GetServerAddr()\n')
|
||||
Write('<dd>')
|
||||
Write(GetServerAddr())
|
||||
Write('\n')
|
||||
Write('<dt>FormatHttpDateTime(GetDate())\n')
|
||||
Write('<dd>')
|
||||
Write(FormatHttpDateTime(GetDate()))
|
||||
Write('\n')
|
||||
Write('</dl>\n')
|
||||
end
|
||||
|
||||
main()
|
||||
|
|
Loading…
Add table
Reference in a new issue