@sap/cds-dk 10.1.0 → 10.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/bind.js +4 -0
  3. package/bin/build.js +1 -0
  4. package/bin/compile/index.js +12 -11
  5. package/bin/completion/index.js +21 -10
  6. package/bin/completion/scripts/cds.fish +179 -127
  7. package/bin/completion/scripts/cds.ps1 +88 -14
  8. package/bin/completion/scripts/cds.sh +71 -5
  9. package/bin/completion/scripts/cds.zsh +81 -15
  10. package/bin/completion/templates/cds-tmpl.ps1 +15 -6
  11. package/bin/deploy/index.js +7 -3
  12. package/bin/env.js +4 -1
  13. package/bin/export.js +9 -1
  14. package/bin/import.js +4 -1
  15. package/bin/init.js +1 -0
  16. package/bin/inspect.js +3 -1
  17. package/bin/lint.js +5 -3
  18. package/bin/minify.js +6 -1
  19. package/bin/repl.js +11 -2
  20. package/bin/run.js +12 -1
  21. package/bin/serve.js +13 -2
  22. package/bin/up.js +4 -1
  23. package/bin/util/containerize.js +2 -0
  24. package/bin/watch/index.js +8 -1
  25. package/lib/build/plugins/internal.js +0 -26
  26. package/lib/build/plugins/nodejs/index.js +8 -3
  27. package/lib/build/util.js +2 -4
  28. package/lib/cds.js +8 -2
  29. package/lib/init/constants.js +1 -1
  30. package/lib/init/merge.js +21 -17
  31. package/lib/init/template/cloud-logging/index.js +3 -3
  32. package/lib/init/template/data/as-json.js +3 -0
  33. package/lib/init/template/destination/index.js +3 -3
  34. package/lib/init/template/enterprise-messaging/index.js +4 -1
  35. package/lib/init/template/event-mesh/index.js +3 -0
  36. package/lib/init/template/multitenancy/index.js +2 -2
  37. package/lib/init/template/workzone-standard/index.js +1 -1
  38. package/node_modules/@cap-js/db-service/CHANGELOG.md +30 -0
  39. package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
  40. package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
  41. package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
  42. package/node_modules/@cap-js/db-service/lib/cqn4sql.js +125 -21
  43. package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
  44. package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
  45. package/node_modules/@cap-js/db-service/lib/search.js +1 -1
  46. package/node_modules/@cap-js/db-service/package.json +1 -1
  47. package/node_modules/@cap-js/sqlite/CHANGELOG.md +28 -0
  48. package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
  49. package/node_modules/@cap-js/sqlite/package.json +3 -3
  50. package/node_modules/@sap/cds/CHANGELOG.md +19 -0
  51. package/node_modules/@sap/cds/common.cds +3 -3
  52. package/node_modules/@sap/cds/lib/compile/minify.js +3 -1
  53. package/node_modules/@sap/cds/lib/env/cds-env.js +1 -1
  54. package/node_modules/@sap/cds/lib/env/cds-requires.js +1 -0
  55. package/node_modules/@sap/cds/libx/_runtime/common/generic/crud.js +7 -1
  56. package/node_modules/@sap/cds/libx/_runtime/common/utils/cqn.js +1 -1
  57. package/node_modules/@sap/cds/libx/_runtime/fiori/lean-draft.js +1 -1
  58. package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  59. package/node_modules/@sap/cds/libx/_runtime/messaging/kafka.js +1 -1
  60. package/node_modules/@sap/cds/libx/http/location.js +10 -11
  61. package/node_modules/@sap/cds/libx/odata/middleware/read.js +1 -1
  62. package/node_modules/@sap/cds/libx/queue/SchedulingService.js +4 -0
  63. package/node_modules/@sap/cds/libx/queue/index.js +44 -10
  64. package/node_modules/@sap/cds/libx/queue/processing.js +47 -16
  65. package/node_modules/@sap/cds/libx/queue/utils.js +3 -0
  66. package/node_modules/@sap/cds/package.json +1 -1
  67. package/node_modules/@sap/cds/tasks/enterprise-messaging-deploy.js +13 -5
  68. package/node_modules/@sap/cds-compiler/CHANGELOG.md +15 -0
  69. package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +7 -3
  70. package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +2 -0
  71. package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +12 -0
  72. package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +5 -3
  73. package/node_modules/@sap/cds-compiler/lib/transform/db/flattening.js +4 -3
  74. package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
  75. package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +19 -0
  76. package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
  77. package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +1 -1
  78. package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
  79. package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +29 -2
  80. package/node_modules/@sap/cds-compiler/package.json +1 -1
  81. package/node_modules/@sap/cds-mtxs/CHANGELOG.md +34 -0
  82. package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
  83. package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +3 -0
  84. package/node_modules/@sap/cds-mtxs/lib/utils.js +5 -4
  85. package/node_modules/@sap/cds-mtxs/package.json +1 -1
  86. package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +62 -7
  87. package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
  88. package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
  89. package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
  90. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
  91. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
  92. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
  93. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
  94. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +8 -3
  95. package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
  96. package/node_modules/@sap/cds-mtxs/srv/model-provider.js +11 -2
  97. package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
  98. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
  99. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
  100. package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +3 -2
  101. package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
  102. package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
  103. package/node_modules/proxy-addr/HISTORY.md +60 -54
  104. package/node_modules/proxy-addr/README.md +1 -1
  105. package/node_modules/proxy-addr/index.js +32 -0
  106. package/node_modules/proxy-addr/package.json +8 -4
  107. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  108. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  109. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  110. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  111. package/node_modules/yaml/package.json +1 -1
  112. package/package.json +2 -2
  113. /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
  114. /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
@@ -0,0 +1,212 @@
1
+ 'use strict'
2
+
3
+ const { Worker } = require('worker_threads')
4
+ const path = require('path')
5
+ const cds = require('@sap/cds')
6
+
7
+ const LOG = cds.log('mtx')
8
+ const WORKER_PATH = path.join(__dirname, 'job-worker.js')
9
+
10
+ const IDLE = 'idle'
11
+ const BUSY = 'busy'
12
+ const BOOTING = 'booting'
13
+
14
+ class DeploymentPool {
15
+
16
+ constructor(size) {
17
+ this.size = size
18
+ this.workers = [] // { worker, state, pending: Map<id, {resolve,reject}> }
19
+ this.waitQueue = [] // queued acquire requests: { resolve, reject }
20
+ this._idCounter = 0
21
+ this._shuttingDown = false
22
+ }
23
+
24
+ /**
25
+ * Spawn a new worker, bootstrap it, and add it to the pool.
26
+ * Returns a promise that resolves when the worker is ready.
27
+ */
28
+ _spawn() {
29
+ return new Promise((resolve, reject) => {
30
+ const worker = new Worker(WORKER_PATH, { env: process.env, workerData: { cdsEnv: cds.env, cdsRoot: cds.root } })
31
+ const entry = { worker, state: BOOTING, pending: new Map() }
32
+ this.workers.push(entry)
33
+
34
+ const onReady = (msg) => {
35
+ if (msg.ready) {
36
+ worker.off('message', onReady)
37
+ worker.off('error', onError)
38
+ entry.state = IDLE
39
+ worker.on('message', (msg) => this._onMessage(entry, msg))
40
+ worker.on('error', (err) => this._onError(entry, err))
41
+ worker.on('exit', (code) => this._onExit(entry, code))
42
+ resolve(entry)
43
+ } else if (msg.error) {
44
+ worker.off('message', onReady)
45
+ worker.off('error', onError)
46
+ this.workers = this.workers.filter(e => e !== entry)
47
+ reject(new Error(msg.error))
48
+ }
49
+ }
50
+
51
+ const onError = (err) => {
52
+ worker.off('message', onReady)
53
+ this.workers = this.workers.filter(e => e !== entry)
54
+ reject(err)
55
+ }
56
+
57
+ worker.on('message', onReady)
58
+ worker.on('error', onError)
59
+ })
60
+ }
61
+
62
+ /**
63
+ * Handle messages from a worker (command responses).
64
+ */
65
+ _onMessage(entry, { id, result, error }) {
66
+ const pending = entry.pending.get(id)
67
+ if (!pending) return
68
+ entry.pending.delete(id)
69
+ if (error) pending.reject(new Error(error))
70
+ else pending.resolve(result)
71
+ }
72
+
73
+ /**
74
+ * Handle worker errors.
75
+ */
76
+ _onError(entry, err) {
77
+ LOG.error('Deployment worker error:', err.message)
78
+ // Reject all pending tasks on this worker
79
+ for (const [, { reject }] of entry.pending) {
80
+ reject(new Error(`Worker error: ${err.message}`))
81
+ }
82
+ entry.pending.clear()
83
+ }
84
+
85
+ /**
86
+ * Handle worker exit — reject in-flight tasks and replace the worker.
87
+ */
88
+ _onExit(entry, code) {
89
+ this.workers = this.workers.filter(e => e !== entry)
90
+
91
+ // Reject any in-flight tasks
92
+ for (const [, { reject }] of entry.pending) {
93
+ reject(new Error(`Worker exited unexpectedly with code ${code}`))
94
+ }
95
+ entry.pending.clear()
96
+
97
+ // Do not replace workers during shutdown
98
+ if (this._shuttingDown) return
99
+
100
+ // Immediately replace the crashed worker
101
+ LOG.warn('Deployment worker exited with code', code, '- spawning replacement')
102
+ this._spawn().then(newEntry => {
103
+ // If there are queued requests waiting, serve them
104
+ if (this.waitQueue.length > 0 && newEntry.state === IDLE) {
105
+ newEntry.state = BUSY
106
+ const { resolve } = this.waitQueue.shift()
107
+ resolve(newEntry)
108
+ }
109
+ }).catch(err => {
110
+ LOG.error('Failed to spawn replacement worker:', err.message)
111
+ // If there are queued requests waiting, reject the first one
112
+ if (this.waitQueue.length > 0) {
113
+ const { reject } = this.waitQueue.shift()
114
+ reject(err)
115
+ }
116
+ })
117
+ }
118
+
119
+ /**
120
+ * Acquire an idle worker. If none available and pool not full, spawns a new one.
121
+ * If all workers are busy and pool is full, queues the request.
122
+ */
123
+ async acquire() {
124
+ // Try to find an idle worker
125
+ const idle = this.workers.find(e => e.state === IDLE)
126
+ if (idle) {
127
+ idle.state = BUSY
128
+ return idle
129
+ }
130
+
131
+ // Pool not full — spawn a new worker
132
+ if (this.workers.length < this.size) {
133
+ const entry = await this._spawn()
134
+ entry.state = BUSY
135
+ return entry
136
+ }
137
+
138
+ // All busy — wait in queue
139
+ return new Promise((resolve, reject) => {
140
+ this.waitQueue.push({ resolve, reject })
141
+ })
142
+ }
143
+
144
+ /**
145
+ * Release a worker back to idle state. Serves the next queued request if any.
146
+ */
147
+ release(entry) {
148
+ if (this.waitQueue.length > 0) {
149
+ entry.state = BUSY
150
+ const { resolve } = this.waitQueue.shift()
151
+ resolve(entry)
152
+ } else {
153
+ entry.state = IDLE
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Execute a service action on a worker.
159
+ * Acquires a worker, sends the command, waits for the result, releases the worker.
160
+ */
161
+ async exec(service, op, tenant, args) {
162
+ const entry = await this.acquire()
163
+ const id = ++this._idCounter
164
+ try {
165
+ const result = await new Promise((resolve, reject) => {
166
+ entry.pending.set(id, { resolve, reject })
167
+ entry.worker.postMessage({ id, service, op, tenant, args, cdsEnv: cds.env })
168
+ })
169
+ return result
170
+ } finally {
171
+ this.release(entry)
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Shut down all workers. Sends a shutdown message to each worker and waits for them to exit.
177
+ */
178
+ async shutdown() {
179
+ this._shuttingDown = true
180
+
181
+ // Reject all queued acquire requests
182
+ for (const { reject } of this.waitQueue) {
183
+ reject(new Error('Worker pool is shutting down'))
184
+ }
185
+ this.waitQueue = []
186
+
187
+ // Send shutdown message to all workers and wait for them to exit
188
+ await Promise.all(this.workers.map(entry => {
189
+ for (const [, { reject }] of entry.pending) {
190
+ reject(new Error('Worker pool is shutting down'))
191
+ }
192
+ entry.pending.clear()
193
+ entry.worker.postMessage({ shutdown: true })
194
+ return new Promise(resolve => entry.worker.once('exit', resolve))
195
+ }))
196
+ this.workers = []
197
+ }
198
+ }
199
+
200
+ let _pool
201
+
202
+ /**
203
+ * Returns the singleton DeploymentPool instance.
204
+ * @param {number} size - Pool size (number of workers)
205
+ */
206
+ module.exports = function getDeploymentPool(size) {
207
+ if (!_pool) {
208
+ _pool = new DeploymentPool(size)
209
+ cds.on('shutdown', async () => _pool.shutdown())
210
+ }
211
+ return _pool
212
+ }
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+
3
+ const { parentPort, workerData } = require('worker_threads')
4
+ const cds = require('@sap/cds')
5
+ const { _import, isfile, path } = cds.utils
6
+
7
+ if (workerData?.cdsRoot) cds.root = workerData.cdsRoot
8
+ if (workerData?.cdsEnv) cds.env.add(workerData.cdsEnv)
9
+
10
+ async function bootstrap() {
11
+ const services = [
12
+ '@sap/cds-mtxs/srv/deployment-service',
13
+ '@sap/cds-mtxs/srv/model-provider',
14
+ '@sap/cds-mtxs/srv/cf/saas-provisioning-service',
15
+ '@sap/cds-mtxs/srv/extensibility-service',
16
+ ]
17
+ await _local_cli_js()
18
+ cds.emit('bootstrap')
19
+ const model = await cds.load([...services, '@sap/cds-mtxs/db/extensions'])
20
+ cds.model = cds.compile.for.nodejs(model)
21
+ const unserved = !cds.services['cds.xt.DeploymentService']
22
+ if (unserved) await cds.serve(services)
23
+ await cds.plugins
24
+ await cds.emit('served', cds.services)
25
+ parentPort.postMessage({ ready: true })
26
+ }
27
+
28
+ async function _local_cli_js() {
29
+ const _local = file => isfile(file) || isfile(path.join(cds.env.folders.srv, file))
30
+ const cli_js = process.env.CDS_TYPESCRIPT && (_local('cli.ts') || _local('server.ts')) || _local('cli.js') || _local('server.js')
31
+ if (cli_js) await _import(cli_js)
32
+ }
33
+
34
+ parentPort.on('message', async (msg) => {
35
+ if (msg.shutdown) {
36
+ await cds.shutdown()
37
+ process.exit(0)
38
+ }
39
+ const { id, service, op, tenant, args, cdsEnv } = msg
40
+ if (cdsEnv) cds.env.add(cdsEnv)
41
+ const srv = cds.services[service]
42
+ if (!srv) {
43
+ parentPort.postMessage({ id, error: `Service ${service} not available` })
44
+ return
45
+ }
46
+ try {
47
+ const result = await srv[op](tenant, ...Object.values(args))
48
+ parentPort.postMessage({ id, result })
49
+ } catch (e) {
50
+ const error = e.message ?? 'Unknown error'
51
+ parentPort.postMessage({ id, error })
52
+ } finally {
53
+ // cds.db.disconnect(cds.env.requires.multitenancy.t0 ?? 't0')
54
+ // cds.db.disconnect(tenant)
55
+ }
56
+ })
57
+
58
+ bootstrap().catch(e => {
59
+ parentPort.postMessage({ error: `Bootstrap failed: ${e.message}` })
60
+ process.exit(1)
61
+ })
@@ -1,144 +1,150 @@
1
- 2.0.7 / 2021-05-31
2
- ==================
1
+ 2.0.8
2
+ =====
3
+
4
+ * Fix [CVE-2026-90711](https://www.cve.org/CVERecord?id=CVE-2026-90711) ([GHSA-jqcg-44mw-7w3h](https://github.com/jshttp/proxy-addr/security/advisories/GHSA-jqcg-44mw-7w3h))
5
+
6
+
7
+ 2.0.7
8
+ =====
3
9
 
4
10
  * deps: forwarded@0.2.0
5
11
  - Use `req.socket` over deprecated `req.connection`
6
12
 
7
- 2.0.6 / 2020-02-24
8
- ==================
13
+ 2.0.6
14
+ =====
9
15
 
10
16
  * deps: ipaddr.js@1.9.1
11
17
 
12
- 2.0.5 / 2019-04-16
13
- ==================
18
+ 2.0.5
19
+ =====
14
20
 
15
21
  * deps: ipaddr.js@1.9.0
16
22
 
17
- 2.0.4 / 2018-07-26
18
- ==================
23
+ 2.0.4
24
+ =====
19
25
 
20
26
  * deps: ipaddr.js@1.8.0
21
27
 
22
- 2.0.3 / 2018-02-19
23
- ==================
28
+ 2.0.3
29
+ =====
24
30
 
25
31
  * deps: ipaddr.js@1.6.0
26
32
 
27
- 2.0.2 / 2017-09-24
28
- ==================
33
+ 2.0.2
34
+ =====
29
35
 
30
36
  * deps: forwarded@~0.1.2
31
37
  - perf: improve header parsing
32
38
  - perf: reduce overhead when no `X-Forwarded-For` header
33
39
 
34
- 2.0.1 / 2017-09-10
35
- ==================
40
+ 2.0.1
41
+ =====
36
42
 
37
43
  * deps: forwarded@~0.1.1
38
44
  - Fix trimming leading / trailing OWS
39
45
  - perf: hoist regular expression
40
46
  * deps: ipaddr.js@1.5.2
41
47
 
42
- 2.0.0 / 2017-08-08
43
- ==================
48
+ 2.0.0
49
+ =====
44
50
 
45
51
  * Drop support for Node.js below 0.10
46
52
 
47
- 1.1.5 / 2017-07-25
48
- ==================
53
+ 1.1.5
54
+ =====
49
55
 
50
56
  * Fix array argument being altered
51
57
  * deps: ipaddr.js@1.4.0
52
58
 
53
- 1.1.4 / 2017-03-24
54
- ==================
59
+ 1.1.4
60
+ =====
55
61
 
56
62
  * deps: ipaddr.js@1.3.0
57
63
 
58
- 1.1.3 / 2017-01-14
59
- ==================
64
+ 1.1.3
65
+ =====
60
66
 
61
67
  * deps: ipaddr.js@1.2.0
62
68
 
63
- 1.1.2 / 2016-05-29
64
- ==================
69
+ 1.1.2
70
+ =====
65
71
 
66
72
  * deps: ipaddr.js@1.1.1
67
73
  - Fix IPv6-mapped IPv4 validation edge cases
68
74
 
69
- 1.1.1 / 2016-05-03
70
- ==================
75
+ 1.1.1
76
+ =====
71
77
 
72
78
  * Fix regression matching mixed versions against multiple subnets
73
79
 
74
- 1.1.0 / 2016-05-01
75
- ==================
80
+ 1.1.0
81
+ =====
76
82
 
77
83
  * Fix accepting various invalid netmasks
78
84
  - IPv4 netmasks must be contingous
79
85
  - IPv6 addresses cannot be used as a netmask
80
86
  * deps: ipaddr.js@1.1.0
81
87
 
82
- 1.0.10 / 2015-12-09
83
- ===================
88
+ 1.0.10
89
+ ======
84
90
 
85
91
  * deps: ipaddr.js@1.0.5
86
92
  - Fix regression in `isValid` with non-string arguments
87
93
 
88
- 1.0.9 / 2015-12-01
89
- ==================
94
+ 1.0.9
95
+ =====
90
96
 
91
97
  * deps: ipaddr.js@1.0.4
92
98
  - Fix accepting some invalid IPv6 addresses
93
99
  - Reject CIDRs with negative or overlong masks
94
100
  * perf: enable strict mode
95
101
 
96
- 1.0.8 / 2015-05-10
97
- ==================
102
+ 1.0.8
103
+ =====
98
104
 
99
105
  * deps: ipaddr.js@1.0.1
100
106
 
101
- 1.0.7 / 2015-03-16
102
- ==================
107
+ 1.0.7
108
+ =====
103
109
 
104
110
  * deps: ipaddr.js@0.1.9
105
111
  - Fix OOM on certain inputs to `isValid`
106
112
 
107
- 1.0.6 / 2015-02-01
108
- ==================
113
+ 1.0.6
114
+ =====
109
115
 
110
116
  * deps: ipaddr.js@0.1.8
111
117
 
112
- 1.0.5 / 2015-01-08
113
- ==================
118
+ 1.0.5
119
+ =====
114
120
 
115
121
  * deps: ipaddr.js@0.1.6
116
122
 
117
- 1.0.4 / 2014-11-23
118
- ==================
123
+ 1.0.4
124
+ =====
119
125
 
120
126
  * deps: ipaddr.js@0.1.5
121
127
  - Fix edge cases with `isValid`
122
128
 
123
- 1.0.3 / 2014-09-21
124
- ==================
129
+ 1.0.3
130
+ =====
125
131
 
126
132
  * Use `forwarded` npm module
127
133
 
128
- 1.0.2 / 2014-09-18
129
- ==================
134
+ 1.0.2
135
+ =====
130
136
 
131
137
  * Fix a global leak when multiple subnets are trusted
132
138
  * Support Node.js 0.6
133
139
  * deps: ipaddr.js@0.1.3
134
140
 
135
- 1.0.1 / 2014-06-03
136
- ==================
141
+ 1.0.1
142
+ =====
137
143
 
138
144
  * Fix links in npm package
139
145
 
140
- 1.0.0 / 2014-05-08
141
- ==================
146
+ 1.0.0
147
+ =====
142
148
 
143
149
  * Add `trust` argument to determine proxy trust on
144
150
  * Accepts custom function
@@ -150,12 +156,12 @@
150
156
  * Add `proxyaddr.compile` to pre-compile `trust` function
151
157
  to make subsequent calls faster
152
158
 
153
- 0.0.1 / 2014-05-04
154
- ==================
159
+ 0.0.1
160
+ =====
155
161
 
156
162
  * Fix bad npm publish
157
163
 
158
- 0.0.0 / 2014-05-04
159
- ==================
164
+ 0.0.0
165
+ =====
160
166
 
161
167
  * Initial release
@@ -37,7 +37,7 @@ proxyaddr(req, function (addr) { return addr === '127.0.0.1' })
37
37
  proxyaddr(req, function (addr, i) { return i < 1 })
38
38
  ```
39
39
 
40
- The `trust` arugment may also be a single IP address string or an
40
+ The `trust` argument may also be a single IP address string or an
41
41
  array of trusted addresses, as plain IP addresses, CIDR-formatted
42
42
  strings, or IP/netmask strings.
43
43
 
@@ -256,6 +256,13 @@ function trustMulti (subnets) {
256
256
  if (!isip(addr)) return false
257
257
 
258
258
  var ip = parseip(addr)
259
+
260
+ if (ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) {
261
+ // Canonicalize IPv4-mapped candidates to IPv4 so a mapped address
262
+ // cannot bypass the cross-family guard via the same-family path
263
+ ip = ip.toIPv4Address()
264
+ }
265
+
259
266
  var ipconv
260
267
  var kind = ip.kind()
261
268
 
@@ -272,6 +279,12 @@ function trustMulti (subnets) {
272
279
  continue
273
280
  }
274
281
 
282
+ if (subnetkind !== 'ipv4' && !(subnetrange >= 96 && subnetip.isIPv4MappedAddress())) {
283
+ // IPv6 subnet only spans IPv4 when it is a mapped subnet whose
284
+ // prefix covers the ::ffff: marker; otherwise it must not match IPv4
285
+ continue
286
+ }
287
+
275
288
  if (!ipconv) {
276
289
  // Convert IP to match subnet IP kind
277
290
  ipconv = subnetkind === 'ipv4'
@@ -280,6 +293,9 @@ function trustMulti (subnets) {
280
293
  }
281
294
 
282
295
  trusted = ipconv
296
+ } else if (kind === 'ipv6' && subnetip.isIPv4MappedAddress()) {
297
+ // A native IPv6 candidate cannot match an IPv4-mapped subnet
298
+ continue
283
299
  }
284
300
 
285
301
  if (trusted.match(subnetip, subnetrange)) {
@@ -308,6 +324,13 @@ function trustSingle (subnet) {
308
324
  if (!isip(addr)) return false
309
325
 
310
326
  var ip = parseip(addr)
327
+
328
+ if (ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) {
329
+ // Canonicalize IPv4-mapped candidates to IPv4 so a mapped address
330
+ // cannot bypass the cross-family guard via the same-family path
331
+ ip = ip.toIPv4Address()
332
+ }
333
+
311
334
  var kind = ip.kind()
312
335
 
313
336
  if (kind !== subnetkind) {
@@ -316,10 +339,19 @@ function trustSingle (subnet) {
316
339
  return false
317
340
  }
318
341
 
342
+ if (!subnetisipv4 && !(subnetrange >= 96 && subnetip.isIPv4MappedAddress())) {
343
+ // IPv6 subnet only spans IPv4 when it is a mapped subnet whose
344
+ // prefix covers the ::ffff: marker; otherwise it must not match IPv4
345
+ return false
346
+ }
347
+
319
348
  // Convert IP to match subnet IP kind
320
349
  ip = subnetisipv4
321
350
  ? ip.toIPv4Address()
322
351
  : ip.toIPv4MappedAddress()
352
+ } else if (kind === 'ipv6' && subnetip.isIPv4MappedAddress()) {
353
+ // A native IPv6 candidate cannot match an IPv4-mapped subnet
354
+ return false
323
355
  }
324
356
 
325
357
  return ip.match(subnetip, subnetrange)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "proxy-addr",
3
3
  "description": "Determine address of proxied request",
4
- "version": "2.0.7",
4
+ "version": "2.0.8",
5
5
  "author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -10,6 +10,10 @@
10
10
  "x-forwarded-for"
11
11
  ],
12
12
  "repository": "jshttp/proxy-addr",
13
+ "funding": {
14
+ "type": "opencollective",
15
+ "url": "https://opencollective.com/express"
16
+ },
13
17
  "dependencies": {
14
18
  "forwarded": "0.2.0",
15
19
  "ipaddr.js": "1.9.1"
@@ -17,11 +21,11 @@
17
21
  "devDependencies": {
18
22
  "benchmark": "2.1.4",
19
23
  "beautify-benchmark": "0.2.4",
20
- "deep-equal": "1.0.1",
24
+ "deep-equal": "1.1.2",
21
25
  "eslint": "7.26.0",
22
26
  "eslint-config-standard": "14.1.1",
23
- "eslint-plugin-import": "2.23.4",
24
- "eslint-plugin-markdown": "2.2.0",
27
+ "eslint-plugin-import": "2.32.0",
28
+ "eslint-plugin-markdown": "2.2.1",
25
29
  "eslint-plugin-node": "11.1.0",
26
30
  "eslint-plugin-promise": "4.3.1",
27
31
  "eslint-plugin-standard": "4.1.0",
@@ -64,46 +64,47 @@ function plainValue(source, onError) {
64
64
  }
65
65
  if (badChar)
66
66
  onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`);
67
- return foldLines(source);
67
+ return unfoldLines(source);
68
68
  }
69
69
  function singleQuotedValue(source, onError) {
70
70
  if (source[source.length - 1] !== "'" || source.length === 1)
71
71
  onError(source.length, 'MISSING_CHAR', "Missing closing 'quote");
72
- return foldLines(source.slice(1, -1)).replace(/''/g, "'");
72
+ return unfoldLines(source.slice(1, -1)).replace(/''/g, "'");
73
73
  }
74
- function foldLines(source) {
74
+ function unfoldLines(source) {
75
+ const line = /(.*?)\r?\n/sy;
76
+ let match = line.exec(source);
77
+ if (!match)
78
+ return source;
75
79
  /**
76
- * The negative lookbehind here and in the `re` RegExp is to
80
+ * The negative lookbehinds in these RegExps are to
77
81
  * prevent causing a polynomial search time in certain cases.
78
82
  *
79
- * The try-catch is for Safari, which doesn't support this yet:
83
+ * The try-catch is for Safari < 16.4 and other old browsers:
80
84
  * https://caniuse.com/js-regexp-lookbehind
81
85
  */
82
- let first, line;
86
+ let trimEnd, trimBoth;
83
87
  try {
84
- first = new RegExp('(.*?)(?<![ \t])[ \t]*\r?\n', 'sy');
85
- line = new RegExp('[ \t]*(.*?)(?:(?<![ \t])[ \t]*)?\r?\n', 'sy');
88
+ trimEnd = new RegExp('(?<![ \t])[ \t]+$');
89
+ trimBoth = new RegExp('^[ \t]+|(?<![ \t])[ \t]+$', 'g');
86
90
  }
87
91
  catch {
88
- first = /(.*?)[ \t]*\r?\n/sy;
89
- line = /[ \t]*(.*?)[ \t]*\r?\n/sy;
92
+ trimEnd = /[ \t]+$/;
93
+ trimBoth = /^[ \t]+|[ \t]+$/g;
90
94
  }
91
- let match = first.exec(source);
92
- if (!match)
93
- return source;
94
- let res = match[1];
95
+ let res = match[1].replace(trimEnd, '');
95
96
  let sep = ' ';
96
- let pos = first.lastIndex;
97
- line.lastIndex = pos;
97
+ let pos = line.lastIndex;
98
98
  while ((match = line.exec(source))) {
99
- if (match[1] === '') {
99
+ const lm = match[1].replace(trimBoth, '');
100
+ if (lm === '') {
100
101
  if (sep === '\n')
101
102
  res += sep;
102
103
  else
103
104
  sep = '\n';
104
105
  }
105
106
  else {
106
- res += sep + match[1];
107
+ res += sep + lm;
107
108
  sep = ' ';
108
109
  }
109
110
  pos = line.lastIndex;