syntax.
-
-
-
-
-
-
-
-
-Berners-Lee, et. al. Standards Track [Page 39]
-
-RFC 2396 URI Generic Syntax August 1998
-
-
-H. Full Copyright Statement
-
- Copyright (C) The Internet Society (1998). All Rights Reserved.
-
- This document and translations of it may be copied and furnished to
- others, and derivative works that comment on or otherwise explain it
- or assist in its implementation may be prepared, copied, published
- and distributed, in whole or in part, without restriction of any
- kind, provided that the above copyright notice and this paragraph are
- included on all such copies and derivative works. However, this
- document itself may not be modified in any way, such as by removing
- the copyright notice or references to the Internet Society or other
- Internet organizations, except as needed for the purpose of
- developing Internet standards in which case the procedures for
- copyrights defined in the Internet Standards process must be
- followed, or as required to translate it into languages other than
- English.
-
- The limited permissions granted above are perpetual and will not be
- revoked by the Internet Society or its successors or assigns.
-
- This document and the information contained herein is provided on an
- "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
- TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
- BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
- HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
- MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Berners-Lee, et. al. Standards Track [Page 40]
-
diff --git a/net/http/underlong.c b/net/http/underlong.c
new file mode 100644
index 000000000..0d924dbfb
--- /dev/null
+++ b/net/http/underlong.c
@@ -0,0 +1,85 @@
+/*-*- 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/intrin/pcmpgtb.h"
+#include "libc/intrin/pmovmskb.h"
+#include "libc/mem/mem.h"
+#include "libc/str/str.h"
+#include "libc/str/thompike.h"
+#include "libc/str/tpenc.h"
+#include "net/http/escape.h"
+
+/**
+ * Canonicalizes overlong Thompson-Pike encodings.
+ *
+ * Please note that the IETF banned these numbers, so if you want to
+ * enforce their ban you can simply strcmp() the result to check for
+ * the existence of banned numbers.
+ *
+ * @param p is input value
+ * @param n if -1 implies strlen
+ * @param z if non-NULL receives output length
+ * @return allocated NUL-terminated buffer, or NULL w/ errno
+ */
+char *Underlong(const char *p, size_t n, size_t *z) {
+ uint64_t w;
+ char *r, *q;
+ size_t i, j, m;
+ wint_t x, a, b;
+ int8_t v1[16], v2[16], vz[16];
+ if (z) *z = 0;
+ if (n == -1) n = p ? strlen(p) : 0;
+ if ((q = r = malloc(n + 1))) {
+ for (i = 0; i < n;) {
+ memset(vz, 0, 16); /* 50x speedup for ASCII */
+ while (i + 16 < n) {
+ memcpy(v1, p + i, 16);
+ pcmpgtb(v2, v1, vz);
+ if (pmovmskb((void *)v2) != 0xFFFF) break;
+ memcpy(q, v1, 16);
+ q += 16;
+ i += 16;
+ }
+ x = p[i++] & 0xff;
+ if (x >= 0300) {
+ a = ThomPikeByte(x);
+ m = ThomPikeLen(x) - 1;
+ if (i + m <= n) {
+ for (j = 0;;) {
+ b = p[i + j] & 0xff;
+ if (!ThomPikeCont(b)) break;
+ a = ThomPikeMerge(a, b);
+ if (++j == m) {
+ x = a;
+ i += j;
+ break;
+ }
+ }
+ }
+ }
+ w = tpenc(x);
+ do {
+ *q++ = w;
+ } while ((w >>= 8));
+ }
+ if (z) *z = q - r;
+ *q++ = '\0';
+ if ((q = realloc(r, q - r))) r = q;
+ }
+ return r;
+}
diff --git a/net/http/uri.h b/net/http/uri.h
deleted file mode 100644
index 9cb4e224e..000000000
--- a/net/http/uri.h
+++ /dev/null
@@ -1,118 +0,0 @@
-#ifndef COSMOPOLITAN_NET_HTTP_URI_H_
-#define COSMOPOLITAN_NET_HTTP_URI_H_
-#include "libc/dns/dns.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
-
-enum UriScheme {
- kUriSchemeHttp = 1,
- kUriSchemeHttps,
- kUriSchemeFile,
- kUriSchemeData,
- kUriSchemeZip,
- kUriSchemeSip,
- kUriSchemeSips,
- kUriSchemeTel,
- kUriSchemeSsh,
- kUriSchemeGs,
- kUriSchemeS3
-};
-
-struct UriSlice {
- /*
- * !i && !n means absent
- * i && !n means empty
- */
- unsigned i, n;
-};
-
-struct UriSlices {
- unsigned i, n;
- struct UriSlice *p;
-};
-
-struct UriKeyval {
- struct UriSlice k, v;
-};
-
-struct UriKeyvals {
- unsigned i, n;
- struct UriKeyval * p;
-};
-
-struct UriRef {
- unsigned r;
-};
-
-struct UriRefs {
- unsigned i, n;
- struct UriRef * p;
-};
-
-struct Uri {
- /*
- * e.g. "", "http", "sip", "http", "dns+http", etc.
- */
- struct UriSlice scheme;
-
- /*
- * Holds remainder for exotic URI schemes, e.g. data.
- */
- struct UriSlice opaque;
-
- /*
- * e.g. sip:user@host, //user:pass@host
- */
- struct UriSlice userinfo;
-
- /*
- * e.g. "", "example.com", "1.2.3.4", "::1", etc.
- */
- struct UriSlice host;
-
- /*
- * e.g. "", "5060", "80", etc.
- */
- struct UriSlice port;
-
- /*
- * e.g. /dir/index.html means
- * - memcmp("/dir/index.html",
- * p + segs.p[0].i,
- * (segs.p[segs.i - 1].n +
- * (segs.p[segs.i - 1].i -
- * segs.p[0].i))) == 0
- * - memcmp("/dir", p + segs.p[0].i, segs.p[0].n) == 0
- * - memcmp("/index.html", p + segs.p[1].i, segs.p[1].n) == 0
- */
- struct UriSlices segs;
-
- /* e.g. ;lr;isup-oli=00;day=tuesday */
- struct UriKeyvals params;
-
- /*
- * e.g. /dir;super=rare/index.html
- *
- * let 𝑖 ∈ [0,params.i)
- * paramsegs.p[𝑖].r ∈ [0,segs.i]
- */
- struct UriRefs paramsegs;
-
- /* e.g. ?boop&subject=project%20x&lol=cat */
- struct UriKeyvals queries;
-
- /* e.g. #anchor */
- struct UriSlice fragment;
-};
-
-int uricspn(const char *data, size_t size);
-int uriparse(struct Uri *, const char *, size_t) paramsnonnull((1));
-enum UriScheme urischeme(struct UriSlice, const char *)
- paramsnonnull() nosideeffect;
-struct UriSlice uripath(const struct Uri *) paramsnonnull() nosideeffect;
-char *urislice2cstr(char *, size_t, struct UriSlice, const char *, const char *)
- paramsnonnull((1, 4));
-
-COSMOPOLITAN_C_END_
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_NET_HTTP_URI_H_ */
diff --git a/net/http/uricspn-avx.S b/net/http/uricspn-avx.S
deleted file mode 100644
index e03f71a9f..000000000
--- a/net/http/uricspn-avx.S
+++ /dev/null
@@ -1,61 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 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/macros.internal.h"
-
-// Verifies buffer contains only URI characters.
-//
-// @param %rdi is data which should be 32-byte aligned
-// @param %rsi is byte length of data
-// @return number of kosher bytes
-// @cost 10x faster than fastest Ragel code
-uricspn$avx:
- .leafprologue
- .profilable
- vmovaps .Luric(%rip),%xmm0
- mov $14,%eax
- mov %rsi,%rdx
- xor %esi,%esi
-0: vmovdqu (%rdi,%rsi),%xmm1
- vmovdqu 16(%rdi,%rsi),%xmm2
- vpcmpestri $0b00010100,%xmm1,%xmm0
- jc 1f
- jo 1f
- add $16,%rsi
- sub $16,%rdx
- vpcmpestri $0b00010100,%xmm2,%xmm0
- jc 1f
- jo 1f
- add $16,%rsi
- sub $16,%rdx
- jmp 0b
-1: lea (%rsi,%rcx),%rax
- .leafepilogue
- .endfn uricspn$avx,globl,hidden
-
- .rodata.cst16
-.Luric: .byte '!','!'
- .byte '$',';'
- .byte '=','='
- .byte '?','Z'
- .byte '_','_'
- .byte 'a','z'
- .byte '~','~'
- .byte 0,0
- .endobj .Luric
- .previous
diff --git a/net/http/uricspn.c b/net/http/uricspn.c
deleted file mode 100644
index 502b5e894..000000000
--- a/net/http/uricspn.c
+++ /dev/null
@@ -1,185 +0,0 @@
-/*-*- 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/assert.h"
-#include "libc/nexgen32e/x86feature.h"
-#include "libc/sysv/errfuns.h"
-#include "net/http/uri.h"
-
-/*
- * GENERATED BY
- *
- * ragel -o net/http/uricspn.c net/http/uricspn.rl
- *
- * TODO(jart): Rewrite in normal C.
- */
-
-#define static
-
-/* clang-format off */
-
-#line 29 "net/http/uricspn.rl"
-
-#line 34 "build/bootstrap/net/http/uricspn.c"
-static const char _uricspn_key_offsets[] = {
- 0, 0
-};
-
-static const char _uricspn_trans_keys[] = {
- 33, 61, 95, 126, 36, 59, 63, 90,
- 97, 122, 0
-};
-
-static const char _uricspn_single_lengths[] = {
- 0, 4
-};
-
-static const char _uricspn_range_lengths[] = {
- 0, 3
-};
-
-static const char _uricspn_index_offsets[] = {
- 0, 0
-};
-
-static const char _uricspn_trans_targs[] = {
- 1, 1, 1, 1, 1, 1, 1, 0,
- 0
-};
-
-static const int uricspn_start = 1;
-static const int uricspn_first_final = 1;
-static const int uricspn_error = 0;
-
-static const int uricspn_en_machina = 1;
-
-
-#line 30 "net/http/uricspn.rl"
-/* clang-format on */
-
-int uricspn(const char *data, size_t size) {
- int uricspn$avx(const char *, size_t) hidden;
- const char *p, *pe;
- int cs;
-
- assert(data || !size);
- assert(size <= 0x7ffff000);
- assert(size <= 0x7ffff000);
-
- if (X86_HAVE(AVX)) {
- return uricspn$avx(data, size);
- }
-
- p = data;
- pe = data + size;
-
- /* clang-format off */
-
-
-#line 56 "net/http/uricspn.rl"
-
-
-
-#line 94 "build/bootstrap/net/http/uricspn.c"
- {
- cs = uricspn_start;
- }
-
-#line 59 "net/http/uricspn.rl"
- cs = uricspn_en_machina;
-
-#line 102 "build/bootstrap/net/http/uricspn.c"
- {
- int _klen;
- unsigned int _trans;
- const char *_keys;
-
- if ( p == pe )
- goto _test_eof;
- if ( cs == 0 )
- goto _out;
-_resume:
- _keys = _uricspn_trans_keys + _uricspn_key_offsets[cs];
- _trans = _uricspn_index_offsets[cs];
-
- _klen = _uricspn_single_lengths[cs];
- if ( _klen > 0 ) {
- const char *_lower = _keys;
- const char *_mid;
- const char *_upper = _keys + _klen - 1;
- while (1) {
- if ( _upper < _lower )
- break;
-
- _mid = _lower + ((_upper-_lower) >> 1);
- if ( (*p) < *_mid )
- _upper = _mid - 1;
- else if ( (*p) > *_mid )
- _lower = _mid + 1;
- else {
- _trans += (unsigned int)(_mid - _keys);
- goto _match;
- }
- }
- _keys += _klen;
- _trans += _klen;
- }
-
- _klen = _uricspn_range_lengths[cs];
- if ( _klen > 0 ) {
- const char *_lower = _keys;
- const char *_mid;
- const char *_upper = _keys + (_klen<<1) - 2;
- while (1) {
- if ( _upper < _lower )
- break;
-
- _mid = _lower + (((_upper-_lower) >> 1) & ~1);
- if ( (*p) < _mid[0] )
- _upper = _mid - 2;
- else if ( (*p) > _mid[1] )
- _lower = _mid + 2;
- else {
- _trans += (unsigned int)((_mid - _keys)>>1);
- goto _match;
- }
- }
- _trans += _klen;
- }
-
-_match:
- cs = _uricspn_trans_targs[_trans];
-
- if ( cs == 0 )
- goto _out;
- if ( ++p != pe )
- goto _resume;
- _test_eof: {}
- _out: {}
- }
-
-#line 61 "net/http/uricspn.rl"
-
- /* clang-format on */
-
- if (cs >= uricspn_first_final) {
- return p - data;
- } else {
- return einval();
- }
-}
diff --git a/net/http/uricspn.svgz b/net/http/uricspn.svgz
deleted file mode 100644
index f6ab643b4..000000000
Binary files a/net/http/uricspn.svgz and /dev/null differ
diff --git a/net/http/uriparse.c b/net/http/uriparse.c
deleted file mode 100644
index f3ea440a2..000000000
--- a/net/http/uriparse.c
+++ /dev/null
@@ -1,724 +0,0 @@
-/*-*- 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/assert.h"
-#include "libc/dce.h"
-#include "libc/dns/dns.h"
-#include "libc/log/log.h"
-#include "libc/str/str.h"
-#include "libc/sysv/errfuns.h"
-#include "net/http/uri.h"
-
-/*
- * GENERATED BY
- *
- * ragel -o net/http/uriparse.c net/http/uriparse.rl
- *
- * TODO(jart): Rewrite in normal C.
- */
-
-#define static
-
-/* clang-format off */
-
-#line 32 "net/http/uriparse.rl"
-
-#line 37 "build/bootstrap/net/http/uriparse.c"
-static const char _uriparse_actions[] = {
- 0, 1, 0, 1, 1, 1, 2, 1,
- 3, 1, 4, 1, 5, 1, 6, 1,
- 8, 1, 11, 1, 12, 2, 0, 2,
- 2, 4, 8, 2, 5, 8, 2, 6,
- 9, 2, 6, 10, 2, 7, 9, 2,
- 7, 10, 2, 8, 0, 2, 11, 0,
- 3, 4, 8, 0, 3, 5, 8, 0,
- 3, 6, 9, 0, 3, 7, 9, 0
-
-};
-
-static const short _uriparse_key_offsets[] = {
- 0, 0, 6, 12, 18, 24, 37, 43,
- 49, 64, 70, 76, 91, 97, 103, 118,
- 124, 130, 145, 151, 157, 169, 188, 202,
- 208, 214, 224, 226, 233, 241, 256, 273,
- 279, 285, 302, 308, 314, 326, 332, 338,
- 357, 371, 377, 383, 393, 395, 410, 416,
- 422, 437, 443, 449, 456, 464, 479, 494,
- 509, 520, 531, 546, 564, 581, 598, 614,
- 625, 630, 634, 653, 671, 689, 707, 727,
- 728, 739, 742, 759, 775, 777, 797
-};
-
-static const char _uriparse_trans_keys[] = {
- 48, 57, 65, 70, 97, 102, 48, 57,
- 65, 70, 97, 102, 48, 57, 65, 70,
- 97, 102, 48, 57, 65, 70, 97, 102,
- 33, 37, 61, 95, 126, 36, 46, 48,
- 58, 64, 90, 97, 122, 48, 57, 65,
- 70, 97, 102, 48, 57, 65, 70, 97,
- 102, 33, 37, 93, 95, 126, 36, 43,
- 45, 46, 48, 58, 65, 91, 97, 122,
- 48, 57, 65, 70, 97, 102, 48, 57,
- 65, 70, 97, 102, 33, 37, 93, 95,
- 126, 36, 43, 45, 46, 48, 58, 65,
- 91, 97, 122, 48, 57, 65, 70, 97,
- 102, 48, 57, 65, 70, 97, 102, 33,
- 36, 37, 63, 93, 95, 126, 39, 43,
- 45, 58, 65, 91, 97, 122, 48, 57,
- 65, 70, 97, 102, 48, 57, 65, 70,
- 97, 102, 33, 36, 37, 63, 93, 95,
- 126, 39, 43, 45, 58, 65, 91, 97,
- 122, 48, 57, 65, 70, 97, 102, 48,
- 57, 65, 70, 97, 102, 33, 37, 47,
- 61, 95, 126, 36, 58, 64, 90, 97,
- 122, 33, 37, 43, 58, 61, 63, 91,
- 95, 126, 36, 44, 45, 46, 48, 57,
- 65, 90, 97, 122, 33, 37, 61, 64,
- 95, 126, 36, 46, 48, 58, 63, 90,
- 97, 122, 48, 57, 65, 70, 97, 102,
- 48, 57, 65, 70, 97, 102, 43, 91,
- 45, 46, 48, 57, 65, 90, 97, 122,
- 48, 57, 46, 48, 58, 65, 70, 97,
- 102, 46, 93, 48, 58, 65, 70, 97,
- 102, 33, 37, 58, 61, 64, 95, 126,
- 36, 46, 48, 57, 63, 90, 97, 122,
- 33, 37, 38, 44, 47, 61, 64, 91,
- 93, 95, 126, 36, 58, 63, 90, 97,
- 122, 48, 57, 65, 70, 97, 102, 48,
- 57, 65, 70, 97, 102, 33, 37, 38,
- 44, 47, 61, 64, 91, 93, 95, 126,
- 36, 58, 63, 90, 97, 122, 48, 57,
- 65, 70, 97, 102, 48, 57, 65, 70,
- 97, 102, 33, 37, 47, 61, 95, 126,
- 36, 59, 63, 90, 97, 122, 48, 57,
- 65, 70, 97, 102, 48, 57, 65, 70,
- 97, 102, 33, 37, 43, 58, 61, 63,
- 91, 95, 126, 36, 44, 45, 46, 48,
- 57, 65, 90, 97, 122, 33, 37, 61,
- 64, 95, 126, 36, 46, 48, 58, 63,
- 90, 97, 122, 48, 57, 65, 70, 97,
- 102, 48, 57, 65, 70, 97, 102, 43,
- 91, 45, 46, 48, 57, 65, 90, 97,
- 122, 48, 57, 33, 37, 93, 95, 126,
- 36, 43, 45, 46, 48, 58, 65, 91,
- 97, 122, 48, 57, 65, 70, 97, 102,
- 48, 57, 65, 70, 97, 102, 33, 37,
- 93, 95, 126, 36, 43, 45, 46, 48,
- 58, 65, 91, 97, 122, 48, 57, 65,
- 70, 97, 102, 48, 57, 65, 70, 97,
- 102, 46, 48, 58, 65, 70, 97, 102,
- 46, 93, 48, 58, 65, 70, 97, 102,
- 33, 37, 58, 61, 64, 95, 126, 36,
- 46, 48, 57, 63, 90, 97, 122, 33,
- 35, 37, 47, 59, 61, 64, 95, 126,
- 36, 57, 65, 90, 97, 122, 33, 35,
- 37, 47, 59, 61, 63, 95, 126, 36,
- 57, 64, 90, 97, 122, 33, 37, 61,
- 95, 126, 36, 59, 63, 90, 97, 122,
- 33, 37, 61, 95, 126, 36, 59, 63,
- 90, 97, 122, 33, 35, 37, 47, 59,
- 61, 63, 95, 126, 36, 58, 64, 90,
- 97, 122, 33, 35, 37, 47, 59, 61,
- 63, 93, 95, 126, 36, 43, 45, 58,
- 65, 91, 97, 122, 33, 35, 37, 47,
- 59, 63, 93, 95, 126, 36, 43, 45,
- 58, 65, 91, 97, 122, 33, 35, 37,
- 38, 61, 63, 93, 95, 126, 36, 43,
- 45, 58, 65, 91, 97, 122, 33, 35,
- 37, 38, 63, 93, 95, 126, 36, 43,
- 45, 58, 65, 91, 97, 122, 35, 43,
- 47, 58, 63, 45, 57, 65, 90, 97,
- 122, 35, 47, 63, 48, 57, 35, 47,
- 58, 63, 33, 35, 37, 43, 47, 58,
- 61, 63, 64, 95, 126, 36, 44, 45,
- 57, 65, 90, 97, 122, 33, 35, 37,
- 47, 58, 61, 63, 64, 95, 126, 36,
- 46, 48, 57, 65, 90, 97, 122, 33,
- 35, 37, 38, 44, 47, 61, 64, 91,
- 93, 95, 126, 36, 58, 63, 90, 97,
- 122, 33, 35, 37, 38, 44, 47, 61,
- 64, 91, 93, 95, 126, 36, 58, 63,
- 90, 97, 122, 33, 35, 37, 43, 47,
- 58, 59, 61, 63, 64, 95, 126, 36,
- 44, 45, 57, 65, 90, 97, 122, 35,
- 43, 58, 59, 45, 46, 48, 57, 65,
- 90, 97, 122, 59, 48, 57, 33, 37,
- 59, 61, 93, 95, 126, 36, 43, 45,
- 46, 48, 58, 65, 91, 97, 122, 33,
- 37, 59, 93, 95, 126, 36, 43, 45,
- 46, 48, 58, 65, 91, 97, 122, 58,
- 59, 33, 37, 43, 58, 59, 61, 63,
- 64, 95, 126, 36, 44, 45, 46, 48,
- 57, 65, 90, 97, 122, 33, 37, 58,
- 59, 61, 64, 95, 126, 36, 46, 48,
- 57, 63, 90, 97, 122, 0
-};
-
-static const char _uriparse_single_lengths[] = {
- 0, 0, 0, 0, 0, 5, 0, 0,
- 5, 0, 0, 5, 0, 0, 7, 0,
- 0, 7, 0, 0, 6, 9, 6, 0,
- 0, 2, 0, 1, 2, 7, 11, 0,
- 0, 11, 0, 0, 6, 0, 0, 9,
- 6, 0, 0, 2, 0, 5, 0, 0,
- 5, 0, 0, 1, 2, 7, 9, 9,
- 5, 5, 9, 10, 9, 9, 8, 5,
- 3, 4, 11, 10, 12, 12, 12, 1,
- 3, 1, 7, 6, 2, 10, 8
-};
-
-static const char _uriparse_range_lengths[] = {
- 0, 3, 3, 3, 3, 4, 3, 3,
- 5, 3, 3, 5, 3, 3, 4, 3,
- 3, 4, 3, 3, 3, 5, 4, 3,
- 3, 4, 1, 3, 3, 4, 3, 3,
- 3, 3, 3, 3, 3, 3, 3, 5,
- 4, 3, 3, 4, 1, 5, 3, 3,
- 5, 3, 3, 3, 3, 4, 3, 3,
- 3, 3, 3, 4, 4, 4, 4, 3,
- 1, 0, 4, 4, 3, 3, 4, 0,
- 4, 1, 5, 5, 0, 5, 4
-};
-
-static const short _uriparse_index_offsets[] = {
- 0, 0, 4, 8, 12, 16, 26, 30,
- 34, 45, 49, 53, 64, 68, 72, 84,
- 88, 92, 104, 108, 112, 122, 137, 148,
- 152, 156, 163, 165, 170, 176, 188, 203,
- 207, 211, 226, 230, 234, 244, 248, 252,
- 267, 278, 282, 286, 293, 295, 306, 310,
- 314, 325, 329, 333, 338, 344, 356, 369,
- 382, 391, 400, 413, 428, 442, 456, 469,
- 478, 483, 488, 504, 519, 535, 551, 568,
- 570, 578, 581, 594, 606, 609, 625
-};
-
-static const unsigned char _uriparse_indicies[] = {
- 0, 0, 0, 1, 2, 2, 2, 1,
- 3, 3, 3, 1, 4, 4, 4, 1,
- 5, 6, 5, 5, 5, 5, 5, 5,
- 5, 1, 7, 7, 7, 1, 5, 5,
- 5, 1, 8, 9, 8, 8, 8, 8,
- 8, 8, 8, 8, 1, 10, 10, 10,
- 1, 11, 11, 11, 1, 12, 13, 12,
- 12, 12, 12, 12, 12, 12, 12, 1,
- 14, 14, 14, 1, 15, 15, 15, 1,
- 16, 16, 17, 16, 16, 16, 16, 16,
- 16, 16, 16, 1, 18, 18, 18, 1,
- 19, 19, 19, 1, 20, 20, 21, 20,
- 20, 20, 20, 20, 20, 20, 20, 1,
- 22, 22, 22, 1, 23, 23, 23, 1,
- 5, 6, 24, 5, 5, 5, 5, 5,
- 5, 1, 25, 26, 27, 25, 25, 25,
- 28, 25, 25, 25, 27, 27, 27, 27,
- 1, 29, 30, 29, 31, 29, 29, 29,
- 29, 29, 29, 1, 32, 32, 32, 1,
- 29, 29, 29, 1, 33, 28, 33, 33,
- 33, 33, 1, 34, 1, 35, 35, 35,
- 35, 1, 36, 37, 36, 36, 36, 1,
- 29, 30, 29, 29, 31, 29, 29, 29,
- 38, 29, 29, 1, 39, 40, 29, 29,
- 16, 29, 31, 16, 16, 39, 39, 39,
- 39, 39, 1, 41, 41, 41, 1, 42,
- 42, 42, 1, 43, 44, 29, 29, 20,
- 29, 31, 20, 20, 43, 43, 43, 43,
- 43, 1, 45, 45, 45, 1, 46, 46,
- 46, 1, 47, 48, 49, 47, 47, 47,
- 47, 47, 47, 1, 50, 50, 50, 1,
- 47, 47, 47, 1, 51, 52, 53, 51,
- 51, 51, 54, 51, 51, 51, 53, 53,
- 53, 53, 1, 55, 56, 55, 57, 55,
- 55, 55, 55, 55, 55, 1, 58, 58,
- 58, 1, 55, 55, 55, 1, 59, 60,
- 59, 59, 59, 59, 1, 61, 1, 62,
- 63, 62, 62, 62, 62, 62, 62, 62,
- 62, 1, 64, 64, 64, 1, 65, 65,
- 65, 1, 66, 67, 66, 66, 66, 66,
- 66, 66, 66, 66, 1, 68, 68, 68,
- 1, 69, 69, 69, 1, 70, 70, 70,
- 70, 1, 71, 72, 71, 71, 71, 1,
- 55, 56, 55, 55, 57, 55, 55, 55,
- 73, 55, 55, 1, 74, 75, 76, 49,
- 74, 74, 74, 74, 74, 74, 77, 77,
- 1, 4, 78, 79, 80, 4, 4, 81,
- 4, 4, 4, 4, 4, 1, 82, 83,
- 82, 82, 82, 82, 82, 82, 1, 2,
- 84, 2, 2, 2, 2, 2, 2, 1,
- 5, 78, 6, 80, 85, 5, 81, 5,
- 5, 5, 5, 5, 1, 11, 86, 87,
- 88, 89, 90, 91, 11, 11, 11, 11,
- 11, 11, 11, 1, 15, 92, 93, 94,
- 95, 96, 15, 15, 15, 15, 15, 15,
- 15, 1, 19, 97, 98, 99, 100, 19,
- 19, 19, 19, 19, 19, 19, 19, 1,
- 23, 101, 102, 103, 23, 23, 23, 23,
- 23, 23, 23, 23, 1, 104, 105, 106,
- 107, 108, 105, 105, 105, 1, 109, 110,
- 112, 111, 1, 113, 114, 115, 116, 1,
- 29, 104, 30, 117, 106, 118, 29, 119,
- 31, 29, 29, 29, 117, 117, 117, 1,
- 29, 109, 30, 110, 29, 29, 121, 31,
- 29, 29, 29, 120, 29, 29, 1, 42,
- 97, 122, 123, 29, 19, 124, 31, 19,
- 19, 42, 42, 42, 42, 42, 1, 46,
- 101, 125, 126, 29, 23, 29, 31, 23,
- 23, 46, 46, 46, 46, 46, 1, 4,
- 78, 79, 127, 80, 128, 4, 4, 81,
- 4, 4, 4, 4, 127, 127, 127, 1,
- 75, 1, 129, 130, 131, 129, 129, 129,
- 129, 1, 133, 132, 1, 65, 134, 135,
- 136, 65, 65, 65, 65, 65, 65, 65,
- 65, 1, 69, 137, 138, 69, 69, 69,
- 69, 69, 69, 69, 69, 1, 139, 140,
- 1, 55, 56, 141, 142, 131, 55, 55,
- 57, 55, 55, 55, 141, 141, 141, 141,
- 1, 55, 56, 55, 133, 55, 57, 55,
- 55, 55, 143, 55, 55, 1, 0
-};
-
-static const char _uriparse_trans_targs[] = {
- 2, 0, 57, 4, 55, 58, 6, 7,
- 59, 9, 10, 59, 60, 12, 13, 60,
- 61, 15, 16, 61, 62, 18, 19, 62,
- 21, 22, 23, 66, 27, 22, 23, 25,
- 24, 63, 64, 28, 28, 65, 67, 68,
- 31, 32, 68, 69, 34, 35, 69, 71,
- 37, 20, 38, 40, 41, 77, 51, 40,
- 41, 43, 42, 72, 51, 73, 74, 46,
- 47, 74, 75, 49, 50, 75, 52, 52,
- 76, 78, 55, 56, 3, 70, 56, 3,
- 5, 14, 57, 1, 1, 8, 56, 9,
- 5, 8, 11, 14, 56, 12, 5, 8,
- 14, 56, 15, 14, 17, 56, 18, 14,
- 56, 63, 5, 26, 14, 56, 5, 64,
- 14, 56, 5, 26, 14, 66, 29, 30,
- 67, 30, 31, 30, 33, 34, 30, 70,
- 36, 72, 44, 45, 73, 45, 46, 45,
- 48, 49, 45, 44, 45, 77, 53, 78
-};
-
-static const char _uriparse_trans_actions[] = {
- 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 1, 0, 0, 1, 1, 0, 0,
- 1, 1, 0, 0, 1, 1, 0, 0,
- 0, 1, 1, 1, 0, 0, 0, 7,
- 0, 1, 1, 1, 0, 9, 1, 1,
- 1, 0, 0, 1, 1, 0, 0, 19,
- 0, 1, 0, 1, 1, 1, 1, 0,
- 0, 7, 0, 1, 0, 1, 1, 1,
- 0, 0, 1, 1, 0, 0, 1, 0,
- 9, 1, 1, 0, 1, 1, 17, 0,
- 45, 17, 1, 1, 0, 17, 30, 0,
- 56, 30, 13, 30, 36, 0, 60, 36,
- 36, 33, 0, 33, 13, 39, 0, 39,
- 24, 0, 48, 9, 24, 27, 52, 0,
- 27, 15, 42, 0, 15, 0, 9, 24,
- 0, 27, 0, 33, 13, 0, 39, 0,
- 3, 0, 9, 9, 0, 11, 0, 30,
- 13, 0, 36, 0, 0, 0, 9, 0
-};
-
-static const char _uriparse_eof_actions[] = {
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 17,
- 21, 5, 17, 30, 36, 33, 39, 24,
- 27, 15, 24, 27, 33, 39, 17, 0,
- 9, 11, 30, 36, 0, 9, 11
-};
-
-static const int uriparse_start = 54;
-static const int uriparse_first_final = 54;
-static const int uriparse_error = 0;
-
-static const int uriparse_en_sip = 39;
-static const int uriparse_en_uri = 54;
-
-
-#line 33 "net/http/uriparse.rl"
-/* clang-format on */
-
-/**
- * Parses URI.
- *
- * This is a general URL parser. It's typically used for HTTP. Support
- * for the bonus syntax needed by SIP is provided. The whirlwhind tour
- * of the URI rabbit hole is as follows:
- *
- * /foo.html
- * //justine.local/foo.html
- * http://justine.local/foo.html
- * http://bettersearchengine.local/search.cgi?q=my%20query
- * file:///etc/passwd
- * gs://bucket/object.txt
- * zip:///usr/share/zoneinfo/GMT
- * sip:127.0.0.1:5060;lr
- * sip:+12125650666@gateway.example
- * sip:bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00
- * data:video/mpeg;base64,gigabytesofhex
- *
- * This parser operates on slices rather than C strings. It performs
- * slicing and validation only. Operations like turning "%20"→" " or
- * "80"→80 and perfect hashing can be done later, if needed.
- *
- * The Uri object is owned by the caller; it has a lifecycle like the
- * following:
- *
- * struct Uri uri;
- * memset(&uri, 0, sizeof(uri));
- *
- * uriparse(&uri, s1, strlen(s1));
- * CHECK_EQ(kUriSchemeHttp, urischeme(uri->scheme, s1));
- *
- * uriparse(&uri, s2, strlen(s2));
- * printf("host = %`.*s\n", uri->host.n, s2 + uri->host.i);
- *
- * Inner arrays may be granted memory by the caller. The uri->𝐴.i field
- * is cleared at the mark of this function. No more than uri->𝐴.n items
- * can be inserted. If we need more than that, then ENOMEM is returned
- * rather than dynamically extending uri->𝐴.p. However, if uri->𝐴.n==0,
- * we assume caller doesn't care about uri->𝐴 and its data is discarded.
- *
- * @param uri is owned by caller
- * @param p is caller-owned uri string; won't copy/alias/mutate
- * @return 0 on success, or -1 w/ errno
- * @see RFC2396: Uniform Resource Identifiers (URI): Generic Syntax
- * @see RFC3261: SIP: Session Initiation Protocol
- */
-int uriparse(struct Uri *uri, const char *p, size_t size) {
- unsigned zero, cs;
- struct UriKeyval kv;
- const char *pe, *eof, *buf, *mark;
-
- assert(p || !size);
- assert(size <= 0x7ffff000);
-
-#define ABSENT ((struct UriSlice){zero, zero})
-#define SLICE ((struct UriSlice){mark - buf, p - mark})
-
- cs = zero = VEIL("r", 0u);
- eof = pe = (mark = buf = p) + size;
-
- uri->scheme = ABSENT;
- uri->opaque = ABSENT;
- uri->userinfo = ABSENT;
- uri->host = ABSENT;
- uri->port = ABSENT;
- uri->fragment = ABSENT;
- uri->segs.i = zero;
- uri->paramsegs.i = zero;
- uri->params.i = zero;
- uri->queries.i = zero;
-
- /* clang-format off */
-
-
-#line 229 "net/http/uriparse.rl"
-
-
-
-#line 435 "build/bootstrap/net/http/uriparse.c"
- {
- cs = uriparse_start;
- }
-
-#line 232 "net/http/uriparse.rl"
- cs = uriparse_en_uri;
-
-#line 443 "build/bootstrap/net/http/uriparse.c"
- {
- int _klen;
- unsigned int _trans;
- const char *_acts;
- unsigned int _nacts;
- const char *_keys;
-
- if ( p == pe )
- goto _test_eof;
- if ( cs == 0 )
- goto _out;
-_resume:
- _keys = _uriparse_trans_keys + _uriparse_key_offsets[cs];
- _trans = _uriparse_index_offsets[cs];
-
- _klen = _uriparse_single_lengths[cs];
- if ( _klen > 0 ) {
- const char *_lower = _keys;
- const char *_mid;
- const char *_upper = _keys + _klen - 1;
- while (1) {
- if ( _upper < _lower )
- break;
-
- _mid = _lower + ((_upper-_lower) >> 1);
- if ( (*p) < *_mid )
- _upper = _mid - 1;
- else if ( (*p) > *_mid )
- _lower = _mid + 1;
- else {
- _trans += (unsigned int)(_mid - _keys);
- goto _match;
- }
- }
- _keys += _klen;
- _trans += _klen;
- }
-
- _klen = _uriparse_range_lengths[cs];
- if ( _klen > 0 ) {
- const char *_lower = _keys;
- const char *_mid;
- const char *_upper = _keys + (_klen<<1) - 2;
- while (1) {
- if ( _upper < _lower )
- break;
-
- _mid = _lower + (((_upper-_lower) >> 1) & ~1);
- if ( (*p) < _mid[0] )
- _upper = _mid - 2;
- else if ( (*p) > _mid[1] )
- _lower = _mid + 2;
- else {
- _trans += (unsigned int)((_mid - _keys)>>1);
- goto _match;
- }
- }
- _trans += _klen;
- }
-
-_match:
- _trans = _uriparse_indicies[_trans];
- cs = _uriparse_trans_targs[_trans];
-
- if ( _uriparse_trans_actions[_trans] == 0 )
- goto _again;
-
- _acts = _uriparse_actions + _uriparse_trans_actions[_trans];
- _nacts = (unsigned int) *_acts++;
- while ( _nacts-- > 0 )
- {
- switch ( *_acts++ )
- {
- case 0:
-#line 110 "net/http/uriparse.rl"
- { mark = p; }
- break;
- case 1:
-#line 111 "net/http/uriparse.rl"
- { uri->scheme = SLICE; }
- break;
- case 3:
-#line 113 "net/http/uriparse.rl"
- { uri->userinfo = SLICE; }
- break;
- case 4:
-#line 114 "net/http/uriparse.rl"
- { uri->host = SLICE; }
- break;
- case 5:
-#line 115 "net/http/uriparse.rl"
- { uri->port = SLICE; }
- break;
- case 6:
-#line 117 "net/http/uriparse.rl"
- {
- kv.k = SLICE;
- kv.v = (struct UriSlice){zero, zero};
- }
- break;
- case 7:
-#line 122 "net/http/uriparse.rl"
- {
- kv.v = SLICE;
- }
- break;
- case 8:
-#line 126 "net/http/uriparse.rl"
- {
- uri->segs.i = zero;
- uri->paramsegs.i = zero;
- }
- break;
- case 9:
-#line 131 "net/http/uriparse.rl"
- {
- if (uri->params.n) {
- if (uri->params.i < uri->params.n) {
- uri->params.p[uri->params.i++] = kv;
- } else {
- return enomem();
- }
- }
- }
- break;
- case 10:
-#line 141 "net/http/uriparse.rl"
- {
- if (uri->queries.n) {
- if (uri->queries.i < uri->queries.n) {
- uri->queries.p[uri->queries.i++] = kv;
- } else {
- return enomem();
- }
- }
- }
- break;
- case 11:
-#line 151 "net/http/uriparse.rl"
- {
- if (p > mark && uri->segs.n) {
- if (uri->segs.i < uri->segs.n) {
- uri->segs.p[uri->segs.i++] = SLICE;
- } else {
- return enomem();
- }
- }
- }
- break;
- case 12:
-#line 161 "net/http/uriparse.rl"
- {
- switch (urischeme(uri->scheme, buf)) {
- case kUriSchemeSip:
- case kUriSchemeSips:
- --p;
- {cs = 39;goto _again;}
- default:
- if (uricspn(p, pe - p) == pe - p) {
- uri->opaque = (struct UriSlice){p - buf, pe - p};
- return zero;
- } else {
- return einval();
- }
- }
- }
- break;
-#line 611 "build/bootstrap/net/http/uriparse.c"
- }
- }
-
-_again:
- if ( cs == 0 )
- goto _out;
- if ( ++p != pe )
- goto _resume;
- _test_eof: {}
- if ( p == eof )
- {
- const char *__acts = _uriparse_actions + _uriparse_eof_actions[cs];
- unsigned int __nacts = (unsigned int) *__acts++;
- while ( __nacts-- > 0 ) {
- switch ( *__acts++ ) {
- case 0:
-#line 110 "net/http/uriparse.rl"
- { mark = p; }
- break;
- case 2:
-#line 112 "net/http/uriparse.rl"
- { uri->fragment = SLICE; }
- break;
- case 4:
-#line 114 "net/http/uriparse.rl"
- { uri->host = SLICE; }
- break;
- case 5:
-#line 115 "net/http/uriparse.rl"
- { uri->port = SLICE; }
- break;
- case 6:
-#line 117 "net/http/uriparse.rl"
- {
- kv.k = SLICE;
- kv.v = (struct UriSlice){zero, zero};
- }
- break;
- case 7:
-#line 122 "net/http/uriparse.rl"
- {
- kv.v = SLICE;
- }
- break;
- case 8:
-#line 126 "net/http/uriparse.rl"
- {
- uri->segs.i = zero;
- uri->paramsegs.i = zero;
- }
- break;
- case 9:
-#line 131 "net/http/uriparse.rl"
- {
- if (uri->params.n) {
- if (uri->params.i < uri->params.n) {
- uri->params.p[uri->params.i++] = kv;
- } else {
- return enomem();
- }
- }
- }
- break;
- case 10:
-#line 141 "net/http/uriparse.rl"
- {
- if (uri->queries.n) {
- if (uri->queries.i < uri->queries.n) {
- uri->queries.p[uri->queries.i++] = kv;
- } else {
- return enomem();
- }
- }
- }
- break;
- case 11:
-#line 151 "net/http/uriparse.rl"
- {
- if (p > mark && uri->segs.n) {
- if (uri->segs.i < uri->segs.n) {
- uri->segs.p[uri->segs.i++] = SLICE;
- } else {
- return enomem();
- }
- }
- }
- break;
-#line 699 "build/bootstrap/net/http/uriparse.c"
- }
- }
- }
-
- _out: {}
- }
-
-#line 234 "net/http/uriparse.rl"
-
- /* clang-format on */
-
- if (cs >= uriparse_first_final) {
- if (uri->host.n <= DNS_NAME_MAX && uri->port.n <= 6) {
- return zero;
- } else {
- return eoverflow();
- }
- } else {
- return einval();
- }
-}
diff --git a/net/http/uriparse.rl b/net/http/uriparse.rl
deleted file mode 100644
index 04133c16e..000000000
--- a/net/http/uriparse.rl
+++ /dev/null
@@ -1,247 +0,0 @@
-/*-*- 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 2020 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/assert.h"
-#include "libc/dce.h"
-#include "libc/dns/dns.h"
-#include "libc/log/log.h"
-#include "libc/str/str.h"
-#include "libc/sysv/errfuns.h"
-#include "net/http/uri.h"
-
-/* TODO(jart): Rewrite in C */
-
-#define static
-
-/* clang-format off */
-%% machine uriparse;
-%% write data;
-/* clang-format on */
-
-/**
- * Parses URI.
- *
- * This is a general URL parser. It's typically used for HTTP. Support
- * for the bonus syntax needed by SIP is provided. The whirlwhind tour
- * of the URI rabbit hole is as follows:
- *
- * /foo.html
- * //justine.local/foo.html
- * http://justine.local/foo.html
- * http://bettersearchengine.local/search.cgi?q=my%20query
- * file:///etc/passwd
- * gs://bucket/object.txt
- * zip:///usr/share/zoneinfo/GMT
- * sip:127.0.0.1:5060;lr
- * sip:+12125650666@gateway.example
- * sip:bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00
- * data:video/mpeg;base64,gigabytesofhex
- *
- * This parser operates on slices rather than C strings. It performs
- * slicing and validation only. Operations like turning "%20"→" " or
- * "80"→80 and perfect hashing can be done later, if needed.
- *
- * The Uri object is owned by the caller; it has a lifecycle like the
- * following:
- *
- * struct Uri uri;
- * memset(&uri, 0, sizeof(uri));
- *
- * uriparse(&uri, s1, strlen(s1));
- * CHECK_EQ(kUriSchemeHttp, urischeme(uri->scheme, s1));
- *
- * uriparse(&uri, s2, strlen(s2));
- * printf("host = %`.*s\n", uri->host.n, s2 + uri->host.i);
- *
- * Inner arrays may be granted memory by the caller. The uri->𝐴.i field
- * is cleared at the mark of this function. No more than uri->𝐴.n items
- * can be inserted. If we need more than that, then ENOMEM is returned
- * rather than dynamically extending uri->𝐴.p. However, if uri->𝐴.n==0,
- * we assume caller doesn't care about uri->𝐴 and its data is discarded.
- *
- * @param uri is owned by caller
- * @param p is caller-owned uri string; won't copy/alias/mutate
- * @return 0 on success, or -1 w/ errno
- * @see RFC2396: Uniform Resource Identifiers (URI): Generic Syntax
- * @see RFC3261: SIP: Session Initiation Protocol
- */
-int uriparse(struct Uri *uri, const char *p, size_t size) {
- unsigned zero, cs;
- struct UriKeyval kv;
- const char *pe, *eof, *buf, *mark;
-
- assert(p || !size);
- assert(size <= 0x7ffff000);
-
-#define ABSENT ((struct UriSlice){zero, zero})
-#define SLICE ((struct UriSlice){mark - buf, p - mark})
-
- cs = zero = VEIL("r", 0u);
- eof = pe = (mark = buf = p) + size;
-
- uri->scheme = ABSENT;
- uri->opaque = ABSENT;
- uri->userinfo = ABSENT;
- uri->host = ABSENT;
- uri->port = ABSENT;
- uri->fragment = ABSENT;
- uri->segs.i = zero;
- uri->paramsegs.i = zero;
- uri->params.i = zero;
- uri->queries.i = zero;
-
- /* clang-format off */
-
- %%{
- action Mark { mark = p; }
- action SetScheme { uri->scheme = SLICE; }
- action SetFragment { uri->fragment = SLICE; }
- action SetUserinfo { uri->userinfo = SLICE; }
- action SetHost { uri->host = SLICE; }
- action SetPort { uri->port = SLICE; }
-
- action SetKey {
- kv.k = SLICE;
- kv.v = (struct UriSlice){zero, zero};
- }
-
- action SetVal {
- kv.v = SLICE;
- }
-
- action RestartSegs {
- uri->segs.i = zero;
- uri->paramsegs.i = zero;
- }
-
- action AppendParam {
- if (uri->params.n) {
- if (uri->params.i < uri->params.n) {
- uri->params.p[uri->params.i++] = kv;
- } else {
- return enomem();
- }
- }
- }
-
- action AppendQuery {
- if (uri->queries.n) {
- if (uri->queries.i < uri->queries.n) {
- uri->queries.p[uri->queries.i++] = kv;
- } else {
- return enomem();
- }
- }
- }
-
- action AppendSegment {
- if (p > mark && uri->segs.n) {
- if (uri->segs.i < uri->segs.n) {
- uri->segs.p[uri->segs.i++] = SLICE;
- } else {
- return enomem();
- }
- }
- }
-
- action HandleOpaquePart {
- switch (urischeme(uri->scheme, buf)) {
- case kUriSchemeSip:
- case kUriSchemeSips:
- --p;
- fgoto sip;
- default:
- if (uricspn(p, pe - p) == pe - p) {
- uri->opaque = (struct UriSlice){p - buf, pe - p};
- return zero;
- } else {
- return einval();
- }
- }
- }
-
- mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")";
- reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ",";
- unreserved = alnum | mark;
- ipv4c = digit | ".";
- ipv6c = xdigit | "." | ":";
- hostc = alnum | "-" | ".";
- telc = digit | "+" | "-";
- schemec = alnum | "+" | "-" | ".";
- userinfoc = unreserved | "&" | "=" | "+" | "$" | "," | "?" | ":";
- paramc = unreserved | "[" | "]" | ":" | "&" | "+" | "$";
- queryc = unreserved | "[" | "]" | "/" | "?" | ":" | "+" | "$";
- pathc = unreserved | ":" | "@" | "&" | "=" | "+" | "$" | ",";
- relc = unreserved | ";" | "@" | "&" | "=" | "+" | "$" | ",";
- uric = reserved | unreserved;
-
- escaped = "%" xdigit xdigit;
- pathchar = escaped | pathc;
- urichar = escaped | uric;
- relchar = escaped | relc;
- userinfochar = escaped | userinfoc;
- paramchar = escaped | paramc;
- querychar = escaped | queryc;
-
- paramkey = paramchar+ >Mark %SetKey;
- paramval = paramchar+ >Mark %SetVal;
- param = ";" paramkey ( "=" paramval )? %AppendParam;
-
- querykey = querychar+ >Mark %SetKey;
- queryval = querychar+ >Mark %SetVal;
- query = querykey ( "=" queryval )? %AppendQuery;
- queries = "?" query ( "&" query )*;
-
- scheme = ( alpha @Mark schemec* ) ":" @SetScheme;
- userinfo = userinfochar+ >Mark "@" @SetUserinfo;
- host6 = "[" ( ipv6c+ >Mark %SetHost ) "]";
- host = host6 | ( ( ipv4c | hostc | telc )+ >Mark %SetHost );
- port = digit+ >Mark %SetPort;
- hostport = host ( ":" port )?;
- authority = userinfo? hostport;
- segment = pathchar+ %AppendSegment param*;
- rel_segment = relchar+ >Mark %AppendSegment;
- path_segments = segment ( "/" @Mark segment )*;
- abs_path = "/" @Mark path_segments;
- net_path = "//" authority abs_path? >RestartSegs;
- hier_part = ( net_path | abs_path ) queries?;
- rel_path = rel_segment abs_path?;
- opaque_part = ( urichar -- "/" ) @HandleOpaquePart;
- fragment = "#" urichar* >Mark %SetFragment;
- relativeURI = ( net_path | abs_path | rel_path ) queries?;
- absoluteURI = scheme ( hier_part | opaque_part );
- sip := authority >Mark param*;
- uri := ( relativeURI | absoluteURI )? fragment?;
- }%%
-
- %% write init;
- cs = uriparse_en_uri;
- %% write exec;
-
- /* clang-format on */
-
- if (cs >= uriparse_first_final) {
- if (uri->host.n <= DNS_NAME_MAX && uri->port.n <= 6) {
- return zero;
- } else {
- return eoverflow();
- }
- } else {
- return einval();
- }
-}
diff --git a/net/http/uriparse.svgz b/net/http/uriparse.svgz
deleted file mode 100644
index f6ab643b4..000000000
Binary files a/net/http/uriparse.svgz and /dev/null differ
diff --git a/net/http/url.h b/net/http/url.h
new file mode 100644
index 000000000..77c047145
--- /dev/null
+++ b/net/http/url.h
@@ -0,0 +1,39 @@
+#ifndef COSMOPOLITAN_NET_HTTP_URL_H_
+#define COSMOPOLITAN_NET_HTTP_URL_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+struct UrlView {
+ size_t n;
+ char *p;
+};
+
+struct UrlParams {
+ size_t n;
+ struct UrlParam {
+ struct UrlView key;
+ struct UrlView val;
+ } * p;
+};
+
+struct Url {
+ struct UrlView scheme; /* must be [A-Za-z][-+.0-9A-Za-z]* or empty */
+ struct UrlView user; /* depends on host non-absence */
+ struct UrlView pass; /* depends on user non-absence */
+ struct UrlView host; /* or reg_name */
+ struct UrlView port; /* depends on host non-absence */
+ struct UrlView path; /* or opaque_part */
+ struct UrlParams params;
+ struct UrlView fragment;
+};
+
+char *EncodeUrl(struct Url *, size_t *);
+char *ParseUrl(const char *, size_t, struct Url *);
+char *ParseParams(const char *, size_t, struct UrlParams *);
+char *ParseRequestUri(const char *, size_t, struct Url *);
+char *ParseHost(const char *, size_t, struct Url *);
+char *EscapeUrlView(char *, struct UrlView *, const char[256]);
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_NET_HTTP_URL_H_ */
diff --git a/net/http/visualizecontrolcodes.c b/net/http/visualizecontrolcodes.c
index d1049731d..3ba14fdac 100644
--- a/net/http/visualizecontrolcodes.c
+++ b/net/http/visualizecontrolcodes.c
@@ -20,7 +20,7 @@
#include "libc/str/str.h"
#include "libc/str/thompike.h"
#include "libc/str/tpenc.h"
-#include "net/http/http.h"
+#include "net/http/escape.h"
/**
* Filters out control codes from string.
@@ -29,9 +29,13 @@
* want full blown C string literal escaping, but we don't want things
* like raw ANSI control codes from untrusted devices in our terminals.
*
+ * This function also canonicalizes overlong encodings. Therefore it
+ * isn't necessary to say VisualizeControlCodes(Underlong(𝑥))) since
+ * VisualizeControlCodes(𝑥) will do the same thing.
+ *
* @param data is input value
* @param size if -1 implies strlen
- * @param out_size if non-NULL receives output length on success
+ * @param out_size if non-NULL receives output length
* @return allocated NUL-terminated buffer, or NULL w/ errno
*/
char *VisualizeControlCodes(const char *data, size_t size, size_t *out_size) {
@@ -40,7 +44,7 @@ char *VisualizeControlCodes(const char *data, size_t size, size_t *out_size) {
unsigned i, n;
wint_t x, a, b;
const char *p, *e;
- if (size == -1) size = strlen(data);
+ if (size == -1) size = data ? strlen(data) : 0;
if ((r = malloc(size * 6 + 1))) {
q = r;
p = data;
@@ -85,9 +89,14 @@ char *VisualizeControlCodes(const char *data, size_t size, size_t *out_size) {
} while ((w >>= 8));
}
}
- if (out_size) *out_size = q - r;
+ n = q - r;
*q++ = '\0';
if ((q = realloc(r, q - r))) r = q;
+ } else {
+ n = 0;
+ }
+ if (out_size) {
+ *out_size = n;
}
return r;
}
diff --git a/test/libc/bits/bitreverse_test.c b/test/libc/bits/bitreverse_test.c
index ae2a8014a..acd47ae28 100644
--- a/test/libc/bits/bitreverse_test.c
+++ b/test/libc/bits/bitreverse_test.c
@@ -21,29 +21,24 @@
#include "libc/testlib/testlib.h"
TEST(bitreverse, test) {
+ EXPECT_EQ(0xde, BITREVERSE8(123));
EXPECT_EQ(0xde, bitreverse8(123));
- EXPECT_EQ(0xde, (bitreverse8)(123));
+ EXPECT_EQ(0xde00, BITREVERSE16(123));
EXPECT_EQ(0xde00, bitreverse16(123));
- EXPECT_EQ(0xde00, (bitreverse16)(123));
EXPECT_EQ(0xde000000u, bitreverse32(123));
- EXPECT_EQ(0xde000000u, (bitreverse32)(123));
EXPECT_EQ(0xde00000000000000ul, bitreverse64(123));
- EXPECT_EQ(0xde00000000000000ul, (bitreverse64)(123));
EXPECT_EQ(0x482d96c305f7c697ul, bitreverse64(0xe963efa0c369b412));
- EXPECT_EQ(0x482d96c305f7c697ul, (bitreverse64)(0xe963efa0c369b412));
}
BENCH(bitreverse, bench) {
- EZBENCH2("bitreverse8 mac", donothing,
+ EZBENCH2("BITREVERSE8", donothing,
+ EXPROPRIATE(BITREVERSE8(CONCEAL("r", 123))));
+ EZBENCH2("bitreverse8", donothing,
EXPROPRIATE(bitreverse8(CONCEAL("r", 123))));
- EZBENCH2("bitreverse8 fun", donothing,
- EXPROPRIATE((bitreverse8)(CONCEAL("r", 123))));
- EZBENCH2("bitreverse16 mac", donothing,
- EXPROPRIATE(bitreverse16(CONCEAL("r", 123))));
- EZBENCH2("bitreverse16 fun", donothing,
- EXPROPRIATE((bitreverse16)(CONCEAL("r", 123))));
- EZBENCH2("bitreverse32 mac", donothing,
+ EZBENCH2("BITREVERSE16", donothing,
+ EXPROPRIATE(BITREVERSE16(CONCEAL("r", 123))));
+ EZBENCH2("bitreverse32", donothing,
EXPROPRIATE(bitreverse32(CONCEAL("r", 123))));
- EZBENCH2("bitreverse32 fun", donothing,
- EXPROPRIATE((bitreverse32)(CONCEAL("r", (123)))));
+ EZBENCH2("bitreverse64", donothing,
+ EXPROPRIATE(bitreverse64(CONCEAL("r", 123))));
}
diff --git a/test/libc/calls/mprotect_test.c b/test/libc/calls/mprotect_test.c
new file mode 100644
index 000000000..7857f1066
--- /dev/null
+++ b/test/libc/calls/mprotect_test.c
@@ -0,0 +1,66 @@
+/*-*- 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/calls/calls.h"
+#include "libc/runtime/runtime.h"
+#include "libc/sysv/consts/prot.h"
+#include "libc/sysv/consts/sa.h"
+#include "libc/testlib/testlib.h"
+
+jmp_buf jb;
+bool gotsegv;
+struct sigaction old[2];
+
+void OnSigSegv(int sig) {
+ gotsegv = true;
+ longjmp(jb, 1);
+}
+
+void SetUp(void) {
+ sigaction(SIGBUS, NULL, &old[0]);
+ sigaction(SIGSEGV, NULL, &old[1]);
+}
+
+void TearDown(void) {
+ sigaction(SIGBUS, &old[0], NULL);
+ sigaction(SIGSEGV, &old[1], NULL);
+}
+
+TEST(mprotect, test) {
+ char *p = gc(memalign(PAGESIZE, PAGESIZE));
+ p[0] = 0;
+ ASSERT_NE(-1, mprotect(p, PAGESIZE, PROT_READ | PROT_WRITE));
+ p[0] = 1;
+ EXPECT_EQ(1, p[0]);
+}
+
+TEST(mprotect, testSegfault) {
+ char *p;
+ struct sigaction ss = {.sa_handler = OnSigSegv, .sa_flags = SA_NODEFER};
+ if (IsWindows()) return; /* TODO */
+ p = gc(memalign(PAGESIZE, PAGESIZE));
+ EXPECT_NE(-1, sigaction(SIGBUS, &ss, NULL));
+ EXPECT_NE(-1, sigaction(SIGSEGV, &ss, NULL));
+ if (!setjmp(jb)) p[0] = 1;
+ EXPECT_FALSE(gotsegv);
+ EXPECT_NE(-1, mprotect(p, sizeof(p), PROT_READ));
+ if (!setjmp(jb)) p[0] = 2;
+ EXPECT_TRUE(gotsegv);
+ EXPECT_EQ(1, p[0]);
+ EXPECT_NE(-1, mprotect(p, sizeof(p), PROT_READ | PROT_WRITE));
+}
diff --git a/test/libc/fmt/itoa64radix10_test.c b/test/libc/fmt/itoa64radix10_test.c
index 970e87ead..c9387aa0f 100644
--- a/test/libc/fmt/itoa64radix10_test.c
+++ b/test/libc/fmt/itoa64radix10_test.c
@@ -40,6 +40,8 @@ TEST(uint64toarray_radix10, test) {
char buf[21];
EXPECT_EQ(1, uint64toarray_radix10(0, buf));
EXPECT_STREQ("0", buf);
+ EXPECT_EQ(4, uint64toarray_radix10(1024, buf));
+ EXPECT_STREQ("1024", buf);
EXPECT_EQ(20, uint64toarray_radix10(UINT64_MAX, buf));
EXPECT_STREQ("18446744073709551615", buf);
EXPECT_EQ(19, uint64toarray_radix10(INT64_MIN, buf));
diff --git a/test/libc/fmt/palandprintf_test.c b/test/libc/fmt/palandprintf_test.c
index f1b65ead9..a9b5750b5 100644
--- a/test/libc/fmt/palandprintf_test.c
+++ b/test/libc/fmt/palandprintf_test.c
@@ -38,8 +38,9 @@
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
-static char buffer[128];
-#define Format(...) gc(xasprintf(__VA_ARGS__))
+char buffer[1000];
+/* #define Format(...) gc(xasprintf(__VA_ARGS__)) */
+#define Format(...) (snprintf(buffer, sizeof(buffer), __VA_ARGS__), buffer)
TEST(sprintf, test_space_flag) {
EXPECT_STREQ(" 42", Format("% d", 42));
@@ -593,32 +594,14 @@ TEST(snprintf, testFixedWidthString_wontOverrunInput) {
TEST(snprintf, testFixedWidthStringIsNull_wontOverrunBuffer) {
int N = 3;
char *buf = malloc(N + 1);
- EXPECT_EQ(6, snprintf(buf, N + 1, "%.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"(nu ", buf);
- EXPECT_EQ(6, snprintf(buf, N + 1, "%#.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"(nu ", buf);
- EXPECT_EQ(4, snprintf(buf, N + 1, "%`.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"NUL ", buf);
- EXPECT_EQ(4, snprintf(buf, N + 1, "%`#.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"NUL ", buf);
- free(buf);
-}
-
-TEST(snprintf, testFixedWidthStringIsNull_wontLeakMemory) {
- int N = 16;
- char *buf = malloc(N + 1);
- memset(buf, 0, N + 1);
- EXPECT_EQ(6, snprintf(buf, N + 1, "%.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"(null) ", buf);
- memset(buf, 0, N + 1);
- EXPECT_EQ(6, snprintf(buf, N + 1, "%#.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"(null) ", buf);
- memset(buf, 0, N + 1);
- EXPECT_EQ(4, snprintf(buf, N + 1, "%`.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"NULL ", buf);
- memset(buf, 0, N + 1);
- EXPECT_EQ(4, snprintf(buf, N + 1, "%`#.*s", pushpop(N), pushpop(NULL)));
- EXPECT_BINEQ(u"NULL ", buf);
+ EXPECT_EQ(3, snprintf(buf, N + 1, "%.*s", pushpop(N), pushpop(NULL)));
+ EXPECT_STREQ("(nu", buf);
+ EXPECT_EQ(3, snprintf(buf, N + 1, "%#.*s", pushpop(N), pushpop(NULL)));
+ EXPECT_STREQ("(nu", buf);
+ EXPECT_EQ(3, snprintf(buf, N + 1, "%`'.*s", pushpop(N), pushpop(NULL)));
+ EXPECT_STREQ("NUL", buf);
+ EXPECT_EQ(3, snprintf(buf, N + 1, "%`#.*s", pushpop(N), pushpop(NULL)));
+ EXPECT_STREQ("NUL", buf);
free(buf);
}
@@ -640,7 +623,9 @@ TEST(palandprintf, precisionStillRespectsNulTerminatorIfNotEscOrRepr) {
}
BENCH(palandprintf, bench) {
+ EZBENCH2("ascii", donothing, Format(VEIL("r", "hiuhcreohucreo")));
EZBENCH2("ascii %s", donothing, Format("%s", VEIL("r", "hiuhcreohucreo")));
+ EZBENCH2("ascii %`'s", donothing, Format("%`'s", VEIL("r", "hiuhcreohucre")));
EZBENCH2("utf8 %s", donothing, Format("%s", VEIL("r", "hi (╯°□°)╯")));
EZBENCH2("snprintf %hs", donothing, Format("%hs", VEIL("r", u"hi (╯°□°)╯")));
EZBENCH2("snprintf %ls", donothing, Format("%ls", VEIL("r", L"hi (╯°□°)╯")));
@@ -648,6 +633,8 @@ BENCH(palandprintf, bench) {
EZBENCH2("23 %d", donothing, Format("%d", VEIL("r", 23)));
EZBENCH2("INT_MIN %x", donothing, Format("%x", VEIL("r", INT_MIN)));
EZBENCH2("INT_MIN %d", donothing, Format("%d", VEIL("r", INT_MIN)));
+ EZBENCH2("LONG_MIN %x", donothing, Format("%lx", VEIL("r", LONG_MIN)));
+ EZBENCH2("LONG_MIN %d", donothing, Format("%ld", VEIL("r", LONG_MIN)));
EZBENCH2("23 int64toarray", donothing, int64toarray_radix10(23, buffer));
EZBENCH2("INT_MIN int64toarray", donothing,
int64toarray_radix10(INT_MIN, buffer));
diff --git a/test/libc/fmt/strerror_test.c b/test/libc/fmt/strerror_test.c
index b6b607136..7ba6ee727 100644
--- a/test/libc/fmt/strerror_test.c
+++ b/test/libc/fmt/strerror_test.c
@@ -30,34 +30,26 @@
*/
TEST(strerror, e2big) {
- if (IsTiny()) return;
EXPECT_STARTSWITH("E2BIG", strerror(E2BIG));
}
TEST(strerror, enosys) {
- if (IsTiny()) return;
EXPECT_STARTSWITH("ENOSYS", strerror(ENOSYS));
}
TEST(strerror, einval) {
- if (IsTiny()) return;
EXPECT_STARTSWITH("EINVAL", strerror(EINVAL));
}
TEST(strerror, symbolizingTheseNumbersAsErrorsIsHeresyInUnixStyle) {
- EXPECT_STARTSWITH("E?", strerror(0));
- EXPECT_STARTSWITH("E?", strerror(-1));
+ EXPECT_STARTSWITH("EUNKNOWN", strerror(0));
+ EXPECT_STARTSWITH("EUNKNOWN", strerror(-1));
}
TEST(strerror, enotconn_orLinkerIsntUsingLocaleC_orCodeIsOutOfSync) {
- if (IsTiny()) return;
EXPECT_STARTSWITH("ENOTCONN", strerror(ENOTCONN));
}
TEST(strerror, exfull_orLinkerIsntUsingLocaleC_orCodeIsOutOfSync) {
- if (IsLinux() && !IsTiny()) {
- EXPECT_STARTSWITH("EXFULL", strerror(EXFULL));
- } else {
- EXPECT_STARTSWITH("E?", strerror(EXFULL));
- }
+ EXPECT_STARTSWITH("ETXTBSY", strerror(ETXTBSY));
}
diff --git a/test/libc/release/test.mk b/test/libc/release/test.mk
index 01aac0224..872f9395d 100644
--- a/test/libc/release/test.mk
+++ b/test/libc/release/test.mk
@@ -8,7 +8,7 @@ o/$(MODE)/test/libc/release/cosmopolitan.zip: \
o/$(MODE)/ape/ape.o \
o/$(MODE)/ape/ape-no-modify-self.o \
o/$(MODE)/cosmopolitan.a
- @$(COMPILE) -AZIP -T$@ zip -j $@ $^
+ @$(COMPILE) -AZIP -T$@ zip -qj $@ $^
o/$(MODE)/test/libc/release/smoke.com: \
o/$(MODE)/test/libc/release/smoke.com.dbg
diff --git a/test/libc/sock/inet_pton_test.c b/test/libc/sock/inet_pton_test.c
index 1963fb149..30415f079 100644
--- a/test/libc/sock/inet_pton_test.c
+++ b/test/libc/sock/inet_pton_test.c
@@ -23,18 +23,46 @@
#include "libc/testlib/testlib.h"
TEST(inet_pton, testLocalhost) {
- uint32_t addr;
- ASSERT_EQ(1, inet_pton(AF_INET, "127.0.0.1", &addr));
- EXPECT_EQ(htonl(INADDR_LOOPBACK), addr);
+ uint8_t addr[4] = {255, 255, 255, 255};
+ EXPECT_EQ(1, inet_pton(AF_INET, "127.0.0.1", &addr));
+ EXPECT_EQ(127, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(0, addr[2]);
+ EXPECT_EQ(1, addr[3]);
}
-TEST(inet_pton, testBadAddresses) {
- uint32_t addr;
- ASSERT_EQ(0, inet_pton(AF_INET, "127.0.0", &addr));
- ASSERT_EQ(0, inet_pton(AF_INET, "256.0.0.1", &addr));
+TEST(inet_pton, testShortAddress_doesntFillFullValue) {
+ uint8_t addr[4] = {255, 255, 255, 255};
+ EXPECT_EQ(0, inet_pton(AF_INET, "127.0.0", &addr));
+ EXPECT_EQ(127, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(0, addr[2]);
+ EXPECT_EQ(255, addr[3]);
}
-TEST(inet_pton, testBadFamily) {
- uint32_t addr = 666;
- ASSERT_EQ(-1, inet_pton(666, "127.0.0.1", &addr));
+TEST(inet_pton, testOverflow_stopsParsing) {
+ uint8_t addr[4] = {255, 255, 255, 255};
+ EXPECT_EQ(0, inet_pton(AF_INET, "0.300.0", &addr));
+ EXPECT_EQ(0, addr[0]);
+ EXPECT_EQ(255, addr[1]);
+ EXPECT_EQ(255, addr[2]);
+ EXPECT_EQ(255, addr[3]);
+}
+
+TEST(inet_pton, testBadChar_stopsParsing) {
+ uint8_t addr[4] = {255, 255, 255, 255};
+ EXPECT_EQ(0, inet_pton(AF_INET, "127-.0.0", &addr));
+ EXPECT_EQ(127, addr[0]);
+ EXPECT_EQ(255, addr[1]);
+ EXPECT_EQ(255, addr[2]);
+ EXPECT_EQ(255, addr[3]);
+}
+
+TEST(inet_pton, testBadFamily_returnsNegAndChangesNothing) {
+ uint8_t addr[4] = {255, 255, 255, 255};
+ EXPECT_EQ(-1, inet_pton(666, "127.0.0.1", &addr));
+ EXPECT_EQ(255, addr[0]);
+ EXPECT_EQ(255, addr[1]);
+ EXPECT_EQ(255, addr[2]);
+ EXPECT_EQ(255, addr[3]);
}
diff --git a/test/libc/str/crc32c_test.c b/test/libc/str/crc32c_test.c
index b755eb62b..27984f7d9 100644
--- a/test/libc/str/crc32c_test.c
+++ b/test/libc/str/crc32c_test.c
@@ -20,6 +20,8 @@
#include "libc/nexgen32e/crc32.h"
#include "libc/nexgen32e/x86feature.h"
#include "libc/str/str.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
#define FANATICS "Fanatics"
@@ -40,12 +42,6 @@ TEST(crc32c, test) {
strlen(hyperion) - strlen(FANATICS)));
}
-FIXTURE(crc32c, pure_) {
- *(void **)(&crc32c) = (void *)crc32c_pure;
-}
-
-FIXTURE(crc32c, sse42_) {
- if (X86_HAVE(SSE4_2)) {
- *(void **)(&crc32c) = (void *)crc32c_sse42;
- }
+BENCH(crc32c, bench) {
+ EZBENCH2("crc32c", donothing, crc32c(0, kHyperion, kHyperionSize));
}
diff --git a/test/libc/str/regex_test.c b/test/libc/str/regex_test.c
index 61088522d..c57e4e2b9 100644
--- a/test/libc/str/regex_test.c
+++ b/test/libc/str/regex_test.c
@@ -17,6 +17,7 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/str/str.h"
+#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "third_party/regex/regex.h"
@@ -30,3 +31,125 @@ TEST(regex, test) {
EXPECT_EQ(REG_NOMATCH, regexec(&rx, "0", 0, NULL, 0));
regfree(&rx);
}
+
+TEST(regex, testDns) {
+ regex_t rx;
+ EXPECT_EQ(REG_OK, regcomp(&rx, "^[-._0-9A-Za-z]*$", REG_EXTENDED));
+ EXPECT_EQ(REG_OK, regexec(&rx, "", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "foo.com", 0, NULL, 0));
+ EXPECT_EQ(REG_NOMATCH, regexec(&rx, "bar@example", 0, NULL, 0));
+ regfree(&rx);
+}
+
+TEST(regex, testIpBasic) {
+ regex_t rx;
+ EXPECT_EQ(REG_OK, regcomp(&rx,
+ "^"
+ "\\([0-9][0-9]*\\)\\."
+ "\\([0-9][0-9]*\\)\\."
+ "\\([0-9][0-9]*\\)\\."
+ "\\([0-9][0-9]*\\)"
+ "$",
+ 0));
+ const char *s = "127.0.0.1";
+ regmatch_t *m = gc(calloc(rx.re_nsub + 1, sizeof(regmatch_t)));
+ ASSERT_EQ(4, rx.re_nsub);
+ EXPECT_EQ(REG_OK, regexec(&rx, s, rx.re_nsub + 1, m, 0));
+ EXPECT_STREQ("127", gc(strndup(s + m[1].rm_so, m[1].rm_eo - m[1].rm_so)));
+ EXPECT_STREQ("0", gc(strndup(s + m[2].rm_so, m[2].rm_eo - m[2].rm_so)));
+ EXPECT_STREQ("0", gc(strndup(s + m[3].rm_so, m[3].rm_eo - m[3].rm_so)));
+ EXPECT_STREQ("1", gc(strndup(s + m[4].rm_so, m[4].rm_eo - m[4].rm_so)));
+ regfree(&rx);
+}
+
+TEST(regex, testIpExtended) {
+ regex_t rx;
+ EXPECT_EQ(REG_OK, regcomp(&rx,
+ "^"
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})"
+ "$",
+ REG_EXTENDED));
+ const char *s = "127.0.0.1";
+ regmatch_t *m = gc(calloc(rx.re_nsub + 1, sizeof(regmatch_t)));
+ ASSERT_EQ(4, rx.re_nsub);
+ EXPECT_EQ(REG_OK, regexec(&rx, s, rx.re_nsub + 1, m, 0));
+ EXPECT_STREQ("127", gc(strndup(s + m[1].rm_so, m[1].rm_eo - m[1].rm_so)));
+ EXPECT_STREQ("0", gc(strndup(s + m[2].rm_so, m[2].rm_eo - m[2].rm_so)));
+ EXPECT_STREQ("0", gc(strndup(s + m[3].rm_so, m[3].rm_eo - m[3].rm_so)));
+ EXPECT_STREQ("1", gc(strndup(s + m[4].rm_so, m[4].rm_eo - m[4].rm_so)));
+ regfree(&rx);
+}
+
+void A(void) {
+ regex_t rx;
+ regcomp(&rx, "^[-._0-9A-Za-z]*$", REG_EXTENDED);
+ regexec(&rx, "foo.com", 0, NULL, 0);
+ regfree(&rx);
+}
+
+void B(regex_t *rx) {
+ regexec(rx, "foo.com", 0, NULL, 0);
+}
+
+void C(void) {
+ regex_t rx;
+ regcomp(&rx, "^[-._0-9A-Za-z]*$", 0);
+ regexec(&rx, "foo.com", 0, NULL, 0);
+ regfree(&rx);
+}
+
+void D(regex_t *rx, regmatch_t *m) {
+ regexec(rx, "127.0.0.1", rx->re_nsub + 1, m, 0);
+}
+
+BENCH(regex, bench) {
+ regex_t rx;
+ regmatch_t *m;
+ regcomp(&rx, "^[-._0-9A-Za-z]*$", REG_EXTENDED);
+ EZBENCH2("precompiled extended", donothing, B(&rx));
+ regfree(&rx);
+ EZBENCH2("easy api extended", donothing, A());
+ EZBENCH2("easy api basic", donothing, C());
+
+ EXPECT_EQ(REG_OK, regcomp(&rx,
+ "^"
+ "\\([0-9][0-9]*\\)\\."
+ "\\([0-9][0-9]*\\)\\."
+ "\\([0-9][0-9]*\\)\\."
+ "\\([0-9][0-9]*\\)"
+ "$",
+ 0));
+ m = calloc(rx.re_nsub + 1, sizeof(regmatch_t));
+ EZBENCH2("precompiled basic match", donothing, D(&rx, m));
+ free(m);
+ regfree(&rx);
+
+ EXPECT_EQ(REG_OK, regcomp(&rx,
+ "^"
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})"
+ "$",
+ REG_EXTENDED));
+ m = calloc(rx.re_nsub + 1, sizeof(regmatch_t));
+ EZBENCH2("precompiled extended match", donothing, D(&rx, m));
+ free(m);
+ regfree(&rx);
+
+ EXPECT_EQ(REG_OK, regcomp(&rx,
+ "^"
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})\\."
+ "([0-9]{1,3})"
+ "$",
+ REG_EXTENDED | REG_NOSUB));
+ m = calloc(rx.re_nsub + 1, sizeof(regmatch_t));
+ EZBENCH2("precompiled nosub match", donothing, D(&rx, m));
+ free(m);
+ regfree(&rx);
+}
diff --git a/test/net/http/decodelatin1_test.c b/test/net/http/decodelatin1_test.c
index ccb507181..12fef2dec 100644
--- a/test/net/http/decodelatin1_test.c
+++ b/test/net/http/decodelatin1_test.c
@@ -16,8 +16,11 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/errno.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
-#include "net/http/http.h"
+#include "net/http/escape.h"
size_t n;
@@ -25,4 +28,41 @@ TEST(DecodeLatin1, test) {
EXPECT_STREQ("", DecodeLatin1(NULL, 0, 0));
EXPECT_STREQ("¥atta", DecodeLatin1("\245atta", -1, &n));
EXPECT_EQ(6, n);
+ EXPECT_STREQ("\245atta", EncodeLatin1("¥atta", -1, &n, 0));
+ EXPECT_EQ(5, n);
+}
+
+TEST(DecodeLatin1, testAbleToImposeCharacterRestrictions) {
+ errno = 0;
+ EXPECT_EQ(0, EncodeLatin1("\200atta", -1, &n, kControlC1));
+ EXPECT_EQ(0, n);
+ EXPECT_EQ(EILSEQ, errno);
+ errno = 0;
+ EXPECT_EQ(0, EncodeLatin1("\002atta", -1, &n, kControlC0));
+ EXPECT_EQ(0, n);
+ EXPECT_EQ(EILSEQ, errno);
+}
+
+TEST(EncodeLatin1, roundTrip) {
+ int i;
+ char b[256];
+ for (i = 0; i < 256; ++i) b[i] = i;
+ char *utf8 = DecodeLatin1(b, 256, &n);
+ EXPECT_EQ(384, n);
+ char *lat1 = EncodeLatin1(utf8, n, &n, 0);
+ ASSERT_EQ(256, n);
+ EXPECT_EQ(0, memcmp(b, lat1, 256));
+}
+
+TEST(DecodeLatin1, testOom_returnsNullAndSetsSizeToZero) {
+ n = 31337;
+ EXPECT_EQ(NULL, DecodeLatin1("hello", 0x1000000000000, &n));
+ EXPECT_EQ(0, n);
+}
+
+BENCH(DecodeLatin1, bench) {
+ EZBENCH2("DecodeLatin1", donothing,
+ DecodeLatin1(kHyperion, kHyperionSize, 0));
+ EZBENCH2("EncodeLatin1", donothing,
+ EncodeLatin1(kHyperion, kHyperionSize, 0, 0));
}
diff --git a/test/net/http/encodebase64_test.c b/test/net/http/encodebase64_test.c
index 4e4f23a30..24d5f4465 100644
--- a/test/net/http/encodebase64_test.c
+++ b/test/net/http/encodebase64_test.c
@@ -23,7 +23,7 @@
#include "libc/testlib/ezbench.h"
#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
-#include "net/http/base64.h"
+#include "net/http/escape.h"
size_t i, n, m;
char *p, *q, b[32];
@@ -126,6 +126,18 @@ TEST(DecodeBase64, testInvalidSequences_skipsOverThem) {
EXPECT_BINEQ(u"♦ ", gc(DecodeBase64("====BB==", 8, 0)));
}
+TEST(DecodeBase64, testOom_returnsNullAndSetsSizeToZero) {
+ n = 31337;
+ EXPECT_EQ(NULL, DecodeBase64("hello", 0x1000000000000, &n));
+ EXPECT_EQ(0, n);
+}
+
+TEST(EncodeBase64, testOom_returnsNullAndSetsSizeToZero) {
+ n = 31337;
+ EXPECT_EQ(NULL, EncodeBase64("hello", 0x1000000000000, &n));
+ EXPECT_EQ(0, n);
+}
+
TEST(Base64, RoundTrip) {
for (i = 0; i < 1000; ++i) {
n = rand() % 32;
diff --git a/test/net/http/encodehttpheadervalue_test.c b/test/net/http/encodehttpheadervalue_test.c
index 9df05d12b..d5a9b4aa7 100644
--- a/test/net/http/encodehttpheadervalue_test.c
+++ b/test/net/http/encodehttpheadervalue_test.c
@@ -22,7 +22,7 @@
#include "libc/stdio/stdio.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
-#include "net/http/http.h"
+#include "net/http/escape.h"
char *p;
size_t n;
@@ -70,6 +70,12 @@ TEST(EncodeHttpHeaderValue, testC1_isForbidden) {
EXPECT_EQ(NULL, gc(EncodeHttpHeaderValue("\302\205", 2, 0)));
}
+TEST(EncodeHttpHeaderValue, testOom_returnsNullAndSetsSizeToZero) {
+ n = 31337;
+ EXPECT_EQ(NULL, EncodeHttpHeaderValue("hello", 0x1000000000000, &n));
+ EXPECT_EQ(0, n);
+}
+
BENCH(EncodeHttpHeaderValue, bench) {
n = 22851;
p = gc(malloc(n));
diff --git a/test/net/http/escapehtml_test.c b/test/net/http/escapehtml_test.c
index 963baba0c..96928c061 100644
--- a/test/net/http/escapehtml_test.c
+++ b/test/net/http/escapehtml_test.c
@@ -24,10 +24,11 @@
#include "net/http/escape.h"
char *escapehtml(const char *s) {
- struct EscapeResult r;
- r = EscapeHtml(s, strlen(s));
- ASSERT_EQ(strlen(r.data), r.size);
- return r.data;
+ char *p;
+ size_t n;
+ p = EscapeHtml(s, strlen(s), &n);
+ ASSERT_EQ(strlen(p), n);
+ return p;
}
TEST(escapehtml, test) {
@@ -49,5 +50,5 @@ TEST(escapehtml, testAstralPlanes_doesNothing) {
BENCH(escapehtml, bench) {
EZBENCH2("escapehtml", donothing,
- free(EscapeHtml(kHyperion, kHyperionSize).data));
+ free(EscapeHtml(kHyperion, kHyperionSize, 0)));
}
diff --git a/test/net/http/escapejsstringliteral_test.c b/test/net/http/escapejsstringliteral_test.c
index 98da2f6ca..4555bc733 100644
--- a/test/net/http/escapejsstringliteral_test.c
+++ b/test/net/http/escapejsstringliteral_test.c
@@ -24,10 +24,11 @@
#include "net/http/escape.h"
char *escapejs(const char *s) {
- struct EscapeResult r;
- r = EscapeJsStringLiteral(s, strlen(s));
- ASSERT_EQ(strlen(r.data), r.size);
- return r.data;
+ char *p;
+ size_t n;
+ p = EscapeJsStringLiteral(s, strlen(s), &n);
+ ASSERT_EQ(strlen(p), n);
+ return p;
}
TEST(EscapeJsStringLiteral, test) {
@@ -53,27 +54,29 @@ TEST(EscapeJsStringLiteral, testBrokenUnicode_sparesInnocentCharacters) {
void makefile1(void) {
FILE *f;
- struct EscapeResult r;
- r = EscapeJsStringLiteral(kHyperion, kHyperionSize);
+ char *p;
+ size_t n;
+ p = EscapeJsStringLiteral(kHyperion, kHyperionSize, &n);
f = fopen("/tmp/a", "wb");
- fwrite(r.data, r.size, 1, f);
+ fwrite(p, n, 1, f);
fclose(f);
- free(r.data);
+ free(p);
}
void makefile2(void) {
int fd;
- struct EscapeResult r;
- r = EscapeJsStringLiteral(kHyperion, kHyperionSize);
+ char *p;
+ size_t n;
+ p = EscapeJsStringLiteral(kHyperion, kHyperionSize, &n);
fd = creat("/tmp/a", 0644);
- write(fd, r.data, r.size);
+ write(fd, p, n);
close(fd);
- free(r.data);
+ free(p);
}
BENCH(EscapeJsStringLiteral, bench) {
EZBENCH2("escapejs", donothing,
- free(EscapeJsStringLiteral(kHyperion, kHyperionSize).data));
+ free(EscapeJsStringLiteral(kHyperion, kHyperionSize, 0)));
EZBENCH2("makefile1", donothing, makefile1());
EZBENCH2("makefile2", donothing, makefile2());
}
diff --git a/test/net/http/escapeurlparam_test.c b/test/net/http/escapeurlparam_test.c
index c7469c700..f550279af 100644
--- a/test/net/http/escapeurlparam_test.c
+++ b/test/net/http/escapeurlparam_test.c
@@ -24,30 +24,31 @@
#include "net/http/escape.h"
char *escapeparam(const char *s) {
- struct EscapeResult r;
- r = EscapeUrlParam(s, strlen(s));
- ASSERT_EQ(strlen(r.data), r.size);
- return r.data;
+ char *p;
+ size_t n;
+ p = EscapeParam(s, -1, &n);
+ ASSERT_EQ(strlen(p), n);
+ return p;
}
-TEST(escapeparam, test) {
+TEST(EscapeParam, test) {
EXPECT_STREQ("abc%20%26%3C%3E%22%27%01%02",
gc(escapeparam("abc &<>\"'\1\2")));
}
-TEST(escapeparam, testLargeGrowth) {
+TEST(EscapeParam, testLargeGrowth) {
EXPECT_STREQ("%22%22%22", gc(escapeparam("\"\"\"")));
}
-TEST(escapeparam, testEmpty) {
+TEST(EscapeParam, testEmpty) {
EXPECT_STREQ("", gc(escapeparam("")));
}
-TEST(escapeparam, testAstralPlanes_usesUtf8HexEncoding) {
+TEST(EscapeParam, testAstralPlanes_usesUtf8HexEncoding) {
EXPECT_STREQ("%F0%90%8C%B0", escapeparam("𐌰"));
}
-BENCH(escapeparam, bench) {
- EZBENCH2("escapeparam", donothing,
- free(EscapeUrlParam(kHyperion, kHyperionSize).data));
+BENCH(EscapeParam, bench) {
+ EZBENCH2("EscapeParam", donothing,
+ free(EscapeParam(kHyperion, kHyperionSize, 0)));
}
diff --git a/test/net/http/hascontrolcodes_test.c b/test/net/http/hascontrolcodes_test.c
new file mode 100644
index 000000000..26afa3a48
--- /dev/null
+++ b/test/net/http/hascontrolcodes_test.c
@@ -0,0 +1,51 @@
+/*-*- 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/testlib/ezbench.h"
+#include "libc/testlib/hyperion.h"
+#include "libc/testlib/testlib.h"
+#include "net/http/escape.h"
+
+TEST(HasControlCodes, test) {
+ EXPECT_FALSE(
+ HasControlCodes(kHyperion, kHyperionSize, kControlC0 | kControlC1));
+ EXPECT_TRUE(HasControlCodes("hi\1", -1, kControlC0));
+ EXPECT_FALSE(HasControlCodes("hi\1", -1, kControlC1));
+ EXPECT_FALSE(HasControlCodes("hi there", -1, 0));
+ EXPECT_TRUE(HasControlCodes("hi\tthere", -1, kControlWs));
+}
+
+TEST(HasControlCodes, testDoesUtf8) {
+ EXPECT_FALSE(HasControlCodes(u8"→", -1, kControlC0 | kControlC1));
+ EXPECT_FALSE(HasControlCodes("\304\200", -1, kControlC0 | kControlC1));
+ EXPECT_TRUE(HasControlCodes("\300\200", -1, kControlC0 | kControlC1));
+ EXPECT_FALSE(HasControlCodes("\300\200", -1, kControlC1));
+ EXPECT_TRUE(HasControlCodes("\302\202", -1, kControlC0 | kControlC1));
+ EXPECT_TRUE(HasControlCodes("\302\202", -1, kControlC1));
+ EXPECT_FALSE(HasControlCodes("\302\202", -1, kControlC0));
+}
+
+TEST(HasControlCodes, testHasLatin1FallbackBehavior) {
+ EXPECT_TRUE(HasControlCodes("\202", -1, kControlWs | kControlC1));
+ EXPECT_FALSE(HasControlCodes("\202", -1, kControlC0));
+}
+
+BENCH(HasControlCodes, bench) {
+ EZBENCH2("HasControlCodes", donothing,
+ HasControlCodes(kHyperion, kHyperionSize, kControlWs));
+}
diff --git a/test/net/http/indentlines_test.c b/test/net/http/indentlines_test.c
index 106bf9215..c82358ac7 100644
--- a/test/net/http/indentlines_test.c
+++ b/test/net/http/indentlines_test.c
@@ -21,7 +21,7 @@
#include "libc/testlib/ezbench.h"
#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
-#include "net/http/http.h"
+#include "net/http/escape.h"
TEST(IndentLines, testEmpty) {
char *p;
diff --git a/test/net/http/isacceptablehost_test.c b/test/net/http/isacceptablehost_test.c
new file mode 100644
index 000000000..d212afd20
--- /dev/null
+++ b/test/net/http/isacceptablehost_test.c
@@ -0,0 +1,131 @@
+/*-*- 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/testlib/ezbench.h"
+#include "libc/testlib/testlib.h"
+#include "net/http/http.h"
+
+TEST(IsAcceptableHost, test) {
+ EXPECT_TRUE(IsAcceptableHost("", -1));
+ EXPECT_TRUE(IsAcceptableHost("0.0.0.0", -1));
+ EXPECT_FALSE(IsAcceptableHost("1.2.3", -1));
+ EXPECT_TRUE(IsAcceptableHost("1.2.3.4", -1));
+ EXPECT_FALSE(IsAcceptableHost("1.2.3.4.5", -1));
+ EXPECT_TRUE(IsAcceptableHost("1.2.3.4.5.arpa", -1));
+ EXPECT_TRUE(IsAcceptableHost("255.255.255.255", -1));
+ EXPECT_FALSE(IsAcceptableHost("255.255.255", -1));
+ EXPECT_FALSE(IsAcceptableHost("256.255.255.255", -1));
+ EXPECT_TRUE(IsAcceptableHost("hello.example", -1));
+ EXPECT_FALSE(IsAcceptableHost("hello..example", -1));
+ EXPECT_TRUE(IsAcceptableHost("hello", -1));
+ EXPECT_FALSE(IsAcceptableHost("hello\177", -1));
+ EXPECT_FALSE(IsAcceptableHost("hello.example\300\200", -1));
+ EXPECT_FALSE(IsAcceptableHost(".", -1));
+ EXPECT_FALSE(IsAcceptableHost(".e", -1));
+ EXPECT_TRUE(IsAcceptableHost("e.", -1));
+ EXPECT_FALSE(IsAcceptableHost(".hi.example", -1));
+ EXPECT_FALSE(IsAcceptableHost("hi..example", -1));
+ EXPECT_TRUE(IsAcceptableHost("hi-there.example", -1));
+ EXPECT_TRUE(IsAcceptableHost("_there.example", -1));
+ EXPECT_TRUE(IsAcceptableHost("-there.example", -1));
+ EXPECT_TRUE(IsAcceptableHost("there-.example", -1));
+ EXPECT_FALSE(IsAcceptableHost("ther#e.example", -1));
+ EXPECT_TRUE(IsAcceptableHost("localhost", -1));
+}
+
+TEST(IsAcceptablePort, test) {
+ EXPECT_TRUE(IsAcceptablePort("", -1));
+ EXPECT_TRUE(IsAcceptablePort("0", -1));
+ EXPECT_TRUE(IsAcceptablePort("65535", -1));
+ EXPECT_FALSE(IsAcceptablePort("65536", -1));
+ EXPECT_FALSE(IsAcceptablePort("-1", -1));
+ EXPECT_FALSE(IsAcceptablePort("http", -1));
+}
+
+TEST(ParseIp, test) {
+ EXPECT_EQ(-1, ParseIp("", -1));
+ EXPECT_EQ(0x00000000, ParseIp("0.0.0.0", -1));
+ EXPECT_EQ(0x01020304, ParseIp("1.2.3.4", -1));
+ EXPECT_EQ(0x80020304, ParseIp("128.2.3.4", -1));
+ EXPECT_EQ(0xFFFFFFFF, ParseIp("255.255.255.255", -1));
+ EXPECT_EQ(0xcb007100, ParseIp("203.0.113.0", -1));
+ EXPECT_EQ(0x00000000, ParseIp("...", -1)); /* meh */
+ EXPECT_EQ(-1, ParseIp("128.2..3.4", -1));
+ EXPECT_EQ(-1, ParseIp("1.2.3", -1));
+ EXPECT_EQ(-1, ParseIp("256.255.255.255", -1));
+ EXPECT_EQ(-1, ParseIp("1.2.3.4.5", -1));
+ EXPECT_EQ(-1, ParseIp("1.2.3.4.5.arpa", -1));
+ EXPECT_EQ(-1, ParseIp("255.255.255", -1));
+ EXPECT_EQ(-1, ParseIp("hello", -1));
+ EXPECT_EQ(-1, ParseIp("hello\177", -1));
+ EXPECT_EQ(-1, ParseIp("hello.example\300\200", -1));
+ EXPECT_EQ(-1, ParseIp(".", -1));
+ EXPECT_EQ(-1, ParseIp(".e", -1));
+ EXPECT_EQ(-1, ParseIp("e.", -1));
+ EXPECT_EQ(-1, ParseIp(".hi.example", -1));
+ EXPECT_EQ(-1, ParseIp("hi..example", -1));
+ EXPECT_EQ(-1, ParseIp("hi-there.example", -1));
+ EXPECT_EQ(-1, ParseIp("_there.example", -1));
+ EXPECT_EQ(-1, ParseIp("-there.example", -1));
+ EXPECT_EQ(-1, ParseIp("there-.example", -1));
+ EXPECT_EQ(-1, ParseIp("ther#e.example", -1));
+ EXPECT_EQ(-1, ParseIp("localhost", -1));
+ EXPECT_EQ(-1, ParseIp("hello.example", -1));
+ EXPECT_EQ(-1, ParseIp("hello..example", -1));
+}
+
+TEST(ParseForwarded, test) {
+ uint32_t ip = 7;
+ uint16_t port = 7;
+ EXPECT_EQ(-1, ParseForwarded("", -1, &ip, &port));
+ EXPECT_EQ(-1, ParseForwarded("0.0.0.0", -1, &ip, &port));
+ EXPECT_EQ(-1, ParseForwarded("8.8.8.8", -1, &ip, &port));
+ EXPECT_EQ(-1, ParseForwarded("[::1]:123", -1, &ip, &port));
+ EXPECT_EQ(7, ip);
+ EXPECT_EQ(7, port);
+ EXPECT_EQ(0, ParseForwarded("0.0.0.1:123", -1, &ip, &port));
+ EXPECT_EQ(0x00000001, ip);
+ EXPECT_EQ(123, port);
+ EXPECT_EQ(0, ParseForwarded("1.2.3.4:123", -1, &ip, &port));
+ EXPECT_EQ(0x01020304, ip);
+ EXPECT_EQ(123, port);
+ EXPECT_EQ(0, ParseForwarded("128.2.3.4:123", -1, &ip, &port));
+ EXPECT_EQ(0x80020304, ip);
+ EXPECT_EQ(123, port);
+ EXPECT_EQ(0, ParseForwarded("255.255.255.255:123", -1, &ip, &port));
+ EXPECT_EQ(0xFFFFFFFF, ip);
+ EXPECT_EQ(123, port);
+ EXPECT_EQ(0, ParseForwarded("203.0.113.0:123", -1, &ip, &port));
+ EXPECT_EQ(0xcb007100, ip);
+ EXPECT_EQ(123, port);
+ EXPECT_EQ(0, ParseForwarded("203.0.113.42:31337", -1, &ip, &port));
+ EXPECT_EQ(-1, ParseForwarded("...:123", -1, &ip, &port));
+ EXPECT_EQ(-1, ParseForwarded("203.0.113.0:123123123", -1, &ip, &port));
+}
+
+BENCH(IsAcceptableHost, bench) {
+ uint32_t ip;
+ uint16_t port;
+ EZBENCH2("IsAcceptableHost 127.0.0.1", donothing,
+ IsAcceptableHost("127.0.0.1", 9));
+ EZBENCH2("IsAcceptablePort 80", donothing, IsAcceptablePort("80", 2));
+ EZBENCH2("ParseForwarded 80", donothing,
+ ParseForwarded("203.0.113.42:31337", 20, &ip, &port));
+ EZBENCH2("IsAcceptableHost foo.example", donothing,
+ IsAcceptableHost("foo.example:31337", 17));
+}
diff --git a/test/net/http/isacceptablehttprequestpath_test.c b/test/net/http/isacceptablehttprequestpath_test.c
deleted file mode 100644
index a256bdf55..000000000
--- a/test/net/http/isacceptablehttprequestpath_test.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*-*- 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));
-}
diff --git a/test/net/http/isacceptablepath_test.c b/test/net/http/isacceptablepath_test.c
new file mode 100644
index 000000000..4c0378ea8
--- /dev/null
+++ b/test/net/http/isacceptablepath_test.c
@@ -0,0 +1,95 @@
+/*-*- 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(IsAcceptablePath, test) {
+ EXPECT_TRUE(IsAcceptablePath("*", 1));
+ EXPECT_TRUE(IsAcceptablePath("/", 1));
+ EXPECT_TRUE(IsAcceptablePath("index.html", 10));
+ EXPECT_TRUE(IsAcceptablePath("/index.html", 11));
+ EXPECT_TRUE(IsAcceptablePath("/index.html", -1));
+ EXPECT_TRUE(IsAcceptablePath("/redbean.png", -1));
+}
+
+TEST(IsAcceptablePath, testEmptyString_allowedIfYouLikeImplicitLeadingSlash) {
+ EXPECT_TRUE(IsAcceptablePath(0, 0));
+ EXPECT_TRUE(IsAcceptablePath(0, -1));
+ EXPECT_TRUE(IsAcceptablePath("", 0));
+}
+
+TEST(IsAcceptablePath, testHiddenFiles_notAllowed) {
+ EXPECT_FALSE(IsAcceptablePath("/.index.html", 12));
+ EXPECT_FALSE(IsAcceptablePath("/x/.index.html", 14));
+}
+
+TEST(IsAcceptablePath, testDoubleSlash_notAllowed) {
+ EXPECT_FALSE(IsAcceptablePath("//", 2));
+ EXPECT_FALSE(IsAcceptablePath("foo//", 5));
+ EXPECT_FALSE(IsAcceptablePath("/foo//", 6));
+ EXPECT_FALSE(IsAcceptablePath("/foo//bar", 9));
+}
+
+TEST(IsAcceptablePath, testNoncanonicalDirectories_areForbidden) {
+ EXPECT_FALSE(IsAcceptablePath(".", 1));
+ EXPECT_FALSE(IsAcceptablePath("..", 2));
+ EXPECT_FALSE(IsAcceptablePath("/.", 2));
+ EXPECT_FALSE(IsAcceptablePath("/..", 3));
+ EXPECT_FALSE(IsAcceptablePath("./", 2));
+ EXPECT_FALSE(IsAcceptablePath("../", 3));
+ EXPECT_FALSE(IsAcceptablePath("/./", 3));
+ EXPECT_FALSE(IsAcceptablePath("/../", 4));
+ EXPECT_FALSE(IsAcceptablePath("x/.", 3));
+ EXPECT_FALSE(IsAcceptablePath("x/..", 4));
+ EXPECT_FALSE(IsAcceptablePath("x/./", 4));
+ EXPECT_FALSE(IsAcceptablePath("x/../", 5));
+ EXPECT_FALSE(IsAcceptablePath("/x/./", 5));
+ EXPECT_FALSE(IsAcceptablePath("/x/../", 6));
+}
+
+TEST(IsAcceptablePath, testNoncanonicalWindowsDirs_areForbidden) {
+ EXPECT_FALSE(IsAcceptablePath(".", 1));
+ EXPECT_FALSE(IsAcceptablePath("..", 2));
+ EXPECT_FALSE(IsAcceptablePath("\\.", 2));
+ EXPECT_FALSE(IsAcceptablePath("\\..", 3));
+ EXPECT_FALSE(IsAcceptablePath(".\\", 2));
+ EXPECT_FALSE(IsAcceptablePath("..\\", 3));
+ EXPECT_FALSE(IsAcceptablePath("\\.\\", 3));
+ EXPECT_FALSE(IsAcceptablePath("\\..\\", 4));
+ EXPECT_FALSE(IsAcceptablePath("x\\.", 3));
+ EXPECT_FALSE(IsAcceptablePath("x\\..", 4));
+ EXPECT_FALSE(IsAcceptablePath("x\\.\\", 4));
+ EXPECT_FALSE(IsAcceptablePath("x\\..\\", 5));
+ EXPECT_FALSE(IsAcceptablePath("\\x\\.\\", 5));
+ EXPECT_FALSE(IsAcceptablePath("\\x\\..\\", 6));
+}
+
+TEST(IsAcceptablePath, testOverlongSlashDot_isDetected) {
+ EXPECT_FALSE(IsAcceptablePath("/\300\256", 3));
+ EXPECT_FALSE(IsAcceptablePath("/\300\257", 3));
+ EXPECT_FALSE(IsAcceptablePath("\300\256\300\256", 4));
+}
+
+BENCH(IsAcceptablePath, bench) {
+ EZBENCH2("IsAcceptablePath", donothing, IsAcceptablePath("*", 1));
+ EZBENCH2("IsAcceptablePath", donothing, IsAcceptablePath("/index.html", 11));
+}
diff --git a/test/net/http/isreasonablepath_test.c b/test/net/http/isreasonablepath_test.c
new file mode 100644
index 000000000..7348d5a87
--- /dev/null
+++ b/test/net/http/isreasonablepath_test.c
@@ -0,0 +1,91 @@
+/*-*- 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/testlib/ezbench.h"
+#include "libc/testlib/testlib.h"
+#include "net/http/http.h"
+
+TEST(IsReasonablePath, test) {
+ EXPECT_TRUE(IsReasonablePath("/", 1));
+ EXPECT_TRUE(IsReasonablePath("index.html", 10));
+ EXPECT_TRUE(IsReasonablePath("/index.html", 11));
+ EXPECT_TRUE(IsReasonablePath("/index.html", -1));
+ EXPECT_TRUE(IsReasonablePath("/redbean.png", -1));
+}
+
+TEST(IsReasonablePath, testEmptyString_allowedIfYouLikeImplicitLeadingSlash) {
+ EXPECT_TRUE(IsReasonablePath(0, 0));
+ EXPECT_TRUE(IsReasonablePath(0, -1));
+ EXPECT_TRUE(IsReasonablePath("", 0));
+}
+
+TEST(IsReasonablePath, testHiddenFiles_areAllowed) {
+ EXPECT_TRUE(IsReasonablePath("/.index.html", 12));
+ EXPECT_TRUE(IsReasonablePath("/x/.index.html", 14));
+}
+
+TEST(IsReasonablePath, testDoubleSlash_isAllowed) {
+ EXPECT_TRUE(IsReasonablePath("//", 2));
+ EXPECT_TRUE(IsReasonablePath("foo//", 5));
+ EXPECT_TRUE(IsReasonablePath("/foo//", 6));
+ EXPECT_TRUE(IsReasonablePath("/foo//bar", 9));
+}
+
+TEST(IsReasonablePath, testNoncanonicalDirectories_areForbidden) {
+ EXPECT_FALSE(IsReasonablePath(".", 1));
+ EXPECT_FALSE(IsReasonablePath("..", 2));
+ EXPECT_FALSE(IsReasonablePath("/.", 2));
+ EXPECT_FALSE(IsReasonablePath("/..", 3));
+ EXPECT_FALSE(IsReasonablePath("./", 2));
+ EXPECT_FALSE(IsReasonablePath("../", 3));
+ EXPECT_FALSE(IsReasonablePath("/./", 3));
+ EXPECT_FALSE(IsReasonablePath("/../", 4));
+ EXPECT_FALSE(IsReasonablePath("x/.", 3));
+ EXPECT_FALSE(IsReasonablePath("x/..", 4));
+ EXPECT_FALSE(IsReasonablePath("x/./", 4));
+ EXPECT_FALSE(IsReasonablePath("x/../", 5));
+ EXPECT_FALSE(IsReasonablePath("/x/./", 5));
+ EXPECT_FALSE(IsReasonablePath("/x/../", 6));
+}
+
+TEST(IsReasonablePath, testNoncanonicalWindowsDirs_areForbidden) {
+ EXPECT_FALSE(IsReasonablePath(".", 1));
+ EXPECT_FALSE(IsReasonablePath("..", 2));
+ EXPECT_FALSE(IsReasonablePath("\\.", 2));
+ EXPECT_FALSE(IsReasonablePath("\\..", 3));
+ EXPECT_FALSE(IsReasonablePath(".\\", 2));
+ EXPECT_FALSE(IsReasonablePath("..\\", 3));
+ EXPECT_FALSE(IsReasonablePath("\\.\\", 3));
+ EXPECT_FALSE(IsReasonablePath("\\..\\", 4));
+ EXPECT_FALSE(IsReasonablePath("x\\.", 3));
+ EXPECT_FALSE(IsReasonablePath("x\\..", 4));
+ EXPECT_FALSE(IsReasonablePath("x\\.\\", 4));
+ EXPECT_FALSE(IsReasonablePath("x\\..\\", 5));
+ EXPECT_FALSE(IsReasonablePath("\\x\\.\\", 5));
+ EXPECT_FALSE(IsReasonablePath("\\x\\..\\", 6));
+}
+
+TEST(IsReasonablePath, testOverlongSlashDot_isDetected) {
+ EXPECT_FALSE(IsReasonablePath("/\300\256", 3)); /* /. */
+ EXPECT_TRUE(IsReasonablePath("/\300\257", 3)); /* // */
+ EXPECT_FALSE(IsReasonablePath("\300\256\300\256", 4)); /* .. */
+}
+
+BENCH(IsReasonablePath, bench) {
+ EZBENCH2("IsReasonablePath", donothing, IsReasonablePath("/index.html", 11));
+}
diff --git a/test/net/http/parsecontentlength_test.c b/test/net/http/parsecontentlength_test.c
index 582bed94d..eae45c108 100644
--- a/test/net/http/parsecontentlength_test.c
+++ b/test/net/http/parsecontentlength_test.c
@@ -20,13 +20,16 @@
#include "net/http/http.h"
TEST(ParseContentLength, test) {
- EXPECT_EQ(0, ParseContentLength("", 0));
+ EXPECT_EQ(-1, ParseContentLength(0, 0));
+ EXPECT_EQ(-1, ParseContentLength("", 0));
EXPECT_EQ(-1, ParseContentLength("-1", 2));
EXPECT_EQ(-1, ParseContentLength("-2", 2));
+ EXPECT_EQ(-1, ParseContentLength("e", -1));
+ EXPECT_EQ(-1, ParseContentLength(",", -1));
+ EXPECT_EQ(-1, ParseContentLength("\0", 1));
EXPECT_EQ(0, ParseContentLength("0", 1));
EXPECT_EQ(1, ParseContentLength("1", 1));
- EXPECT_EQ(0x7fffffff, ParseContentLength("2147483647", 10));
- EXPECT_EQ(-1, ParseContentLength("2147483648", 10));
- EXPECT_EQ(-1, ParseContentLength("9223372036854775808", 19));
- EXPECT_EQ(-1, ParseContentLength("88223372036854775808", 20));
+ EXPECT_EQ(42, ParseContentLength("42, 42", -1)); /* RFC7230 § 3.3.2 */
+ EXPECT_EQ(0x000000ffffffffff, ParseContentLength("1099511627775", -1));
+ EXPECT_EQ(-1, ParseContentLength("1099511627776", -1));
}
diff --git a/test/net/http/parsehttprange_test.c b/test/net/http/parsehttprange_test.c
index 75b9f00db..27801b314 100644
--- a/test/net/http/parsehttprange_test.c
+++ b/test/net/http/parsehttprange_test.c
@@ -20,11 +20,11 @@
#include "libc/testlib/testlib.h"
#include "net/http/http.h"
-TEST(ParseHttpRange, testEmptyHack) {
+TEST(ParseHttpRange, testEmptyHack_refusedBecauseItWontEncodeInContentRange) {
long start, length;
const char *s = "bytes=-0";
- EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length));
- EXPECT_EQ(100, start);
+ EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
+ EXPECT_EQ(0, start);
EXPECT_EQ(0, length);
}
@@ -36,6 +36,22 @@ TEST(ParseHttpRange, testEmptyRange_isntEmpty) {
EXPECT_EQ(1, length);
}
+TEST(ParseHttpRange, testEmptyRangeOfOneByteFile_itWorks) {
+ long start, length;
+ const char *s = "bytes=0-0";
+ EXPECT_TRUE(ParseHttpRange(s, strlen(s), 1, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(1, length);
+}
+
+TEST(ParseHttpRange, testEmptyRangeOfEmptyFile_outOfRange) {
+ long start, length;
+ const char *s = "bytes=0-0";
+ EXPECT_FALSE(ParseHttpRange(s, strlen(s), 0, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(0, length);
+}
+
TEST(ParseHttpRange, testInclusiveIndexing) {
long start, length;
const char *s = "bytes=0-10";
@@ -81,7 +97,7 @@ TEST(ParseHttpRange, testOutOfRange) {
const char *s = "bytes=0-100";
EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
EXPECT_EQ(0, start);
- EXPECT_EQ(101, length);
+ EXPECT_EQ(0, length);
}
TEST(ParseHttpRange, testInvalidRange) {
@@ -104,6 +120,14 @@ TEST(ParseHttpRange, testOverflow_duringAddition_setsErrorRange) {
long start, length;
const char *s = "bytes=4611686018427387904-4611686018427387915";
EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
- EXPECT_EQ(4611686018427387904, start);
- EXPECT_EQ(12, length);
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(0, length);
+}
+
+TEST(ParseHttpRange, testMultipartRange_notImplemented) {
+ long start, length;
+ const char *s = "bytes=0-100,200-300";
+ EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length));
+ EXPECT_EQ(0, start);
+ EXPECT_EQ(0, length);
}
diff --git a/test/net/http/parsehttprequest_test.c b/test/net/http/parsehttprequest_test.c
index 5047fa829..cf9cb22b9 100644
--- a/test/net/http/parsehttprequest_test.c
+++ b/test/net/http/parsehttprequest_test.c
@@ -27,7 +27,6 @@
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
#include "net/http/http.h"
-#include "net/http/uri.h"
struct HttpRequest req[1];
@@ -39,10 +38,6 @@ static char *slice(const char *m, struct HttpRequestSlice s) {
return p;
}
-static unsigned version(const char *m) {
- return ParseHttpVersion(m + req->version.a, req->version.b - req->version.a);
-}
-
void SetUp(void) {
InitHttpRequest(req);
}
@@ -51,9 +46,9 @@ void TearDown(void) {
DestroyHttpRequest(req);
}
-/* TEST(ParseHttpRequest, soLittleState) { */
-/* ASSERT_EQ(280, sizeof(struct HttpRequest)); */
-/* } */
+TEST(ParseHttpRequest, soLittleState) {
+ ASSERT_LE(sizeof(struct HttpRequest), 512);
+}
TEST(ParseHttpRequest, testEmpty_tooShort) {
EXPECT_EQ(0, ParseHttpRequest(req, "", 0));
@@ -68,7 +63,7 @@ TEST(ParseHttpRequest, testNoHeaders) {
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/foo", gc(slice(m, req->uri)));
- EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
+ EXPECT_EQ(10, req->version);
}
TEST(ParseHttpRequest, testSomeHeaders) {
@@ -80,7 +75,7 @@ Content-Length: 0\r\n\
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpPost, req->method);
EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri)));
- EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
+ EXPECT_EQ(10, req->version);
EXPECT_STREQ("foo.example", gc(slice(m, req->headers[kHttpHost])));
EXPECT_STREQ("0", gc(slice(m, req->headers[kHttpContentLength])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpEtag])));
@@ -91,8 +86,7 @@ TEST(ParseHttpRequest, testHttp101) {
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri)));
- EXPECT_STREQ("HTTP/1.1", gc(slice(m, req->version)));
- EXPECT_EQ(101, version(m));
+ EXPECT_EQ(11, req->version);
}
TEST(ParseHttpRequest, testHttp100) {
@@ -100,17 +94,48 @@ TEST(ParseHttpRequest, testHttp100) {
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri)));
- EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
- EXPECT_EQ(100, version(m));
+ EXPECT_EQ(10, req->version);
}
-TEST(ParseHttpRequest, testHttp009) {
+TEST(ParseHttpRequest, testUnknownMethod_canBeUsedIfYouWant) {
+ static const char m[] = "#%*+_^ / HTTP/1.0\r\n\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_FALSE(req->method);
+ EXPECT_STREQ("WUT", kHttpMethod[req->method]);
+ EXPECT_STREQ("#%*+_^", gc(slice(m, req->xmethod)));
+}
+
+TEST(ParseHttpRequest, testIllegalMethod) {
+ static const char m[] = "ehd@oruc / HTTP/1.0\r\n\r\n";
+ EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_STREQ("WUT", kHttpMethod[req->method]);
+}
+
+TEST(ParseHttpRequest, testIllegalMethodCasing_weAllowItAndPreserveIt) {
+ static const char m[] = "get / HTTP/1.0\r\n\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_STREQ("GET", kHttpMethod[req->method]);
+ EXPECT_STREQ("get", gc(slice(m, req->xmethod)));
+}
+
+TEST(ParseHttpRequest, testEmptyMethod_isntAllowed) {
+ static const char m[] = " / HTTP/1.0\r\n\r\n";
+ EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_STREQ("WUT", kHttpMethod[req->method]);
+}
+
+TEST(ParseHttpRequest, testEmptyUri_isntAllowed) {
+ static const char m[] = "GET HTTP/1.0\r\n\r\n";
+ EXPECT_EQ(-1, ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_STREQ("GET", kHttpMethod[req->method]);
+}
+
+TEST(ParseHttpRequest, testHttp09) {
static const char m[] = "GET /\r\n\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/", gc(slice(m, req->uri)));
- EXPECT_STREQ("", gc(slice(m, req->version)));
- EXPECT_EQ(9, version(m));
+ EXPECT_EQ(9, req->version);
}
TEST(ParseHttpRequest, testLeadingLineFeeds_areIgnored) {
@@ -153,7 +178,7 @@ Content-Length: 0\n\
EXPECT_EQ(strlen(m) - 1, ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpPost, req->method);
EXPECT_STREQ("/foo?bar%20hi", gc(slice(m, req->uri)));
- EXPECT_STREQ("HTTP/1.0", gc(slice(m, req->version)));
+ EXPECT_EQ(10, req->version);
EXPECT_STREQ("foo.example", gc(slice(m, req->headers[kHttpHost])));
EXPECT_STREQ("0", gc(slice(m, req->headers[kHttpContentLength])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpEtag])));
@@ -174,7 +199,7 @@ Accept-Language: en-US,en;q=0.9\r\n\
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_EQ(kHttpGet, req->method);
EXPECT_STREQ("/tool/net/redbean.png", gc(slice(m, req->uri)));
- EXPECT_STREQ("HTTP/1.1", gc(slice(m, req->version)));
+ EXPECT_EQ(11, req->version);
EXPECT_STREQ("10.10.10.124:8080", gc(slice(m, req->headers[kHttpHost])));
EXPECT_STREQ("1", gc(slice(m, req->headers[kHttpDnt])));
EXPECT_STREQ("", gc(slice(m, req->headers[kHttpExpect])));
@@ -193,6 +218,54 @@ X-User-Agent: hi\r\n\
EXPECT_STREQ("hi", gc(slice(m, req->xheaders.p[0].v)));
}
+TEST(ParseHttpRequest, testNormalHeaderOnMultipleLines_getsOverwritten) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+Content-Type: text/html\r\n\
+Content-Type: text/plain\r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_STREQ("text/plain", gc(slice(m, req->headers[kHttpContentType])));
+ ASSERT_EQ(0, req->xheaders.n);
+}
+
+TEST(ParseHttpRequest, testCommaSeparatedOnMultipleLines_becomesLinear) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+Accept: text/html\r\n\
+Accept: text/plain\r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_STREQ("text/html", gc(slice(m, req->headers[kHttpAccept])));
+ ASSERT_EQ(1, req->xheaders.n);
+ EXPECT_STREQ("Accept", gc(slice(m, req->xheaders.p[0].k)));
+ EXPECT_STREQ("text/plain", gc(slice(m, req->xheaders.p[0].v)));
+}
+
+TEST(HeaderHas, testHeaderSpansMultipleLines) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+Accept-Encoding: deflate\r\n\
+ACCEPT-ENCODING: gzip\r\n\
+ACCEPT-encoding: bzip2\r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_TRUE(HeaderHas(req, m, kHttpAcceptEncoding, "gzip", -1));
+ EXPECT_TRUE(HeaderHas(req, m, kHttpAcceptEncoding, "deflate", -1));
+ EXPECT_FALSE(HeaderHas(req, m, kHttpAcceptEncoding, "funzip", -1));
+}
+
+TEST(HeaderHas, testHeaderOnSameLIne) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+Accept-Encoding: deflate, gzip, bzip2\r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_TRUE(HeaderHas(req, m, kHttpAcceptEncoding, "gzip", -1));
+ EXPECT_TRUE(HeaderHas(req, m, kHttpAcceptEncoding, "deflate", -1));
+ EXPECT_FALSE(HeaderHas(req, m, kHttpAcceptEncoding, "funzip", -1));
+}
+
TEST(ParseHttpRequest, testHeaderValuesWithWhitespace_getsTrimmed) {
static const char m[] = "\
OPTIONS * HTTP/1.0\r\n\
@@ -200,6 +273,45 @@ User-Agent: \t hi there \t \r\n\
\r\n";
EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
EXPECT_STREQ("hi there", gc(slice(m, req->headers[kHttpUserAgent])));
+ EXPECT_STREQ("*", gc(slice(m, req->uri)));
+}
+
+TEST(ParseHttpRequest, testAbsentHost_setsSliceToZero) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_EQ(0, req->headers[kHttpHost].a);
+ EXPECT_EQ(0, req->headers[kHttpHost].b);
+}
+
+TEST(ParseHttpRequest, testEmptyHost_setsSliceToNonzeroValue) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+Host:\r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_NE(0, req->headers[kHttpHost].a);
+ EXPECT_EQ(req->headers[kHttpHost].a, req->headers[kHttpHost].b);
+}
+
+TEST(ParseHttpRequest, testEmptyHost2_setsSliceToNonzeroValue) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+Host: \r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EXPECT_NE(0, req->headers[kHttpHost].a);
+ EXPECT_EQ(req->headers[kHttpHost].a, req->headers[kHttpHost].b);
+}
+
+TEST(IsMimeType, test) {
+ ASSERT_TRUE(IsMimeType("text/plain", -1, "text/plain"));
+ ASSERT_TRUE(IsMimeType("TEXT/PLAIN", -1, "text/plain"));
+ ASSERT_TRUE(IsMimeType("TEXT/PLAIN ", -1, "text/plain"));
+ ASSERT_TRUE(IsMimeType("text/plain; charset=utf-8", -1, "text/plain"));
+ ASSERT_FALSE(IsMimeType("TEXT/PLAI ", -1, "text/plain"));
+ ASSERT_FALSE(IsMimeType("", -1, "text/plain"));
}
void DoTiniestHttpRequest(void) {
@@ -261,3 +373,24 @@ BENCH(ParseHttpRequest, bench) {
EZBENCH2("DoStandardChromeRequest", donothing, DoStandardChromeRequest());
EZBENCH2("DoUnstandardChromeRequest", donothing, DoUnstandardChromeRequest());
}
+
+BENCH(HeaderHas, bench) {
+ static const char m[] = "\
+GET / HTTP/1.1\r\n\
+X-In-Your-Way-A: a\r\n\
+X-In-Your-Way-B: b\r\n\
+X-In-Your-Way-C: b\r\n\
+Accept-Encoding: deflate\r\n\
+ACCEPT-ENCODING: gzip\r\n\
+ACCEPT-encoding: bzip2\r\n\
+\r\n";
+ EXPECT_EQ(strlen(m), ParseHttpRequest(req, m, strlen(m)));
+ EZBENCH2("HeaderHas text/plain", donothing,
+ HeaderHas(req, m, kHttpAccept, "text/plain", 7));
+ EZBENCH2("HeaderHas deflate", donothing,
+ HeaderHas(req, m, kHttpAcceptEncoding, "deflate", 7));
+ EZBENCH2("HeaderHas gzip", donothing,
+ HeaderHas(req, m, kHttpAcceptEncoding, "gzip", 4));
+ EZBENCH2("IsMimeType", donothing,
+ IsMimeType("text/plain; charset=utf-8", -1, "text/plain"));
+}
diff --git a/test/net/http/parseurl_test.c b/test/net/http/parseurl_test.c
new file mode 100644
index 000000000..ca462f70a
--- /dev/null
+++ b/test/net/http/parseurl_test.c
@@ -0,0 +1,725 @@
+/*-*- 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/limits.h"
+#include "libc/mem/mem.h"
+#include "libc/rand/rand.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/hyperion.h"
+#include "libc/testlib/testlib.h"
+#include "net/http/http.h"
+#include "net/http/url.h"
+
+TEST(ParseUrl, testEmpty) {
+ struct Url h;
+ gc(ParseUrl(0, 0, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.params.n);
+ ASSERT_STREQ("", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testFragment) {
+ struct Url h;
+ gc(ParseUrl("#x", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.path.n);
+ ASSERT_EQ(1, h.fragment.n);
+ ASSERT_BINEQ(u"x", h.fragment.p);
+ ASSERT_STREQ("#x", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testFragmentAbsent_isNull) {
+ struct Url h;
+ gc(ParseUrl("", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.fragment.p);
+ ASSERT_EQ(0, h.fragment.n);
+ ASSERT_STREQ("", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testFragmentEmpty_isNonNull) {
+ struct Url h;
+ gc(ParseUrl("#", -1, &h)); /* python's uri parser is wrong here */
+ gc(h.params.p);
+ ASSERT_NE(0, h.fragment.p);
+ ASSERT_EQ(0, h.fragment.n);
+ ASSERT_STREQ("#", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testPathFragment) {
+ struct Url h;
+ gc(ParseUrl("x#y", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ('x', h.path.p[0]);
+ ASSERT_EQ(1, h.fragment.n);
+ ASSERT_EQ('y', h.fragment.p[0]);
+ ASSERT_STREQ("x#y", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testAbsolutePath) {
+ struct Url h;
+ gc(ParseUrl("/x/y", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(4, h.path.n);
+ ASSERT_BINEQ(u"/x/y", h.path.p);
+ ASSERT_STREQ("/x/y", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testRelativePath1) {
+ struct Url h;
+ gc(ParseUrl("x", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ('x', h.path.p[0]);
+ ASSERT_STREQ("x", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testOptions) {
+ struct Url h;
+ gc(ParseUrl("*", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ('*', h.path.p[0]);
+ ASSERT_STREQ("*", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testRelativePath2) {
+ struct Url h;
+ gc(ParseUrl("x/y", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(3, h.path.n);
+ ASSERT_BINEQ(u"x/y", h.path.p);
+ ASSERT_STREQ("x/y", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testRoot) {
+ struct Url h;
+ gc(ParseUrl("/", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ('/', h.path.p[0]);
+ ASSERT_STREQ("/", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testSchemePath) {
+ struct Url h;
+ gc(ParseUrl("x:y", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.scheme.n);
+ ASSERT_BINEQ(u"x", h.scheme.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_BINEQ(u"y", h.path.p);
+ ASSERT_STREQ("x:y", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testSchemeAuthority) {
+ struct Url h;
+ gc(ParseUrl("x://y", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.scheme.n);
+ ASSERT_EQ('x', h.scheme.p[0]);
+ ASSERT_EQ(1, h.host.n);
+ ASSERT_EQ('y', h.host.p[0]);
+ ASSERT_STREQ("x://y", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testParamsQuestion_doesntTurnIntoSpace) {
+ struct Url h;
+ gc(ParseUrl("x?+", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_BINEQ(u"x", h.path.p);
+ ASSERT_EQ(1, h.params.n);
+ ASSERT_EQ(1, h.params.p[0].key.n);
+ ASSERT_EQ('+', h.params.p[0].key.p[0]);
+ ASSERT_STREQ("x?%2B", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testUrl) {
+ struct Url h;
+ gc(ParseUrl("a://b:B@c:C/d?e#f", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.scheme.n);
+ ASSERT_EQ('a', h.scheme.p[0]);
+ ASSERT_EQ(1, h.user.n);
+ ASSERT_EQ('b', h.user.p[0]);
+ ASSERT_EQ(1, h.pass.n);
+ ASSERT_EQ('B', h.pass.p[0]);
+ ASSERT_STREQ("c", gc(strndup(h.host.p, h.host.n)));
+ ASSERT_EQ(1, h.port.n);
+ ASSERT_EQ('C', h.port.p[0]);
+ ASSERT_EQ(2, h.path.n);
+ ASSERT_BINEQ(u"/d", h.path.p);
+ ASSERT_EQ(1, h.params.n);
+ ASSERT_EQ(1, h.params.p[0].key.n);
+ ASSERT_BINEQ(u"e", h.params.p[0].key.p);
+ ASSERT_EQ(0, h.params.p[0].val.n);
+ ASSERT_EQ(0, h.params.p[0].val.p);
+ ASSERT_EQ(1, h.fragment.n);
+ ASSERT_BINEQ(u"f", h.fragment.p);
+ ASSERT_STREQ("a://b:B@c:C/d?e#f", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testEmptyQueryKeyVal_decodesToEmptyStrings) {
+ struct Url h;
+ gc(ParseUrl("?=", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.params.n);
+ ASSERT_EQ(0, h.params.p[0].key.n);
+ ASSERT_NE(0, h.params.p[0].key.p);
+ ASSERT_EQ(0, h.params.p[0].val.n);
+ ASSERT_NE(0, h.params.p[0].val.p);
+ ASSERT_STREQ("?=", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testMultipleEquals_goesIntoValue) {
+ struct Url h;
+ gc(ParseUrl("?==", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.params.n);
+ ASSERT_EQ(0, h.params.p[0].key.n);
+ ASSERT_NE(0, h.params.p[0].key.p);
+ ASSERT_EQ(1, h.params.p[0].val.n);
+ ASSERT_EQ('=', h.params.p[0].val.p[0]);
+ ASSERT_STREQ("?=%3D", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testUrlWithoutScheme) {
+ struct Url h;
+ gc(ParseUrl("//b@c/d?e#f", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.scheme.n);
+ ASSERT_EQ(1, h.user.n);
+ ASSERT_EQ('b', h.user.p[0]);
+ ASSERT_EQ(1, h.host.n);
+ ASSERT_EQ('c', h.host.p[0]);
+ ASSERT_EQ(2, h.path.n);
+ ASSERT_BINEQ(u"/d", h.path.p);
+ ASSERT_EQ(1, h.params.n);
+ ASSERT_EQ(1, h.params.p[0].key.n);
+ ASSERT_BINEQ(u"e", h.params.p[0].key.p);
+ ASSERT_EQ(0, h.params.p[0].val.n);
+ ASSERT_EQ(0, h.params.p[0].val.p);
+ ASSERT_EQ(1, h.fragment.n);
+ ASSERT_BINEQ(u"f", h.fragment.p);
+ ASSERT_STREQ("//b@c/d?e#f", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testUrlWithoutUser) {
+ struct Url h;
+ gc(ParseUrl("a://c/d?e#f", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.scheme.n);
+ ASSERT_EQ('a', h.scheme.p[0]);
+ ASSERT_EQ(0, h.user.n);
+ ASSERT_EQ(0, h.pass.n);
+ ASSERT_EQ(1, h.host.n);
+ ASSERT_EQ('c', h.host.p[0]);
+ ASSERT_EQ(0, h.port.n);
+ ASSERT_EQ(2, h.path.n);
+ ASSERT_BINEQ(u"/d", h.path.p);
+ ASSERT_EQ(1, h.params.n);
+ ASSERT_EQ(1, h.params.p[0].key.n);
+ ASSERT_EQ('e', h.params.p[0].key.p[0]);
+ ASSERT_EQ(0, h.params.p[0].val.n);
+ ASSERT_EQ(0, h.params.p[0].val.p);
+ ASSERT_EQ(1, h.fragment.n);
+ ASSERT_EQ('f', h.fragment.p[0]);
+ ASSERT_STREQ("a://c/d?e#f", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testEmptyParams_absentCanBeDiscerned) {
+ struct Url h;
+ gc(ParseUrl("", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.params.n);
+ ASSERT_EQ(NULL, h.params.p);
+ gc(ParseUrl("?", -1, &h)); /* python's uri parser is wrong here */
+ gc(h.params.p);
+ ASSERT_EQ(0, h.params.n);
+ ASSERT_NE(NULL, h.params.p);
+}
+
+TEST(ParseUrl, testWeirdAmps_areReprodicible) {
+ struct Url h;
+ gc(ParseUrl("?&&", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(3, h.params.n);
+ ASSERT_EQ(0, h.params.p[0].key.n);
+ ASSERT_NE(0, h.params.p[0].key.p);
+ ASSERT_EQ(0, h.params.p[0].val.n);
+ ASSERT_EQ(0, h.params.p[0].val.p);
+ ASSERT_EQ(0, h.params.p[1].key.n);
+ ASSERT_NE(0, h.params.p[1].key.p);
+ ASSERT_EQ(0, h.params.p[1].val.n);
+ ASSERT_EQ(0, h.params.p[1].val.p);
+ ASSERT_EQ(0, h.params.p[2].key.n);
+ ASSERT_NE(0, h.params.p[2].key.p);
+ ASSERT_EQ(0, h.params.p[2].val.n);
+ ASSERT_EQ(0, h.params.p[2].val.p);
+ ASSERT_STREQ("?&&", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testOpaquePart_canLetQuestionMarkGoInPath) {
+ struct Url h; /* python's uri parser is wrong here */
+ gc(ParseUrl("s:o!$%&'()*+,-./09:;=?@AZ_az#fragged", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(26, h.path.n);
+ ASSERT_EQ(0, memcmp(h.path.p, "o!$%&'()*+,-./09:;=?@AZ_az", 26));
+ ASSERT_EQ(0, h.params.n);
+ ASSERT_EQ(NULL, h.params.p);
+ ASSERT_STREQ("s:o!$%25&'()*+,-./09:;=%3F@AZ_az#fragged",
+ gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testSchemePathWithoutAuthority_paramsAreAllowed) {
+ struct Url h;
+ gc(ParseUrl("s:/o!$%&'()*+,-./09:;=?@AZ_az#fragged", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(20, h.path.n);
+ ASSERT_EQ(0, memcmp(h.path.p, "/o!$%&'()*+,-./09:;=", 20));
+ ASSERT_EQ(1, h.params.n);
+ ASSERT_STREQ("s:/o!$%25&'()*+,-./09:;=?%40AZ_az#fragged",
+ gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testOpaquePart_permitsPercentEncoding) {
+ struct Url h;
+ gc(ParseUrl("s:%2Fo!$%&'()*+,-./09:;=?@AZ_az#fragged", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(27, h.path.n);
+ ASSERT_EQ(0, memcmp(h.path.p, "/o!$%&'()*+,-./09:;=?@AZ_az", 27));
+ ASSERT_EQ(0, h.params.n);
+ ASSERT_STREQ("s:/o!$%25&\'()*+,-./09:;=%3F@AZ_az#fragged",
+ gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testTelephone) {
+ struct Url h;
+ gc(ParseUrl("tel:+1-212-867-5309", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(15, h.path.n);
+ ASSERT_BINEQ(u"+1-212-867-5309", h.path.p);
+ ASSERT_STREQ("tel:+1-212-867-5309", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testLolv6) {
+ struct Url h;
+ gc(ParseUrl("//[::1]:31337", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(3, h.host.n);
+ ASSERT_BINEQ(u"::1", h.host.p);
+ ASSERT_EQ(5, h.port.n);
+ ASSERT_BINEQ(u"31337", h.port.p);
+ ASSERT_STREQ("//[::1]:31337", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testLolV6_withoutPort) {
+ struct Url h;
+ gc(ParseUrl("//[::1]", -1, &h));
+ gc(h.params.p);
+ ASSERT_STREQ("//[::1]", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testLolv7) {
+ struct Url h;
+ gc(ParseUrl("//[vf.::1]", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(6, h.host.n);
+ ASSERT_BINEQ(u"vf.::1", h.host.p);
+ ASSERT_EQ(0, h.port.n);
+ ASSERT_EQ(0, h.port.p);
+ ASSERT_STREQ("//[vf.::1]", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testLolv7WithoutColon_weCantProduceLegalEncodingSadly) {
+ struct Url h;
+ gc(ParseUrl("//[v7.7.7.7]", -1, &h));
+ gc(h.params.p);
+ ASSERT_STREQ("//v7.7.7.7", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testObviouslyIllegalIpLiteral_getsTreatedAsRegName) {
+ struct Url h;
+ gc(ParseUrl("//[vf.::1%00]", -1, &h));
+ gc(h.params.p);
+ ASSERT_STREQ("//vf.%3A%3A1%00", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseHost, test) {
+ struct Url h = {0};
+ gc(ParseHost("foo.example:80", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(11, h.host.n);
+ ASSERT_BINEQ(u"foo.example", h.host.p);
+ ASSERT_EQ(2, h.port.n);
+ ASSERT_BINEQ(u"80", h.port.p);
+ ASSERT_STREQ("//foo.example:80", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseHost, testObviouslyIllegalIpLiteral_getsTreatedAsRegName) {
+ struct Url h = {0};
+ gc(ParseHost("[vf.::1%00]", -1, &h));
+ gc(h.params.p);
+ ASSERT_STREQ("//vf.%3A%3A1%00", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseHost, testUnclosedIpv6_doesntSetPort) {
+ struct Url h = {0};
+ gc(ParseHost("2001:db8:cafe::17", -1, &h));
+ gc(h.params.p);
+ ASSERT_STREQ("2001:db8:cafe::17", gc(strndup(h.host.p, h.host.n)));
+ ASSERT_EQ(0, h.port.n);
+ ASSERT_EQ(0, h.port.p);
+ ASSERT_STREQ("//2001%3Adb8%3Acafe%3A%3A17", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(EncodeUrl, testHostPortPlacedInHostField_ungoodIdea) {
+ struct Url h = {0};
+ h.host.n = strlen("foo.example:80");
+ h.host.p = "foo.example:80";
+ ASSERT_STREQ("//foo.example%3A80", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testUrlWithoutParams) {
+ struct Url h;
+ gc(ParseUrl("a://b@c/d#f", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.scheme.n);
+ ASSERT_EQ('a', h.scheme.p[0]);
+ ASSERT_EQ(1, h.user.n);
+ ASSERT_EQ('b', h.user.p[0]);
+ ASSERT_EQ(1, h.host.n);
+ ASSERT_EQ('c', h.host.p[0]);
+ ASSERT_EQ(2, h.path.n);
+ ASSERT_BINEQ(u"/d", h.path.p);
+ ASSERT_EQ(0, h.params.n);
+ ASSERT_EQ(1, h.fragment.n);
+ ASSERT_EQ('f', h.fragment.p[0]);
+ ASSERT_STREQ("a://b@c/d#f", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testLatin1_doesNothing) {
+ struct Url h;
+ const char b[1] = {0377};
+ gc(ParseUrl(b, 1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ(0, memcmp("\377", h.path.p, 1));
+ ASSERT_STREQ("%FF", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseRequestUri, testLatin1_expandsMemoryToUtf8) {
+ struct Url h;
+ const char b[1] = {0377};
+ gc(ParseRequestUri(b, 1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(2, h.path.n);
+ ASSERT_EQ(0, memcmp("\303\277", h.path.p, 2));
+}
+
+TEST(ParseUrl, testPercentShrinkingMemory) {
+ struct Url h;
+ gc(ParseUrl("%Ff", 3, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ(0, memcmp("\377", h.path.p, 1));
+ ASSERT_STREQ("%FF", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testEscapingWontOverrun) {
+ struct Url h;
+ char b[1] = {'%'};
+ gc(ParseUrl(b, 1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ(0, memcmp("%", h.path.p, 1));
+ ASSERT_STREQ("%25", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testBadPercent_getsIgnored) {
+ struct Url h;
+ gc(ParseUrl("%FZ", 3, &h));
+ gc(h.params.p);
+ ASSERT_EQ(3, h.path.n);
+ ASSERT_EQ(0, memcmp("%FZ", h.path.p, 3));
+}
+
+TEST(ParseUrl, testFileUrl) {
+ struct Url h;
+ gc(ParseUrl("file:///etc/passwd", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(4, h.scheme.n);
+ ASSERT_BINEQ(u"file", h.scheme.p);
+ ASSERT_EQ(0, h.host.n);
+ ASSERT_NE(0, h.host.p);
+ ASSERT_EQ(0, h.port.n);
+ ASSERT_EQ(0, h.port.p);
+ ASSERT_EQ(11, h.path.n);
+ ASSERT_BINEQ(u"/etc/passwd", h.path.p);
+ ASSERT_STREQ("file:///etc/passwd", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(EncodeUrl, testModifyingParseResultAndReencoding_addsStructure) {
+ size_t n;
+ struct Url h;
+ gc(ParseUrl("rel", -1, &h));
+ gc(h.params.p);
+ h.host.n = 7;
+ h.host.p = "justine";
+ ASSERT_STREQ("//justine/rel", gc(EncodeUrl(&h, &n)));
+ ASSERT_EQ(13, n);
+}
+
+TEST(EncodeUrl, testTortureCharacters_doesWhatYouAskItToDoButSchemeCantEscape) {
+ size_t n;
+ struct Url h;
+ memset(&h, 0, sizeof(h));
+ h.scheme.n = 1;
+ h.scheme.p = "/";
+ h.user.n = 1;
+ h.user.p = "";
+ h.pass.n = 1;
+ h.pass.p = "";
+ h.host.n = 1;
+ h.host.p = "";
+ h.port.n = 1;
+ h.port.p = "";
+ h.path.n = 1;
+ h.path.p = "";
+ h.params = (struct UrlParams){.n = 1,
+ .p = (struct UrlParam[]){{
+ .key = (struct UrlView){.n = 1, .p = ""},
+ .val = (struct UrlView){.n = 1, .p = ""},
+ }}};
+ h.fragment.n = 1;
+ h.fragment.p = "";
+ ASSERT_STREQ("/://%00:%00@%00:%00/%00?%00=%00#%00", gc(EncodeUrl(&h, &n)));
+ ASSERT_EQ(35, n);
+}
+
+TEST(EncodeUrl, testUserPassPort_allDependOnHostNonAbsence) {
+ size_t n;
+ struct Url h;
+ memset(&h, 0, sizeof(h));
+ h.scheme.n = 1;
+ h.scheme.p = "/";
+ h.user.n = 1;
+ h.user.p = "";
+ h.pass.n = 1;
+ h.pass.p = "";
+ h.host.n = 0;
+ h.host.p = 0;
+ h.port.n = 1;
+ h.port.p = "";
+ h.path.n = 1;
+ h.path.p = "";
+ h.params = (struct UrlParams){.n = 1,
+ .p = (struct UrlParam[]){{
+ .key = (struct UrlView){.n = 1, .p = ""},
+ .val = (struct UrlView){.n = 1, .p = ""},
+ }}};
+ h.fragment.n = 1;
+ h.fragment.p = "";
+ ASSERT_STREQ("/:%00?%00=%00#%00", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(EncodeUrl, testEmptyRegName_isLegal) {
+ size_t n;
+ struct Url h;
+ memset(&h, 0, sizeof(h));
+ h.scheme.n = 1;
+ h.scheme.p = "/";
+ h.user.n = 1;
+ h.user.p = "";
+ h.pass.n = 1;
+ h.pass.p = "";
+ h.host.n = 0;
+ h.host.p = "";
+ h.port.n = 1;
+ h.port.p = "";
+ h.path.n = 1;
+ h.path.p = "";
+ h.params = (struct UrlParams){.n = 1,
+ .p = (struct UrlParam[]){{
+ .key = (struct UrlView){.n = 1, .p = ""},
+ .val = (struct UrlView){.n = 1, .p = ""},
+ }}};
+ h.fragment.n = 1;
+ h.fragment.p = "";
+ ASSERT_STREQ("/://%00:%00@:%00/%00?%00=%00#%00", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testEmptyScheme_isNotPossible) {
+ struct Url h;
+ gc(ParseUrl(":", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.scheme.n);
+ ASSERT_EQ(0, h.scheme.p);
+ ASSERT_EQ(1, h.path.n);
+ ASSERT_EQ(':', h.path.p[0]);
+ ASSERT_STREQ(":", gc(EncodeUrl(&h, 0)));
+ gc(ParseUrl("://hi", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.scheme.n);
+ ASSERT_EQ(0, h.scheme.p);
+ ASSERT_EQ(5, h.path.n);
+ ASSERT_BINEQ(u"://hi", h.path.p);
+ ASSERT_STREQ("://hi", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testZipUri2) {
+ struct Url h;
+ gc(ParseUrl("zip:etc/passwd", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(3, h.scheme.n);
+ ASSERT_BINEQ(u"zip", h.scheme.p);
+ ASSERT_EQ(10, h.path.n);
+ ASSERT_BINEQ(u"etc/passwd", h.path.p);
+ ASSERT_STREQ("zip:etc/passwd", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testZipUri3) {
+ struct Url h;
+ gc(ParseUrl("zip:/etc/passwd", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.host.n);
+ ASSERT_EQ(0, h.host.p);
+ ASSERT_EQ(3, h.scheme.n);
+ ASSERT_BINEQ(u"zip", h.scheme.p);
+ ASSERT_EQ(11, h.path.n);
+ ASSERT_BINEQ(u"/etc/passwd", h.path.p);
+ ASSERT_STREQ("zip:/etc/passwd", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testDataUri) {
+ struct Url h;
+ gc(ParseUrl("data:image/png;base64,09AZaz+/==", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.host.n);
+ ASSERT_EQ(0, h.host.p);
+ ASSERT_EQ(4, h.scheme.n);
+ ASSERT_BINEQ(u"data", h.scheme.p);
+ ASSERT_EQ(27, h.path.n);
+ ASSERT_BINEQ(u"image/png;base64,09AZaz+/==", h.path.p);
+ ASSERT_STREQ("data:image/png;base64,09AZaz+/==", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseUrl, testBadSchemeCharacter_parserAssumesItsPath) {
+ struct Url h;
+ gc(ParseUrl("fil\e://hi", -1, &h));
+ gc(h.params.p);
+ ASSERT_EQ(0, h.scheme.n);
+ ASSERT_EQ(0, h.scheme.p);
+ ASSERT_EQ(9, h.path.n);
+ ASSERT_BINEQ(u"fil←://hi", h.path.p);
+ ASSERT_STREQ("fil%1B://hi", gc(EncodeUrl(&h, 0)));
+}
+
+TEST(ParseParams, testEmpty) {
+ struct UrlParams h = {0};
+ gc(ParseParams(0, 0, &h));
+ gc(h.p);
+ ASSERT_EQ(0, h.n);
+}
+
+TEST(ParseParams, test) {
+ struct UrlParams h = {0};
+ gc(ParseParams("a=b&c&x+y%7A=", -1, &h));
+ gc(h.p);
+ ASSERT_EQ(3, h.n);
+ ASSERT_EQ(1, h.p[0].key.n);
+ ASSERT_EQ(1, h.p[0].val.n);
+ ASSERT_EQ(1, h.p[1].key.n);
+ ASSERT_NE(0, h.p[1].key.p);
+ ASSERT_EQ(0, h.p[1].val.n);
+ ASSERT_EQ(0, h.p[1].val.p);
+ ASSERT_EQ(4, h.p[2].key.n);
+ ASSERT_EQ(0, h.p[2].val.n);
+ EXPECT_EQ('a', h.p[0].key.p[0]);
+ EXPECT_EQ('b', h.p[0].val.p[0]);
+ EXPECT_EQ('c', h.p[1].key.p[0]);
+ EXPECT_BINEQ(u"x yz", h.p[2].key.p);
+}
+
+TEST(ParseParams, testLatin1_doesNothing) {
+ struct UrlParams h = {0};
+ gc(ParseParams("\200", -1, &h));
+ gc(h.p);
+ ASSERT_EQ(1, h.n);
+ ASSERT_EQ(1, h.p[0].key.n);
+ ASSERT_EQ(0200, h.p[0].key.p[0] & 255);
+}
+
+TEST(ParseParams, testUtf8_doesNothing) {
+ struct UrlParams h = {0};
+ gc(ParseParams("\300\200", -1, &h));
+ gc(h.p);
+ ASSERT_EQ(1, h.n);
+ ASSERT_EQ(2, h.p[0].key.n);
+ ASSERT_EQ(0300, h.p[0].key.p[0] & 255);
+ ASSERT_EQ(0200, h.p[0].key.p[1] & 255);
+}
+
+TEST(ParseRequestUri, fuzz) {
+ int i, j;
+ struct Url h;
+ char B[13], C[] = "/:#?%[]:@&=abc123xyz\200\300";
+ for (i = 0; i < 1024; ++i) {
+ for (j = 0; j < sizeof(B); ++j) {
+ B[j] = C[rand() % sizeof(C)];
+ }
+ free(ParseRequestUri(B, 8, &h));
+ free(h.params.p);
+ }
+}
+
+void A(void) {
+ struct UrlParams h = {0};
+ free(ParseParams(kHyperion, kHyperionSize, &h));
+ free(h.p);
+}
+
+BENCH(ParseUrl, bench) {
+ struct Url h;
+ EZBENCH2("ParseParams hyperion", donothing, A());
+ EZBENCH2("ParseUrl a", donothing, free(ParseUrl("a", -1, &h)));
+ EZBENCH2("ParseUrl a://b@c/d#f", donothing,
+ free(ParseUrl("a://b@c/d#f", -1, &h)));
+ EZBENCH2("ParseUrl a://b@c/d?z#f", donothing, ({
+ free(ParseUrl("a://b@c/?zd#f", -1, &h));
+ free(h.params.p);
+ }));
+ EZBENCH2("ParseHost", donothing, free(ParseHost("127.0.0.1:34832", 15, &h)));
+ EZBENCH2("ParseIp", donothing, ParseIp("127.0.0.1", 9));
+}
+
+BENCH(EncodeUrl, bench) {
+ struct Url h;
+ gc(ParseUrl("a", -1, &h));
+ EZBENCH2("EncodeUrl a", donothing, free(EncodeUrl(&h, 0)));
+ gc(ParseUrl("a://b@c/d#f", -1, &h));
+ EZBENCH2("EncodeUrl a://b@c/d#f", donothing, free(EncodeUrl(&h, 0)));
+ gc(ParseUrl("a://b@c/?zd#f", -1, &h));
+ gc(h.params.p);
+ EZBENCH2("EncodeUrl a://b@c/d?z#f", donothing, free(EncodeUrl(&h, 0)));
+ gc(ParseUrl(kHyperion, kHyperionSize, &h));
+ gc(h.params.p);
+ EZBENCH2("EncodeUrl hyperion", donothing, free(EncodeUrl(&h, 0)));
+}
diff --git a/net/http/urislice2cstr.c b/test/net/http/underlong_test.c
similarity index 74%
rename from net/http/urislice2cstr.c
rename to test/net/http/underlong_test.c
index 0633af2d9..043084956 100644
--- a/net/http/urislice2cstr.c
+++ b/test/net/http/underlong_test.c
@@ -1,7 +1,7 @@
/*-*- 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 2020 Justine Alexandra Roberts Tunney │
+│ 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 │
@@ -16,30 +16,25 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/macros.internal.h"
-#include "libc/str/str.h"
-#include "net/http/uri.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/escape.h"
-/* TODO(jart): Unescape */
-
-char *urislice2cstr(char *buf, size_t size, struct UriSlice slice,
- const char *uristr, const char *defaultval) {
+TEST(Underlong, test) {
size_t n;
- const char *p;
- if (size) {
- if (slice.n) {
- p = uristr + slice.i;
- n = slice.n;
- } else if (defaultval) {
- p = defaultval;
- n = strlen(defaultval);
- } else {
- p = NULL;
- n = 0;
- }
- n = MIN(n, size - 1);
- memcpy(buf, p, n);
- buf[n] = '\0';
- }
- return buf;
+ EXPECT_BINEQ(u"e e", gc(Underlong("e\300\200e", -1, &n)));
+ EXPECT_EQ(3, n);
+}
+
+TEST(Underlong, testNormalText) {
+ size_t n;
+ EXPECT_STREQ(kHyperion, gc(Underlong(kHyperion, kHyperionSize, &n)));
+ EXPECT_EQ(kHyperionSize, n);
+}
+
+BENCH(Underlong, bench) {
+ EZBENCH2("Underlong", donothing,
+ free(Underlong(kHyperion, kHyperionSize, 0)));
}
diff --git a/test/net/http/uricspn_test.c b/test/net/http/uricspn_test.c
deleted file mode 100644
index 2e6801633..000000000
--- a/test/net/http/uricspn_test.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*-*- 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 2020 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/testlib/ezbench.h"
-#include "libc/testlib/testlib.h"
-#include "net/http/uri.h"
-
-_Alignas(32) const char kWinsockIcoPngBase64[] = "\
-base64,iVBORw0KGgoAAAANSUhEUgAAAJcAAACXCAYAAAAYn8l5AAAABmJLR0QA/\
-wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4woLByMP6uwgW\
-QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAMeSURBVHja7\
-d1BcuIwEAVQPMW94GbAzeBkZJepUXpw01ixDO+tE0PML+lHtsV0v9/vO+jgj1OAc\
-CFcIFwIF8IFP+wrvzRNkzP3y7a4YmTkQrgQLnitc/XqA3GX+9SrU/+ei8vl8uMnT\
-qeTkQvTIggXwoVC36mOJhZa3Upm5ALhQrjQuV6jT2HkQrgQLhAuNlLo+96Z6q5XI\
-xcIF8KFzrXbWTDt0jTf4AkrIxfChXCBcCFcCBcIF8LFO9iP/gajx9jXMvrj80Yuh\
-AuEC52r2q9G6jnRxWQX7Y1cCBfCBcLFxxb6tsBH5f12uz08xvV6Lb328Xh8+nfO5\
-/NsyVfwjVwIF8IFa3auzALpXL96pRst0dWinta+loVWIxfChXCBcCFcCBcIF8LFe\
-xn+6Z+5xc5oYTOzQJr5mcrFbYxcCBfCBcKFQv9AexdC9U7UueMueWwjFwgXwoVwO\
-QUIF8IFwkV3e6dgfdETQ5knmIxcmBZBuBAuUOgH1Rb6LRZ8IxfChXDBt+le2N9nq\
-a0a222VRn/aJrp5sO1CS22XlPkC9fa1R/tuIiMXwoVwgXDx5oV+ruCPJlrI7LXfa\
-XsuMouo1YXWXv8IGLkwLSJcMGbnyrzWmqK/s31/Ue+pdJr2uNECbrvoXP0cen2eR\
-i5MiwgXCBf9DX8n6ta+lCmzkFkp+FGhb89N9Yu52uMs9eVYRi5MiwgXbKdzba0TV\
-h7NjzpY5i7Tpb78tD1OZrE408GMXJgWES4QLhT6zRf8qAxXFlqXKu+Vgp/5xyX6u\
-41cmBYRLvg7dS5xJyqPzW2HFH0Ev9mxKjJ3wRq5MC0iXCBc9FdaRM38DzD6o/kjF\
-frRy7uRC+FCuOBlpUVUnjzJhQvXo+8PaxEV0yLCBU9xs+Cg2ies1+5g0RPfRi5Mi\
-wgXCBcK/UeYe3Ims6ia2RN1zfJu5MK0iHDBQy5cj/AhFLZd6inarskWSpgWES4QL\
-sZkEXUAS227VJU5ti2UMC0iXKBzfUIPW3vbqrm96qP3Z+TCtIhwgXCh0POfAt1T5\
-i6Nw+Ew+/6MXJgWES7Quejf74xcdPMFQQsgQ0YEZnUAAAAASUVORK5CYII=";
-
-size_t size;
-
-void SetUp(void) {
- size = strlen(kWinsockIcoPngBase64);
-}
-BENCH(strlen, bench) {
- EZBENCH(donothing, (size = strlen(kWinsockIcoPngBase64)));
-}
-TEST(uricspn, test) {
- EXPECT_EQ(size, uricspn(kWinsockIcoPngBase64, size));
-}
-BENCH(uricspn, bench) {
- EZBENCH(donothing, uricspn(kWinsockIcoPngBase64, size));
-}
diff --git a/test/net/http/uriparse_test.c b/test/net/http/uriparse_test.c
deleted file mode 100644
index 306066dd1..000000000
--- a/test/net/http/uriparse_test.c
+++ /dev/null
@@ -1,141 +0,0 @@
-/*-*- 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 2020 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/bits/bits.h"
-#include "libc/bits/initializer.internal.h"
-#include "libc/errno.h"
-#include "libc/log/log.h"
-#include "libc/macros.internal.h"
-#include "libc/mem/mem.h"
-#include "libc/runtime/gc.internal.h"
-#include "libc/stdio/stdio.h"
-#include "libc/str/str.h"
-#include "libc/testlib/ezbench.h"
-#include "libc/testlib/testlib.h"
-#include "libc/x/x.h"
-#include "net/http/uri.h"
-
-#define URIPARSE(URI) uriparse(&uri, (p = URI), (size = sizeof(URI) - 1))
-
-static const char kHttpCosmopolitanVideoUrl[] =
- "http://cosmopolitan.storage.googleapis.com/pub/vid/blankspace.mpg";
-
-static const char kSipPriceIsTortureUri[] =
- "sip:bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00";
-
-static const char kWinsockIcoPngBase64[] = "\
-base64,iVBORw0KGgoAAAANSUhEUgAAAJcAAACXCAYAAAAYn8l5AAAABmJLR0QA/\
-wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4woLByMP6uwgW\
-QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAMeSURBVHja7\
-d1BcuIwEAVQPMW94GbAzeBkZJepUXpw01ixDO+tE0PML+lHtsV0v9/vO+jgj1OAc\
-CFcIFwIF8IFP+wrvzRNkzP3y7a4YmTkQrgQLnitc/XqA3GX+9SrU/+ei8vl8uMnT\
-qeTkQvTIggXwoVC36mOJhZa3Upm5ALhQrjQuV6jT2HkQrgQLhAuNlLo+96Z6q5XI\
-xcIF8KFzrXbWTDt0jTf4AkrIxfChXCBcCFcCBcIF8LFO9iP/gajx9jXMvrj80Yuh\
-AuEC52r2q9G6jnRxWQX7Y1cCBfCBcLFxxb6tsBH5f12uz08xvV6Lb328Xh8+nfO5\
-/NsyVfwjVwIF8IFa3auzALpXL96pRst0dWinta+loVWIxfChXCBcCFcCBcIF8LFe\
-xn+6Z+5xc5oYTOzQJr5mcrFbYxcCBfCBcKFQv9AexdC9U7UueMueWwjFwgXwoVwO\
-QUIF8IFwkV3e6dgfdETQ5knmIxcmBZBuBAuUOgH1Rb6LRZ8IxfChXDBt+le2N9nq\
-a0a222VRn/aJrp5sO1CS22XlPkC9fa1R/tuIiMXwoVwgXDx5oV+ruCPJlrI7LXfa\
-XsuMouo1YXWXv8IGLkwLSJcMGbnyrzWmqK/s31/Ue+pdJr2uNECbrvoXP0cen2eR\
-i5MiwgXCBf9DX8n6ta+lCmzkFkp+FGhb89N9Yu52uMs9eVYRi5MiwgXbKdzba0TV\
-h7NjzpY5i7Tpb78tD1OZrE408GMXJgWES4QLhT6zRf8qAxXFlqXKu+Vgp/5xyX6u\
-41cmBYRLvg7dS5xJyqPzW2HFH0Ev9mxKjJ3wRq5MC0iXCBc9FdaRM38DzD6o/kjF\
-frRy7uRC+FCuOBlpUVUnjzJhQvXo+8PaxEV0yLCBU9xs+Cg2ies1+5g0RPfRi5Mi\
-wgXCBcK/UeYe3Ims6ia2RN1zfJu5MK0iHDBQy5cj/AhFLZd6inarskWSpgWES4QL\
-sZkEXUAS227VJU5ti2UMC0iXKBzfUIPW3vbqrm96qP3Z+TCtIhwgXCh0POfAt1T5\
-i6Nw+Ew+/6MXJgWES7Quejf74xcdPMFQQsgQ0YEZnUAAAAASUVORK5CYII=";
-
-static size_t size;
-static const char *p;
-static struct Uri uri;
-static struct UriMem {
- struct UriSlice segs[8];
- struct UriRef paramsegs[8];
- struct UriKeyval params[4], queries[4];
-} urimem_;
-
-static textstartup void init() {
- uri.segs.n = ARRAYLEN(urimem_.segs);
- uri.segs.p = urimem_.segs;
- uri.params.n = ARRAYLEN(urimem_.params);
- uri.params.p = urimem_.params;
- uri.queries.n = ARRAYLEN(urimem_.queries);
- uri.queries.p = urimem_.queries;
- uri.paramsegs.n = ARRAYLEN(urimem_.paramsegs);
- uri.paramsegs.p = urimem_.paramsegs;
-}
-
-const void *const g_name_ctor[] initarray = {init};
-
-TEST(uriparse, sipPstnUri) {
- EXPECT_NE(-1, URIPARSE("sip:+12125650666"));
- EXPECT_STREQ("sip", gc(strndup(p + uri.scheme.i, uri.scheme.n)));
- EXPECT_STREQ("+12125650666", gc(strndup(p + uri.host.i, uri.host.n)));
- EXPECT_STREQ("", gc(strndup(p + uri.opaque.i, uri.opaque.n)));
-}
-
-TEST(uriparse, printVideoUrl) {
- EXPECT_NE(-1, URIPARSE(kHttpCosmopolitanVideoUrl));
- EXPECT_STREQ("http", gc(strndup(p + uri.scheme.i, uri.scheme.n)));
- EXPECT_STREQ("cosmopolitan.storage.googleapis.com",
- gc(strndup(p + uri.host.i, uri.host.n)));
- EXPECT_STREQ("", gc(strndup(p + uri.port.i, uri.port.n)));
- EXPECT_STREQ("/pub/vid/blankspace.mpg",
- gc(strndup(p + uri.segs.p[0].i,
- (uri.segs.p[uri.segs.i - 1].n +
- (uri.segs.p[uri.segs.i - 1].i - uri.segs.p[0].i)))));
-}
-
-TEST(uriparse, localRelativeFile) {
- EXPECT_NE(-1, URIPARSE("blankspace.mpg"));
- EXPECT_STREQ("", gc(strndup(p + uri.scheme.i, uri.scheme.n)));
- EXPECT_STREQ("", gc(strndup(p + uri.host.i, uri.host.n)));
- EXPECT_STREQ("", gc(strndup(p + uri.port.i, uri.port.n)));
- EXPECT_STREQ("blankspace.mpg",
- gc(strndup(p + uri.segs.p[0].i,
- (uri.segs.p[uri.segs.i - 1].n +
- (uri.segs.p[uri.segs.i - 1].i - uri.segs.p[0].i)))));
-}
-
-TEST(uriparse, badPort_einval) {
- EXPECT_EQ(-1, URIPARSE("http://hello.example:http/"));
- EXPECT_EQ(EINVAL, errno);
-}
-
-TEST(uriparse, datauri) {
- size = strlen((p = gc(xstrcat("data:image/png;", kWinsockIcoPngBase64))));
- EXPECT_NE(-1, uriparse(&uri, p, size));
- EXPECT_EQ(5, uri.opaque.i);
- EXPECT_EQ(size - 5, uri.opaque.n);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-BENCH(uriparse, bench) {
- EZBENCH(donothing, URIPARSE("sip:+12125650666"));
- EZBENCH(donothing, URIPARSE("http://hello.example"));
- EZBENCH(donothing, URIPARSE(kHttpCosmopolitanVideoUrl));
- EZBENCH(donothing, URIPARSE(kSipPriceIsTortureUri));
-}
-
-BENCH(uriparse, bigWinsockIcoPngUri) {
- const char *BigDataIconUri;
- BigDataIconUri = gc(xstrcat("data:image/png;", kWinsockIcoPngBase64));
- size = strlen(kWinsockIcoPngBase64);
- EZBENCH(donothing, uriparse(&uri, BigDataIconUri, size));
-}
diff --git a/test/net/http/visualizecontrolcodes_test.c b/test/net/http/visualizecontrolcodes_test.c
index b307f0b4d..8c147c7b9 100644
--- a/test/net/http/visualizecontrolcodes_test.c
+++ b/test/net/http/visualizecontrolcodes_test.c
@@ -16,16 +16,32 @@
│ 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/hyperion.h"
#include "libc/testlib/testlib.h"
-#include "net/http/http.h"
+#include "libc/x/x.h"
+#include "net/http/escape.h"
TEST(VisualizeControlCodes, test) {
- EXPECT_STREQ("hello", VisualizeControlCodes("hello", -1, 0));
- EXPECT_STREQ("hello\r\n", VisualizeControlCodes("hello\r\n", -1, 0));
- EXPECT_STREQ("hello␁␂␡", VisualizeControlCodes("hello\1\2\177", -1, 0));
- EXPECT_STREQ("hello\\u0085", VisualizeControlCodes("hello\302\205", -1, 0));
+ EXPECT_STREQ("hello", gc(VisualizeControlCodes("hello", -1, 0)));
+ EXPECT_STREQ("hello\r\n", gc(VisualizeControlCodes("hello\r\n", -1, 0)));
+ EXPECT_STREQ("hello␁␂␡", gc(VisualizeControlCodes("hello\1\2\177", -1, 0)));
+ EXPECT_STREQ("hello\\u0085",
+ gc(VisualizeControlCodes("hello\302\205", -1, 0)));
+}
+
+TEST(VisualizeControlCodes, testOom_returnsNullAndSetsSizeToZero) {
+ size_t n = 31337;
+ EXPECT_EQ(NULL, gc(VisualizeControlCodes("hello", 0x1000000000000, &n)));
+ EXPECT_EQ(0, n);
+}
+
+TEST(VisualizeControlCodes, testWeirdHttp) {
+ size_t n = 31337;
+ char *p, B[] = "\0GET /redbean.lua\n\n";
+ ASSERT_NE(0, (p = gc(VisualizeControlCodes(B, sizeof(B), &n))));
+ EXPECT_STREQ("\"␀GET /redbean.lua\\n\\n␀\"", gc(xasprintf("%`'.*s", n, p)));
}
BENCH(VisualizeControlCodes, bench) {
diff --git a/third_party/chibicc/as.c b/third_party/chibicc/as.c
index 8d59d04a6..88c430b49 100644
--- a/third_party/chibicc/as.c
+++ b/third_party/chibicc/as.c
@@ -16,6 +16,7 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/elf/def.h"
@@ -138,16 +139,7 @@
#define APPEND(L) L.p = realloc(L.p, ++L.n * sizeof(*L.p))
#define IS(P, N, S) (N == sizeof(S) - 1 && !strncasecmp(P, S, sizeof(S) - 1))
#define MAX(X, Y) ((Y) < (X) ? (X) : (Y))
-#define LOAD128BE(S) ((unsigned __int128)LOAD64BE(S) << 64 | LOAD64BE((S) + 8))
-#define LOAD64BE(S) \
- ((unsigned long)((unsigned char *)(S))[0] << 070 | \
- (unsigned long)((unsigned char *)(S))[1] << 060 | \
- (unsigned long)((unsigned char *)(S))[2] << 050 | \
- (unsigned long)((unsigned char *)(S))[3] << 040 | \
- (unsigned long)((unsigned char *)(S))[4] << 030 | \
- (unsigned long)((unsigned char *)(S))[5] << 020 | \
- (unsigned long)((unsigned char *)(S))[6] << 010 | \
- (unsigned long)((unsigned char *)(S))[7] << 000)
+#define READ128BE(S) ((unsigned __int128)READ64BE(S) << 64 | READ64BE((S) + 8))
struct As {
int i; // things
@@ -1911,13 +1903,13 @@ static void CopyLower(char *k, const char *p, int n) {
static unsigned long MakeKey64(const char *p, int n) {
char k[8] = {0};
CopyLower(k, p, n);
- return LOAD64BE(k);
+ return READ64BE(k);
}
static unsigned __int128 MakeKey128(const char *p, int n) {
char k[16] = {0};
CopyLower(k, p, n);
- return LOAD128BE(k);
+ return READ128BE(k);
}
static bool Prefix(struct As *a, const char *p, int n) {
@@ -1929,7 +1921,7 @@ static bool Prefix(struct As *a, const char *p, int n) {
r = ARRAYLEN(kPrefix) - 1;
while (l <= r) {
m = (l + r) >> 1;
- y = LOAD64BE(kPrefix[m]);
+ y = READ64BE(kPrefix[m]);
if (x < y) {
r = m - 1;
} else if (x > y) {
@@ -1954,7 +1946,7 @@ static bool FindReg(const char *p, int n, struct Reg *out_reg) {
r = ARRAYLEN(kRegs) - 1;
while (l <= r) {
m = (l + r) >> 1;
- y = LOAD64BE(kRegs[m].s);
+ y = READ64BE(kRegs[m].s);
if (x < y) {
r = m - 1;
} else if (x > y) {
@@ -3710,7 +3702,7 @@ static bool OnDirective8(struct As *a, struct Slice s) {
r = ARRAYLEN(kDirective8) - 1;
while (l <= r) {
m = (l + r) >> 1;
- y = LOAD64BE(kDirective8[m].s);
+ y = READ64BE(kDirective8[m].s);
if (x < y) {
r = m - 1;
} else if (x > y) {
@@ -3733,7 +3725,7 @@ static bool OnDirective16(struct As *a, struct Slice s) {
r = ARRAYLEN(kDirective16) - 1;
while (l <= r) {
m = (l + r) >> 1;
- y = LOAD128BE(kDirective16[m].s);
+ y = READ128BE(kDirective16[m].s);
if (x < y) {
r = m - 1;
} else if (x > y) {
diff --git a/third_party/chibicc/hashmap.c b/third_party/chibicc/hashmap.c
index 05ec2e775..90902ae08 100644
--- a/third_party/chibicc/hashmap.c
+++ b/third_party/chibicc/hashmap.c
@@ -26,7 +26,7 @@ static void rehash(HashMap *map) {
nkeys++;
}
}
- size_t cap = map->capacity;
+ int cap = MAX(8, map->capacity);
while ((nkeys * 100) / cap >= LOW_WATERMARK) cap = cap * 2;
assert(cap > 0);
// Create a new hashmap and copy all key-values.
diff --git a/third_party/dlmalloc/dlmalloc.c b/third_party/dlmalloc/dlmalloc.c
index 4bf1b9810..4b33edc6c 100644
--- a/third_party/dlmalloc/dlmalloc.c
+++ b/third_party/dlmalloc/dlmalloc.c
@@ -37,9 +37,10 @@ hidden struct MallocParams g_mparams;
*/
static void *dlmalloc_requires_more_vespene_gas(size_t size) {
char *p;
- p = mapanon(size);
- if (weaken(__asan_poison)) {
- weaken(__asan_poison)((uintptr_t)p, size, kAsanHeapFree);
+ if ((p = mapanon(size)) != MAP_FAILED) {
+ if (weaken(__asan_poison)) {
+ weaken(__asan_poison)((uintptr_t)p, size, kAsanHeapFree);
+ }
}
return p;
}
@@ -836,7 +837,7 @@ textstartup void dlmalloc_init(void) {
if (g_mparams.magic == 0) {
size_t magic;
size_t psize = PAGESIZE;
- size_t gsize = FRAMESIZE;
+ size_t gsize = DEFAULT_GRANULARITY;
/* Sanity-check configuration:
size_t must be unsigned and as wide as pointer type.
ints must be at least 4 bytes.
diff --git a/third_party/dlmalloc/dlmalloc.internal.h b/third_party/dlmalloc/dlmalloc.internal.h
index a88b8000d..5a3357dd1 100644
--- a/third_party/dlmalloc/dlmalloc.internal.h
+++ b/third_party/dlmalloc/dlmalloc.internal.h
@@ -907,7 +907,7 @@ extern struct MallocParams g_mparams;
#else /* GNUC */
#define RTCHECK(e) (e)
#endif /* GNUC */
-#else /* !IsTrustworthy() */
+#else /* !IsTrustworthy() */
#define RTCHECK(e) (1)
#endif /* !IsTrustworthy() */
@@ -1230,12 +1230,12 @@ forceinline msegmentptr segment_holding(mstate m, char *addr) {
#define check_malloc_state(M) do_check_malloc_state(M)
#endif /* DEBUG */
-void do_check_free_chunk(mstate m, mchunkptr p) hidden;
-void do_check_inuse_chunk(mstate m, mchunkptr p) hidden;
-void do_check_top_chunk(mstate m, mchunkptr p) hidden;
-void do_check_malloced_chunk(mstate m, void *mem, size_t s) hidden;
-void do_check_mmapped_chunk(mstate m, mchunkptr p) hidden;
-void do_check_malloc_state(mstate m) hidden;
+void do_check_free_chunk(mstate, mchunkptr) hidden;
+void do_check_inuse_chunk(mstate, mchunkptr) hidden;
+void do_check_top_chunk(mstate, mchunkptr) hidden;
+void do_check_malloced_chunk(mstate, void *, size_t) hidden;
+void do_check_mmapped_chunk(mstate, mchunkptr) hidden;
+void do_check_malloc_state(mstate) hidden;
/* ─────────────────────────── prototypes ──────────────────────────────── */
diff --git a/third_party/regex/regex.h b/third_party/regex/regex.h
index c2d2a6c4b..038511dd4 100644
--- a/third_party/regex/regex.h
+++ b/third_party/regex/regex.h
@@ -14,8 +14,8 @@ COSMOPOLITAN_C_START_
#define REG_NEWLINE 4
#define REG_NOSUB 8
-#define REG_NOTBOL 1
-#define REG_NOTEOL 2
+#define REG_NOTBOL 1 /* ^ should not match beginning of string */
+#define REG_NOTEOL 2 /* $ should not match end of string */
#define REG_OK 0
#define REG_NOMATCH 1
@@ -36,20 +36,20 @@ COSMOPOLITAN_C_START_
typedef long regoff_t;
-struct Regex {
+struct PosixRegex {
size_t re_nsub;
void *__opaque, *__padding[4];
size_t __nsub2;
char __padding2;
};
-struct RegexMatch {
+struct PosixRegexMatch {
regoff_t rm_so;
regoff_t rm_eo;
};
-typedef struct Regex regex_t;
-typedef struct RegexMatch regmatch_t;
+typedef struct PosixRegex regex_t;
+typedef struct PosixRegexMatch regmatch_t;
int regcomp(regex_t *, const char *, int);
int regexec(const regex_t *, const char *, size_t, regmatch_t *, int);
diff --git a/tool/build/lib/cvt.c b/tool/build/lib/cvt.c
index 7a85bb262..04f4373dc 100644
--- a/tool/build/lib/cvt.c
+++ b/tool/build/lib/cvt.c
@@ -18,6 +18,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.internal.h"
#include "libc/math.h"
+#include "libc/str/str.h"
#include "tool/build/lib/cvt.h"
#include "tool/build/lib/endian.h"
#include "tool/build/lib/machine.h"
diff --git a/tool/build/lib/endian.h b/tool/build/lib/endian.h
index da716f707..f23d1d39b 100644
--- a/tool/build/lib/endian.h
+++ b/tool/build/lib/endian.h
@@ -1,116 +1,53 @@
#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_ENDIAN_H_
#define COSMOPOLITAN_TOOL_BUILD_LIB_ENDIAN_H_
-#include "libc/dce.h"
-#include "libc/str/str.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-#if __BYTE_ORDER__ + 0 == 1234
+#include "libc/bits/bits.h"
-#define Read8(P) \
- ({ \
- uint8_t *Ptr = (P); \
- *Ptr; \
+#define Read8(P) (*(const uint8_t *)(P))
+
+#define Read16(P) \
+ ({ \
+ const uint8_t *Ptr = (const uint8_t *)(P); \
+ READ16LE(P); \
})
-#define Read16(P) \
- ({ \
- uint16_t Res; \
- uint8_t *Ptr = (P); \
- memcpy(&Res, Ptr, 2); \
- Res; \
+#define Read32(P) \
+ ({ \
+ const uint8_t *Ptr = (const uint8_t *)(P); \
+ READ32LE(P); \
})
-#define Read32(P) \
- ({ \
- uint32_t Res; \
- uint8_t *Ptr = (P); \
- memcpy(&Res, Ptr, 4); \
- Res; \
+#define Read64(P) \
+ ({ \
+ const uint8_t *Ptr = (const uint8_t *)(P); \
+ READ64LE(P); \
})
-#define Read64(P) \
- ({ \
- uint64_t Res; \
- uint8_t *Ptr = (P); \
- memcpy(&Res, Ptr, 8); \
- Res; \
- })
-
-#define Write8(P, B) \
+#define Write8(P, V) \
do { \
+ uint8_t Val = (V); \
uint8_t *Ptr = (P); \
- *Ptr = (B); \
+ *Ptr = Val; \
} while (0)
-#define Write16(P, V) \
- do { \
- uint16_t Val = (V); \
- uint8_t *Ptr = (P); \
- memcpy(Ptr, &Val, 2); \
+#define Write16(P, V) \
+ do { \
+ uint16_t Val = (V); \
+ uint8_t *Ptr = (P); \
+ WRITE16LE(Ptr, Val); \
} while (0)
-#define Write32(P, V) \
- do { \
- uint32_t Val = (V); \
- uint8_t *Ptr = (P); \
- memcpy(Ptr, &Val, 4); \
+#define Write32(P, V) \
+ do { \
+ uint32_t Val = (V); \
+ uint8_t *Ptr = (P); \
+ WRITE32LE(Ptr, Val); \
} while (0)
-#define Write64(P, V) \
- do { \
- uint64_t Val = (V); \
- uint8_t *Ptr = (P); \
- memcpy(Ptr, &Val, 8); \
+#define Write64(P, V) \
+ do { \
+ uint64_t Val = (V); \
+ uint8_t *Ptr = (P); \
+ WRITE64LE(Ptr, Val); \
} while (0)
-#else
-
-forceinline uint16_t Read8(const uint8_t p[hasatleast 1]) {
- return p[0];
-}
-
-forceinline uint16_t Read16(const uint8_t p[hasatleast 2]) {
- return p[0] | p[1] << 010;
-}
-
-forceinline uint32_t Read32(const uint8_t bytes[hasatleast 4]) {
- return (uint32_t)bytes[0] << 000 | (uint32_t)bytes[1] << 010 |
- (uint32_t)bytes[2] << 020 | (uint32_t)bytes[3] << 030;
-}
-
-forceinline uint64_t Read64(const uint8_t bytes[hasatleast 8]) {
- return (uint64_t)bytes[0] << 000 | (uint64_t)bytes[1] << 010 |
- (uint64_t)bytes[2] << 020 | (uint64_t)bytes[3] << 030 |
- (uint64_t)bytes[4] << 040 | (uint64_t)bytes[5] << 050 |
- (uint64_t)bytes[6] << 060 | (uint64_t)bytes[7] << 070;
-}
-
-forceinline void Write8(unsigned char p[hasatleast 1], uint8_t x) {
- p[0] = x >> 000;
-}
-
-forceinline void Write16(unsigned char p[hasatleast 2], uint16_t x) {
- p[0] = x >> 000;
- p[1] = x >> 010;
-}
-
-forceinline void Write32(unsigned char p[hasatleast 4], uint64_t x) {
- p[0] = x >> 000;
- p[1] = x >> 010;
- p[2] = x >> 020;
- p[3] = x >> 030;
-}
-
-forceinline void Write64(unsigned char p[hasatleast 8], uint64_t x) {
- p[0] = x >> 000;
- p[1] = x >> 010;
- p[2] = x >> 020;
- p[3] = x >> 030;
- p[4] = x >> 040;
- p[5] = x >> 050;
- p[6] = x >> 060;
- p[7] = x >> 070;
-}
-
-#endif /* ENDIAN */
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_ENDIAN_H_ */
diff --git a/tool/build/lib/machine.c b/tool/build/lib/machine.c
index c6478ce4b..72a42522a 100644
--- a/tool/build/lib/machine.c
+++ b/tool/build/lib/machine.c
@@ -20,6 +20,7 @@
#include "libc/macros.internal.h"
#include "libc/rand/rand.h"
#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
#include "tool/build/lib/abp.h"
#include "tool/build/lib/address.h"
#include "tool/build/lib/alu.h"
@@ -1568,7 +1569,11 @@ static void Op1ae(struct Machine *m, uint32_t rde) {
}
static void OpSalc(struct Machine *m, uint32_t rde) {
- Write8(m->ax, GetFlag(m->flags, FLAGS_CF));
+ if (GetFlag(m->flags, FLAGS_CF)) {
+ m->ax[0] = 255;
+ } else {
+ m->ax[0] = 0;
+ }
}
static void OpBofram(struct Machine *m, uint32_t rde) {
diff --git a/tool/build/lib/stack.c b/tool/build/lib/stack.c
index 0ebe775dc..b646183a5 100644
--- a/tool/build/lib/stack.c
+++ b/tool/build/lib/stack.c
@@ -18,6 +18,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/log/check.h"
#include "libc/macros.internal.h"
+#include "libc/str/str.h"
#include "tool/build/lib/address.h"
#include "tool/build/lib/endian.h"
#include "tool/build/lib/memory.h"
diff --git a/tool/build/lib/syscall.c b/tool/build/lib/syscall.c
index e22a07d0e..9ffd239f8 100644
--- a/tool/build/lib/syscall.c
+++ b/tool/build/lib/syscall.c
@@ -77,6 +77,8 @@
#include "tool/build/lib/throw.h"
#include "tool/build/lib/xlaterrno.h"
+#define SA_RESTORER 0x04000000
+
#define AT_FDCWD_LINUX -100
#define TIOCGWINSZ_LINUX 0x5413
#define TCGETS_LINUX 0x5401
diff --git a/tool/build/lib/word.c b/tool/build/lib/word.c
index f77df94b1..fd5d0ab8e 100644
--- a/tool/build/lib/word.c
+++ b/tool/build/lib/word.c
@@ -16,6 +16,7 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/str/str.h"
#include "tool/build/lib/endian.h"
#include "tool/build/lib/memory.h"
#include "tool/build/lib/word.h"
diff --git a/tool/build/package.c b/tool/build/package.c
index b9d1bbe49..a6c1f8184 100644
--- a/tool/build/package.c
+++ b/tool/build/package.c
@@ -25,6 +25,7 @@
#include "libc/bits/safemacros.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
+#include "libc/dce.h"
#include "libc/elf/def.h"
#include "libc/elf/elf.h"
#include "libc/elf/struct/rela.h"
@@ -165,8 +166,8 @@ struct Packages {
};
int CompareSymbolName(const struct Symbol *a, const struct Symbol *b,
- const char *strs[hasatleast 2]) {
- return strcmp(&strs[0][a->name], &strs[1][b->name]);
+ const char *tab) {
+ return strcmp(tab + a->name, tab + b->name);
}
struct Package *LoadPackage(const char *path) {
@@ -189,6 +190,7 @@ struct Package *LoadPackage(const char *path) {
pkg->strings.p);
pkg->addr = pkg;
pkg->size = st.st_size;
+ CHECK_NE(-1, mprotect(pkg, st.st_size, PROT_READ));
return pkg;
}
@@ -378,37 +380,48 @@ void LoadObjects(struct Package *pkg) {
size_t i;
struct Object *obj;
for (i = 0; i < pkg->objects.i; ++i) {
- obj = &pkg->objects.p[i];
+ obj = pkg->objects.p + i;
OpenObject(pkg, obj, O_RDONLY, PROT_READ, MAP_SHARED);
LoadSymbols(pkg, i);
CloseObject(obj);
}
- qsort_r(&pkg->symbols.p[0], pkg->symbols.i, sizeof(pkg->symbols.p[0]),
- (void *)CompareSymbolName,
- (const char *[2]){pkg->strings.p, pkg->strings.p});
+ qsort_r(pkg->symbols.p, pkg->symbols.i, sizeof(*pkg->symbols.p),
+ (void *)CompareSymbolName, pkg->strings.p);
+}
+
+struct Symbol *BisectSymbol(struct Package *pkg, const char *name) {
+ int c;
+ long m, l, r;
+ l = 0;
+ r = pkg->symbols.i - 1;
+ while (l <= r) {
+ m = (l + r) >> 1;
+ c = strcmp(pkg->strings.p + pkg->symbols.p[m].name, name);
+ if (c < 0) {
+ l = m + 1;
+ } else if (c > 0) {
+ r = m - 1;
+ } else {
+ return pkg->symbols.p + m;
+ }
+ }
+ return NULL;
}
bool FindSymbol(const char *name, struct Package *pkg,
struct Packages *directdeps, struct Package **out_pkg,
struct Symbol **out_sym) {
- size_t i;
- struct Package *dep;
- struct Symbol key, *sym;
- key.name = 0;
- if ((sym = bisect(&key, &pkg->symbols.p[0], pkg->symbols.i,
- sizeof(pkg->symbols.p[0]), (void *)CompareSymbolName,
- (const char *[2]){name, pkg->strings.p}))) {
- if (out_pkg) *out_pkg = pkg;
+ size_t i, j;
+ struct Symbol *sym;
+ if ((sym = BisectSymbol(pkg, name))) {
if (out_sym) *out_sym = sym;
+ if (out_pkg) *out_pkg = pkg;
return true;
}
for (i = 0; i < directdeps->i; ++i) {
- dep = directdeps->p[i];
- if ((sym = bisect(&key, &dep->symbols.p[0], dep->symbols.i,
- sizeof(dep->symbols.p[0]), (void *)CompareSymbolName,
- (const char *[2]){name, dep->strings.p}))) {
- if (out_pkg) *out_pkg = dep;
+ if ((sym = BisectSymbol(directdeps->p[i], name))) {
if (out_sym) *out_sym = sym;
+ if (out_pkg) *out_pkg = directdeps->p[i];
return true;
}
}
@@ -422,15 +435,15 @@ void CheckStrictDeps(struct Package *pkg, struct Packages *deps) {
for (i = 0; i < pkg->undefs.i; ++i) {
undef = &pkg->undefs.p[i];
if (undef->bind == STB_WEAK) continue;
- if (!FindSymbol(&pkg->strings.p[undef->name], pkg, deps, NULL, NULL)) {
- fprintf(stderr, "%s: %s (%s) %s %s\n", "error",
- &pkg->strings.p[undef->name],
- &pkg->strings.p[pkg->objects.p[undef->object].path],
- "not defined by direct deps of", &pkg->strings.p[pkg->path]);
+ if (!FindSymbol(pkg->strings.p + undef->name, pkg, deps, NULL, NULL)) {
+ fprintf(stderr, "%s: %`'s (%s) %s %s\n", "error",
+ pkg->strings.p + undef->name,
+ pkg->strings.p + pkg->objects.p[undef->object].path,
+ "not defined by direct deps of", pkg->strings.p + pkg->path);
for (j = 0; j < deps->i; ++j) {
dep = deps->p[j];
fputc('\t', stderr);
- fputs(&dep->strings.p[dep->path], stderr);
+ fputs(dep->strings.p + dep->path, stderr);
fputc('\n', stderr);
}
exit(1);
diff --git a/tool/build/zipobj.c b/tool/build/zipobj.c
index 5804775c6..3b6f21186 100644
--- a/tool/build/zipobj.c
+++ b/tool/build/zipobj.c
@@ -55,16 +55,6 @@
#define ZIP_LOCALFILE_SECTION ".zip.2."
#define ZIP_DIRECTORY_SECTION ".zip.4."
-#define PUT8(P, V) *P++ = V
-#define PUT16(P, V) P[0] = V & 0xff, P[1] = V >> 010 & 0xff, P += 2
-#define PUT32(P, V) \
- P[0] = V & 0xff, P[1] = V >> 010 & 0xff, P[2] = V >> 020 & 0xff, \
- P[3] = V >> 030 & 0xff, P += 4
-#define PUT64(P, V) \
- P[0] = V & 0xff, P[1] = V >> 010 & 0xff, P[2] = V >> 020 & 0xff, \
- P[3] = V >> 030 & 0xff, P[4] = V >> 040 & 0xff, P[5] = V >> 050 & 0xff, \
- P[6] = V >> 060 & 0xff, P[7] = V >> 070 & 0xff, P += 8
-
#define DOS_DATE(YEAR, MONTH_IDX1, DAY_IDX1) \
(((YEAR)-1980) << 9 | (MONTH_IDX1) << 5 | (DAY_IDX1))
#define DOS_TIME(HOUR, MINUTE, SECOND) \
@@ -116,10 +106,24 @@ void GetOpts(int *argc, char ***argv) {
CHECK_NOTNULL(outpath_);
}
-bool IsPureAscii(const void *data, size_t size) {
+bool IsUtf8(const void *data, size_t size) {
+ const unsigned char *p, *pe;
+ for (p = data, pe = p + size; p + 2 <= pe; ++p) {
+ if (*p >= 0300) {
+ if (*p >= 0200 && *p < 0300) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+bool IsText(const void *data, size_t size) {
const unsigned char *p, *pe;
for (p = data, pe = p + size; p < pe; ++p) {
- if (!*p || *p >= 0x80) {
+ if (*p <= 3) {
return false;
}
}
@@ -146,86 +150,81 @@ void GetDosLocalTime(int64_t utcunixts, uint16_t *out_time,
*out_date = DOS_DATE(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday + 1);
}
-static unsigned char *EmitZipLfileHdr(unsigned char *op, const void *name,
+static int DetermineVersionNeededToExtract(int method) {
+ if (method == kZipCompressionDeflate) {
+ return kZipEra1993;
+ } else {
+ return kZipEra1989;
+ }
+}
+
+static unsigned char *EmitZipLfileHdr(unsigned char *p, const void *name,
size_t namesize, uint32_t crc,
uint8_t era, uint16_t gflags,
uint16_t method, uint16_t mtime,
uint16_t mdate, size_t compsize,
size_t uncompsize) {
- PUT32(op, kZipLfileHdrMagic);
- PUT8(op, kZipEra1993);
- PUT8(op, kZipOsDos);
- PUT16(op, gflags);
- PUT16(op, method);
- PUT16(op, mtime);
- PUT16(op, mdate);
- PUT32(op, crc);
- PUT32(op, compsize);
- PUT32(op, uncompsize);
- PUT16(op, namesize);
- PUT16(op, 0); /* extra */
- return mempcpy(op, name, namesize);
+ p = WRITE32LE(p, kZipLfileHdrMagic);
+ *p++ = era;
+ *p++ = kZipOsDos;
+ p = WRITE16LE(p, gflags);
+ p = WRITE16LE(p, method);
+ p = WRITE16LE(p, mtime);
+ p = WRITE16LE(p, mdate);
+ p = WRITE32LE(p, crc);
+ p = WRITE32LE(p, compsize);
+ p = WRITE32LE(p, uncompsize);
+ p = WRITE16LE(p, namesize);
+ p = WRITE16LE(p, 0); /* extra */
+ return mempcpy(p, name, namesize);
}
-static void EmitZipCdirHdr(unsigned char *op, const void *name, size_t namesize,
+static void EmitZipCdirHdr(unsigned char *p, const void *name, size_t namesize,
uint32_t crc, uint8_t era, uint16_t gflags,
uint16_t method, uint16_t mtime, uint16_t mdate,
uint16_t iattrs, uint16_t dosmode, uint16_t unixmode,
size_t compsize, size_t uncompsize,
size_t commentsize, struct stat *st) {
uint64_t mt, at, ct;
- PUT32(op, kZipCfileHdrMagic);
- PUT8(op, 20);
- PUT8(op, kZipOsDos);
- PUT8(op, kZipEra1993);
- PUT8(op, kZipOsDos);
- PUT16(op, gflags);
- PUT16(op, method);
- PUT16(op, mtime);
- PUT16(op, mdate);
+ p = WRITE32LE(p, kZipCfileHdrMagic);
+ *p++ = kZipCosmopolitanVersion;
+ *p++ = kZipOsUnix;
+ *p++ = era;
+ *p++ = kZipOsDos;
+ p = WRITE16LE(p, gflags);
+ p = WRITE16LE(p, method);
+ p = WRITE16LE(p, mtime);
+ p = WRITE16LE(p, mdate);
/* 16 */
- PUT32(op, crc);
- PUT32(op, compsize);
- PUT32(op, uncompsize);
- PUT16(op, namesize);
-#if 0
-#define CFILE_HDR_SIZE kZipCfileHdrMinSize
- PUT16(op, 0); /* extra size */
- /* 32 */
- PUT16(op, commentsize);
- PUT16(op, 0); /* disk */
- PUT16(op, iattrs);
- PUT16(op, dosmode);
- PUT16(op, unixmode);
- PUT32(op, 0); /* RELOCATE ME (kZipCfileOffsetOffset) */
- /* 46 */
- memcpy(op, name, namesize);
-#else
+ p = WRITE32LE(p, crc);
+ p = WRITE32LE(p, compsize);
+ p = WRITE32LE(p, uncompsize);
+ p = WRITE16LE(p, namesize);
#define CFILE_HDR_SIZE (kZipCfileHdrMinSize + 36)
- PUT16(op, 36); /* extra size */
+ p = WRITE16LE(p, 36); /* extra size */
/* 32 */
- PUT16(op, commentsize);
- PUT16(op, 0); /* disk */
- PUT16(op, iattrs);
- PUT32(op, dosmode);
- PUT32(op, 0); /* RELOCATE ME (kZipCfileOffsetOffset) */
+ p = WRITE16LE(p, commentsize);
+ p = WRITE16LE(p, 0); /* disk */
+ p = WRITE16LE(p, iattrs);
+ p = WRITE16LE(p, dosmode);
+ p = WRITE16LE(p, unixmode);
+ p = WRITE32LE(p, 0); /* RELOCATE ME (kZipCfileOffsetOffset) */
/* 46 */
- memcpy(op, name, namesize);
- op += namesize;
- PUT16(op, kZipExtraNtfs);
- PUT16(op, 32);
- PUT32(op, 0);
- PUT16(op, 1);
- PUT16(op, 24);
+ memcpy(p, name, namesize);
+ p += namesize;
+ p = WRITE16LE(p, kZipExtraNtfs);
+ p = WRITE16LE(p, 32);
+ p = WRITE32LE(p, 0);
+ p = WRITE16LE(p, 1);
+ p = WRITE16LE(p, 24);
#define NTTIME(t) \
(t.tv_sec + MODERNITYSECONDS) * HECTONANOSECONDS + t.tv_nsec / 100
mt = NTTIME(st->st_mtim);
at = NTTIME(st->st_atim);
ct = NTTIME(st->st_ctim);
- PUT64(op, mt);
- PUT64(op, at);
- PUT64(op, ct);
-#endif
+ p = WRITE64LE(p, mt);
+ p = WRITE64LE(p, at);
+ p = WRITE64LE(p, ct);
}
void EmitZip(struct ElfWriter *elf, const char *name, size_t namesize,
@@ -238,15 +237,17 @@ void EmitZip(struct ElfWriter *elf, const char *name, size_t namesize,
size_t lfilehdrsize, uncompsize, compsize, commentsize;
uint16_t method, gflags, mtime, mdate, iattrs, dosmode;
+ gflags = 0;
+ iattrs = 0;
compsize = st->st_size;
uncompsize = st->st_size;
CHECK_LE(uncompsize, UINT32_MAX);
lfilehdrsize = kZipLfileHdrMinSize + namesize;
crc = crc32_z(0, data, uncompsize);
GetDosLocalTime(st->st_mtim.tv_sec, &mtime, &mdate);
- gflags = IsPureAscii(name, namesize) ? 0 : kZipGflagUtf8;
+ if (IsUtf8(name, namesize)) gflags |= kZipGflagUtf8;
+ if (IsText(data, st->st_size)) iattrs |= kZipIattrText;
commentsize = kZipCdirHdrLinkableSize - (CFILE_HDR_SIZE + namesize);
- iattrs = IsPureAscii(data, st->st_size) ? kZipIattrAscii : kZipIattrBinary;
dosmode = !(st->st_mode & 0200) ? kNtFileAttributeReadonly : 0;
method = (st->st_size >= kMinCompressSize && ShouldCompress(name, namesize))
? kZipCompressionDeflate
@@ -280,7 +281,7 @@ void EmitZip(struct ElfWriter *elf, const char *name, size_t namesize,
if (method == kZipCompressionNone) {
memcpy(lfile + lfilehdrsize, data, uncompsize);
}
- era = (gflags || method) ? kZipEra1993 : kZipEra1989;
+ era = method ? kZipEra1993 : kZipEra1989;
EmitZipLfileHdr(lfile, name, namesize, crc, era, gflags, method, mtime, mdate,
compsize, uncompsize);
elfwriter_commit(elf, lfilehdrsize + compsize);
diff --git a/tool/decode/lib/zipnames.c b/tool/decode/lib/zipnames.c
index 23bb65d28..39f028f7d 100644
--- a/tool/decode/lib/zipnames.c
+++ b/tool/decode/lib/zipnames.c
@@ -29,13 +29,15 @@ const struct IdName kZipCompressionNames[] = {
const struct IdName kZipExtraNames[] = {
{kZipExtraZip64, "kZipExtraZip64"},
{kZipExtraNtfs, "kZipExtraNtfs"},
+ {kZipExtraUnix, "kZipExtraUnix"},
{kZipExtraExtendedTimestamp, "kZipExtraExtendedTimestamp"},
+ {kZipExtraInfoZipNewUnixExtra, "kZipExtraInfoZipNewUnixExtra"},
{0, 0},
};
const struct IdName kZipIattrNames[] = {
{kZipIattrBinary, "kZipIattrBinary"},
- {kZipIattrAscii, "kZipIattrAscii"},
+ {kZipIattrText, "kZipIattrText"},
{0, 0},
};
diff --git a/tool/decode/zip.c b/tool/decode/zip.c
index 3ee9bcb1c..151f74bd6 100644
--- a/tool/decode/zip.c
+++ b/tool/decode/zip.c
@@ -22,6 +22,7 @@
#include "libc/calls/struct/stat.h"
#include "libc/fmt/conv.h"
#include "libc/log/check.h"
+#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/crc32.h"
#include "libc/nt/struct/filetime.h"
@@ -45,17 +46,17 @@
* @fileoverview Zip File Disassembler.
*/
-nodiscard char *formatdosdate(uint16_t dosdate) {
+nodiscard char *FormatDosDate(uint16_t dosdate) {
return xasprintf("%04u-%02u-%02u", ((dosdate >> 9) & 0b1111111) + 1980,
(dosdate >> 5) & 0b1111, dosdate & 0b11111);
}
-nodiscard char *formatdostime(uint16_t dostime) {
+nodiscard char *FormatDosTime(uint16_t dostime) {
return xasprintf("%02u:%02u:%02u", (dostime >> 11) & 0b11111,
(dostime >> 5) & 0b111111, (dostime << 1) & 0b111110);
}
-void advancepos(uint8_t *map, size_t *pos, size_t off) {
+void AdvancePosition(uint8_t *map, size_t *pos, size_t off) {
CHECK_GE(off, *pos);
if (off > *pos) {
printf("\n/\t<%s>\n", "LIMBO");
@@ -65,7 +66,7 @@ void advancepos(uint8_t *map, size_t *pos, size_t off) {
*pos = off;
}
-void showgeneralflag(uint16_t generalflag) {
+void ShowGeneralFlag(uint16_t generalflag) {
puts("\
/ ┌─utf8\n\
/ │ ┌─strong encryption\n\
@@ -77,21 +78,21 @@ void showgeneralflag(uint16_t generalflag) {
show(".short", format(b1, "0b%016b", generalflag), "generalflag");
}
-void showtimestamp(uint16_t time, uint16_t date) {
+void ShowTimestamp(uint16_t time, uint16_t date) {
show(".short", format(b1, "%#04hx", time),
- gc(xasprintf("%s (%s)", "lastmodifiedtime", gc(formatdostime(time)))));
+ gc(xasprintf("%s (%s)", "lastmodifiedtime", gc(FormatDosTime(time)))));
show(".short", format(b1, "%#04hx", date),
- gc(xasprintf("%s (%s)", "lastmodifieddate", gc(formatdosdate(date)))));
+ gc(xasprintf("%s (%s)", "lastmodifieddate", gc(FormatDosDate(date)))));
}
-void showcompressmethod(uint16_t compressmethod) {
+void ShowCompressionMethod(uint16_t compressmethod) {
show(".short",
firstnonnull(findnamebyid(kZipCompressionNames, compressmethod),
format(b1, "%hu", compressmethod)),
"compressionmethod");
}
-void showextrantfs(uint8_t *ntfs) {
+void ShowNtfs(uint8_t *ntfs, size_t n) {
struct timespec mtime, atime, ctime;
mtime = FileTimeToTimeSpec(
(struct NtFileTime){READ32LE(ntfs + 8), READ32LE(ntfs + 12)});
@@ -115,47 +116,85 @@ void showextrantfs(uint8_t *ntfs) {
void ShowExtendedTimestamp(uint8_t *p, size_t n, bool islocal) {
int flag;
- int64_t x;
- struct timespec ts;
- flag = *p++;
- show(".byte", gc(xasprintf("0b%03hhb", flag)), "fields present in local");
- if (!islocal) {
- show(".quad", gc(xasprintf("%u", READ32LE(p))),
- gc(xasprintf("%s (%s)", "last modified", gc(xiso8601(&ts)))));
- } else {
- if (flag & 1) {
- ts = (struct timespec){READ32LE(p)};
- show(".quad", gc(xasprintf("%u", READ32LE(p))),
- gc(xasprintf("%s (%s)", "last modified", gc(xiso8601(&ts)))));
+ if (n) {
+ --n;
+ flag = *p++;
+ show(".byte", gc(xasprintf("0b%03hhb", flag)), "fields present in local");
+ if ((flag & 1) && n >= 4) {
+ show(".long", gc(xasprintf("%u", READ32LE(p))),
+ gc(xasprintf("%s (%s)", "last modified",
+ gc(xiso8601(&(struct timespec){READ32LE(p)})))));
p += 4;
+ n -= 4;
}
flag >>= 1;
- if (flag & 1) {
- ts = (struct timespec){READ32LE(p)};
- show(".quad", gc(xasprintf("%u", READ32LE(p))),
- gc(xasprintf("%s (%s)", "access time", gc(xiso8601(&ts)))));
- p += 4;
- }
- flag >>= 1;
- if (flag & 1) {
- ts = (struct timespec){READ32LE(p)};
- show(".quad", gc(xasprintf("%u", READ32LE(p))),
- gc(xasprintf("%s (%s)", "creation time", gc(xiso8601(&ts)))));
+ if (islocal) {
+ if ((flag & 1) && n >= 4) {
+ show(".long", gc(xasprintf("%u", READ32LE(p))),
+ gc(xasprintf("%s (%s)", "access time",
+ gc(xiso8601(&(struct timespec){READ32LE(p)})))));
+ p += 4;
+ n -= 4;
+ }
+ flag >>= 1;
+ if ((flag & 1) && n >= 4) {
+ show(".long", gc(xasprintf("%u", READ32LE(p))),
+ gc(xasprintf("%s (%s)", "creation time",
+ gc(xiso8601(&(struct timespec){READ32LE(p)})))));
+ p += 4;
+ n -= 4;
+ }
}
}
}
-void showextra(uint8_t *extra, bool islocal) {
+void ShowZip64(uint8_t *p, size_t n, bool islocal) {
+ if (n >= 8) {
+ show(".quad", gc(xasprintf("%lu", READ64LE(p))),
+ gc(xasprintf("uncompressed size (%,ld)", READ64LE(p))));
+ }
+ if (n >= 16) {
+ show(".quad", gc(xasprintf("%lu", READ64LE(p + 8))),
+ gc(xasprintf("compressed size (%,ld)", READ64LE(p + 8))));
+ }
+ if (n >= 24) {
+ show(".quad", gc(xasprintf("%lu", READ64LE(p + 16))),
+ gc(xasprintf("lfile hdr offset (%,ld)", READ64LE(p + 16))));
+ }
+ if (n >= 28) {
+ show(".long", gc(xasprintf("%u", READ32LE(p + 24))), "disk number");
+ }
+}
+
+void ShowInfoZipNewUnixExtra(uint8_t *p, size_t n, bool islocal) {
+ if (p[0] == 1 && p[1] == 4 && p[6] == 4) {
+ show(".byte", "1", "version");
+ show(".byte", "4", "uid length");
+ show(".long", gc(xasprintf("%u", READ32LE(p + 2))), "uid");
+ show(".byte", "4", "gid length");
+ show(".long", gc(xasprintf("%u", READ32LE(p + 7))), "gid");
+ } else {
+ disassemblehex(p, n, stdout);
+ }
+}
+
+void ShowExtra(uint8_t *extra, bool islocal) {
switch (ZIP_EXTRA_HEADERID(extra)) {
case kZipExtraNtfs:
- showextrantfs(ZIP_EXTRA_CONTENT(extra));
+ ShowNtfs(ZIP_EXTRA_CONTENT(extra), ZIP_EXTRA_CONTENTSIZE(extra));
break;
case kZipExtraExtendedTimestamp:
ShowExtendedTimestamp(ZIP_EXTRA_CONTENT(extra),
ZIP_EXTRA_CONTENTSIZE(extra), islocal);
break;
case kZipExtraZip64:
- /* TODO */
+ ShowZip64(ZIP_EXTRA_CONTENT(extra), ZIP_EXTRA_CONTENTSIZE(extra),
+ islocal);
+ break;
+ case kZipExtraInfoZipNewUnixExtra:
+ ShowInfoZipNewUnixExtra(ZIP_EXTRA_CONTENT(extra),
+ ZIP_EXTRA_CONTENTSIZE(extra), islocal);
+ break;
default:
disassemblehex(ZIP_EXTRA_CONTENT(extra), ZIP_EXTRA_CONTENTSIZE(extra),
stdout);
@@ -163,7 +202,7 @@ void showextra(uint8_t *extra, bool islocal) {
}
}
-void showexternalattributes(uint8_t *cf) {
+void ShowExternalAttributes(uint8_t *cf) {
uint32_t ea;
ea = ZIP_CFILE_EXTERNALATTRIBUTES(cf);
if (ZIP_CFILE_FILEATTRCOMPAT(cf) == kZipOsUnix) {
@@ -175,7 +214,7 @@ void showexternalattributes(uint8_t *cf) {
}
}
-void showextras(uint8_t *extras, uint16_t extrassize, bool islocal) {
+void ShowExtras(uint8_t *extras, uint16_t extrassize, bool islocal) {
int i;
bool first;
uint8_t *p, *pe;
@@ -194,14 +233,14 @@ void showextras(uint8_t *extras, uint16_t extrassize, bool islocal) {
first = false;
printf("%d:", (i + 1) * 10);
}
- showextra(p, islocal);
+ ShowExtra(p, islocal);
printf("%d:", (i + 2) * 10);
}
}
putchar('\n');
}
-void showlocalfileheader(uint8_t *lf, uint16_t idx) {
+void ShowLocalFileHeader(uint8_t *lf, uint16_t idx) {
printf("\n/\t%s #%hu (%zu %s)\n", "local file", idx + 1,
ZIP_LFILE_HDRSIZE(lf), "bytes");
show(".ascii", format(b1, "%`'.*s", 4, lf), "magic");
@@ -213,17 +252,23 @@ void showlocalfileheader(uint8_t *lf, uint16_t idx) {
firstnonnull(findnamebyid(kZipOsNames, ZIP_LFILE_OSNEED(lf)),
gc(xasprintf("%d", ZIP_LFILE_OSNEED(lf)))),
"os need");
- showgeneralflag(ZIP_LFILE_GENERALFLAG(lf));
- showcompressmethod(ZIP_LFILE_COMPRESSIONMETHOD(lf));
- showtimestamp(ZIP_LFILE_LASTMODIFIEDTIME(lf), ZIP_LFILE_LASTMODIFIEDDATE(lf));
- show(".long", format(b1, "%#x", ZIP_LFILE_CRC32(lf)),
- gc(xasprintf(
- "%s (%#x)", "crc32z",
- crc32_z(0, ZIP_LFILE_CONTENT(lf), ZIP_LFILE_COMPRESSEDSIZE(lf)))));
- show(".long", "3f-2f",
- format(b1, "%s (%u %s)", "compressedsize", ZIP_LFILE_COMPRESSEDSIZE(lf),
- "bytes"));
- show(".long", format(b1, "%u", ZIP_LFILE_UNCOMPRESSEDSIZE(lf)),
+ ShowGeneralFlag(ZIP_LFILE_GENERALFLAG(lf));
+ ShowCompressionMethod(ZIP_LFILE_COMPRESSIONMETHOD(lf));
+ ShowTimestamp(ZIP_LFILE_LASTMODIFIEDTIME(lf), ZIP_LFILE_LASTMODIFIEDDATE(lf));
+ show(
+ ".long",
+ format(b1, "%#x", ZIP_LFILE_CRC32(lf)), gc(xasprintf("%s (%#x)", "crc32z", GetZipLfileCompressedSize(lf) /* crc32_z(0, ZIP_LFILE_CONTENT(lf), GetZipLfileCompressedSize(lf)) */)));
+ if (ZIP_LFILE_COMPRESSEDSIZE(lf) == 0xFFFFFFFF) {
+ show(".long", "0xFFFFFFFF", "compressedsize (zip64)");
+ } else {
+ show(".long", "3f-2f",
+ format(b1, "%s (%u %s)", "compressedsize",
+ ZIP_LFILE_COMPRESSEDSIZE(lf), "bytes"));
+ }
+ show(".long",
+ ZIP_LFILE_UNCOMPRESSEDSIZE(lf) == 0xFFFFFFFF
+ ? "0xFFFFFFFF"
+ : format(b1, "%u", ZIP_LFILE_UNCOMPRESSEDSIZE(lf)),
"uncompressedsize");
show(".short", "1f-0f",
format(b1, "%s (%hu %s)", "namesize", ZIP_LFILE_NAMESIZE(lf), "bytes"));
@@ -236,17 +281,19 @@ void showlocalfileheader(uint8_t *lf, uint16_t idx) {
gc(strndup(ZIP_LFILE_NAME(lf), ZIP_LFILE_NAMESIZE(lf)))),
"name");
printf("1:");
- showextras(ZIP_LFILE_EXTRA(lf), ZIP_LFILE_EXTRASIZE(lf), true);
+ ShowExtras(ZIP_LFILE_EXTRA(lf), ZIP_LFILE_EXTRASIZE(lf), true);
printf("2:");
- disassemblehex(ZIP_LFILE_CONTENT(lf), ZIP_LFILE_COMPRESSEDSIZE(lf), stdout);
+ /* disassemblehex(ZIP_LFILE_CONTENT(lf), ZIP_LFILE_COMPRESSEDSIZE(lf),
+ * stdout); */
printf("3:\n");
}
-void showcentralfileheader(uint8_t *cf) {
+void ShowCentralFileHeader(uint8_t *cf) {
printf("\n/\t%s (%zu %s)\n", "central directory file header",
ZIP_CFILE_HDRSIZE(cf), "bytes");
show(".ascii", format(b1, "%`'.*s", 4, cf), "magic");
- show(".byte", gc(xasprintf("%d", ZIP_CFILE_VERSIONMADE(cf))), "version made");
+ show(".byte", gc(xasprintf("%d", ZIP_CFILE_VERSIONMADE(cf))),
+ "zip version made");
show(".byte",
firstnonnull(findnamebyid(kZipOsNames, ZIP_CFILE_FILEATTRCOMPAT(cf)),
gc(xasprintf("%d", ZIP_CFILE_FILEATTRCOMPAT(cf)))),
@@ -259,14 +306,22 @@ void showcentralfileheader(uint8_t *cf) {
firstnonnull(findnamebyid(kZipOsNames, ZIP_CFILE_OSNEED(cf)),
gc(xasprintf("%d", ZIP_CFILE_OSNEED(cf)))),
"os need");
- showgeneralflag(ZIP_CFILE_GENERALFLAG(cf));
- showcompressmethod(ZIP_CFILE_COMPRESSIONMETHOD(cf));
- showtimestamp(ZIP_CFILE_LASTMODIFIEDTIME(cf), ZIP_CFILE_LASTMODIFIEDDATE(cf));
+ ShowGeneralFlag(ZIP_CFILE_GENERALFLAG(cf));
+ ShowCompressionMethod(ZIP_CFILE_COMPRESSIONMETHOD(cf));
+ ShowTimestamp(ZIP_CFILE_LASTMODIFIEDTIME(cf), ZIP_CFILE_LASTMODIFIEDDATE(cf));
show(".long", format(b1, "%#x", ZIP_CFILE_CRC32(cf)), "crc32z");
- show(".long", format(b1, "%u", ZIP_CFILE_COMPRESSEDSIZE(cf)),
- "compressedsize");
- show(".long", format(b1, "%u", ZIP_CFILE_UNCOMPRESSEDSIZE(cf)),
- "uncompressedsize");
+ if (ZIP_CFILE_COMPRESSEDSIZE(cf) == 0xFFFFFFFF) {
+ show(".long", "0xFFFFFFFF", "compressedsize (zip64)");
+ } else {
+ show(".long", format(b1, "%u", ZIP_CFILE_COMPRESSEDSIZE(cf)),
+ "compressedsize");
+ }
+ if (ZIP_CFILE_UNCOMPRESSEDSIZE(cf) == 0xFFFFFFFF) {
+ show(".long", "0xFFFFFFFF", "compressedsize (zip64)");
+ } else {
+ show(".long", format(b1, "%u", ZIP_CFILE_UNCOMPRESSEDSIZE(cf)),
+ "uncompressedsize");
+ }
show(".short", "1f-0f",
format(b1, "%s (%hu %s)", "namesize", ZIP_CFILE_NAMESIZE(cf), "bytes"));
show(
@@ -277,23 +332,29 @@ void showcentralfileheader(uint8_t *cf) {
"bytes"));
show(".short", format(b1, "%hu", ZIP_CFILE_DISK(cf)), "disk");
show(".short",
- RecreateFlags(kZipIattrNames, ZIP_CFILE_INTERNALATTRIBUTES(cf)),
+ RecreateFlags(kZipIattrNames, ZIP_CFILE_INTERNALATTRIBUTES(cf) & 1),
"internalattributes");
- showexternalattributes(cf);
- show(".long", format(b1, "%u", ZIP_CFILE_OFFSET(cf)), "lfile hdr offset");
+ ShowExternalAttributes(cf);
+ if (ZIP_CFILE_OFFSET(cf) == 0xFFFFFFFF) {
+ show(".long", "0xFFFFFFFF", "lfile hdr offset (zip64)");
+ } else {
+ show(".long", format(b1, "%u", ZIP_CFILE_OFFSET(cf)), "lfile hdr offset");
+ }
printf("0:");
show(".ascii",
format(b1, "%`'s",
gc(strndup(ZIP_CFILE_NAME(cf), ZIP_CFILE_NAMESIZE(cf)))),
"name");
printf("1:");
- showextras(ZIP_CFILE_EXTRA(cf), ZIP_CFILE_EXTRASIZE(cf), false);
+ ShowExtras(ZIP_CFILE_EXTRA(cf), ZIP_CFILE_EXTRASIZE(cf), false);
printf("2:");
- disassemblehex(ZIP_CFILE_COMMENT(cf), ZIP_CFILE_COMMENTSIZE(cf), stdout);
+ show(".ascii",
+ format(b1, "%`'.*s", ZIP_CFILE_COMMENTSIZE(cf), ZIP_CFILE_COMMENT(cf)),
+ "comment");
printf("3:\n");
}
-void showcentraldirheader(uint8_t *cd) {
+void ShowCentralDirHeader32(uint8_t *cd) {
printf("\n/\t%s (%zu %s)\n", "end of central directory header",
ZIP_CDIR_HDRSIZE(cd), "bytes");
show(".ascii", format(b1, "%`'.*s", 4, cd), "magic");
@@ -312,39 +373,116 @@ void showcentraldirheader(uint8_t *cd) {
printf("1:\n");
}
-void disassemblezip(uint8_t *map, size_t mapsize) {
+void ShowCentralDirHeader64(uint8_t *cd) {
+ printf("\n/\t%s (%zu %s)\n", "zip64 end of central directory header",
+ ZIP_CDIR64_HDRSIZE(cd), "bytes");
+ show(".ascii", format(b1, "%`'.*s", 4, cd), "magic");
+ show(".quad", format(b1, "%lu", ZIP_CDIR64_HDRSIZE(cd) - 12), "hdr size");
+ show(".short", format(b1, "%hd", ZIP_CDIR64_VERSIONMADE(cd)), "version made");
+ show(".short", format(b1, "%hd", ZIP_CDIR64_VERSIONNEED(cd)), "version need");
+ show(".long", format(b1, "%d", ZIP_CDIR64_DISK(cd)), "disk");
+ show(".long", format(b1, "%d", ZIP_CDIR64_STARTINGDISK(cd)), "startingdisk");
+ show(".quad", format(b1, "%lu", ZIP_CDIR64_RECORDSONDISK(cd)),
+ "recordsondisk");
+ show(".quad", format(b1, "%lu", ZIP_CDIR64_RECORDS(cd)), "records");
+ show(".quad", format(b1, "%lu", ZIP_CDIR64_SIZE(cd)), "cdir size");
+ show(".quad", format(b1, "%lu", ZIP_CDIR64_OFFSET(cd)), "cdir offset");
+ printf("0:");
+ disassemblehex(ZIP_CDIR64_COMMENT(cd), ZIP_CDIR64_COMMENTSIZE(cd), stdout);
+ printf("1:\n");
+}
+
+uint8_t *GetZipCdir32(const uint8_t *p, size_t n) {
+ size_t i;
+ if (n >= kZipCdirHdrMinSize) {
+ i = n - kZipCdirHdrMinSize;
+ do {
+ if (READ32LE(p + i) == kZipCdirHdrMagic && IsZipCdir32(p, n, i)) {
+ return (/*unconst*/ uint8_t *)(p + i);
+ }
+ } while (i--);
+ }
+ return NULL;
+}
+
+uint8_t *GetZipCdir64(const uint8_t *p, size_t n) {
+ size_t i;
+ if (n >= kZipCdir64HdrMinSize) {
+ i = n - kZipCdir64HdrMinSize;
+ do {
+ if (READ32LE(p + i) == kZipCdir64HdrMagic && IsZipCdir64(p, n, i)) {
+ return (/*unconst*/ uint8_t *)(p + i);
+ }
+ } while (i--);
+ }
+ return NULL;
+}
+
+void DisassembleZip(const char *path, uint8_t *p, size_t n) {
size_t pos;
uint16_t i;
static int records;
- uint8_t *cd, *cf, *lf;
- CHECK_NOTNULL((cd = zipfindcentraldir(map, mapsize)));
+ uint8_t *eocd32, *eocd64, *cdir, *cf, *lf, *q;
+ if (endswith(path, ".com.dbg") && (q = memmem(p, n, "MZqFpD", 6))) {
+ n -= q - p;
+ p += q - p;
+ }
+ eocd32 = GetZipCdir32(p, n);
+ eocd64 = GetZipCdir64(p, n);
+ CHECK(eocd32 || eocd64);
pos = 0;
- records = ZIP_CDIR_RECORDS(cd);
- for (i = 0, cf = map + ZIP_CDIR_OFFSET(cd); i < records;
- ++i, cf += ZIP_CFILE_HDRSIZE(cf)) {
- lf = map + ZIP_CFILE_OFFSET(cf);
+ if (eocd64) {
+ records = ZIP_CDIR64_RECORDS(eocd64);
+ cdir = p + ZIP_CDIR64_OFFSET(eocd64);
+ } else {
+ records = ZIP_CDIR_RECORDS(eocd32);
+ cdir = p + ZIP_CDIR_OFFSET(eocd32);
+ }
+ for (i = 0, cf = cdir; i < records; ++i, cf += ZIP_CFILE_HDRSIZE(cf)) {
+ lf = p + GetZipCfileOffset(cf);
CHECK_EQ(kZipLfileHdrMagic, ZIP_LFILE_MAGIC(lf));
- advancepos(map, &pos, lf - map);
- showlocalfileheader(lf, i);
- pos = (lf - map) + ZIP_LFILE_SIZE(lf);
+ AdvancePosition(p, &pos, lf - p);
+ ShowLocalFileHeader(lf, i);
+ pos = (lf - p) + ZIP_LFILE_SIZE(lf);
}
- for (i = 0, cf = map + ZIP_CDIR_OFFSET(cd); i < records;
- ++i, cf += ZIP_CFILE_HDRSIZE(cf)) {
+ for (i = 0, cf = cdir; i < records; ++i, cf += ZIP_CFILE_HDRSIZE(cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(cf));
- advancepos(map, &pos, cf - map);
- showcentralfileheader(cf);
- pos = (cf - map) + ZIP_CFILE_HDRSIZE(cf);
+ AdvancePosition(p, &pos, cf - p);
+ ShowCentralFileHeader(cf);
+ pos = (cf - p) + ZIP_CFILE_HDRSIZE(cf);
}
- advancepos(map, &pos, cd - map);
- showcentraldirheader(cd);
- pos = (cd - map) + ZIP_CDIR_HDRSIZE(cd);
- advancepos(map, &pos, mapsize);
+ if (eocd32 && eocd64) {
+ if (eocd32 < eocd64) {
+ ShowCentralDirHeader32(eocd32);
+ AdvancePosition(p, &pos, eocd32 - p);
+ ShowCentralDirHeader64(eocd64);
+ AdvancePosition(p, &pos, eocd64 - p);
+ } else {
+ ShowCentralDirHeader64(eocd64);
+ AdvancePosition(p, &pos, eocd64 - p);
+ ShowCentralDirHeader32(eocd32);
+ AdvancePosition(p, &pos, eocd32 - p);
+ }
+ } else if (eocd32) {
+ ShowCentralDirHeader32(eocd32);
+ AdvancePosition(p, &pos, eocd32 - p);
+ } else {
+ ShowCentralDirHeader64(eocd64);
+ AdvancePosition(p, &pos, eocd64 - p);
+ }
+ if (!eocd64 || eocd32 > eocd64) {
+ pos = eocd32 - p + ZIP_CDIR_HDRSIZE(eocd32);
+ } else {
+ pos = eocd64 - p + ZIP_CDIR_HDRSIZE(eocd64);
+ }
+ AdvancePosition(p, &pos, n);
}
int main(int argc, char *argv[]) {
int fd;
uint8_t *map;
struct stat st;
+ showcrashreports();
CHECK_EQ(2, argc);
CHECK_NE(-1, (fd = open(argv[1], O_RDONLY)));
CHECK_NE(-1, fstat(fd, &st));
@@ -353,7 +491,7 @@ int main(int argc, char *argv[]) {
(map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)));
showtitle("αcτµαlly pδrταblε εxεcµταblε", "tool/decode/zip",
basename(argv[1]), NULL, &kModelineAsm);
- disassemblezip(map, st.st_size);
+ DisassembleZip(argv[1], map, st.st_size);
CHECK_NE(-1, munmap(map, st.st_size));
CHECK_NE(-1, close(fd));
return 0;
diff --git a/tool/net/.init.lua b/tool/net/.init.lua
index 080402535..778f344ec 100644
--- a/tool/net/.init.lua
+++ b/tool/net/.init.lua
@@ -1,3 +1,2 @@
-- special script called by main redbean process at startup
-ProgramRedirect(0, '/favicon.ico', '/tool/net/redbean.ico')
HidePath('/usr/share/zoneinfo/')
diff --git a/tool/net/demo/.init.lua b/tool/net/demo/.init.lua
new file mode 100644
index 000000000..077d60f7d
--- /dev/null
+++ b/tool/net/demo/.init.lua
@@ -0,0 +1,17 @@
+-- /.init.lua is loaded at startup in redbean's main process
+
+HidePath('/usr/share/zoneinfo/')
+
+function OnHttpRequest()
+ if HasParam('magic') then
+ Write('\r\n')
+ Write('OnHttpRequest() has intercepted your request \r\n')
+ Write('because you specified the magic parameter\r\n')
+ Write('
\r\n')
+ Write(EscapeHtml(LoadAsset('/.init.lua')))
+ Write(' \r\n')
+ else
+ Route()
+ end
+ SetHeader('Server', 'redbean!')
+end
diff --git a/tool/net/.reload.lua b/tool/net/demo/.reload.lua
similarity index 100%
rename from tool/net/.reload.lua
rename to tool/net/demo/.reload.lua
diff --git a/tool/net/demo/404.html b/tool/net/demo/404.html
new file mode 100644
index 000000000..5e893efee
--- /dev/null
+++ b/tool/net/demo/404.html
@@ -0,0 +1,11 @@
+
+404 not found
+
+
+ _ _ ___ _ _ _ __ _
+| || | / _ \| || | _ __ ___ | |_ / _| ___ _ _ _ __ __| |
+| || |_| | | | || |_ | '_ \ / _ \| __| | |_ / _ \| | | | '_ \ / _` |
+|__ _| |_| |__ _| | | | | (_) | |_ | _| (_) | |_| | | | | (_| |
+ |_| \___/ |_| |_| |_|\___/ \__| |_| \___/ \__,_|_| |_|\__,_|
+
+
diff --git a/tool/net/demo/hello.lua b/tool/net/demo/hello.lua
new file mode 100644
index 000000000..602458869
--- /dev/null
+++ b/tool/net/demo/hello.lua
@@ -0,0 +1 @@
+Write('hello world\r\n')
diff --git a/tool/net/redbean.html b/tool/net/demo/index.html
similarity index 86%
rename from tool/net/redbean.html
rename to tool/net/demo/index.html
index c7f6fe5b8..8cca37ed7 100644
--- a/tool/net/redbean.html
+++ b/tool/net/demo/index.html
@@ -1,8 +1,8 @@
redbean
-
-
+
+
redbean
diff --git a/tool/net/demo/redbean-form.lua b/tool/net/demo/redbean-form.lua
new file mode 100644
index 000000000..527c72081
--- /dev/null
+++ b/tool/net/demo/redbean-form.lua
@@ -0,0 +1,70 @@
+-- redbean post request handler demo
+
+local function main()
+ if GetMethod() ~= 'POST' then
+ ServeError(405)
+ SetHeader('Allow', 'POST')
+ return
+ end
+ SetStatus(200)
+ SetHeader('Content-Type', 'text/html; charset=utf-8')
+ Write('\r\n')
+ Write('redbean \r\n')
+ Write('POST Request HTML Form Handler Demo \r\n')
+
+ Write(' ')
+ firstname = GetParam('firstname')
+ lastname = GetParam('lastname')
+ if firstname and lastname then
+ Write('Hello ')
+ Write(EscapeHtml(VisualizeControlCodes(firstname)))
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(lastname)))
+ Write('! ')
+ Write('Thank you for using redbean.')
+ end
+
+ Write('
\r\n')
+
+ Write('Params\r\n')
+ Write(' \r\n')
+ Write('\r\n')
+ params = GetParams()
+ for i = 1,#params do
+ Write('')
+ Write(EscapeHtml(VisualizeControlCodes(params[i][1])))
+ Write('\r\n')
+ if params[i][2] then
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(params[i][2])))
+ Write('\r\n')
+ end
+ end
+ Write(' \r\n')
+
+ Write(' Headers\r\n')
+ Write(' \r\n')
+ Write('\r\n')
+ for k,v in pairs(GetHeaders()) do
+ Write('')
+ Write(EscapeHtml(k))
+ Write('\r\n')
+ Write(' ')
+ Write(EscapeHtml(v))
+ Write('\r\n')
+ end
+ Write(' \r\n')
+
+ Write(' Payload\r\n')
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(GetPayload())))
+ Write('\r\n')
+
+ Write('
\r\n')
+
+ Write('')
+ Write('Click here ')
+ Write('to return to the previous page.\r\n')
+end
+
+main()
diff --git a/tool/net/demo/redbean-xhr.lua b/tool/net/demo/redbean-xhr.lua
new file mode 100644
index 000000000..a2daf44d1
--- /dev/null
+++ b/tool/net/demo/redbean-xhr.lua
@@ -0,0 +1,8 @@
+-- redbean xhr handler demo
+hdr = GetHeader('x-custom-header')
+if hdr then
+ SetHeader('Vary', 'X-Custom-Header')
+ SetHeader('X-Custom-Header', 'hello ' .. hdr)
+else
+ ServeError(400)
+end
diff --git a/tool/net/redbean.css b/tool/net/demo/redbean.css
similarity index 100%
rename from tool/net/redbean.css
rename to tool/net/demo/redbean.css
diff --git a/tool/net/demo/redbean.lua b/tool/net/demo/redbean.lua
new file mode 100644
index 000000000..3c9650fbd
--- /dev/null
+++ b/tool/net/demo/redbean.lua
@@ -0,0 +1,350 @@
+-- redbean lua server page demo
+
+local function main()
+ -- This is the best way to print data to the console or log file.
+ Log(kLogWarn, "hello from \e[1mlua\e[0m!")
+
+ -- This check is pedantic but might be good to have.
+ if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then
+ ServeError(405)
+ SetHeader('Allow', 'GET, HEAD')
+ return
+ end
+
+ -- These two lines are optional.
+ -- The default behavior is to do this if you don't.
+ SetStatus(200) -- Shorthand for SetStatus(200, "OK")
+ SetHeader('Content-Type', 'text/html; charset=utf-8')
+
+ -- Response data is buffered until the script finishes running.
+ -- Compression is applied automatically, based on your headers.
+ Write('\r\n')
+ Write('
redbean \r\n')
+ Write('\r\n')
+ Write(' \r\n')
+ Write('redbean lua server page demo\r\n')
+ Write(' \r\n')
+
+ -- Prevent caching.
+ -- We need this because we're doing things like putting the client's
+ -- IP address in the response so we naturally don't want that cached
+ SetHeader('Expires', FormatHttpDateTime(GetDate()))
+ SetHeader('Cache-Control', 'no-cache, must-revalidate, max-age=0')
+
+ -- GetUrl() is the resolved Request-URI (TODO: Maybe change API to return a URL object?)
+ Write('Thank you for visiting ')
+ Write(GetUrl()) -- redbean encoded this value so it doesn't need html entity escaping
+ Write('
\r\n')
+
+ -- GetParam(NAME) is the fastest easiest way to get URL and FORM params
+ -- If you want the RequestURL query params specifically in full do this
+ Write('
request url parameters \r\n')
+ params = ParseUrl(GetUrl()).params -- like GetParams() but w/o form body
+ if params and #params>0 then
+ Write('\r\n')
+ for i = 1,#params do
+ Write('')
+ Write(EscapeHtml(VisualizeControlCodes(params[i][1])))
+ Write('\r\n')
+ if params[i][2] then
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(params[i][2])))
+ Write('\r\n')
+ end
+ end
+ Write(' \r\n')
+ Write("Whatever you do, don't click on ")
+ Write('')
+ Write(EscapeHtml(VisualizeControlCodes(GetPath())))
+ Write('?magic \r\n')
+ else
+ Write('
\r\n')
+ Write('none \r\n')
+ Write('ProTip: Try clicking here !\r\n')
+ end
+
+ -- redbean command line arguments
+ -- these come *after* the c getopt server arguments
+ Write('
command line arguments \r\n')
+ if #argv > 0 then
+ Write('\r\n')
+ for i = 1,#argv do
+ Write('')
+ Write(EscapeHtml(VisualizeControlCodes(argv[i])))
+ Write('\r\n')
+ end
+ Write(' \r\n')
+ else
+ Write('none \r\n')
+ end
+
+ Write([[
+
post request html form demo
+
+ ]])
+
+ Write([[
+ xmlhttprequest request demo
+
+ X-Custom-Header
+ send
+
+
+ ]])
+
+ -- fast redbean apis for accessing already parsed request data
+ Write('extra information \r\n')
+ Write('\r\n')
+ Write('GetMethod()\r\n')
+ Write(' ')
+ Write(EscapeHtml(GetMethod())) -- & and ' are legal in http methods
+ Write('\r\n')
+ if GetUser() then
+ Write(' GetUser()\r\n')
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(GetUser())))
+ Write('\r\n')
+ end
+ if GetScheme() then
+ Write(' GetScheme()\r\n')
+ Write(' ')
+ Write(GetScheme())
+ Write('\r\n')
+ end
+ if GetPass() then
+ Write(' GetPass()\r\n')
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(GetPass())))
+ Write('\r\n')
+ end
+ Write(' GetHost() (from HTTP Request-URI or Host header or X-Forwarded-Host header or Berkeley Sockets) \r\n')
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(GetHost())))
+ Write('\r\n')
+ Write(' GetPort() (from HTTP Request-URI or Host header or X-Forwarded-Host header or Berkeley Sockets) \r\n')
+ Write(' ')
+ Write(tostring(GetPort()))
+ Write('\r\n')
+ Write(' GetPath() (from HTTP Request-URI) \r\n')
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(GetPath())))
+ Write('\r\n')
+ Write(' GetEffectivePath() (actual path used internally to load the lua asset: routed depending on host, request path, and rewrites) \r\n')
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(GetEffectivePath())))
+ Write('\r\n')
+ if GetFragment() then
+ Write(' GetFragment()\r\n')
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(GetFragment())))
+ Write('\r\n')
+ end
+ Write(' GetRemoteAddr() (from Bekeley Sockets or X-Forwarded-For header) \r\n')
+ Write(' ')
+ ip, port = GetRemoteAddr()
+ Write(string.format('%s, %d', FormatIp(ip), port))
+ if CategorizeIp(ip) then
+ Write(' \r\n')
+ Write(CategorizeIp(ip))
+ end
+ Write('\r\n')
+ Write(' GetClientAddr()\r\n')
+ Write(' ')
+ ip, port = GetClientAddr()
+ Write(string.format('%s, %d', FormatIp(ip), port))
+ if CategorizeIp(ip) then
+ Write(' \r\n')
+ Write(CategorizeIp(ip))
+ end
+ Write('\r\n')
+ Write(' GetServerIp()\r\n')
+ Write(' ')
+ ip, port = GetServerAddr()
+ Write(string.format('%s, %d', FormatIp(ip), port))
+ if CategorizeIp(ip) then
+ Write(' \r\n')
+ Write(CategorizeIp(ip))
+ end
+ Write('\r\n')
+ Write(' \r\n')
+
+ -- redbean apis for generalized parsing and encoding
+ referer = GetHeader('Referer')
+ if referer then
+ url = ParseUrl(referer)
+ if url.scheme then
+ url.scheme = string.upper(url.scheme)
+ end
+ Write('referer url \r\n')
+ Write('\r\n')
+ Write(EscapeHtml(EncodeUrl(url)))
+ Write('
\r\n')
+ if url.scheme then
+ Write('scheme\r\n')
+ Write(' \r\n')
+ Write(url.scheme)
+ end
+ if url.user then
+ Write(' user\r\n')
+ Write(' \r\n')
+ Write(EscapeHtml(VisualizeControlCodes(url.user)))
+ end
+ if url.pass then
+ Write(' pass\r\n')
+ Write(' \r\n')
+ Write(EscapeHtml(VisualizeControlCodes(url.pass)))
+ end
+ if url.host then
+ Write(' host\r\n')
+ Write(' \r\n')
+ Write(EscapeHtml(VisualizeControlCodes(url.host)))
+ end
+ if url.port then
+ Write(' port\r\n')
+ Write(' \r\n')
+ Write(EscapeHtml(VisualizeControlCodes(url.port)))
+ end
+ if url.path then
+ Write(' path\r\n')
+ Write(' \r\n')
+ Write(EscapeHtml(VisualizeControlCodes(url.path)))
+ end
+ if url.params then
+ Write(' params\r\n')
+ Write(' \r\n')
+ Write('\r\n')
+ for i = 1,#url.params do
+ Write('')
+ Write(EscapeHtml(VisualizeControlCodes(url.params[i][1])))
+ Write('\r\n')
+ if url.params[i][2] then
+ Write(' ')
+ Write(EscapeHtml(VisualizeControlCodes(url.params[i][2])))
+ Write('\r\n')
+ end
+ end
+ Write(' \r\n')
+ end
+ if url.fragment then
+ Write(' fragment\r\n')
+ Write(' \r\n')
+ Write(EscapeHtml(VisualizeControlCodes(url.fragment)))
+ end
+ Write(' \r\n')
+ end
+
+ Write('posix extended regular expressions \r\n')
+ s = 'my ' .. FormatIp(GetRemoteAddr()) .. ' ip'
+ -- traditional regular expressions
+ m,a,b,c,d = re.search([[\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)]], s, re.BASIC)
+ -- easy api (~10x slower because compile is O(2ⁿ) and search is O(n))
+ m,a,b,c,d = re.search([[([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})]], s)
+ -- proper api (especially if you put the re.compile() line in /.init.lua)
+ pat = re.compile([[([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})]])
+ m,a,b,c,d = pat:search(s) -- m and rest are nil if match not found
+ Write('\r\n')
+ Write([[pat = re.compile('([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})')]])
+ Write(string.format('\r\nm,a,b,c,d = pat:search(%q)\r\n', s))
+ Write(' \r\n')
+ Write('\r\n')
+ Write('m\r\n')
+ Write(' ')
+ Write(tostring(m))
+ Write('\r\n')
+ Write(' a\r\n')
+ Write(' ')
+ Write(tostring(a))
+ Write('\r\n')
+ Write(' b\r\n')
+ Write(' ')
+ Write(tostring(b))
+ Write('\r\n')
+ Write(' c\r\n')
+ Write(' ')
+ Write(tostring(c))
+ Write('\r\n')
+ Write(' d\r\n')
+ Write(' ')
+ Write(tostring(d))
+ Write('\r\n')
+ Write(' \r\n')
+
+ Write('source code to this page \r\n')
+ Write('\r\n')
+ Write(EscapeHtml(LoadAsset(GetEffectivePath())))
+ Write(' \r\n')
+
+ -- redbean zip assets
+ Write('zip assets \r\n')
+ paths = GetZipPaths()
+ if #paths > 0 then
+ Write('\r\n')
+ for i = 1,#paths do
+ Write('\r\n')
+ Write('')
+ Write(EscapeHtml(VisualizeControlCodes(paths[i])))
+ Write(' ')
+ if IsHiddenPath(paths[i]) then
+ Write(' [HIDDEN] ')
+ end
+ if not IsAcceptablePath(paths[i]) then
+ Write(' [BLOCKED] ')
+ end
+ if not IsCompressed(paths[i]) then
+ Write(' [UNCOMPRESSED] ')
+ end
+ if (GetAssetMode(paths[i]) & 0xF000) == 0x4000 then
+ Write(' [DIRECTORY] ')
+ end
+ Write(' \r\n')
+ Write('Modified: ')
+ Write(FormatHttpDateTime(GetLastModifiedTime(paths[i])))
+ Write(' \r\n')
+ Write('Mode: ')
+ Write(string.format("0%o", GetAssetMode(paths[i])))
+ Write(' \r\n')
+ Write('Size: ')
+ Write(tostring(GetAssetSize(paths[i])))
+ Write(' \r\n')
+ if GetComment(paths[i]) then
+ Write('Comment: ')
+ Write(EscapeHtml(VisualizeControlCodes(GetComment(paths[i]))))
+ Write(' \r\n')
+ end
+ Write('\r\n')
+ end
+ Write(' \r\n')
+ else
+ Write('none \r\n')
+ end
+
+end
+
+main()
diff --git a/tool/net/demo/seekable.txt b/tool/net/demo/seekable.txt
new file mode 100644
index 000000000..a6f1d23fc
--- /dev/null
+++ b/tool/net/demo/seekable.txt
@@ -0,0 +1,26 @@
+A
+B
+C
+D
+E
+F
+G
+H
+I
+J
+K
+L
+M
+N
+O
+P
+Q
+R
+S
+T
+U
+V
+W
+X
+Y
+Z
diff --git a/tool/net/demo/virtualbean.html b/tool/net/demo/virtualbean.html
new file mode 100644
index 000000000..49326e0f8
--- /dev/null
+++ b/tool/net/demo/virtualbean.html
@@ -0,0 +1,7 @@
+
+
+
redbean virtual host
+virtualbean.justine.lol
+This page uses redbean virtual hosting. Please refer to
+//redbean.justine.lol
+to see the primary instance.
diff --git a/tool/net/redbean.ico b/tool/net/favicon.ico
similarity index 100%
rename from tool/net/redbean.ico
rename to tool/net/favicon.ico
diff --git a/tool/net/net.mk b/tool/net/net.mk
index 8bd0dd324..bc720ede2 100644
--- a/tool/net/net.mk
+++ b/tool/net/net.mk
@@ -42,6 +42,7 @@ TOOL_NET_DIRECTDEPS = \
NET_HTTP \
THIRD_PARTY_GETOPT \
THIRD_PARTY_LUA \
+ THIRD_PARTY_REGEX \
THIRD_PARTY_ZLIB \
TOOL_DECODE_LIB
@@ -63,20 +64,83 @@ o/$(MODE)/tool/net/%.com.dbg: \
o/$(MODE)/tool/net/redbean.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean.o \
- o/$(MODE)/tool/net/redbean.ico.zip.o \
- o/$(MODE)/tool/net/redbean.png.zip.o \
- o/$(MODE)/tool/net/redbean.css.zip.o \
- o/$(MODE)/tool/net/redbean.html.zip.o \
- o/$(MODE)/tool/net/redbean.lua.zip.o \
- o/$(MODE)/tool/net/redbean-form.lua.zip.o \
- o/$(MODE)/tool/net/redbean-xhr.lua.zip.o \
- o/$(MODE)/tool/net/.init.lua.zip.o \
- o/$(MODE)/tool/net/.reload.lua.zip.o \
o/$(MODE)/tool/net/net.pkg \
$(CRT) \
$(APE)
@$(APELINK)
+o/$(MODE)/tool/net/redbean.com: \
+ o/$(MODE)/tool/net/redbean.com.dbg \
+ tool/net/net.mk \
+ tool/net/.init.lua \
+ tool/net/favicon.ico \
+ tool/net/redbean.png
+ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@
+ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.ape bs=64 count=11 conv=notrunc 2>/dev/null
+ @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.ape tool/net/.init.lua tool/net/favicon.ico tool/net/redbean.png
+
+o/$(MODE)/tool/net/redbean-demo.com.dbg: \
+ o/$(MODE)/tool/net/redbean.com.dbg
+ @$(COMPILE) -ACP -T$@ cp $< $@
+
+o/$(MODE)/tool/net/redbean-demo.com: \
+ o/$(MODE)/tool/net/redbean-demo.com.dbg \
+ tool/net/net.mk \
+ tool/net/favicon.ico \
+ tool/net/redbean.png \
+ tool/net/demo/.init.lua \
+ tool/net/demo/.reload.lua \
+ tool/net/demo/404.html \
+ tool/net/demo/hello.lua \
+ tool/net/demo/index.html \
+ tool/net/demo/redbean.css \
+ tool/net/demo/redbean.lua \
+ tool/net/demo/redbean-form.lua \
+ tool/net/demo/redbean-xhr.lua \
+ tool/net/demo/seekable.txt \
+ tool/net/demo/virtualbean.html \
+ tool/net/redbean.c \
+ net/http/parsehttprequest.c \
+ net/http/parseurl.c \
+ net/http/encodeurl.c \
+ test/net/http/parsehttprequest_test.c \
+ test/net/http/parseurl_test.c
+ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@
+ @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/.redbean-demo
+ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-demo/.ape bs=64 count=11 conv=notrunc 2>/dev/null
+ @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.redbean-demo/.ape tool/net/demo/.init.lua tool/net/demo/.reload.lua tool/net/demo/hello.lua
+ @echo "<-- check out this lua server page" | $(COMPILE) -AZIP -T$@ zip -cqj $@ tool/net/demo/redbean.lua
+ @$(COMPILE) -AZIP -T$@ zip -qj $@ tool/net/demo/404.html tool/net/favicon.ico tool/net/redbean.png tool/net/demo/redbean-form.lua tool/net/demo/redbean-xhr.lua
+ @echo Uncompressed for HTTP Range requests | $(COMPILE) -AZIP -T$@ zip -cqj0 $@ tool/net/demo/seekable.txt
+ @$(COMPILE) -AZIP -T$@ zip -q $@ tool/net/ tool/net/demo/ tool/net/demo/index.html tool/net/demo/redbean.css tool/net/redbean.c net/http/parsehttprequest.c net/http/parseurl.c net/http/encodeurl.c test/net/http/parsehttprequest_test.c test/net/http/parseurl_test.c
+ @echo "
This is a live instance of redbean : a tiny multiplatform webserver that went viral on hacker news a few months ago. since then, we've added Lua dynamic serving, which also goes as fast as 1,000,000 requests per second on a core i9 (rather than a cheap virtual machine like this). the text you're reading now is a PKZIP End Of Central Directory comment.
redbean aims to be production worthy across six operating systems, using a single executable file (this demo is hosted on FreeBSD 13). redbean has been enhanced to restore the APE header after startup. It automatically generates this listing page based on your ZIP contents. If you use redbean as an application server / web development environment, then you'll find other new and useful features like function call logging so you can get that sweet sweet microsecond scale latency." | $(COMPILE) -AZIP -T$@ zip -z $@
+ @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/virtualbean.justine.lol/
+ @$(COMPILE) -ACP -T$@ cp tool/net/redbean.png o/$(MODE)/tool/net/virtualbean.justine.lol/redbean.png
+ @$(COMPILE) -ACP -T$@ cp tool/net/demo/virtualbean.html o/$(MODE)/tool/net/virtualbean.justine.lol/index.html
+ @(cd o/$(MODE)/tool/net && zip -q redbean-demo.com virtualbean.justine.lol/)
+ @(cd o/$(MODE)/tool/net && echo 'Go to http://virtualbean.justine.lol ' | zip -cq redbean-demo.com virtualbean.justine.lol/index.html)
+ @(cd o/$(MODE)/tool/net && zip -q redbean-demo.com virtualbean.justine.lol/redbean.png)
+
+o/$(MODE)/tool/net/redbean-static.com: \
+ o/$(MODE)/tool/net/redbean-static.com.dbg \
+ tool/net/favicon.ico \
+ tool/net/redbean.png
+ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@
+ @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/.redbean-static
+ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-static/.ape bs=64 count=11 conv=notrunc 2>/dev/null
+ @$(COMPILE) -AZIP -T$@ zip -qj $@ o/$(MODE)/tool/net/.redbean-static/.ape tool/net/favicon.ico tool/net/redbean.png
+
+o/$(MODE)/tool/net/redbean-static.com.dbg: \
+ $(TOOL_NET_DEPS) \
+ o/$(MODE)/tool/net/redbean-static.o \
+ o/$(MODE)/tool/net/net.pkg \
+ $(CRT) \
+ $(APE)
+ @$(APELINK)
+
+o/$(MODE)/tool/net/redbean-static.o: tool/net/redbean.c
+ @$(COMPILE) -AOBJECTIFY.c $(OBJECTIFY.c) -DSTATIC $(OUTPUT_OPTION) $<
+
.PHONY: o/$(MODE)/tool/net
o/$(MODE)/tool/net: \
$(TOOL_NET_BINS) \
diff --git a/tool/net/redbean-form.lua b/tool/net/redbean-form.lua
deleted file mode 100644
index 5b8f03855..000000000
--- a/tool/net/redbean-form.lua
+++ /dev/null
@@ -1,70 +0,0 @@
--- redbean post request handler demo
-
-local function main()
- if GetMethod() ~= 'POST' then
- ServeError(405)
- SetHeader('Allow', 'POST')
- return
- end
- SetStatus(200)
- SetHeader('Content-Type', 'text/html; charset=utf-8')
- Write('\n')
- Write('
redbean \n')
- Write('POST Request HTML Form Handler Demo \n')
-
- Write('')
- firstname = GetParam('firstname')
- lastname = GetParam('lastname')
- if firstname and lastname then
- Write('Hello ')
- Write(EscapeHtml(firstname))
- Write(' ')
- Write(EscapeHtml(lastname))
- Write('! ')
- Write('Thank you for using redbean.')
- end
-
- Write('
\n')
-
- Write('Params\n')
- Write(' \n')
- Write('\n')
- params = GetParams()
- for i = 1,#params do
- Write('')
- Write(EscapeHtml(params[i][1]))
- Write('\n')
- if params[i][2] then
- Write(' ')
- Write(EscapeHtml(params[i][2]))
- Write('\n')
- end
- end
- Write(' \n')
-
- Write(' Headers\n')
- Write(' \n')
- Write('\n')
- for k,v in pairs(GetHeaders()) do
- Write('')
- Write(EscapeHtml(k))
- Write('\n')
- Write(' ')
- Write(EscapeHtml(v))
- Write('\n')
- end
- Write(' \n')
-
- Write(' Payload\n')
- Write(' ')
- Write(EscapeHtml(GetPayload()))
- Write('\n')
-
- Write('
\n')
-
- Write('')
- Write('Click here ')
- Write('to return to the previous page.\n')
-end
-
-main()
diff --git a/tool/net/redbean-xhr.lua b/tool/net/redbean-xhr.lua
deleted file mode 100644
index cd93d5d4e..000000000
--- a/tool/net/redbean-xhr.lua
+++ /dev/null
@@ -1,3 +0,0 @@
--- redbean xhr handler demo
-SetHeader('Vary', 'X-Custom-Header')
-SetHeader('X-Custom-Header', 'hello ' .. GetHeader('x-custom-header'))
diff --git a/tool/net/redbean.c b/tool/net/redbean.c
index 6b00f5eca..e9b8437c5 100644
--- a/tool/net/redbean.c
+++ b/tool/net/redbean.c
@@ -16,147 +16,72 @@
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
-#include "libc/bits/bswap.h"
#include "libc/bits/popcnt.h"
#include "libc/bits/safemacros.internal.h"
#include "libc/calls/calls.h"
-#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/itimerval.h"
+#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/stat.h"
-#include "libc/calls/weirdtypes.h"
-#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
-#include "libc/fmt/fmt.h"
#include "libc/fmt/itoa.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
-#include "libc/macros.internal.h"
#include "libc/math.h"
#include "libc/mem/fmt.h"
-#include "libc/mem/mem.h"
#include "libc/nexgen32e/bsf.h"
#include "libc/nexgen32e/bsr.h"
#include "libc/nexgen32e/crc32.h"
-#include "libc/rand/rand.h"
+#include "libc/runtime/clktck.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/stdio/stdio.h"
-#include "libc/str/str.h"
-#include "libc/str/thompike.h"
-#include "libc/str/undeflate.h"
-#include "libc/str/utf16.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/auxv.h"
-#include "libc/sysv/consts/clock.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
-#include "libc/sysv/consts/f.h"
-#include "libc/sysv/consts/fd.h"
#include "libc/sysv/consts/inaddr.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/map.h"
+#include "libc/sysv/consts/msync.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
-#include "libc/sysv/consts/sa.h"
+#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/consts/shut.h"
-#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "libc/sysv/consts/tcp.h"
#include "libc/sysv/consts/w.h"
-#include "libc/sysv/errfuns.h"
-#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "libc/zip.h"
-#include "libc/zipos/zipos.internal.h"
-#include "net/http/base64.h"
#include "net/http/escape.h"
#include "net/http/http.h"
+#include "net/http/ip.h"
+#include "net/http/url.h"
#include "third_party/getopt/getopt.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/ltests.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/lualib.h"
+#include "third_party/regex/regex.h"
#include "third_party/zlib/zlib.h"
-#define USAGE \
- " [-hvdsm] [-p PORT] [-- SCRIPTARGS...]\n\
-\n\
-DESCRIPTION\n\
-\n\
- redbean - single-file distributable web server\n\
-\n\
-FLAGS\n\
-\n\
- -h help\n\
- -v verbosity\n\
- -d daemonize\n\
- -u uniprocess\n\
- -z print port\n\
- -m log messages\n\
- -b log message bodies\n\
- -k encourage keep-alive\n\
- -D DIR serve assets from directory\n\
- -c INT cache seconds\n\
- -r /X=/Y redirect X to Y\n\
- -R /X=/Y rewrite X to Y\n\
- -l ADDR listen ip [default 0.0.0.0]\n\
- -p PORT listen port [default 8080]\n\
- -L PATH log file location\n\
- -P PATH pid file location\n\
- -U INT daemon set user id\n\
- -G INT daemon set group id\n\
- -B STR changes brand\n\
-\n\
-FEATURES\n\
-\n\
- - Lua v5.4\n\
- - HTTP v0.9\n\
- - HTTP v1.0\n\
- - HTTP v1.1\n\
- - Content-Encoding\n\
- - Range / Content-Range\n\
- - Last-Modified / If-Modified-Since\n\
-\n\
-USAGE\n\
-\n\
- This executable is also a ZIP file that contains static assets.\n\
-\n\
- unzip -vl redbean.com # shows listing of zip contents\n\
-\n\
- Audio video content should not be compressed in your ZIP files.\n\
- Uncompressed assets enable browsers to send Range HTTP request.\n\
- On the other hand compressed assets are best for gzip encoding.\n\
-\n\
- zip redbean.com index.html # adds file\n\
- zip -0 redbean.com video.mp4 # adds without compression\n\
-\n\
- You can run redbean interactively in your terminal as follows:\n\
-\n\
- redbean.com -vv\n\
- CTRL-C # 1x: graceful shutdown\n\
- CTRL-C # 2x: forceful shutdown\n\
-\n\
- You can have redbean run as a daemon by doing the following:\n\
-\n\
- redbean.com -vv -d -L redbean.log -P redbean.pid\n\
- kill -TERM $(cat redbean.pid) # 1x: graceful shutdown\n\
- kill -TERM $(cat redbean.pid) # 2x: forceful shutdown\n\
-\n\
- redbean imposes a 32kb limit on requests to limit the memory of\n\
- connection processes, which grow to whatever number your system\n\
- limits and tcp stack configuration allow. If fork() should fail\n\
- then redbean starts shutting idle connections down.\n\
-\n"
-
#define HASH_LOAD_FACTOR /* 1. / */ 4
#define DEFAULT_PORT 8080
+#define read(F, P, N) readv(F, &(struct iovec){P, N}, 1)
+#define Hash(P, N) max(1, crc32c(0, P, N));
+#define LockInc(P) asm volatile("lock inc%z0\t%0" : "=m"(*(P)))
+#define AppendCrlf(P) mempcpy(P, "\r\n", 2)
+#define HasHeader(H) (!!msg.headers[H].a)
+#define HeaderData(H) (inbuf.p + msg.headers[H].a)
+#define HeaderLength(H) (msg.headers[H].b - msg.headers[H].a)
+#define HeaderEqualCase(H, S) \
+ SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H))
+
static const struct itimerval kHeartbeat = {
{0, 500000},
{0, 500000},
@@ -175,68 +100,83 @@ static const uint8_t kGzipHeader[] = {
kZipOsUnix, // OS
};
-static const char kHexToInt[256] = {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // 0x30
- 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50
- 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xc0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xd0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xe0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xf0
+static const char *const kIndexPaths[] = {
+#ifndef STATIC
+ "index.lua",
+#endif
+ "index.html",
};
static const struct ContentTypeExtension {
unsigned char ext[8];
const char *mime;
} kContentTypeExtension[] = {
- {"S", "text/plain"}, //
- {"bmp", "image/x-ms-bmp"}, //
- {"c", "text/plain"}, //
- {"cc", "text/plain"}, //
- {"css", "text/css"}, //
- {"csv", "text/csv"}, //
- {"gif", "image/gif"}, //
- {"h", "text/plain"}, //
- {"html", "text/html"}, //
- {"i", "text/plain"}, //
- {"ico", "image/vnd.microsoft.icon"}, //
- {"jpeg", "image/jpeg"}, //
- {"jpg", "image/jpeg"}, //
- {"js", "application/javascript"}, //
- {"json", "application/json"}, //
- {"m4a", "audio/mpeg"}, //
- {"markdown", "text/plain"}, //
- {"md", "text/plain"}, //
- {"mp2", "audio/mpeg"}, //
- {"mp3", "audio/mpeg"}, //
- {"mp4", "video/mp4"}, //
- {"mpg", "video/mpeg"}, //
- {"otf", "font/otf"}, //
- {"pdf", "application/pdf"}, //
- {"png", "image/png"}, //
- {"s", "text/plain"}, //
- {"svg", "image/svg+xml"}, //
- {"tiff", "image/tiff"}, //
- {"ttf", "font/ttf"}, //
- {"txt", "text/plain"}, //
- {"wav", "audio/x-wav"}, //
- {"woff", "font/woff"}, //
- {"woff2", "font/woff2"}, //
- {"xml", "application/xml"}, //
- {"zip", "application/zip"}, //
+ {"7z", "application/x-7z-compressed"}, //
+ {"S", "text/plain"}, //
+ {"aac", "audio/aac"}, //
+ {"apng", "image/apng"}, //
+ {"avi", "video/x-msvideo"}, //
+ {"avif", "image/avif"}, //
+ {"bmp", "image/bmp"}, //
+ {"c", "text/plain"}, //
+ {"cc", "text/plain"}, //
+ {"css", "text/css"}, //
+ {"csv", "text/csv"}, //
+ {"gif", "image/gif"}, //
+ {"h", "text/plain"}, //
+ {"htm", "text/html"}, //
+ {"html", "text/html"}, //
+ {"i", "text/plain"}, //
+ {"ico", "image/vnd.microsoft.icon"}, //
+ {"jar", "appliaction/java-archive"}, //
+ {"jpeg", "image/jpeg"}, //
+ {"jpg", "image/jpeg"}, //
+ {"js", "application/javascript"}, //
+ {"json", "application/json"}, //
+ {"m4a", "audio/mpeg"}, //
+ {"markdown", "text/plain"}, //
+ {"md", "text/plain"}, //
+ {"mp2", "audio/mpeg"}, //
+ {"mp3", "audio/mpeg"}, //
+ {"mp4", "video/mp4"}, //
+ {"mpeg", "video/mpeg"}, //
+ {"mpg", "video/mpeg"}, //
+ {"oga", "audio/ogg"}, //
+ {"ogg", "application/ogg"}, //
+ {"ogv", "video/ogg"}, //
+ {"ogx", "application/ogg"}, //
+ {"otf", "font/otf"}, //
+ {"pdf", "application/pdf"}, //
+ {"png", "image/png"}, //
+ {"rar", "application/vnd.rar"}, //
+ {"rtf", "application/rtf"}, //
+ {"s", "text/plain"}, //
+ {"sh", "application/x-sh"}, //
+ {"svg", "image/svg+xml"}, //
+ {"swf", "application/x-shockwave-flash"}, //
+ {"tar", "application/x-tar"}, //
+ {"tiff", "image/tiff"}, //
+ {"ttf", "font/ttf"}, //
+ {"txt", "text/plain"}, //
+ {"wav", "audio/x-wav"}, //
+ {"weba", "audio/webm"}, //
+ {"webm", "video/webm"}, //
+ {"webp", "image/webp"}, //
+ {"woff", "font/woff"}, //
+ {"woff2", "font/woff2"}, //
+ {"xhtml", "application/xhtml+xml"}, //
+ {"xls", "application/vnd.ms-excel"}, //
+ {"xml", "application/xml"}, //
+ {"zip", "application/zip"}, //
+};
+
+static const char kRegCode[][9] = {
+ "OK", "NOMATCH", "BADPAT", "ECOLLATE", "ECTYPE", "EESCAPE", "ESUBREG",
+ "EBRACK", "EPAREN", "EBRACE", "BADBR", "ERANGE", "ESPACE", "BADRPT",
};
struct Buffer {
- size_t n;
+ size_t n, c;
char *p;
};
@@ -245,36 +185,20 @@ struct Strings {
char **p;
};
-struct Parser {
- int i;
- int c;
- const char *data;
- int size;
- bool isform;
- bool islatin1;
- char *p;
- char *q;
-};
-
-struct Params {
- size_t n;
- struct Param {
- struct Buffer key;
- struct Buffer val; // val.n may be SIZE_MAX
- } * p;
-};
-
-struct Request {
- struct Buffer path;
- struct Params params;
- struct Buffer fragment;
-};
-
static struct Freelist {
- size_t n;
+ size_t n, c;
void **p;
} freelist;
+static struct Unmaplist {
+ size_t n, c;
+ struct Unmap {
+ int f;
+ void *p;
+ size_t n;
+ } * p;
+} unmaplist;
+
static struct Redirects {
size_t n;
struct Redirect {
@@ -288,8 +212,10 @@ static struct Redirects {
static struct Assets {
uint32_t n;
struct Asset {
- uint32_t lf;
+ bool istext;
uint32_t hash;
+ uint64_t cf;
+ uint64_t lf;
int64_t lastmodified;
char *lastmodifiedstr;
struct File {
@@ -300,7 +226,105 @@ static struct Assets {
} assets;
static struct Shared {
- int workers; //
+ int workers;
+ long double nowish;
+ long double lastmeltdown;
+ char currentdate[32];
+ struct rusage server;
+ struct rusage children;
+ long accepterrors;
+ long acceptinterrupts;
+ long acceptresets;
+ long badlengths;
+ long badmessages;
+ long badmethods;
+ long badranges;
+ long closeerrors;
+ long compressedresponses;
+ long connectionshandled;
+ long connectsrefused;
+ long continues;
+ long decompressedresponses;
+ long deflates;
+ long dropped;
+ long dynamicrequests;
+ long emfiles;
+ long enetdowns;
+ long enfiles;
+ long enobufs;
+ long enomems;
+ long enonets;
+ long errors;
+ long expectsrefused;
+ long failedchildren;
+ long forbiddens;
+ long forkerrors;
+ long frags;
+ long fumbles;
+ long http09;
+ long http10;
+ long http11;
+ long http12;
+ long hugepayloads;
+ long identityresponses;
+ long inflates;
+ long listingrequests;
+ long loops;
+ long mapfails;
+ long maps;
+ long meltdowns;
+ long messageshandled;
+ long missinglengths;
+ long netafrinic;
+ long netanonymous;
+ long netapnic;
+ long netapple;
+ long netarin;
+ long netatt;
+ long netcogent;
+ long netcomcast;
+ long netdod;
+ long netford;
+ long netlacnic;
+ long netloopback;
+ long netother;
+ long netprivate;
+ long netprudential;
+ long netripe;
+ long nettestnet;
+ long netusps;
+ long notfounds;
+ long notmodifieds;
+ long openfails;
+ long partialresponses;
+ long payloaddisconnects;
+ long pipelinedrequests;
+ long precompressedresponses;
+ long readerrors;
+ long readinterrupts;
+ long readresets;
+ long readtimeouts;
+ long redirects;
+ long reloads;
+ long rewrites;
+ long serveroptions;
+ long shutdowns;
+ long slowloris;
+ long slurps;
+ long statfails;
+ long staticrequests;
+ long stats;
+ long statuszrequests;
+ long synchronizationfailures;
+ long terminatedchildren;
+ long thiscorruption;
+ long transfersrefused;
+ long urisrefused;
+ long verifies;
+ long writeerrors;
+ long writeinterruputs;
+ long writeresets;
+ long writetimeouts;
} * shared;
static bool killed;
@@ -308,13 +332,15 @@ static bool istext;
static bool zombied;
static bool gzipped;
static bool branded;
+static bool funtrace;
static bool meltdown;
-static bool unbranded;
static bool heartless;
static bool printport;
static bool heartbeat;
static bool daemonize;
+static bool logrusage;
static bool logbodies;
+static bool loglatency;
static bool terminated;
static bool uniprocess;
static bool invalidated;
@@ -323,6 +349,8 @@ static bool checkedmethod;
static bool connectionclose;
static bool keyboardinterrupt;
static bool encouragekeepalive;
+static bool loggednetworkorigin;
+static bool hasluaglobalhandler;
static int frags;
static int gmtoff;
@@ -331,23 +359,23 @@ static int client;
static int daemonuid;
static int daemongid;
static int statuscode;
-static unsigned httpversion;
+static int messageshandled;
static uint32_t clientaddrsize;
static lua_State *L;
static size_t zsize;
-static void *content;
-static uint8_t *zdir;
+static char *content;
+static uint8_t *cdir;
static uint8_t *zmap;
static size_t hdrsize;
static size_t msgsize;
static size_t amtread;
+static char *extrahdrs;
static char *luaheaderp;
-static const char *sauce;
static const char *brand;
static const char *pidpath;
static const char *logpath;
-static int64_t programtime;
+static struct Strings loops;
static size_t contentlength;
static int64_t cacheseconds;
static uint8_t gzip_footer[8];
@@ -355,24 +383,194 @@ static const char *serverheader;
static struct Strings stagedirs;
static struct Strings hidepaths;
-static struct Buffer logo;
static struct Buffer inbuf;
static struct Buffer hdrbuf;
static struct Buffer outbuf;
-static struct Request request;
+static struct linger linger;
+static struct timeval timeout;
+static struct Buffer effectivepath;
+
+static struct Url url;
+static struct HttpRequest msg;
+static char slashpath[PATH_MAX];
-static long double nowish;
static long double startread;
-static long double lastmeltdown;
+static long double lastrefresh;
+static long double startserver;
static long double startrequest;
static long double startconnection;
static struct sockaddr_in serveraddr;
static struct sockaddr_in clientaddr;
-static struct HttpRequest msg;
-static char currentdate[32];
-static char clientaddrstr[32];
-static char serveraddrstr[32];
+static wontreturn void PrintUsage(FILE *f, int rc) {
+ /* clang-format off */
+ fprintf(f, "\
+SYNOPSIS\n\
+\n\
+ %s [-hvduzmbagf] [-p PORT] [-- SCRIPTARGS...]\n\
+\n\
+DESCRIPTION\n\
+\n\
+ redbean - single-file distributable web server\n\
+\n\
+FLAGS\n\
+\n\
+ -h help\n\
+ -v verbosity [repeat]\n\
+ -d daemonize\n\
+ -u uniprocess\n\
+ -z print port\n\
+ -m log messages\n\
+ -b log message body\n\
+ -a log resource usage\n\
+ -g log handler latency\n"
+#ifndef TINY
+" -f log worker function calls\n"
+#endif
+" -H K:V sets http header globally [repeat]\n\
+ -D DIR serve assets from local directory [repeat]\n\
+ -t MS tunes read and write timeouts [default 30000]\n\
+ -c SEC configures static asset cache-control headers\n\
+ -r /X=/Y redirect X to Y [repeat]\n\
+ -R /X=/Y rewrites X to Y [repeat]\n\
+ -l ADDR listen ip [default 0.0.0.0]\n\
+ -p PORT listen port [default 8080]\n\
+ -L PATH log file location\n\
+ -P PATH pid file location\n\
+ -U INT daemon set user id\n\
+ -G INT daemon set group id\n\
+\n\
+FEATURES\n\
+\n"
+#ifndef STATIC
+" - Lua v5.4\n"
+#endif
+" - HTTP v0.9\n\
+ - HTTP v1.0\n\
+ - HTTP v1.1\n\
+ - Pipelining\n\
+ - Accounting\n\
+ - Content-Encoding\n\
+ - Range / Content-Range\n\
+ - Last-Modified / If-Modified-Since\n\
+\n\
+USAGE\n\
+\n\
+ This executable is also a ZIP file that contains static assets.\n\
+ You can run redbean interactively in your terminal as follows:\n\
+\n\
+ ./redbean.com -vvvmbag # starts server verbosely\n\
+ open http://127.0.0.1:8080/ # shows zip listing page\n\
+ CTRL-C # 1x: graceful shutdown\n\
+ CTRL-C # 2x: forceful shutdown\n\
+\n\
+ You can override the default listing page by adding:\n\
+\n"
+#ifndef STATIC
+" zip redbean.com index.lua # lua server pages take priority\n"
+#endif
+" zip redbean.com index.html # default page for directory\n\
+\n\
+ The listing page only applies to the root directory. However the\n\
+ default index page applies to subdirectories too. In order for it\n\
+ to work, there needs to be an empty directory entry in the zip.\n\
+ That should already be the default practice of your zip editor.\n\
+\n\
+ wget \\\n\
+ --mirror \\\n\
+ --convert-links \\\n\
+ --adjust-extension \\\n\
+ --page-requisites \\\n\
+ --no-parent \\\n\
+ --no-if-modified-since \\\n\
+ http://a.example/index.html\n\
+ zip -r redbean.com a.example/ # default page for directory\n\
+\n\
+ redbean normalizes the trailing slash for you automatically:\n\
+\n\
+ $ printf 'GET /a.example HTTP/1.0\\n\\n' | nc 127.0.0.1 8080\n\
+ HTTP/1.0 307 Temporary Redirect\n\
+ Location: /a.example/\n\
+\n\
+ Virtual hosting is accomplished this way too. The Host is simply\n\
+ prepended to the path, and if it doesn't exist, it gets removed.\n\
+\n\
+ $ printf 'GET / HTTP/1.1\\nHost:a.example\\n\\n' | nc 127.0.0.1 8080\n\
+ HTTP/1.1 200 OK\n\
+ Link: ; rel=\"canonical\"\n\
+\n\
+ If you mirror a lot of websites within your redbean then you can\n\
+ actually tell your browser that redbean is your proxy server, in\n\
+ which redbean will act as your private version of the Internet.\n\
+\n\
+ $ printf 'GET http://a.example HTTP/1.0\\n\\n' | nc 127.0.0.1 8080\n\
+ HTTP/1.0 200 OK\n\
+ Link: ; rel=\"canonical\"\n\
+\n\
+ If you use a reverse proxy, then redbean recognizes the following\n\
+ provided that the proxy forwards requests over the local network:\n\
+\n\
+ X-Forwarded-For: 203.0.113.42:31337\n\
+ X-Forwarded-Host: foo.example:80\n\
+\n\
+ There's a text/plain statistics page called /statusz that makes\n\
+ it easy to track and monitor the health of your redbean:\n\
+\n\
+ printf 'GET /statusz\\n\\n' | nc 127.0.0.1 8080\n\
+\n\
+ redbean will display an error page using the /redbean.png logo\n\
+ by default, embedded as a bas64 data uri. You can override the\n\
+ custom page for various errors by adding files to the zip root.\n\
+\n\
+ zip redbean.com 404.html # custom not found page\n\
+\n\
+ Audio video content should not be compressed in your ZIP files.\n\
+ Uncompressed assets enable browsers to send Range HTTP request.\n\
+ On the other hand compressed assets are best for gzip encoding.\n\
+\n\
+ zip redbean.com index.html # adds file\n\
+ zip -0 redbean.com video.mp4 # adds without compression\n\
+\n\
+ You can have redbean run as a daemon by doing the following:\n\
+\n\
+ redbean.com -vv -d -L redbean.log -P redbean.pid\n\
+ kill -TERM $(cat redbean.pid) # 1x: graceful shutdown\n\
+ kill -TERM $(cat redbean.pid) # 2x: forceful shutdown\n\
+\n\
+ redbean currently has a 32kb limit on request messages and 64kb\n\
+ including the payload. redbean will grow to whatever the system\n\
+ limits allow. Should fork() or accept() fail redbean will react\n\
+ by going into \"meltdown mode\" which closes lingering workers.\n\
+ You can trigger this at any time using:\n\
+\n\
+ kill -USR2 $(cat redbean.pid)\n\
+\n\
+ Another failure condition is running out of disk space in which\n\
+ case redbean reacts by truncating the log file. Lastly, redbean\n\
+ does the best job possible reporting on resource usage when the\n\
+ logger is in debug mode noting that NetBSD is the best at this.\n\
+\n\
+ Your redbean is an actually portable executable, that's able to\n\
+ run on six different operating systems. To do that, it needs to\n\
+ overwrite its own MZ header at startup, with ELF or Mach-O, and\n\
+ then puts the original back once the program loads. If you want\n\
+ your redbean to follow the platform-local executable convention\n\
+ then delete the /.ape file from zip.\n\
+\n\
+ redbean contains software licensed ISC, MIT, BSD-2, BSD-3, zlib\n\
+ which makes it a permissively licensed gift to anyone who might\n\
+ find it useful. The transitive closure of legalese can be found\n\
+ inside the binary. redbean also respects your privacy and won't\n\
+ phone home because your computer is its home.\n\
+\n\
+SEE ALSO\n\
+\n\
+ https://justine.lol/redbean/index.html\n\
+ https://news.ycombinator.com/item?id=26271117\n\
+\n", program_invocation_name);
+ /* clang-format on */
+ exit(rc);
+}
static void OnChld(void) {
zombied = true;
@@ -411,6 +609,14 @@ static void OnHup(void) {
}
}
+static bool SlicesEqual(const char *a, size_t n, const char *b, size_t m) {
+ return n == m && !memcmp(a, b, n);
+}
+
+static bool SlicesEqualCase(const char *a, size_t n, const char *b, size_t m) {
+ return n == m && !memcasecmp(a, b, n);
+}
+
static int CompareSlices(const char *a, size_t n, const char *b, size_t m) {
int c;
if ((c = memcmp(a, b, MIN(n, m)))) return c;
@@ -427,6 +633,106 @@ static int CompareSlicesCase(const char *a, size_t n, const char *b, size_t m) {
return 0;
}
+static void *FreeLater(void *p) {
+ if (p) {
+ if (++freelist.n > freelist.c) {
+ freelist.c = freelist.n + 2;
+ freelist.c += freelist.c >> 1;
+ freelist.p = xrealloc(freelist.p, freelist.c * sizeof(*freelist.p));
+ }
+ freelist.p[freelist.n - 1] = p;
+ }
+ return p;
+}
+
+static void UnmapLater(int f, void *p, size_t n) {
+ if (++unmaplist.n > unmaplist.c) {
+ unmaplist.c = unmaplist.n + 2;
+ unmaplist.c += unmaplist.c >> 1;
+ unmaplist.p = xrealloc(unmaplist.p, unmaplist.c * sizeof(*unmaplist.p));
+ }
+ unmaplist.p[unmaplist.n - 1].f = f;
+ unmaplist.p[unmaplist.n - 1].p = p;
+ unmaplist.p[unmaplist.n - 1].n = n;
+}
+
+static void CollectGarbage(void) {
+ DestroyHttpRequest(&msg);
+ while (freelist.n) {
+ free(freelist.p[--freelist.n]);
+ }
+ while (unmaplist.n) {
+ --unmaplist.n;
+ LOGIFNEG1(munmap(unmaplist.p[unmaplist.n].p, unmaplist.p[unmaplist.n].n));
+ LOGIFNEG1(close(unmaplist.p[unmaplist.n].f));
+ }
+}
+
+static void UseOutput(void) {
+ content = FreeLater(outbuf.p);
+ contentlength = outbuf.n;
+ outbuf.p = 0;
+ outbuf.n = 0;
+ outbuf.c = 0;
+}
+
+static void DropOutput(void) {
+ free(outbuf.p);
+ outbuf.p = 0;
+ outbuf.n = 0;
+ outbuf.c = 0;
+}
+
+static void ClearOutput(void) {
+ outbuf.n = 0;
+}
+
+static void Grow(size_t n) {
+ do {
+ if (outbuf.c) {
+ outbuf.c += outbuf.c >> 1;
+ } else {
+ outbuf.c = 16 * 1024;
+ }
+ } while (n > outbuf.c);
+ outbuf.p = xrealloc(outbuf.p, outbuf.c);
+}
+
+static void AppendData(const char *data, size_t size) {
+ size_t n;
+ n = outbuf.n + size;
+ if (n > outbuf.c) Grow(n);
+ memcpy(outbuf.p + outbuf.n, data, size);
+ outbuf.n = n;
+}
+
+static void Append(const char *fmt, ...) {
+ int n;
+ char *p;
+ va_list va, vb;
+ va_start(va, fmt);
+ va_copy(vb, va);
+ n = vsnprintf(outbuf.p + outbuf.n, outbuf.c - outbuf.n, fmt, va);
+ if (n >= outbuf.c - outbuf.n) {
+ Grow(outbuf.n + n + 1);
+ vsnprintf(outbuf.p + outbuf.n, outbuf.c - outbuf.n, fmt, vb);
+ }
+ va_end(vb);
+ va_end(va);
+ outbuf.n += n;
+}
+
+static char *MergePaths(const char *p, size_t n, const char *q, size_t m,
+ size_t *z) {
+ char *r;
+ if (n && p[n - 1] == '/') --n;
+ if (m && q[0] == '/') ++q, --m;
+ r = xmalloc(n + 1 + m + 1);
+ mempcpy(mempcpy(mempcpy(mempcpy(r, p, n), "/", 1), q, m), "", 1);
+ if (z) *z = n + 1 + m;
+ return r;
+}
+
static long FindRedirect(const char *path, size_t n) {
int c, m, l, r, z;
l = 0;
@@ -489,7 +795,7 @@ static int CompareInts(const uint64_t x, uint64_t y) {
return x > y ? 1 : x < y ? -1 : 0;
}
-static const char *FindContentType(uint64_t ext) {
+static const char *BisectContentType(uint64_t ext) {
int c, m, l, r;
l = 0;
r = ARRAYLEN(kContentTypeExtension) - 1;
@@ -507,7 +813,7 @@ static const char *FindContentType(uint64_t ext) {
return NULL;
}
-static const char *GetContentType2(const char *path, size_t n) {
+static const char *FindContentType(const char *path, size_t n) {
size_t i;
uint64_t x;
const char *p, *r;
@@ -516,7 +822,7 @@ static const char *GetContentType2(const char *path, size_t n) {
x <<= 8;
x |= path[i] & 0xFF;
}
- if ((r = FindContentType(bswap_64(x)))) {
+ if ((r = BisectContentType(bswap_64(x)))) {
return r;
}
}
@@ -525,38 +831,95 @@ static const char *GetContentType2(const char *path, size_t n) {
static const char *GetContentType(struct Asset *a, const char *path, size_t n) {
const char *r;
- if (a->file && (r = GetContentType2(a->file->path, strlen(a->file->path)))) {
+ if (a->file && (r = FindContentType(a->file->path, strlen(a->file->path)))) {
return r;
}
return firstnonnull(
- GetContentType2(path, n),
- firstnonnull(GetContentType2(ZIP_LFILE_NAME(zmap + a->lf),
+ FindContentType(path, n),
+ firstnonnull(FindContentType(ZIP_LFILE_NAME(zmap + a->lf),
ZIP_LFILE_NAMESIZE(zmap + a->lf)),
- "application/octet-stream"));
+ a->istext ? "text/plain" : "application/octet-stream"));
}
-static void DescribeAddress(char buf[32], const struct sockaddr_in *addr) {
- char *p = buf;
- const uint8_t *ip4 = (const uint8_t *)&addr->sin_addr.s_addr;
- p += uint64toarray_radix10(ip4[0], p), *p++ = '.';
- p += uint64toarray_radix10(ip4[1], p), *p++ = '.';
- p += uint64toarray_radix10(ip4[2], p), *p++ = '.';
- p += uint64toarray_radix10(ip4[3], p), *p++ = ':';
- p += uint64toarray_radix10(ntohs(addr->sin_port), p);
- *p = '\0';
+static void DescribeAddress(char buf[32], uint32_t addr, uint16_t port) {
+ char *p;
+ const char *s;
+ p = buf;
+ p += uint64toarray_radix10((addr & 0xFF000000) >> 030, p), *p++ = '.';
+ p += uint64toarray_radix10((addr & 0x00FF0000) >> 020, p), *p++ = '.';
+ p += uint64toarray_radix10((addr & 0x0000FF00) >> 010, p), *p++ = '.';
+ p += uint64toarray_radix10((addr & 0x000000FF) >> 000, p), *p++ = ':';
+ p += uint64toarray_radix10(port, p);
+ if ((s = GetIpCategoryName(CategorizeIp(addr)))) {
+ *p++ = ' ';
+ p = stpcpy(p, s);
+ }
+ *p++ = '\0';
+}
+
+static void GetServerAddr(uint32_t *ip, uint16_t *port) {
+ *ip = ntohl(serveraddr.sin_addr.s_addr);
+ if (port) *port = ntohs(serveraddr.sin_port);
+}
+
+static void GetClientAddr(uint32_t *ip, uint16_t *port) {
+ *ip = ntohl(clientaddr.sin_addr.s_addr);
+ if (port) *port = ntohs(clientaddr.sin_port);
+}
+
+static void GetRemoteAddr(uint32_t *ip, uint16_t *port) {
+ GetClientAddr(ip, port);
+ if (HasHeader(kHttpXForwardedFor) &&
+ (IsPrivateIp(*ip) || IsLoopbackIp(*ip))) {
+ ParseForwarded(HeaderData(kHttpXForwardedFor),
+ HeaderLength(kHttpXForwardedFor), ip, port);
+ }
+}
+
+static char *DescribeClient(void) {
+ uint32_t ip;
+ uint16_t port;
+ static char clientaddrstr[32];
+ GetRemoteAddr(&ip, &port);
+ DescribeAddress(clientaddrstr, ip, port);
+ return clientaddrstr;
+}
+
+static char *DescribeServer(void) {
+ uint32_t ip;
+ uint16_t port;
+ static char serveraddrstr[32];
+ GetServerAddr(&ip, &port);
+ DescribeAddress(serveraddrstr, ip, port);
+ return serveraddrstr;
}
static void ProgramBrand(const char *s) {
free(brand);
free(serverheader);
brand = strdup(s);
- if (!strstr(s, "redbean")) unbranded = true;
if (!(serverheader = EncodeHttpHeaderValue(brand, -1, 0))) {
fprintf(stderr, "error: brand isn't latin1 encodable: %`'s", brand);
exit(1);
}
}
+static void ProgramLinger(long sec) {
+ linger.l_onoff = sec > 0;
+ linger.l_linger = MAX(0, sec);
+}
+
+static void ProgramTimeout(long ms) {
+ ldiv_t d;
+ if (ms <= 30) {
+ fprintf(stderr, "error: timeout needs to be 31ms or greater\n");
+ exit(1);
+ }
+ d = ldiv(ms, 1000);
+ timeout.tv_sec = d.quot;
+ timeout.tv_usec = d.rem * 1000;
+}
+
static void ProgramCache(long x) {
cacheseconds = x;
}
@@ -566,19 +929,20 @@ static void ProgramPort(long x) {
}
static void SetDefaults(void) {
- ProgramBrand("redbean/0.3");
+#ifdef STATIC
+ ProgramBrand("redbean-static/0.4");
+#else
+ ProgramBrand("redbean/0.4");
+#endif
+ __log_level = kLogWarn;
ProgramCache(-1);
+ ProgramTimeout(30 * 1000);
ProgramPort(DEFAULT_PORT);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = INADDR_ANY;
if (IsWindows()) uniprocess = true;
}
-static wontreturn void PrintUsage(FILE *f, int rc) {
- fprintf(f, "SYNOPSIS\n\n %s%s", program_invocation_name, USAGE);
- exit(rc);
-}
-
static char *RemoveTrailingSlashes(char *s) {
size_t n;
n = strlen(s);
@@ -591,19 +955,58 @@ static void AddString(struct Strings *l, char *s) {
l->p[l->n - 1] = s;
}
+static bool HasString(struct Strings *l, char *s) {
+ size_t i;
+ for (i = 0; i < l->n; ++i) {
+ if (!strcmp(l->p[i], s)) {
+ return true;
+ }
+ }
+ return false;
+}
+
static void AddStagingDirectory(const char *dirpath) {
char *s;
s = RemoveTrailingSlashes(strdup(dirpath));
- if (!isdirectory(s)) {
- fprintf(stderr, "error: not a directory: %s\n", s);
+ if (isempty(s) || !isdirectory(s)) {
+ fprintf(stderr, "error: not a directory: %`'s\n", s);
exit(1);
}
AddString(&stagedirs, s);
}
+static void ProgramHeader(const char *s) {
+ char *p, *v, *h;
+ if ((p = strchr(s, ':')) && IsValidHttpToken(s, p - s) &&
+ (v = EncodeLatin1(p + 1, -1, 0, kControlC0 | kControlC1 | kControlWs))) {
+ switch (GetHttpHeader(s, p - s)) {
+ case kHttpDate:
+ case kHttpConnection:
+ case kHttpContentLength:
+ case kHttpContentEncoding:
+ case kHttpContentRange:
+ fprintf(stderr, "error: can't program header: %`'s\n", s);
+ exit(1);
+ case kHttpServer:
+ ProgramBrand(p + 1);
+ break;
+ default:
+ p = xasprintf("%s%.*s:%s\r\n", extrahdrs ? extrahdrs : "", p - s, s, v);
+ free(extrahdrs);
+ extrahdrs = p;
+ break;
+ }
+ free(v);
+ } else {
+ fprintf(stderr, "error: illegal header: %`'s\n", s);
+ exit(1);
+ }
+}
+
static void GetOpts(int argc, char *argv[]) {
int opt;
- while ((opt = getopt(argc, argv, "zhduvmbl:p:w:r:R:c:L:P:U:G:B:D:")) != -1) {
+ while ((opt = getopt(argc, argv, "azhdugvmbfl:p:r:R:H:c:L:P:U:G:B:D:t:")) !=
+ -1) {
switch (opt) {
case 'v':
__log_level++;
@@ -611,9 +1014,15 @@ static void GetOpts(int argc, char *argv[]) {
case 'd':
daemonize = true;
break;
+ case 'a':
+ logrusage = true;
+ break;
case 'u':
uniprocess = true;
break;
+ case 'g':
+ loglatency = true;
+ break;
case 'm':
logmessages = true;
break;
@@ -623,9 +1032,15 @@ static void GetOpts(int argc, char *argv[]) {
case 'z':
printport = true;
break;
+ case 'f':
+ funtrace = true;
+ break;
case 'k':
encouragekeepalive = true;
break;
+ case 't':
+ ProgramTimeout(strtol(optarg, NULL, 0));
+ break;
case 'r':
ProgramRedirectArg(307, optarg);
break;
@@ -647,6 +1062,9 @@ static void GetOpts(int argc, char *argv[]) {
case 'B':
ProgramBrand(optarg);
break;
+ case 'H':
+ ProgramHeader(optarg);
+ break;
case 'L':
logpath = optarg;
break;
@@ -673,7 +1091,11 @@ static void GetOpts(int argc, char *argv[]) {
static void Daemonize(void) {
char ibuf[21];
int i, fd, pid;
- for (i = 0; i < 128; ++i) close(i);
+ for (i = 3; i < 128; ++i) {
+ if (i != server) {
+ close(i);
+ }
+ }
if ((pid = fork()) > 0) exit(0);
setsid();
if ((pid = fork()) > 0) _exit(0);
@@ -687,66 +1109,172 @@ static void Daemonize(void) {
freopen("/dev/null", "r", stdin);
freopen(logpath, "a", stdout);
freopen(logpath, "a", stderr);
- LOGIFNEG1(setuid(daemonuid));
LOGIFNEG1(setgid(daemongid));
+ LOGIFNEG1(setuid(daemonuid));
}
-static void OnWorkerExit(int pid, int ws) {
- int w;
- w = --shared->workers;
+static void ReportWorkerExit(int pid, int ws) {
+ --shared->workers;
if (WIFEXITED(ws)) {
if (WEXITSTATUS(ws)) {
- WARNF("worker %d exited with %d (%,d workers remain)", pid,
- WEXITSTATUS(ws), w);
+ LockInc(&shared->failedchildren);
+ WARNF("%d exited with %d (%,d workers remain)", pid, WEXITSTATUS(ws),
+ shared->workers);
} else {
- DEBUGF("worker %d exited (%,d workers remain)", pid, w);
+ DEBUGF("%d exited (%,d workers remain)", pid, shared->workers);
}
} else {
- WARNF("worker %d terminated with %s (%,d workers remain)", pid,
- strsignal(WTERMSIG(ws)), w);
+ LockInc(&shared->terminatedchildren);
+ WARNF("%d terminated with %s (%,d workers remain)", pid,
+ strsignal(WTERMSIG(ws)), shared->workers);
}
}
+static void AppendResourceReport(struct rusage *ru, const char *nl) {
+ long utime, stime;
+ long double ticks;
+ if (ru->ru_maxrss) {
+ Append("ballooned to %,ldkb in size%s", ru->ru_maxrss, nl);
+ }
+ if ((utime = ru->ru_utime.tv_sec * 1000000 + ru->ru_utime.tv_usec) |
+ (stime = ru->ru_stime.tv_sec * 1000000 + ru->ru_stime.tv_usec)) {
+ ticks = ceill((long double)(utime + stime) / (1000000.L / CLK_TCK));
+ Append("needed %,ldµs cpu (%d%% kernel)%s", utime + stime,
+ (int)((long double)stime / (utime + stime) * 100), nl);
+ if (ru->ru_idrss) {
+ Append("needed %,ldkb memory on average%s", lroundl(ru->ru_idrss / ticks),
+ nl);
+ }
+ if (ru->ru_isrss) {
+ Append("needed %,ldkb stack on average%s", lroundl(ru->ru_isrss / ticks),
+ nl);
+ }
+ if (ru->ru_ixrss) {
+ Append("mapped %,ldkb shared on average%s", lroundl(ru->ru_ixrss / ticks),
+ nl);
+ }
+ }
+ if (ru->ru_minflt || ru->ru_majflt) {
+ Append("caused %,ld page faults (%d%% memcpy)%s",
+ ru->ru_minflt + ru->ru_majflt,
+ (int)((long double)ru->ru_minflt / (ru->ru_minflt + ru->ru_majflt) *
+ 100),
+ nl);
+ }
+ if (ru->ru_nvcsw + ru->ru_nivcsw > 1) {
+ Append(
+ "%,ld context switches (%d%% consensual)%s",
+ ru->ru_nvcsw + ru->ru_nivcsw,
+ (int)((long double)ru->ru_nvcsw / (ru->ru_nvcsw + ru->ru_nivcsw) * 100),
+ nl);
+ }
+ if (ru->ru_msgrcv || ru->ru_msgsnd) {
+ Append("received %,ld message%s and sent %,ld%s", ru->ru_msgrcv,
+ ru->ru_msgrcv == 1 ? "" : "s", ru->ru_msgsnd, nl);
+ }
+ if (ru->ru_inblock || ru->ru_oublock) {
+ Append("performed %,ld read%s and %,ld write i/o operations%s",
+ ru->ru_inblock, ru->ru_inblock == 1 ? "" : "s", ru->ru_oublock, nl);
+ }
+ if (ru->ru_nsignals) {
+ Append("received %,ld signals%s", ru->ru_nsignals, nl);
+ }
+ if (ru->ru_nswap) {
+ Append("got swapped %,ld times%s", ru->ru_nswap, nl);
+ }
+}
+
+static void AddTimeval(struct timeval *x, const struct timeval *y) {
+ x->tv_sec += y->tv_sec;
+ x->tv_usec += y->tv_usec;
+ if (x->tv_usec >= 1000000) {
+ x->tv_usec -= 1000000;
+ x->tv_sec += 1;
+ }
+}
+
+static void AddRusage(struct rusage *x, const struct rusage *y) {
+ AddTimeval(&x->ru_utime, &y->ru_utime);
+ AddTimeval(&x->ru_stime, &y->ru_stime);
+ x->ru_maxrss = MAX(x->ru_maxrss, y->ru_maxrss);
+ x->ru_ixrss += y->ru_ixrss;
+ x->ru_idrss += y->ru_idrss;
+ x->ru_isrss += y->ru_isrss;
+ x->ru_minflt += y->ru_minflt;
+ x->ru_majflt += y->ru_majflt;
+ x->ru_nswap += y->ru_nswap;
+ x->ru_inblock += y->ru_inblock;
+ x->ru_oublock += y->ru_oublock;
+ x->ru_msgsnd += y->ru_msgsnd;
+ x->ru_msgrcv += y->ru_msgrcv;
+ x->ru_nsignals += y->ru_nsignals;
+ x->ru_nvcsw += y->ru_nvcsw;
+ x->ru_nivcsw += y->ru_nivcsw;
+}
+
+static void ReportWorkerResources(int pid, struct rusage *ru) {
+ const char *s;
+ if (logrusage || LOGGABLE(kLogDebug)) {
+ AppendResourceReport(ru, "\n");
+ if (outbuf.n) {
+ if ((s = IndentLines(outbuf.p, outbuf.n - 1, 0, 1))) {
+ LOGF("resource report for pid %d\n%s", pid, s);
+ free(s);
+ }
+ ClearOutput();
+ }
+ }
+}
+
+static void HandleWorkerExit(int pid, int ws, struct rusage *ru) {
+ ++shared->connectionshandled;
+ AddRusage(&shared->children, ru);
+ ReportWorkerExit(pid, ws);
+ ReportWorkerResources(pid, ru);
+}
+
static void WaitAll(void) {
int ws, pid;
+ struct rusage ru;
for (;;) {
- if ((pid = wait(&ws)) != -1) {
- OnWorkerExit(pid, ws);
+ if ((pid = wait4(-1, &ws, 0, &ru)) != -1) {
+ HandleWorkerExit(pid, ws, &ru);
} else {
if (errno == ECHILD) break;
if (errno == EINTR) {
if (killed) {
killed = false;
terminated = false;
- WARNF("%s redbean shall terminate harder", serveraddrstr);
+ WARNF("redbean shall terminate harder");
LOGIFNEG1(kill(0, SIGTERM));
}
continue;
}
- FATALF("%s wait error %s", serveraddrstr, strerror(errno));
+ FATALF("%s wait error %s", DescribeServer(), strerror(errno));
}
}
}
static void ReapZombies(void) {
int ws, pid;
- zombied = false;
+ struct rusage ru;
do {
- if ((pid = waitpid(-1, &ws, WNOHANG)) != -1) {
+ zombied = false;
+ if ((pid = wait4(-1, &ws, WNOHANG, &ru)) != -1) {
if (pid) {
- OnWorkerExit(pid, ws);
+ HandleWorkerExit(pid, ws, &ru);
} else {
break;
}
} else {
if (errno == ECHILD) break;
if (errno == EINTR) continue;
- FATALF("%s wait error %s", serveraddrstr, strerror(errno));
+ FATALF("%s wait error %s", DescribeServer(), strerror(errno));
}
} while (!terminated);
}
-static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) {
+static inline ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) {
ssize_t rc;
size_t wrote;
do {
@@ -764,6 +1292,7 @@ static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) {
}
} while (wrote);
} else if (errno == EINTR) {
+ LockInc(&shared->writeinterruputs);
if (killed || (meltdown && nowl() - startread > 2)) {
return -1;
}
@@ -774,46 +1303,22 @@ static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) {
return 0;
}
-static uint32_t Hash(const void *data, size_t size) {
- uint32_t h;
- h = crc32c(0, data, size);
- if (!h) h = 1;
- return h;
-}
-
-static bool HasHeader(int h) {
- return msg.headers[h].b > msg.headers[h].a;
-}
-
-static int CompareHeader(int h, const char *s) {
- return CompareSlices(s, strlen(s), inbuf.p + msg.headers[h].a,
- msg.headers[h].b - msg.headers[h].a);
-}
-
-static bool HeaderEquals(int h, const char *s) {
- return !CompareHeader(h, s);
-}
-
static bool ClientAcceptsGzip(void) {
- return httpversion >= 100 &&
- !!memmem(inbuf.p + msg.headers[kHttpAcceptEncoding].a,
- msg.headers[kHttpAcceptEncoding].b -
- msg.headers[kHttpAcceptEncoding].a,
- "gzip", 4);
+ return msg.version >= 10 && /* RFC1945 § 3.5 */
+ HeaderHas(&msg, inbuf.p, kHttpAcceptEncoding, "gzip", 4);
}
static void UpdateCurrentDate(long double now) {
int64_t t;
struct tm tm;
- t = nowish = now;
+ t = now;
+ shared->nowish = now;
gmtime_r(&t, &tm);
- FormatHttpDateTime(currentdate, &tm);
+ FormatHttpDateTime(shared->currentdate, &tm);
}
-static int64_t GetGmtOffset(void) {
- int64_t t;
+static int64_t GetGmtOffset(int64_t t) {
struct tm tm;
- t = nowl();
localtime_r(&t, &tm);
return tm.tm_gmtoff;
}
@@ -822,20 +1327,34 @@ static int64_t LocoTimeToZulu(int64_t x) {
return x - gmtoff;
}
-static int64_t GetLastModifiedZip(const uint8_t *cfile) {
+static int64_t GetZipCfileLastModified(const uint8_t *zcf) {
const uint8_t *p, *pe;
- for (p = ZIP_CFILE_EXTRA(cfile), pe = p + ZIP_CFILE_EXTRASIZE(cfile); p < pe;
+ for (p = ZIP_CFILE_EXTRA(zcf), pe = p + ZIP_CFILE_EXTRASIZE(zcf); p + 4 <= pe;
p += ZIP_EXTRA_SIZE(p)) {
- if (ZIP_EXTRA_HEADERID(p) == kZipExtraNtfs) {
- return LocoTimeToZulu(READ64LE(ZIP_EXTRA_CONTENT(p) + 8) /
- HECTONANOSECONDS -
- MODERNITYSECONDS);
- } else if (ZIP_EXTRA_HEADERID(p) == kZipExtraExtendedTimestamp) {
- return READ32LE(ZIP_EXTRA_CONTENT(p) + 1);
+ if (ZIP_EXTRA_HEADERID(p) == kZipExtraNtfs &&
+ ZIP_EXTRA_CONTENTSIZE(p) >= 4 + 4 + 8 * 3 &&
+ READ16LE(ZIP_EXTRA_CONTENT(p) + 4) == 1 &&
+ READ16LE(ZIP_EXTRA_CONTENT(p) + 6) == 24) {
+ return READ64LE(ZIP_EXTRA_CONTENT(p) + 8) / HECTONANOSECONDS -
+ MODERNITYSECONDS;
}
}
- return LocoTimeToZulu(DosDateTimeToUnix(ZIP_CFILE_LASTMODIFIEDDATE(cfile),
- ZIP_CFILE_LASTMODIFIEDTIME(cfile)));
+ for (p = ZIP_CFILE_EXTRA(zcf), pe = p + ZIP_CFILE_EXTRASIZE(zcf); p + 4 <= pe;
+ p += ZIP_EXTRA_SIZE(p)) {
+ if (ZIP_EXTRA_HEADERID(p) == kZipExtraExtendedTimestamp &&
+ ZIP_EXTRA_CONTENTSIZE(p) >= 1 + 4 && (*ZIP_EXTRA_CONTENT(p) & 1)) {
+ return (int32_t)READ32LE(ZIP_EXTRA_CONTENT(p) + 1);
+ }
+ }
+ for (p = ZIP_CFILE_EXTRA(zcf), pe = p + ZIP_CFILE_EXTRASIZE(zcf); p + 4 <= pe;
+ p += ZIP_EXTRA_SIZE(p)) {
+ if (ZIP_EXTRA_HEADERID(p) == kZipExtraUnix &&
+ ZIP_EXTRA_CONTENTSIZE(p) >= 4 + 4) {
+ return (int32_t)READ32LE(ZIP_EXTRA_CONTENT(p) + 4);
+ }
+ }
+ return LocoTimeToZulu(DosDateTimeToUnix(ZIP_CFILE_LASTMODIFIEDDATE(zcf),
+ ZIP_CFILE_LASTMODIFIEDTIME(zcf)));
}
static bool IsCompressed(struct Asset *a) {
@@ -843,13 +1362,16 @@ static bool IsCompressed(struct Asset *a) {
ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate;
}
+static int GetMode(struct Asset *a) {
+ return a->file ? a->file->st.st_mode : GetZipCfileMode(zmap + a->cf);
+}
+
static bool IsNotModified(struct Asset *a) {
- if (httpversion < 100) return false;
+ if (msg.version < 10) return false;
if (!HasHeader(kHttpIfModifiedSince)) return false;
return a->lastmodified >=
- ParseHttpDateTime(inbuf.p + msg.headers[kHttpIfModifiedSince].a,
- msg.headers[kHttpIfModifiedSince].b -
- msg.headers[kHttpIfModifiedSince].a);
+ ParseHttpDateTime(HeaderData(kHttpIfModifiedSince),
+ HeaderLength(kHttpIfModifiedSince));
}
static char *FormatUnixHttpDateTime(char *s, int64_t t) {
@@ -859,49 +1381,28 @@ 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]);
- free(freelist.p);
- freelist.p = 0;
- freelist.n = 0;
- free(outbuf.p);
- free(request.params.p);
- DestroyHttpRequest(&msg);
-}
-
static bool IsCompressionMethodSupported(int method) {
return method == kZipCompressionNone || method == kZipCompressionDeflate;
}
static void IndexAssets(void) {
int64_t lm;
+ uint64_t cf, lf;
struct Asset *p;
- uint32_t i, n, m, cf, step, hash;
+ uint32_t i, n, m, step, hash;
CHECK_GE(HASH_LOAD_FACTOR, 2);
- n = ZIP_CDIR_RECORDS(zdir);
+ CHECK(READ32LE(cdir) == kZipCdir64HdrMagic ||
+ READ32LE(cdir) == kZipCdirHdrMagic);
+ n = GetZipCdirRecords(cdir);
m = roundup2pow(MAX(1, n) * HASH_LOAD_FACTOR);
p = xcalloc(m, sizeof(struct Asset));
- CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir));
- for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+ for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
- if (!IsCompressionMethodSupported(ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf))) {
- WARNF("don't understand zip compression method %d used by %`'.*s",
- ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf),
- ZIP_CFILE_NAMESIZE(zmap + cf), ZIP_CFILE_NAME(zmap + cf));
- continue;
- }
- if (ZIP_CFILE_NAMESIZE(zmap + cf) > 1 &&
- ZIP_CFILE_NAME(zmap + cf)[ZIP_CFILE_NAMESIZE(zmap + cf) - 1] == '/' &&
- !ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf)) {
+ lf = GetZipCfileOffset(zmap + cf);
+ if (!IsCompressionMethodSupported(ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf))) {
+ LOGF("don't understand zip compression method %d used by %`'.*s",
+ ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf),
+ ZIP_CFILE_NAMESIZE(zmap + cf), ZIP_CFILE_NAME(zmap + cf));
continue;
}
hash = Hash(ZIP_CFILE_NAME(zmap + cf), ZIP_CFILE_NAMESIZE(zmap + cf));
@@ -910,9 +1411,11 @@ static void IndexAssets(void) {
i = (hash + (step * (step + 1)) >> 1) & (m - 1);
++step;
} while (p[i].hash);
- lm = GetLastModifiedZip(zmap + cf);
+ lm = GetZipCfileLastModified(zmap + cf);
p[i].hash = hash;
- p[i].lf = ZIP_CFILE_OFFSET(zmap + cf);
+ p[i].lf = lf;
+ p[i].cf = cf;
+ p[i].istext = !!(ZIP_CFILE_INTERNALATTRIBUTES(zmap + cf) & kZipIattrText);
p[i].lastmodified = lm;
p[i].lastmodifiedstr = FormatUnixHttpDateTime(xmalloc(30), lm);
}
@@ -929,7 +1432,7 @@ static void OpenZip(const char *path) {
CHECK((zsize = st.st_size));
CHECK_NE(MAP_FAILED,
(zmap = mmap(NULL, zsize, PROT_READ, MAP_SHARED, fd, 0)));
- CHECK_NOTNULL((zdir = zipfindcentraldir(zmap, zsize)));
+ CHECK_NOTNULL((cdir = GetZipCdir(zmap, zsize)));
if (endswith(path, ".com.dbg") && (p = memmem(zmap, zsize, "MZqFpD", 6))) {
zsize -= p - zmap;
zmap = p;
@@ -937,8 +1440,9 @@ static void OpenZip(const char *path) {
close(fd);
}
-static struct Asset *FindAsset(const char *path, size_t pathlen) {
+static struct Asset *GetAssetZip(const char *path, size_t pathlen) {
uint32_t i, step, hash;
+ if (pathlen > 1 && path[0] == '/') ++path, --pathlen;
hash = Hash(path, pathlen);
for (step = 0;; ++step) {
i = (hash + (step * (step + 1)) >> 1) & (assets.n - 1);
@@ -951,234 +1455,57 @@ static struct Asset *FindAsset(const char *path, size_t pathlen) {
}
}
-static struct Asset *LocateAssetZip(const char *path, size_t pathlen) {
- char *p2, *p3, *p4;
- struct Asset *a;
- if (pathlen && path[0] == '/') ++path, --pathlen;
- if (!(a = FindAsset(path, pathlen)) &&
- (!pathlen || (pathlen && path[pathlen - 1] == '/'))) {
- p2 = strndup(path, pathlen);
- p3 = xjoinpaths(p2, "index.lua");
- if (!(a = FindAsset(p3, strlen(p3)))) {
- p4 = xjoinpaths(p2, "index.html");
- a = FindAsset(p4, strlen(p4));
- free(p4);
- }
- free(p3);
- free(p2);
- }
- return a;
-}
-
-static struct Asset *LocateAssetFile(const char *path, size_t pathlen) {
- char *p;
+static struct Asset *GetAssetFile(const char *path, size_t pathlen) {
size_t i;
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 = p = 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(xjoinpaths(p, "index.lua"))),
- &a->file->st) != -1 &&
- S_ISREG(a->file->st.st_mode)) ||
- (stat((a->file->path = FreeLater(xjoinpaths(p, "index.html"))),
- &a->file->st) != -1 &&
- S_ISREG(a->file->st.st_mode)))))) {
+ LockInc(&shared->stats);
+ a->file->path = FreeLater(
+ MergePaths(stagedirs.p[i], strlen(stagedirs.p[i]), path, pathlen, 0));
+ if (stat(a->file->path, &a->file->st) != -1) {
a->lastmodifiedstr = FormatUnixHttpDateTime(
FreeLater(xmalloc(30)),
(a->lastmodified = a->file->st.st_mtim.tv_sec));
return a;
+ } else {
+ LockInc(&shared->statfails);
}
}
}
return NULL;
}
-static struct Asset *LocateAsset(const char *path, size_t pathlen) {
+static struct Asset *GetAsset(const char *path, size_t pathlen) {
+ char *path2;
struct Asset *a;
- if (!(a = LocateAssetFile(path, pathlen))) {
- a = LocateAssetZip(path, pathlen);
+ if (!(a = GetAssetFile(path, pathlen))) {
+ if (!(a = GetAssetZip(path, pathlen))) {
+ if (pathlen > 1 && path[pathlen - 1] != '/' &&
+ pathlen + 1 <= sizeof(slashpath)) {
+ memcpy(mempcpy(slashpath, path, pathlen), "/", 1);
+ a = GetAssetZip(slashpath, pathlen + 1);
+ }
+ }
}
return a;
}
-static void EmitParamKey(struct Parser *u, struct Params *h) {
- h->p = xrealloc(h->p, ++h->n * sizeof(*h->p));
- h->p[h->n - 1].key.p = u->q;
- h->p[h->n - 1].key.n = u->p - u->q;
- u->q = u->p;
-}
-
-static void EmitParamVal(struct Parser *u, struct Params *h, bool t) {
- if (!t) {
- if (u->p > u->q) {
- EmitParamKey(u, h);
- h->p[h->n - 1].val.p = NULL;
- h->p[h->n - 1].val.n = SIZE_MAX;
- }
- } else {
- h->p[h->n - 1].val.p = u->q;
- h->p[h->n - 1].val.n = u->p - u->q;
- u->q = u->p;
- }
-}
-
-static void ParseLatin1(struct Parser *u) {
- *u->p++ = 0300 | u->c >> 6;
- *u->p++ = 0200 | u->c & 077;
-}
-
-static void ParseEscape(struct Parser *u) {
- int a, b;
- a = u->i < u->size ? u->data[u->i++] & 0xff : 0;
- b = u->i < u->size ? u->data[u->i++] & 0xff : 0;
- *u->p++ = kHexToInt[a] << 4 | kHexToInt[b];
-}
-
-static void ParsePath(struct Parser *u, struct Buffer *h) {
- while (u->i < u->size) {
- u->c = u->data[u->i++] & 0xff;
- if (u->c == '#' || u->c == '?') {
- break;
- } else if (u->c == '%') {
- ParseEscape(u);
- } else if (u->c >= 0200 && u->islatin1) {
- ParseLatin1(u);
- } else {
- *u->p++ = u->c;
- }
- }
- h->p = u->q;
- h->n = u->p - u->q;
- u->q = u->p;
-}
-
-static void ParseParams(struct Parser *u, struct Params *h) {
- bool t = false;
- while (u->i < u->size) {
- u->c = u->data[u->i++] & 0xff;
- if (u->c == '#') {
- break;
- } else if (u->c == '%') {
- ParseEscape(u);
- } else if (u->c == '+') {
- *u->p++ = u->isform ? ' ' : '+';
- } else if (u->c == '&') {
- EmitParamVal(u, h, t);
- t = false;
- } else if (u->c == '=') {
- if (!t) {
- if (u->p > u->q) {
- EmitParamKey(u, h);
- t = true;
- }
- } else {
- *u->p++ = '=';
- }
- } else if (u->c >= 0200 && u->islatin1) {
- ParseLatin1(u);
- } else {
- *u->p++ = u->c;
- }
- }
- EmitParamVal(u, h, t);
-}
-
-static void ParseFragment(struct Parser *u, struct Buffer *h) {
- while (u->i < u->size) {
- u->c = u->data[u->i++] & 0xff;
- if (u->c == '%') {
- ParseEscape(u);
- } else if (u->c >= 0200 && u->islatin1) {
- ParseLatin1(u);
- } else {
- *u->p++ = u->c;
- }
- }
- h->p = u->q;
- h->n = u->p - u->q;
- u->q = u->p;
-}
-
-static void ParseRequestUri(void) {
- struct Parser u;
- u.i = 0;
- 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 > 8 && !memcmp(u.data, "http", 4)) {
- /*
- * convert http://www.foo.com/index.html -> /www.foo.com/index.html
- */
- if (u.data[4] == ':' && u.data[5] == '/' && u.data[6] == '/') {
- u.data += 6;
- u.size -= 6;
- } else if (u.data[4] == 's' && u.data[5] == ':' && u.data[6] == '/' &&
- u.data[7] == '/') {
- u.data += 7;
- u.size -= 7;
- }
- }
- u.q = u.p = FreeLater(xmalloc(u.size * 2));
- ParsePath(&u, &request.path);
- if (u.c == '?') ParseParams(&u, &request.params);
- if (u.c == '#') ParseFragment(&u, &request.fragment);
-}
-
-static void ParseFormParams(void) {
- struct Parser u;
- u.i = 0;
- u.c = 0;
- u.isform = true;
- u.islatin1 = false;
- u.data = inbuf.p + hdrsize;
- u.size = msgsize - hdrsize;
- u.q = u.p = FreeLater(xmalloc(u.size));
- ParseParams(&u, &request.params);
-}
-
-static void *AddRange(char *content, long start, long length) {
- intptr_t mend, mstart;
- if (!__builtin_add_overflow((intptr_t)content, start, &mstart) ||
- !__builtin_add_overflow(mstart, length, &mend) ||
- ((intptr_t)zmap <= mstart && mstart <= (intptr_t)zmap + zsize) ||
- ((intptr_t)zmap <= mend && mend <= (intptr_t)zmap + zsize)) {
- return (void *)mstart;
- } else {
- abort();
- }
-}
-
-static char *AppendCrlf(char *p) {
- p[0] = '\r';
- p[1] = '\n';
- return p + 2;
-}
-
static bool MustNotIncludeMessageBody(void) { /* RFC2616 § 4.4 */
return msg.method == kHttpHead || (100 <= statuscode && statuscode <= 199) ||
statuscode == 204 || statuscode == 304;
}
-static char *SetStatus(int code, const char *reason) {
- char *p;
+static char *SetStatus(unsigned code, const char *reason) {
statuscode = code;
- p = hdrbuf.p;
- p = stpcpy(p, "HTTP/1.");
- *p++ = httpversion == 100 ? '0' : '1';
- *p++ = ' ';
- p += uint64toarray_radix10(code, p);
- *p++ = ' ';
- p = stpcpy(p, reason);
- return AppendCrlf(p);
+ stpcpy(hdrbuf.p, "HTTP/1.0 000 ");
+ hdrbuf.p[7] += msg.version & 1;
+ hdrbuf.p[9] += code / 100;
+ hdrbuf.p[10] += code / 10 % 10;
+ hdrbuf.p[11] += code % 10;
+ return AppendCrlf(stpcpy(hdrbuf.p + 13, reason));
}
static char *AppendHeader(char *p, const char *k, const char *v) {
@@ -1189,27 +1516,15 @@ static char *AppendHeader(char *p, const char *k, const char *v) {
static char *AppendContentType(char *p, const char *ct) {
p = stpcpy(p, "Content-Type: ");
p = stpcpy(p, ct);
- if (startswith(ct, "text/") && !strchr(ct, ';')) {
- p = stpcpy(p, "; charset=utf-8");
+ if (startswith(ct, "text/")) {
istext = true;
+ if (!strchr(ct, ';')) {
+ p = stpcpy(p, "; charset=utf-8");
+ }
}
return AppendCrlf(p);
}
-static char *ServeError(int code, const char *reason) {
- char *p;
- size_t reasonlen;
- reasonlen = strlen(reason);
- p = SetStatus(code, reason);
- p = AppendContentType(p, "text/plain");
- content = FreeLater(xmalloc(reasonlen + 3));
- contentlength = reasonlen + 2;
- stpcpy(stpcpy(content, reason), "\r\n");
- WARNF("%s %s %`'.*s %d %s", clientaddrstr, kHttpMethod[msg.method],
- msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, code, reason);
- return p;
-}
-
static char *AppendExpires(char *p, int64_t t) {
struct tm tm;
gmtime_r(&t, &tm);
@@ -1219,14 +1534,22 @@ static char *AppendExpires(char *p, int64_t t) {
}
static char *AppendCache(char *p, int64_t seconds) {
- struct tm tm;
if (seconds < 0) return p;
- p = stpcpy(p, "Cache-Control: ");
- p = stpcpy(p, "max-age=");
+ p = stpcpy(p, "Cache-Control: max-age=");
p += uint64toarray_radix10(seconds, p);
- if (seconds) p = stpcpy(p, ", public");
+ if (seconds) {
+ p = stpcpy(p, ", public");
+ } else {
+ p = stpcpy(p, ", no-store");
+ }
p = AppendCrlf(p);
- return AppendExpires(p, (int64_t)nowish + seconds);
+ return AppendExpires(p, (int64_t)shared->nowish + seconds);
+}
+
+static char *AppendServer(char *p, const char *s) {
+ p = stpcpy(p, "Server: ");
+ p = stpcpy(p, s);
+ return AppendCrlf(p);
}
static char *AppendContentLength(char *p, size_t n) {
@@ -1235,26 +1558,23 @@ static char *AppendContentLength(char *p, size_t n) {
return AppendCrlf(p);
}
-static char *AppendContentRange(char *p, long rangestart, long rangelength,
- long contentlength) {
- long endrange;
- CHECK_GT(rangelength, 0);
- CHECK_GT(rangestart + rangelength, rangestart);
- CHECK_LE(rangestart + rangelength, contentlength);
- endrange = rangestart + rangelength - 1;
+static char *AppendContentRange(char *p, long a, long b, long c) {
p = stpcpy(p, "Content-Range: bytes ");
- p += uint64toarray_radix10(rangestart, p);
- *p++ = '-';
- p += uint64toarray_radix10(endrange, p);
+ if (a >= 0 && b > 0) {
+ p += uint64toarray_radix10(a, p);
+ *p++ = '-';
+ p += uint64toarray_radix10(a + b - 1, p);
+ } else {
+ *p++ = '*';
+ }
*p++ = '/';
- p += uint64toarray_radix10(contentlength, p);
+ p += uint64toarray_radix10(c, p);
return AppendCrlf(p);
}
-static bool Inflate(uint8_t *dp, size_t dn, const uint8_t *sp, size_t sn) {
- bool ok;
+static bool Inflate(void *dp, size_t dn, const void *sp, size_t sn) {
z_stream zs;
- ok = false;
+ LockInc(&shared->inflates);
zs.next_in = sp;
zs.avail_in = sn;
zs.total_in = sn;
@@ -1263,31 +1583,43 @@ static bool Inflate(uint8_t *dp, size_t dn, const uint8_t *sp, size_t sn) {
zs.total_out = dn;
zs.zfree = Z_NULL;
zs.zalloc = Z_NULL;
- if (inflateInit2(&zs, -MAX_WBITS) == Z_OK) {
- switch (inflate(&zs, Z_NO_FLUSH)) {
- case Z_STREAM_END:
- ok = true;
- break;
- case Z_MEM_ERROR:
- WARNF("Z_MEM_ERROR");
- break;
- case Z_DATA_ERROR:
- WARNF("Z_DATA_ERROR");
- break;
- case Z_NEED_DICT:
- WARNF("Z_NEED_DICT");
- break;
- default:
- abort();
- }
- inflateEnd(&zs);
+ CHECK_EQ(Z_OK, inflateInit2(&zs, -MAX_WBITS));
+ switch (inflate(&zs, Z_NO_FLUSH)) {
+ case Z_STREAM_END:
+ CHECK_EQ(Z_OK, inflateEnd(&zs));
+ return true;
+ case Z_DATA_ERROR:
+ inflateEnd(&zs);
+ WARNF("Z_DATA_ERROR");
+ return false;
+ case Z_NEED_DICT:
+ inflateEnd(&zs);
+ WARNF("Z_NEED_DICT");
+ return false;
+ case Z_MEM_ERROR:
+ FATALF("Z_MEM_ERROR");
+ default:
+ abort();
+ }
+}
+
+static bool Verify(void *data, size_t size, uint32_t crc) {
+ uint32_t got;
+ LockInc(&shared->verifies);
+ if (crc == (got = crc32_z(0, data, size))) {
+ return true;
+ } else {
+ LockInc(&shared->thiscorruption);
+ WARNF("corrupt zip file at %`'.*s had crc 0x%08x but expected 0x%08x",
+ msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, got, crc);
+ return false;
}
- return ok;
}
static void *Deflate(const void *data, size_t size, size_t *out_size) {
void *res;
z_stream zs;
+ LockInc(&shared->deflates);
CHECK_EQ(Z_OK, deflateInit2(memset(&zs, 0, sizeof(zs)), 4, Z_DEFLATED,
-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY));
zs.next_in = data;
@@ -1301,148 +1633,317 @@ static void *Deflate(const void *data, size_t size, size_t *out_size) {
}
static void *LoadAsset(struct Asset *a, size_t *out_size) {
+ int mode;
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) {
- CHECK(Inflate(data, size, ZIP_LFILE_CONTENT(zmap + a->lf),
- ZIP_LFILE_COMPRESSEDSIZE(zmap + a->lf)));
- } else {
- memcpy(data, ZIP_LFILE_CONTENT(zmap + a->lf), size);
+ if (S_ISDIR(GetMode(a))) {
+ WARNF("can't load directory");
+ return NULL;
+ }
+ if (!a->file) {
+ size = GetZipLfileUncompressedSize(zmap + a->lf);
+ if (size == SIZE_MAX || !(data = malloc(size + 1))) return NULL;
+ if (IsCompressed(a)) {
+ if (!Inflate(data, size, ZIP_LFILE_CONTENT(zmap + a->lf),
+ GetZipLfileCompressedSize(zmap + a->lf))) {
+ free(data);
+ return NULL;
+ }
+ } else {
+ memcpy(data, ZIP_LFILE_CONTENT(zmap + a->lf), size);
+ }
+ if (!Verify(data, size, ZIP_LFILE_CRC32(zmap + a->lf))) {
+ free(data);
+ return NULL;
+ }
+ data[size] = '\0';
+ if (out_size) *out_size = size;
+ return data;
+ } else {
+ LockInc(&shared->slurps);
+ return xslurp(a->file->path, out_size);
}
- data[size] = '\0';
- if (out_size) *out_size = size;
- return data;
}
-static ssize_t Send(struct iovec *iov, int iovlen) {
+static void AppendLogo(void) {
+ size_t n;
+ char *p, *q;
+ struct Asset *a;
+ if ((a = GetAsset("/redbean.png", 12)) && (p = LoadAsset(a, &n))) {
+ q = EncodeBase64(p, n, &n);
+ Append(" \r\n");
+ free(q);
+ free(p);
+ }
+}
+
+static inline ssize_t Send(struct iovec *iov, int iovlen) {
ssize_t rc;
if ((rc = WritevAll(client, iov, iovlen)) == -1) {
if (errno == ECONNRESET) {
- DEBUGF("%s send reset", clientaddrstr);
+ LockInc(&shared->writeresets);
+ DEBUGF("%s write reset", DescribeClient());
+ } else if (errno == EAGAIN) {
+ LockInc(&shared->writetimeouts);
+ WARNF("%s write timeout", DescribeClient());
} else {
- WARNF("%s send error %s", clientaddrstr, strerror(errno));
+ LockInc(&shared->writeerrors);
+ WARNF("%s write error %s", DescribeClient(), strerror(errno));
}
connectionclose = true;
}
return rc;
}
-static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) {
+static char *CommitOutput(char *p) {
+ uint32_t crc;
+ if (!contentlength) {
+ if (istext && outbuf.n >= 100) {
+ p = stpcpy(p, "Vary: Accept-Encoding\r\n");
+ if (ClientAcceptsGzip()) {
+ gzipped = true;
+ crc = crc32_z(0, outbuf.p, outbuf.n);
+ WRITE32LE(gzip_footer + 0, crc);
+ WRITE32LE(gzip_footer + 4, outbuf.n);
+ content = FreeLater(Deflate(outbuf.p, outbuf.n, &contentlength));
+ DropOutput();
+ } else {
+ UseOutput();
+ }
+ } else {
+ UseOutput();
+ }
+ } else {
+ DropOutput();
+ }
+ return p;
+}
+
+static char *ServeDefaultErrorPage(char *p, unsigned code, const char *reason) {
+ p = AppendContentType(p, "text/html; charset=ISO-8859-1");
+ reason = FreeLater(EscapeHtml(reason, -1, 0));
+ Append("\
+\r\n\
+");
+ Append("%d %s", code, reason);
+ Append("\
+ \r\n\
+\r\n\
+\r\n");
+ AppendLogo();
+ Append("%d %s\r\n", code, reason);
+ Append(" \r\n");
+ UseOutput();
+ return p;
+}
+
+static char *ServeErrorImpl(unsigned code, const char *reason) {
+ size_t n;
+ char *p, *s;
+ struct Asset *a;
+ LockInc(&shared->errors);
+ ClearOutput();
+ p = SetStatus(code, reason);
+ s = xasprintf("/%d.html", code);
+ a = GetAsset(s, strlen(s));
+ free(s);
+ if (!a) {
+ return ServeDefaultErrorPage(p, code, reason);
+ } else if (a->file) {
+ LockInc(&shared->slurps);
+ content = FreeLater(xslurp(a->file->path, &contentlength));
+ return AppendContentType(p, "text/html; charset=utf-8");
+ } else {
+ content = (char *)ZIP_LFILE_CONTENT(zmap + a->lf);
+ contentlength = GetZipLfileCompressedSize(zmap + a->lf);
+ if (IsCompressed(a)) {
+ n = GetZipLfileUncompressedSize(zmap + a->lf);
+ if ((s = FreeLater(malloc(n))) && Inflate(s, n, content, contentlength)) {
+ content = s;
+ contentlength = n;
+ } else {
+ return ServeDefaultErrorPage(p, code, reason);
+ }
+ }
+ if (Verify(content, contentlength, ZIP_LFILE_CRC32(zmap + a->lf))) {
+ return AppendContentType(p, "text/html; charset=utf-8");
+ } else {
+ return ServeDefaultErrorPage(p, code, reason);
+ }
+ }
+}
+
+static char *ServeError(unsigned code, const char *reason) {
+ LOGF("ERROR %d %s", code, reason);
+ return ServeErrorImpl(code, reason);
+}
+
+static char *ServeFailure(unsigned code, const char *reason) {
+ LOGF("FAILURE %d %s %s HTTP%02d %.*s %`'.*s %`'.*s %`'.*s %`'.*s", code,
+ reason, DescribeClient(), msg.version, msg.xmethod.b - msg.xmethod.a,
+ inbuf.p + msg.xmethod.a, HeaderLength(kHttpHost), HeaderData(kHttpHost),
+ msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, HeaderLength(kHttpReferer),
+ HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent),
+ HeaderData(kHttpUserAgent));
+ return ServeErrorImpl(code, reason);
+}
+
+static char *ServeAssetCompressed(struct Asset *a) {
+ uint32_t crc;
+ LockInc(&shared->compressedresponses);
+ DEBUGF("ServeAssetCompressed()");
+ gzipped = true;
+ crc = crc32_z(0, content, contentlength);
+ WRITE32LE(gzip_footer + 0, crc);
+ WRITE32LE(gzip_footer + 4, contentlength);
+ content = FreeLater(Deflate(content, contentlength, &contentlength));
+ return SetStatus(200, "OK");
+}
+
+static char *ServeAssetPrecompressed(struct Asset *a) {
+ char *buf;
+ size_t size;
+ uint32_t crc;
+ crc = ZIP_LFILE_CRC32(zmap + a->lf);
+ size = GetZipLfileUncompressedSize(zmap + a->lf);
+ if (ClientAcceptsGzip()) {
+ LockInc(&shared->precompressedresponses);
+ DEBUGF("ServeAssetPrecompressed()");
+ gzipped = true;
+ WRITE32LE(gzip_footer + 0, crc);
+ WRITE32LE(gzip_footer + 4, size);
+ return SetStatus(200, "OK");
+ } else {
+ LockInc(&shared->decompressedresponses);
+ DEBUGF("ServeAssetDecompressed()");
+ if ((buf = FreeLater(malloc(size))) &&
+ Inflate(buf, size, content, contentlength) && Verify(buf, size, crc)) {
+ content = buf;
+ contentlength = size;
+ return SetStatus(200, "OK");
+ } else {
+ return ServeError(500, "Internal Server Error");
+ }
+ }
+}
+
+static char *ServeAssetRange(struct Asset *a) {
char *p;
long rangestart, rangelength;
+ DEBUGF("ServeAssetRange()");
+ if (ParseHttpRange(HeaderData(kHttpRange), HeaderLength(kHttpRange),
+ contentlength, &rangestart, &rangelength) &&
+ rangestart >= 0 && rangelength >= 0 && rangestart < contentlength &&
+ rangestart + rangelength <= contentlength) {
+ LockInc(&shared->partialresponses);
+ p = SetStatus(206, "Partial Content");
+ p = AppendContentRange(p, rangestart, rangelength, contentlength);
+ content += rangestart;
+ contentlength = rangelength;
+ return p;
+ } else {
+ LockInc(&shared->badranges);
+ LOGF("bad range %`'.*s", HeaderLength(kHttpRange), HeaderData(kHttpRange));
+ p = SetStatus(416, "Range Not Satisfiable");
+ p = AppendContentRange(p, -1, -1, contentlength);
+ content = "";
+ contentlength = 0;
+ return p;
+ }
+}
+
+static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) {
+ int fd;
+ char *p;
+ void *data;
+ size_t size;
+ uint32_t crc;
+ const char *ct;
+ ct = GetContentType(a, path, pathlen);
if (IsNotModified(a)) {
- DEBUGF("%s %s %`'.*s not modified", clientaddrstr, kHttpMethod[msg.method],
- pathlen, path);
+ LockInc(&shared->notmodifieds);
p = SetStatus(304, "Not Modified");
} else {
if (!a->file) {
- content = ZIP_LFILE_CONTENT(zmap + a->lf);
- contentlength = ZIP_LFILE_COMPRESSEDSIZE(zmap + a->lf);
+ content = (char *)ZIP_LFILE_CONTENT(zmap + a->lf);
+ contentlength = GetZipLfileCompressedSize(zmap + a->lf);
+ } else if (a->file->st.st_size) {
+ size = a->file->st.st_size;
+ if ((fd = open(a->file->path, O_RDONLY)) != -1) {
+ data = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (data != MAP_FAILED) {
+ LockInc(&shared->maps);
+ UnmapLater(fd, data, size);
+ content = data;
+ contentlength = size;
+ } else {
+ LockInc(&shared->mapfails);
+ WARNF("mmap(%`'s) failed %s", a->file->path, strerror(errno));
+ close(fd);
+ return ServeError(500, "Internal Server Error");
+ }
+ } else {
+ LockInc(&shared->openfails);
+ WARNF("open(%`'s) failed %s", a->file->path, strerror(errno));
+ if (errno == ENFILE) {
+ LockInc(&shared->enfiles);
+ return ServeError(503, "Service Unavailable");
+ } else if (errno == EMFILE) {
+ LockInc(&shared->emfiles);
+ return ServeError(503, "Service Unavailable");
+ } else {
+ return ServeError(500, "Internal Server Error");
+ }
+ }
} else {
- /* TODO(jart): Use sendfile(). */
- content = FreeLater(xslurp(a->file->path, &contentlength));
+ content = "";
+ contentlength = 0;
}
if (IsCompressed(a)) {
- if (ClientAcceptsGzip()) {
- gzipped = true;
- memcpy(gzip_footer + 0, zmap + a->lf + kZipLfileOffsetCrc32, 4);
- memcpy(gzip_footer + 4, zmap + a->lf + kZipLfileOffsetUncompressedsize,
- 4);
+ p = ServeAssetPrecompressed(a);
+ } else if (msg.version >= 11 && HasHeader(kHttpRange)) {
+ p = ServeAssetRange(a);
+ } else if (!a->file) {
+ LockInc(&shared->identityresponses);
+ DEBUGF("ServeAssetZipIdentity(%`'s)", ct);
+ if (Verify(content, contentlength, ZIP_LFILE_CRC32(zmap + a->lf))) {
p = SetStatus(200, "OK");
- p = AppendHeader(p, "Content-Encoding", "gzip");
} else {
- CHECK(Inflate(
- (content =
- FreeLater(xmalloc(ZIP_LFILE_UNCOMPRESSEDSIZE(zmap + a->lf)))),
- (contentlength = ZIP_LFILE_UNCOMPRESSEDSIZE(zmap + a->lf)),
- ZIP_LFILE_CONTENT(zmap + a->lf),
- ZIP_LFILE_COMPRESSEDSIZE(zmap + a->lf)));
- p = SetStatus(200, "OK");
- }
- } else if (httpversion >= 101 && HasHeader(kHttpRange)) {
- if (ParseHttpRange(inbuf.p + msg.headers[kHttpRange].a,
- msg.headers[kHttpRange].b - msg.headers[kHttpRange].a,
- contentlength, &rangestart, &rangelength)) {
- LOGF("rangestart = %ld rangelength = %ld", rangestart, rangelength);
- p = SetStatus(206, "Partial Content");
- p = AppendContentRange(p, rangestart, rangelength, contentlength);
- content = AddRange(content, rangestart, rangelength);
- contentlength = rangelength;
- } else {
- WARNF("%s %s %`'.*s bad range %`'.*s", clientaddrstr,
- kHttpMethod[msg.method], pathlen, path,
- msg.headers[kHttpRange].b - msg.headers[kHttpRange].a,
- inbuf.p + msg.headers[kHttpRange].a);
- p = SetStatus(416, "Range Not Satisfiable");
- p = AppendContentRange(p, rangestart, rangelength, contentlength);
- content = "";
- contentlength = 0;
+ return ServeError(500, "Internal Server Error");
}
+ } else if (ClientAcceptsGzip() &&
+ (strlen(ct) >= 5 && !memcasecmp(ct, "text/", 5)) &&
+ contentlength < 1024 * 1024 * 1024) {
+ p = ServeAssetCompressed(a);
} else {
+ LockInc(&shared->identityresponses);
+ DEBUGF("ServeAssetIdentity(%`'s)", ct);
p = SetStatus(200, "OK");
}
}
- if (httpversion >= 100) {
- p = AppendHeader(p, "Last-Modified", a->lastmodifiedstr);
- p = AppendContentType(p, GetContentType(a, path, pathlen));
- if (httpversion >= 101) {
- p = AppendCache(p, cacheseconds);
- if (!IsCompressed(a)) {
- p = AppendHeader(p, "Accept-Ranges", "bytes");
- } else {
- p = AppendHeader(p, "Vary", "Accept-Encoding");
- }
+ p = AppendContentType(p, ct);
+ p = stpcpy(p, "Vary: Accept-Encoding\r\n");
+ p = AppendHeader(p, "Last-Modified", a->lastmodifiedstr);
+ if (msg.version >= 11) {
+ p = AppendCache(p, cacheseconds);
+ if (!IsCompressed(a)) {
+ p = stpcpy(p, "Accept-Ranges: bytes\r\n");
}
}
return p;
}
-static void AppendData(const char *data, size_t size) {
- outbuf.p = xrealloc(outbuf.p, outbuf.n + size);
- memcpy(outbuf.p + outbuf.n, data, size);
- outbuf.n += size;
-}
-
-static void AppendString(const char *s) {
- AppendData(s, strlen(s));
-}
-
-static void AppendFmt(const char *fmt, ...) {
- int size;
- char *data;
- va_list va;
- data = NULL;
- va_start(va, fmt);
- CHECK_NE(-1, (size = vasprintf(&data, fmt, va)));
- va_end(va);
- AppendData(data, size);
- free(data);
-}
-
-static char *CommitOutput(char *p) {
- if (istext && outbuf.n >= 100 && ClientAcceptsGzip()) {
- gzipped = true;
- p = AppendHeader(p, "Content-Encoding", "gzip");
- p = AppendHeader(p, "Vary", "Accept-Encoding");
- WRITE32LE(gzip_footer + 0, crc32_z(0, outbuf.p, outbuf.n));
- WRITE32LE(gzip_footer + 4, outbuf.n);
- content = FreeLater(Deflate(outbuf.p, outbuf.n, &contentlength));
- } else {
- content = outbuf.p;
- contentlength = outbuf.n;
- }
- return p;
-}
-
-static char *GetAssetPath(uint32_t cf, size_t *out_size) {
+static char *GetAssetPath(uint64_t cf, size_t *out_size) {
char *p1, *p2;
size_t n1, n2;
p1 = ZIP_CFILE_NAME(zmap + cf);
n1 = ZIP_CFILE_NAMESIZE(zmap + cf);
- p2 = malloc(1 + n1 + 1);
+ p2 = xmalloc(1 + n1 + 1);
n2 = 1 + n1 + 1;
p2[0] = '/';
memcpy(p2 + 1, p1, n1);
@@ -1461,26 +1962,626 @@ static bool IsHiddenPath(const char *s) {
return false;
}
+static char *GetBasicAuthorization(size_t *z) {
+ size_t n;
+ const char *p, *q;
+ p = inbuf.p + msg.headers[kHttpAuthorization].a;
+ n = msg.headers[kHttpAuthorization].b - msg.headers[kHttpAuthorization].a;
+ if ((q = memchr(p, ' ', n)) && SlicesEqualCase(p, q - p, "Basic", 5)) {
+ return DecodeBase64(q + 1, n - (q + 1 - p), z);
+ } else {
+ return NULL;
+ }
+}
+
+static void LaunchBrowser() {
+ char openbrowsercommand[255];
+ char *prog;
+ if (IsWindows()) {
+ prog = "explorer";
+ } else if (IsXnu()) {
+ prog = "open";
+ } else {
+ prog = "xdg-open";
+ }
+ struct in_addr addr = serveraddr.sin_addr;
+ if (addr.s_addr == INADDR_ANY) addr.s_addr = htonl(INADDR_LOOPBACK);
+ snprintf(openbrowsercommand, sizeof(openbrowsercommand), "%s http://%s:%d",
+ prog, inet_ntoa(addr), ntohs(serveraddr.sin_port));
+ DEBUGF("Opening browser with command %s\n", openbrowsercommand);
+ system(openbrowsercommand);
+}
+
+static char *BadMethod(void) {
+ LockInc(&shared->badmethods);
+ return stpcpy(ServeError(405, "Method Not Allowed"), "Allow: GET, HEAD\r\n");
+}
+
+static int GetDecimalWidth(int x) {
+ int w = x ? ceil(log10(x)) : 1;
+ return w + (w - 1) / 3;
+}
+
+static int GetOctalWidth(int x) {
+ return !x ? 1 : x < 8 ? 2 : 1 + bsr(x) / 3;
+}
+
+static const char *DescribeCompressionRatio(char rb[8], uint64_t lf) {
+ long percent;
+ if (ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf) == kZipCompressionNone) {
+ return "n/a";
+ } else {
+ percent = lround(100 - (double)GetZipLfileCompressedSize(zmap + lf) /
+ GetZipLfileUncompressedSize(zmap + lf) * 100);
+ sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent)));
+ return rb;
+ }
+}
+
+static char *ServeListing(void) {
+ long x;
+ ldiv_t y;
+ int w[3];
+ struct tm tm;
+ const char *and;
+ int64_t lastmod;
+ uint64_t cf, lf;
+ struct rusage ru;
+ char *p, *q, *path;
+ char rb[8], tb[64], *rp[6];
+ size_t i, n, pathlen, rn[6];
+ LockInc(&shared->listingrequests);
+ if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod();
+ Append("\
+\r\n\
+ \r\n\
+redbean zip listing \r\n\
+\r\n\
+\r\n");
+ AppendLogo();
+ rp[0] = EscapeHtml(brand, -1, &rn[0]);
+ AppendData(rp[0], rn[0]);
+ free(rp[0]);
+ Append(" \r\n"
+ "\r\n"
+ " \r\n"
+ " \r\n"
+ "\r\n",
+ strnlen(GetZipCdirComment(cdir), GetZipCdirCommentSize(cdir)),
+ GetZipCdirComment(cdir));
+ memset(w, 0, sizeof(w));
+ n = GetZipCdirRecords(cdir);
+ for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+ CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
+ lf = GetZipCfileOffset(zmap + cf);
+ path = GetAssetPath(cf, &pathlen);
+ if (!IsHiddenPath(path)) {
+ w[0] = min(80, max(w[0], strwidth(path, 0) + 2));
+ w[1] = max(w[1], GetOctalWidth(GetZipCfileMode(zmap + cf)));
+ w[2] = max(w[2], GetDecimalWidth(GetZipLfileUncompressedSize(zmap + lf)));
+ }
+ free(path);
+ }
+ n = GetZipCdirRecords(cdir);
+ for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+ CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
+ lf = GetZipCfileOffset(zmap + cf);
+ path = GetAssetPath(cf, &pathlen);
+ if (!IsHiddenPath(path)) {
+ rp[0] = VisualizeControlCodes(path, pathlen, &rn[0]);
+ rp[1] = EscapePath(path, pathlen, &rn[1]);
+ rp[2] = EscapeHtml(rp[1], rn[1], &rn[2]);
+ rp[3] = VisualizeControlCodes(ZIP_CFILE_COMMENT(zmap + cf),
+ strnlen(ZIP_CFILE_COMMENT(zmap + cf),
+ ZIP_CFILE_COMMENTSIZE(zmap + cf)),
+ &rn[3]);
+ rp[4] = EscapeHtml(rp[0], rn[0], &rn[4]);
+ lastmod = GetZipCfileLastModified(zmap + cf);
+ localtime_r(&lastmod, &tm);
+ strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S %Z", &tm);
+ if (IsCompressionMethodSupported(
+ ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf)) &&
+ IsAcceptablePath(path, pathlen)) {
+ Append("%-*.*s %s %0*o %4s %,*ld %'s\r\n",
+ rn[2], rp[2], w[0], rn[4], rp[4], tb, w[1],
+ GetZipCfileMode(zmap + cf), DescribeCompressionRatio(rb, lf),
+ w[2], GetZipLfileUncompressedSize(zmap + lf), rp[3]);
+ } else {
+ Append("%-*.*s %s %0*o %4s %,*ld %'s\r\n", w[0], rn[4], rp[4], tb,
+ w[1], GetZipCfileMode(zmap + cf),
+ DescribeCompressionRatio(rb, lf), w[2],
+ GetZipLfileUncompressedSize(zmap + lf), rp[3]);
+ }
+ free(rp[4]);
+ free(rp[3]);
+ free(rp[2]);
+ free(rp[1]);
+ free(rp[0]);
+ }
+ free(path);
+ }
+ Append(" \r\n");
+ Append("\r\n");
+ Append("\r\n");
+ Append("/statusz \r\n");
+ if (shared->connectionshandled) {
+ Append("says your redbean \r\n");
+ AppendResourceReport(&shared->children, " \r\n");
+ }
+ Append(" \r\n");
+ and = "";
+ x = nowl() - startserver;
+ y = ldiv(x, 24L * 60 * 60);
+ if (y.quot) {
+ Append("%,ld day%s ", y.quot, y.quot == 1 ? "" : "s");
+ and = "and ";
+ }
+ y = ldiv(y.rem, 60 * 60);
+ if (y.quot) {
+ Append("%,ld hour%s ", y.quot, y.quot == 1 ? "" : "s");
+ and = "and ";
+ }
+ y = ldiv(y.rem, 60);
+ if (y.quot) {
+ Append("%,ld minute%s ", y.quot, y.quot == 1 ? "" : "s");
+ and = "and ";
+ }
+ Append("%s%,ld second%s of operation \r\n", and, y.rem,
+ y.rem == 1 ? "" : "s");
+ x = shared->messageshandled;
+ Append("%,ld message%s handled \r\n", x, x == 1 ? "" : "s");
+ x = shared->connectionshandled;
+ Append("%,ld connection%s handled \r\n", x, x == 1 ? "" : "s");
+ x = shared->workers;
+ Append("%,ld connection%s active \r\n", x, x == 1 ? "" : "s");
+ Append("
\r\n");
+ Append(" \r\n");
+ p = SetStatus(200, "OK");
+ p = AppendContentType(p, "text/html");
+ p = AppendCache(p, 0);
+ return CommitOutput(p);
+}
+
+static const char *MergeNames(const char *a, const char *b) {
+ return FreeLater(xasprintf("%s.ru_utime", a));
+}
+
+static void AppendLong1(const char *a, long x) {
+ if (x) Append("%s: %ld\r\n", a, x);
+}
+
+static void AppendLong2(const char *a, const char *b, long x) {
+ if (x) Append("%s.%s: %ld\r\n", a, b, x);
+}
+
+static void AppendTimeval(const char *a, struct timeval *tv) {
+ AppendLong2(a, "tv_sec", tv->tv_sec);
+ AppendLong2(a, "tv_usec", tv->tv_usec);
+}
+
+static void AppendRusage(const char *a, struct rusage *ru) {
+ AppendTimeval(MergeNames(a, "ru_utime"), &ru->ru_utime);
+ AppendTimeval(MergeNames(a, "ru_stime"), &ru->ru_stime);
+ AppendLong2(a, "ru_maxrss", ru->ru_maxrss);
+ AppendLong2(a, "ru_ixrss", ru->ru_ixrss);
+ AppendLong2(a, "ru_idrss", ru->ru_idrss);
+ AppendLong2(a, "ru_isrss", ru->ru_isrss);
+ AppendLong2(a, "ru_minflt", ru->ru_minflt);
+ AppendLong2(a, "ru_majflt", ru->ru_majflt);
+ AppendLong2(a, "ru_nswap", ru->ru_nswap);
+ AppendLong2(a, "ru_inblock", ru->ru_inblock);
+ AppendLong2(a, "ru_oublock", ru->ru_oublock);
+ AppendLong2(a, "ru_msgsnd", ru->ru_msgsnd);
+ AppendLong2(a, "ru_msgrcv", ru->ru_msgrcv);
+ AppendLong2(a, "ru_nsignals", ru->ru_nsignals);
+ AppendLong2(a, "ru_nvcsw", ru->ru_nvcsw);
+ AppendLong2(a, "ru_nivcsw", ru->ru_nivcsw);
+}
+
+static char *ServeStatusz(void) {
+ char *p;
+ LockInc(&shared->statuszrequests);
+ if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod();
+ AppendLong1("pid", getpid());
+ AppendLong1("ppid", getppid());
+ AppendLong1("now", nowl());
+ AppendLong1("nowish", shared->nowish);
+ AppendLong1("heartless", heartless);
+ AppendLong1("gmtoff", gmtoff);
+ AppendLong1("CLK_TCK", CLK_TCK);
+ AppendLong1("startserver", startserver);
+ AppendLong1("lastmeltdown", shared->lastmeltdown);
+ AppendLong1("workers", shared->workers);
+ AppendLong1("assets.n", assets.n);
+ AppendLong1("accepterrors", shared->accepterrors);
+ AppendLong1("acceptinterrupts", shared->acceptinterrupts);
+ AppendLong1("acceptresets", shared->acceptresets);
+ AppendLong1("badlengths", shared->badlengths);
+ AppendLong1("badmessages", shared->badmessages);
+ AppendLong1("badmethods", shared->badmethods);
+ AppendLong1("badranges", shared->badranges);
+ AppendLong1("closeerrors", shared->closeerrors);
+ AppendLong1("compressedresponses", shared->compressedresponses);
+ AppendLong1("connectionshandled", shared->connectionshandled);
+ AppendLong1("connectsrefused", shared->connectsrefused);
+ AppendLong1("continues", shared->continues);
+ AppendLong1("decompressedresponses", shared->decompressedresponses);
+ AppendLong1("deflates", shared->deflates);
+ AppendLong1("dropped", shared->dropped);
+ AppendLong1("dynamicrequests", shared->dynamicrequests);
+ AppendLong1("emfiles", shared->emfiles);
+ AppendLong1("enetdowns", shared->enetdowns);
+ AppendLong1("enfiles", shared->enfiles);
+ AppendLong1("enobufs", shared->enobufs);
+ AppendLong1("enomems", shared->enomems);
+ AppendLong1("enonets", shared->enonets);
+ AppendLong1("errors", shared->errors);
+ AppendLong1("expectsrefused", shared->expectsrefused);
+ AppendLong1("failedchildren", shared->failedchildren);
+ AppendLong1("forbiddens", shared->forbiddens);
+ AppendLong1("forkerrors", shared->forkerrors);
+ AppendLong1("frags", shared->frags);
+ AppendLong1("fumbles", shared->fumbles);
+ AppendLong1("http09", shared->http09);
+ AppendLong1("http10", shared->http10);
+ AppendLong1("http11", shared->http11);
+ AppendLong1("http12", shared->http12);
+ AppendLong1("hugepayloads", shared->hugepayloads);
+ AppendLong1("identityresponses", shared->identityresponses);
+ AppendLong1("inflates", shared->inflates);
+ AppendLong1("listingrequests", shared->listingrequests);
+ AppendLong1("loops", shared->loops);
+ AppendLong1("mapfails", shared->mapfails);
+ AppendLong1("maps", shared->maps);
+ AppendLong1("meltdowns", shared->meltdowns);
+ AppendLong1("messageshandled", shared->messageshandled);
+ AppendLong1("missinglengths", shared->missinglengths);
+ AppendLong1("netafrinic", shared->netafrinic);
+ AppendLong1("netanonymous", shared->netanonymous);
+ AppendLong1("netapnic", shared->netapnic);
+ AppendLong1("netapple", shared->netapple);
+ AppendLong1("netarin", shared->netarin);
+ AppendLong1("netatt", shared->netatt);
+ AppendLong1("netcogent", shared->netcogent);
+ AppendLong1("netcomcast", shared->netcomcast);
+ AppendLong1("netdod", shared->netdod);
+ AppendLong1("netford", shared->netford);
+ AppendLong1("netlacnic", shared->netlacnic);
+ AppendLong1("netloopback", shared->netloopback);
+ AppendLong1("netother", shared->netother);
+ AppendLong1("netprivate", shared->netprivate);
+ AppendLong1("netprudential", shared->netprudential);
+ AppendLong1("netripe", shared->netripe);
+ AppendLong1("nettestnet", shared->nettestnet);
+ AppendLong1("netusps", shared->netusps);
+ AppendLong1("notfounds", shared->notfounds);
+ AppendLong1("notmodifieds", shared->notmodifieds);
+ AppendLong1("openfails", shared->openfails);
+ AppendLong1("partialresponses", shared->partialresponses);
+ AppendLong1("payloaddisconnects", shared->payloaddisconnects);
+ AppendLong1("pipelinedrequests", shared->pipelinedrequests);
+ AppendLong1("precompressedresponses", shared->precompressedresponses);
+ AppendLong1("readerrors", shared->readerrors);
+ AppendLong1("readinterrupts", shared->readinterrupts);
+ AppendLong1("readresets", shared->readresets);
+ AppendLong1("readtimeouts", shared->readtimeouts);
+ AppendLong1("redirects", shared->redirects);
+ AppendLong1("reloads", shared->reloads);
+ AppendLong1("rewrites", shared->rewrites);
+ AppendLong1("serveroptions", shared->serveroptions);
+ AppendLong1("shutdowns", shared->shutdowns);
+ AppendLong1("slowloris", shared->slowloris);
+ AppendLong1("slurps", shared->slurps);
+ AppendLong1("statfails", shared->statfails);
+ AppendLong1("staticrequests", shared->staticrequests);
+ AppendLong1("stats", shared->stats);
+ AppendLong1("statuszrequests", shared->statuszrequests);
+ AppendLong1("synchronizationfailures", shared->synchronizationfailures);
+ AppendLong1("terminatedchildren", shared->terminatedchildren);
+ AppendLong1("thiscorruption", shared->thiscorruption);
+ AppendLong1("transfersrefused", shared->transfersrefused);
+ AppendLong1("urisrefused", shared->urisrefused);
+ AppendLong1("verifies", shared->verifies);
+ AppendLong1("writeerrors", shared->writeerrors);
+ AppendLong1("writeinterruputs", shared->writeinterruputs);
+ AppendLong1("writeresets", shared->writeresets);
+ AppendLong1("writetimeouts", shared->writetimeouts);
+ AppendRusage("server", &shared->server);
+ AppendRusage("children", &shared->children);
+ p = SetStatus(200, "OK");
+ p = AppendContentType(p, "text/plain");
+ p = AppendCache(p, 0);
+ return CommitOutput(p);
+}
+
+static char *RedirectSlash(void) {
+ size_t n;
+ char *p, *e;
+ LockInc(&shared->redirects);
+ p = SetStatus(307, "Temporary Redirect");
+ p = stpcpy(p, "Location: ");
+ e = EscapePath(url.path.p, url.path.n, &n);
+ p = mempcpy(p, e, n);
+ p = stpcpy(p, "/\r\n");
+ free(e);
+ return p;
+}
+
+static char *RoutePath(const char *, size_t);
+static char *ServeIndex(const char *path, size_t pathlen) {
+ size_t i, n;
+ char *p, *q;
+ p = NULL;
+ for (i = 0; !p && i < ARRAYLEN(kIndexPaths); ++i) {
+ q = MergePaths(path, pathlen, kIndexPaths[i], strlen(kIndexPaths[i]), &n);
+ p = RoutePath(q, n);
+ free(q);
+ }
+ return p;
+}
+
+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,
+ ".lua", 4);
+}
+
+static char *GetLuaResponse(void) {
+ char *p;
+ if (!(p = luaheaderp)) {
+ p = SetStatus(200, "OK");
+ p = AppendContentType(p, "text/html");
+ }
+ return p;
+}
+
+static char *ServeLua(struct Asset *a, const char *path, size_t pathlen) {
+ char *code;
+ effectivepath.p = path;
+ effectivepath.n = pathlen;
+ if ((code = FreeLater(LoadAsset(a, NULL)))) {
+ if (luaL_dostring(L, code) == LUA_OK) {
+ return CommitOutput(GetLuaResponse());
+ } else {
+ WARNF("%s", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ }
+ }
+ return ServeError(500, "Internal Server Error");
+}
+
+static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) {
+#ifndef STATIC
+ if (IsLua(a)) {
+ LockInc(&shared->dynamicrequests);
+ return ServeLua(a, path, pathlen);
+ }
+#endif
+ if (msg.method == kHttpGet || msg.method == kHttpHead) {
+ LockInc(&shared->staticrequests);
+ return stpcpy(ServeAsset(a, path, pathlen),
+ "X-Content-Type-Options: nosniff\r\n");
+ } else {
+ return BadMethod();
+ }
+}
+
+static char *HandleRedirect(struct Redirect *r) {
+ int code;
+ struct Asset *a;
+ if (!r->code && (a = GetAsset(r->location, strlen(r->location)))) {
+ LockInc(&shared->rewrites);
+ DEBUGF("rewriting to %`'s", r->location);
+ if (!HasString(&loops, r->location)) {
+ AddString(&loops, r->location);
+ return RoutePath(r->location, strlen(r->location));
+ } else {
+ LockInc(&shared->loops);
+ return SetStatus(508, "Loop Detected");
+ }
+ } else if (msg.version < 10) {
+ return ServeError(505, "HTTP Version Not Supported");
+ } else {
+ LockInc(&shared->redirects);
+ code = r->code;
+ if (!code) code = 307;
+ DEBUGF("%d redirecting %`'s", code, r->location);
+ return AppendHeader(SetStatus(code, GetHttpReason(code)), "Location",
+ FreeLater(EncodeHttpHeaderValue(r->location, -1, 0)));
+ }
+}
+
+static char *HandleFolder(const char *path, size_t pathlen) {
+ char *p;
+ if (url.path.n && url.path.p[url.path.n - 1] != '/' &&
+ SlicesEqual(path, pathlen, url.path.p, url.path.n)) {
+ return RedirectSlash();
+ }
+ if ((p = ServeIndex(path, pathlen))) {
+ return p;
+ } else {
+ LockInc(&shared->forbiddens);
+ LOGF("directory %`'.*s lacks index page", pathlen, path);
+ return ServeError(403, "Forbidden");
+ }
+}
+
+static char *RoutePath(const char *path, size_t pathlen) {
+ int m;
+ long r;
+ char *p;
+ struct Asset *a;
+ DEBUGF("RoutePath(%`'.*s)", pathlen, path);
+ if ((a = GetAsset(path, pathlen))) {
+ if ((m = GetMode(a)) & 0004) {
+ if (!S_ISDIR(m)) {
+ return HandleAsset(a, path, pathlen);
+ } else {
+ return HandleFolder(path, pathlen);
+ }
+ } else {
+ LockInc(&shared->forbiddens);
+ LOGF("asset %`'.*s %#o isn't readable", pathlen, path, m);
+ return ServeError(403, "Forbidden");
+ }
+ } else if ((r = FindRedirect(path, pathlen)) != -1) {
+ return HandleRedirect(redirects.p + r);
+ } else {
+ return NULL;
+ }
+}
+
+static char *RouteHost(const char *host, size_t hostlen, const char *path,
+ size_t pathlen) {
+ size_t hn;
+ char *hp, *p;
+ hn = 1 + hostlen + url.path.n;
+ hp = FreeLater(xmalloc(3 + 1 + hn));
+ hp[0] = '/';
+ mempcpy(mempcpy(hp + 1, host, hostlen), path, pathlen);
+ if ((p = RoutePath(hp, hn))) return p;
+ if (ParseIp(host, hostlen) == -1) {
+ if (hostlen > 4 && !memcmp(host, "www.", 4)) {
+ mempcpy(mempcpy(hp + 1, host + 4, hostlen - 4), path, pathlen);
+ if ((p = RoutePath(hp, hn - 4))) return p;
+ } else {
+ mempcpy(mempcpy(mempcpy(hp + 1, "www.", 4), host, hostlen), path,
+ pathlen);
+ if ((p = RoutePath(hp, hn + 4))) return p;
+ }
+ }
+ return NULL;
+}
+
+static char *Route(const char *host, size_t hostlen, const char *path,
+ size_t pathlen) {
+ char *p;
+ if (hostlen && (p = RouteHost(host, hostlen, path, pathlen))) {
+ return p;
+ }
+ if (SlicesEqual(path, pathlen, "/", 1)) {
+ if ((p = ServeIndex("/", 1))) return p;
+ return ServeListing();
+ } else if ((p = RoutePath(path, pathlen))) {
+ return p;
+ } else if (SlicesEqual(path, pathlen, "/statusz", 8)) {
+ return ServeStatusz();
+ } else {
+ LockInc(&shared->notfounds);
+ return ServeError(404, "Not Found");
+ }
+}
+
+static const char *LuaCheckPath(lua_State *L, int idx, size_t *pathlen) {
+ const char *path;
+ if (lua_isnoneornil(L, idx)) {
+ path = url.path.p;
+ *pathlen = url.path.n;
+ } else {
+ path = luaL_checklstring(L, idx, pathlen);
+ if (!IsReasonablePath(path, *pathlen)) {
+ WARNF("bad path %`'.*s", *pathlen, path);
+ luaL_argerror(L, idx, "bad path");
+ unreachable;
+ }
+ }
+ return path;
+}
+
+static const char *LuaCheckHost(lua_State *L, int idx, size_t *hostlen) {
+ const char *host;
+ if (lua_isnoneornil(L, idx)) {
+ host = url.host.p;
+ *hostlen = url.host.n;
+ } else {
+ host = luaL_checklstring(L, idx, hostlen);
+ if (!IsAcceptableHost(host, *hostlen)) {
+ WARNF("bad host %`'.*s", *hostlen, host);
+ luaL_argerror(L, idx, "bad host");
+ unreachable;
+ }
+ }
+ return host;
+}
+
+static int LuaServeListing(lua_State *L) {
+ luaheaderp = ServeListing();
+ return 0;
+}
+
+static int LuaServeStatusz(lua_State *L) {
+ luaheaderp = ServeStatusz();
+ return 0;
+}
+
static int LuaServeAsset(lua_State *L) {
size_t pathlen;
struct Asset *a;
const char *path;
- path = luaL_checklstring(L, 1, &pathlen);
- if (!(a = LocateAsset(path, pathlen))) {
- return luaL_argerror(L, 1, "not found");
+ path = LuaCheckPath(L, 1, &pathlen);
+ if ((a = GetAsset(path, pathlen)) && !S_ISDIR(GetMode(a))) {
+ luaheaderp = ServeAsset(a, path, pathlen);
+ lua_pushboolean(L, true);
+ } else {
+ lua_pushboolean(L, false);
}
- luaheaderp = ServeAsset(a, path, pathlen);
- return 0;
+ return 1;
}
-static int LuaRespond(lua_State *L, char *respond(int, const char *)) {
+static int LuaServeIndex(lua_State *L) {
+ size_t pathlen;
+ const char *path;
+ path = LuaCheckPath(L, 1, &pathlen);
+ lua_pushboolean(L, !!(luaheaderp = ServeIndex(path, pathlen)));
+ return 1;
+}
+
+static int LuaRoutePath(lua_State *L) {
+ size_t pathlen;
+ const char *path;
+ path = LuaCheckPath(L, 1, &pathlen);
+ lua_pushboolean(L, !!(luaheaderp = RoutePath(path, pathlen)));
+ return 1;
+}
+
+static int LuaRouteHost(lua_State *L) {
+ size_t hostlen, pathlen;
+ const char *host, *path;
+ host = LuaCheckHost(L, 1, &hostlen);
+ path = LuaCheckPath(L, 2, &pathlen);
+ lua_pushboolean(L, !!(luaheaderp = RouteHost(host, hostlen, path, pathlen)));
+ return 1;
+}
+
+static int LuaRoute(lua_State *L) {
+ size_t hostlen, pathlen;
+ const char *host, *path;
+ host = LuaCheckHost(L, 1, &hostlen);
+ path = LuaCheckPath(L, 2, &pathlen);
+ lua_pushboolean(L, !!(luaheaderp = Route(host, hostlen, path, pathlen)));
+ return 1;
+}
+
+static int LuaRespond(lua_State *L, char *respond(unsigned, const char *)) {
char *p;
int code;
size_t reasonlen;
const char *reason;
code = luaL_checkinteger(L, 1);
if (!(100 <= code && code <= 999)) {
- return luaL_argerror(L, 1, "bad status code");
+ luaL_argerror(L, 1, "bad status code");
+ unreachable;
}
if (lua_isnoneornil(L, 2)) {
luaheaderp = respond(code, GetHttpReason(code));
@@ -1490,7 +2591,8 @@ static int LuaRespond(lua_State *L, char *respond(int, const char *)) {
luaheaderp = respond(code, p);
free(p);
} else {
- return luaL_argerror(L, 2, "invalid");
+ luaL_argerror(L, 2, "invalid");
+ unreachable;
}
}
return 0;
@@ -1505,51 +2607,194 @@ static int LuaServeError(lua_State *L) {
}
static int LuaLoadAsset(lua_State *L) {
- char *data;
+ void *data;
struct Asset *a;
const char *path;
size_t size, pathlen;
- path = luaL_checklstring(L, 1, &pathlen);
- if ((a = LocateAsset(path, pathlen))) {
- data = LoadAsset(a, &size);
- lua_pushlstring(L, data, size);
- free(data);
+ path = LuaCheckPath(L, 1, &pathlen);
+ if ((a = GetAsset(path, pathlen))) {
+ if (!a->file && !IsCompressed(a)) {
+ /* fast path: this avoids extra copy */
+ data = ZIP_LFILE_CONTENT(zmap + a->lf);
+ size = GetZipLfileUncompressedSize(zmap + a->lf);
+ if (Verify(data, size, ZIP_LFILE_CRC32(zmap + a->lf))) {
+ lua_pushlstring(L, data, size);
+ return 1;
+ }
+ } else if ((data = LoadAsset(a, &size))) {
+ lua_pushlstring(L, data, size);
+ free(data);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int LuaGetDate(lua_State *L) {
+ lua_pushinteger(L, shared->nowish);
+ return 1;
+}
+
+static int LuaGetVersion(lua_State *L) {
+ lua_pushinteger(L, msg.version);
+ return 1;
+}
+
+static int LuaGetMethod(lua_State *L) {
+ if (msg.method) {
+ lua_pushstring(L, kHttpMethod[msg.method]);
+ } else {
+ lua_pushlstring(L, inbuf.p + msg.xmethod.a, msg.xmethod.b - msg.xmethod.a);
+ }
+ return 1;
+}
+
+static int LuaGetAddr(lua_State *L, void GetAddr(uint32_t *, uint16_t *)) {
+ uint32_t ip;
+ uint16_t port;
+ GetAddr(&ip, &port);
+ lua_pushinteger(L, ip);
+ lua_pushinteger(L, port);
+ return 2;
+}
+
+static int LuaGetServerAddr(lua_State *L) {
+ return LuaGetAddr(L, GetServerAddr);
+}
+
+static int LuaGetClientAddr(lua_State *L) {
+ return LuaGetAddr(L, GetClientAddr);
+}
+
+static int LuaGetRemoteAddr(lua_State *L) {
+ return LuaGetAddr(L, GetRemoteAddr);
+}
+
+static int LuaFormatIp(lua_State *L) {
+ char b[16];
+ uint32_t ip;
+ ip = htonl(luaL_checkinteger(L, 1));
+ inet_ntop(AF_INET, &ip, b, sizeof(b));
+ lua_pushstring(L, b);
+ return 1;
+}
+
+static int LuaParseIp(lua_State *L) {
+ size_t n;
+ const char *s;
+ s = luaL_checklstring(L, 1, &n);
+ lua_pushinteger(L, ParseIp(s, n));
+ return 1;
+}
+
+static int LuaIsIp(lua_State *L, bool IsIp(uint32_t)) {
+ lua_pushboolean(L, IsIp(luaL_checkinteger(L, 1)));
+ return 1;
+}
+
+static int LuaIsPublicIp(lua_State *L) {
+ return LuaIsIp(L, IsPublicIp);
+}
+
+static int LuaIsPrivateIp(lua_State *L) {
+ return LuaIsIp(L, IsPrivateIp);
+}
+
+static int LuaIsLoopbackIp(lua_State *L) {
+ return LuaIsIp(L, IsLoopbackIp);
+}
+
+static int LuaCategorizeIp(lua_State *L) {
+ lua_pushstring(L, GetIpCategoryName(CategorizeIp(luaL_checkinteger(L, 1))));
+ return 1;
+}
+
+static int LuaGetUrl(lua_State *L) {
+ char *p;
+ size_t n;
+ p = EncodeUrl(&url, &n);
+ lua_pushlstring(L, p, n);
+ free(p);
+ return 1;
+}
+
+static void LuaPushUrlView(lua_State *L, struct UrlView *v) {
+ if (v->p) {
+ lua_pushlstring(L, v->p, v->n);
+ } else {
+ lua_pushnil(L);
+ }
+}
+
+static int LuaGetScheme(lua_State *L) {
+ LuaPushUrlView(L, &url.scheme);
+ return 1;
+}
+
+static int LuaGetPath(lua_State *L) {
+ LuaPushUrlView(L, &url.path);
+ return 1;
+}
+
+static int LuaGetEffectivePath(lua_State *L) {
+ lua_pushlstring(L, effectivepath.p, effectivepath.n);
+ return 1;
+}
+
+static int LuaGetFragment(lua_State *L) {
+ LuaPushUrlView(L, &url.fragment);
+ return 1;
+}
+
+static int LuaGetUser(lua_State *L) {
+ size_t n;
+ const char *p, *q;
+ if (url.user.p) {
+ LuaPushUrlView(L, &url.user);
+ } else if ((p = GetBasicAuthorization(&n))) {
+ if (!(q = memchr(p, ':', n))) q = p + n;
+ lua_pushlstring(L, p, q - p);
+ free(p);
} else {
lua_pushnil(L);
}
return 1;
}
-static int LuaGetDate(lua_State *L) {
- lua_pushinteger(L, nowish);
+static int LuaGetPass(lua_State *L) {
+ size_t n;
+ const char *p, *q;
+ if (url.user.p) {
+ LuaPushUrlView(L, &url.pass);
+ } else if ((p = GetBasicAuthorization(&n))) {
+ if ((q = memchr(p, ':', n))) {
+ lua_pushlstring(L, q + 1, p + n - (q + 1));
+ } else {
+ lua_pushnil(L);
+ }
+ free(p);
+ } else {
+ lua_pushnil(L);
+ }
return 1;
}
-static int LuaGetVersion(lua_State *L) {
- lua_pushinteger(L, httpversion);
+static int LuaGetHost(lua_State *L) {
+ char b[16];
+ if (url.host.n) {
+ lua_pushlstring(L, url.host.p, url.host.n);
+ } else {
+ inet_ntop(AF_INET, &serveraddr.sin_addr.s_addr, b, sizeof(b));
+ lua_pushstring(L, b);
+ }
return 1;
}
-static int LuaGetMethod(lua_State *L) {
- lua_pushstring(L, kHttpMethod[msg.method]);
- return 1;
-}
-
-static int LuaGetPath(lua_State *L) {
- lua_pushlstring(L, request.path.p, request.path.n);
- 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) {
- LuaPushLatin1(L, inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a);
+static int LuaGetPort(lua_State *L) {
+ int i, x = 0;
+ for (i = 0; i < url.port.n; ++i) x = url.port.p[i] - '0' + x * 10;
+ if (!x) x = ntohs(serveraddr.sin_port);
+ lua_pushinteger(L, x);
return 1;
}
@@ -1567,40 +2812,68 @@ static int LuaParseHttpDateTime(lua_State *L) {
return 1;
}
-static int LuaGetClientAddr(lua_State *L) {
- lua_pushstring(L, clientaddrstr);
- return 1;
-}
-
-static int LuaGetServerAddr(lua_State *L) {
- lua_pushstring(L, serveraddrstr);
- return 1;
-}
-
static int LuaGetPayload(lua_State *L) {
lua_pushlstring(L, inbuf.p + hdrsize, msgsize - hdrsize);
return 1;
}
-static int LuaGetHeader(lua_State *L) {
- int h;
- const char *key;
- size_t i, keylen;
- key = luaL_checklstring(L, 1, &keylen);
- if ((h = GetHttpHeader(key, keylen)) != -1) {
- LuaPushLatin1(L, inbuf.p + msg.headers[h].a,
- msg.headers[h].b - msg.headers[h].a);
- 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 char *FoldHeader(int h, size_t *z) {
+ char *p;
+ size_t i, n, m;
+ struct HttpRequestHeader *x;
+ n = msg.headers[h].b - msg.headers[h].a;
+ p = xmalloc(n);
+ memcpy(p, inbuf.p + msg.headers[h].a, n);
for (i = 0; i < msg.xheaders.n; ++i) {
- if (!CompareSlicesCase(key, keylen, inbuf.p + msg.xheaders.p[i].k.a,
- msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a)) {
- LuaPushLatin1(L, inbuf.p + msg.xheaders.p[i].v.a,
- msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a);
- return 1;
+ x = msg.xheaders.p + i;
+ if (GetHttpHeader(inbuf.p + x->k.a, x->k.b - x->k.a) == h) {
+ m = x->v.b - x->v.a;
+ p = xrealloc(p, n + 2 + m);
+ memcpy(mempcpy(p + n, ", ", 2), inbuf.p + x->v.a, m);
+ n += 2 + m;
}
}
- lua_pushstring(L, "");
+ *z = n;
+ return p;
+}
+
+static int LuaGetHeader(lua_State *L) {
+ int h;
+ char *val;
+ const char *key;
+ size_t i, keylen, vallen;
+ key = luaL_checklstring(L, 1, &keylen);
+ if ((h = GetHttpHeader(key, keylen)) != -1) {
+ if (msg.headers[h].a) {
+ if (!kHttpRepeatable[h]) {
+ LuaPushLatin1(L, inbuf.p + msg.headers[h].a,
+ msg.headers[h].b - msg.headers[h].a);
+ } else {
+ val = FoldHeader(h, &vallen);
+ LuaPushLatin1(L, val, vallen);
+ free(val);
+ }
+ return 1;
+ }
+ } else {
+ for (i = 0; i < msg.xheaders.n; ++i) {
+ if (SlicesEqualCase(key, keylen, inbuf.p + msg.xheaders.p[i].k.a,
+ msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a)) {
+ LuaPushLatin1(L, inbuf.p + msg.xheaders.p[i].v.a,
+ msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a);
+ return 1;
+ }
+ }
+ }
+ lua_pushnil(L);
return 1;
}
@@ -1609,7 +2882,7 @@ static int LuaGetHeaders(lua_State *L) {
char *name;
lua_newtable(L);
for (i = 0; i < kHttpHeadersMax; ++i) {
- if (msg.headers[i].b - msg.headers[i].a) {
+ if (msg.headers[i].a) {
LuaPushLatin1(L, inbuf.p + msg.headers[i].a,
msg.headers[i].b - msg.headers[i].a);
lua_setfield(L, -2, GetHttpHeaderName(i));
@@ -1629,40 +2902,34 @@ static int LuaGetHeaders(lua_State *L) {
static int LuaSetHeader(lua_State *L) {
int h;
- char *p;
ssize_t rc;
+ char *p, *q;
const char *key, *val, *eval;
size_t i, keylen, vallen, evallen;
key = luaL_checklstring(L, 1, &keylen);
val = luaL_checklstring(L, 2, &vallen);
if ((h = GetHttpHeader(key, keylen)) == -1) {
if (!IsValidHttpToken(key, keylen)) {
- return luaL_argerror(L, 1, "invalid");
+ luaL_argerror(L, 1, "invalid");
+ unreachable;
}
}
if (!(eval = EncodeHttpHeaderValue(val, vallen, &evallen))) {
- return luaL_argerror(L, 2, "invalid");
+ luaL_argerror(L, 2, "invalid");
+ unreachable;
}
- if (!luaheaderp) {
- p = SetStatus(200, "OK");
- } else {
- while (luaheaderp - hdrbuf.p + keylen + 2 + evallen + 2 + 512 > hdrbuf.n) {
- hdrbuf.n += hdrbuf.n >> 1;
- p = xrealloc(hdrbuf.p, hdrbuf.n);
- luaheaderp = p + (luaheaderp - hdrbuf.p);
- hdrbuf.p = p;
- }
- p = luaheaderp;
+ p = GetLuaResponse();
+ while (p - hdrbuf.p + keylen + 2 + evallen + 2 + 512 > hdrbuf.n) {
+ hdrbuf.n += hdrbuf.n >> 1;
+ q = xrealloc(hdrbuf.p, hdrbuf.n);
+ luaheaderp = p = q + (p - hdrbuf.p);
+ hdrbuf.p = q;
}
switch (h) {
- case kHttpDate:
- case kHttpContentRange:
- case kHttpContentLength:
- case kHttpContentEncoding:
- return luaL_argerror(L, 1, "abstracted");
case kHttpConnection:
if (evallen != 5 || memcmp(eval, "close", 5)) {
- return luaL_argerror(L, 2, "unsupported");
+ luaL_argerror(L, 2, "unsupported");
+ unreachable;
}
connectionclose = true;
break;
@@ -1683,12 +2950,11 @@ static int LuaSetHeader(lua_State *L) {
}
static int LuaHasParam(lua_State *L) {
- const char *key;
- size_t i, keylen;
- key = luaL_checklstring(L, 1, &keylen);
- for (i = 0; i < request.params.n; ++i) {
- if (request.params.p[i].key.n == keylen &&
- !memcmp(request.params.p[i].key.p, key, keylen)) {
+ size_t i, n;
+ const char *s;
+ s = luaL_checklstring(L, 1, &n);
+ for (i = 0; i < url.params.n; ++i) {
+ if (SlicesEqual(s, n, url.params.p[i].key.p, url.params.p[i].key.n)) {
lua_pushboolean(L, true);
return 1;
}
@@ -1698,104 +2964,329 @@ static int LuaHasParam(lua_State *L) {
}
static int LuaGetParam(lua_State *L) {
- const char *key;
- size_t i, keylen;
- key = luaL_checklstring(L, 1, &keylen);
- for (i = 0; i < request.params.n; ++i) {
- if (request.params.p[i].key.n == keylen &&
- !memcmp(request.params.p[i].key.p, key, keylen)) {
- if (request.params.p[i].val.n == SIZE_MAX) break;
- lua_pushlstring(L, request.params.p[i].val.p, request.params.p[i].val.n);
- return 1;
+ size_t i, n;
+ const char *s;
+ s = luaL_checklstring(L, 1, &n);
+ for (i = 0; i < url.params.n; ++i) {
+ if (SlicesEqual(s, n, url.params.p[i].key.p, url.params.p[i].key.n)) {
+ if (url.params.p[i].val.p) {
+ lua_pushlstring(L, url.params.p[i].val.p, url.params.p[i].val.n);
+ return 1;
+ } else {
+ break;
+ }
}
}
lua_pushnil(L);
return 1;
}
-static int LuaGetParams(lua_State *L) {
+static void LuaPushUrlParams(lua_State *L, struct UrlParams *h) {
size_t i;
- lua_newtable(L);
- for (i = 0; i < request.params.n; ++i) {
+ if (h->p) {
lua_newtable(L);
- lua_pushlstring(L, request.params.p[i].key.p, request.params.p[i].key.n);
- lua_seti(L, -2, 1);
- if (request.params.p[i].val.n != SIZE_MAX) {
- lua_pushlstring(L, request.params.p[i].val.p, request.params.p[i].val.n);
- lua_seti(L, -2, 2);
+ for (i = 0; i < h->n; ++i) {
+ lua_newtable(L);
+ lua_pushlstring(L, h->p[i].key.p, h->p[i].key.n);
+ lua_seti(L, -2, 1);
+ if (h->p[i].val.p) {
+ lua_pushlstring(L, h->p[i].val.p, h->p[i].val.n);
+ lua_seti(L, -2, 2);
+ }
+ lua_seti(L, -2, i + 1);
}
- lua_seti(L, -2, i + 1);
+ } else {
+ lua_pushnil(L);
+ }
+}
+
+static int LuaGetParams(lua_State *L) {
+ LuaPushUrlParams(L, &url.params);
+ return 1;
+}
+
+static int LuaParseParams(lua_State *L) {
+ void *m;
+ size_t size;
+ const char *data;
+ struct UrlParams h;
+ data = luaL_checklstring(L, 1, &size);
+ memset(&h, 0, sizeof(h));
+ m = ParseParams(data, size, &h);
+ LuaPushUrlParams(L, &h);
+ free(h.p);
+ free(m);
+ return 1;
+}
+
+static void LuaSetUrlView(lua_State *L, struct UrlView *v, const char *k) {
+ LuaPushUrlView(L, v);
+ lua_setfield(L, -2, k);
+}
+
+static int LuaParseUrl(lua_State *L) {
+ void *m;
+ size_t n;
+ struct Url h;
+ const char *p;
+ p = luaL_checklstring(L, 1, &n);
+ m = ParseUrl(p, n, &h);
+ lua_newtable(L);
+ LuaSetUrlView(L, &h.scheme, "scheme");
+ LuaSetUrlView(L, &h.user, "user");
+ LuaSetUrlView(L, &h.pass, "pass");
+ LuaSetUrlView(L, &h.host, "host");
+ LuaSetUrlView(L, &h.port, "port");
+ LuaSetUrlView(L, &h.path, "path");
+ LuaSetUrlView(L, &h.fragment, "fragment");
+ LuaPushUrlParams(L, &h.params);
+ lua_setfield(L, -2, "params");
+ free(h.params.p);
+ free(m);
+ return 1;
+}
+
+static int LuaParseHost(lua_State *L) {
+ void *m;
+ size_t n;
+ struct Url h;
+ const char *p;
+ memset(&h, 0, sizeof(h));
+ p = luaL_checklstring(L, 1, &n);
+ m = ParseHost(p, n, &h);
+ lua_newtable(L);
+ LuaPushUrlView(L, &h.host);
+ LuaPushUrlView(L, &h.port);
+ free(m);
+ return 1;
+}
+
+static int LuaEncodeUrl(lua_State *L) {
+ void *m;
+ size_t size;
+ struct Url h;
+ int i, j, k, n;
+ const char *data;
+ if (!lua_isnil(L, 1)) {
+ memset(&h, 0, sizeof(h));
+ luaL_checktype(L, 1, LUA_TTABLE);
+ if (lua_getfield(L, 1, "scheme"))
+ h.scheme.p = lua_tolstring(L, -1, &h.scheme.n);
+ if (lua_getfield(L, 1, "fragment"))
+ h.fragment.p = lua_tolstring(L, -1, &h.fragment.n);
+ if (lua_getfield(L, 1, "user")) h.user.p = lua_tolstring(L, -1, &h.user.n);
+ if (lua_getfield(L, 1, "pass")) h.pass.p = lua_tolstring(L, -1, &h.pass.n);
+ if (lua_getfield(L, 1, "host")) h.host.p = lua_tolstring(L, -1, &h.host.n);
+ if (lua_getfield(L, 1, "port")) h.port.p = lua_tolstring(L, -1, &h.port.n);
+ if (lua_getfield(L, 1, "path")) h.path.p = lua_tolstring(L, -1, &h.path.n);
+ if (lua_getfield(L, 1, "params")) {
+ luaL_checktype(L, -1, LUA_TTABLE);
+ lua_len(L, -1);
+ n = lua_tointeger(L, -1);
+ for (i = -2, k = 0, j = 1; j <= n; ++j) {
+ if (lua_geti(L, i--, j)) {
+ luaL_checktype(L, -1, LUA_TTABLE);
+ if (lua_geti(L, -1, 1)) {
+ h.params.p =
+ xrealloc(h.params.p, ++h.params.n * sizeof(*h.params.p));
+ h.params.p[h.params.n - 1].key.p =
+ lua_tolstring(L, -1, &h.params.p[h.params.n - 1].key.n);
+ if (lua_geti(L, -2, 2)) {
+ h.params.p[h.params.n - 1].val.p =
+ lua_tolstring(L, -1, &h.params.p[h.params.n - 1].val.n);
+ } else {
+ h.params.p[h.params.n - 1].val.p = 0;
+ h.params.p[h.params.n - 1].val.n = 0;
+ }
+ }
+ i--;
+ }
+ i--;
+ }
+ }
+ data = EncodeUrl(&h, &size);
+ lua_pushlstring(L, data, size);
+ free(data);
+ } else {
+ lua_pushnil(L);
}
return 1;
}
static int LuaWrite(lua_State *L) {
- int h;
size_t size;
const char *data;
- data = luaL_checklstring(L, 1, &size);
- AppendData(data, size);
+ if (!lua_isnil(L, 1)) {
+ data = luaL_checklstring(L, 1, &size);
+ AppendData(data, size);
+ }
return 0;
}
-static int LuaEscaper(lua_State *L,
- struct EscapeResult escape(const char *, size_t)) {
- size_t size;
- const char *data;
- struct EscapeResult r;
- data = luaL_checklstring(L, 1, &size);
- r = escape(data, size);
- lua_pushlstring(L, r.data, r.size);
- free(r.data);
+static int LuaCheckControlFlags(lua_State *L, int idx) {
+ int f = luaL_checkinteger(L, idx);
+ if (f & ~(kControlWs | kControlC0 | kControlC1)) {
+ luaL_argerror(L, idx, "invalid control flags");
+ unreachable;
+ }
+ return f;
+}
+
+static int LuaHasControlCodes(lua_State *L) {
+ int f;
+ size_t n;
+ const char *p;
+ p = luaL_checklstring(L, 1, &n);
+ f = LuaCheckControlFlags(L, 2);
+ lua_pushboolean(L, HasControlCodes(p, n, f));
return 1;
}
-static int LuaEscapeHtml(lua_State *L) {
- return LuaEscaper(L, EscapeHtml);
+static int LuaIsValid(lua_State *L, bool IsValid(const char *, size_t)) {
+ size_t size;
+ const char *data;
+ data = luaL_checklstring(L, 1, &size);
+ lua_pushboolean(L, IsValid(data, size));
+ return 1;
}
-static int LuaEscapeParam(lua_State *L) {
- return LuaEscaper(L, EscapeUrlParam);
+static int LuaIsValidHttpToken(lua_State *L) {
+ return LuaIsValid(L, IsValidHttpToken);
}
-static int LuaEscapePath(lua_State *L) {
- return LuaEscaper(L, EscapeUrlPath);
+static int LuaIsAcceptablePath(lua_State *L) {
+ return LuaIsValid(L, IsAcceptablePath);
}
-static int LuaEscapeSegment(lua_State *L) {
- return LuaEscaper(L, EscapeUrlPathSegment);
+static int LuaIsReasonablePath(lua_State *L) {
+ return LuaIsValid(L, IsReasonablePath);
}
-static int LuaEscapeFragment(lua_State *L) {
- return LuaEscaper(L, EscapeUrlFragment);
+static int LuaIsAcceptableHost(lua_State *L) {
+ return LuaIsValid(L, IsAcceptableHost);
}
-static int LuaEscapeLiteral(lua_State *L) {
- return LuaEscaper(L, EscapeJsStringLiteral);
+static int LuaIsAcceptablePort(lua_State *L) {
+ return LuaIsValid(L, IsAcceptablePort);
+}
+
+static noinline int LuaCoderImpl(lua_State *L,
+ char *Coder(const char *, size_t, size_t *)) {
+ void *p;
+ size_t n;
+ p = luaL_checklstring(L, 1, &n);
+ p = Coder(p, n, &n);
+ lua_pushlstring(L, p, n);
+ free(p);
+ return 1;
+}
+
+static noinline int LuaCoder(lua_State *L,
+ char *Coder(const char *, size_t, size_t *)) {
+ return LuaCoderImpl(L, Coder);
+}
+
+static int LuaUnderlong(lua_State *L) {
+ return LuaCoder(L, Underlong);
}
static int LuaEncodeBase64(lua_State *L) {
+ return LuaCoder(L, EncodeBase64);
+}
+
+static int LuaDecodeBase64(lua_State *L) {
+ return LuaCoder(L, DecodeBase64);
+}
+
+static int LuaDecodeLatin1(lua_State *L) {
+ return LuaCoder(L, DecodeLatin1);
+}
+
+static int LuaEscapeHtml(lua_State *L) {
+ return LuaCoder(L, EscapeHtml);
+}
+
+static int LuaEscapeParam(lua_State *L) {
+ return LuaCoder(L, EscapeParam);
+}
+
+static int LuaEscapePath(lua_State *L) {
+ return LuaCoder(L, EscapePath);
+}
+
+static int LuaEscapeHost(lua_State *L) {
+ return LuaCoder(L, EscapeHost);
+}
+
+static int LuaEscapeIp(lua_State *L) {
+ return LuaCoder(L, EscapeIp);
+}
+
+static int LuaEscapeUser(lua_State *L) {
+ return LuaCoder(L, EscapeUser);
+}
+
+static int LuaEscapePass(lua_State *L) {
+ return LuaCoder(L, EscapePass);
+}
+
+static int LuaEscapeSegment(lua_State *L) {
+ return LuaCoder(L, EscapeSegment);
+}
+
+static int LuaEscapeFragment(lua_State *L) {
+ return LuaCoder(L, EscapeFragment);
+}
+
+static int LuaEscapeLiteral(lua_State *L) {
+ return LuaCoder(L, EscapeJsStringLiteral);
+}
+
+static int LuaVisualizeControlCodes(lua_State *L) {
+ return LuaCoder(L, VisualizeControlCodes);
+}
+
+static int LuaEncodeLatin1(lua_State *L) {
+ int f;
char *p;
- size_t size, n;
- const char *data;
- data = luaL_checklstring(L, 1, &size);
- p = EncodeBase64(data, size, &n);
+ size_t n;
+ p = luaL_checklstring(L, 1, &n);
+ f = LuaCheckControlFlags(L, 2);
+ p = EncodeLatin1(p, n, &n, f);
lua_pushlstring(L, p, n);
free(p);
return 1;
}
-static int LuaDecodeBase64(lua_State *L) {
- char *p;
- size_t size, n;
- const char *data;
- data = luaL_checklstring(L, 1, &size);
- p = DecodeBase64(data, size, &n);
+static int LuaIndentLines(lua_State *L) {
+ void *p;
+ size_t n, j;
+ p = luaL_checklstring(L, 1, &n);
+ j = luaL_optinteger(L, 2, 1);
+ if (!(0 <= j && j <= 65535)) {
+ luaL_argerror(L, 2, "not in range 0..65535");
+ unreachable;
+ }
+ p = IndentLines(p, n, &n, j);
lua_pushlstring(L, p, n);
free(p);
return 1;
}
+static int LuaGetMonospaceWidth(lua_State *L) {
+ int w;
+ if (lua_isinteger(L, 1)) {
+ w = wcwidth(lua_tointeger(L, 1));
+ } else if (lua_isstring(L, 1)) {
+ w = strwidth(luaL_checkstring(L, 1), luaL_optinteger(L, 2, 0) & 7);
+ } else {
+ luaL_argerror(L, 1, "not integer or string");
+ unreachable;
+ }
+ lua_pushinteger(L, w);
+ return 1;
+}
+
static int LuaPopcnt(lua_State *L) {
lua_pushinteger(L, popcnt(luaL_checkinteger(L, 1)));
return 1;
@@ -1807,7 +3298,8 @@ static int LuaBsr(lua_State *L) {
lua_pushinteger(L, bsr(x));
return 1;
} else {
- return luaL_argerror(L, 1, "zero");
+ luaL_argerror(L, 1, "zero");
+ unreachable;
}
}
@@ -1817,38 +3309,48 @@ static int LuaBsf(lua_State *L) {
lua_pushinteger(L, bsf(x));
return 1;
} else {
- return luaL_argerror(L, 1, "zero");
+ luaL_argerror(L, 1, "zero");
+ unreachable;
}
}
-static int LuaCrc32(lua_State *L) {
+static int LuaHash(lua_State *L, uint32_t H(uint32_t, const void *, size_t)) {
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));
+ lua_pushinteger(L, H(i, p, n));
return 1;
}
+static int LuaCrc32(lua_State *L) {
+ return LuaHash(L, crc32_z);
+}
+
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;
+ return LuaHash(L, crc32c);
+}
+
+static noinline int LuaProgramInt(lua_State *L, void Program(long)) {
+ Program(luaL_checkinteger(L, 1));
+ return 0;
}
static int LuaProgramPort(lua_State *L) {
- ProgramPort(luaL_checkinteger(L, 1));
- return 0;
+ return LuaProgramInt(L, ProgramPort);
}
static int LuaProgramCache(lua_State *L) {
- ProgramCache(luaL_checkinteger(L, 1));
- return 0;
+ return LuaProgramInt(L, ProgramCache);
+}
+
+static int LuaProgramLinger(lua_State *L) {
+ return LuaProgramInt(L, ProgramLinger);
+}
+
+static int LuaProgramTimeout(lua_State *L) {
+ return LuaProgramInt(L, ProgramTimeout);
}
static int LuaProgramBrand(lua_State *L) {
@@ -1856,6 +3358,14 @@ static int LuaProgramBrand(lua_State *L) {
return 0;
}
+static int LuaProgramHeader(lua_State *L) {
+ char *s;
+ s = xasprintf("%s: %s", luaL_checkstring(L, 1), luaL_checkstring(L, 2));
+ ProgramHeader(s);
+ free(s);
+ return 0;
+}
+
static int LuaProgramRedirect(lua_State *L) {
ProgramRedirect(luaL_checkinteger(L, 1), luaL_checkstring(L, 2),
luaL_checkstring(L, 3));
@@ -1873,21 +3383,30 @@ static int LuaSetLogLevel(lua_State *L) {
}
static int LuaHidePath(lua_State *L) {
- AddString(&hidepaths, strdup(luaL_checkstring(L, 1)));
+ size_t pathlen;
+ const char *path;
+ path = luaL_checklstring(L, 1, &pathlen);
+ AddString(&hidepaths, strndup(path, pathlen));
return 0;
}
static int LuaLog(lua_State *L) {
int level;
+ char *module;
lua_Debug ar;
- const char *msg, *module;
+ const char *msg;
level = luaL_checkinteger(L, 1);
if (LOGGABLE(level)) {
msg = luaL_checkstring(L, 2);
lua_getstack(L, 1, &ar);
lua_getinfo(L, "nSl", &ar);
- module = !strcmp(ar.name, "main") ? sauce : ar.name;
- flogf(level, module, ar.currentline, NULL, "%s", msg);
+ if (!strcmp(ar.name, "main")) {
+ module = strndup(effectivepath.p, effectivepath.n);
+ flogf(level, module, ar.currentline, NULL, "%s", msg);
+ free(module);
+ } else {
+ flogf(level, ar.name, ar.currentline, NULL, "%s", msg);
+ }
}
return 0;
}
@@ -1899,13 +3418,12 @@ static int LuaIsHiddenPath(lua_State *L) {
static int LuaGetZipPaths(lua_State *L) {
char *path;
- uint32_t cf;
+ uint64_t cf;
size_t i, n, pathlen;
lua_newtable(L);
i = 0;
- n = ZIP_CDIR_RECORDS(zdir);
- CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir));
- for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+ n = GetZipCdirRecords(cdir);
+ for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
path = GetAssetPath(cf, &pathlen);
lua_pushlstring(L, path, pathlen);
@@ -1915,65 +3433,341 @@ static int LuaGetZipPaths(lua_State *L) {
return 1;
}
-static void LuaRun(const char *path) {
+static int LuaGetAssetMode(lua_State *L) {
+ size_t pathlen;
+ struct Asset *a;
+ const char *path;
+ path = LuaCheckPath(L, 1, &pathlen);
+ if ((a = GetAsset(path, pathlen))) {
+ lua_pushinteger(L, GetMode(a));
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+static int LuaGetLastModifiedTime(lua_State *L) {
+ size_t pathlen;
+ struct Asset *a;
+ const char *path;
+ path = LuaCheckPath(L, 1, &pathlen);
+ if ((a = GetAsset(path, pathlen))) {
+ if (a->file) {
+ lua_pushinteger(L, a->file->st.st_mtim.tv_sec);
+ } else {
+ lua_pushinteger(L, GetZipCfileLastModified(zmap + a->cf));
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+static int LuaGetAssetSize(lua_State *L) {
+ size_t pathlen;
+ struct Asset *a;
+ const char *path;
+ path = LuaCheckPath(L, 1, &pathlen);
+ if ((a = GetAsset(path, pathlen))) {
+ if (a->file) {
+ lua_pushinteger(L, a->file->st.st_size);
+ } else {
+ lua_pushinteger(L, GetZipLfileUncompressedSize(zmap + a->lf));
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+static int LuaIsCompressed(lua_State *L) {
+ size_t pathlen;
+ struct Asset *a;
+ const char *path;
+ path = LuaCheckPath(L, 1, &pathlen);
+ if ((a = GetAsset(path, pathlen))) {
+ lua_pushboolean(L, IsCompressed(a));
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+static int LuaGetComment(lua_State *L) {
+ struct Asset *a;
+ const char *path;
+ size_t pathlen, m;
+ path = LuaCheckPath(L, 1, &pathlen);
+ if ((a = GetAssetZip(path, pathlen)) &&
+ (m = strnlen(ZIP_CFILE_COMMENT(zmap + a->cf),
+ ZIP_CFILE_COMMENTSIZE(zmap + a->cf)))) {
+ lua_pushlstring(L, ZIP_CFILE_COMMENT(zmap + a->cf), m);
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+static void LuaSetIntField(lua_State *L, const char *k, lua_Integer v) {
+ lua_pushinteger(L, v);
+ lua_setfield(L, -2, k);
+}
+
+static int LuaLaunchBrowser(lua_State *L) {
+ LaunchBrowser();
+ return 1;
+}
+
+static regex_t *LuaReCompileImpl(lua_State *L, const char *p, int f) {
+ int e;
+ regex_t *r;
+ r = xmalloc(sizeof(regex_t));
+ f &= REG_EXTENDED | REG_ICASE | REG_NEWLINE | REG_NOSUB;
+ f ^= REG_EXTENDED;
+ if ((e = regcomp(r, p, f)) != REG_OK) {
+ free(r);
+ luaL_error(L, "regcomp(%s) → REG_%s", p,
+ kRegCode[MAX(0, MIN(ARRAYLEN(kRegCode) - 1, e))]);
+ unreachable;
+ }
+ return r;
+}
+
+static int LuaReSearchImpl(lua_State *L, regex_t *r, const char *s, int f) {
+ int i, n;
+ regmatch_t *m;
+ n = r->re_nsub + 1;
+ m = xcalloc(n, sizeof(regmatch_t));
+ if (regexec(r, s, n, m, f >> 8) == REG_OK) {
+ for (i = 0; i < n; ++i) {
+ lua_pushlstring(L, s + m[i].rm_so, m[i].rm_eo - m[i].rm_so);
+ }
+ } else {
+ n = 0;
+ }
+ free(m);
+ return n;
+}
+
+static int LuaReSearch(lua_State *L) {
+ regex_t *r;
+ int i, e, n, f;
+ const char *p, *s;
+ p = luaL_checkstring(L, 1);
+ s = luaL_checkstring(L, 2);
+ f = luaL_optinteger(L, 3, 0);
+ if (f & ~(REG_EXTENDED | REG_ICASE | REG_NEWLINE | REG_NOSUB |
+ REG_NOTBOL << 8 | REG_NOTEOL << 8)) {
+ luaL_argerror(L, 3, "invalid flags");
+ unreachable;
+ }
+ r = LuaReCompileImpl(L, p, f);
+ n = LuaReSearchImpl(L, r, s, f);
+ regfree(r);
+ free(r);
+ return n;
+}
+
+static int LuaReCompile(lua_State *L) {
+ int f, e;
+ const char *p;
+ regex_t *r, **u;
+ p = luaL_checkstring(L, 1);
+ f = luaL_optinteger(L, 2, 0);
+ if (f & ~(REG_EXTENDED | REG_ICASE | REG_NEWLINE | REG_NOSUB)) {
+ luaL_argerror(L, 3, "invalid flags");
+ unreachable;
+ }
+ r = LuaReCompileImpl(L, p, f);
+ u = lua_newuserdata(L, sizeof(regex_t *));
+ luaL_setmetatable(L, "regex_t*");
+ *u = r;
+ return 1;
+}
+
+static int LuaReRegexSearch(lua_State *L) {
+ int f;
+ regex_t **u;
+ const char *s;
+ u = luaL_checkudata(L, 1, "regex_t*");
+ s = luaL_checkstring(L, 2);
+ f = luaL_optinteger(L, 3, 0);
+ if (!*u) {
+ luaL_argerror(L, 1, "destroyed");
+ unreachable;
+ }
+ if (f & ~(REG_NOTBOL << 8 | REG_NOTEOL << 8)) {
+ luaL_argerror(L, 3, "invalid flags");
+ unreachable;
+ }
+ return LuaReSearchImpl(L, *u, s, f);
+}
+
+static int LuaReRegexGc(lua_State *L) {
+ regex_t **u;
+ u = luaL_checkudata(L, 1, "regex_t*");
+ if (*u) {
+ regfree(*u);
+ free(*u);
+ *u = NULL;
+ }
+ return 0;
+}
+
+static const luaL_Reg kLuaRe[] = {
+ {"compile", LuaReCompile}, //
+ {"search", LuaReSearch}, //
+ {NULL, NULL}, //
+};
+
+static const luaL_Reg kLuaReRegexMeth[] = {
+ {"search", LuaReRegexSearch}, //
+ {NULL, NULL}, //
+};
+
+static const luaL_Reg kLuaReRegexMeta[] = {
+ {"__gc", LuaReRegexGc}, //
+ {NULL, NULL}, //
+};
+
+static void LuaReRegex(lua_State *L) {
+ luaL_newmetatable(L, "regex_t*");
+ luaL_setfuncs(L, kLuaReRegexMeta, 0);
+ luaL_newlibtable(L, kLuaReRegexMeth);
+ luaL_setfuncs(L, kLuaReRegexMeth, 0);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+}
+
+static int LuaRe(lua_State *L) {
+ luaL_newlib(L, kLuaRe);
+ LuaSetIntField(L, "BASIC", REG_EXTENDED);
+ LuaSetIntField(L, "ICASE", REG_ICASE);
+ LuaSetIntField(L, "NEWLINE", REG_NEWLINE);
+ LuaSetIntField(L, "NOSUB", REG_NOSUB);
+ LuaSetIntField(L, "NOTBOL", REG_NOTBOL << 8);
+ LuaSetIntField(L, "NOTEOL", REG_NOTEOL << 8);
+ LuaReRegex(L);
+ return 1;
+}
+
+static bool LuaRun(const char *path) {
+ size_t pathlen;
struct Asset *a;
const char *code;
- if ((a = LocateAsset(path, strlen(path)))) {
- code = LoadAsset(a, NULL);
- sauce = path + 1;
- if (luaL_dostring(L, code) != LUA_OK) {
- WARNF("%s %s", path, lua_tostring(L, -1));
+ pathlen = strlen(path);
+ if ((a = GetAsset(path, pathlen))) {
+ if ((code = LoadAsset(a, NULL))) {
+ effectivepath.p = path;
+ effectivepath.n = pathlen;
+ DEBUGF("LuaRun(%`'s)", path);
+ if (luaL_dostring(L, code) != LUA_OK) {
+ WARNF("%s %s", path, lua_tostring(L, -1));
+ }
+ free(code);
}
- free(code);
- } else {
- DEBUGF("%s not found", path);
}
+ return !!a;
}
static const luaL_Reg kLuaFuncs[] = {
- {"DecodeBase64", LuaDecodeBase64}, //
- {"EncodeBase64", LuaEncodeBase64}, //
- {"EscapeFragment", LuaEscapeFragment}, //
- {"EscapeHtml", LuaEscapeHtml}, //
- {"EscapeLiteral", LuaEscapeLiteral}, //
- {"EscapeParam", LuaEscapeParam}, //
- {"EscapePath", LuaEscapePath}, //
- {"EscapeSegment", LuaEscapeSegment}, //
- {"FormatHttpDateTime", LuaFormatHttpDateTime}, //
- {"GetClientAddr", LuaGetClientAddr}, //
- {"GetDate", LuaGetDate}, //
- {"GetHeader", LuaGetHeader}, //
- {"GetHeaders", LuaGetHeaders}, //
- {"GetLogLevel", LuaGetLogLevel}, //
- {"GetMethod", LuaGetMethod}, //
- {"GetParam", LuaGetParam}, //
- {"GetParams", LuaGetParams}, //
- {"GetPath", LuaGetPath}, //
- {"GetPayload", LuaGetPayload}, //
- {"GetServerAddr", LuaGetServerAddr}, //
- {"GetUri", LuaGetUri}, //
- {"GetVersion", LuaGetVersion}, //
- {"GetZipPaths", LuaGetZipPaths}, //
- {"HasParam", LuaHasParam}, //
- {"HidePath", LuaHidePath}, //
- {"LoadAsset", LuaLoadAsset}, //
- {"Log", LuaLog}, //
- {"ParseHttpDateTime", LuaParseHttpDateTime}, //
- {"ProgramBrand", LuaProgramBrand}, //
- {"ProgramCache", LuaProgramCache}, //
- {"ProgramPort", LuaProgramPort}, //
- {"ProgramRedirect", LuaProgramRedirect}, //
- {"ServeAsset", LuaServeAsset}, //
- {"ServeError", LuaServeError}, //
- {"SetHeader", LuaSetHeader}, //
- {"SetLogLevel", LuaSetLogLevel}, //
- {"SetStatus", LuaSetStatus}, //
- {"Write", LuaWrite}, //
- {"bsf", LuaBsf}, //
- {"bsr", LuaBsr}, //
- {"crc32", LuaCrc32}, //
- {"crc32c", LuaCrc32c}, //
- {"popcnt", LuaPopcnt}, //
+ {"CategorizeIp", LuaCategorizeIp}, //
+ {"DecodeBase64", LuaDecodeBase64}, //
+ {"DecodeLatin1", LuaDecodeLatin1}, //
+ {"EncodeBase64", LuaEncodeBase64}, //
+ {"EncodeLatin1", LuaEncodeLatin1}, //
+ {"EncodeUrl", LuaEncodeUrl}, //
+ {"EscapeFragment", LuaEscapeFragment}, //
+ {"EscapeHost", LuaEscapeHost}, //
+ {"EscapeHtml", LuaEscapeHtml}, //
+ {"EscapeIp", LuaEscapeIp}, //
+ {"EscapeLiteral", LuaEscapeLiteral}, //
+ {"EscapeParam", LuaEscapeParam}, //
+ {"EscapePass", LuaEscapePass}, //
+ {"EscapePath", LuaEscapePath}, //
+ {"EscapeSegment", LuaEscapeSegment}, //
+ {"EscapeUser", LuaEscapeUser}, //
+ {"FormatHttpDateTime", LuaFormatHttpDateTime}, //
+ {"FormatIp", LuaFormatIp}, //
+ {"GetAssetMode", LuaGetAssetMode}, //
+ {"GetAssetSize", LuaGetAssetSize}, //
+ {"GetClientAddr", LuaGetClientAddr}, //
+ {"GetComment", LuaGetComment}, //
+ {"GetDate", LuaGetDate}, //
+ {"GetEffectivePath", LuaGetEffectivePath}, //
+ {"GetFragment", LuaGetFragment}, //
+ {"GetHeader", LuaGetHeader}, //
+ {"GetHeaders", LuaGetHeaders}, //
+ {"GetHost", LuaGetHost}, //
+ {"GetLastModifiedTime", LuaGetLastModifiedTime}, //
+ {"GetLogLevel", LuaGetLogLevel}, //
+ {"GetMethod", LuaGetMethod}, //
+ {"GetMonospaceWidth", LuaGetMonospaceWidth}, //
+ {"GetParam", LuaGetParam}, //
+ {"GetParams", LuaGetParams}, //
+ {"GetPass", LuaGetPass}, //
+ {"GetPath", LuaGetPath}, //
+ {"GetPayload", LuaGetPayload}, //
+ {"GetPort", LuaGetPort}, //
+ {"GetRemoteAddr", LuaGetRemoteAddr}, //
+ {"GetScheme", LuaGetScheme}, //
+ {"GetServerAddr", LuaGetServerAddr}, //
+ {"GetUrl", LuaGetUrl}, //
+ {"GetUser", LuaGetUser}, //
+ {"GetVersion", LuaGetVersion}, //
+ {"GetZipPaths", LuaGetZipPaths}, //
+ {"HasControlCodes", LuaHasControlCodes}, //
+ {"HasParam", LuaHasParam}, //
+ {"HidePath", LuaHidePath}, //
+ {"IndentLines", LuaIndentLines}, //
+ {"IsAcceptableHost", LuaIsAcceptableHost}, //
+ {"IsAcceptablePath", LuaIsAcceptablePath}, //
+ {"IsAcceptablePort", LuaIsAcceptablePort}, //
+ {"IsCompressed", LuaIsCompressed}, //
+ {"IsHiddenPath", LuaIsHiddenPath}, //
+ {"IsLoopbackIp", LuaIsLoopbackIp}, //
+ {"IsPrivateIp", LuaIsPrivateIp}, //
+ {"IsPublicIp", LuaIsPublicIp}, //
+ {"IsReasonablePath", LuaIsReasonablePath}, //
+ {"IsValidHttpToken", LuaIsValidHttpToken}, //
+ {"LaunchBrowser", LuaLaunchBrowser}, //
+ {"LoadAsset", LuaLoadAsset}, //
+ {"Log", LuaLog}, //
+ {"ParseHost", LuaParseHost}, //
+ {"ParseHttpDateTime", LuaParseHttpDateTime}, //
+ {"ParseIp", LuaParseIp}, //
+ {"ParseParams", LuaParseParams}, //
+ {"ParseUrl", LuaParseUrl}, //
+ {"ProgramBrand", LuaProgramBrand}, //
+ {"ProgramCache", LuaProgramCache}, //
+ {"ProgramHeader", LuaProgramHeader}, //
+ {"ProgramLinger", LuaProgramLinger}, //
+ {"ProgramPort", LuaProgramPort}, //
+ {"ProgramRedirect", LuaProgramRedirect}, //
+ {"ProgramTimeout", LuaProgramTimeout}, //
+ {"Route", LuaRoute}, //
+ {"RouteHost", LuaRouteHost}, //
+ {"RoutePath", LuaRoutePath}, //
+ {"ServeAsset", LuaServeAsset}, //
+ {"ServeError", LuaServeError}, //
+ {"ServeIndex", LuaServeIndex}, //
+ {"ServeListing", LuaServeListing}, //
+ {"ServeStatusz", LuaServeStatusz}, //
+ {"SetHeader", LuaSetHeader}, //
+ {"SetLogLevel", LuaSetLogLevel}, //
+ {"SetStatus", LuaSetStatus}, //
+ {"Underlong", LuaUnderlong}, //
+ {"VisualizeControlCodes", LuaVisualizeControlCodes}, //
+ {"Write", LuaWrite}, //
+ {"bsf", LuaBsf}, //
+ {"bsr", LuaBsr}, //
+ {"crc32", LuaCrc32}, //
+ {"crc32c", LuaCrc32c}, //
+ {"popcnt", LuaPopcnt}, //
+};
+
+static const luaL_Reg kLuaLibs[] = {
+ {"re", LuaRe}, //
};
static void LuaSetArgv(lua_State *L) {
@@ -1992,9 +3786,14 @@ static void LuaSetConstant(lua_State *L, const char *s, long x) {
}
static void LuaInit(void) {
+#ifndef STATIC
size_t i;
L = luaL_newstate();
luaL_openlibs(L);
+ for (i = 0; i < ARRAYLEN(kLuaLibs); ++i) {
+ luaL_requiref(L, kLuaLibs[i].name, kLuaLibs[i].func, 1);
+ lua_pop(L, 1);
+ }
for (i = 0; i < ARRAYLEN(kLuaFuncs); ++i) {
lua_pushcfunction(L, kLuaFuncs[i].func);
lua_setglobal(L, kLuaFuncs[i].name);
@@ -2006,88 +3805,44 @@ static void LuaInit(void) {
LuaSetConstant(L, "kLogWarn", kLogWarn);
LuaSetConstant(L, "kLogError", kLogError);
LuaSetConstant(L, "kLogFatal", kLogFatal);
- LuaRun("/tool/net/.init.lua");
+ if (LuaRun("/.init.lua")) {
+ hasluaglobalhandler = !!lua_getglobal(L, "OnHttpRequest");
+ lua_pop(L, 1);
+ } else {
+ DEBUGF("no /.init.lua defined");
+ }
+#endif
}
static void LuaReload(void) {
- LuaRun("/tool/net/.reload.lua");
-}
-
-static char *ServeLua(struct Asset *a) {
- char *p;
- luaheaderp = NULL;
- sauce = FreeLater(strndup(request.path.p + 1, request.path.n - 1));
- if (luaL_dostring(L, FreeLater(LoadAsset(a, NULL))) == LUA_OK) {
- if (!(p = luaheaderp)) {
- p = SetStatus(200, "OK");
- p = AppendContentType(p, "text/html");
- }
- if (outbuf.n) {
- p = CommitOutput(p);
- }
- return p;
- } else {
- WARNF("%s %s", clientaddrstr, lua_tostring(L, -1));
- lua_pop(L, 1); /* remove message */
- connectionclose = true;
- return ServeError(500, "Internal Server Error");
+#ifndef STATIC
+ if (!LuaRun("/.reload.lua")) {
+ DEBUGF("no /.reload.lua defined");
}
+#endif
}
-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,
- ".lua", 4);
+static void HandleReload(void) {
+ LOGF("reloading");
+ LuaReload();
}
-static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) {
- char *p;
- if (IsLua(a)) {
- p = ServeLua(a);
- } else if (msg.method == kHttpGet || msg.method == kHttpHead) {
- p = ServeAsset(a, path, pathlen);
- p = AppendHeader(p, "X-Content-Type-Options", "nosniff");
- } else {
- p = ServeError(405, "Method Not Allowed");
- }
- return p;
-}
-
-static char *HandleRedirect(struct Redirect *r) {
- struct Asset *a;
- if (!r->code) {
- if ((a = LocateAsset(r->location, strlen(r->location)))) {
- DEBUGF("%s %s %`'.*s rewritten %`'s", clientaddrstr,
- kHttpMethod[msg.method], request.path.n, request.path.p,
- r->location);
- return HandleAsset(a, r->location, strlen(r->location));
- } else {
- return ServeError(505, "HTTP Version Not Supported");
- }
- } else if (httpversion == 9) {
- return ServeError(505, "HTTP Version Not Supported");
- } else {
- DEBUGF("%s %s %`'.*s %d redirecting %`'s", clientaddrstr,
- kHttpMethod[msg.method], request.path.n, request.path.p, r->code,
- r->location);
- return AppendHeader(SetStatus(r->code, GetHttpReason(r->code)), "Location",
- FreeLater(EncodeHttpHeaderValue(r->location, -1, 0)));
- }
+static void HandleHeartbeat(void) {
+ if (nowl() - lastrefresh > 60 * 60) RefreshTime();
+ UpdateCurrentDate(nowl());
+ getrusage(RUSAGE_SELF, &shared->server);
+#ifndef STATIC
+ LuaRun("/.heartbeat.lua");
+#endif
}
static void LogMessage(const char *d, const char *s, size_t n) {
- size_t n2, n3, n4;
- char *s2, *s3, *s4;
- if (!logmessages) return;
+ size_t n2, n3;
+ char *s2, *s3;
while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n;
if ((s2 = DecodeLatin1(s, n, &n2))) {
- if ((s3 = VisualizeControlCodes(s2, n2, &n3))) {
- if ((s4 = IndentLines(s3, n3, &n4, 1))) {
- LOGF("%s %s %,ld byte message\n%.*s", clientaddrstr, d, n, n4, s4);
- free(s4);
- }
+ if ((s3 = IndentLines(s2, n2, &n3, 1))) {
+ LOGF("%s %,ld byte message\n%.*s", d, n, n3, s3);
free(s3);
}
free(s2);
@@ -2097,23 +3852,22 @@ 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, *s3;
size_t n2, n3;
- if (!n || !logbodies) return;
+ if (!n) return;
while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n;
if ((s2 = VisualizeControlCodes(s, n, &n2))) {
if ((s3 = IndentLines(s2, n2, &n3, 1))) {
- LOGF("%s %s %,ld byte payload\n%.*s", clientaddrstr, d, n, n3, s3);
+ LOGF("%s %,ld byte payload\n%.*s", d, n, n3, s3);
free(s3);
}
free(s2);
}
}
-static ssize_t SendMessageString(const char *s) {
+static ssize_t SendString(const char *s) {
size_t n;
ssize_t rc;
n = strlen(s);
- LogMessage("sending", s, n);
- return 0;
+ if (logmessages) LogMessage("sending", s, n);
for (;;) {
if ((rc = write(client, s, n)) != -1 || errno != EINTR) {
return rc;
@@ -2122,13 +3876,13 @@ static ssize_t SendMessageString(const char *s) {
}
static ssize_t SendContinue(void) {
- return SendMessageString("\
+ return SendString("\
HTTP/1.1 100 Continue\r\n\
\r\n");
}
static ssize_t SendTimeout(void) {
- return SendMessageString("\
+ return SendString("\
HTTP/1.1 408 Request Timeout\r\n\
Connection: close\r\n\
Content-Length: 0\r\n\
@@ -2136,7 +3890,7 @@ Content-Length: 0\r\n\
}
static ssize_t SendServiceUnavailable(void) {
- return SendMessageString("\
+ return SendString("\
HTTP/1.1 503 Service Unavailable\r\n\
Connection: close\r\n\
Content-Length: 0\r\n\
@@ -2144,10 +3898,13 @@ Content-Length: 0\r\n\
}
static void LogClose(const char *reason) {
- if (amtread) {
- WARNF("%s %s with %,ld bytes unprocessed", clientaddrstr, reason, amtread);
+ if (amtread || meltdown || killed) {
+ LockInc(&shared->fumbles);
+ LOGF("%s %s with %,ld unprocessed and %,d handled (%,d workers)",
+ DescribeClient(), reason, amtread, messageshandled, shared->workers);
} else {
- DEBUGF("%s %s", clientaddrstr, reason);
+ DEBUGF("%s %s with %,d requests handled", DescribeClient(), reason,
+ messageshandled);
}
}
@@ -2159,282 +3916,302 @@ static const char *DescribeClose(void) {
return "destroyed";
}
-static const char *DescribeCompressionRatio(char rb[8], uint32_t cf) {
- long percent;
- if (ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf) == kZipCompressionNone) {
- return "n/a";
- } else {
- percent = lround(100 - (double)ZIP_CFILE_COMPRESSEDSIZE(zmap + cf) /
- ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf) * 100);
- sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent)));
- return rb;
+static void RecordNetworkOrigin(void) {
+ uint32_t ip;
+ GetRemoteAddr(&ip, 0);
+ switch (CategorizeIp(ip)) {
+ case kIpLoopback:
+ LockInc(&shared->netloopback);
+ break;
+ case kIpPrivate:
+ LockInc(&shared->netprivate);
+ break;
+ case kIpTestnet:
+ LockInc(&shared->nettestnet);
+ break;
+ case kIpAfrinic:
+ LockInc(&shared->netafrinic);
+ break;
+ case kIpLacnic:
+ LockInc(&shared->netlacnic);
+ break;
+ case kIpApnic:
+ LockInc(&shared->netapnic);
+ break;
+ case kIpArin:
+ LockInc(&shared->netarin);
+ break;
+ case kIpRipe:
+ LockInc(&shared->netripe);
+ break;
+ case kIpDod:
+ LockInc(&shared->netdod);
+ break;
+ case kIpAtt:
+ LockInc(&shared->netatt);
+ break;
+ case kIpApple:
+ LockInc(&shared->netapple);
+ break;
+ case kIpFord:
+ LockInc(&shared->netford);
+ break;
+ case kIpCogent:
+ LockInc(&shared->netcogent);
+ break;
+ case kIpPrudential:
+ LockInc(&shared->netprudential);
+ break;
+ case kIpUsps:
+ LockInc(&shared->netusps);
+ break;
+ case kIpComcast:
+ LockInc(&shared->netcomcast);
+ break;
+ case kIpAnonymous:
+ LockInc(&shared->netanonymous);
+ break;
+ default:
+ LockInc(&shared->netother);
+ break;
}
}
-static void LoadLogo(void) {
- char *p;
- size_t n;
- struct Asset *a;
- const char *logopath;
- logopath = "/tool/net/redbean.png";
- if ((a = LocateAsset(logopath, strlen(logopath)))) {
- p = LoadAsset(a, &n);
- logo.p = EncodeBase64(p, n, &logo.n);
- free(p);
- }
-}
-
-static char *ServeListing(void) {
- char rb[8];
- char tb[64];
- int w, x, y;
- struct tm tm;
- char *p, *path;
- int64_t lastmod;
- uint32_t cf, lf;
- size_t i, n, pathlen;
- struct EscapeResult r[3];
- AppendString("\
-\n\
- \n\
-redbean zip listing \n\
-\n\
-\n");
- if (logo.n) {
- AppendString(" \n");
- }
- r[0] = EscapeHtml(brand, strlen(brand));
- AppendData(r[0].data, r[0].size);
- free(r[0].data);
- AppendString(" \n");
- w = x = 0;
- n = ZIP_CDIR_RECORDS(zdir);
- CHECK_EQ(kZipCdirHdrMagic, ZIP_CDIR_MAGIC(zdir));
- for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
- CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
- if (!IsHiddenPath((path = GetAssetPath(cf, &pathlen)))) {
- y = strwidth(path, 0);
- w = MIN(80, MAX(w, y + 2));
- y = ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf);
- y = y ? llog10(y) : 1;
- x = MIN(80, MAX(x, y + (y - 1) / 3 + 2));
- }
- }
- n = ZIP_CDIR_RECORDS(zdir);
- for (cf = ZIP_CDIR_OFFSET(zdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
- CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
- path = GetAssetPath(cf, &pathlen);
- if (!IsHiddenPath(path)) {
- r[0] = EscapeHtml(path, pathlen);
- r[1] = EscapeUrlPath(path, pathlen);
- r[2] = EscapeHtml(r[1].data, r[1].size);
- lastmod = GetLastModifiedZip(zmap + cf);
- localtime_r(&lastmod, &tm);
- strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", &tm);
- if (IsCompressionMethodSupported(
- ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf)) &&
- IsAcceptableHttpRequestPath(path, pathlen)) {
- AppendFmt("%-*.*s %s %4s %,*ld\n", r[2].size,
- r[2].data, w, r[0].size, r[0].data, tb,
- DescribeCompressionRatio(rb, cf), x,
- ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf));
- } else {
- AppendFmt("%-*.*s %s %4s %,*ld\n", w, r[0].size, r[0].data, tb,
- DescribeCompressionRatio(rb, cf), x,
- ZIP_CFILE_UNCOMPRESSEDSIZE(zmap + cf));
- }
- free(r[2].data);
- free(r[1].data);
- free(r[0].data);
- }
- free(path);
- }
- AppendString(" \n");
- if (!unbranded) {
- AppendString("\
-this listing is for /\n\
-when there's no /index.lua or /index.html in your zip \n\
-redbean is based on\n\
-cosmopolitan and\n\
-αcτµαlly\n\
-pδrταblε εxεcµταblε \n\
-redbean is authored by Justine Tunney who's on\n\
-GitHub and\n\
-Twitter \n\
-your redbean is ");
- }
- w = shared->workers;
- AppendFmt("currently servicing %,d connection%s\n", w, w == 1 ? "" : "s");
- AppendString("
\n");
- p = SetStatus(200, "OK");
- p = AppendCache(p, 0);
- return CommitOutput(p);
-}
-
-static char *ServeServerOptions(void) {
+char *ServeServerOptions(void) {
char *p;
p = SetStatus(200, "OK");
- p = AppendHeader(p, "Accept", "*/*");
- p = AppendHeader(p, "Accept-Charset", "utf-8");
- p = AppendHeader(p, "Allow", "GET, HEAD, POST, PUT, DELETE, OPTIONS");
- VERBOSEF("%s pinged our server with OPTIONS *", clientaddrstr);
+#ifdef STATIC
+ p = stpcpy(p, "Allow: GET, HEAD, OPTIONS\r\n");
+#else
+ p = stpcpy(p, "Accept: */*\r\n"
+ "Accept-Charset: utf-8\r\n"
+ "Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS\r\n");
+#endif
return p;
}
+static bool HasAtMostThisElement(int h, const char *s) {
+ size_t i, n;
+ struct HttpRequestHeader *x;
+ if (HasHeader(h)) {
+ n = strlen(s);
+ if (!SlicesEqualCase(s, n, inbuf.p + msg.headers[h].a,
+ msg.headers[h].b - msg.headers[h].a)) {
+ return false;
+ }
+ for (i = 0; i < msg.xheaders.n; ++i) {
+ x = msg.xheaders.p + i;
+ if (GetHttpHeader(inbuf.p + x->k.a, x->k.b - x->k.a) == h &&
+ !SlicesEqualCase(inbuf.p + x->v.a, x->v.b - x->v.a, s, n)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
-static char *HandleMessage(void) {
- long r;
- ssize_t cl, rc;
- struct Asset *a;
- size_t got, need;
- httpversion =
- ParseHttpVersion(inbuf.p + msg.version.a, msg.version.b - msg.version.a);
- if (httpversion > 101) {
- return ServeError(505, "HTTP Version Not Supported");
- }
- if (HasHeader(kHttpExpect) && !HeaderEquals(kHttpExpect, "100-continue")) {
- return ServeError(417, "Expectation Failed");
- }
- if (msg.method == kHttpConnect ||
- (HasHeader(kHttpTransferEncoding) &&
- !HeaderEquals(kHttpTransferEncoding, "identity"))) {
- return ServeError(501, "Not Implemented");
- }
- if (!HasHeader(kHttpContentLength) &&
- (msg.method == kHttpPost || msg.method == kHttpPut)) {
- return ServeError(411, "Length Required");
- }
- if ((cl = ParseContentLength(inbuf.p + msg.headers[kHttpContentLength].a,
- msg.headers[kHttpContentLength].b -
- msg.headers[kHttpContentLength].a)) == -1) {
- return ServeError(400, "Bad Request");
- }
- need = hdrsize + cl; /* synchronization is possible */
- if (need > inbuf.n) {
- return ServeError(413, "Payload Too Large");
- }
- if (HeaderEquals(kHttpExpect, "100-continue") && httpversion >= 101) {
- SendContinue();
- }
- while (amtread < need) {
- if (++frags == 64) {
- LogClose("payload fragged!");
- return ServeError(408, "Request Timeout");
- }
- if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) {
- if (!(got = rc)) {
- LogClose("payload disconnect");
- return ServeError(400, "Bad Request");
- }
- amtread += got;
- } else if (errno == ECONNRESET) {
- LogClose("payload reset");
- return ServeError(400, "Bad Request");
- } else if (errno == EINTR) {
- if (killed || ((meltdown || terminated) && nowl() - startread > 1)) {
- LogClose(DescribeClose());
- return ServeError(503, "Service Unavailable");
- }
+static char *SynchronizeStream(void) {
+ size_t got;
+ ssize_t rc;
+ int64_t cl;
+ if ((cl = ParseContentLength(HeaderData(kHttpContentLength),
+ HeaderLength(kHttpContentLength))) == -1) {
+ if (HasHeader(kHttpContentLength)) {
+ LockInc(&shared->badlengths);
+ LOGF("bad content length");
+ return ServeFailure(400, "Bad Request");
+ } else if (msg.method == kHttpPost || msg.method == kHttpPut) {
+ LockInc(&shared->missinglengths);
+ return ServeFailure(411, "Length Required");
} else {
- WARNF("%s payload recv %s", clientaddrstr, strerror(errno));
- return ServeError(500, "Internal Server Error");
+ cl = 0;
}
}
- msgsize = need; /* we are now synchronized */
- LogBody("received", inbuf.p + hdrsize, msgsize - hdrsize);
- if (httpversion != 101 || !CompareHeader(kHttpConnection, "close")) {
- connectionclose = true;
+ if (hdrsize + cl > amtread) {
+ if (hdrsize + cl > inbuf.n) {
+ LockInc(&shared->hugepayloads);
+ return ServeFailure(413, "Payload Too Large");
+ }
+ if (msg.version >= 11 && HeaderEqualCase(kHttpExpect, "100-continue")) {
+ LockInc(&shared->continues);
+ SendContinue();
+ }
+ while (amtread < hdrsize + cl) {
+ LockInc(&shared->frags);
+ if (++frags == 64) {
+ LockInc(&shared->slowloris);
+ LogClose("payload slowloris");
+ return ServeFailure(408, "Request Timeout");
+ }
+ if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) {
+ if (!(got = rc)) {
+ LockInc(&shared->payloaddisconnects);
+ LogClose("payload disconnect");
+ return ServeFailure(400, "Bad Request"); /* XXX */
+ }
+ amtread += got;
+ } else if (errno == ECONNRESET) {
+ LockInc(&shared->readresets);
+ LogClose("payload reset");
+ return ServeFailure(400, "Bad Request"); /* XXX */
+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ LockInc(&shared->readtimeouts);
+ LogClose("payload read timeout");
+ return ServeFailure(408, "Request Timeout");
+ } else if (errno == EINTR) {
+ LockInc(&shared->readinterrupts);
+ if (killed || ((meltdown || terminated) && nowl() - startread > 1)) {
+ LockInc(&shared->dropped);
+ LogClose(DescribeClose());
+ return ServeFailure(503, "Service Unavailable");
+ }
+ } else {
+ LockInc(&shared->readerrors);
+ LOGF("%s payload read error %s", DescribeClient(), strerror(errno));
+ return ServeFailure(500, "Internal Server Error");
+ }
+ }
}
- ParseRequestUri();
- if (msg.method == kHttpOptions &&
- !CompareSlices(request.path.p, request.path.n, "*", 1)) {
- return ServeServerOptions();
+ msgsize = hdrsize + cl;
+ return NULL;
+}
+
+static void ParseRequestParameters(void) {
+ uint32_t ip;
+ FreeLater(ParseRequestUri(inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a, &url));
+ if (!url.host.p) {
+ GetRemoteAddr(&ip, 0);
+ if (HasHeader(kHttpXForwardedHost) &&
+ (IsPrivateIp(ip) || IsLoopbackIp(ip))) {
+ FreeLater(ParseHost(HeaderData(kHttpXForwardedHost),
+ HeaderLength(kHttpXForwardedHost), &url));
+ } else if (HasHeader(kHttpHost)) {
+ FreeLater(
+ ParseHost(HeaderData(kHttpHost), HeaderLength(kHttpHost), &url));
+ }
+ } else if (!url.path.n) {
+ url.path.p = "/";
+ url.path.n = 1;
}
- if (!IsAcceptableHttpRequestPath(request.path.p, request.path.n)) {
- WARNF("%s could not parse request %`'.*s", clientaddrstr,
- msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a);
- connectionclose = true;
- return ServeError(400, "Bad Request");
+ if (HasHeader(kHttpContentType) &&
+ IsMimeType(HeaderData(kHttpContentType), HeaderLength(kHttpContentType),
+ "application/x-www-form-urlencoded")) {
+ FreeLater(ParseParams(inbuf.p + hdrsize, msgsize - hdrsize, &url.params));
}
- if (HeaderEquals(kHttpContentType, "application/x-www-form-urlencoded")) {
- ParseFormParams();
- }
- VERBOSEF("%s %s %`'.*s referrer %`'.*s", clientaddrstr,
- kHttpMethod[msg.method], request.path.n, request.path.p,
- msg.headers[kHttpReferer].b - msg.headers[kHttpReferer].a,
- inbuf.p + msg.headers[kHttpReferer].a);
- if ((a = LocateAsset(request.path.p, request.path.n))) {
- return HandleAsset(a, request.path.p, request.path.n);
- } else if ((r = FindRedirect(request.path.p, request.path.n)) != -1) {
- return HandleRedirect(redirects.p + r);
- } else if (!CompareSlices(request.path.p, request.path.n, "/", 1)) {
- return ServeListing();
+ FreeLater(url.params.p);
+}
+
+static char *OnHttpRequest(void) {
+ effectivepath.p = url.path.p;
+ effectivepath.n = url.path.n;
+ lua_getglobal(L, "OnHttpRequest");
+ if (lua_pcall(L, 0, 0, 0) == LUA_OK) {
+ return CommitOutput(GetLuaResponse());
} else {
- return ServeError(404, "Not Found");
+ WARNF("%s", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ return ServeError(500, "Internal Server Error");
}
}
-static bool HandleRequest(void) {
- int rc;
+static char *HandleRequest(void) {
char *p;
+ if (msg.version < 10) {
+ LockInc(&shared->http09);
+ } else if (msg.version == 10) {
+ LockInc(&shared->http10);
+ } else if (msg.version == 11) {
+ LockInc(&shared->http11);
+ } else {
+ LockInc(&shared->http12);
+ return ServeFailure(505, "HTTP Version Not Supported");
+ }
+ if ((p = SynchronizeStream())) return p;
+ if (logbodies) LogBody("received", inbuf.p + hdrsize, msgsize - hdrsize);
+ if (msg.version < 11 || HeaderEqualCase(kHttpConnection, "close")) {
+ connectionclose = true;
+ }
+ if (msg.method == kHttpOptions &&
+ SlicesEqual(inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a, "*", 1)) {
+ LockInc(&shared->serveroptions);
+ return ServeServerOptions();
+ }
+ if (msg.method == kHttpConnect) {
+ LockInc(&shared->connectsrefused);
+ return ServeFailure(501, "Not Implemented");
+ }
+ if (!HasAtMostThisElement(kHttpExpect, "100-continue")) {
+ LockInc(&shared->expectsrefused);
+ return ServeFailure(417, "Expectation Failed");
+ }
+ if (!HasAtMostThisElement(kHttpTransferEncoding, "identity")) {
+ LockInc(&shared->transfersrefused);
+ return ServeFailure(501, "Not Implemented");
+ }
+ ParseRequestParameters();
+ if (!url.path.n || url.path.p[0] != '/' ||
+ !IsAcceptablePath(url.path.p, url.path.n) ||
+ !IsAcceptableHost(url.host.p, url.host.n) ||
+ !IsAcceptablePort(url.port.p, url.port.n)) {
+ LockInc(&shared->urisrefused);
+ return ServeFailure(400, "Bad Request");
+ }
+ LOGF("RECEIVED %s HTTP%02d %.*s %s %`'.*s %`'.*s", DescribeClient(),
+ msg.version, msg.xmethod.b - msg.xmethod.a, inbuf.p + msg.xmethod.a,
+ FreeLater(EncodeUrl(&url, 0)), HeaderLength(kHttpReferer),
+ HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent),
+ HeaderData(kHttpUserAgent));
+ if (hasluaglobalhandler) return OnHttpRequest();
+ return Route(url.host.p, url.host.n, url.path.p, url.path.n);
+}
+
+static bool HandleMessage(void) {
+ int rc;
int iovlen;
+ char *p, *s;
struct iovec iov[4];
long actualcontentlength;
+ g_syscount = 0;
if ((rc = ParseHttpRequest(&msg, inbuf.p, amtread)) != -1) {
if (!rc) return false;
hdrsize = rc;
- LogMessage("received", inbuf.p, hdrsize);
- p = HandleMessage();
+ if (logmessages) LogMessage("received", inbuf.p, hdrsize);
+ RecordNetworkOrigin();
+ p = HandleRequest();
} else {
- httpversion = 101;
+ LockInc(&shared->badmessages);
connectionclose = true;
- p = ServeError(400, "Bad Request");
- DEBUGF("%s received garbage %`'.*s", clientaddrstr, amtread, inbuf.p);
+ LOGF("%s sent garbage %`'s", DescribeClient(),
+ VisualizeControlCodes(inbuf.p, MIN(128, amtread), 0));
+ p = ServeError(400, "Bad Message");
}
if (!msgsize) {
amtread = 0;
connectionclose = true;
- DEBUGF("%s could not synchronize message stream", clientaddrstr);
- } else if (msgsize < amtread) {
- DEBUGF("%s has %,ld pipelined bytes", clientaddrstr, amtread - msgsize);
- memmove(inbuf.p, inbuf.p + msgsize, amtread - msgsize);
- amtread -= msgsize;
- } else {
- amtread = 0;
+ LockInc(&shared->synchronizationfailures);
+ DEBUGF("could not synchronize message stream");
}
- if (httpversion >= 100) {
- p = AppendHeader(p, "Date", currentdate);
- if (!branded) {
- p = AppendHeader(p, "Server", serverheader);
- }
+ if (0 && connectionclose) {
+ LockInc(&shared->shutdowns);
+ shutdown(client, SHUT_RD);
+ }
+ if (msg.version >= 10) {
+ p = AppendCrlf(stpcpy(stpcpy(p, "Date: "), shared->currentdate));
+ if (!branded) p = AppendServer(p, serverheader);
+ if (extrahdrs) p = stpcpy(p, extrahdrs);
if (connectionclose) {
- p = AppendHeader(p, "Connection", "close");
- } else if (encouragekeepalive && httpversion >= 101) {
- p = AppendHeader(p, "Connection", "keep-alive");
+ p = stpcpy(p, "Connection: close\r\n");
+ } else if (encouragekeepalive && msg.version >= 11) {
+ p = stpcpy(p, "Connection: keep-alive\r\n");
}
actualcontentlength = contentlength;
if (gzipped) {
actualcontentlength += sizeof(kGzipHeader) + sizeof(gzip_footer);
+ p = stpcpy(p, "Content-Encoding: gzip\r\n");
}
p = AppendContentLength(p, actualcontentlength);
p = AppendCrlf(p);
CHECK_LE(p - hdrbuf.p, hdrbuf.n);
- LogMessage("sending", hdrbuf.p, p - hdrbuf.p);
+ if (logmessages) LogMessage("sending", hdrbuf.p, p - hdrbuf.p);
iov[0].iov_base = hdrbuf.p;
iov[0].iov_len = p - hdrbuf.p;
iovlen = 1;
@@ -2458,48 +4235,59 @@ static bool HandleRequest(void) {
iov[0].iov_len = contentlength;
iovlen = 1;
}
+ if (loglatency || LOGGABLE(kLogDebug)) {
+ flogf(kLogDebug, __FILE__, __LINE__, NULL, "%`'.*s latency %,ldµs",
+ msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a,
+ (long)((nowl() - startrequest) * 1e6L));
+ }
Send(iov, iovlen);
- CollectGarbage();
+ LockInc(&shared->messageshandled);
+ ++messageshandled;
return true;
}
static void InitRequest(void) {
frags = 0;
+ gzipped = 0;
+ branded = 0;
+ content = 0;
msgsize = 0;
- outbuf.p = 0;
+ loops.n = 0;
outbuf.n = 0;
- content = NULL;
- gzipped = false;
- branded = false;
+ luaheaderp = 0;
contentlength = 0;
InitHttpRequest(&msg);
}
-static void ProcessRequests(void) {
+static void HandleMessages(void) {
ssize_t rc;
size_t got;
- long double now;
for (;;) {
InitRequest();
startread = nowl();
for (;;) {
- if (!msg.i && amtread && HandleRequest()) break;
+ if (!msg.i && amtread) {
+ startrequest = nowl();
+ if (HandleMessage()) break;
+ }
if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) {
- startrequest = now = nowl();
- if (now - nowish > 1) UpdateCurrentDate(now);
+ startrequest = nowl();
got = rc;
amtread += got;
if (amtread) {
- if (HandleRequest()) {
+ DEBUGF("%s read %,zd bytes", DescribeClient(), got);
+ if (HandleMessage()) {
break;
} else if (got) {
+ LockInc(&shared->frags);
if (++frags == 32) {
SendTimeout();
- LogClose("fragged!");
+ LogClose("slowloris");
+ LockInc(&shared->slowloris);
return;
} else {
- DEBUGF("%s fragmented msg %,ld %,ld", clientaddrstr, amtread,
- got);
+ DEBUGF("%s fragged msg added %,ld bytes to %,ld byte buffer",
+ DescribeClient(), amtread, got);
}
}
}
@@ -2507,40 +4295,74 @@ static void ProcessRequests(void) {
LogClose("disconnect");
return;
}
+ } else if (errno == EINTR) {
+ LockInc(&shared->readinterrupts);
+ } else if (errno == EAGAIN) {
+ LockInc(&shared->readtimeouts);
+ if (amtread) SendTimeout();
+ LogClose("timeout");
+ return;
} else if (errno == ECONNRESET) {
+ LockInc(&shared->readresets);
LogClose("reset");
return;
- } else if (errno != EINTR) {
- WARNF("%s recv msg %s", clientaddrstr, strerror(errno));
+ } else {
+ LockInc(&shared->readerrors);
+ WARNF("%s read failed %s", DescribeClient(), strerror(errno));
return;
}
if (killed || (terminated && !amtread) ||
(meltdown && (!amtread || nowl() - startread > 1))) {
- if (amtread) SendServiceUnavailable();
+ if (amtread) {
+ LockInc(&shared->dropped);
+ SendServiceUnavailable();
+ }
LogClose(DescribeClose());
return;
}
}
- if (connectionclose || killed || ((terminated || meltdown) && !amtread)) {
- LogClose(DescribeClose());
- return;
+ if (msgsize == amtread) {
+ amtread = 0;
+ if (connectionclose || killed || terminated || meltdown) {
+ LogClose(DescribeClose());
+ return;
+ }
+ } else {
+ CHECK_LT(msgsize, amtread);
+ LockInc(&shared->pipelinedrequests);
+ DEBUGF("%,ld pipelined bytes", amtread - msgsize);
+ memmove(inbuf.p, inbuf.p + msgsize, amtread - msgsize);
+ amtread -= msgsize;
+ if (connectionclose || killed) {
+ LogClose(DescribeClose());
+ return;
+ }
}
+ CollectGarbage();
}
}
static void EnterMeltdownMode(void) {
- if (lastmeltdown && nowl() - lastmeltdown < 1) return;
- WARNF("redbean is entering meltdown mode with %,d workers", shared->workers);
+ if (shared->lastmeltdown && nowl() - shared->lastmeltdown < 1) return;
+ WARNF("redbean is melting down (%,d workers)", shared->workers);
LOGIFNEG1(kill(0, SIGUSR2));
- lastmeltdown = nowl();
+ shared->lastmeltdown = nowl();
+ ++shared->meltdowns;
}
-static void ProcessConnection(void) {
+static void EmergencyClose(int fd) {
+ struct linger nolinger = {0};
+ setsockopt(fd, SOL_SOCKET, SO_LINGER, &nolinger, sizeof(nolinger));
+ close(fd);
+}
+
+static void HandleConnection(void) {
int pid;
clientaddrsize = sizeof(clientaddr);
if ((client = accept4(server, &clientaddr, &clientaddrsize, SOCK_CLOEXEC)) !=
-1) {
startconnection = nowl();
+ messageshandled = 0;
if (uniprocess) {
pid = -1;
connectionclose = true;
@@ -2549,46 +4371,128 @@ static void ProcessConnection(void) {
case 0:
meltdown = false;
connectionclose = false;
+ if (funtrace && !IsTiny()) {
+ ftrace_install();
+ }
break;
case -1:
+ FATALF("%s too many processes %s", DescribeServer(), strerror(errno));
+ ++shared->forkerrors;
+ LockInc(&shared->dropped);
EnterMeltdownMode();
SendServiceUnavailable();
- /* fallthrough */
+ EmergencyClose(client);
+ return;
default:
++shared->workers;
close(client);
return;
}
}
- DescribeAddress(clientaddrstr, &clientaddr);
- DEBUGF("%s accept", clientaddrstr);
- ProcessRequests();
- LOGIFNEG1(close(client));
- if (!pid) _exit(0);
- } else if (errno != EINTR) {
- FATALF("%s accept error %s", serveraddrstr, strerror(errno));
+ if (!pid) close(server);
+ DEBUGF("%s accepted", DescribeClient());
+ HandleMessages();
+ DEBUGF("%s closing after %,ldµs", DescribeClient(),
+ (long)((nowl() - startconnection) * 1e6L));
+ if (close(client) == -1) {
+ LockInc(&shared->closeerrors);
+ WARNF("%s close failed", DescribeClient());
+ }
+ if (!pid) {
+ _exit(0);
+ } else {
+ CollectGarbage();
+ }
+ } else if (errno == EINTR || errno == EAGAIN) {
+ ++shared->acceptinterrupts;
+ } else if (errno == ENFILE) {
+ LockInc(&shared->enfiles);
+ WARNF("%s too many open files", DescribeServer());
+ EnterMeltdownMode();
+ } else if (errno == EMFILE) {
+ LockInc(&shared->emfiles);
+ WARNF("%s ran out of open file quota", DescribeServer());
+ EnterMeltdownMode();
+ } else if (errno == ENOMEM) {
+ LockInc(&shared->enomems);
+ WARNF("%s ran out of memory");
+ EnterMeltdownMode();
+ } else if (errno == ENOBUFS) {
+ LockInc(&shared->enobufs);
+ WARNF("%s ran out of buffer");
+ EnterMeltdownMode();
+ } else if (errno == ENONET) {
+ ++shared->enonets;
+ WARNF("%s network gone", DescribeServer());
+ sleep(1);
+ } else if (errno == ENETDOWN) {
+ ++shared->enetdowns;
+ WARNF("%s network down", DescribeServer());
+ sleep(1);
+ } else if (errno == ECONNABORTED) {
+ ++shared->acceptresets;
+ WARNF("%s connection reset before accept");
+ } else if (errno == ENETUNREACH || errno == EHOSTUNREACH ||
+ errno == EOPNOTSUPP || errno == ENOPROTOOPT || errno == EPROTO) {
+ ++shared->accepterrors;
+ WARNF("%s ephemeral accept error %s", DescribeServer(), strerror(errno));
+ } else {
+ FATALF("%s accept error %s", DescribeServer(), strerror(errno));
}
}
-static void TuneServerSocket(void) {
- int yes = 1;
- LOGIFNEG1(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)));
- LOGIFNEG1(setsockopt(server, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)));
- LOGIFNEG1(setsockopt(server, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)));
- LOGIFNEG1(setsockopt(server, IPPROTO_TCP, TCP_FASTOPEN, &yes, sizeof(yes)));
- LOGIFNEG1(setsockopt(server, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes)));
+static void Tune(int a, int b, int x, const char *as, const char *bs) {
+#define Tune(A, B, X) Tune(A, B, X, #A, #B)
+ if (!b) return;
+ if (setsockopt(server, a, b, &x, sizeof(x)) == -1) {
+ WARNF("setsockopt(server, %s, %s, %d) failed %s", as, bs, x,
+ strerror(errno));
+ }
}
-void RedBean(int argc, char *argv[]) {
+static void TuneSockets(void) {
+ Tune(SOL_SOCKET, SO_REUSEADDR, 1);
+ Tune(IPPROTO_TCP, TCP_CORK, 0);
+ Tune(IPPROTO_TCP, TCP_NODELAY, 1);
+ Tune(IPPROTO_TCP, TCP_FASTOPEN, 1);
+ Tune(IPPROTO_TCP, TCP_QUICKACK, 1);
+ setsockopt(server, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
+ setsockopt(server, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
+ setsockopt(server, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
+}
+
+static void RestoreApe(const char *prog) {
+ char *p;
+ size_t n;
+ struct Asset *a;
+ extern char ape_rom_vaddr[] __attribute__((__weak__));
+ if (IsWindows()) return; /* TODO */
+ if (IsOpenbsd()) return; /* TODO */
+ if (IsNetbsd()) return; /* TODO */
+ if (endswith(prog, ".com.dbg")) return;
+ close(OpenExecutable());
+ if ((a = GetAssetZip("/.ape", 5)) && (p = LoadAsset(a, &n))) {
+ mprotect(ape_rom_vaddr, PAGESIZE, PROT_READ | PROT_WRITE);
+ memcpy(ape_rom_vaddr, p, MIN(n, PAGESIZE));
+ msync(ape_rom_vaddr, PAGESIZE, MS_ASYNC);
+ mprotect(ape_rom_vaddr, PAGESIZE, PROT_NONE);
+ free(p);
+ } else {
+ LOGF("/.ape not found");
+ }
+}
+
+void RedBean(int argc, char *argv[], const char *prog) {
uint32_t addrsize;
- gmtoff = GetGmtOffset();
+ gmtoff = GetGmtOffset((lastrefresh = startserver = nowl()));
+ CHECK_GT(CLK_TCK, 0);
CHECK_NE(MAP_FAILED,
(shared = mmap(NULL, ROUNDUP(sizeof(struct Shared), FRAMESIZE),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
-1, 0)));
- OpenZip((const char *)getauxval(AT_EXECFN));
+ OpenZip(prog);
IndexAssets();
- LoadLogo();
+ RestoreApe(prog);
SetDefaults();
GetOpts(argc, argv);
LuaInit();
@@ -2601,12 +4505,13 @@ void RedBean(int argc, char *argv[]) {
xsigaction(SIGUSR2, OnUsr2, 0, 0, 0);
xsigaction(SIGALRM, OnAlrm, 0, 0, 0);
xsigaction(SIGPIPE, SIG_IGN, 0, 0, 0);
+ /* TODO(jart): SIGXCPU and SIGXFSZ */
if (setitimer(ITIMER_REAL, &kHeartbeat, NULL) == -1) {
heartless = true;
}
- CHECK_NE(-1,
- (server = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP)));
- TuneServerSocket();
+ server = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
+ CHECK_NE(-1, server);
+ TuneSockets();
if (bind(server, &serveraddr, sizeof(serveraddr)) == -1) {
if (errno == EADDRINUSE) {
fprintf(stderr, "error: address in use\n"
@@ -2616,50 +4521,61 @@ void RedBean(int argc, char *argv[]) {
}
exit(1);
}
- if (daemonize) Daemonize();
CHECK_NE(-1, listen(server, 10));
addrsize = sizeof(serveraddr);
CHECK_NE(-1, getsockname(server, &serveraddr, &addrsize));
- DescribeAddress(serveraddrstr, &serveraddr);
- VERBOSEF("%s listen", serveraddrstr);
+ VERBOSEF("LISTEN %s", DescribeServer());
if (printport) {
printf("%d\n", ntohs(serveraddr.sin_port));
fflush(stdout);
}
+ if (daemonize) Daemonize();
UpdateCurrentDate(nowl());
- inbuf.n = 64 * 1024;
- inbuf.p = xvalloc(inbuf.n);
+ freelist.c = 8;
+ freelist.p = xcalloc(freelist.c, sizeof(*freelist.p));
+ unmaplist.c = 1;
+ unmaplist.p = xcalloc(unmaplist.c, sizeof(*unmaplist.p));
hdrbuf.n = 4 * 1024;
hdrbuf.p = xvalloc(hdrbuf.n);
+ inbuf.n = 64 * 1024;
+ inbuf.p = xvalloc(inbuf.n);
while (!terminated) {
if (zombied) {
ReapZombies();
} else if (invalidated) {
- LuaReload();
+ HandleReload();
invalidated = false;
} else if (heartbeat) {
- UpdateCurrentDate(nowl());
+ HandleHeartbeat();
heartbeat = false;
+ } else if (meltdown) {
+ EnterMeltdownMode();
+ meltdown = false;
} else {
- if (heartless) {
- UpdateCurrentDate(nowl());
- }
- ProcessConnection();
+ if (heartless) HandleHeartbeat();
+ HandleConnection();
}
}
- VERBOSEF("%s shutting down", serveraddrstr);
LOGIFNEG1(close(server));
- if (!keyboardinterrupt) {
+ if (keyboardinterrupt) {
+ LOGF("received keyboard interrupt");
+ } else {
+ LOGF("received term signal");
if (!killed) {
terminated = false;
}
+ DEBUGF("sending TERM to process group");
LOGIFNEG1(kill(0, SIGTERM));
}
WaitAll();
+ VERBOSEF("shutdown complete");
}
int main(int argc, char *argv[]) {
- showcrashreports();
- RedBean(argc, argv);
+ if (!IsTiny()) {
+ setenv("GDB", "", true);
+ showcrashreports();
+ }
+ RedBean(argc, argv, (const char *)getauxval(AT_EXECFN));
return 0;
}
diff --git a/tool/net/redbean.lua b/tool/net/redbean.lua
deleted file mode 100644
index ec8716733..000000000
--- a/tool/net/redbean.lua
+++ /dev/null
@@ -1,115 +0,0 @@
--- redbean lua server page demo
-
-local function main()
- -- This is the best way to print data to the console or log file.
- Log(kLogWarn, "hello from \e[1mlua\e[0m!")
-
- -- This check is pedantic but might be good to have.
- if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then
- ServeError(405)
- SetHeader('Allow', 'GET, HEAD')
- return
- end
-
- -- These two lines are optional.
- -- The default behavior is to do this if you don't.
- SetStatus(200) -- Shorthand for SetStatus(200, "OK")
- SetHeader('Content-Type', 'text/html; charset=utf-8')
-
- -- Response data is buffered until the script finishes running.
- -- Compression is applied automatically, based on your headers.
- Write('\n')
- Write('redbean \n')
- Write('redbean lua server page demo \n')
-
- -- Prevent caching.
- -- We need this because we're doing things like putting the client's
- -- IP address in the response so we naturally don't want that cached
- SetHeader('Expires', FormatHttpDateTime(GetDate()))
- SetHeader('Cache-Control', 'no-cache, must-revalidate, max-age=0')
-
- -- GetParams() returns an ordered list of Request-URI query params.
- Write('request uri parameters \n')
- params = GetParams()
- if #params > 0 then
- Write('\n')
- for i = 1,#params do
- Write('')
- Write(EscapeHtml(params[i][1]))
- Write('\n')
- if params[i][2] then
- Write(' ')
- Write(EscapeHtml(params[i][2]))
- Write('\n')
- end
- end
- Write(' \n')
- else
- Write('\n')
- Write('none \n')
- Write('ProTip: Try clicking here !\n')
- end
-
- -- Access redbean command line arguments.
- -- These are the ones that come *after* the redbean server arguments.
- Write('
command line arguments \n')
- if #argv > 0 then
- Write('\n')
- for i = 1,#argv do
- Write('')
- Write(EscapeHtml(argv[i]))
- Write('\n')
- end
- Write(' \n')
- else
- Write('none \n')
- end
-
- Write([[
-
post request html form demo
-
- ]])
-
- Write([[
- xmlhttprequest request demo
-
- X-Custom-Header
- send
-
-
- ]])
-
- Write('extra information \n')
- Write('GetClientAddr()\n')
- Write(' ')
- Write(GetClientAddr())
- Write('\n')
- Write(' GetServerAddr()\n')
- Write(' ')
- Write(GetServerAddr())
- Write('\n')
- Write('\n')
-end
-
-main()