Repository navigation
Security
- Fixed request parsing that took quadratic time on crafted input. A single request could tie up a worker for seconds, or much longer if you've raised your body size limits. Both are now linear:
- Nested parameter names like
a[b][c], when the name has many[without a closing]. This runs on every routed request before before filters. - The
Cookieheader, when it contains long runs without=.
- Nested parameter names like
- Nested parameters are expanded at most 64 levels deep. A name with more levels is kept as a single flat key instead of allocating a table for every level.
- The default route no longer redirects off domain. A request for
//evil.com/used to redirect to the protocol relative URL//evil.com. Leading slashes are now collapsed, so it redirects to/evil.com. - The HTML builder throws an error for attribute names that contain whitespace, control characters, or any of
"'>/=. Attribute names can't be escaped, and previously they were passed through directly. An untrusted value used as an attribute name could escape the sanitizer. util.underscoreandutil.singularizeare now linear time. Previously they were quadratic on long input due to unanchored pattern matches.
Requests
util.parse_query_stringnow decodes+as a space, the same asngx.decode_args. Values containing=are kept whole, and a segment without a key is skipped instead of failing the whole parse. Previouslya=b=c&d=eparsed as just{a = "b"}. This is also used to parse query strings and form bodies on the cqueues server.- Cookie parsing no longer requires a space after
;. Previouslya=1;b=2produced a cookie named;b. - The default route collapses multiple trailing slashes to one before redirecting, so
/hello///redirects to/hello/and can match a route that ends in/. A single trailing slash is still removed. json_paramsignores aContent-Typeheader that was sent more than once, instead of throwing an error.- The cqueues server parses form bodies whose content type has parameters, like
application/x-www-form-urlencoded; charset=UTF-8.
Models and Database
-
Added
no_transactiontolapis.db.migrationsfor migrations that can't run inside a transaction, likeCREATE INDEX CONCURRENTLY. Inglobaltransaction mode the open transaction is committed before the migration and a new one is started after it. A dry run stops before anyno_transactionmigration, since it can't be rolled back.import no_transaction from require "lapis.db.migrations" { [1790452452]: no_transaction => db.query "create index concurrently games_title_idx on games (title)" }
-
Postgres column types accept
references,on_delete, andon_updateoptions for foreign key constraints.types.foreign_key references: "users", on_delete: "cascade" -- integer NOT NULL REFERENCES "users" ON DELETE CASCADE
-
SQLite schema
typesthrows an error for an unknown column type instead of returningnil.
MySQL
Model:updatesupports thewhereoption.returningandon_conflictnow throw an error instead of being silently ignored. This applies todb.insert,db.update,db.delete,Model:create, andModel:update.Model:updatenow handles the model instance the same way as the Postgres and SQLite models: values are only stored on the instance after a row is updated, updating a column todb.NULLsets the field tonil, andupdated_atis stored on the instance. Previously values were assigned before the query ran, even if the update failed.
Validation
-
Added
types.params_jointo combine params types into a single checker, useful for sharing a common set of fields across endpoints. Every member is tested against the same input and their outputs are merged. It replaces the undocumentedtypes.multi_params, which has been removed.paginated = types.params_shape { {"page", types.empty / 1 + types.db_id} } search_params = types.params_join { paginated types.params_shape { {"q", types.limited_text 100} } }
-
Fixed the legacy
is_colorvalidator accepting letters outside ofa-f, like#ZZZ.
Configuration
-
Arrays in config are replaced instead of merged by index. An empty table clears an existing array. Merging a table that mixes array and hash keys, or merging a hash table into an existing array, throws an error.
config {"alpha", "beta"}, -> emails {"one@example.com", "two@example.com"} config "beta", -> emails {"three@example.com"} -- beta's emails is now {"three@example.com"} -- previously it was {"three@example.com", "two@example.com"}
Utilities
util.escapematchesngx.escape_uri:-._~!*'()are no longer escaped, hex digits are uppercase, andnilreturns an empty string instead of throwing an error.util.to_jsondrops references back to a parent table instead of overflowing the stack. A table used in more than one place is still encoded each time.lapis.logging.flatten_paramsincludes nested tables in its output, which were previously dropped, and stops at 5 levels deep. Values that aren't strings are written withtostring.
Testing and CLI
lapis generate specworks with--type helpers, and generated Lua model specs no longer have a typo inrequire.simulate_actionthrows an error if the URL isn't a string.
Documentation
Most of the guides got a full pass: typos, broken examples, and a number of functions and options that were never documented.
Full Changelog: v1.19.0...v1.20.0