Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Verify vendor libraries
run: |
fail=0
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
if [ ! -f "$lib" ]; then
echo "MISSING: $lib"
fail=1
Expand Down Expand Up @@ -132,7 +132,7 @@ jobs:
cp c_bridges/child-process-bridge.o release/lib/
cp c_bridges/child-process-spawn.o release/lib/
cp c_bridges/os-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o release/lib/
cp c_bridges/dotenv-bridge.o release/lib/
cp c_bridges/watch-bridge.o release/lib/
tar -czf chadscript-linux-x64.tar.gz -C release chad lib
Expand Down Expand Up @@ -186,7 +186,7 @@ jobs:
- name: Verify vendor libraries
run: |
fail=0
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
for lib in vendor/bdwgc/libgc.a vendor/yyjson/libyyjson.a vendor/libuv/build/libuv.a vendor/picohttpparser/picohttpparser.o c_bridges/lws-bridge.o c_bridges/multipart-bridge.o c_bridges/regex-bridge.o c_bridges/child-process-bridge.o c_bridges/child-process-spawn.o c_bridges/os-bridge.o c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o c_bridges/dotenv-bridge.o c_bridges/watch-bridge.o; do
if [ ! -f "$lib" ]; then
echo "MISSING: $lib"
fail=1
Expand Down Expand Up @@ -257,7 +257,7 @@ jobs:
cp c_bridges/child-process-bridge.o release/lib/
cp c_bridges/child-process-spawn.o release/lib/
cp c_bridges/os-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o release/lib/
cp c_bridges/time-bridge.o c_bridges/base64-bridge.o c_bridges/url-bridge.o c_bridges/uri-bridge.o release/lib/
cp c_bridges/dotenv-bridge.o release/lib/
cp c_bridges/watch-bridge.o release/lib/
tar -czf chadscript-macos-arm64.tar.gz -C release chad lib
Expand Down
64 changes: 62 additions & 2 deletions c_bridges/base64-bridge.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// base64-bridge.c — base64 decode for Buffer.from(str, 'base64') support.
// Returns a heap-allocated %Uint8Array struct (GC-managed).
// base64-bridge.c — base64 encode/decode for Buffer.from, btoa, atob support.

#include <stdint.h>
#include <string.h>
Expand All @@ -9,6 +8,67 @@ extern void* GC_malloc(size_t sz);

typedef struct { char* data; int len; int cap; } CsUint8Array;

static const char b64_enc[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

char* cs_btoa(const char* input) {
if (!input) {
char* out = (char*)GC_malloc_atomic(1);
out[0] = '\0';
return out;
}
size_t in_len = strlen(input);
size_t out_len = ((in_len + 2) / 3) * 4 + 1;
char* out = (char*)GC_malloc_atomic(out_len);
size_t i = 0, j = 0;
while (i < in_len) {
unsigned char a = (unsigned char)input[i++];
int has_b = (i < in_len);
unsigned char b = has_b ? (unsigned char)input[i++] : 0;
int has_c = (i < in_len);
unsigned char c = has_c ? (unsigned char)input[i++] : 0;
unsigned int triple = (a << 16) | (b << 8) | c;
out[j++] = b64_enc[(triple >> 18) & 0x3F];
out[j++] = b64_enc[(triple >> 12) & 0x3F];
out[j++] = has_b ? b64_enc[(triple >> 6) & 0x3F] : '=';
out[j++] = has_c ? b64_enc[triple & 0x3F] : '=';
}
out[j] = '\0';
return out;
}

char* cs_atob(const char* input) {
if (!input) {
char* out = (char*)GC_malloc_atomic(1);
out[0] = '\0';
return out;
}
size_t in_len = strlen(input);
size_t max_out = (in_len / 4) * 3 + 4;
char* out = (char*)GC_malloc_atomic(max_out);
size_t out_pos = 0;
int buf = 0, bits = 0;
for (size_t i = 0; i < in_len; i++) {
unsigned char ch = (unsigned char)input[i];
signed char v;
if (ch >= 'A' && ch <= 'Z') v = ch - 'A';
else if (ch >= 'a' && ch <= 'z') v = ch - 'a' + 26;
else if (ch >= '0' && ch <= '9') v = ch - '0' + 52;
else if (ch == '+') v = 62;
else if (ch == '/') v = 63;
else if (ch == '=') break;
else continue;
buf = (buf << 6) | v;
bits += 6;
if (bits >= 8) {
bits -= 8;
out[out_pos++] = (char)(buf >> bits);
buf &= (1 << bits) - 1;
}
}
out[out_pos] = '\0';
return out;
}

static const signed char b64_dec[256] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
Expand Down
66 changes: 66 additions & 0 deletions c_bridges/uri-bridge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <string.h>
#include <stdint.h>

extern void* GC_malloc_atomic(size_t sz);

static const char hex_chars[17] = "0123456789ABCDEF";

static int is_unreserved(unsigned char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~';
}

char* cs_encode_uri_component(const char* input) {
if (!input) {
char* out = (char*)GC_malloc_atomic(1);
out[0] = '\0';
return out;
}
size_t in_len = strlen(input);
char* out = (char*)GC_malloc_atomic(in_len * 3 + 1);
size_t j = 0;
for (size_t i = 0; i < in_len; i++) {
unsigned char c = (unsigned char)input[i];
if (is_unreserved(c)) {
out[j++] = (char)c;
} else {
out[j++] = '%';
out[j++] = hex_chars[(c >> 4) & 0xF];
out[j++] = hex_chars[c & 0xF];
}
}
out[j] = '\0';
return out;
}

static int hex_val(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return -1;
}

char* cs_decode_uri_component(const char* input) {
if (!input) {
char* out = (char*)GC_malloc_atomic(1);
out[0] = '\0';
return out;
}
size_t in_len = strlen(input);
char* out = (char*)GC_malloc_atomic(in_len + 1);
size_t j = 0;
for (size_t i = 0; i < in_len; i++) {
if (input[i] == '%' && i + 2 < in_len) {
int hi = hex_val(input[i + 1]);
int lo = hex_val(input[i + 2]);
if (hi >= 0 && lo >= 0) {
out[j++] = (char)((hi << 4) | lo);
i += 2;
continue;
}
}
out[j++] = input[i];
}
out[j] = '\0';
return out;
}
12 changes: 12 additions & 0 deletions docs/stdlib/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ const sig = crypto.hmacSha256("secret", "payload");
// "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"
```

## `crypto.pbkdf2(password, salt, iterations, keylen)`

Derive a key using PBKDF2-HMAC-SHA256. Returns a hex string of `keylen` bytes (2×keylen characters). Use for password hashing and key derivation.

```typescript
const hash = crypto.pbkdf2("password", "salt", 1, 20);
// "0c60c80f961f0e71f3a9b524af6012062fe037a6"

const key = crypto.pbkdf2(userPassword, randomSalt, 100000, 32);
```

## `crypto.randomBytes(n)`

Generate `n` random bytes, returned as a hex string (2n characters).
Expand Down Expand Up @@ -77,5 +88,6 @@ console.log(uuid);
| `crypto.sha512()` | OpenSSL EVP API (`EVP_sha512`) |
| `crypto.md5()` | OpenSSL EVP API (`EVP_md5`) |
| `crypto.hmacSha256()` | OpenSSL `HMAC()` with `EVP_sha256` |
| `crypto.pbkdf2()` | OpenSSL `PKCS5_PBKDF2_HMAC()` with `EVP_sha256` |
| `crypto.randomBytes()` | `RAND_bytes()` |
| `crypto.randomUUID()` | `RAND_bytes(16)` + version/variant bits + `snprintf` |
63 changes: 63 additions & 0 deletions docs/stdlib/encoding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Encoding

Global functions for base64 and percent-encoding (URL encoding). Available without any import.

## `btoa(str)`

Encode a string to base64.

```typescript
const encoded = btoa("hello world");
// "aGVsbG8gd29ybGQ="

const auth = "Basic " + btoa(username + ":" + password);
```

## `atob(str)`

Decode a base64 string.

```typescript
const decoded = atob("aGVsbG8gd29ybGQ=");
// "hello world"
```

## `encodeURIComponent(str)`

Percent-encode a string per RFC 3986. Unreserved characters (`A-Z a-z 0-9 - _ . ~`) are left as-is; everything else is encoded as `%XX`.

```typescript
const q = encodeURIComponent("hello world & foo=bar");
// "hello%20world%20%26%20foo%3Dbar"

const url = "https://api.example.com/search?q=" + encodeURIComponent(userInput);
```

## `decodeURIComponent(str)`

Decode a percent-encoded string.

```typescript
const s = decodeURIComponent("hello%20world%20%26%20foo%3Dbar");
// "hello world & foo=bar"
```

## Example

```typescript
const token = btoa("user:pass");
console.log(token); // "dXNlcjpwYXNz"
console.log(atob(token)); // "user:pass"

const params = "name=" + encodeURIComponent("John Doe") + "&city=" + encodeURIComponent("New York");
// "name=John%20Doe&city=New%20York"
```

## Native Implementation

| API | Maps to |
|-----|---------|
| `btoa()` | `cs_btoa()` in `c_bridges/base64-bridge.c` |
| `atob()` | `cs_atob()` in `c_bridges/base64-bridge.c` |
| `encodeURIComponent()` | `cs_encode_uri_component()` in `c_bridges/uri-bridge.c` |
| `decodeURIComponent()` | `cs_decode_uri_component()` in `c_bridges/uri-bridge.c` |
6 changes: 5 additions & 1 deletion docs/stdlib/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ Most APIs are available everywhere with no import:
```typescript
console.log("hello");
const data = fs.readFileSync("file.txt");
const hash = crypto.createHash("sha256");
const hash = crypto.sha256("hello");
const encoded = btoa("hello");
const url = "https://api.example.com/q=" + encodeURIComponent(query);
const u = new URL("https://example.com:8080/path?q=hello");
const p = new URLSearchParams("q=hello&page=2");
```

## Modules
Expand Down
72 changes: 72 additions & 0 deletions docs/stdlib/url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# URL

Built-in `URL` and `URLSearchParams` classes for parsing and manipulating URLs. Available without any import.

## `URL`

Parse a URL string into its components.

```typescript
const u = new URL("https://example.com:8080/path/to/page?q=hello&page=2#section");

u.protocol // "https:"
u.hostname // "example.com"
u.port // "8080"
u.host // "example.com:8080"
u.pathname // "/path/to/page"
u.search // "?q=hello&page=2"
u.hash // "#section"
u.origin // "https://example.com:8080"
u.href // "https://example.com:8080/path/to/page?q=hello&page=2#section"
```

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `protocol` | `string` | Scheme with colon — `"https:"` |
| `hostname` | `string` | Host without port — `"example.com"` |
| `port` | `string` | Port number as string, or `""` if absent |
| `host` | `string` | `hostname` + `:` + `port` if port present |
| `pathname` | `string` | Path component — `"/path/to/page"` |
| `search` | `string` | Query string including `?`, or `""` |
| `hash` | `string` | Fragment including `#`, or `""` |
| `origin` | `string` | `protocol + "//" + host` |
| `href` | `string` | Full URL string |

## `URLSearchParams`

Parse and manipulate query strings.

```typescript
const p = new URLSearchParams("q=hello&page=2");

p.get("q") // "hello"
p.get("page") // "2"
p.has("q") // true
p.has("foo") // false

p.set("q", "world")
p.append("tag", "foo")
p.delete("page")

p.toString() // "q=world&tag=foo"
```

### Methods

| Method | Returns | Description |
|--------|---------|-------------|
| `get(key)` | `string` | Value for key, or `""` if absent |
| `has(key)` | `boolean` | Whether key exists |
| `set(key, value)` | `void` | Set or replace value for key |
| `append(key, value)` | `void` | Add key/value (allows duplicates) |
| `delete(key)` | `void` | Remove all entries for key |
| `toString()` | `string` | Serialize back to query string (no leading `?`) |

## Native Implementation

| API | Maps to |
|-----|---------|
| `URL` properties | `cs_url_parse_*()` in `c_bridges/url-bridge.c` |
| `URLSearchParams` methods | `cs_urlsearch_*()` in `c_bridges/url-bridge.c` |
2 changes: 1 addition & 1 deletion scripts/build-target-sdk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fi

# Copy C bridge object files
echo " Copying bridge objects..."
for bridge in child-process-bridge.o os-bridge.o time-bridge.o base64-bridge.o url-bridge.o regex-bridge.o dotenv-bridge.o watch-bridge.o lws-bridge.o multipart-bridge.o child-process-spawn.o; do
for bridge in child-process-bridge.o os-bridge.o time-bridge.o base64-bridge.o url-bridge.o uri-bridge.o regex-bridge.o dotenv-bridge.o watch-bridge.o lws-bridge.o multipart-bridge.o child-process-spawn.o; do
if [ -f "$C_BRIDGES_DIR/$bridge" ]; then
cp "$C_BRIDGES_DIR/$bridge" "$SDK_DIR/bridges/"
fi
Expand Down
10 changes: 10 additions & 0 deletions scripts/build-vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,16 @@ else
echo "==> url-bridge already built, skipping"
fi

# --- uri-bridge ---
URI_BRIDGE_SRC="$C_BRIDGES_DIR/uri-bridge.c"
URI_BRIDGE_OBJ="$C_BRIDGES_DIR/uri-bridge.o"
if [ ! -f "$URI_BRIDGE_OBJ" ] || [ "$URI_BRIDGE_SRC" -nt "$URI_BRIDGE_OBJ" ]; then
echo "==> Building uri-bridge..."
cc -c -O2 -fPIC "$URI_BRIDGE_SRC" -o "$URI_BRIDGE_OBJ"
echo " -> $URI_BRIDGE_OBJ"
else
echo "==> uri-bridge already built, skipping"
fi
# --- child-process-spawn (async, requires libuv) ---
CP_SPAWN_SRC="$C_BRIDGES_DIR/child-process-spawn.c"
CP_SPAWN_OBJ="$C_BRIDGES_DIR/child-process-spawn.o"
Expand Down
Loading
Loading