173 Commits

Author SHA1 Message Date
Bob Farrell
1ea3f6c4ff Fix session nonce re-use
Use a new secure-random nonce for each session encryption.

Many thanks to @Trundle for writing [this gist](https://gist.github.com/Trundle/76c0d8f80999617f4d8373c03f86391a)
highlighting the severity of this issue.
2024-05-01 20:51:39 +01:00
Bob Farrell
3c88d845a8 Catch error.BrokenPipe and close connection
Users have reported this error occurring occasionally, close connection
and continue processing requests when it occurs.
2024-04-29 18:58:07 +01:00
Bob Farrell
ca153eb82c Initialize request with default status code .not_found
Avoid loading an undefined error code if `/errors` view is requested
directly.
2024-04-28 15:13:57 +01:00
Bob Farrell
1c7df8a91d Custom error pages
Render `src/app/views/errors.zig` view (`index` action) when an
unexpected error occurs. If the view does not exist, try rendering
`public/404.html`, `public/500.html` (etc.), finally falling back to a
standard basic HTML/JSON response.
2024-04-28 11:54:05 +01:00
Bob Farrell
6b940e8a53 0.13 support
Add support for 0.13. For now, we can be compatible with both 0.12 and
0.13. We will drop 0.12 after a while.
2024-04-27 23:03:53 +01:00
Bob Farrell
9d12b5c717 Update Zmpl, adds Markdown mode formatters
Also adds support for `.md.zmpl` templates - root node is Markdown.
2024-04-27 22:22:30 +01:00
Bob Farrell
47c35632b5 Email framework
Create mailers with `jetzig generate mailer <name>`. Mailers define
default values for email fields (e.g. subject, from address, etc.).
Mailers use Zmpl for rendering text/HTML parts.

Send an email from a request with `request.mail()`. Call
`deliver(.background, .{})` on the return value to use the built-in
mail job to send the email asynchronously.

Improve query/HTTP request body param parsing - unescape `+` and `%XX`
characters.
2024-04-21 21:00:03 +01:00
Bob Farrell
0c7a11497f Background jobs
Use in-memory KV store (JetKV) for simple job queue.

Build script generates an array of Zig modules in `src/app/jobs/` and
stores their name + run function (`run(allocator, params, logger)`).

View functions schedule jobs with arbitrary params.

Thread pool spawns a (configurable) number of workers and pops jobs from
the queue, then invokes the appropriate run function.
2024-04-15 18:12:40 +01:00
Bob Farrell
0b968c3b44 Minor improvements to request params and headers
Fully disambiguate request params from body and query string and clean
up convoluted code, reduce allocs. Implement
`jetzig.http.Request.queryParams` which always returns only the parsed
query string, never the request body.

Deprecate `headers.getFirstValue` in favour of `headers.get`, implement
`headers.getAll`.
2024-04-13 19:24:40 +01:00
Bob Farrell
16204a4832 Zmpl v2
Update to Zmpl v2, update demo app to be compatible with v2 syntax. Add
deprecation warning for v1 (v1 is default for now - will force v2 soon).
2024-04-07 10:34:34 +01:00
bobf
8096962cf6
Merge pull request #54 from jetzig-framework/empty-http-params
Closes #53: Add support for empty HTTP query param
2024-04-06 20:49:23 +01:00
Bob Farrell
4579d6c029 Closes #53: Add support for empty HTTP query param
When query param is empty, use `jetzig.data.Data.NullType` - this way
the value is still non-null (i.e. can be captured) and is still a
`jetzig.data.Value` so is consistent with previous implementation.

Add very weird bugfix for 404 responses.

Fix `Bad Request` response code.
2024-04-06 20:20:47 +01:00
Bob Farrell
336b80ed77 Disable colors on Windows
Too many unknowns trying to make this work reliably on Windows so just
disabling now to declutter output, we can re-enable later and do it
properly.
2024-04-03 19:11:40 +01:00
Bob Farrell
605c85ca97 Fix UAF in static routes
Dupe resource ID before freeing Data memory.
2024-04-03 18:49:32 +01:00
Bob Farrell
cd4932ff79 Use view_name correctly 2024-04-02 22:39:52 +01:00
Bob Farrell
f1ec5b216d Force unix-style path for view name
Windows compat.
2024-04-02 22:30:21 +01:00
Bob Farrell
eed58edeba Allow responding to HTML when no template present
If no template present, allow the request to render the view and then
only render a 404 if the view does not redirect, otherwise all views
would need a placeholder Zmpl template even if the view does not use it.

Also parse HTTP body as query string if present.
2024-04-02 22:15:18 +01:00
Bob Farrell
116668be3e Closes #48: Simplify route generation
Generate an array of `jetzig.views.Route` in `GenerateRoutes.zig` exe
instead of a meta-route that we later translate into an actual route.
This makes things much simpler for static routes at build time and
dynamic routes at run time as we no longer need to use comptime - we
just have an array of routes ready-made.
2024-04-02 21:02:27 +01:00
Bob Farrell
ec045cccd6 Add environments, use pretty-printed JSON in development
Fix secret generation - overallocate length to ensure we have enough
bytes.

Error if no secret provided in production mode.
2024-03-28 22:39:58 +00:00
Bob Farrell
95a8330629 Refactor rendering
Render appropriately-formatted errors for HTML/JSON.
2024-03-28 22:39:27 +00:00
Bob Farrell
625257bcfc Allow auto-routing to markdown files
If a route isn't matched but the URI can match directly to a markdown
file, render it directly. This allows putting arbitrary markdown files
in e.g. `/foo/bar/abc.md`, `/foo/bar/def.md` and rendering them as
`/foo/bar/abc.html`, `/foo/bar/def.html`. Since markdown are static
content only (i.e. no template data etc.) it makes sense for them to be
renderable without needing to create a view for each one.
2024-03-28 22:09:29 +00:00
Bob Farrell
13e77b4520 Implement nested routes
Allow creating routes nested in sub directories of arbitrary depth in
`src/app/views/`.
2024-03-27 19:58:20 +00:00
Bob Farrell
0e29934718 Implement Markdown
Create `.md` files instead of `.zmpl` files to render static markdown
content.
2024-03-26 22:20:27 +00:00
Bob Farrell
f8d1db5460 Inject jetzig_view and jetzig_action into Zmpl templates 2024-03-22 20:37:02 +00:00
Bob Farrell
054d1b5d82 Fix default error logfile
Don't use stderr for error logfile if primary logfile is not stdout
(i.e. always use the same file).

Fix non-colorized duration in DeveolpmentLogger - use std.fmt.fmtDurationSigned.
2024-03-20 23:39:27 +00:00
Bob Farrell
cc0a1c5792 Document default config values in init project 2024-03-20 21:26:59 +00:00
Bob Farrell
381c38d85d Overhaul logging, implement JSON logger
Log errors to separate files, specify minimum log level, implement JSON
logging.
2024-03-20 21:17:14 +00:00
Bob Farrell
87bcc4c9e0 Provide tooling for running Jetzig in deployment 2024-03-19 23:03:55 +00:00
Bob Farrell
993caa5d3f Implement redirects + htmx redirect support 2024-03-17 18:37:45 +00:00
Bob Farrell
9b255eb19a Refactor path parsing
Remove horrible path segments code, re-write as an abstracted,
alloc-less `jetzig.http.Path`.
2024-03-17 18:37:45 +00:00
Bob Farrell
60d27d9a6c Implement htmx middleware
When `HX-Target` header is present, bypass any configured layout for the
request. This allows a full page reload to render with a layout, i.e.
render the entire page, while a request from htmx will load just the
content directly generated by the view.
2024-03-17 18:36:32 +00:00
Bob Farrell
bc64f58c44 Remove unneeded filesystem lookup
`root_path` is cruft from previous route/template matching mechanism.
Caused failure when running binary in isolation: binary can now run
independently without any dependency on file system other than:

```
* public/ <-- should always be separate to allow e.g. serving directly
              via nginx
* static/ <-- later will be compiled into binary
```
2024-03-17 10:37:18 +00:00
Bob Farrell
3c47dff96e Fix Headers.getFirstValue
Bugfix to resolve issue with undefined behaviour when calling
`getFirstValue` - first header would always be returned. Add regression
test.
2024-03-17 10:26:10 +00:00
Bob Farrell
aa036fde8b Implement layouts
Latest Zmpl provides `template.renderWithLayout(other_template, data)`,
allowing a template to be renedered within another template.

Create layouts in `src/app/views/layouts/` or use
`jetzig generate layout [name]` and set `pub const layout = "name";` in
each view file.
2024-03-11 22:36:18 +00:00
Bob Farrell
5ff4d7127d Fix Windows paths again
Previous fix did not work, `@import` expects forward slashes so escaping
backslashes doesn't help. Replace `\` with `/` instead.
2024-03-10 17:13:17 +00:00
Bob Farrell
e004eb3d86 Fix routes.zig Windows paths
Use `std.zig.pstringEscape` to correctly escape a string for setting as
a Zig string. This should catch any potential issue with escaping
strings.
2024-03-10 17:01:41 +00:00
Bob Farrell
a78d53a19a Allow importing .zig files from src/ in views
Views are copied to zig-cache, meaning that any files in `src/` are not
available for `@import` by default. Copy all .zig files from `src/` into
zig-cache so that relative imports work as expected. This also removes
the need to specifically copy views into zig-cache as the full tree is
now present.

Also fix a bug in static route fallback - remove superfluous underscore
so static routes with non-matching params render default HTML/JSON
content.
2024-03-10 14:10:53 +00:00
Bob Farrell
dd54d55423 Use content-type header if present to detect format
Detect request format (HTML or JSON) from `content-type` header.

Previous:
- path extension (.json, .html)
- "Accept" header
- default (.UNKNOWN => .HTML)

Current:
- path extension (.json, .html)
- "Accept" header
- "Content-Type" header
- default (.UNKNOWN => .HTML)

Adjust `http.Headers` to do case-insensitive matching to conform to HTTP
spec: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names
2024-03-10 11:31:39 +00:00
Bob Farrell
f954439489 CLI generate command
Provide `jetzig generate ...` commands for views, middleware, and
partials.
2024-03-09 18:47:08 +00:00
Bob Farrell
26cbe2b67c Swap out importing jetzig.zig for explicit files containing tests
Latest Zig nightly now hits a panic, probably we have a cyclic reference
in the tree. We should fix this ASAP otherwise we risk adding tests
without running them, but for now this should get builds passing again
with latest Zig.
2024-03-09 14:33:54 +00:00
Bob Farrell
3a91bbea48 Skip parsing functions that are not in Route.Action enum
Allow custom functions with arbitrary signatures and avoid any issues of
trying to parse functions that are not Jeztig route action functions
(i.e. index, get, post, put, patch, delete).
2024-03-09 11:43:44 +00:00
Bob Farrell
d3a3582136 Implement init command, get rid of old init script/artifacts
Remove old bash script for setting up a new project, do everything in
Zig to make it platform agnostic and give us an easy place to add
scaffolding commands in future.
2024-03-06 22:57:11 +00:00
Bob Farrell
29f4771264 Upgrade Zmpl (latest version provides partials), fix mime map memory leak 2024-03-03 14:12:35 +00:00
Bob Farrell
ce93abcd65 Simplify headers assignment, fix tests 2024-03-02 12:11:22 +00:00
Bob Farrell
6ea210259d std.http overhaul
Andrew overhauled std.http to avoid allocations and put control of
connection + stream into hands of users. For now, do a barebones
implementation to restore compatibility, later we can do keepalive and
pipelining. For now, still getting decent enough performance the "slow"
way.

Relevant commit: 6395ba852a

Commit message copied here for posterity:

> Author: Andrew Kelley <andrew@ziglang.org>
> Date:   Tue Feb 20 03:30:51 2024 -0700
> std.http.Server: rework the API entirely
> Mainly, this removes the poorly named `wait`, `send`, `finish`
> functions, which all operated on the same "Response" object, which was
> actually being used as the request.
>
> Now, it looks like this:
> 1. std.net.Server.accept() gives you a std.net.Server.Connection
> 2. std.http.Server.init() with the connection
> 3. Server.receiveHead() gives you a Request
> 4. Request.reader() gives you a body reader
> 5. Request.respond() is a one-shot, or Request.respondStreaming() creates
>    a Response
> 6. Response.writer() gives you a body writer
> 7. Response.end() finishes the response; Response.endChunked() allows
>    passing response trailers.
>
> In other words, the type system now guides the API user down the correct
> path.
>
> receiveHead allows extra bytes to be read into the read buffer, and then
> will reuse those bytes for the body or the next request upon connection
> reuse.
>
> respond(), the one-shot function, will send the entire response in one
> syscall.
>
> Streaming response bodies no longer wastefully wraps every call to write
> with a chunk header and trailer; instead it only sends the HTTP chunk
> wrapper when flushing. This means the user can still control when it
> happens but it also does not add unnecessary chunks.
>
> Empirically, in my example project that uses this API, the usage code is
> significantly less noisy, it has less error handling while handling
> errors more correctly, it's more obvious what is happening, and it is
> syscall-optimal.
>
> Additionally:
> * Uncouple std.http.HeadParser from protocol.zig
> * Delete std.Server.Connection; use std.net.Server.Connection instead.
>   - The API user supplies the read buffer when initializing the
>     http.Server, and it is used for the HTTP head as well as a buffer
>     for reading the body into.
> * Replace and document the State enum. No longer is there both "start"
>   and "first".
2024-03-01 22:47:11 +00:00
Bob Farrell
d855b9f703 Implement MIME type inference
When serving content from `public/`, use MIME type lookup to provide an
appropriate content-type header.

MIME types borrowed from:

  https://mimetype.io/all-types
  https://github.com/patrickmccallum/mimetype-io/blob/master/src/mimeData.json
2024-02-26 22:38:43 +00:00
Bob Farrell
be85c13369 Provide interface for adding response headers 2024-02-25 14:20:22 +00:00
Bob Farrell
a07c71e725 Refactor response interface
Create `jetzig.http.Response` when `std.http.Server.Response` is created
and delegate all management of `std.http.Server.Response` to
`jetzig.http.Response`. Using this internally means we use the same
internal interface for the `Response` as the user, which gives us things
like response headers management in views for free (user simply calls
`request.response.headers.append()` etc.).
2024-02-25 10:32:34 +00:00
Bob Farrell
216b86c235 JSON and query param parsing/SSG params
Implement `jetzig.http.Request.params()` which parses either a JSON
request body or a query param string into a `jetzig.data.Value`.

Allow configuring params for static site generation - configure an array
of params for each endpoint which is then parsed at build time and a
separate JSON and HTML output is generated for each by invoking the
relevant view function and passing in resource ID/params. Params are
stored in generated `routes.zig` for route matching at run time.
2024-02-25 09:25:00 +00:00
Bob Farrell
280f1eaadd Update to latest Zmpl version
Fix a few bits of breakage with build - Jetzig does not have an
executable, use `demo/` which links to local Jetzig to test features.
2024-02-18 18:42:46 +00:00