From b8516a1286ee10a6d42a28b0cca5189217ee37dd Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Thu, 13 Aug 2026 00:53:40 -0400 Subject: [PATCH] import UBI nodejs-24.18.0-3.module+el8.10.0+24639+56878497 --- ...1-CVE-2026-13149-Brace-Expansion-DOS.patch | 159 - ...CVE-2026-69152-brace-expansion-5.0.9.patch | 674 ++++ ...192-CVE-2026-54272-ip-address-10.4.0.patch | 811 ++++ SOURCES/0001-update-sqlite-to-3.53.4.patch | 3489 +++++++++++++++++ SPECS/nodejs.spec | 23 +- 5 files changed, 4992 insertions(+), 164 deletions(-) delete mode 100644 SOURCES/0001-CVE-2026-13149-Brace-Expansion-DOS.patch create mode 100644 SOURCES/0001-CVE-2026-69152-brace-expansion-5.0.9.patch create mode 100644 SOURCES/0001-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch create mode 100644 SOURCES/0001-update-sqlite-to-3.53.4.patch diff --git a/SOURCES/0001-CVE-2026-13149-Brace-Expansion-DOS.patch b/SOURCES/0001-CVE-2026-13149-Brace-Expansion-DOS.patch deleted file mode 100644 index a711155..0000000 --- a/SOURCES/0001-CVE-2026-13149-Brace-Expansion-DOS.patch +++ /dev/null @@ -1,159 +0,0 @@ -From 193b73ce0138d3d2d62acd97fe2268bcdfcd8da6 Mon Sep 17 00:00:00 2001 -From: rpm-build -Date: Tue, 14 Jul 2026 09:01:08 +0000 -Subject: [PATCH] deps: update brace-expansion to 5.0.7 - -A run of non-expanding {} groups expanded post once per group before the -early returns that never use it, doubling the work on every group. expand_ -ran in O(2^n) and blocked for minutes on a ~90 byte input. - -Defer expanding post until a brace set is known to expand, and turn the -{a},b} restart into a loop so a long run of {} groups can't exhaust the -call stack. - -Backport of upstream fix: -https://github.com/juliangruber/brace-expansion/commit/c7e33ec13ac1a684c116720843ce24e208611754 - -CVE: CVE-2026-13149 ---- - .../brace-expansion/dist/commonjs/index.js | 38 ++++++++++++------- - .../brace-expansion/dist/esm/index.js | 38 ++++++++++++------- - .../node_modules/brace-expansion/package.json | 2 +- - 3 files changed, 49 insertions(+), 29 deletions(-) - -diff --git a/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js b/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js -index 33063dd..e4e5bd8 100644 ---- a/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js -+++ b/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js -@@ -95,19 +95,23 @@ function gte(i, y) { - function expand_(str, max, isTop) { - /** @type {string[]} */ - const expansions = []; -- const m = (0, balanced_match_1.balanced)('{', '}', str); -- if (!m) -- return [str]; -- // no need to expand pre, since it is guaranteed to be free of brace-sets -- const pre = m.pre; -- const post = m.post.length ? expand_(m.post, max, false) : ['']; -- if (/\$$/.test(m.pre)) { -- for (let k = 0; k < post.length && k < max; k++) { -- const expansion = pre + '{' + m.body + '}' + post[k]; -- expansions.push(expansion); -+ // The `{a},b}` rewrite below restarts expansion on a rewritten string with -+ // the same `max` and `isTop = true`. Loop instead of recursing so a long run -+ // of non-expanding `{}` groups can't exhaust the call stack. -+ for (;;) { -+ const m = (0, balanced_match_1.balanced)('{', '}', str); -+ if (!m) -+ return [str]; -+ // no need to expand pre, since it is guaranteed to be free of brace-sets -+ const pre = m.pre; -+ if (/\$$/.test(m.pre)) { -+ const post = m.post.length ? expand_(m.post, max, false) : ['']; -+ for (let k = 0; k < post.length && k < max; k++) { -+ const expansion = pre + '{' + m.body + '}' + post[k]; -+ expansions.push(expansion); -+ } -+ return expansions; - } -- } -- else { - const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); - const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); - const isSequence = isNumericSequence || isAlphaSequence; -@@ -116,10 +120,16 @@ function expand_(str, max, isTop) { - // {a},b} - if (m.post.match(/,(?!,).*\}/)) { - str = m.pre + '{' + m.body + escClose + m.post; -- return expand_(str, max, true); -+ isTop = true; -+ continue; - } - return [str]; - } -+ // Only expand post once we know this brace set actually expands. Computing -+ // it before the early returns above expanded post a second time on every -+ // non-expanding `{}`, which is what made inputs like `a{},{},{}...` blow up -+ // exponentially. -+ const post = m.post.length ? expand_(m.post, max, false) : ['']; - let n; - if (isSequence) { - n = m.body.split(/\.\./); -@@ -195,7 +205,7 @@ function expand_(str, max, isTop) { - } - } - } -+ return expansions; - } -- return expansions; - } - //# sourceMappingURL=index.js.map -\ No newline at end of file -diff --git a/deps/npm/node_modules/brace-expansion/dist/esm/index.js b/deps/npm/node_modules/brace-expansion/dist/esm/index.js -index 32399e7..b2d2aa9 100644 ---- a/deps/npm/node_modules/brace-expansion/dist/esm/index.js -+++ b/deps/npm/node_modules/brace-expansion/dist/esm/index.js -@@ -91,19 +91,23 @@ function gte(i, y) { - function expand_(str, max, isTop) { - /** @type {string[]} */ - const expansions = []; -- const m = balanced('{', '}', str); -- if (!m) -- return [str]; -- // no need to expand pre, since it is guaranteed to be free of brace-sets -- const pre = m.pre; -- const post = m.post.length ? expand_(m.post, max, false) : ['']; -- if (/\$$/.test(m.pre)) { -- for (let k = 0; k < post.length && k < max; k++) { -- const expansion = pre + '{' + m.body + '}' + post[k]; -- expansions.push(expansion); -+ // The `{a},b}` rewrite below restarts expansion on a rewritten string with -+ // the same `max` and `isTop = true`. Loop instead of recursing so a long run -+ // of non-expanding `{}` groups can't exhaust the call stack. -+ for (;;) { -+ const m = balanced('{', '}', str); -+ if (!m) -+ return [str]; -+ // no need to expand pre, since it is guaranteed to be free of brace-sets -+ const pre = m.pre; -+ if (/\$$/.test(m.pre)) { -+ const post = m.post.length ? expand_(m.post, max, false) : ['']; -+ for (let k = 0; k < post.length && k < max; k++) { -+ const expansion = pre + '{' + m.body + '}' + post[k]; -+ expansions.push(expansion); -+ } -+ return expansions; - } -- } -- else { - const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); - const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); - const isSequence = isNumericSequence || isAlphaSequence; -@@ -112,10 +116,16 @@ function expand_(str, max, isTop) { - // {a},b} - if (m.post.match(/,(?!,).*\}/)) { - str = m.pre + '{' + m.body + escClose + m.post; -- return expand_(str, max, true); -+ isTop = true; -+ continue; - } - return [str]; - } -+ // Only expand post once we know this brace set actually expands. Computing -+ // it before the early returns above expanded post a second time on every -+ // non-expanding `{}`, which is what made inputs like `a{},{},{}...` blow up -+ // exponentially. -+ const post = m.post.length ? expand_(m.post, max, false) : ['']; - let n; - if (isSequence) { - n = m.body.split(/\.\./); -@@ -191,7 +201,7 @@ function expand_(str, max, isTop) { - } - } - } -+ return expansions; - } -- return expansions; - } - //# sourceMappingURL=index.js.map -\ No newline at end of file diff --git a/SOURCES/0001-CVE-2026-69152-brace-expansion-5.0.9.patch b/SOURCES/0001-CVE-2026-69152-brace-expansion-5.0.9.patch new file mode 100644 index 0000000..ec7ca02 --- /dev/null +++ b/SOURCES/0001-CVE-2026-69152-brace-expansion-5.0.9.patch @@ -0,0 +1,674 @@ +From d8919f08bff03ff671a17f7b672edcba8a756c86 Mon Sep 17 00:00:00 2001 +From: tjuhasz +Date: Tue, 4 Aug 2026 16:36:52 +0200 +Subject: [PATCH] deps: update brace-expansion to 5.0.9 + +Update brace-expansion package in deps/npm/node_modules to version 5.0.9 +to address CVE-2026-13149. + +Key security fix: +- Adds EXPANSION_MAX_LENGTH to cap the total character count of the + accumulator during expansion, preventing memory exhaustion from + deeply chained brace groups (CVE-2026-14257) +- Refactors expand_ to iterate top-level brace groups instead of + recursing on m.post, preventing stack overflow on deeply chained + input +- Extracts combine() helper that enforces both max count and + maxLength bounds at the single point where output grows + +CVE: CVE-2026-13149 +--- + .../brace-expansion/dist/commonjs/index.js | 246 ++++++++++++------ + .../brace-expansion/dist/esm/index.js | 244 +++++++++++------ + .../node_modules/brace-expansion/package.json | 6 +- + 3 files changed, 336 insertions(+), 160 deletions(-) + +diff --git a/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js b/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js +index 33063dd..869a6be 100644 +--- a/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js ++++ b/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js +@@ -1,6 +1,6 @@ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); +-exports.EXPANSION_MAX = void 0; ++exports.EXPANSION_MAX_LENGTH = exports.EXPANSION_MAX = void 0; + exports.expand = expand; + const balanced_match_1 = require("balanced-match"); + const escSlash = '\0SLASH' + Math.random() + '\0'; +@@ -19,6 +19,17 @@ const closePattern = /\\}/g; + const commaPattern = /\\,/g; + const periodPattern = /\\\./g; + exports.EXPANSION_MAX = 100_000; ++// `EXPANSION_MAX` caps the *number* of expansions, but not their length. An ++// input like `'{a,b}'.repeat(1500)` stays under that count - its output is ++// truncated to 100k results - while making every result ~1500 characters ++// long. The result set, and the intermediate arrays built while combining ++// brace sets, then grow large enough to exhaust memory and crash the process ++// (CVE-2026-14257). `EXPANSION_MAX_LENGTH` bounds the total number of ++// characters the accumulator may hold at any point, so memory stays flat no ++// matter how many brace groups are chained. The limit sits well above any ++// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M ++// characters) so legitimate input is unaffected. ++exports.EXPANSION_MAX_LENGTH = 4_000_000; + function numeric(str) { + return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); + } +@@ -68,7 +79,7 @@ function expand(str, options = {}) { + if (!str) { + return []; + } +- const { max = exports.EXPANSION_MAX } = options; ++ const { max = exports.EXPANSION_MAX, maxLength = exports.EXPANSION_MAX_LENGTH } = options; + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, +@@ -78,7 +89,7 @@ function expand(str, options = {}) { + if (str.slice(0, 2) === '{}') { + str = '\\{\\}' + str.slice(2); + } +- return expand_(escapeBraces(str), max, true).map(unescapeBraces); ++ return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + } + function embrace(str) { + return '{' + str + '}'; +@@ -92,22 +103,117 @@ function lte(i, y) { + function gte(i, y) { + return i >= y; + } +-function expand_(str, max, isTop) { +- /** @type {string[]} */ +- const expansions = []; +- const m = (0, balanced_match_1.balanced)('{', '}', str); +- if (!m) +- return [str]; +- // no need to expand pre, since it is guaranteed to be free of brace-sets +- const pre = m.pre; +- const post = m.post.length ? expand_(m.post, max, false) : ['']; +- if (/\$$/.test(m.pre)) { +- for (let k = 0; k < post.length && k < max; k++) { +- const expansion = pre + '{' + m.body + '}' + post[k]; +- expansions.push(expansion); ++// Build `{ acc[a] + pre + values[v] }` for every combination, capping the ++// number of results at `max` and the total number of characters at `maxLength`. ++// This is the one place output grows, so bounding it here keeps the single ++// accumulator - and therefore memory - flat regardless of how many brace groups ++// are combined (CVE-2026-14257). ++function combine(acc, pre, values, max, maxLength, dropEmpties) { ++ const out = []; ++ let length = 0; ++ for (let a = 0; a < acc.length; a++) { ++ for (let v = 0; v < values.length; v++) { ++ if (out.length >= max) ++ return out; ++ const expansion = acc[a] + pre + values[v]; ++ // Bash drops empty results at the top level. Skip them before they count ++ // against `max`, so `max` bounds the number of *kept* results. ++ if (dropEmpties && !expansion) ++ continue; ++ if (length + expansion.length > maxLength) ++ return out; ++ out.push(expansion); ++ length += expansion.length; ++ } ++ } ++ return out; ++} ++// The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`) ++// sequence body. ++function expandSequence(body, isAlphaSequence, max, maxLength) { ++ const n = body.split(/\.\./); ++ const N = []; ++ // A sequence body always splits into two or three parts, but the compiler ++ // can't know that. ++ /* c8 ignore start */ ++ if (n[0] === undefined || n[1] === undefined) { ++ return N; ++ } ++ /* c8 ignore stop */ ++ const x = numeric(n[0]); ++ const y = numeric(n[1]); ++ const width = Math.max(n[0].length, n[1].length); ++ let incr = n.length === 3 && n[2] !== undefined ? ++ Math.max(Math.abs(numeric(n[2])), 1) ++ : 1; ++ let test = lte; ++ const reverse = y < x; ++ if (reverse) { ++ incr *= -1; ++ test = gte; ++ } ++ const pad = n.some(isPadded); ++ let length = 0; ++ for (let i = x; test(i, y) && N.length < max; i += incr) { ++ let c; ++ if (isAlphaSequence) { ++ c = String.fromCharCode(i); ++ if (c === '\\') { ++ c = ''; ++ } + } ++ else { ++ c = String(i); ++ if (pad) { ++ const need = width - c.length; ++ if (need > 0) { ++ const z = new Array(need + 1).join('0'); ++ if (i < 0) { ++ c = '-' + z + c.slice(1); ++ } ++ else { ++ c = z + c; ++ } ++ } ++ } ++ } ++ if (length + c.length > maxLength) ++ break; ++ N.push(c); ++ length += c.length; + } +- else { ++ return N; ++} ++function expand_(str, max, maxLength, isTop) { ++ // Consume the string's top-level brace groups left to right, threading a ++ // running set of combined prefixes (`acc`). Expanding the tail iteratively - ++ // rather than recursing on `m.post` once per group - keeps the native stack ++ // depth constant, so deeply chained input (`'{a,b}'.repeat(3000)`) can no ++ // longer overflow the stack, and leaves a single accumulator whose size ++ // `maxLength` bounds directly (CVE-2026-14257). ++ let acc = ['']; ++ // Bash drops empty results, but only when the *first* top-level group is a ++ // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop ++ // is on the final strings, so it is applied to whichever `combine` produces ++ // them (the one with no brace set left in the tail). ++ let dropEmpties = false; ++ let firstGroup = true; ++ for (;;) { ++ const m = (0, balanced_match_1.balanced)('{', '}', str); ++ // No brace set left: the rest of the string is literal. ++ if (!m) { ++ return combine(acc, str, [''], max, maxLength, dropEmpties); ++ } ++ // no need to expand pre, since it is guaranteed to be free of brace-sets ++ const pre = m.pre; ++ if (/\$$/.test(pre)) { ++ acc = combine(acc, pre + '{' + m.body + '}', [''], max, maxLength, dropEmpties && !m.post.length); ++ firstGroup = false; ++ if (!m.post.length) ++ break; ++ str = m.post; ++ continue; ++ } + const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + const isSequence = isNumericSequence || isAlphaSequence; +@@ -116,86 +222,68 @@ function expand_(str, max, isTop) { + // {a},b} + if (m.post.match(/,(?!,).*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; +- return expand_(str, max, true); ++ isTop = true; ++ continue; + } +- return [str]; ++ // Nothing here expands, so the whole remaining string is literal. ++ return combine(acc, pre + '{' + m.body + '}' + m.post, [''], max, maxLength, dropEmpties); + } +- let n; ++ if (firstGroup) { ++ dropEmpties = isTop && !isSequence; ++ firstGroup = false; ++ } ++ let values; + if (isSequence) { +- n = m.body.split(/\.\./); ++ values = expandSequence(m.body, isAlphaSequence, max, maxLength); + } + else { +- n = parseCommaParts(m.body); ++ let n = parseCommaParts(m.body); + if (n.length === 1 && n[0] !== undefined) { + // x{{a,b}}y ==> x{a}y x{b}y +- n = expand_(n[0], max, false).map(embrace); ++ n = expand_(n[0], max, maxLength, false).map(embrace); + //XXX is this necessary? Can't seem to hit it in tests. + /* c8 ignore start */ + if (n.length === 1) { +- return post.map(p => m.pre + n[0] + p); ++ acc = combine(acc, pre + n[0], [''], max, maxLength, dropEmpties && !m.post.length); ++ if (!m.post.length) ++ break; ++ str = m.post; ++ continue; + } + /* c8 ignore stop */ + } +- } +- // at this point, n is the parts, and we know it's not a comma set +- // with a single entry. +- let N; +- if (isSequence && n[0] !== undefined && n[1] !== undefined) { +- const x = numeric(n[0]); +- const y = numeric(n[1]); +- const width = Math.max(n[0].length, n[1].length); +- let incr = n.length === 3 && n[2] !== undefined ? +- Math.max(Math.abs(numeric(n[2])), 1) +- : 1; +- let test = lte; +- const reverse = y < x; +- if (reverse) { +- incr *= -1; +- test = gte; +- } +- const pad = n.some(isPadded); +- N = []; +- for (let i = x; test(i, y) && N.length < max; i += incr) { +- let c; +- if (isAlphaSequence) { +- c = String.fromCharCode(i); +- if (c === '\\') { +- c = ''; +- } ++ // Values that `combine` is going to drop as empty produce no result, so ++ // they must not count against `max` - otherwise `{a,,b}` with `max: 2` ++ // would stop at `['a', '']` and yield one result instead of two. Skipping ++ // them outright keeps `values` bounded while leaving `max` a bound on ++ // *kept* results. ++ let dropsEmpties = dropEmpties && !m.post.length && !pre; ++ for (let d = 0; dropsEmpties && d < acc.length; d++) { ++ if (acc[d]) { ++ dropsEmpties = false; + } +- else { +- c = String(i); +- if (pad) { +- const need = width - c.length; +- if (need > 0) { +- const z = new Array(need + 1).join('0'); +- if (i < 0) { +- c = '-' + z + c.slice(1); +- } +- else { +- c = z + c; +- } +- } +- } +- } +- N.push(c); +- } +- } +- else { +- N = []; +- for (let j = 0; j < n.length; j++) { +- N.push.apply(N, expand_(n[j], max, false)); + } +- } +- for (let j = 0; j < N.length; j++) { +- for (let k = 0; k < post.length && expansions.length < max; k++) { +- const expansion = pre + N[j] + post[k]; +- if (!isTop || isSequence || expansion) { +- expansions.push(expansion); ++ values = []; ++ let valuesLength = 0; ++ outer: for (let j = 0; j < n.length; j++) { ++ const expanded = expand_(n[j], max, maxLength, false); ++ for (let k = 0; k < expanded.length; k++) { ++ const v = expanded[k]; ++ if (dropsEmpties && !v) ++ continue; ++ if (values.length >= max || valuesLength + v.length > maxLength) { ++ break outer; ++ } ++ values.push(v); ++ valuesLength += v.length; + } + } + } ++ acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length); ++ if (!m.post.length) ++ break; ++ str = m.post; + } +- return expansions; ++ return acc; + } + //# sourceMappingURL=index.js.map +\ No newline at end of file +diff --git a/deps/npm/node_modules/brace-expansion/dist/esm/index.js b/deps/npm/node_modules/brace-expansion/dist/esm/index.js +index 32399e7..fd68f57 100644 +--- a/deps/npm/node_modules/brace-expansion/dist/esm/index.js ++++ b/deps/npm/node_modules/brace-expansion/dist/esm/index.js +@@ -15,6 +15,17 @@ const closePattern = /\\}/g; + const commaPattern = /\\,/g; + const periodPattern = /\\\./g; + export const EXPANSION_MAX = 100_000; ++// `EXPANSION_MAX` caps the *number* of expansions, but not their length. An ++// input like `'{a,b}'.repeat(1500)` stays under that count - its output is ++// truncated to 100k results - while making every result ~1500 characters ++// long. The result set, and the intermediate arrays built while combining ++// brace sets, then grow large enough to exhaust memory and crash the process ++// (CVE-2026-14257). `EXPANSION_MAX_LENGTH` bounds the total number of ++// characters the accumulator may hold at any point, so memory stays flat no ++// matter how many brace groups are chained. The limit sits well above any ++// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M ++// characters) so legitimate input is unaffected. ++export const EXPANSION_MAX_LENGTH = 4_000_000; + function numeric(str) { + return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); + } +@@ -64,7 +75,7 @@ export function expand(str, options = {}) { + if (!str) { + return []; + } +- const { max = EXPANSION_MAX } = options; ++ const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options; + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, +@@ -74,7 +85,7 @@ export function expand(str, options = {}) { + if (str.slice(0, 2) === '{}') { + str = '\\{\\}' + str.slice(2); + } +- return expand_(escapeBraces(str), max, true).map(unescapeBraces); ++ return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + } + function embrace(str) { + return '{' + str + '}'; +@@ -88,22 +99,117 @@ function lte(i, y) { + function gte(i, y) { + return i >= y; + } +-function expand_(str, max, isTop) { +- /** @type {string[]} */ +- const expansions = []; +- const m = balanced('{', '}', str); +- if (!m) +- return [str]; +- // no need to expand pre, since it is guaranteed to be free of brace-sets +- const pre = m.pre; +- const post = m.post.length ? expand_(m.post, max, false) : ['']; +- if (/\$$/.test(m.pre)) { +- for (let k = 0; k < post.length && k < max; k++) { +- const expansion = pre + '{' + m.body + '}' + post[k]; +- expansions.push(expansion); ++// Build `{ acc[a] + pre + values[v] }` for every combination, capping the ++// number of results at `max` and the total number of characters at `maxLength`. ++// This is the one place output grows, so bounding it here keeps the single ++// accumulator - and therefore memory - flat regardless of how many brace groups ++// are combined (CVE-2026-14257). ++function combine(acc, pre, values, max, maxLength, dropEmpties) { ++ const out = []; ++ let length = 0; ++ for (let a = 0; a < acc.length; a++) { ++ for (let v = 0; v < values.length; v++) { ++ if (out.length >= max) ++ return out; ++ const expansion = acc[a] + pre + values[v]; ++ // Bash drops empty results at the top level. Skip them before they count ++ // against `max`, so `max` bounds the number of *kept* results. ++ if (dropEmpties && !expansion) ++ continue; ++ if (length + expansion.length > maxLength) ++ return out; ++ out.push(expansion); ++ length += expansion.length; ++ } ++ } ++ return out; ++} ++// The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`) ++// sequence body. ++function expandSequence(body, isAlphaSequence, max, maxLength) { ++ const n = body.split(/\.\./); ++ const N = []; ++ // A sequence body always splits into two or three parts, but the compiler ++ // can't know that. ++ /* c8 ignore start */ ++ if (n[0] === undefined || n[1] === undefined) { ++ return N; ++ } ++ /* c8 ignore stop */ ++ const x = numeric(n[0]); ++ const y = numeric(n[1]); ++ const width = Math.max(n[0].length, n[1].length); ++ let incr = n.length === 3 && n[2] !== undefined ? ++ Math.max(Math.abs(numeric(n[2])), 1) ++ : 1; ++ let test = lte; ++ const reverse = y < x; ++ if (reverse) { ++ incr *= -1; ++ test = gte; ++ } ++ const pad = n.some(isPadded); ++ let length = 0; ++ for (let i = x; test(i, y) && N.length < max; i += incr) { ++ let c; ++ if (isAlphaSequence) { ++ c = String.fromCharCode(i); ++ if (c === '\\') { ++ c = ''; ++ } + } ++ else { ++ c = String(i); ++ if (pad) { ++ const need = width - c.length; ++ if (need > 0) { ++ const z = new Array(need + 1).join('0'); ++ if (i < 0) { ++ c = '-' + z + c.slice(1); ++ } ++ else { ++ c = z + c; ++ } ++ } ++ } ++ } ++ if (length + c.length > maxLength) ++ break; ++ N.push(c); ++ length += c.length; + } +- else { ++ return N; ++} ++function expand_(str, max, maxLength, isTop) { ++ // Consume the string's top-level brace groups left to right, threading a ++ // running set of combined prefixes (`acc`). Expanding the tail iteratively - ++ // rather than recursing on `m.post` once per group - keeps the native stack ++ // depth constant, so deeply chained input (`'{a,b}'.repeat(3000)`) can no ++ // longer overflow the stack, and leaves a single accumulator whose size ++ // `maxLength` bounds directly (CVE-2026-14257). ++ let acc = ['']; ++ // Bash drops empty results, but only when the *first* top-level group is a ++ // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop ++ // is on the final strings, so it is applied to whichever `combine` produces ++ // them (the one with no brace set left in the tail). ++ let dropEmpties = false; ++ let firstGroup = true; ++ for (;;) { ++ const m = balanced('{', '}', str); ++ // No brace set left: the rest of the string is literal. ++ if (!m) { ++ return combine(acc, str, [''], max, maxLength, dropEmpties); ++ } ++ // no need to expand pre, since it is guaranteed to be free of brace-sets ++ const pre = m.pre; ++ if (/\$$/.test(pre)) { ++ acc = combine(acc, pre + '{' + m.body + '}', [''], max, maxLength, dropEmpties && !m.post.length); ++ firstGroup = false; ++ if (!m.post.length) ++ break; ++ str = m.post; ++ continue; ++ } + const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + const isSequence = isNumericSequence || isAlphaSequence; +@@ -112,86 +218,68 @@ function expand_(str, max, isTop) { + // {a},b} + if (m.post.match(/,(?!,).*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; +- return expand_(str, max, true); ++ isTop = true; ++ continue; + } +- return [str]; ++ // Nothing here expands, so the whole remaining string is literal. ++ return combine(acc, pre + '{' + m.body + '}' + m.post, [''], max, maxLength, dropEmpties); + } +- let n; ++ if (firstGroup) { ++ dropEmpties = isTop && !isSequence; ++ firstGroup = false; ++ } ++ let values; + if (isSequence) { +- n = m.body.split(/\.\./); ++ values = expandSequence(m.body, isAlphaSequence, max, maxLength); + } + else { +- n = parseCommaParts(m.body); ++ let n = parseCommaParts(m.body); + if (n.length === 1 && n[0] !== undefined) { + // x{{a,b}}y ==> x{a}y x{b}y +- n = expand_(n[0], max, false).map(embrace); ++ n = expand_(n[0], max, maxLength, false).map(embrace); + //XXX is this necessary? Can't seem to hit it in tests. + /* c8 ignore start */ + if (n.length === 1) { +- return post.map(p => m.pre + n[0] + p); ++ acc = combine(acc, pre + n[0], [''], max, maxLength, dropEmpties && !m.post.length); ++ if (!m.post.length) ++ break; ++ str = m.post; ++ continue; + } + /* c8 ignore stop */ + } +- } +- // at this point, n is the parts, and we know it's not a comma set +- // with a single entry. +- let N; +- if (isSequence && n[0] !== undefined && n[1] !== undefined) { +- const x = numeric(n[0]); +- const y = numeric(n[1]); +- const width = Math.max(n[0].length, n[1].length); +- let incr = n.length === 3 && n[2] !== undefined ? +- Math.max(Math.abs(numeric(n[2])), 1) +- : 1; +- let test = lte; +- const reverse = y < x; +- if (reverse) { +- incr *= -1; +- test = gte; +- } +- const pad = n.some(isPadded); +- N = []; +- for (let i = x; test(i, y) && N.length < max; i += incr) { +- let c; +- if (isAlphaSequence) { +- c = String.fromCharCode(i); +- if (c === '\\') { +- c = ''; +- } ++ // Values that `combine` is going to drop as empty produce no result, so ++ // they must not count against `max` - otherwise `{a,,b}` with `max: 2` ++ // would stop at `['a', '']` and yield one result instead of two. Skipping ++ // them outright keeps `values` bounded while leaving `max` a bound on ++ // *kept* results. ++ let dropsEmpties = dropEmpties && !m.post.length && !pre; ++ for (let d = 0; dropsEmpties && d < acc.length; d++) { ++ if (acc[d]) { ++ dropsEmpties = false; + } +- else { +- c = String(i); +- if (pad) { +- const need = width - c.length; +- if (need > 0) { +- const z = new Array(need + 1).join('0'); +- if (i < 0) { +- c = '-' + z + c.slice(1); +- } +- else { +- c = z + c; +- } +- } +- } +- } +- N.push(c); +- } +- } +- else { +- N = []; +- for (let j = 0; j < n.length; j++) { +- N.push.apply(N, expand_(n[j], max, false)); + } +- } +- for (let j = 0; j < N.length; j++) { +- for (let k = 0; k < post.length && expansions.length < max; k++) { +- const expansion = pre + N[j] + post[k]; +- if (!isTop || isSequence || expansion) { +- expansions.push(expansion); ++ values = []; ++ let valuesLength = 0; ++ outer: for (let j = 0; j < n.length; j++) { ++ const expanded = expand_(n[j], max, maxLength, false); ++ for (let k = 0; k < expanded.length; k++) { ++ const v = expanded[k]; ++ if (dropsEmpties && !v) ++ continue; ++ if (values.length >= max || valuesLength + v.length > maxLength) { ++ break outer; ++ } ++ values.push(v); ++ valuesLength += v.length; + } + } + } ++ acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length); ++ if (!m.post.length) ++ break; ++ str = m.post; + } +- return expansions; ++ return acc; + } + //# sourceMappingURL=index.js.map +\ No newline at end of file +diff --git a/deps/npm/node_modules/brace-expansion/package.json b/deps/npm/node_modules/brace-expansion/package.json +index 8152480..4376400 100644 +--- a/deps/npm/node_modules/brace-expansion/package.json ++++ b/deps/npm/node_modules/brace-expansion/package.json +@@ -1,7 +1,7 @@ + { + "name": "brace-expansion", + "description": "Brace expansion as known from sh/bash", +- "version": "5.0.6", ++ "version": "5.0.9", + "files": [ + "dist" + ], +@@ -46,7 +46,7 @@ + }, + "license": "MIT", + "engines": { +- "node": "18 || 20 || >=22" ++ "node": "20 || >=22" + }, + "tshy": { + "exports": { +@@ -59,6 +59,6 @@ + "module": "./dist/esm/index.js", + "repository": { + "type": "git", +- "url": "git+ssh://git@github.com/juliangruber/brace-expansion.git" ++ "url": "git+https://github.com/juliangruber/brace-expansion.git" + } + } +-- +2.54.0 + diff --git a/SOURCES/0001-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch b/SOURCES/0001-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch new file mode 100644 index 0000000..06eb81d --- /dev/null +++ b/SOURCES/0001-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch @@ -0,0 +1,811 @@ +From caaad6f1bc0fec6d7bbabae0f6ebfd605eb885f4 Mon Sep 17 00:00:00 2001 +From: tjuhasz +Date: Tue, 4 Aug 2026 16:26:19 +0200 +Subject: [PATCH] deps: update ip-address to 10.4.0 + +Update ip-address package in deps/npm/node_modules to version 10.4.0 +to address CVE-2026-69192 and CVE-2026-54272. + +Key security fixes: +- IPv4 leading zero validation: rejects ambiguous octal notation that + could cause disagreement between parsers about which host a string + names (SSRF vector) +- isHostInSubnet: new method used by all classifiers (isLoopback, + isPrivate, isLinkLocal, etc.) so classification is no longer affected + by the CIDR suffix, preventing SSRF filter bypasses +- Improved URL parsing regexes that properly anchor and handle + protocol prefixes +- IPv6 embedded IPv4 awareness: isLoopback/isPrivate/isUnspecified + now check the embedded v4 address for mapped/NAT64 addresses + +CVE: CVE-2026-69192, CVE-2026-54272 +--- + .../node_modules/ip-address/dist/common.js | 49 +++- + deps/npm/node_modules/ip-address/dist/ipv4.js | 44 ++-- + deps/npm/node_modules/ip-address/dist/ipv6.js | 233 ++++++++++++++---- + .../ip-address/dist/v4/constants.js | 6 +- + .../ip-address/dist/v6/constants.js | 5 +- + deps/npm/node_modules/ip-address/package.json | 10 +- + 6 files changed, 262 insertions(+), 85 deletions(-) + +diff --git a/deps/npm/node_modules/ip-address/dist/common.js b/deps/npm/node_modules/ip-address/dist/common.js +index 6b76e05..0c15d21 100644 +--- a/deps/npm/node_modules/ip-address/dist/common.js ++++ b/deps/npm/node_modules/ip-address/dist/common.js +@@ -1,23 +1,47 @@ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.isInSubnet = isInSubnet; ++exports.isHostInSubnet = isHostInSubnet; + exports.isCorrect = isCorrect; + exports.prefixLengthFromMask = prefixLengthFromMask; ++exports.assertByteArray = assertByteArray; + exports.numberToPaddedHex = numberToPaddedHex; + exports.stringToPaddedHex = stringToPaddedHex; + exports.testBit = testBit; + const address_error_1 = require("./address-error"); ++/** ++ * Returns whether this address's *network* is contained within `address`, ++ * i.e. whether every address this one can represent also falls inside ++ * `address`. A network wider than `address` is not contained in it, so ++ * `10.0.0.0/8` is not in `10.0.0.0/16`. ++ * ++ * To ask whether the address itself falls inside a range, ignoring any CIDR ++ * suffix it was written with, use {@link isHostInSubnet} instead. That is the ++ * question the special-use classifiers ask. ++ */ + function isInSubnet(address) { + if (this.subnetMask < address.subnetMask) { + return false; + } +- if (this.mask(address.subnetMask) === address.mask()) { +- return true; +- } +- return false; ++ return isHostInSubnet.call(this, address); ++} ++/** ++ * Returns whether this address's host bits fall inside `address`, ignoring ++ * this address's own subnet mask. ++ * ++ * This is the primitive the special-use classifiers (`isLoopback`, ++ * `isPrivate`, `isLinkLocal`, `getType`, …) are built on: they answer a ++ * question about the address, so the answer must not change with the CIDR ++ * suffix the caller happened to write. Use this rather than ++ * {@link isInSubnet} when classifying a single address — notably when the ++ * address came from untrusted input and the result backs a trust-boundary ++ * decision such as an SSRF allow/deny filter. ++ */ ++function isHostInSubnet(address) { ++ return this.mask(address.subnetMask) === address.mask(); + } + function isCorrect(defaultBits) { +- return function () { ++ return function isCorrectForm() { + if (this.addressMinusSuffix !== this.correctForm()) { + return false; + } +@@ -46,6 +70,21 @@ function prefixLengthFromMask(value, totalBits) { + } + return firstZero; + } ++/** ++ * Throws `AddressError` unless `bytes` holds exactly `byteCount` integers, ++ * each from `minimum` to 255. Pass a `minimum` of `-128` where signed bytes ++ * are accepted and folded to unsigned, and `0` where they are not. ++ */ ++function assertByteArray(bytes, byteCount, family, minimum) { ++ if (bytes.length !== byteCount) { ++ throw new address_error_1.AddressError(`${family} addresses require exactly ${byteCount} bytes`); ++ } ++ for (let i = 0; i < bytes.length; i++) { ++ if (!Number.isInteger(bytes[i]) || bytes[i] < minimum || bytes[i] > 255) { ++ throw new address_error_1.AddressError(`All bytes must be integers between ${minimum} and 255`); ++ } ++ } ++} + function numberToPaddedHex(number) { + return number.toString(16).padStart(2, '0'); + } +diff --git a/deps/npm/node_modules/ip-address/dist/ipv4.js b/deps/npm/node_modules/ip-address/dist/ipv4.js +index 2c0fd18..1a14ddc 100644 +--- a/deps/npm/node_modules/ip-address/dist/ipv4.js ++++ b/deps/npm/node_modules/ip-address/dist/ipv4.js +@@ -35,6 +35,7 @@ const isCorrect4 = common.isCorrect(constants.BITS); + */ + class Address4 { + constructor(address) { ++ this.addressMinusSuffix = ''; + this.groups = constants.GROUPS; + this.parsedAddress = []; + this.parsedSubnet = ''; +@@ -51,6 +52,13 @@ class Address4 { + * @returns {boolean} + */ + this.isInSubnet = common.isInSubnet; ++ /** ++ * Returns true if this address's host bits fall inside the given subnet, ++ * ignoring this address's own subnet mask. See ++ * {@link common.isHostInSubnet}. ++ * @returns {boolean} ++ */ ++ this.isHostInSubnet = common.isHostInSubnet; + this.address = address; + const subnet = constants.RE_SUBNET_STRING.exec(address); + if (subnet) { +@@ -78,7 +86,7 @@ class Address4 { + new Address4(address); + return true; + } +- catch (e) { ++ catch { + return false; + } + } +@@ -90,6 +98,11 @@ class Address4 { + */ + parse(address) { + const groups = address.split('.'); ++ // Checked before the general match so the error names the actual problem. ++ // Address6 rejects the same notation on its v4-in-v6 path. ++ if (groups.some((group) => /^0\d/.test(group))) { ++ throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes."); ++ } + if (!address.match(constants.RE_ADDRESS)) { + throw new address_error_1.AddressError('Invalid IPv4 address.'); + } +@@ -128,7 +141,6 @@ class Address4 { + static fromAddressAndWildcardMask(address, wildcardMask) { + const wildcard = new Address4(wildcardMask).bigInt(); + const allOnes = (BigInt(1) << BigInt(constants.BITS)) - BigInt(1); +- // eslint-disable-next-line no-bitwise + const mask = wildcard ^ allOnes; + const bits = common.prefixLengthFromMask(mask, constants.BITS); + return new Address4(`${address}/${bits}`); +@@ -328,7 +340,7 @@ class Address4 { + * @returns {Address4} + */ + static fromBigInt(bigInt) { +- if (bigInt < 0n || bigInt > 0xffffffffn) { ++ if (bigInt < BigInt(0) || bigInt > BigInt(0xffffffff)) { + throw new address_error_1.AddressError('IPv4 BigInt must be in the range 0 to 2**32 - 1'); + } + return Address4.fromHex(bigInt.toString(16).padStart(8, '0')); +@@ -341,15 +353,7 @@ class Address4 { + * @returns {Address4} + */ + static fromByteArray(bytes) { +- if (bytes.length !== 4) { +- throw new address_error_1.AddressError('IPv4 addresses require exactly 4 bytes'); +- } +- // Validate that all bytes are within valid range (0-255) +- for (let i = 0; i < bytes.length; i++) { +- if (!Number.isInteger(bytes[i]) || bytes[i] < 0 || bytes[i] > 255) { +- throw new address_error_1.AddressError('All bytes must be integers between 0 and 255'); +- } +- } ++ common.assertByteArray(bytes, 4, 'IPv4', 0); + return this.fromUnsignedByteArray(bytes); + } + /** +@@ -403,49 +407,49 @@ class Address4 { + * @returns {boolean} + */ + isMulticast() { +- return this.isInSubnet(MULTICAST_V4); ++ return this.isHostInSubnet(MULTICAST_V4); + } + /** + * Returns true if the address is in one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private address ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`). + * @returns {boolean} + */ + isPrivate() { +- return PRIVATE_V4.some((subnet) => this.isInSubnet(subnet)); ++ return PRIVATE_V4.some((subnet) => this.isHostInSubnet(subnet)); + } + /** + * Returns true if the address is in the loopback range `127.0.0.0/8` ([RFC 1122](https://datatracker.ietf.org/doc/html/rfc1122)). + * @returns {boolean} + */ + isLoopback() { +- return this.isInSubnet(LOOPBACK_V4); ++ return this.isHostInSubnet(LOOPBACK_V4); + } + /** + * Returns true if the address is in the link-local range `169.254.0.0/16` ([RFC 3927](https://datatracker.ietf.org/doc/html/rfc3927)). + * @returns {boolean} + */ + isLinkLocal() { +- return this.isInSubnet(LINK_LOCAL_V4); ++ return this.isHostInSubnet(LINK_LOCAL_V4); + } + /** + * Returns true if the address is the unspecified address `0.0.0.0`. + * @returns {boolean} + */ + isUnspecified() { +- return this.isInSubnet(UNSPECIFIED_V4); ++ return this.isHostInSubnet(UNSPECIFIED_V4); + } + /** + * Returns true if the address is the limited broadcast address `255.255.255.255` ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)). + * @returns {boolean} + */ + isBroadcast() { +- return this.isInSubnet(BROADCAST_V4); ++ return this.isHostInSubnet(BROADCAST_V4); + } + /** + * Returns true if the address is in the carrier-grade NAT range `100.64.0.0/10` ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)). + * @returns {boolean} + */ + isCGNAT() { +- return this.isInSubnet(CGNAT_V4); ++ return this.isHostInSubnet(CGNAT_V4); + } + /** + * Returns a zero-padded base-2 string representation of the address +@@ -463,7 +467,7 @@ class Address4 { + */ + groupForV6() { + const segments = this.parsedAddress; +- return this.address.replace(constants.RE_ADDRESS, `${segments ++ return this.correctForm().replace(constants.RE_ADDRESS, `${segments + .slice(0, 2) + .join('.')}.${segments + .slice(2, 4) +diff --git a/deps/npm/node_modules/ip-address/dist/ipv6.js b/deps/npm/node_modules/ip-address/dist/ipv6.js +index a78020e..f64a8dd 100644 +--- a/deps/npm/node_modules/ip-address/dist/ipv6.js ++++ b/deps/npm/node_modules/ip-address/dist/ipv6.js +@@ -73,7 +73,6 @@ function paddedHex(octet) { + return parseInt(octet, 16).toString(16).padStart(4, '0'); + } + function unsignByte(b) { +- // eslint-disable-next-line no-bitwise + return b & 0xff; + } + /** +@@ -97,6 +96,13 @@ class Address6 { + * @returns {boolean} + */ + this.isInSubnet = common.isInSubnet; ++ /** ++ * Returns true if this address's host bits fall inside the given subnet, ++ * ignoring this address's own subnet mask. See ++ * {@link common.isHostInSubnet}. ++ * @returns {boolean} ++ */ ++ this.isHostInSubnet = common.isHostInSubnet; + /** + * Returns true if the address is correct, false otherwise + * @returns {boolean} +@@ -121,7 +127,10 @@ class Address6 { + } + address = address.replace(constants6.RE_SUBNET_STRING, ''); + } +- else if (/\//.test(address)) { ++ // RE_SUBNET_STRING anchors on the end of the address, so it strips only ++ // the trailing suffix. A second one left behind (`::/0/1`) is malformed ++ // and must be rejected rather than parsed as an address group. ++ if (/\//.test(address)) { + throw new address_error_1.AddressError('Invalid subnet mask.'); + } + const zone = constants6.RE_ZONE_STRING.exec(address); +@@ -145,7 +154,7 @@ class Address6 { + new Address6(address); + return true; + } +- catch (e) { ++ catch { + return false; + } + } +@@ -160,7 +169,7 @@ class Address6 { + * address.correctForm(); // '::e8:d4a5:1000' + */ + static fromBigInt(bigInt) { +- if (bigInt < 0n || bigInt > (1n << BigInt(constants6.BITS)) - 1n) { ++ if (bigInt < BigInt(0) || bigInt > (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1)) { + throw new address_error_1.AddressError('IPv6 BigInt must be in the range 0 to 2**128 - 1'); + } + const hex = bigInt.toString(16).padStart(32, '0'); +@@ -181,12 +190,15 @@ class Address6 { + * addressAndPort.port; // 8080 + */ + static fromURL(url) { ++ var _a; + let host; + let port = null; + let result; ++ // Remove the protocol prefix, if any ++ const stripped = url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, ''); + // If we have brackets parse them and find a port +- if (url.indexOf('[') !== -1 && url.indexOf(']:') !== -1) { +- result = constants6.RE_URL_WITH_PORT.exec(url); ++ if (stripped.indexOf('[') !== -1 && stripped.indexOf(']:') !== -1) { ++ result = constants6.RE_URL_WITH_PORT.exec(stripped); + if (result === null) { + return { + error: 'failed to parse address with port', +@@ -196,13 +208,9 @@ class Address6 { + } + host = result[1]; + port = result[2]; +- // If there's a URL extract the address + } +- else if (url.indexOf('/') !== -1) { +- // Remove the protocol prefix +- url = url.replace(/^[a-z0-9]+:\/\//, ''); +- // Parse the address +- result = constants6.RE_URL.exec(url); ++ else { ++ result = constants6.RE_URL.exec(stripped); + if (result === null) { + return { + error: 'failed to parse address from URL', +@@ -210,17 +218,13 @@ class Address6 { + port: null, + }; + } +- host = result[1]; +- // Otherwise just assign the URL to the host and let the library parse it +- } +- else { +- host = url; ++ host = (_a = result[1]) !== null && _a !== void 0 ? _a : result[2]; + } + // If there's a port convert it to an integer + if (port) { + port = parseInt(port, 10); +- // squelch out of range ports +- if (port < 0 || port > 65536) { ++ // squelch out of range ports (valid ports are 0-65535) ++ if (port < 0 || port > 65535) { + port = null; + } + } +@@ -258,7 +262,6 @@ class Address6 { + static fromAddressAndWildcardMask(address, wildcardMask) { + const wildcard = new Address6(wildcardMask).bigInt(); + const allOnes = (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1); +- // eslint-disable-next-line no-bitwise + const mask = wildcard ^ allOnes; + const bits = common.prefixLengthFromMask(mask, constants6.BITS); + return new Address6(`${address}/${bits}`); +@@ -493,7 +496,7 @@ class Address6 { + getType() { + for (let i = 0; i < TYPE_SUBNETS.length; i++) { + const entry = TYPE_SUBNETS[i]; +- if (this.isInSubnet(entry[0])) { ++ if (this.isHostInSubnet(entry[0])) { + return entry[1]; + } + } +@@ -635,20 +638,27 @@ class Address6 { + } + const groups = address.split(':'); + const lastGroup = groups.slice(-1)[0]; ++ // RE_ADDRESS rejects octets with a leading zero, so a dotted-quad tail is ++ // matched permissively first: that way this notation still gets its own ++ // message with the offending octet highlighted, rather than falling ++ // through as an unrecognized group. ++ const v4Octets = lastGroup.split('.'); ++ if (v4Octets.length === constants4.GROUPS && ++ v4Octets.every((octet) => /^\d{1,3}$/.test(octet))) { ++ if (v4Octets.some((octet) => /^0\d/.test(octet))) { ++ // The prefix groups haven't been through the bad-character check ++ // yet, so escape them before including in the error HTML. ++ const highlighted = v4Octets.map(spanLeadingZeroes4).join('.'); ++ const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':'); ++ const separator = groups.length > 1 ? ':' : ''; ++ throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`); ++ } ++ } + const address4 = lastGroup.match(constants4.RE_ADDRESS); + if (address4) { + this.parsedAddress4 = address4[0]; +- this.address4 = new ipv4_1.Address4(this.parsedAddress4); +- for (let i = 0; i < this.address4.groups; i++) { +- if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) { +- // The prefix groups haven't been through the bad-character check +- // yet, so escape them before including in the error HTML. +- const highlighted = this.address4.parsedAddress.map(spanLeadingZeroes4).join('.'); +- const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':'); +- const separator = groups.length > 1 ? ':' : ''; +- throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`); +- } +- } ++ const v4Suffix = this.subnetMask >= 96 ? `/${this.subnetMask - 96}` : ''; ++ this.address4 = new ipv4_1.Address4(`${this.parsedAddress4}${v4Suffix}`); + this.v4 = true; + groups[groups.length - 1] = this.address4.toGroup6(); + address = groups.join(':'); +@@ -734,7 +744,11 @@ class Address6 { + return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`); + } + /** +- * Return the last two groups of this address as an IPv4 address string ++ * Return the last two groups of this address as an IPv4 address string. ++ * If this address carries a CIDR prefix that covers the trailing 32 bits ++ * (i.e. `subnetMask >= 96`), the resulting `Address4` inherits the ++ * corresponding v4 prefix (`subnetMask - 96`); otherwise it defaults to ++ * `/32`. + * @returns {Address4} + * @example + * var address = new Address6('2001:4860:4001::1825:bf11'); +@@ -742,7 +756,18 @@ class Address6 { + */ + to4() { + const binary = this.binaryZeroPad().split(''); +- return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16).padStart(8, '0')); ++ const hex = BigInt(`0b${binary.slice(96, 128).join('')}`) ++ .toString(16) ++ .padStart(8, '0'); ++ if (this.subnetMask >= 96) { ++ const v4Mask = this.subnetMask - 96; ++ const groups = []; ++ for (let i = 0; i < 8; i += 2) { ++ groups.push(parseInt(hex.slice(i, i + 2), 16)); ++ } ++ return new ipv4_1.Address4(`${groups.join('.')}/${v4Mask}`); ++ } ++ return ipv4_1.Address4.fromHex(hex); + } + /** + * Return the v4-in-v6 form of the address +@@ -756,7 +781,7 @@ class Address6 { + if (!/:$/.test(correct)) { + infix = ':'; + } +- return correct + infix + address4.address; ++ return correct + infix + address4.correctForm(); + } + /** + * Decodes the Teredo tunneling fields embedded in this address. Returns the +@@ -788,11 +813,9 @@ class Address6 { + */ + const prefix = this.getBitsBase16(0, 32); + const bitsForUdpPort = this.getBits(80, 96); +- // eslint-disable-next-line no-bitwise + const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString(); + const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64)); + const bitsForClient4 = this.getBits(96, 128); +- // eslint-disable-next-line no-bitwise + const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16).padStart(8, '0')); + const flagsBase2 = this.getBitsBase2(64, 80); + const coneNat = (0, common_1.testBit)(flagsBase2, 15); +@@ -874,12 +897,14 @@ class Address6 { + } + else { + const beforeU = 64 - pl; +- bits = +- prefixBits.slice(0, pl) + +- v4Bits.slice(0, beforeU) + +- '00000000' + +- v4Bits.slice(beforeU) + +- '0'.repeat(128 - 72 - (32 - beforeU)); ++ bits = [ ++ prefixBits.slice(0, pl), ++ v4Bits.slice(0, beforeU), ++ // Bits 64 to 71 are the reserved u octet and are always zero. ++ '00000000', ++ v4Bits.slice(beforeU), ++ '0'.repeat(128 - 72 - (32 - beforeU)), ++ ].join(''); + } + const hex = BigInt(`0b${bits}`).toString(16).padStart(32, '0'); + const groups = []; +@@ -902,7 +927,7 @@ class Address6 { + if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) { + throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96'); + } +- if (!this.isInSubnet(prefix6)) { ++ if (!this.isHostInSubnet(prefix6)) { + return null; + } + const bits = this.binaryZeroPad(); +@@ -927,9 +952,9 @@ class Address6 { + * @returns {Array} + */ + toByteArray() { +- const valueWithoutPadding = this.bigInt().toString(16); +- const leadingPad = '0'.repeat(valueWithoutPadding.length % 2); +- const value = `${leadingPad}${valueWithoutPadding}`; ++ const value = this.bigInt() ++ .toString(16) ++ .padStart(constants6.BITS / 4, '0'); + const bytes = []; + for (let i = 0, length = value.length; i < length; i += 2) { + bytes.push(parseInt(value.substring(i, i + 2), 16)); +@@ -943,24 +968,39 @@ class Address6 { + * @returns {Array} + */ + toUnsignedByteArray() { ++ // toByteArray() emits 0 to 255, so unsigning it is an identity mapping and ++ // the two methods return equal arrays. 11.0.0 keeps one of them and makes ++ // this a deprecated alias; test/common-test.ts fails at that version. + return this.toByteArray().map(unsignByte); + } + /** + * Convert a byte array to an Address6 object. + * ++ * Accepts unsigned bytes (0 to 255) or signed bytes (-128 to 127, as an ++ * `Int8Array` or a Java `byte[]` holds them), folding signed values to their ++ * unsigned equivalent. Throws `AddressError` unless given exactly 16 ++ * integers from -128 to 255. ++ * + * To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`. + * @returns {Address6} + */ + static fromByteArray(bytes) { ++ // Address4.fromByteArray takes unsigned bytes only. 11.0.0 aligns this ++ // method with it, at which point the -128 floor here, unsignByte, and the ++ // mapping below all go; test/common-test.ts fails at that version. ++ common.assertByteArray(bytes, 16, 'IPv6', -128); + return this.fromUnsignedByteArray(bytes.map(unsignByte)); + } + /** + * Convert an unsigned byte array to an Address6 object. + * ++ * Throws `AddressError` unless given exactly 16 integers from 0 to 255. ++ * + * To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`. + * @returns {Address6} + */ + static fromUnsignedByteArray(bytes) { ++ common.assertByteArray(bytes, 16, 'IPv6', 0); + const BYTE_MAX = BigInt('256'); + let result = BigInt('0'); + let multiplier = BigInt('1'); +@@ -982,7 +1022,11 @@ class Address6 { + * @returns {boolean} + */ + isLinkLocal() { +- // Zeroes are required, i.e. we can't check isInSubnet with 'fe80::/10' ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isLinkLocal(); ++ } ++ // Zeroes are required, i.e. we can't check isHostInSubnet with 'fe80::/10' + if (this.getBitsBase2(0, 64) === + '1111111010000000000000000000000000000000000000000000000000000000') { + return true; +@@ -994,6 +1038,10 @@ class Address6 { + * @returns {boolean} + */ + isMulticast() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isMulticast(); ++ } + const type = this.getType(); + return type === 'Multicast' || type.startsWith('Multicast '); + } +@@ -1016,27 +1064,54 @@ class Address6 { + * @returns {boolean} + */ + isMapped4() { +- return this.isInSubnet(IPV4_MAPPED_SUBNET); ++ return this.isHostInSubnet(IPV4_MAPPED_SUBNET); ++ } ++ /** ++ * If this address embeds a routable IPv4 address — i.e. it is IPv4-mapped ++ * (`::ffff:0:0/96`) or sits in the NAT64 well-known prefix (`64:ff9b::/96`, ++ * [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052)) — return that ++ * embedded address as an {@link Address4}; otherwise return null. ++ * ++ * The special-property checks (`isLoopback`, `isLinkLocal`, `isMulticast`, ++ * `isUnspecified`, `isPrivate`, `isCGNAT`, `isBroadcast`) call this first and ++ * delegate to the embedded {@link Address4} when present, so a literal such as ++ * `::ffff:127.0.0.1` is classified by what it actually reaches (loopback) ++ * rather than by its IPv6 wrapper (which `getType()` reports as IPv4-mapped). ++ * This matters wherever the checks back a trust-boundary decision (e.g. an ++ * SSRF allow/deny filter): without normalization, `::ffff:10.0.0.1`, ++ * `::ffff:169.254.169.254`, `64:ff9b::7f00:1`, etc. would all read as ++ * non-internal. ++ * @returns {Address4 | null} ++ */ ++ embeddedIPv4() { ++ if (this.isMapped4() || this.isHostInSubnet(NAT64_WELL_KNOWN_SUBNET)) { ++ return this.to4(); ++ } ++ return null; + } + /** + * Returns true if the address is a Teredo address, false otherwise + * @returns {boolean} + */ + isTeredo() { +- return this.isInSubnet(TEREDO_SUBNET); ++ return this.isHostInSubnet(TEREDO_SUBNET); + } + /** + * Returns true if the address is a 6to4 address, false otherwise + * @returns {boolean} + */ + is6to4() { +- return this.isInSubnet(SIX_TO_FOUR_SUBNET); ++ return this.isHostInSubnet(SIX_TO_FOUR_SUBNET); + } + /** + * Returns true if the address is a loopback address, false otherwise + * @returns {boolean} + */ + isLoopback() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isLoopback(); ++ } + return this.getType() === 'Loopback'; + } + /** +@@ -1044,13 +1119,64 @@ class Address6 { + * @returns {boolean} + */ + isULA() { +- return this.isInSubnet(ULA_SUBNET); ++ return this.isHostInSubnet(ULA_SUBNET); ++ } ++ /** ++ * Returns true if the address is private, i.e. a Unique Local Address in ++ * `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)) or an ++ * IPv4-mapped / NAT64 address whose embedded IPv4 address is in one of the ++ * [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private ranges ++ * (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to ++ * {@link Address4.isPrivate}; use it instead of {@link isULA} when you need to ++ * catch mapped RFC 1918 addresses as well as native ULAs. ++ * @returns {boolean} ++ */ ++ isPrivate() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isPrivate(); ++ } ++ return this.isULA(); ++ } ++ /** ++ * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded ++ * IPv4 address is in the carrier-grade NAT range `100.64.0.0/10` ++ * ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)), false ++ * otherwise. There is no native IPv6 CGNAT range, so this only ever returns ++ * true for an embedded IPv4 address (e.g. `::ffff:100.64.0.1`). ++ * @returns {boolean} ++ */ ++ isCGNAT() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isCGNAT(); ++ } ++ return false; ++ } ++ /** ++ * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded ++ * IPv4 address is the limited broadcast address `255.255.255.255` ++ * ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)), false otherwise. ++ * There is no IPv6 broadcast, so this only ever returns true for an embedded ++ * IPv4 address (e.g. `::ffff:255.255.255.255`). ++ * @returns {boolean} ++ */ ++ isBroadcast() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isBroadcast(); ++ } ++ return false; + } + /** + * Returns true if the address is the unspecified address `::`. + * @returns {boolean} + */ + isUnspecified() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isUnspecified(); ++ } + return this.getType() === 'Unspecified'; + } + /** +@@ -1058,7 +1184,7 @@ class Address6 { + * @returns {boolean} + */ + isDocumentation() { +- return this.isInSubnet(DOCUMENTATION_SUBNET); ++ return this.isHostInSubnet(DOCUMENTATION_SUBNET); + } + // #endregion + // #region HTML +@@ -1214,4 +1340,5 @@ const SIX_TO_FOUR_SUBNET = new Address6('2002::/16'); + const ULA_SUBNET = new Address6('fc00::/7'); + const DOCUMENTATION_SUBNET = new Address6('2001:db8::/32'); + const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96'); ++const NAT64_WELL_KNOWN_SUBNET = new Address6('64:ff9b::/96'); + //# sourceMappingURL=ipv6.js.map +\ No newline at end of file +diff --git a/deps/npm/node_modules/ip-address/dist/v4/constants.js b/deps/npm/node_modules/ip-address/dist/v4/constants.js +index 6fa2518..158288b 100644 +--- a/deps/npm/node_modules/ip-address/dist/v4/constants.js ++++ b/deps/npm/node_modules/ip-address/dist/v4/constants.js +@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); + exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0; + exports.BITS = 32; + exports.GROUPS = 4; +-exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/g; ++// Each octet is 0-255 written without a leading zero. A leading zero is ++// octal to the WHATWG URL parser, inet_aton, and getaddrinfo, but decimal to ++// parseInt(part, 10), so accepting the notation would make this library ++// disagree with the network stack about which host a string names. ++exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/g; + exports.RE_SUBNET_STRING = /\/\d{1,2}$/; + //# sourceMappingURL=constants.js.map +\ No newline at end of file +diff --git a/deps/npm/node_modules/ip-address/dist/v6/constants.js b/deps/npm/node_modules/ip-address/dist/v6/constants.js +index 1a8cd1d..4616cad 100644 +--- a/deps/npm/node_modules/ip-address/dist/v6/constants.js ++++ b/deps/npm/node_modules/ip-address/dist/v6/constants.js +@@ -44,6 +44,7 @@ exports.TYPES = { + 'ff05::1:3/128': 'Multicast (All DHCP servers in this site)', + '::/128': 'Unspecified', + '::1/128': 'Loopback', ++ '::ffff:0:0/96': 'IPv4-mapped', + 'ff00::/8': 'Multicast', + 'fe80::/10': 'Link-local unicast', + 'fc00::/7': 'Unique local', +@@ -76,6 +77,6 @@ exports.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/; + * @static + */ + exports.RE_ZONE_STRING = /%.*$/; +-exports.RE_URL = /^\[{0,1}([0-9a-f:]+)\]{0,1}/; +-exports.RE_URL_WITH_PORT = /\[([0-9a-f:]+)\]:([0-9]{1,5})/; ++exports.RE_URL = /^(?:\[([0-9a-f:.]+)\]|([0-9a-f:.]+))(?:[/?#].*)?$/i; ++exports.RE_URL_WITH_PORT = /^\[([0-9a-f:.]+)\]:([0-9]{1,5})(?:[/?#].*)?$/i; + //# sourceMappingURL=constants.js.map +\ No newline at end of file +diff --git a/deps/npm/node_modules/ip-address/package.json b/deps/npm/node_modules/ip-address/package.json +index 47d109e..dc778c2 100644 +--- a/deps/npm/node_modules/ip-address/package.json ++++ b/deps/npm/node_modules/ip-address/package.json +@@ -16,7 +16,7 @@ + "bigint", + "browser" + ], +- "version": "10.2.0", ++ "version": "10.4.0", + "author": "Beau Gunderson (https://beaugunderson.com/)", + "license": "MIT", + "main": "dist/ip-address.js", +@@ -25,6 +25,9 @@ + "docs": "tsx scripts/build-readme.ts", + "build": "rm -rf dist; mkdir dist; tsc", + "prepack": "npm run docs && npm run build", ++ "prepare": "git config core.hooksPath hooks || true", ++ "lint": "prettier --check . && eslint . --ext .ts,.js --max-warnings 0", ++ "lint:fix": "prettier --write . && eslint . --ext .ts,.js --max-warnings 0 --fix", + "test-ci": "c8 --experimental-monocart mocha", + "test": "mocha", + "watch": "mocha --watch" +@@ -54,7 +57,7 @@ + ], + "repository": { + "type": "git", +- "url": "git://github.com/beaugunderson/ip-address.git" ++ "url": "https://github.com/beaugunderson/ip-address.git" + }, + "overrides": { + "diff": "^8.0.3", +@@ -70,11 +73,10 @@ + "chai": "^6.2.2", + "eslint": "^8.57.1", + "eslint_d": "^14.0.4", +- "eslint-config-airbnb": "^19.0.4", ++ "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-filenames": "^1.3.2", + "eslint-plugin-import": "^2.32.0", +- "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", + "mocha": "^11.7.5", +-- +2.54.0 + diff --git a/SOURCES/0001-update-sqlite-to-3.53.4.patch b/SOURCES/0001-update-sqlite-to-3.53.4.patch new file mode 100644 index 0000000..29b75b9 --- /dev/null +++ b/SOURCES/0001-update-sqlite-to-3.53.4.patch @@ -0,0 +1,3489 @@ +From 8edefbfbc6b55d1994a8b6eada6e06386172493a Mon Sep 17 00:00:00 2001 +From: Andrei Radchenko +Date: Fri, 31 Jul 2026 14:16:25 +0200 +Subject: [PATCH] update sqlite to 3.53.4 + +--- + deps/sqlite/sqlite3.c | 1723 ++++++++++++++++++++++++++++++----------- + deps/sqlite/sqlite3.h | 28 +- + 2 files changed, 1277 insertions(+), 474 deletions(-) + +diff --git a/deps/sqlite/sqlite3.c b/deps/sqlite/sqlite3.c +index 0c83f247..0644a39f 100644 +--- a/deps/sqlite/sqlite3.c ++++ b/deps/sqlite/sqlite3.c +@@ -1,6 +1,6 @@ + /****************************************************************************** + ** This file is an amalgamation of many separate C source files from SQLite +-** version 3.53.1. By combining all the individual C code files into this ++** version 3.53.4. By combining all the individual C code files into this + ** single large file, the entire code can be compiled as a single translation + ** unit. This allows many compilers to do optimizations that would not be + ** possible if the files were compiled separately. Performance improvements +@@ -18,7 +18,7 @@ + ** separate file. This file contains only code for the core SQLite library. + ** + ** The content in this amalgamation comes from Fossil check-in +-** c88b22011a54b4f6fbd149e9f8e4de77658c with changes in files: ++** bf7c7f30031888f4e796e429ab3978879485 with changes in files: + ** + ** + */ +@@ -467,12 +467,12 @@ extern "C" { + ** [sqlite3_libversion_number()], [sqlite3_sourceid()], + ** [sqlite_version()] and [sqlite_source_id()]. + */ +-#define SQLITE_VERSION "3.53.1" +-#define SQLITE_VERSION_NUMBER 3053001 +-#define SQLITE_SOURCE_ID "2026-05-05 10:34:17 c88b22011a54b4f6fbd149e9f8e4de77658ce58143a1af0e3785e4e6475127e9" ++#define SQLITE_VERSION "3.53.4" ++#define SQLITE_VERSION_NUMBER 3053004 ++#define SQLITE_SOURCE_ID "2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc" + #define SQLITE_SCM_BRANCH "branch-3.53" +-#define SQLITE_SCM_TAGS "release version-3.53.1" +-#define SQLITE_SCM_DATETIME "2026-05-05T10:34:17.344Z" ++#define SQLITE_SCM_TAGS "release version-3.53.4" ++#define SQLITE_SCM_DATETIME "2026-07-24T19:02:57.525Z" + + /* + ** CAPI3REF: Run-Time Library Version Numbers +@@ -4687,7 +4687,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + ** or in an ORDER BY or GROUP BY clause.)^ + ** + ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
SQLITE_LIMIT_EXPR_DEPTH
+-**
The maximum depth of the parse tree on any expression.
)^ ++**
The maximum depth of the parse tree on any expression and ++** the maximum nesting depth for subqueries and VIEWs
)^ + ** + ** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
SQLITE_LIMIT_PARSER_DEPTH
+ **
The maximum depth of the LALR(1) parser stack used to analyze +@@ -4718,7 +4719,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + **
The maximum index number of any [parameter] in an SQL statement.)^ + ** + ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(
SQLITE_LIMIT_TRIGGER_DEPTH
+-**
The maximum depth of recursion for triggers.
)^ ++**
The maximum depth of recursion for triggers, and the maximum ++** nesting depth for separate triggers.
)^ + ** + ** [[SQLITE_LIMIT_WORKER_THREADS]] ^(
SQLITE_LIMIT_WORKER_THREADS
+ **
The maximum number of auxiliary worker threads that a single +@@ -13174,11 +13176,23 @@ SQLITE_API int sqlite3changeset_apply_v3( + ** database behave as if they were declared with "ON UPDATE NO ACTION ON + ** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL + ** or SET DEFAULT. ++** ++**
SQLITE_CHANGESETAPPLY_NOUPDATELOOP
++** Sometimes, a changeset contains two or more update statements such that ++** although after applying all updates the database will contain no ++** constraint violations, no single update can be applied before the others. ++** The simplest example of this is a pair of UPDATEs that have "swapped" ++** two column values with a UNIQUE constraint. ++**

++** Usually, sqlite3changeset_apply() and similar functions work hard to try ++** to find a way to apply such a changeset. However, if this flag is set, ++** then all such updates are considered CONSTRAINT conflicts. + */ + #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 + #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 + #define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 + #define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008 ++#define SQLITE_CHANGESETAPPLY_NOUPDATELOOP 0x0010 + + /* + ** CAPI3REF: Constants Passed To The Conflict Handler +@@ -15788,6 +15802,13 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); + # define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) + #endif + ++/* ++** sizeof64() is like sizeof(), but always returns a 64-bit value, even ++** on 32-bit builds. This can help to avoid overflow by ensuring 64-bit ++** arithmetic is used consistently in both 32-bit and 64-bit builds. ++*/ ++#define sizeof64(X) ((sqlite3_int64)sizeof(X)) ++ + /* + ** Work around C99 "flex-array" syntax for pre-C99 compilers, so as + ** to avoid complaints from -fsanitize=strict-bounds. +@@ -17149,7 +17170,7 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); + + SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); + SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *); +-SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *); ++SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree*, Btree*); + + SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); + +@@ -20907,6 +20928,7 @@ struct Parse { + int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ + int iSelfTab; /* Table associated with an index on expr, or negative + ** of the base register during check-constraint eval */ ++ int nNestSel; /* Number of nested SELECT statements and/or VIEWs */ + int nLabel; /* The *negative* of the number of labels used */ + int nLabelAlloc; /* Number of slots in aLabel */ + int *aLabel; /* Space to hold the labels */ +@@ -22448,7 +22470,15 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void); + SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); + SQLITE_PRIVATE void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*); + SQLITE_PRIVATE void sqlite3AlterDropConstraint(Parse*,SrcList*,Token*,Token*); +-SQLITE_PRIVATE void sqlite3AlterAddConstraint(Parse*,SrcList*,Token*,Token*,const char*,int); ++SQLITE_PRIVATE void sqlite3AlterAddConstraint( ++ Parse *pParse, /* Parse context */ ++ SrcList *pSrc, /* Table to add constraint to */ ++ Token *pFirst, /* First token of new constraint */ ++ Token *pName, /* Name of new constraint. NULL if name omitted. */ ++ const char *zExpr, /* Text of CHECK expression */ ++ int nExpr, /* Size of pExpr in bytes */ ++ Expr *pExpr /* The parsed CHECK expression */ ++); + SQLITE_PRIVATE void sqlite3AlterSetNotNull(Parse*, SrcList*, Token*, Token*); + SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *, int *); + SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...); +@@ -27561,7 +27591,7 @@ SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p + }else{ + double r; + rc = pVfs->xCurrentTime(pVfs, &r); +- *pTimeOut = (sqlite3_int64)(r*86400000.0); ++ *pTimeOut = sqlite3RealToI64(r*86400000.0); + } + return rc; + } +@@ -33257,8 +33287,8 @@ SQLITE_API void sqlite3_str_vappendf( + ** all control characters, and for backslash itself. + ** For %#Q, do the same but only if there is at least + ** one control character. */ +- u32 nBack = 0; +- u32 nCtrl = 0; ++ i64 nBack = 0; ++ i64 nCtrl = 0; + for(k=0; k='a' && c<='z' ){ + n += (c - 'a')*mult; ++ if( n>nOut ) return -1 /* oversized/malformed input */; + mult *= 26; + c = aIn[++i]; + } +- if( j+n>nOut ) return -1; ++ if( j+n>nOut ) return -1 /* oversized/malformed input */; + memset(&aOut[j], 0, n); + j += n; + if( c==0 || mult==1 ) break; /* progress stalled if mult==1 */ +@@ -39600,7 +39631,7 @@ static void kvvfsDecodeJournal( + i = 0; + mult = 1; + while( (c = zTxt[i++])>='a' && c<='z' ){ +- n += (zTxt[i] - 'a')*mult; ++ n += (c - 'a')*mult; + mult *= 26; + } + sqlite3_free(pFile->aJrnl); +@@ -39646,9 +39677,7 @@ static int kvvfsClose(sqlite3_file *pProtoFile){ + pFile->isJournal ? "journal" : "db")); + sqlite3_free(pFile->aJrnl); + sqlite3_free(pFile->aData); +-#ifdef SQLITE_WASM + memset(pFile, 0, sizeof(*pFile)); +-#endif + return SQLITE_OK; + } + +@@ -39678,6 +39707,7 @@ static int kvvfsReadJrnl( + aTxt, szTxt+1); + if( rc>=0 ){ + kvvfsDecodeJournal(pFile, aTxt, szTxt); ++ rc = 0; + } + sqlite3_free(aTxt); + if( rc ) return rc; +@@ -45307,9 +45337,9 @@ static int unixShmMap( + nReqRegion = ((iRegion+nShmPerMap) / nShmPerMap) * nShmPerMap; + + if( pShmNode->nRegionszRegion = szRegion; + +@@ -45340,7 +45370,7 @@ static int unixShmMap( + */ + else{ + static const int pgsz = 4096; +- int iPg; ++ i64 iPg; + + /* Write to the last byte of each newly allocated or extended page */ + assert( (nByte % pgsz)==0 ); +@@ -45366,8 +45396,8 @@ static int unixShmMap( + } + pShmNode->apRegion = apNew; + while( pShmNode->nRegionhShm>=0 ){ + pMem = osMmap(0, nMap, +@@ -49741,10 +49771,8 @@ static struct win_syscall { + #define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ + BOOL))aSyscall[63].pCurrent) + +- { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, +- +-#define osGetNativeSystemInfo ((VOID(WINAPI*)( \ +- LPSYSTEM_INFO))aSyscall[64].pCurrent) ++ { "GetNativeSystemInfo", (SYSCALL)0, 0 }, ++ /* ^^^^^^^^^^^^^^^^^^^----------------^------- placeholder only */ + + #if defined(SQLITE_WIN32_HAS_ANSI) + { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, +@@ -52999,11 +53027,29 @@ SQLITE_API int sqlite3_win_test_unc_locking = 0; + + /* + ** Return true if the string passed as the only argument is likely +-** to be a UNC path. In other words, if it starts with "\\". ++** to be a UNC path. Return false if note. ++** ++** Return true if: ++** ++** (1) The name begins with "\\" ++** (2) But does not begin with "\\?\C:\" where C can be any alphabetic ++** character. ++** ++** For testing, also return true in all cases if the global variable ++** sqlite3_win_test_unc_locking is true. + */ + static int winIsUNCPath(const char *zFile){ + if( zFile[0]=='\\' && zFile[1]=='\\' ){ +- return 1; ++ if( zFile[2]=='?' ++ && zFile[3]=='\\' ++ && sqlite3Isalpha(zFile[4]) ++ && zFile[5]==':' ++ && winIsDirSep(zFile[6]) ++ ){ ++ return sqlite3_win_test_unc_locking; ++ }else{ ++ return 1; ++ } + } + return sqlite3_win_test_unc_locking; + } +@@ -53341,7 +53387,7 @@ static int winShmMap( + if( pShmNode->nRegion<=iRegion ){ + HANDLE hShared = pShmNode->hSharedShm; + struct ShmRegion *apNew; /* New aRegion[] array */ +- int nByte = (iRegion+1)*szRegion; /* Minimum required file size */ ++ i64 nByte = ((i64)iRegion+1)*(i64)szRegion; /* Minimum file size */ + sqlite3_int64 sz; /* Current size of wal-index file */ + + pShmNode->szRegion = szRegion; +@@ -53372,7 +53418,7 @@ static int winShmMap( + + /* Map the requested memory region into this processes address space. */ + apNew = (struct ShmRegion*)sqlite3_realloc64( +- pShmNode->aRegion, (iRegion+1)*sizeof(apNew[0]) ++ pShmNode->aRegion, ((i64)iRegion+1)*sizeof(apNew[0]) + ); + if( !apNew ){ + rc = SQLITE_IOERR_NOMEM_BKPT; +@@ -53394,15 +53440,14 @@ static int winShmMap( + #elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA + hMap = osCreateFileMappingA(hShared, NULL, protect, 0, nByte, NULL); + #endif +- +- OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%d, rc=%s\n", ++ OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%lld, rc=%s\n", + osGetCurrentProcessId(), pShmNode->nRegion, nByte, + hMap ? "ok" : "failed")); + if( hMap ){ +- int iOffset = pShmNode->nRegion*szRegion; ++ i64 iOffset = pShmNode->nRegion*szRegion; + int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; + pMap = osMapViewOfFile(hMap, flags, +- 0, iOffset - iOffsetShift, szRegion + iOffsetShift ++ 0, iOffset - iOffsetShift, (i64)szRegion + iOffsetShift + ); + OSTRACE(("SHM-MAP-MAP pid=%lu, region=%d, offset=%d, size=%d, rc=%s\n", + osGetCurrentProcessId(), pShmNode->nRegion, iOffset, +@@ -53424,7 +53469,7 @@ static int winShmMap( + + shmpage_out: + if( pShmNode->nRegion>iRegion ){ +- int iOffset = iRegion*szRegion; ++ i64 iOffset = (i64)iRegion*(i64)szRegion; + int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; + char *p = (char *)pShmNode->aRegion[iRegion].pMap; + *pp = (void *)&p[iOffsetShift]; +@@ -56020,7 +56065,7 @@ SQLITE_API unsigned char *sqlite3_serialize( + sqlite3_int64 sz; + int szPage = 0; + sqlite3_stmt *pStmt = 0; +- unsigned char *pOut; ++ unsigned char *pOut = 0; + char *zSql; + int rc; + +@@ -56030,12 +56075,13 @@ SQLITE_API unsigned char *sqlite3_serialize( + return 0; + } + #endif ++ sqlite3_mutex_enter(db->mutex); + + if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; + p = memdbFromDbSchema(db, zSchema); + iDb = sqlite3FindDbName(db, zSchema); + if( piSize ) *piSize = -1; +- if( iDb<0 ) return 0; ++ if( iDb<0 ) goto serialize_out; + if( p ){ + MemStore *pStore = p->pStore; + assert( pStore->pMutex==0 ); +@@ -56046,19 +56092,17 @@ SQLITE_API unsigned char *sqlite3_serialize( + pOut = sqlite3_malloc64( pStore->sz ); + if( pOut ) memcpy(pOut, pStore->aData, pStore->sz); + } +- return pOut; ++ goto serialize_out; + } + pBt = db->aDb[iDb].pBt; +- if( pBt==0 ) return 0; ++ if( pBt==0 ) goto serialize_out; + szPage = sqlite3BtreeGetPageSize(pBt); + zSql = sqlite3_mprintf("PRAGMA \"%w\".page_count", zSchema); + rc = zSql ? sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) : SQLITE_NOMEM; + sqlite3_free(zSql); +- if( rc ) return 0; ++ if( rc ) goto serialize_out; + rc = sqlite3_step(pStmt); +- if( rc!=SQLITE_ROW ){ +- pOut = 0; +- }else{ ++ if( rc==SQLITE_ROW ){ + sz = sqlite3_column_int64(pStmt, 0)*szPage; + if( sz==0 ){ + sqlite3_reset(pStmt); +@@ -56092,6 +56136,9 @@ SQLITE_API unsigned char *sqlite3_serialize( + } + } + sqlite3_finalize(pStmt); ++ ++ serialize_out: ++ sqlite3_mutex_leave(db->mutex); + return pOut; + } + +@@ -57945,22 +57992,24 @@ static int pcache1InitBulk(PCache1 *pCache){ + if( szBulk > pCache->szAlloc*(i64)pCache->nMax ){ + szBulk = pCache->szAlloc*(i64)pCache->nMax; + } +- zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); +- sqlite3EndBenignMalloc(); +- if( zBulk ){ +- int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; +- do{ +- PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage]; +- pX->page.pBuf = zBulk; +- pX->page.pExtra = (u8*)pX + ROUND8(sizeof(*pX)); +- assert( EIGHT_BYTE_ALIGNMENT( pX->page.pExtra ) ); +- pX->isBulkLocal = 1; +- pX->isAnchor = 0; +- pX->pNext = pCache->pFree; +- pX->pLruPrev = 0; /* Initializing this saves a valgrind error */ +- pCache->pFree = pX; +- zBulk += pCache->szAlloc; +- }while( --nBulk ); ++ if( szBulk>=pCache->szAlloc ){ ++ zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); ++ sqlite3EndBenignMalloc(); ++ if( zBulk ){ ++ int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; ++ do{ ++ PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage]; ++ pX->page.pBuf = zBulk; ++ pX->page.pExtra = (u8*)pX + ROUND8(sizeof(*pX)); ++ assert( EIGHT_BYTE_ALIGNMENT( pX->page.pExtra ) ); ++ pX->isBulkLocal = 1; ++ pX->isAnchor = 0; ++ pX->pNext = pCache->pFree; ++ pX->pLruPrev = 0; /* Initializing this saves a valgrind error */ ++ pCache->pFree = pX; ++ zBulk += pCache->szAlloc; ++ }while( --nBulk ); ++ } + } + return pCache->pFree!=0; + } +@@ -60858,39 +60907,43 @@ static void checkPage(PgHdr *pPg){ + #endif /* SQLITE_CHECK_PAGES */ + + /* +-** When this is called the journal file for pager pPager must be open. +-** This function attempts to read a super-journal file name from the +-** end of the file and, if successful, copies it into memory supplied +-** by the caller. See comments above writeSuperJournal() for the format +-** used to store a super-journal file name at the end of a journal file. +-** +-** zSuper must point to a buffer of at least nSuper bytes allocated by +-** the caller. This should be sqlite3_vfs.mxPathname+1 (to ensure there is +-** enough space to write the super-journal name). If the super-journal +-** name in the journal is longer than nSuper bytes (including a +-** nul-terminator), then this is handled as if no super-journal name +-** were present in the journal. ++** Free a buffer allocated by the readSuperJournal() function. ++*/ ++static void freeSuperJournal(char *zSuper){ ++ if( zSuper ){ ++ sqlite3_free(&zSuper[-4]); ++ } ++} ++ ++/* ++** Parameter pJrnl is a file-handle open on a journal file. This function ++** attempts to read a super-journal file name from the end of the journal ++** file. If successful, it sets output parameter (*pzSuper) to point to a ++** buffer containing the super-journal name as a nul-terminated string. ++** The caller is responsible for freeing the buffer using freeSuperJournal(). + ** +-** If a super-journal file name is present at the end of the journal +-** file, then it is copied into the buffer pointed to by zSuper. A +-** nul-terminator byte is appended to the buffer following the +-** super-journal file name. ++** Refer to comments above writeSuperJournal() for the format used to store ++** a super-journal file name at the end of a journal file. + ** +-** If it is determined that no super-journal file name is present +-** zSuper[0] is set to 0 and SQLITE_OK returned. ++** Parameter nSuper is passed the maximum allowable size of the super journal ++** name in bytes. If the super-journal name in the journal is longer than ++** nSuper bytes (including a nul-terminator), then this is handled as if no ++** super-journal name were present in the journal. + ** +-** If an error occurs while reading from the journal file, an SQLite +-** error code is returned. ++** If there is no super-journal name at the end of pJrnl, (*pzSuper) is ++** set to 0 and SQLITE_OK is returned. Or, if an error occurs while reading ++** the super-journal name, an SQLite error code is returned and (*pzSuper) ++** is set to 0. + */ +-static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u64 nSuper){ ++static int readSuperJournal(sqlite3_file *pJrnl, u64 nSuper, char **pzSuper){ + int rc; /* Return code */ + u32 len; /* Length in bytes of super-journal name */ + i64 szJ; /* Total size in bytes of journal file pJrnl */ + u32 cksum; /* MJ checksum value read from journal */ +- u32 u; /* Unsigned loop counter */ + unsigned char aMagic[8]; /* A buffer to hold the magic header */ +- zSuper[0] = '\0'; ++ char *zOut = 0; + ++ *pzSuper = 0; + if( SQLITE_OK!=(rc = sqlite3OsFileSize(pJrnl, &szJ)) + || szJ<16 + || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-16, &len)) +@@ -60900,27 +60953,34 @@ static int readSuperJournal(sqlite3_file *pJrnl, char *zSuper, u64 nSuper){ + || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-12, &cksum)) + || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8)) + || memcmp(aMagic, aJournalMagic, 8) +- || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, zSuper, len, szJ-16-len)) + ){ + return rc; + } + +- /* See if the checksum matches the super-journal name */ +- for(u=0; uzJournal */ ++ ++ /* Check if this looks like a real super-journal name. If it does not, ++ ** return SQLITE_OK without attempting to delete it. This is to limit ++ ** the degree to which a crafted journal file can be used to cause ++ ** SQLite to delete arbitrary files. */ ++ if( pagerIsSuperJrnlName(zSuper)==0 ){ ++ return SQLITE_OK; ++ } + + /* Allocate space for both the pJournal and pSuper file descriptors. + ** If successful, open the super-journal file for reading. +@@ -62150,9 +62251,8 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ + */ + rc = sqlite3OsFileSize(pSuper, &nSuperJournal); + if( rc!=SQLITE_OK ) goto delsuper_out; +- nSuperPtr = 1 + (i64)pVfs->mxPathname; +- assert( nSuperJournal>=0 && nSuperPtr>0 ); +- zFree = sqlite3Malloc(4 + nSuperJournal + nSuperPtr + 2); ++ assert( nSuperJournal>=0 ); ++ zFree = sqlite3Malloc(4 + nSuperJournal + 2); + if( !zFree ){ + rc = SQLITE_NOMEM_BKPT; + goto delsuper_out; +@@ -62161,7 +62261,6 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ + } + zFree[0] = zFree[1] = zFree[2] = zFree[3] = 0; + zSuperJournal = &zFree[4]; +- zSuperPtr = &zSuperJournal[nSuperJournal+2]; + rc = sqlite3OsRead(pSuper, zSuperJournal, (int)nSuperJournal, 0); + if( rc!=SQLITE_OK ) goto delsuper_out; + zSuperJournal[nSuperJournal] = 0; +@@ -62169,43 +62268,56 @@ static int pager_delsuper(Pager *pPager, const char *zSuper){ + + zJournal = zSuperJournal; + while( (zJournal-zSuperJournal)zJournal)==0 ){ ++ bSeen = 1; ++ }else{ ++ int exists; ++ rc = sqlite3OsAccess(pVfs, zJournal, SQLITE_ACCESS_EXISTS, &exists); + if( rc!=SQLITE_OK ){ + goto delsuper_out; + } ++ if( exists ){ ++ char *zSuperPtr = 0; + +- rc = readSuperJournal(pJournal, zSuperPtr, nSuperPtr); +- sqlite3OsClose(pJournal); +- if( rc!=SQLITE_OK ){ +- goto delsuper_out; +- } ++ /* One of the journals pointed to by the super-journal exists. ++ ** Open it and check if it points at the super-journal. If ++ ** so, return without deleting the super-journal file. ++ ** NB: zJournal is really a MAIN_JOURNAL. But call it a ++ ** SUPER_JOURNAL here so that the VFS will not send the zJournal ++ ** name into sqlite3_database_file_object(). ++ */ ++ int c; ++ int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_SUPER_JOURNAL); ++ rc = sqlite3OsOpen(pVfs, zJournal, pJournal, flags, 0); ++ if( rc!=SQLITE_OK ){ ++ goto delsuper_out; ++ } + +- c = zSuperPtr[0]!=0 && strcmp(zSuperPtr, zSuper)==0; +- if( c ){ +- /* We have a match. Do not delete the super-journal file. */ +- goto delsuper_out; ++ rc = readSuperJournal(pJournal, 1+(u64)pVfs->mxPathname, &zSuperPtr); ++ sqlite3OsClose(pJournal); ++ if( rc!=SQLITE_OK ){ ++ assert( zSuperPtr==0 ); ++ goto delsuper_out; ++ } ++ ++ c = zSuperPtr!=0 && strcmp(zSuperPtr, zSuper)==0; ++ freeSuperJournal(zSuperPtr); ++ if( c ){ ++ /* We have a match. Do not delete the super-journal file. */ ++ goto delsuper_out; ++ } + } + } + zJournal += (sqlite3Strlen30(zJournal)+1); + } + + sqlite3OsClose(pSuper); +- rc = sqlite3OsDelete(pVfs, zSuper, 0); ++ if( bSeen ){ ++ /* Only delete the super-journal if bSeen is true - indicating that ++ ** the super-journal contained a pointer to this database's journal ++ ** file. */ ++ rc = sqlite3OsDelete(pVfs, zSuper, 0); ++ } + + delsuper_out: + sqlite3_free(zFree); +@@ -62410,19 +62522,11 @@ static int pager_playback(Pager *pPager, int isHot){ + ** If a super-journal file name is specified, but the file is not + ** present on disk, then the journal is not hot and does not need to be + ** played back. +- ** +- ** TODO: Technically the following is an error because it assumes that +- ** buffer Pager.pTmpSpace is (mxPathname+1) bytes or larger. i.e. that +- ** (pPager->pageSize >= pPager->pVfs->mxPathname+1). Using os_unix.c, +- ** mxPathname is 512, which is the same as the minimum allowable value +- ** for pageSize. + */ +- zSuper = pPager->pTmpSpace; +- rc = readSuperJournal(pPager->jfd, zSuper, 1+(i64)pPager->pVfs->mxPathname); +- if( rc==SQLITE_OK && zSuper[0] ){ ++ rc = readSuperJournal(pPager->jfd, 1+(i64)pPager->pVfs->mxPathname, &zSuper); ++ if( rc==SQLITE_OK && zSuper ){ + rc = sqlite3OsAccess(pVfs, zSuper, SQLITE_ACCESS_EXISTS, &res); + } +- zSuper = 0; + if( rc!=SQLITE_OK || !res ){ + goto end_playback; + } +@@ -62551,30 +62655,20 @@ end_playback: + */ + pPager->changeCountDone = pPager->tempFile; + +- if( rc==SQLITE_OK ){ +- /* Leave 4 bytes of space before the super-journal filename in memory. +- ** This is because it may end up being passed to sqlite3OsOpen(), in +- ** which case it requires 4 0x00 bytes in memory immediately before +- ** the filename. */ +- zSuper = &pPager->pTmpSpace[4]; +- rc = readSuperJournal(pPager->jfd, zSuper, 1+(i64)pPager->pVfs->mxPathname); +- testcase( rc!=SQLITE_OK ); +- } + if( rc==SQLITE_OK + && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) + ){ + rc = sqlite3PagerSync(pPager, 0); + } + if( rc==SQLITE_OK ){ +- rc = pager_end_transaction(pPager, zSuper[0]!='\0', 0); ++ rc = pager_end_transaction(pPager, zSuper!=0, 0); + testcase( rc!=SQLITE_OK ); + } +- if( rc==SQLITE_OK && zSuper[0] && res ){ ++ if( rc==SQLITE_OK && zSuper && res ){ + /* If there was a super-journal and this routine will return success, + ** see if it is possible to delete the super-journal. + */ +- assert( zSuper==&pPager->pTmpSpace[4] ); +- memset(pPager->pTmpSpace, 0, 4); ++ assert( memcmp(&zSuper[-4], "\0\0\0\0", 4)==0 ); + rc = pager_delsuper(pPager, zSuper); + testcase( rc!=SQLITE_OK ); + } +@@ -62587,6 +62681,7 @@ end_playback: + ** back a journal created by a process with a different sector size + ** value. Reset it to the correct value for this process. + */ ++ freeSuperJournal(zSuper); + setSectorSize(pPager); + return rc; + } +@@ -68446,6 +68541,12 @@ static int walDecodeFrame( + return 0; + } + ++ /* Need a valid page size ++ */ ++ if( !pWal->szPage ){ ++ return 0; ++ } ++ + /* A frame is only valid if a checksum of the WAL header, + ** all prior frames, the first 16 bytes of this frame-header, + ** and the frame-data matches the checksum in the last 8 +@@ -70300,7 +70401,7 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ + + /* Allocate a buffer to read frames into */ + assert( (pWal->szPage & (pWal->szPage-1))==0 ); +- assert( pWal->szPage>=512 && pWal->szPage<=65536 ); ++ assert( (pWal->szPage>=512 && pWal->szPage<=65536) || pWal->szPage==0 ); + szFrame = pWal->szPage + WAL_FRAME_HDRSIZE; + aFrame = (u8 *)sqlite3_malloc64(szFrame); + if( aFrame==0 ){ +@@ -72798,6 +72899,9 @@ struct IntegrityCk { + u32 *heap; /* Min-heap used for analyzing cell coverage */ + sqlite3 *db; /* Database connection running the check */ + i64 nRow; /* Number of rows visited in current tree */ ++#ifdef SQLITE_DEBUG ++ u32 mxHeap; /* Maximum number of entries in the Min-heap */ ++#endif + }; + + /* +@@ -75259,8 +75363,12 @@ static int btreeComputeFreeSpace(MemPage *pPage){ + } + next = get2byte(&data[pc]); + size = get2byte(&data[pc+2]); ++ if( size<4 ){ ++ /* Minimum freeblock size is 4 */ ++ return SQLITE_CORRUPT_PAGE(pPage); ++ } + nFree = nFree + size; +- if( next<=pc+size+3 ) break; ++ if( next0 ){ +@@ -78306,7 +78414,9 @@ static int accessPayload( + ** means "not yet known" (the cache is lazily populated). + */ + if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){ +- int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; ++ i64 nOvfl = pCur->info.nPayload; ++ testcase( nOvfl - pCur->info.nLocal + ovflSize - 1 > 0xffffffffU ); ++ nOvfl = (nOvfl - pCur->info.nLocal + ovflSize-1)/ovflSize; + if( pCur->aOverflow==0 + || nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow) + ){ +@@ -78411,6 +78521,12 @@ static int accessPayload( + (eOp==0 ? PAGER_GET_READONLY : 0) + ); + if( rc==SQLITE_OK ){ ++ if( eOp!=0 ++ && (sqlite3PagerPageRefcount(pDbPage)!=1 ++ || NEVER(((MemPage*)sqlite3PagerGetExtra(pDbPage))->isInit)) ){ ++ sqlite3PagerUnref(pDbPage); ++ return SQLITE_CORRUPT_PAGE(pPage); ++ } + aPayload = sqlite3PagerGetData(pDbPage); + nextPage = get4byte(aPayload); + rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); +@@ -79085,14 +79201,14 @@ static int indexCellCompare( + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ +- testcase( pCell+nCell+1==pPage->aDataEnd ); ++ if( pCell + nCell >= pPage->aDataEnd ) return 99; + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ +- testcase( pCell+nCell+2==pPage->aDataEnd ); ++ if( pCell + nCell >= pPage->aDataEnd ) return 99; + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* If the record extends into overflow pages, do not attempt +@@ -79254,14 +79370,17 @@ bypass_moveto_root: + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ +- testcase( pCell+nCell+1==pPage->aDataEnd ); ++ if( pCell + nCell >= pPage->aDataEnd ){ ++ rc = SQLITE_CORRUPT_PAGE(pPage); ++ goto moveto_index_finish; ++ } + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal ++ && pCell + nCell < pPage->aDataEnd + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ +- testcase( pCell+nCell+2==pPage->aDataEnd ); + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* The record flows over onto one or more overflow pages. In +@@ -84126,6 +84245,7 @@ static int checkTreePage( + } + }else{ + /* Populate the coverage-checking heap for leaf pages */ ++ assert( heap[0] < pCheck->mxHeap ); + btreeHeapInsert(heap, (pc<<16)|(pc+info.nSize-1)); + } + } +@@ -84145,6 +84265,7 @@ static int checkTreePage( + u32 size; + pc = get2byteAligned(&data[cellStart+i*2]); + size = pPage->xCellSize(pPage, &data[pc]); ++ assert( heap[0] < pCheck->mxHeap ); + btreeHeapInsert(heap, (pc<<16)|(pc+size-1)); + } + } +@@ -84161,6 +84282,7 @@ static int checkTreePage( + assert( (u32)i<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */ + size = get2byte(&data[i+2]); + assert( (u32)(i+size)<=usableSize ); /* due to btreeComputeFreeSpace() */ ++ assert( heap[0] < pCheck->mxHeap ); + btreeHeapInsert(heap, (((u32)i)<<16)|(i+size-1)); + /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a + ** big-endian integer which is the offset in the b-tree page of the next +@@ -84295,6 +84417,9 @@ SQLITE_PRIVATE int sqlite3BtreeIntegrityCheck( + goto integrity_ck_cleanup; + } + sCheck.heap = (u32*)sqlite3PageMalloc( pBt->pageSize ); ++#ifdef SQLITE_DEBUG ++ sCheck.mxHeap = pBt->pageSize/4 - 1; ++#endif + if( sCheck.heap==0 ){ + checkOom(&sCheck); + goto integrity_ck_cleanup; +@@ -84712,6 +84837,7 @@ SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){ + */ + struct sqlite3_backup { + sqlite3* pDestDb; /* Destination database handle */ ++ char *zDestDb; + Btree *pDest; /* Destination b-tree file */ + u32 iDestSchema; /* Original schema cookie in destination */ + int bDestLocked; /* True once a write-transaction is open on pDest */ +@@ -84801,10 +84927,8 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ + ** Attempt to set the page size of the destination to match the page size + ** of the source. + */ +-static int setDestPgsz(sqlite3_backup *p){ +- int rc; +- rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),0,0); +- return rc; ++static int setDestPgsz(Btree *pDest, Btree *pSrc){ ++ return sqlite3BtreeSetPageSize(pDest, sqlite3BtreeGetPageSize(pSrc), 0, 0); + } + + /* +@@ -84861,27 +84985,37 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init( + ); + p = 0; + }else { ++ int nDest = sqlite3Strlen30(zDestDb); ++ + /* Allocate space for a new sqlite3_backup object... + ** EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a + ** call to sqlite3_backup_init() and is destroyed by a call to + ** sqlite3_backup_finish(). */ +- p = (sqlite3_backup *)sqlite3MallocZero(sizeof(sqlite3_backup)); ++ p = (sqlite3_backup*)sqlite3MallocZero(sizeof(sqlite3_backup)+nDest+1); + if( !p ){ + sqlite3Error(pDestDb, SQLITE_NOMEM_BKPT); ++ }else{ ++ p->zDestDb = (char*)&p[1]; ++ memcpy(p->zDestDb, zDestDb, nDest); + } + } + + /* If the allocation succeeded, populate the new object. */ + if( p ){ ++ /* Do not store the pointer to the destination b-tree at this point. ++ ** This is because there is nothing preventing it from being detached ++ ** or otherwise freed before the first call to sqlite3_backup_step() ++ ** on this object. The source b-tree does not have this problem, as ++ ** incrementing Btree.nBackup (see below) effectively locks the object. */ ++ Btree *pDest = findBtree(pDestDb, pDestDb, zDestDb); + p->pSrc = findBtree(pDestDb, pSrcDb, zSrcDb); +- p->pDest = findBtree(pDestDb, pDestDb, zDestDb); + p->pDestDb = pDestDb; + p->pSrcDb = pSrcDb; + p->iNext = 1; + p->isAttached = 0; + +- if( 0==p->pSrc || 0==p->pDest +- || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK ++ if( 0==p->pSrc || 0==pDest ++ || checkReadTransaction(pDestDb, pDest)!=SQLITE_OK + ){ + /* One (or both) of the named databases did not exist or an OOM + ** error was hit. Or there is a transaction open on the destination +@@ -85005,7 +85139,7 @@ static void attachBackupObject(sqlite3_backup *p){ + */ + SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ + int rc; +- int destMode; /* Destination journal mode */ ++ int destMode = 0; /* Destination journal mode */ + int pgszSrc = 0; /* Source page size */ + int pgszDest = 0; /* Destination page size */ + +@@ -85021,7 +85155,8 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ + rc = p->rc; + if( !isFatalError(rc) ){ + Pager * const pSrcPager = sqlite3BtreePager(p->pSrc); /* Source pager */ +- Pager * const pDestPager = sqlite3BtreePager(p->pDest); /* Dest pager */ ++ Btree * pDest = 0; /* Dest btree */ ++ Pager * pDestPager = 0; /* Dest pager */ + int ii; /* Iterator variable */ + int nSrcPage = -1; /* Size of source db in pages */ + int bCloseTrans = 0; /* True if src db requires unlocking */ +@@ -85035,6 +85170,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ + rc = SQLITE_OK; + } + ++ + /* If there is no open read-transaction on the source database, open + ** one now. If a transaction is opened here, then it will be closed + ** before this function exits. +@@ -85044,34 +85180,48 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ + bCloseTrans = 1; + } + ++ /* Locate the destination btree and pager. */ ++ if( (pDest = p->pDest)==0 ){ ++ pDest = findBtree(p->pDestDb, p->pDestDb, p->zDestDb); ++ } ++ if( pDest==0 ){ ++ rc = SQLITE_ERROR; ++ }else{ ++ pDestPager = sqlite3BtreePager(pDest); ++ } ++ + /* If the destination database has not yet been locked (i.e. if this + ** is the first call to backup_step() for the current backup operation), + ** try to set its page size to the same as the source database. This + ** is especially important on ZipVFS systems, as in that case it is + ** not possible to create a database file that uses one page size by + ** writing to it with another. */ +- if( p->bDestLocked==0 && rc==SQLITE_OK && setDestPgsz(p)==SQLITE_NOMEM ){ ++ if( p->bDestLocked==0 && rc==SQLITE_OK ++ && setDestPgsz(pDest, p->pSrc)==SQLITE_NOMEM ++ ){ + rc = SQLITE_NOMEM; + } + + /* Lock the destination database, if it is not locked already. */ + if( SQLITE_OK==rc && p->bDestLocked==0 +- && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2, ++ && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(pDest, 2, + (int*)&p->iDestSchema)) + ){ + p->bDestLocked = 1; ++ p->pDest = pDest; + } + + /* Do not allow backup if the destination database is in WAL mode + ** and the page sizes are different between source and destination */ +- pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); +- pgszDest = sqlite3BtreeGetPageSize(p->pDest); +- destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); +- if( SQLITE_OK==rc +- && (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager)) +- && pgszSrc!=pgszDest +- ){ +- rc = SQLITE_READONLY; ++ if( rc==SQLITE_OK ){ ++ pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); ++ pgszDest = sqlite3BtreeGetPageSize(p->pDest); ++ destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); ++ if( (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager)) ++ && pgszSrc!=pgszDest ++ ){ ++ rc = SQLITE_READONLY; ++ } + } + + /* Now that there is a read-lock on the source database, query the +@@ -85289,7 +85439,9 @@ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ + } + + /* If a transaction is still open on the Btree, roll it back. */ +- sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); ++ if( p->pDest ){ ++ sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); ++ } + + /* Set the error code of the destination database handle. */ + rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc; +@@ -93557,8 +93709,14 @@ SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context *pCtx){ + ** added or changed. + */ + SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){ +- Vdbe *p = (Vdbe*)pStmt; +- return p==0 || p->expired; ++ int iRet = 1; ++ if( pStmt ){ ++ Vdbe *p = (Vdbe*)pStmt; ++ sqlite3_mutex_enter(p->db->mutex); ++ iRet = p->expired; ++ sqlite3_mutex_leave(p->db->mutex); ++ } ++ return iRet; + } + #endif + +@@ -111099,6 +111257,7 @@ static int lookupName( + pExpr->op = TK_FUNCTION; + pExpr->u.zToken = "coalesce"; + pExpr->x.pList = pFJMatch; ++ pExpr->affExpr = SQLITE_AFF_DEFER; + cnt = 1; + goto lookupname_end; + }else{ +@@ -111267,6 +111426,35 @@ static int exprProbability(Expr *p){ + return (int)(r*134217728.0); + } + ++/* ++** Set the EP_SubtArg property on every expression inside of ++** pList. If any subexpression is actually a subquery, then ++** also set the EP_SubtArg property on the first result-set ++** column of that subquery. ++*/ ++static SQLITE_NOINLINE void resolveSetExprSubtypeArg(ExprList *pList){ ++ int nn, ii; ++ nn = pList ? pList->nExpr : 0; ++ for(ii=0; iia[ii].pExpr; ++ while( 1 /*exit-by-break*/ ){ ++ ExprSetProperty(pExpr, EP_SubtArg); ++ if( pExpr->op==TK_SELECT ){ ++ assert( ExprUseXSelect(pExpr) ); ++ assert( pExpr->x.pSelect!=0 ); ++ resolveSetExprSubtypeArg(pExpr->x.pSelect->pEList); ++ break; ++ } ++ if( pExpr->op==TK_UPLUS ){ ++ pExpr = pExpr->pLeft; ++ assert( pExpr!=0 ); ++ }else{ ++ break; ++ } ++ } ++ } ++} ++ + /* + ** This routine is callback for sqlite3WalkExpr(). + ** +@@ -111511,10 +111699,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ + if( (pDef->funcFlags & SQLITE_SUBTYPE) + || ExprHasProperty(pExpr, EP_SubtArg) + ){ +- int ii; +- for(ii=0; iia[ii].pExpr, EP_SubtArg); +- } ++ resolveSetExprSubtypeArg(pList); + } + + if( pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG) ){ +@@ -116835,7 +117020,7 @@ static void sqlite3ExprCodeIN( + Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i); + if( pParse->nErr ) goto sqlite3ExprCodeIN_oom_error; + if( sqlite3ExprCanBeNull(p) ){ +- sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); ++ sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+aiMap[i], destStep2); + VdbeCoverage(v); + } + } +@@ -116909,9 +117094,18 @@ static void sqlite3ExprCodeIN( + CollSeq *pColl; + int r3 = sqlite3GetTempReg(pParse); + p = sqlite3VectorFieldSubexpr(pLeft, i); +- pColl = sqlite3ExprCollSeq(pParse, p); +- sqlite3VdbeAddOp3(v, OP_Column, iTab, i, r3); +- sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3, ++ if( ExprUseXSelect(pExpr) ){ ++ Expr *pRhs = pExpr->x.pSelect->pEList->a[i].pExpr; ++ pColl = sqlite3BinaryCompareCollSeq(pParse, p, pRhs); ++ }else{ ++ /* If the RHS of the IN(...) expression are scalar expressions, do ++ ** not consider their collation sequences. The documentation says ++ ** "The collating sequence used for expressions of the form "x IN (y, z, ++ ** ...)" is the collating sequence of x.". */ ++ pColl = sqlite3ExprCollSeq(pParse, p); ++ } ++ sqlite3VdbeAddOp3(v, OP_Column, iTab, aiMap[i], r3); ++ sqlite3VdbeAddOp4(v, OP_Ne, rLhs+aiMap[i], destNotNull, r3, + (void*)pColl, P4_COLLSEQ); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, r3); +@@ -117332,26 +117526,37 @@ static int exprCodeInlineFunction( + } + + /* +-** Expression Node callback for sqlite3ExprCanReturnSubtype(). ++** Expression Node callback for sqlite3ExprCanReturnSubtype(). If ++** pExpr is able to return a subtype, set pWalker->eCode and abort ++** the search. If pExpr can never return a subtype, prune search. ++** ++** The only expressions that can return a subtype are: ++** ++** 1. A function ++** 2. The no-op "+" operator ++** 3. A CASE...END expression ++** 4. A CAST() expression ++** 5. A "expr COLLATE colseq" expression. + ** +-** Only a function call is able to return a subtype. So if the node +-** is not a function call, return WRC_Prune immediately. ++** For any other kind of expression, prune the search. + ** +-** A function call is able to return a subtype if it has the +-** SQLITE_RESULT_SUBTYPE property. ++** For case 1, the expression can yield a subtype if the function has ++** the SQLITE_RESULT_SUBTYPE property. Functions can also return ++** a subtype (via sqlite3_result_value()) if any of the arguments can ++** return a subtype. + ** +-** Assume that every function is able to pass-through a subtype from +-** one of its argument (using sqlite3_result_value()). Most functions +-** are not this way, but we don't have a mechanism to distinguish those +-** that are from those that are not, so assume they all work this way. +-** That means that if one of its arguments is another function and that +-** other function is able to return a subtype, then this function is +-** able to return a subtype. ++** In all cases 1 through 5, the expression might also return a subtype ++** if any operand can return a subtype. + */ + static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){ + int n; + FuncDef *pDef; + sqlite3 *db; ++ if( pExpr->op==TK_CASE || pExpr->op==TK_UPLUS ++ || pExpr->op==TK_COLLATE || pExpr->op==TK_CAST ++ ){ ++ return WRC_Continue; ++ } + if( pExpr->op!=TK_FUNCTION ){ + return WRC_Prune; + } +@@ -117361,7 +117566,7 @@ static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){ + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + pWalker->eCode = 1; +- return WRC_Prune; ++ return WRC_Abort; + } + return WRC_Continue; + } +@@ -123339,19 +123544,31 @@ SQLITE_PRIVATE void sqlite3AlterAddConstraint( + SrcList *pSrc, /* Table to add constraint to */ + Token *pFirst, /* First token of new constraint */ + Token *pName, /* Name of new constraint. NULL if name omitted. */ +- const char *pExpr, /* Text of CHECK expression */ +- int nExpr /* Size of pExpr in bytes */ ++ const char *zExpr, /* Text of CHECK expression */ ++ int nExpr, /* Size of pExpr in bytes */ ++ Expr *pExpr /* The parsed CHECK expression */ + ){ + Table *pTab = 0; /* Table identified by pSrc */ + int iDb = 0; /* Which schema does pTab live in */ + const char *zDb = 0; /* Name of the schema in which pTab lives */ + const char *pCons = 0; /* Text of the constraint */ + int nCons; /* Bytes of text to use from pCons[] */ ++ int rc; /* Result from error checking pExpr */ + + /* Look up the table being altered. */ + assert( pSrc->nSrc==1 ); + pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 1); +- if( !pTab ) return; ++ if( !pTab ){ ++ sqlite3ExprDelete(pParse->db, pExpr); ++ return; ++ } ++ ++ /* Verify that the new CHECK constraint does not contain any ++ ** internal-use-only function. Forum post 2026-05-10T01:11:28Z ++ */ ++ rc = sqlite3ResolveSelfReference(pParse, pTab, NC_IsCheck, pExpr, 0); ++ sqlite3ExprDelete(pParse->db, pExpr); ++ if( rc ) return; + + /* If this new constraint has a name, check that it is not a duplicate of + ** an existing constraint. It is an error if it is. */ +@@ -123372,7 +123589,7 @@ SQLITE_PRIVATE void sqlite3AlterAddConstraint( + sqlite3NestedParse(pParse, + "SELECT sqlite_fail('constraint failed', %d) " + "FROM %Q.%Q WHERE (%.*s) IS NOT TRUE", +- SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, pExpr ++ SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, zExpr + ); + + /* Edit the SQL for the named table. */ +@@ -125227,9 +125444,9 @@ static int loadStatTbl( + } + pIdx->nSampleCol = nIdxCol; + pIdx->mxSample = nSample; +- nByte = ROUND8(sizeof(IndexSample) * nSample); +- nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample; +- nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ ++ nByte = ROUND8(sizeof64(IndexSample) * nSample); ++ nByte += sizeof64(tRowcnt) * nIdxCol * 3 * nSample; ++ nByte += nIdxCol * sizeof64(tRowcnt); /* Space for Index.aAvgEq[] */ + + pIdx->aSample = sqlite3DbMallocZero(db, nByte); + if( pIdx->aSample==0 ){ +@@ -125237,7 +125454,7 @@ static int loadStatTbl( + return SQLITE_NOMEM_BKPT; + } + pPtr = (u8*)pIdx->aSample; +- pPtr += ROUND8(nSample*sizeof(pIdx->aSample[0])); ++ pPtr += ROUND8(nSample*sizeof64(pIdx->aSample[0])); + pSpace = (tRowcnt*)pPtr; + assert( EIGHT_BYTE_ALIGNMENT( pSpace ) ); + pIdx->aAvgEq = pSpace; pSpace += nIdxCol; +@@ -134049,9 +134266,18 @@ static void printfFunc( + sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + str.printfFlags = SQLITE_PRINTF_SQLFUNC; + sqlite3_str_appendf(&str, zFormat, &x); +- n = str.nChar; +- sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n, +- SQLITE_DYNAMIC); ++ if( str.accError==SQLITE_OK ){ ++ n = str.nChar; ++ sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n, ++ SQLITE_DYNAMIC); ++ }else{ ++ if( str.accError==SQLITE_NOMEM ){ ++ sqlite3_result_error_nomem(context); ++ }else{ ++ sqlite3_result_error_toobig(context); ++ } ++ sqlite3_str_reset(&str); ++ } + } + } + +@@ -135689,11 +135915,16 @@ static void sumInverse(sqlite3_context *context, int argc, sqlite3_value**argv){ + assert( p->cnt>0 ); + p->cnt--; + if( !p->approx ){ +- if( sqlite3SubInt64(&p->iSum, sqlite3_value_int64(argv[0])) ){ +- p->ovrfl = 1; +- p->approx = 1; ++ i64 x = p->iSum; ++ if( sqlite3SubInt64(&x, sqlite3_value_int64(argv[0]))==0 ){ ++ p->iSum = x; ++ return; + } +- }else if( type==SQLITE_INTEGER ){ ++ p->ovrfl = 1; ++ p->approx = 1; ++ kahanBabuskaNeumaierInit(p, p->iSum); ++ } ++ if( type==SQLITE_INTEGER ){ + i64 iVal = sqlite3_value_int64(argv[0]); + if( iVal!=SMALLEST_INT64 ){ + kahanBabuskaNeumaierStepInt64(p, -iVal); +@@ -136666,47 +136897,46 @@ static void percentSort(double *a, unsigned int n){ + int i; /* Loop counter */ + double rPivot; /* The pivot value */ + +- assert( n>=2 ); +- if( a[0]>a[n-1] ){ +- SWAP_DOUBLE(a[0],a[n-1]) +- } +- if( n==2 ) return; +- iGt = n-1; +- i = n/2; +- if( a[0]>a[i] ){ +- SWAP_DOUBLE(a[0],a[i]) +- }else if( a[i]>a[iGt] ){ +- SWAP_DOUBLE(a[i],a[iGt]) +- } +- if( n==3 ) return; +- rPivot = a[i]; +- iLt = i = 1; +- do{ +- if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) +- iLt++; +- i++; +- }else if( a[i]>rPivot ){ +- do{ +- iGt--; +- }while( iGt>i && a[iGt]>rPivot ); ++ while( n>=2 ){ ++ if( a[0]>a[n-1] ){ ++ SWAP_DOUBLE(a[0],a[n-1]) ++ } ++ if( n==2 ) return; ++ iGt = n-1; ++ i = n/2; ++ if( a[0]>a[i] ){ ++ SWAP_DOUBLE(a[0],a[i]) ++ }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) ++ } ++ if( n==3 ) return; ++ rPivot = a[i]; ++ iLt = i = 1; ++ do{ ++ if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) ++ iLt++; ++ i++; ++ }else if( a[i]>rPivot ){ ++ do{ ++ iGt--; ++ }while( iGt>i && a[iGt]>rPivot ); ++ SWAP_DOUBLE(a[i],a[iGt]) ++ }else{ ++ i++; ++ } ++ }while( i(int)(n/2) ){ ++ if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); ++ n = iLt; + }else{ +- i++; ++ if( iLt>=2 ) percentSort(a, iLt); ++ a += iGt; ++ n -= iGt; + } +- }while( i=2 ) percentSort(a, iLt); +- if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); +- +-/* Uncomment for testing */ +-#if 0 +- for(i=0; idb; + u64 savedFlags; + ++ pParse->nNestSel++; ++#if SQLITE_MAX_EXPR_DEPTH>0 ++ if( pParse->nNestSel >= db->aLimit[SQLITE_LIMIT_EXPR_DEPTH] ){ ++ sqlite3ErrorMsg(pParse, "VIEWs and/or subqueries nested too deep"); ++ return 0; ++ } ++#endif + savedFlags = db->flags; + db->flags &= ~(u64)SQLITE_FullColNames; + db->flags |= SQLITE_ShortColNames; +@@ -151234,6 +151471,8 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect, c + sqlite3DeleteTable(db, pTab); + return 0; + } ++ pParse->nNestSel--; ++ assert( pParse->nNestSel>=0 ); + return pTab; + } + +@@ -156204,8 +156443,11 @@ static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ + ** does not refer to a table to the right of CheckOnCtx.iJoin. */ + do { + SrcList *pSrc = pCtx->pSrc; ++ int nSrc = pSrc->nSrc; + int iTab = pExpr->iTable; +- if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){ ++ int ii; ++ for(ii=0; iia[ii].iCursor!=iTab; ii++){} ++ if( iiiJoin && iTab>pCtx->iJoin ){ + sqlite3ErrorMsg(pWalker->pParse, + "%s references tables to its right", +@@ -159174,7 +159416,7 @@ static TriggerPrg *codeRowTrigger( + Table *pTab, /* The table pTrigger is attached to */ + int orconf /* ON CONFLICT policy to code trigger program with */ + ){ +- Parse *pTop = sqlite3ParseToplevel(pParse); ++ Parse *pTop; /* Top level Parse object */ + sqlite3 *db = pParse->db; /* Database handle */ + TriggerPrg *pPrg; /* Value to return */ + Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */ +@@ -159183,10 +159425,24 @@ static TriggerPrg *codeRowTrigger( + SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ + int iEndTrigger = 0; /* Label to jump to if WHEN is false */ + Parse sSubParse; /* Parse context for sub-vdbe */ ++ int nDepth; /* Trigger depth */ ++ ++ /* Ensure that triggers are not chained too deep. This test is linear ++ ** in the chaining depth, but sensible code ought not be chaining ++ ** triggers excessively, so that shouldn't be a problem. ++ */ ++ pTop = pParse; ++ for(nDepth=0; pTop->pOuterParse; pTop = pTop->pOuterParse, nDepth++){} ++ if( nDepth>=db->aLimit[SQLITE_LIMIT_TRIGGER_DEPTH] ){ ++ sqlite3ErrorMsg(pParse, "triggers nested too deep"); ++ return 0; ++ } + ++ pTop = sqlite3ParseToplevel(pParse); + assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); + assert( pTop->pVdbe ); + ++ + /* Allocate the TriggerPrg and SubProgram objects. To ensure that they + ** are freed if an error occurs, link them into the Parse.pTriggerPrg + ** list of the top-level Parse object sooner rather than later. */ +@@ -161187,7 +161443,8 @@ SQLITE_PRIVATE void sqlite3UpsertDoUpdate( + /* excluded.* columns of type REAL need to be converted to a hard real */ + for(i=0; inCol; i++){ + if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ +- sqlite3VdbeAddOp1(v, OP_RealAffinity, pTop->regData+i); ++ int iStorage = pTop->regData + sqlite3TableColumnToStorage(pTab, i); ++ sqlite3VdbeAddOp1(v, OP_RealAffinity, iStorage); + } + } + sqlite3Update(pParse, pSrc, sqlite3ExprListDup(db,pUpsert->pUpsertSet,0), +@@ -161775,6 +162032,7 @@ SQLITE_API int sqlite3_drop_modules(sqlite3 *db, const char** azNames){ + #ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; + #endif ++ sqlite3_mutex_enter(db->mutex); + for(pThis=sqliteHashFirst(&db->aModule); pThis; pThis=pNext){ + Module *pMod = (Module*)sqliteHashData(pThis); + pNext = sqliteHashNext(pThis); +@@ -161785,6 +162043,7 @@ SQLITE_API int sqlite3_drop_modules(sqlite3 *db, const char** azNames){ + } + createModule(db, pMod->zName, 0, 0, 0); + } ++ sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + } + +@@ -166405,6 +166664,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( + WO_EQ|WO_IN|WO_IS, 0); + if( pAlt==0 ) continue; + if( pAlt->wtFlags & (TERM_CODED) ) continue; ++ if( ExprHasProperty(pAlt->pExpr, EP_Collate) ) continue; + if( (pAlt->eOperator & WO_IN) + && ExprUseXSelect(pAlt->pExpr) + && (pAlt->pExpr->x.pSelect->pEList->nExpr>1) +@@ -167158,7 +167418,10 @@ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ + static void markTermAsChild(WhereClause *pWC, int iChild, int iParent){ + pWC->a[iChild].iParent = iParent; + pWC->a[iChild].truthProb = pWC->a[iParent].truthProb; ++ assert( pWC->a[iParent].nChild < UMXV(pWC->a[0].nChild) ); + pWC->a[iParent].nChild++; ++ testcase( pWC->a[iParent].nChild == UMXV(pWC->a[0].nChild) ); ++ + } + + /* +@@ -167597,8 +167860,8 @@ static void exprAnalyzeOrTerm( + ** 3. Not originating in the ON clause of an OUTER JOIN + ** 4. The operator is not IS or else the query does not contain RIGHT JOIN + ** 5. The affinities of A and B must be compatible +-** 6a. Both operands use the same collating sequence OR +-** 6b. The overall collating sequence is BINARY ++** 6. Both operands use the same collating sequence, and they must not ++** use explicit COLLATE clauses. + ** If this routine returns TRUE, that means that the RHS can be substituted + ** for the LHS anyplace else in the WHERE clause where the LHS column occurs. + ** This is an optimization. No harm comes from returning 0. But if 1 is +@@ -167606,10 +167869,9 @@ static void exprAnalyzeOrTerm( + */ + static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){ + char aff1, aff2; +- CollSeq *pColl; + if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; /* (1) */ + if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; /* (2) */ +- if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* (3) */ ++ if( ExprHasProperty(pExpr, EP_OuterON|EP_Collate) ) return 0; /* (3) */ + assert( pSrc!=0 ); + if( pExpr->op==TK_IS + && pSrc->nSrc>=2 +@@ -167624,10 +167886,7 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){ + ){ + return 0; /* (5) */ + } +- pColl = sqlite3ExprCompareCollSeq(pParse, pExpr); +- if( !sqlite3IsBinary(pColl) +- && !sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight) +- ){ ++ if( !sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight) ){ + return 0; /* (6) */ + } + return 1; +@@ -167939,6 +168198,7 @@ static void exprAnalyze( + pList = pExpr->x.pList; + assert( pList!=0 ); + assert( pList->nExpr==2 ); ++ assert( pWC->a[idxTerm].nChild==0 ); + for(i=0; i<2; i++){ + Expr *pNewExpr; + int idxNew; +@@ -167959,7 +168219,7 @@ static void exprAnalyze( + /* Analyze a term that is composed of two or more subterms connected by + ** an OR operator. + */ +- else if( pExpr->op==TK_OR ){ ++ else if( pExpr->op==TK_OR && !ExprHasProperty(pExpr, EP_Collate) ){ + assert( pWC->op==TK_AND ); + exprAnalyzeOrTerm(pSrc, pWC, idxTerm); + pTerm = &pWC->a[idxTerm]; +@@ -168149,8 +168409,11 @@ static void exprAnalyze( + && pExpr->x.pSelect->pWin==0 + #endif + && pWC->op==TK_AND ++ && pExpr->x.pSelect->pEList->nExpr <= UMXV(pTerm->nChild) ++ /* ^-- See bug 2026-06-04T10:00:49Z */ + ){ + int i; ++ assert( pTerm->nChild==0 ); + for(i=0; ipLeft); i++){ + int idxNew; + idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL|TERM_SLICE); +@@ -171777,7 +172040,8 @@ static int whereRangeVectorLen( + idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); + if( aff!=idxaff ) break; + +- pColl = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr); ++ if( ExprHasProperty(pTerm->pExpr, EP_Commuted) ) SWAP(Expr*, pRhs, pLhs); ++ pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + if( pColl==0 ) break; + if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; + } +@@ -176699,7 +176963,7 @@ static void nth_valueStepFunc( + break; + case SQLITE_FLOAT: { + double fVal = sqlite3_value_double(apArg[1]); +- if( ((i64)fVal)!=fVal ) goto error_out; ++ if( sqlite3RealToI64(fVal)!=fVal ) goto error_out; + iVal = (i64)fVal; + break; + } +@@ -184251,9 +184515,11 @@ static YYACTIONTYPE yy_reduce( + ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy14, yymsp[-1].minor.yy454); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( yymsp[-4].minor.yy454 ){ ++ int i; + yymsp[-4].minor.yy454->x.pList = pList; +- if( ALWAYS(pList->nExpr) ){ +- yymsp[-4].minor.yy454->flags |= pList->a[0].pExpr->flags & EP_Propagate; ++ for(i=0; inExpr; i++){ ++ assert( pList->a[i].pExpr!=0 ); ++ yymsp[-4].minor.yy454->flags |= pList->a[i].pExpr->flags & EP_Propagate; + } + }else{ + sqlite3ExprListDelete(pParse->db, pList); +@@ -184721,15 +184987,13 @@ static YYACTIONTYPE yy_reduce( + break; + case 300: /* cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf */ + { +- sqlite3AlterAddConstraint(pParse, yymsp[-8].minor.yy203, &yymsp[-6].minor.yy0, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1)); ++ sqlite3AlterAddConstraint(pParse, yymsp[-8].minor.yy203, &yymsp[-6].minor.yy0, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1), yymsp[-2].minor.yy454); + } +- yy_destructor(yypParser,219,&yymsp[-2].minor); + break; + case 301: /* cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf */ + { +- sqlite3AlterAddConstraint(pParse, yymsp[-6].minor.yy203, &yymsp[-4].minor.yy0, 0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1)); ++ sqlite3AlterAddConstraint(pParse, yymsp[-6].minor.yy203, &yymsp[-4].minor.yy0, 0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1), yymsp[-2].minor.yy454); + } +- yy_destructor(yypParser,219,&yymsp[-2].minor); + break; + case 302: /* cmd ::= create_vtab */ + {sqlite3VtabFinishParse(pParse,0);} +@@ -188218,13 +188482,17 @@ static int nocaseCollatingFunc( + ** Return the ROWID of the most recent insert + */ + SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){ ++ i64 iRet; + #ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } + #endif +- return db->lastRowid; ++ sqlite3_mutex_enter(db->mutex); ++ iRet = db->lastRowid; ++ sqlite3_mutex_leave(db->mutex); ++ return iRet; + } + + /* +@@ -188246,13 +188514,17 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3 *db, sqlite3_int64 iRowid) + ** Return the number of changes in the most recent call to sqlite3_exec(). + */ + SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3 *db){ ++ i64 iRet; + #ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } + #endif +- return db->nChange; ++ sqlite3_mutex_enter(db->mutex); ++ iRet = db->nChange; ++ sqlite3_mutex_leave(db->mutex); ++ return iRet; + } + SQLITE_API int sqlite3_changes(sqlite3 *db){ + return (int)sqlite3_changes64(db); +@@ -188262,13 +188534,17 @@ SQLITE_API int sqlite3_changes(sqlite3 *db){ + ** Return the number of changes since the database handle was opened. + */ + SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3 *db){ ++ i64 iRet; + #ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } + #endif +- return db->nTotalChange; ++ sqlite3_mutex_enter(db->mutex); ++ iRet = db->nTotalChange; ++ sqlite3_mutex_leave(db->mutex); ++ return iRet; + } + SQLITE_API int sqlite3_total_changes(sqlite3 *db){ + return (int)sqlite3_total_changes64(db); +@@ -188951,6 +189227,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ + #ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; + #endif ++ sqlite3_mutex_enter(db->mutex); + if( ms>0 ){ + sqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback, + (void*)db); +@@ -188961,6 +189238,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ + }else{ + sqlite3_busy_handler(db, 0, 0); + } ++ sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + } + +@@ -189866,9 +190144,11 @@ SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){ + */ + SQLITE_API int sqlite3_error_offset(sqlite3 *db){ + int iOffset = -1; +- if( db && sqlite3SafetyCheckSickOrOk(db) && db->errCode ){ ++ if( db && sqlite3SafetyCheckSickOrOk(db) ){ + sqlite3_mutex_enter(db->mutex); +- iOffset = db->errByteOffset; ++ if( db->errCode ){ ++ iOffset = db->errByteOffset; ++ } + sqlite3_mutex_leave(db->mutex); + } + return iOffset; +@@ -189922,25 +190202,43 @@ SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){ + ** passed to this function, we assume a malloc() failed during sqlite3_open(). + */ + SQLITE_API int sqlite3_errcode(sqlite3 *db){ +- if( db && !sqlite3SafetyCheckSickOrOk(db) ){ ++ int iRet; ++ if( !db ) return SQLITE_NOMEM_BKPT; ++ if( !sqlite3SafetyCheckSickOrOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +- if( !db || db->mallocFailed ){ +- return SQLITE_NOMEM_BKPT; ++ sqlite3_mutex_enter(db->mutex); ++ if( db->mallocFailed ){ ++ iRet = SQLITE_NOMEM_BKPT; ++ }else{ ++ iRet = db->errCode & db->errMask; + } +- return db->errCode & db->errMask; ++ sqlite3_mutex_leave(db->mutex); ++ return iRet; + } + SQLITE_API int sqlite3_extended_errcode(sqlite3 *db){ +- if( db && !sqlite3SafetyCheckSickOrOk(db) ){ ++ int iRet; ++ if( !db ) return SQLITE_NOMEM_BKPT; ++ if( !sqlite3SafetyCheckSickOrOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +- if( !db || db->mallocFailed ){ +- return SQLITE_NOMEM_BKPT; ++ sqlite3_mutex_enter(db->mutex); ++ if( db->mallocFailed ){ ++ iRet = SQLITE_NOMEM_BKPT; ++ }else{ ++ iRet = db->errCode; + } +- return db->errCode; ++ sqlite3_mutex_leave(db->mutex); ++ return iRet; + } + SQLITE_API int sqlite3_system_errno(sqlite3 *db){ +- return db ? db->iSysErrno : 0; ++ int iRet = 0; ++ if( db ){ ++ sqlite3_mutex_enter(db->mutex); ++ iRet = db->iSysErrno; ++ sqlite3_mutex_leave(db->mutex); ++ } ++ return iRet; + } + + /* +@@ -190135,6 +190433,7 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ + if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ + return -1; + } ++ sqlite3_mutex_enter(db->mutex); + oldLimit = db->aLimit[limitId]; + if( newLimit>=0 ){ /* IMP: R-52476-28732 */ + if( newLimit>aHardLimit[limitId] ){ +@@ -190144,6 +190443,7 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ + } + db->aLimit[limitId] = newLimit; + } ++ sqlite3_mutex_leave(db->mutex); + return oldLimit; /* IMP: R-53341-35419 */ + } + +@@ -190186,7 +190486,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( + const char *zVfs = zDefaultVfs; + char *zFile; + char c; +- int nUri = sqlite3Strlen30(zUri); ++ i64 nUri = strlen(zUri); + + assert( *pzErrMsg==0 ); + +@@ -190196,8 +190496,8 @@ SQLITE_PRIVATE int sqlite3ParseUri( + ){ + char *zOpt; + int eState; /* Parser state when parsing URI */ +- int iIn; /* Input character index */ +- int iOut = 0; /* Output character index */ ++ i64 iIn; /* Input character index */ ++ i64 iOut = 0; /* Output character index */ + u64 nByte = nUri+8; /* Bytes of space to allocate */ + + /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen +@@ -190231,7 +190531,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( + while( zUri[iIn] && zUri[iIn]!='/' ) iIn++; + if( iIn!=7 && (iIn!=16 || memcmp("localhost", &zUri[7], 9)) ){ + *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s", +- iIn-7, &zUri[7]); ++ (int)(iIn-7), &zUri[7]); + rc = SQLITE_ERROR; + goto parse_uri_out; + } +@@ -190306,11 +190606,11 @@ SQLITE_PRIVATE int sqlite3ParseUri( + ** here. Options that are interpreted here include "vfs" and those that + ** correspond to flags that may be passed to the sqlite3_open_v2() + ** method. */ +- zOpt = &zFile[sqlite3Strlen30(zFile)+1]; ++ zOpt = &zFile[strlen(zFile)+1]; + while( zOpt[0] ){ +- int nOpt = sqlite3Strlen30(zOpt); ++ i64 nOpt = strlen(zOpt); + char *zVal = &zOpt[nOpt+1]; +- int nVal = sqlite3Strlen30(zVal); ++ i64 nVal = strlen(zVal); + + if( nOpt==3 && memcmp("vfs", zOpt, 3)==0 ){ + zVfs = zVal; +@@ -190356,7 +190656,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( + int mode = 0; + for(i=0; aMode[i].z; i++){ + const char *z = aMode[i].z; +- if( nVal==sqlite3Strlen30(z) && 0==memcmp(zVal, z, nVal) ){ ++ if( nVal==(i64)strlen(z) && 0==memcmp(zVal, z, nVal) ){ + mode = aMode[i].mode; + break; + } +@@ -191041,13 +191341,17 @@ SQLITE_API int sqlite3_global_recover(void){ + ** by the next COMMIT or ROLLBACK. + */ + SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){ ++ int iRet; + #ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } + #endif +- return db->autoCommit; ++ sqlite3_mutex_enter(db->mutex); ++ iRet = db->autoCommit; ++ sqlite3_mutex_leave(db->mutex); ++ return iRet; + } + + /* +@@ -192072,17 +192376,19 @@ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ + ** of range. + */ + SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N){ ++ const char *zRet = 0; + #ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } + #endif +- if( N<0 || N>=db->nDb ){ +- return 0; +- }else{ +- return db->aDb[N].zDbSName; ++ sqlite3_mutex_enter(db->mutex); ++ if( N>=0 && NnDb ){ ++ zRet = db->aDb[N].zDbSName; + } ++ sqlite3_mutex_leave(db->mutex); ++ return zRet; + } + + /* +@@ -193933,6 +194239,12 @@ SQLITE_PRIVATE int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk); + SQLITE_EXTENSION_INIT1 + #endif + ++ ++/* ++** Assume any b-tree layer with more levels than this is corrupt. ++*/ ++#define FTS3_MAX_BTREE_HEIGHT 48 ++ + typedef struct Fts3HashWrapper Fts3HashWrapper; + struct Fts3HashWrapper { + Fts3Hash hash; /* Hash table */ +@@ -195649,7 +195961,11 @@ static int fts3SelectLeaf( + assert( piLeaf || piLeaf2 ); + + fts3GetVarint32(zNode, &iHeight); +- rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); ++ if( iHeight>FTS3_MAX_BTREE_HEIGHT ){ ++ rc = FTS_CORRUPT_VTAB; ++ }else{ ++ rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); ++ } + assert_fts3_nc( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) ); + + if( rc==SQLITE_OK && iHeight>1 ){ +@@ -195694,8 +196010,13 @@ static void fts3PutDeltaVarint( + sqlite3_int64 iVal /* Write this value to the list */ + ){ + assert_fts3_nc( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) ); +- *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); +- *piPrev = iVal; ++ if( iVal-(*piPrev)>=0 ){ ++ /* Refuse to write a negative delta integer. This only happens with a ++ ** corrupt db (see the assert above) and can cause buffer overwrites ++ ** in some cases. */ ++ *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); ++ *piPrev = iVal; ++ } + } + + /* +@@ -198049,6 +198370,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ + char *p1; + char *p2; + char *aOut; ++ i64 nAlloc = (i64)nPoslist*2 + FTS3_BUFFER_PADDING; + + if( nMaxUndeferred>iPrev ){ + p1 = aPoslist; +@@ -198060,7 +198382,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ + nDistance = iPrev - nMaxUndeferred; + } + +- aOut = (char *)sqlite3Fts3MallocZero(((i64)nPoslist)+FTS3_BUFFER_PADDING); ++ aOut = (char *)sqlite3Fts3MallocZero(nAlloc); + if( !aOut ){ + sqlite3_free(aPoslist); + return SQLITE_NOMEM; +@@ -200178,7 +200500,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ + /* State 3. The integer just read is a column number. */ + default: assert( eState==3 ); + iCol = (int)v; +- if( iCol<1 ){ ++ if( iCol<1 || iCol>(pFts3->nColumn+1) ){ + rc = SQLITE_CORRUPT_VTAB; + break; + } +@@ -200868,6 +201190,7 @@ static int getNextNode( + assert( nKey==4 ); + if( zInput[4]=='/' && zInput[5]>='0' && zInput[5]<='9' ){ + nKey += 1+sqlite3Fts3ReadInt(&zInput[nKey+1], &nNear); ++ if( nNear>=1000000000 ) nNear = 1000000000; + } + } + +@@ -207114,6 +207437,10 @@ static void fts3ReadEndBlockField( + for(/* no-op */; zText[i]>='0' && zText[i]<='9'; i++){ + iVal = iVal*10 + (zText[i] - '0'); + } ++ ++ /* This if() clause is just to avoid an integer overflow. The record is ++ ** corrupt in this case. */ ++ if( (i64)iVal==SMALLEST_INT64 ) iMul = 1; + *pnByte = ((i64)iVal * (i64)iMul); + } + } +@@ -208340,7 +208667,7 @@ static int fts3IncrmergeLoad( + return FTS_CORRUPT_VTAB; + } + +- pWriter->nLeafEst = (int)((iEnd - iStart) + 1)/FTS_MAX_APPENDABLE_HEIGHT; ++ pWriter->nLeafEst = (int)(((iEnd - iStart)+1)/FTS_MAX_APPENDABLE_HEIGHT); + pWriter->iStart = iStart; + pWriter->iEnd = iEnd; + pWriter->iAbsLevel = iAbsLevel; +@@ -210458,8 +210785,8 @@ static int fts3StringAppend( + ** to grow the buffer until so that it is big enough to accommodate the + ** appended data. + */ +- if( pStr->n+nAppend+1>=pStr->nAlloc ){ +- sqlite3_int64 nAlloc = pStr->nAlloc+(sqlite3_int64)nAppend+100; ++ if( (i64)pStr->n+(i64)nAppend+1>=(i64)pStr->nAlloc ){ ++ i64 nAlloc = pStr->nAlloc+(i64)nAppend+100; + char *zNew = sqlite3_realloc64(pStr->z, nAlloc); + if( !zNew ){ + return SQLITE_NOMEM; +@@ -210731,7 +211058,7 @@ static int fts3ExprLHits( + if( p->flag==FTS3_MATCHINFO_LHITS ){ + p->aMatchinfo[iStart + iCol] = (u32)nHit; + }else if( nHit ){ +- p->aMatchinfo[iStart + (iCol+1)/32] |= (1 << (iCol&0x1F)); ++ p->aMatchinfo[iStart + iCol/32] |= (1U << (iCol&0x1F)); + } + } + assert( *pIter==0x00 || *pIter==0x01 ); +@@ -213221,7 +213548,7 @@ static void jsonAppendSqlValue( + break; + } + case SQLITE_FLOAT: { +- jsonPrintf(100, p, "%!0.15g", sqlite3_value_double(pValue)); ++ jsonPrintf(100, p, "%!0.17g", sqlite3_value_double(pValue)); + break; + } + case SQLITE_INTEGER: { +@@ -214535,9 +214862,10 @@ static u32 jsonbPayloadSize(const JsonParse *pParse, u32 i, u32 *pSz){ + u8 x; + u32 sz; + u32 n; +- assert( i<=pParse->nBlob ); +- x = pParse->aBlob[i]>>4; +- if( x<=11 ){ ++ if( i>=pParse->nBlob ){ ++ *pSz = 0; ++ return 0; ++ }else if( (x = pParse->aBlob[i]>>4)<=11 ){ + sz = x; + n = 1; + }else if( x==12 ){ +@@ -214662,7 +214990,8 @@ static u32 jsonTranslateBlobToText( + if( sz==0 ) goto malformed_jsonb; + if( zIn[0]=='-' ){ + jsonAppendChar(pOut, '-'); +- k++; ++ if( sz<=1 ) goto malformed_jsonb; ++ k = 1; + } + if( zIn[k]=='.' ){ + jsonAppendChar(pOut, '0'); +@@ -217320,11 +217649,9 @@ static void jsonGroupInverse( + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); +-#ifdef NEVER + /* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will + ** always have been called to initialize it */ + if( NEVER(!pStr) ) return; +-#endif + z = pStr->zBuf; + for(i=1; inUsed && ((c = z[i])!=',' || inStr || nNest); i++){ + if( c=='"' ){ +@@ -217353,6 +217680,13 @@ static void jsonGroupInverse( + ** json_group_obj(NAME,VALUE) + ** + ** Return a JSON object composed of all names and values in the aggregate. ++** ++** Rows for which NAME is NULL do not result in a new entry. However, we ++** do initially insert a "@" entry into the growing string for each null entry ++** and change the first character of the string to "@" to signal that the ++** string contains null entries. The "@" markers are needed in order to ++** correctly process xInverse() requests. The initial "@" is converted ++** back into "{" and the "@" null values are removed by jsonObjectCompute(). + */ + static void jsonObjectStep( + sqlite3_context *ctx, +@@ -217370,7 +217704,7 @@ static void jsonObjectStep( + if( pStr->zBuf==0 ){ + jsonStringInit(pStr, ctx); + jsonAppendChar(pStr, '{'); +- }else if( pStr->nUsed>1 && z!=0 ){ ++ }else if( pStr->nUsed>1 ){ + jsonAppendChar(pStr, ','); + } + pStr->pCtx = ctx; +@@ -217378,6 +217712,9 @@ static void jsonObjectStep( + jsonAppendString(pStr, z, n); + jsonAppendChar(pStr, ':'); + jsonAppendSqlValue(pStr, argv[1]); ++ }else{ ++ pStr->zBuf[0] = '@'; ++ jsonAppendRawNZ(pStr, "@", 1); + } + } + } +@@ -217386,20 +217723,64 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); + if( pStr ){ +- jsonAppendRawNZ(pStr, "}", 2); +- jsonStringTrimOneChar(pStr); ++ JsonString *pOgStr = pStr; ++ JsonString tmpStr; ++ jsonAppendRawNZ(pOgStr, "}", 2); /* Ensure it is zero-terminated */ ++ jsonStringTrimOneChar(pOgStr); /* Remove the zero terminator */ + pStr->pCtx = ctx; + if( pStr->eErr ){ + jsonReturnString(pStr, 0, 0); + return; +- }else if( flags & JSON_BLOB ){ ++ } ++ if( pStr->zBuf[0]!='{' ){ ++ /* The string contains null entries that need to be removed */ ++ u64 i, j; ++ int inStr = 0; ++ if( !isFinal ){ ++ /* Work with a temporary copy of the string if this is not the ++ ** final result */ ++ jsonStringInit(&tmpStr, ctx); ++ jsonAppendRawNZ(&tmpStr, pStr->zBuf, pStr->nUsed+1); ++ pStr = &tmpStr; ++ if( pStr->eErr ){ ++ jsonReturnString(pStr, 0, 0); ++ return; ++ } ++ jsonStringTrimOneChar(pStr); /* Remove zero terminator */ ++ } ++ /* Fix up the string by changing the initial "@" flag back to ++ ** to "{" and removing all subsequence "@" entries, with their ++ ** associated comma delimeters. */ ++ pStr->zBuf[0] = '{'; ++ for(i=j=1; inUsed; i++){ ++ char c = pStr->zBuf[i]; ++ if( c=='"' ){ ++ inStr = !inStr; ++ pStr->zBuf[j++] = '"'; ++ }else if( c=='\\' ){ ++ pStr->zBuf[j++] = '\\'; ++ pStr->zBuf[j++] = pStr->zBuf[++i]; ++ }else if( c=='@' && !inStr ){ ++ assert( i+1nUsed ); ++ if( pStr->zBuf[i+1]==',' ){ ++ i++; ++ }else if( pStr->zBuf[j-1]==',' ){ ++ j--; ++ } ++ }else{ ++ pStr->zBuf[j++] = c; ++ } ++ } ++ pStr->zBuf[j] = 0; /* Restore zero terminator */ ++ pStr->nUsed = j; /* Truncate the string */ ++ } ++ if( flags & JSON_BLOB ){ + jsonReturnStringAsBlob(pStr); + if( isFinal ){ + if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf); + }else{ +- jsonStringTrimOneChar(pStr); ++ jsonStringTrimOneChar(pOgStr); + } +- return; + }else if( isFinal ){ + sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, + pStr->bStatic ? SQLITE_TRANSIENT : +@@ -217407,8 +217788,9 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ + pStr->bStatic = 1; + }else{ + sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); +- jsonStringTrimOneChar(pStr); ++ jsonStringTrimOneChar(pOgStr); + } ++ if( pStr!=pOgStr ) jsonStringReset(pStr); + }else if( flags & JSON_BLOB ){ + static const unsigned char emptyObject = 0x0c; + sqlite3_result_blob(ctx, &emptyObject, 1, SQLITE_STATIC); +@@ -217573,7 +217955,9 @@ static int jsonSkipLabel(JsonEachCursor *p){ + if( p->eType==JSONB_OBJECT ){ + u32 sz = 0; + u32 n = jsonbPayloadSize(&p->sParse, p->i, &sz); +- return p->i + n + sz; ++ sz += p->i + n; ++ if( sz >= p->sParse.nBlob ) sz = p->i; ++ return sz; + }else{ + return p->i; + } +@@ -218260,7 +218644,7 @@ struct Rtree { + u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */ + u8 nBytesPerCell; /* Bytes consumed per cell */ + u8 inWrTrans; /* True if inside write transaction */ +- u8 nAux; /* # of auxiliary columns in %_rowid */ ++ u16 nAux; /* # of auxiliary columns in %_rowid */ + #ifdef SQLITE_ENABLE_GEOPOLY + u8 nAuxNotNull; /* Number of initial not-null aux columns */ + #endif +@@ -218400,7 +218784,7 @@ struct RtreeCursor { + sqlite3_stmt *pReadAux; /* Statement to read aux-data */ + RtreeSearchPoint sPoint; /* Cached next search point */ + RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */ +- u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */ ++ u32 anQueue[RTREE_MAX_DEPTH+2]; /* Number of queued entries by iLevel */ + }; + + /* Return the Rtree of a RtreeCursor */ +@@ -218855,6 +219239,9 @@ static int nodeAcquire( + rc = SQLITE_CORRUPT_VTAB; + RTREE_IS_CORRUPT(pRtree); + } ++ }else if( iNode<=0 ){ ++ RTREE_IS_CORRUPT(pRtree); ++ rc = SQLITE_CORRUPT_VTAB; + }else if( pRtree->iNodeSize==sqlite3_blob_bytes(pRtree->pNodeBlob) ){ + pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode)+pRtree->iNodeSize); + if( !pNode ){ +@@ -218880,7 +219267,7 @@ static int nodeAcquire( + */ + if( rc==SQLITE_OK && pNode && iNode==1 ){ + pRtree->iDepth = readInt16(pNode->zData); +- if( pRtree->iDepth>RTREE_MAX_DEPTH ){ ++ if( pRtree->iDepth>=RTREE_MAX_DEPTH ){ + rc = SQLITE_CORRUPT_VTAB; + RTREE_IS_CORRUPT(pRtree); + } +@@ -219496,7 +219883,7 @@ static int nodeRowidIndex( + ){ + int ii; + int nCell = NCELL(pNode); +- assert( nCell<200 ); ++ assert( nCell<65536 && nCell>=0 ); + for(ii=0; iiRTREE_MAXCELLS ){ ++ RTREE_IS_CORRUPT(pRtree); ++ return SQLITE_CORRUPT_VTAB; ++ } + pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell); + while( p->iCellRTREE_MAX_AUX_COLUMN+3 ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[2 + (argc>=6)]); + return SQLITE_ERROR; +@@ -223654,6 +224044,11 @@ static int geopolyInit( + int ii; + (void)pAux; + ++ if( argc>=RTREE_MAX_AUX_COLUMN+4 ){ ++ *pzErr = sqlite3_mprintf("Too many columns for a geopoly table"); ++ return SQLITE_ERROR; ++ } ++ + sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + +@@ -224788,7 +225183,7 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ + const UChar *zInput; /* Pointer to input string */ + UChar *zOutput = 0; /* Pointer to output buffer */ + int nInput; /* Size of utf-16 input string in bytes */ +- int nOut; /* Size of output buffer in bytes */ ++ sqlite3_int64 nOut; /* Size of output buffer in bytes */ + int cnt; + int bToUpper; /* True for toupper(), false for tolower() */ + UErrorCode status; +@@ -224811,7 +225206,7 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ + } + + for(cnt=0; cnt<2; cnt++){ +- UChar *zNew = sqlite3_realloc(zOutput, nOut); ++ UChar *zNew = sqlite3_realloc64(zOutput, nOut); + if( zNew==0 ){ + sqlite3_free(zOutput); + sqlite3_result_error_nomem(p); +@@ -224820,9 +225215,9 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ + zOutput = zNew; + status = U_ZERO_ERROR; + if( bToUpper ){ +- nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); ++ nOut = 2LL*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + }else{ +- nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); ++ nOut = 2LL*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + } + + if( U_SUCCESS(status) ){ +@@ -226435,16 +226830,26 @@ static unsigned int rbuDeltaGetInt(const char **pz, int *pLen){ + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36, + -1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -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, -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, -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, -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, -1, + }; + unsigned int v = 0; + int c; + unsigned char *z = (unsigned char*)*pz; +- unsigned char *zStart = z; +- while( (c = zValue[0x7f&*(z++)])>=0 ){ +- v = (v<<6) + c; ++ unsigned char *zEnd = z + (*pLen); ++ while( z=0 ){ ++ v = (v<<6) + c; ++ z++; + } +- z--; +- *pLen -= (int)(z - zStart); ++ ++ *pLen -= (int)(z - (unsigned char*)*pz); + *pz = (char*)z; + return v; + } +@@ -226513,21 +226918,22 @@ static int rbuDeltaApply( + int lenDelta, /* Length of the delta */ + char *zOut /* Write the output into this preallocated buffer */ + ){ +- unsigned int limit; +- unsigned int total = 0; ++ sqlite3_uint64 limit; ++ sqlite3_uint64 total = 0; + #if RBU_ENABLE_DELTA_CKSUM + char *zOrigOut = zOut; + #endif + + limit = rbuDeltaGetInt(&zDelta, &lenDelta); +- if( *zDelta!='\n' ){ ++ if( lenDelta<=0 || *zDelta!='\n' ){ + /* ERROR: size integer not terminated by "\n" */ + return -1; + } +- zDelta++; lenDelta--; +- while( *zDelta && lenDelta>0 ){ ++ zDelta++; lenDelta--; /* Skip the \n */ ++ while( lenDelta>0 && zDelta[0] ){ + unsigned int cnt, ofst; + cnt = rbuDeltaGetInt(&zDelta, &lenDelta); ++ if( lenDelta<=0 ) return -1; + switch( zDelta[0] ){ + case '@': { + zDelta++; lenDelta--; +@@ -226557,7 +226963,7 @@ static int rbuDeltaApply( + /* ERROR: insert command gives an output larger than predicted */ + return -1; + } +- if( (int)cnt>lenDelta ){ ++ if( cnt>lenDelta ){ + /* ERROR: insert count exceeds size of delta */ + return -1; + } +@@ -226595,7 +227001,7 @@ static int rbuDeltaApply( + static int rbuDeltaOutputSize(const char *zDelta, int lenDelta){ + int size; + size = rbuDeltaGetInt(&zDelta, &lenDelta); +- if( *zDelta!='\n' ){ ++ if( lenDelta<=0 || *zDelta!='\n' ){ + /* ERROR: size integer not terminated by "\n" */ + return -1; + } +@@ -226643,7 +227049,7 @@ static void rbuFossilDeltaFunc( + return; + } + +- aOut = sqlite3_malloc(nOut+1); ++ aOut = sqlite3_malloc64((i64)nOut+1); + if( aOut==0 ){ + sqlite3_result_error_nomem(context); + }else{ +@@ -228570,13 +228976,13 @@ static int rbuGetUpdateStmt( + char *zUpdate = 0; + + pUp->zMask = (char*)&pUp[1]; +- memcpy(pUp->zMask, zMask, pIter->nTblCol); + pUp->pNext = pIter->pRbuUpdate; + pIter->pRbuUpdate = pUp; + + if( zSet ){ + const char *zPrefix = ""; +- ++ assert( p->rc==SQLITE_OK ); ++ memcpy(pUp->zMask, zMask, pIter->nTblCol); + if( pIter->eType!=RBU_PK_VTAB ) zPrefix = "rbu_imp_"; + zUpdate = sqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s", + zPrefix, pIter->zTbl, zSet, zWhere +@@ -228666,6 +229072,9 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ + + case RBU_STATE_ROW: + pRet->nRow = sqlite3_column_int(pStmt, 1); ++ if( pRet->nRow<0 ){ ++ rc = SQLITE_CORRUPT; ++ } + break; + + case RBU_STATE_PROGRESS: +@@ -232537,12 +232946,13 @@ static int dbpageFilter( + pCsr->szPage = sqlite3BtreeGetPageSize(pBt); + pCsr->mxPgno = sqlite3BtreeLastPage(pBt); + if( idxNum & 1 ){ ++ i64 iPg = sqlite3_value_int64(argv[idxNum>>1]); + assert( argc>(idxNum>>1) ); +- pCsr->pgno = sqlite3_value_int(argv[idxNum>>1]); +- if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){ ++ if( iPg<1 || iPg>pCsr->mxPgno ){ + pCsr->pgno = 1; + pCsr->mxPgno = 0; + }else{ ++ pCsr->pgno = (Pgno)iPg; + pCsr->mxPgno = pCsr->pgno; + } + }else{ +@@ -234821,7 +235231,7 @@ static void sessionAppendStr( + int *pRc + ){ + int nStr = sqlite3Strlen30(zStr); +- if( 0==sessionBufferGrow(p, nStr+1, pRc) ){ ++ if( 0==sessionBufferGrow(p, (i64)nStr+1, pRc) ){ + memcpy(&p->aBuf[p->nBuf], zStr, nStr); + p->nBuf += nStr; + p->aBuf[p->nBuf] = 0x00; +@@ -234889,6 +235299,16 @@ static int sessionPrepareDfltStmt( + return rc; + } + ++/* ++** Finalize statement pStmt. If (*pRc) is SQLITE_OK when this function is ++** called, set it to the results of the sqlite3_finalize() call. Or, if ++** it is already set to an error code, leave it as is. ++*/ ++static void sessionFinalizeStmt(sqlite3_stmt *pStmt, int *pRc){ ++ int rc = sqlite3_finalize(pStmt); ++ if( *pRc==SQLITE_OK ) *pRc = rc; ++} ++ + /* + ** Table pTab has one or more existing change-records with old.* records + ** with fewer than pTab->nCol columns. This function updates all such +@@ -234911,9 +235331,8 @@ static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){ + } + } + ++ sessionFinalizeStmt(pStmt, &rc); + pSession->rc = rc; +- rc = sqlite3_finalize(pStmt); +- if( pSession->rc==SQLITE_OK ) pSession->rc = rc; + return pSession->rc; + } + +@@ -235481,7 +235900,7 @@ static int sessionDiffFindNew( + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pStmt; +- rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0); ++ rc = sqlite3_prepare_v2(pSession->db, zStmt, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; + pDiffCtx->pStmt = pStmt; +@@ -235544,7 +235963,7 @@ static int sessionDiffFindModified( + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pStmt; +- rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0); ++ rc = sqlite3_prepare_v2(pSession->db, zStmt, -1, &pStmt, 0); + + if( rc==SQLITE_OK ){ + SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; +@@ -236239,11 +236658,11 @@ static int sessionSelectStmt( + ); + sessionAppendStr(&cols, "tbl, ?2, stat", &rc); + }else{ +- #if 0 ++#if 0 + if( bRowid ){ + sessionAppendStr(&cols, SESSIONS_ROWID, &rc); + } +- #endif ++#endif + for(i=0; iiNext>=pInput->nData ) break; ++ if( pInput->iNext+1>=pInput->nData ){ ++ if( pInput->iNext!=pInput->nData ){ ++ rc = SQLITE_CORRUPT_BKPT; ++ goto finished_invert; ++ } ++ break; ++ } + eType = pInput->aData[pInput->iNext]; + + switch( eType ){ +@@ -237693,6 +238118,7 @@ struct SessionApplyCtx { + u8 bRebaseStarted; /* If table header is already in rebase */ + u8 bRebase; /* True to collect rebase information */ + u8 bIgnoreNoop; /* True to ignore no-op conflicts */ ++ u8 bNoUpdateLoop; /* No update-loop processing */ + int bRowid; + char *zErr; /* Error message, if any */ + }; +@@ -238266,7 +238692,7 @@ static int sessionConflictHandler( + u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; + int nBlob = pIter->in.iNext - pIter->in.iCurrent; + sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); +- return SQLITE_OK; ++ return rc; + }else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE + || eType==SQLITE_CHANGESET_CONFLICT + ){ +@@ -238514,7 +238940,264 @@ static int sessionApplyOneWithRetry( + } + + /* +-** Retry the changes accumulated in the pApply->constraints buffer. ++** Create an iterator to iterate through the retry buffer pRetry. ++*/ ++static int sessionRetryIterInit( ++ SessionBuffer *pRetry, /* Buffer to iterate through */ ++ int bPatchset, /* True for patchset, false for changeset */ ++ const char *zTab, /* Table name */ ++ SessionApplyCtx *pApply, /* Session apply context */ ++ sqlite3_changeset_iter **ppIter /* OUT: New iterator */ ++){ ++ sqlite3_changeset_iter *pRet = 0; ++ int rc = SQLITE_OK; ++ ++ rc = sessionChangesetStart( ++ &pRet, 0, 0, pRetry->nBuf, pRetry->aBuf, pApply->bInvertConstraints, 1 ++ ); ++ if( rc==SQLITE_OK ){ ++ size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); ++ pRet->bPatchset = bPatchset; ++ pRet->zTab = (char*)zTab; ++ pRet->nCol = pApply->nCol; ++ pRet->abPK = pApply->abPK; ++ sessionBufferGrow(&pRet->tblhdr, nByte, &rc); ++ pRet->apValue = (sqlite3_value**)pRet->tblhdr.aBuf; ++ if( rc==SQLITE_OK ){ ++ memset(pRet->apValue, 0, nByte); ++ }else{ ++ sqlite3changeset_finalize(pRet); ++ pRet = 0; ++ } ++ } ++ ++ *ppIter = pRet; ++ return rc; ++} ++ ++/* ++** Attempt to apply all the changes in retry buffer pRetry to the database. ++** Except, if parameter iSkip is greater than or equal to 0, skip change ++** iSkip. ++*/ ++static int sessionApplyRetryBuffer( ++ SessionBuffer *pRetry, /* Buffer to apply changes from */ ++ int iSkip, /* If >=0, index of change to omit */ ++ sqlite3 *db, /* Database handle */ ++ int bPatchset, /* True for patchset, false for changeset */ ++ const char *zTab, /* Name of table to write to */ ++ SessionApplyCtx *pApply, /* Apply context */ ++ int(*xConflict)(void*, int, sqlite3_changeset_iter*), ++ void *pCtx /* First argument passed to xConflict */ ++){ ++ int rc = SQLITE_OK; ++ int rc2 = SQLITE_OK; ++ int ii = 0; ++ sqlite3_changeset_iter *pIter = 0; ++ ++ assert( pApply->constraints.nBuf==0 ); ++ ++ rc = sessionRetryIterInit(pRetry, bPatchset, zTab, pApply, &pIter); ++ ++ for(ii=0; rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter); ii++){ ++ if( ii!=iSkip ){ ++ rc = sessionApplyOneWithRetry(db, pIter, pApply, xConflict, pCtx); ++ } ++ } ++ ++ rc2 = sqlite3changeset_finalize(pIter); ++ if( rc==SQLITE_OK ) rc = rc2; ++ assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); ++ ++ return rc; ++} ++ ++/* ++** Check if table zTab in the "main" database of db is a WITHOUT ROWID ++** table. ++** ++** If no error occurs, return SQLITE_OK and set output variable (*pbWR) to ++** true if zTab is a WITHOUT ROWID table, or false otherwise. Or, if an ++** error does occur, return an SQLite error code. The final value of (*pbWR) ++** is undefined in this case. ++*/ ++static int sessionTableIsWithoutRowid(sqlite3 *db, const char *zTab, int *pbWR){ ++ sqlite3_stmt *pList = 0; ++ char *zSql = 0; ++ int rc = SQLITE_OK; ++ ++ zSql = sqlite3_mprintf("PRAGMA table_list = %Q", zTab); ++ if( zSql==0 ){ ++ rc = SQLITE_NOMEM; ++ }else{ ++ rc = sqlite3_prepare_v2(db, zSql, -1, &pList, 0); ++ sqlite3_free(zSql); ++ } ++ ++ if( rc==SQLITE_OK ){ ++ sqlite3_step(pList); ++ *pbWR = sqlite3_column_int(pList, 4); ++ rc = sqlite3_finalize(pList); ++ } ++ ++ return rc; ++} ++ ++/* ++** Iterator pUp points to an UPDATE change. This function deletes the ++** affected row from the database and creates an INSERT statement that ++** may be used to reinsert the row as it is after the UPDATE change ++** has been applied. ++** ++** If successful, SQLITE_OK is returned and output variable (*ppInsert) ++** is left pointing to a prepared INSERT statement. It is the responsibility ++** of the caller to eventually free this statement using sqlite3_finalize(). ++** Or, if an error occurs, an SQLite error code is returned and (*ppInsert) ++** set to NULL. pApply->zErr may be set to an error message in this case. ++*/ ++static int sessionUpdateToDeleteInsert( ++ sqlite3 *db, /* Database to write to */ ++ const char *zTab, /* Table name */ ++ SessionApplyCtx *pApply, /* Apply context */ ++ sqlite3_changeset_iter *pUp, /* Iterator pointing to UPDATE change */ ++ sqlite3_stmt **ppInsert /* OUT: INSERT statement */ ++){ ++ sqlite3_stmt *pRet = 0; /* The INSERT statement */ ++ sqlite3_stmt *pSelect = 0; /* SELECT to read current values of row */ ++ int rc = SQLITE_OK; ++ int bWR = 0; ++ ++ rc = sessionTableIsWithoutRowid(db, zTab, &bWR); ++ if( rc==SQLITE_OK ){ ++ char *zSelect = 0; ++ char *zInsert = 0; ++ SessionBuffer cols = {0, 0, 0}; ++ SessionBuffer insbind = {0, 0, 0}; ++ SessionBuffer pkcols = {0, 0, 0}; ++ SessionBuffer selbind = {0, 0, 0}; ++ ++ const char *zComma = ""; ++ const char *zComma2 = ""; ++ int ii; ++ for(ii=0; iinCol; ii++){ ++ sessionAppendStr(&cols, zComma, &rc); ++ sessionAppendIdent(&cols, pApply->azCol[ii], &rc); ++ sessionAppendStr(&insbind, zComma, &rc); ++ sessionAppendStr(&insbind, "?", &rc); ++ zComma = ", "; ++ ++ if( pApply->abPK[ii] ){ ++ sessionAppendStr(&pkcols, zComma2, &rc); ++ sessionAppendIdent(&pkcols, pApply->azCol[ii], &rc); ++ sessionAppendStr(&selbind, zComma2, &rc); ++ sessionAppendPrintf(&selbind, &rc, "?%d", ii+1); ++ zComma2 = ", "; ++ } ++ } ++ if( bWR==0 ){ ++ sessionAppendStr(&cols, zComma, &rc); ++ sessionAppendStr(&cols, SESSIONS_ROWID, &rc); ++ sessionAppendStr(&insbind, zComma, &rc); ++ sessionAppendStr(&insbind, "?", &rc); ++ } ++ ++ if( rc==SQLITE_OK ){ ++ zSelect = sqlite3_mprintf("SELECT %s FROM %Q WHERE (%s) IS (%s)", ++ cols.aBuf, zTab, pkcols.aBuf, selbind.aBuf ++ ); ++ if( zSelect==0 ) rc = SQLITE_NOMEM; ++ } ++ if( rc==SQLITE_OK ){ ++ zInsert = sqlite3_mprintf("INSERT INTO %Q(%s) VALUES(%s)", ++ zTab, cols.aBuf, insbind.aBuf ++ ); ++ if( zInsert==0 ) rc = SQLITE_NOMEM; ++ } ++ ++ if( rc==SQLITE_OK ){ ++ rc = sessionPrepare(db, &pSelect, &pApply->zErr, zSelect); ++ } ++ if( rc==SQLITE_OK ){ ++ rc = sessionPrepare(db, &pRet, &pApply->zErr, zInsert); ++ } ++ ++ sqlite3_free(zSelect); ++ sqlite3_free(zInsert); ++ sqlite3_free(cols.aBuf); ++ sqlite3_free(insbind.aBuf); ++ sqlite3_free(pkcols.aBuf); ++ sqlite3_free(selbind.aBuf); ++ } ++ ++ if( rc==SQLITE_OK ){ ++ rc = sessionBindRow( ++ pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pSelect ++ ); ++ } ++ ++ if( rc==SQLITE_OK && sqlite3_step(pSelect)==SQLITE_ROW ){ ++ int iCol; ++ for(iCol=0; iColnCol; iCol++){ ++ sqlite3_value *pVal = pUp->apValue[iCol+pApply->nCol]; ++ if( pVal==0 ){ ++ pVal = sqlite3_column_value(pSelect, iCol); ++ } ++ rc = sqlite3_bind_value(pRet, iCol+1, pVal); ++ } ++ if( bWR==0 ){ ++ sqlite3_bind_int64(pRet, iCol+1, sqlite3_column_int64(pSelect, iCol)); ++ } ++ } ++ sessionFinalizeStmt(pSelect, &rc); ++ ++ /* Delete the row from the database. */ ++ if( rc==SQLITE_OK ){ ++ rc = sessionBindRow( ++ pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pApply->pDelete ++ ); ++ sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); ++ } ++ if( rc==SQLITE_OK ){ ++ sqlite3_step(pApply->pDelete); ++ rc = sqlite3_reset(pApply->pDelete); ++ } ++ ++ if( rc!=SQLITE_OK ){ ++ sqlite3_finalize(pRet); ++ pRet = 0; ++ } ++ ++ *ppInsert = pRet; ++ return rc; ++} ++ ++/* ++** Retry the changes accumulated in the pApply->constraints buffer. The ++** pApply->constraints buffer contains all changes to table zTab that ++** could not be applied due to SQLITE_CONSTRAINT errors. This function ++** attempts to apply them as follows: ++** ++** 1) It runs through the buffer and attempts to retry each change, ++** removing any that are successfully applied from the buffer. This ++** is repeated until no further progress can be made. ++** ++** 2) For each UPDATE change in the buffer, try the following in a ++** savepoint transaction: ++** ++** a) DELETE the affected row, ++** b) Attempt step (1) with remaining changes, ++** c) Attempt to INSERT a row equivalent to the one that would be ++** created by applying this UPDATE change. ++** ++** If the INSERT in (c) succeeds, the savepoint is committed and all ++** successfully applied changes are removed from the buffer. Step (2) ++** is then repeated. ++** ++** 3) Once step (2) has been attempted for each UPDATE in the change, ++** a final attempt is made to apply each remaining change. This time, ++** if an SQLITE_CONSTRAINT error is encountered, the conflict handler ++** is invoked and the user has to decide whether to omit the change ++** or rollback the entire _apply() operation. + */ + static int sessionRetryConstraints( + sqlite3 *db, +@@ -238525,41 +239208,101 @@ static int sessionRetryConstraints( + void *pCtx /* First argument passed to xConflict */ + ){ + int rc = SQLITE_OK; ++ int iUpdate = 0; + ++ /* Step (1) */ + while( pApply->constraints.nBuf ){ +- sqlite3_changeset_iter *pIter2 = 0; + SessionBuffer cons = pApply->constraints; + memset(&pApply->constraints, 0, sizeof(SessionBuffer)); + +- rc = sessionChangesetStart( +- &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1 ++ rc = sessionApplyRetryBuffer( ++ &cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx ++ ); ++ ++ sqlite3_free(cons.aBuf); ++ if( rc!=SQLITE_OK ) break; ++ ++ /* If no progress has been made this round, break out of the loop. */ ++ if( pApply->constraints.nBuf>=cons.nBuf ) break; ++ } ++ ++ /* Step (2) */ ++ while( rc==SQLITE_OK && pApply->constraints.nBuf && !pApply->bNoUpdateLoop ){ ++ SessionBuffer cons = {0, 0, 0}; ++ sqlite3_changeset_iter *pUp = 0; ++ sqlite3_stmt *pInsert = 0; ++ int iSkip = 0; ++ ++ rc = sessionRetryIterInit( ++ &pApply->constraints, bPatchset, zTab, pApply, &pUp + ); + if( rc==SQLITE_OK ){ +- size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); +- int rc2; +- pIter2->bPatchset = bPatchset; +- pIter2->zTab = (char*)zTab; +- pIter2->nCol = pApply->nCol; +- pIter2->abPK = pApply->abPK; +- sessionBufferGrow(&pIter2->tblhdr, nByte, &rc); +- pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf; +- if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte); ++ int iThis = -1; ++ while( SQLITE_ROW==sqlite3changeset_next(pUp) ){ ++ if( pUp->op==SQLITE_UPDATE ) iThis++; ++ if( iThis==iUpdate ) break; ++ iSkip++; ++ } ++ if( iThis==iUpdate ){ ++ rc = sqlite3_exec(db, "SAVEPOINT update_op", 0, 0, 0); ++ if( rc==SQLITE_OK ){ ++ rc = sessionUpdateToDeleteInsert(db, zTab, pApply, pUp, &pInsert); ++ } ++ } ++ sqlite3changeset_finalize(pUp); ++ if( iThis!=iUpdate ) break; ++ } ++ ++ if( rc==SQLITE_OK ){ ++ cons = pApply->constraints; + +- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){ +- rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx); ++ while( rc==SQLITE_OK && pApply->constraints.nBuf>0 ){ ++ SessionBuffer app = pApply->constraints; ++ memset(&pApply->constraints, 0, sizeof(SessionBuffer)); ++ rc = sessionApplyRetryBuffer( ++ &app, iSkip, db, bPatchset, zTab, pApply, xConflict, pCtx ++ ); ++ if( app.aBuf!=cons.aBuf ){ ++ sqlite3_free(app.aBuf); ++ } ++ if( pApply->constraints.nBuf>=app.nBuf ){ ++ break; ++ } ++ iSkip = -1; + } ++ } + +- rc2 = sqlite3changeset_finalize(pIter2); +- if( rc==SQLITE_OK ) rc = rc2; ++ iUpdate++; ++ if( rc==SQLITE_OK ){ ++ sqlite3_step(pInsert); ++ rc = sqlite3_finalize(pInsert); ++ if( rc==SQLITE_CONSTRAINT ){ ++ rc = sqlite3_exec(db, "ROLLBACK TO update_op", 0, 0, 0); ++ sqlite3_free(pApply->constraints.aBuf); ++ pApply->constraints = cons; ++ memset(&cons, 0, sizeof(cons)); ++ }else if( rc==SQLITE_OK ){ ++ iUpdate = 0; ++ } ++ if( rc==SQLITE_OK ){ ++ rc = sqlite3_exec(db, "RELEASE update_op", 0, 0, 0); ++ } ++ }else{ ++ sqlite3_finalize(pInsert); + } +- assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); + + sqlite3_free(cons.aBuf); +- if( rc!=SQLITE_OK ) break; +- if( pApply->constraints.nBuf>=cons.nBuf ){ +- /* No progress was made on the last round. */ +- pApply->bDeferConstraints = 0; +- } ++ } ++ ++ /* Step (3) */ ++ if( rc==SQLITE_OK && pApply->constraints.nBuf ){ ++ SessionBuffer cons = pApply->constraints; ++ memset(&pApply->constraints, 0, sizeof(SessionBuffer)); ++ pApply->bDeferConstraints = 0; ++ rc = sessionApplyRetryBuffer( ++ &cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx ++ ); ++ sqlite3_free(cons.aBuf); + } + + return rc; +@@ -238613,6 +239356,7 @@ static int sessionChangesetApply( + sApply.bRebase = (ppRebase && pnRebase); + sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP); ++ sApply.bNoUpdateLoop = !!(flags & SQLITE_CHANGESETAPPLY_NOUPDATELOOP); + if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ + rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); + } +@@ -239567,7 +240311,12 @@ static int sessionChangesetToHash( + + pIter->in.bNoDiscard = 1; + while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){ +- rc = sessionOneChangeIterToHash(pGrp, pIter, bRebase); ++ if( bRebase && pIter->bPatchset ){ ++ /* A patchset may not be used as a rebase */ ++ rc = SQLITE_ERROR; ++ }else{ ++ rc = sessionOneChangeIterToHash(pGrp, pIter, bRebase); ++ } + if( rc!=SQLITE_OK ) break; + } + +@@ -239885,14 +240634,17 @@ static void sessionAppendRecordMerge( + u8 *a2, int n2, /* Record 2 */ + int *pRc /* IN/OUT: error code */ + ){ +- sessionBufferGrow(pBuf, n1+n2, pRc); ++ u8 *a1Eof = &a1[n1]; ++ u8 *a2Eof = &a2[n2]; ++ ++ sessionBufferGrow(pBuf, (i64)n1+n2, pRc); + if( *pRc==SQLITE_OK ){ + int i; + u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; + for(i=0; i0 && (*a1==0 || *a1==0xFF)) ){ + memcpy(pOut, a2, nn2); + pOut += nn2; + }else{ +@@ -239934,20 +240686,21 @@ static void sessionAppendPartialUpdate( + u8 *aChange, int nChange, /* Record to rebase against */ + int *pRc /* IN/OUT: Return Code */ + ){ +- sessionBufferGrow(pBuf, 2+nRec+nChange, pRc); ++ sessionBufferGrow(pBuf, (i64)2+nRec+nChange, pRc); + if( *pRc==SQLITE_OK ){ + int bData = 0; + u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; + int i; + u8 *a1 = aRec; + u8 *a2 = aChange; ++ u8 *a2Eof = &a2[nChange]; + + *pOut++ = SQLITE_UPDATE; + *pOut++ = pIter->bIndirect; + for(i=0; inCol; i++){ + int n1 = sessionSerialLen(a1); +- int n2 = sessionSerialLen(a2); +- if( pIter->abPK[i] || a2[0]==0 ){ ++ int n2 = (a2>=a2Eof) ? 0 : sessionSerialLen(a2); ++ if( n2<=0 || pIter->abPK[i] || a2[0]==0 ){ + if( !pIter->abPK[i] && a1[0] ) bData = 1; + memcpy(pOut, a1, n1); + pOut += n1; +@@ -240148,8 +240901,8 @@ SQLITE_API int sqlite3rebaser_configure( + sqlite3_rebaser *p, + int nRebase, const void *pRebase + ){ +- sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ + int rc; /* Return code */ ++ sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ + rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase); + if( rc==SQLITE_OK ){ + rc = sessionChangesetToHash(pIter, &p->grp, 1); +@@ -240424,7 +241177,7 @@ SQLITE_API int sqlite3changegroup_change_blob( + const void *pVal, + int nVal + ){ +- sqlite3_int64 nByte = 1 + sessionVarintLen(nVal) + nVal; ++ sqlite3_int64 nByte = 1 + sessionVarintLen(nVal) + (i64)nVal; + int rc = SQLITE_OK; + SessionBuffer *pBuf = 0; + +@@ -244274,7 +245027,7 @@ static void fts5SnippetFunction( + int rc = SQLITE_OK; /* Return code */ + int iCol; /* 1st argument to snippet() */ + const char *zEllips; /* 4th argument to snippet() */ +- int nToken; /* 5th argument to snippet() */ ++ i64 nToken; /* 5th argument to snippet() */ + int nInst = 0; /* Number of instance matches this row */ + int i; /* Used to iterate through instances */ + int nPhrase; /* Number of phrases in query */ +@@ -244299,7 +245052,7 @@ static void fts5SnippetFunction( + ctx.zClose = fts5ValueToText(apVal[2]); + ctx.iRangeEnd = -1; + zEllips = fts5ValueToText(apVal[3]); +- nToken = sqlite3_value_int(apVal[4]); ++ nToken = (int)(MIN( MAX(sqlite3_value_int64(apVal[4]), 0), 64)); + + iBestCol = (iCol>=0 ? iCol : 0); + nPhrase = pApi->xPhraseCount(pFts); +@@ -247015,7 +247768,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ + i64 iPos = a[i].reader.iPos; + Fts5PoslistWriter *pWriter = &a[i].writer; + if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){ +- sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos); ++ sqlite3Fts5PoslistSafeAppend(a[i].pOut, &pWriter->iPrev, iPos); + } + } + +@@ -247966,10 +248719,10 @@ static int fts5ParseTokenize( + memset(pSyn, 0, (size_t)nByte); + pSyn->pTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); + pSyn->nFullTerm = pSyn->nQueryTerm = nToken; ++ memcpy(pSyn->pTerm, pToken, nToken); + if( pCtx->pConfig->bTokendata ){ + pSyn->nQueryTerm = (int)strlen(pSyn->pTerm); + } +- memcpy(pSyn->pTerm, pToken, nToken); + pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym; + pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn; + } +@@ -250969,6 +251722,7 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ + pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); + if( pRet ){ + pRet->nn = nByte; ++ pRet->szLeaf = 0; + aOut = pRet->p = (u8*)pRet + szData; + }else{ + rc = SQLITE_NOMEM; +@@ -250981,10 +251735,8 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ + sqlite3_free(pRet); + pRet = 0; + }else{ +- /* TODO1: Fix this */ + pRet->p[nByte] = 0x00; + pRet->p[nByte+1] = 0x00; +- pRet->szLeaf = fts5GetU16(&pRet->p[2]); + } + } + p->rc = rc; +@@ -251005,10 +251757,18 @@ static void fts5DataRelease(Fts5Data *pData){ + sqlite3_free(pData); + } + ++/* ++** Read a leaf-page record. This is similar to fts5DataRead(), except that ++** it fills in the Fts5Data.szLeaf value before returning. ++*/ + static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ + Fts5Data *pRet = fts5DataRead(p, iRowid); + if( pRet ){ +- if( pRet->nn<4 || pRet->szLeaf>pRet->nn ){ ++ assert( pRet->szLeaf==0 ); ++ if( pRet->nn>=4 ){ ++ pRet->szLeaf = fts5GetU16(&pRet->p[2]); ++ } ++ if( pRet->szLeaf<4 || pRet->szLeaf>pRet->nn ){ + FTS5_CORRUPT_ROWID(p, iRowid); + fts5DataRelease(pRet); + pRet = 0; +@@ -251252,7 +252012,7 @@ static int fts5StructureDecode( + i += fts5GetVarint32(&pData[i], nTotal); + if( nTotalnMerge ) rc = FTS5_CORRUPT; + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, +- nTotal * sizeof(Fts5StructureSegment) ++ (i64)nTotal * sizeof(Fts5StructureSegment) + ); + nSegment -= nTotal; + } +@@ -252208,7 +252968,7 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ + while( p->rc==SQLITE_OK && pIter->iLeafPgno>pIter->iTermLeafPgno ){ + Fts5Data *pNew; + pIter->iLeafPgno--; +- pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID( ++ pNew = fts5LeafRead(p, FTS5_SEGMENT_ROWID( + pIter->pSeg->iSegid, pIter->iLeafPgno + )); + if( pNew ){ +@@ -252662,6 +253422,10 @@ static void fts5LeafSeek( + if( nKeepn ){ ++ FTS5_CORRUPT_ITER(p, pIter); ++ return; ++ } + + assert( nKeep>=nMatch ); + if( nKeep==nMatch ){ +@@ -252913,6 +253677,10 @@ static void fts5SegIterNextInit( + + pIter->iPgidxOff = pIter->pLeaf->szLeaf; + pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], iTermOff); ++ if( iTermOff > pIter->pLeaf->szLeaf ){ ++ p->rc = FTS5_CORRUPT; ++ return; ++ } + pIter->iLeafOffset = iTermOff; + fts5SegIterLoadTerm(p, pIter, 0); + fts5SegIterLoadNPos(p, pIter); +@@ -253638,8 +254406,7 @@ static void fts5PoslistFilterCallback( + + do { + while( ieState ){ + fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); +@@ -253788,7 +254555,7 @@ static void fts5IndexExtractColset( + /* Advance pointer p until it points to pEnd or an 0x01 byte that is + ** not part of a varint */ + while( paiCol[i]==iCurrent ){ +@@ -253885,8 +254652,11 @@ static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){ + + assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); + assert( pIter->pColset ); ++ assert( pIter->poslist.nSpace>=pIter->pIndex->pConfig->nCol ); + +- if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf ){ ++ if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf ++ || pSeg->nPos>pIter->pIndex->pConfig->nCol ++ ){ + fts5IterSetOutputs_Col(pIter, pSeg); + }else{ + u8 *a = (u8*)&pSeg->pLeaf->p[pSeg->iLeafOffset]; +@@ -255237,7 +256007,7 @@ static void fts5SecureDeleteOverflow( + int iNext = 0; + u8 *aPg = 0; + +- pLeaf = fts5DataRead(p, iRowid); ++ pLeaf = fts5LeafRead(p, iRowid); + if( pLeaf==0 ) break; + aPg = pLeaf->p; + +@@ -255245,7 +256015,7 @@ static void fts5SecureDeleteOverflow( + if( iNext!=0 ){ + *pbLastInDoclist = 0; + } +- if( iNext==0 && pLeaf->szLeaf!=pLeaf->nn ){ ++ if( iNext==0 && pLeaf->szLeafnn ){ + fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext); + } + +@@ -255380,6 +256150,11 @@ static void fts5DoSecureDelete( + }else{ + iStart = fts5GetU16(&aPg[0]); + } ++ if( iStart>nPg ){ ++ FTS5_CORRUPT_IDX(p); ++ sqlite3_free(aIdx); ++ return; ++ } + + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + assert_nc( iSOP<=pSeg->iLeafOffset ); +@@ -255527,7 +256302,7 @@ static void fts5DoSecureDelete( + /* The entry being removed may be the only position list in + ** its doclist. */ + for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ +- Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); ++ Fts5Data *pPg = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); + int bEmpty = (pPg && pPg->nn==4); + fts5DataRelease(pPg); + if( bEmpty==0 ) break; +@@ -255535,7 +256310,7 @@ static void fts5DoSecureDelete( + + if( iPgno==pSeg->iTermLeafPgno ){ + i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); +- Fts5Data *pTerm = fts5DataRead(p, iId); ++ Fts5Data *pTerm = fts5LeafRead(p, iId); + if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ + u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; + int nTermIdx = pTerm->nn - pTerm->szLeaf; +@@ -258065,8 +258840,8 @@ static void fts5IndexTombstoneRebuild( + ){ + const int MINSLOT = 32; + int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey); +- int nSlot = 0; /* Number of slots in each output page */ +- int nOut = 0; ++ i64 nSlot = 0; /* Number of slots in each output page */ ++ i64 nOut = 0; + + /* Figure out how many output pages (nOut) and how many slots per + ** page (nSlot). There are three possibilities: +@@ -258091,23 +258866,26 @@ static void fts5IndexTombstoneRebuild( + nSlot = MINSLOT; + }else if( pSeg->nPgTombstone==1 ){ + /* Case 2. */ +- int nElem = (int)fts5GetU32(&pData1->p[4]); ++ u32 nElem = fts5GetU32(&pData1->p[4]); + assert( pData1 && iPg1==0 ); +- nOut = 1; +- nSlot = MAX(nElem*4, MINSLOT); +- if( nSlot>nSlotPerPage ) nOut = 0; ++ if( nElem>((u32)nSlotPerPage/4) ){ ++ nOut = 0; ++ }else{ ++ nOut = 1; ++ nSlot = MAX((i64)nElem*4, MINSLOT); ++ } + } + if( nOut==0 ){ + /* Case 3. */ +- nOut = (pSeg->nPgTombstone * 2 + 1); ++ nOut = ((i64)pSeg->nPgTombstone * 2 + 1); + nSlot = nSlotPerPage; + } + + /* Allocate the required array and output pages */ + while( 1 ){ + int res = 0; +- int ii = 0; +- int szPage = 0; ++ i64 ii = 0; ++ i64 szPage = 0; + Fts5Data **apOut = 0; + + /* Allocate space for the new hash table */ +@@ -258484,7 +259262,7 @@ static void fts5IndexIntegrityCheckEmpty( + /* Now check that the iter.nEmpty leaves following the current leaf + ** (a) exist and (b) contain no terms. */ + for(i=iFirst; p->rc==SQLITE_OK && i<=iLast; i++){ +- Fts5Data *pLeaf = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); ++ Fts5Data *pLeaf = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); + if( pLeaf ){ + if( !fts5LeafIsTermless(pLeaf) + || (i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf)) +@@ -258612,9 +259390,13 @@ static void fts5IndexIntegrityCheckSegment( + FTS5_CORRUPT_ROWID(p, iRow); + }else{ + iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm); +- res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); +- if( res==0 ) res = nTerm - nIdxTerm; +- if( res<0 ) FTS5_CORRUPT_ROWID(p, iRow); ++ if( (i64)iOff+(i64)nTerm>(i64)pLeaf->szLeaf ){ ++ FTS5_CORRUPT_ROWID(p, iRow); ++ }else{ ++ res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); ++ if( res==0 ) res = nTerm - nIdxTerm; ++ if( res<0 ) FTS5_CORRUPT_ROWID(p, iRow); ++ } + } + + fts5IntegrityCheckPgidx(p, iRow, pLeaf); +@@ -258645,7 +259427,7 @@ static void fts5IndexIntegrityCheckSegment( + /* Check any rowid-less pages that occur before the current leaf. */ + for(iPg=iPrevLeaf+1; iPgeDetail==FTS5_DETAIL_COLUMNS ){ + Fts5Sorter *pSorter = pCsr->pSorter; +- int n; +- if( pSorter ){ +- int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); +- n = pSorter->aIdx[iPhrase] - i1; +- pIter->a = &pSorter->aPoslist[i1]; ++ if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){ ++ rc = SQLITE_RANGE; + }else{ +- rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); +- } +- if( rc==SQLITE_OK ){ +- assert( pIter->a || n==0 ); +- pIter->b = (pIter->a ? &pIter->a[n] : 0); +- *piCol = 0; +- fts5ApiPhraseNextColumn(pCtx, pIter, piCol); ++ int n; ++ if( pSorter ){ ++ int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); ++ n = pSorter->aIdx[iPhrase] - i1; ++ pIter->a = &pSorter->aPoslist[i1]; ++ }else{ ++ rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); ++ } ++ if( rc==SQLITE_OK ){ ++ assert( pIter->a || n==0 ); ++ pIter->b = (pIter->a ? &pIter->a[n] : 0); ++ *piCol = 0; ++ fts5ApiPhraseNextColumn(pCtx, pIter, piCol); ++ } + } + }else{ + int n; +@@ -263256,7 +264042,7 @@ static void fts5SourceIdFunc( + ){ + assert( nArg==0 ); + UNUSED_PARAM2(nArg, apUnused); +- sqlite3_result_text(pCtx, "fts5: 2026-05-05 10:34:17 c88b22011a54b4f6fbd149e9f8e4de77658ce58143a1af0e3785e4e6475127e9", -1, SQLITE_TRANSIENT); ++ sqlite3_result_text(pCtx, "fts5: 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc", -1, SQLITE_TRANSIENT); + } + + /* +@@ -263898,34 +264684,31 @@ static int sqlite3Fts5StorageOpen( + if( pConfig->eContent==FTS5_CONTENT_NORMAL + || pConfig->eContent==FTS5_CONTENT_UNINDEXED + ){ +- int nDefn = 32 + pConfig->nCol*10; +- char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 20); +- if( zDefn==0 ){ +- rc = SQLITE_NOMEM; +- }else{ +- int i; +- int iOff; +- sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); +- iOff = (int)strlen(zDefn); +- for(i=0; inCol; i++){ +- if( pConfig->eContent==FTS5_CONTENT_NORMAL +- || pConfig->abUnindexed[i] +- ){ +- sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); +- iOff += (int)strlen(&zDefn[iOff]); +- } ++ int i = 0; ++ char *zDefn = 0; ++ sqlite3_str *pDefn = sqlite3_str_new(pConfig->db); ++ ++ sqlite3_str_appendf(pDefn, "id INTEGER PRIMARY KEY"); ++ for(i=0; inCol; i++){ ++ if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->abUnindexed[i] ){ ++ sqlite3_str_appendf(pDefn, ", c%d", i); + } +- if( pConfig->bLocale ){ +- for(i=0; inCol; i++){ +- if( pConfig->abUnindexed[i]==0 ){ +- sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", l%d", i); +- iOff += (int)strlen(&zDefn[iOff]); +- } ++ } ++ if( pConfig->bLocale ){ ++ for(i=0; inCol; i++){ ++ if( pConfig->abUnindexed[i]==0 ){ ++ sqlite3_str_appendf(pDefn, ", l%d", i); + } + } ++ } ++ zDefn = sqlite3_str_finish(pDefn); ++ ++ if( zDefn ){ + rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); ++ sqlite3_free(zDefn); ++ }else{ ++ rc = SQLITE_NOMEM; + } +- sqlite3_free(zDefn); + } + + if( rc==SQLITE_OK && pConfig->bColumnsize ){ +@@ -265650,8 +266433,14 @@ static int fts5PorterCreate( + const char *zBase = "unicode61"; + fts5_tokenizer_v2 *pV2 = 0; + +- if( nArg>0 ){ +- zBase = azArg[0]; ++ while( nArg>0 ){ ++ if( sqlite3_stricmp(azArg[0],"porter")==0 ){ ++ nArg--; ++ azArg++; ++ }else{ ++ zBase = azArg[0]; ++ break; ++ } + } + + pRet = (PorterTokenizer*)sqlite3_malloc64(sizeof(PorterTokenizer)); +diff --git a/deps/sqlite/sqlite3.h b/deps/sqlite/sqlite3.h +index 8ee26c99..c91b81c0 100644 +--- a/deps/sqlite/sqlite3.h ++++ b/deps/sqlite/sqlite3.h +@@ -146,12 +146,12 @@ extern "C" { + ** [sqlite3_libversion_number()], [sqlite3_sourceid()], + ** [sqlite_version()] and [sqlite_source_id()]. + */ +-#define SQLITE_VERSION "3.53.1" +-#define SQLITE_VERSION_NUMBER 3053001 +-#define SQLITE_SOURCE_ID "2026-05-05 10:34:17 c88b22011a54b4f6fbd149e9f8e4de77658ce58143a1af0e3785e4e6475127e9" ++#define SQLITE_VERSION "3.53.4" ++#define SQLITE_VERSION_NUMBER 3053004 ++#define SQLITE_SOURCE_ID "2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc" + #define SQLITE_SCM_BRANCH "branch-3.53" +-#define SQLITE_SCM_TAGS "release version-3.53.1" +-#define SQLITE_SCM_DATETIME "2026-05-05T10:34:17.344Z" ++#define SQLITE_SCM_TAGS "release version-3.53.4" ++#define SQLITE_SCM_DATETIME "2026-07-24T19:02:57.525Z" + + /* + ** CAPI3REF: Run-Time Library Version Numbers +@@ -4366,7 +4366,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + ** or in an ORDER BY or GROUP BY clause.

)^ + ** + ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
SQLITE_LIMIT_EXPR_DEPTH
+-**
The maximum depth of the parse tree on any expression.
)^ ++**
The maximum depth of the parse tree on any expression and ++** the maximum nesting depth for subqueries and VIEWs
)^ + ** + ** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
SQLITE_LIMIT_PARSER_DEPTH
+ **
The maximum depth of the LALR(1) parser stack used to analyze +@@ -4397,7 +4398,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + **
The maximum index number of any [parameter] in an SQL statement.)^ + ** + ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(
SQLITE_LIMIT_TRIGGER_DEPTH
+-**
The maximum depth of recursion for triggers.
)^ ++**
The maximum depth of recursion for triggers, and the maximum ++** nesting depth for separate triggers.
)^ + ** + ** [[SQLITE_LIMIT_WORKER_THREADS]] ^(
SQLITE_LIMIT_WORKER_THREADS
+ **
The maximum number of auxiliary worker threads that a single +@@ -12853,11 +12855,23 @@ SQLITE_API int sqlite3changeset_apply_v3( + ** database behave as if they were declared with "ON UPDATE NO ACTION ON + ** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL + ** or SET DEFAULT. ++** ++**
SQLITE_CHANGESETAPPLY_NOUPDATELOOP
++** Sometimes, a changeset contains two or more update statements such that ++** although after applying all updates the database will contain no ++** constraint violations, no single update can be applied before the others. ++** The simplest example of this is a pair of UPDATEs that have "swapped" ++** two column values with a UNIQUE constraint. ++**

++** Usually, sqlite3changeset_apply() and similar functions work hard to try ++** to find a way to apply such a changeset. However, if this flag is set, ++** then all such updates are considered CONSTRAINT conflicts. + */ + #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 + #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 + #define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 + #define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008 ++#define SQLITE_CHANGESETAPPLY_NOUPDATELOOP 0x0010 + + /* + ** CAPI3REF: Constants Passed To The Conflict Handler +-- +2.55.0 + diff --git a/SPECS/nodejs.spec b/SPECS/nodejs.spec index e160b71..4e63f40 100644 --- a/SPECS/nodejs.spec +++ b/SPECS/nodejs.spec @@ -3,7 +3,7 @@ %{load:%{_sourcedir}/nodejs.srpm.macros} # === Versions of any software shipped in the main nodejs tarball -%nodejs_define_version node 1:24.18.0-2%{?dist} -p +%nodejs_define_version node 1:24.18.0-3%{?dist} -p # The following ones are generated via script; # expect anything between the markers to be overwritten on any update. @@ -41,7 +41,7 @@ # Version from node-v24.18.0/deps/npm/package.json %nodejs_define_version npm 1:11.16.0 # Version from node-v24.18.0/deps/sqlite/sqlite3.h -%nodejs_define_version sqlite 3.53.1 +%nodejs_define_version sqlite 3.53.4 # Version from node-v24.18.0/deps/uvwasi/include/uvwasi.h %nodejs_define_version uvwasi 0.0.23 # Version from node-v24.18.0/deps/v8/include/v8-version.h @@ -162,9 +162,11 @@ Patch0002: 0002-fips-disable-options.patch # Sourced from: # https://github.com/nodejs/node/commit/fd350185539242b7d383ebf38f6041f10b472b39 Patch0003: 0001-CVE-2026-59873-CVE-2026-59874-upgrade-bundled-tar-to-7.5.19.patch -# Sourced from: -# https://github.com/juliangruber/brace-expansion/commit/c7e33ec13ac1a684c116720843ce24e208611754 -Patch0004: 0001-CVE-2026-13149-Brace-Expansion-DOS.patch +Patch0004: 0001-update-sqlite-to-3.53.4.patch +# ip-address rebase +Patch0005: 0001-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch +# brace-expansion rebase +Patch0006: 0001-CVE-2026-69152-brace-expansion-5.0.9.patch %description Node.js is a platform built on Chrome's JavaScript runtime @@ -546,6 +548,17 @@ bash '%{SOURCE10}' "${RPM_BUILD_ROOT}%{_bindir}/node" test/ '%{SOURCE11}' || : %{_pkgdocdir}/npm/ %changelog +* Wed Aug 5 2026 Tomas Juhasz - 1:24.18.0-3 +- deps: update npm/ip-address to 10.4.0 +- deps: update npm/brace-expansion to 5.9.0 + Fix: CVE-2026-69192 & CVE-2026-54272 CVE-2026-69152 + Resolves: RHEL-223388 RHEL-223907 RHEL-223792 + +* Tue Aug 04 2026 Andrei Radchenko - 1:24.18.0-3 +- Backport patch update sqlite to 3.53.4 + Fixes: CVE-2026-11824, CVE-2026-11822 + Resolves: RHEL-218252 RHEL-218257 + * Wed Jul 22 2026 Jan Staněk - 1:24.18.0-2 - Backport patches for various CVEs Fixes: CVE-2026-59873 CVE-2026-59874 CVE-2026-13149