tina4ruby 3.13.97 → 3.13.99
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -0
- data/lib/tina4/ai.rb +32 -3
- data/lib/tina4/api.rb +5 -0
- data/lib/tina4/auto_crud.rb +62 -4
- data/lib/tina4/background.rb +112 -31
- data/lib/tina4/cache.rb +3 -2
- data/lib/tina4/cli.rb +55 -67
- data/lib/tina4/database.rb +97 -49
- data/lib/tina4/database_adapter.rb +169 -15
- data/lib/tina4/dev_admin.rb +137 -9
- data/lib/tina4/dispatch_pipeline.rb +145 -4
- data/lib/tina4/drivers/firebird_driver.rb +59 -12
- data/lib/tina4/drivers/mongodb_driver.rb +98 -14
- data/lib/tina4/drivers/mssql_driver.rb +39 -2
- data/lib/tina4/drivers/mysql_driver.rb +43 -3
- data/lib/tina4/drivers/odbc_driver.rb +36 -2
- data/lib/tina4/drivers/postgres_driver.rb +5 -0
- data/lib/tina4/drivers/sqlite_driver.rb +11 -1
- data/lib/tina4/env.rb +1 -1
- data/lib/tina4/error_overlay.rb +43 -49
- data/lib/tina4/field_types.rb +33 -16
- data/lib/tina4/frond.rb +24 -2
- data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +1 -1
- data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +1 -1
- data/lib/tina4/graphql.rb +2 -2
- data/lib/tina4/log.rb +652 -485
- data/lib/tina4/mcp.rb +9 -1
- data/lib/tina4/messenger.rb +25 -0
- data/lib/tina4/middleware.rb +189 -76
- data/lib/tina4/migration.rb +47 -15
- data/lib/tina4/orm.rb +280 -59
- data/lib/tina4/port_takeover.rb +202 -0
- data/lib/tina4/public/js/tina4-dev-admin.min.js +23 -19
- data/lib/tina4/rack_app.rb +201 -59
- data/lib/tina4/realtime.rb +6 -1
- data/lib/tina4/request.rb +259 -51
- data/lib/tina4/router.rb +20 -2
- data/lib/tina4/seeder.rb +68 -19
- data/lib/tina4/shutdown.rb +4 -0
- data/lib/tina4/sql_translator.rb +115 -86
- data/lib/tina4/swagger.rb +19 -3
- data/lib/tina4/template.rb +61 -6
- data/lib/tina4/test_client.rb +49 -3
- data/lib/tina4/testing.rb +16 -11
- data/lib/tina4/validator.rb +7 -1
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +28 -40
- data/lib/tina4.rb +12 -1
- metadata +3 -19
- data/lib/tina4/scss/tina4css/_alerts.scss +0 -34
- data/lib/tina4/scss/tina4css/_badges.scss +0 -22
- data/lib/tina4/scss/tina4css/_buttons.scss +0 -69
- data/lib/tina4/scss/tina4css/_cards.scss +0 -49
- data/lib/tina4/scss/tina4css/_forms.scss +0 -156
- data/lib/tina4/scss/tina4css/_grid.scss +0 -81
- data/lib/tina4/scss/tina4css/_modals.scss +0 -84
- data/lib/tina4/scss/tina4css/_nav.scss +0 -149
- data/lib/tina4/scss/tina4css/_pagination.scss +0 -63
- data/lib/tina4/scss/tina4css/_reset.scss +0 -94
- data/lib/tina4/scss/tina4css/_tables.scss +0 -54
- data/lib/tina4/scss/tina4css/_typography.scss +0 -55
- data/lib/tina4/scss/tina4css/_utilities.scss +0 -208
- data/lib/tina4/scss/tina4css/_variables.scss +0 -117
- data/lib/tina4/scss/tina4css/base.scss +0 -1
- data/lib/tina4/scss/tina4css/colors.scss +0 -48
- data/lib/tina4/scss/tina4css/tina4.scss +0 -18
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -3,6 +3,7 @@ require "json"
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
require "time"
|
|
5
5
|
require "uri"
|
|
6
|
+
require "cgi"
|
|
6
7
|
|
|
7
8
|
module Tina4
|
|
8
9
|
# Middleware wrapper that tags requests arriving on the AI dev port.
|
|
@@ -31,7 +32,11 @@ module Tina4
|
|
|
31
32
|
attr_accessor :current
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
# ONE static search set + order across the four frameworks
|
|
36
|
+
# (ST-SEARCHDIR-DIVERGE): the app's public then src/public. TINA4_PUBLIC_DIR
|
|
37
|
+
# is prepended per-request in #try_static, and the framework's bundled public
|
|
38
|
+
# dir is appended as the fallback in #initialize.
|
|
39
|
+
STATIC_DIRS = %w[public src/public].freeze
|
|
35
40
|
|
|
36
41
|
# CORS is now handled by Tina4::CorsMiddleware
|
|
37
42
|
|
|
@@ -80,11 +85,36 @@ module Tina4
|
|
|
80
85
|
Tina4::Router.websocket("/__dev_reload", &DEV_RELOAD_WS_HANDLER)
|
|
81
86
|
end
|
|
82
87
|
|
|
88
|
+
# Rack entry point. Establishes the per-request correlation id (feature 43),
|
|
89
|
+
# runs the dispatch pipeline, and echoes the id on the response.
|
|
90
|
+
def call(env)
|
|
91
|
+
# Honour a sanitized inbound X-Request-ID so a client or upstream service
|
|
92
|
+
# can thread its own id through - a CR/LF, over-long or illegal-charset
|
|
93
|
+
# value is rejected (never echoed) - else generate one. Thread it into the
|
|
94
|
+
# logger NOW (thread-local, so a Puma worker thread never sees another
|
|
95
|
+
# request's id), so every log line for this request carries it, and echo it
|
|
96
|
+
# on the response whatever outcome the pipeline produced (200/404/500/413).
|
|
97
|
+
request_id = Tina4::Log.sanitize_request_id(env["HTTP_X_REQUEST_ID"]) || SecureRandom.hex(4)
|
|
98
|
+
Tina4::Log.set_request_id(request_id)
|
|
99
|
+
|
|
100
|
+
begin
|
|
101
|
+
result = dispatch_pipeline(env)
|
|
102
|
+
result[1]["x-request-id"] = request_id if result.is_a?(Array) && result[1].is_a?(Hash)
|
|
103
|
+
result
|
|
104
|
+
ensure
|
|
105
|
+
# The request pipeline installs the id before its first log and
|
|
106
|
+
# clears it in `finally`/`ensure` after its last (Decision 12 /
|
|
107
|
+
# LOG-Q03), so an overlapping request on another thread can never
|
|
108
|
+
# observe a stale id from a request that already finished.
|
|
109
|
+
Tina4::Log.clear_request_id
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
83
113
|
# Run the dispatch pipeline. See REQUEST_STAGES / RESPONSE_STAGES above.
|
|
84
114
|
#
|
|
85
115
|
# Every branch this used to hold now lives in a named stage, so the only
|
|
86
116
|
# control flow left here is "walk the list, stop when a stage answers".
|
|
87
|
-
def
|
|
117
|
+
def dispatch_pipeline(env)
|
|
88
118
|
ctx = DispatchContext.new(
|
|
89
119
|
env: env,
|
|
90
120
|
method: env["REQUEST_METHOD"],
|
|
@@ -188,6 +218,12 @@ module Tina4
|
|
|
188
218
|
def try_static(path, env = nil)
|
|
189
219
|
return nil if path.include?("..")
|
|
190
220
|
|
|
221
|
+
# Security: never serve a dotfile (.env, .git/config, .htpasswd, ...).
|
|
222
|
+
# Reject any leading-dot segment in the REQUEST path before touching the
|
|
223
|
+
# filesystem; #confined_static_file re-checks the RESOLVED path so a symlink
|
|
224
|
+
# pointing AT a dotfile inside the public dir is refused too.
|
|
225
|
+
return nil if hidden_segment?(path)
|
|
226
|
+
|
|
191
227
|
# The framework ships the Swagger UI as STATIC assets under
|
|
192
228
|
# lib/tina4/public/swagger/. Static serving is independent of the gated
|
|
193
229
|
# /swagger handler, so without this check the UI stays reachable in
|
|
@@ -201,23 +237,52 @@ module Tina4
|
|
|
201
237
|
return nil
|
|
202
238
|
end
|
|
203
239
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
240
|
+
# TINA4_PUBLIC_DIR override is honoured first (ST-PUBLICDIR-ENV-PARTIAL),
|
|
241
|
+
# then the pre-computed project + framework roots.
|
|
242
|
+
roots = @static_roots
|
|
243
|
+
custom = ENV["TINA4_PUBLIC_DIR"]
|
|
244
|
+
roots = [custom] + roots if custom && !custom.empty?
|
|
245
|
+
|
|
246
|
+
roots.each do |root|
|
|
247
|
+
served = confined_static_file(root, path, env)
|
|
248
|
+
return served if served
|
|
209
249
|
|
|
210
250
|
# Only try index.html for directory-like paths
|
|
211
251
|
if path.end_with?("/") || !path.include?(".")
|
|
212
|
-
|
|
213
|
-
if
|
|
214
|
-
return serve_static_file(index_path, env)
|
|
215
|
-
end
|
|
252
|
+
served = confined_static_file(root, File.join(path, "index.html"), env)
|
|
253
|
+
return served if served
|
|
216
254
|
end
|
|
217
255
|
end
|
|
218
256
|
nil
|
|
219
257
|
end
|
|
220
258
|
|
|
259
|
+
# Serve a file under `root` ONLY when its REAL path stays confined under the
|
|
260
|
+
# real `root` (realpath + trailing separator, ADR-0050) and names no dotfile
|
|
261
|
+
# segment. File.realpath follows symlinks and collapses `..`, so a symlink
|
|
262
|
+
# pointing outside, a sibling-prefix dir (publicsecret) and a `..` escape all
|
|
263
|
+
# fail the containment a bare string check would miss. Returns the Rack
|
|
264
|
+
# response triple, or nil when the file is absent, escapes, or is hidden.
|
|
265
|
+
def confined_static_file(root, rel_path, env)
|
|
266
|
+
real_dir = File.realpath(root)
|
|
267
|
+
real_path = File.realpath(File.join(root, rel_path))
|
|
268
|
+
return nil unless File.file?(real_path)
|
|
269
|
+
return nil unless real_path.start_with?(real_dir + File::SEPARATOR)
|
|
270
|
+
|
|
271
|
+
relative = real_path[(real_dir.length + 1)..] || ""
|
|
272
|
+
return nil if hidden_segment?(relative)
|
|
273
|
+
|
|
274
|
+
serve_static_file(real_path, env)
|
|
275
|
+
rescue SystemCallError
|
|
276
|
+
# realpath raises when a path component is missing — a plain 404, not an error.
|
|
277
|
+
nil
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Whether any separator-delimited segment of `path` is hidden (begins with a
|
|
281
|
+
# dot). Refuses `.env`/`.git`; a `..` segment also begins with a dot.
|
|
282
|
+
def hidden_segment?(path)
|
|
283
|
+
path.split(File::SEPARATOR).any? { |segment| !segment.empty? && segment.start_with?(".") }
|
|
284
|
+
end
|
|
285
|
+
|
|
221
286
|
# Serve a static asset with cache-revalidation semantics (parity with the
|
|
222
287
|
# Python master). A static file may be cached by the browser but MUST be
|
|
223
288
|
# revalidated before use, so a redeployed asset reaches the browser on the
|
|
@@ -234,7 +299,11 @@ module Tina4
|
|
|
234
299
|
content_type = Tina4::Response::MIME_TYPES[ext] || "application/octet-stream"
|
|
235
300
|
|
|
236
301
|
stat = File.stat(full_path)
|
|
237
|
-
|
|
302
|
+
# Format PINNED across all four frameworks (feature 40, CE-DEC-02):
|
|
303
|
+
# decimal `W/"<size>-<mtime>"`, integer-second mtime - a client behind
|
|
304
|
+
# a reverse proxy sees an identical validator for the same file
|
|
305
|
+
# regardless of backend language.
|
|
306
|
+
etag = %(W/"#{stat.size}-#{stat.mtime.to_i}")
|
|
238
307
|
last_modified = stat.mtime.httpdate
|
|
239
308
|
|
|
240
309
|
headers = {
|
|
@@ -262,10 +331,10 @@ module Tina4
|
|
|
262
331
|
def static_not_modified?(env, etag, mtime)
|
|
263
332
|
if_none_match = env["HTTP_IF_NONE_MATCH"]
|
|
264
333
|
if if_none_match && !if_none_match.empty?
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return if_none_match
|
|
334
|
+
# Shared with the dynamic conditional-GET path (#etag_matches? in
|
|
335
|
+
# DispatchPipeline, feature 40) so both use IDENTICAL RFC-7232
|
|
336
|
+
# weak-comparison semantics.
|
|
337
|
+
return etag_matches?(if_none_match, etag)
|
|
269
338
|
end
|
|
270
339
|
|
|
271
340
|
if_modified_since = env["HTTP_IF_MODIFIED_SINCE"]
|
|
@@ -326,12 +395,42 @@ module Tina4
|
|
|
326
395
|
[200, { "content-type" => "application/json; charset=utf-8" }, [JSON.generate(Tina4::Swagger.generate)]]
|
|
327
396
|
end
|
|
328
397
|
|
|
329
|
-
|
|
330
|
-
|
|
398
|
+
# The ONE JSON error envelope for a negotiated 403/404/500 (ERR-DEC-02).
|
|
399
|
+
#
|
|
400
|
+
# Reuses the existing Tina4::Response.error_response envelope
|
|
401
|
+
# (error: true, code, message, status) already shared by app-level
|
|
402
|
+
# response.error() calls, plus request_id for correlation (feature 43,
|
|
403
|
+
# ERR-404-REQUESTID) - the SAME shape Python/PHP/Node build.
|
|
404
|
+
ERROR_CODE_NAMES = { 403 => "FORBIDDEN", 404 => "NOT_FOUND", 500 => "INTERNAL_SERVER_ERROR" }.freeze
|
|
405
|
+
|
|
406
|
+
def error_json_body(status, message, request_id)
|
|
407
|
+
Tina4::Response.error_response(
|
|
408
|
+
ERROR_CODE_NAMES[status] || "HTTP_#{status}", message, status
|
|
409
|
+
).merge(request_id: request_id)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Build the 403 a legacy per-route auth_handler gets. ERR-DEC-01/
|
|
413
|
+
# ERR-DEC-02: negotiated on Accept - a JSON API client gets the canonical
|
|
414
|
+
# JSON envelope directly (no template attempt at all); a browser gets the
|
|
415
|
+
# SAME errors/403.twig-or-fallback HTML path 404/500 use. The path is
|
|
416
|
+
# escaped before it ever reaches the template (Ruby's Frond does not
|
|
417
|
+
# auto-escape {{ }} output - same reflected-path XSS class feature 127
|
|
418
|
+
# already fixed for #handle_404; this closes the SAME gap on 403).
|
|
419
|
+
def handle_403(path = "", accept = "")
|
|
420
|
+
request_id = Tina4::Log.get_request_id || "-"
|
|
421
|
+
if Tina4::Template.wants_json?(accept)
|
|
422
|
+
return [403, { "content-type" => "application/json" }, [JSON.generate(error_json_body(403, "Forbidden", request_id))]]
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
body = begin
|
|
426
|
+
Tina4::Template.render_error(403, { "path" => CGI.escapeHTML(path.to_s), "request_id" => request_id })
|
|
427
|
+
rescue StandardError
|
|
428
|
+
"403 Forbidden"
|
|
429
|
+
end
|
|
331
430
|
[403, { "content-type" => "text/html" }, [body]]
|
|
332
431
|
end
|
|
333
432
|
|
|
334
|
-
def handle_404(path)
|
|
433
|
+
def handle_404(path, accept = "")
|
|
335
434
|
# Try serving a template file (e.g. /hello -> src/templates/pages/hello.twig)
|
|
336
435
|
template_response = try_serve_template(path)
|
|
337
436
|
return template_response if template_response
|
|
@@ -342,7 +441,25 @@ module Tina4
|
|
|
342
441
|
return render_landing_page if path == "/" && dev_mode?
|
|
343
442
|
|
|
344
443
|
Tina4::Log.warning("404 Not Found: #{path}")
|
|
345
|
-
|
|
444
|
+
request_id = Tina4::Log.get_request_id || "-"
|
|
445
|
+
|
|
446
|
+
# ERR-DEC-02: a JSON API client gets the JSON error body directly - no
|
|
447
|
+
# need to even try the HTML template. ERR-404-REQUESTID: the id now
|
|
448
|
+
# rides in the body too (the response header already carries it
|
|
449
|
+
# unconditionally, feature 43).
|
|
450
|
+
if Tina4::Template.wants_json?(accept)
|
|
451
|
+
return [404, { "content-type" => "application/json" }, [JSON.generate(error_json_body(404, "Not Found", request_id))]]
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# DEVADMIN-DEC-04 (feature 127): the error page reflects the request path
|
|
455
|
+
# into HTML and the Twig error template does NOT auto-escape, so a crafted
|
|
456
|
+
# /x<script> path would run script in the response origin. Escape it here
|
|
457
|
+
# (same reflected-request-path XSS class as the dev-toolbar fix).
|
|
458
|
+
body = begin
|
|
459
|
+
Tina4::Template.render_error(404, { "path" => CGI.escapeHTML(path.to_s), "request_id" => request_id })
|
|
460
|
+
rescue StandardError
|
|
461
|
+
"404 Not Found"
|
|
462
|
+
end
|
|
346
463
|
[404, { "content-type" => "text/html" }, [body]]
|
|
347
464
|
end
|
|
348
465
|
|
|
@@ -354,12 +471,6 @@ module Tina4
|
|
|
354
471
|
!%w[off false 0 no disabled].include?(val)
|
|
355
472
|
end
|
|
356
473
|
|
|
357
|
-
def should_show_landing_page?
|
|
358
|
-
# Check if any index template exists in src/templates/
|
|
359
|
-
templates_dir = File.join(@root_dir, "src", "templates")
|
|
360
|
-
%w[index.html index.twig index.erb].none? { |f| File.file?(File.join(templates_dir, f)) }
|
|
361
|
-
end
|
|
362
|
-
|
|
363
474
|
def try_serve_template(path)
|
|
364
475
|
tpl_file = resolve_template(path)
|
|
365
476
|
return nil unless tpl_file
|
|
@@ -431,18 +542,6 @@ module Tina4
|
|
|
431
542
|
cache
|
|
432
543
|
end
|
|
433
544
|
|
|
434
|
-
def try_serve_index_template
|
|
435
|
-
templates_dir = File.join(@root_dir, "src", "templates")
|
|
436
|
-
%w[index.html index.twig index.erb].each do |f|
|
|
437
|
-
path = File.join(templates_dir, f)
|
|
438
|
-
if File.file?(path)
|
|
439
|
-
body = Tina4::Template.render(f, {}) rescue File.read(path)
|
|
440
|
-
return [200, { "content-type" => "text/html" }, [body]]
|
|
441
|
-
end
|
|
442
|
-
end
|
|
443
|
-
nil
|
|
444
|
-
end
|
|
445
|
-
|
|
446
545
|
def render_landing_page
|
|
447
546
|
port = ENV["PORT"] || "7145"
|
|
448
547
|
|
|
@@ -687,23 +786,61 @@ module Tina4
|
|
|
687
786
|
end
|
|
688
787
|
end
|
|
689
788
|
|
|
789
|
+
# The canonical per-request id (set at the top of #call), so the id a
|
|
790
|
+
# user reports off the 500 page matches the log lines and the
|
|
791
|
+
# X-Request-ID response header - not a throwaway.
|
|
792
|
+
request_id = Tina4::Log.get_request_id || SecureRandom.hex(4)
|
|
793
|
+
accept = env && env["HTTP_ACCEPT"]
|
|
794
|
+
|
|
795
|
+
# v3.13.7 SECURITY (CWE-209): production response body must NOT contain the
|
|
796
|
+
# stack trace. The trace stays in Log.error above and reaches observability
|
|
797
|
+
# via the tina4.request.error event. error_message is ALWAYS empty here -
|
|
798
|
+
# never touch this: a JSON client's message field stays the generic
|
|
799
|
+
# "Server Error" too, never the real exception (ERR-DEC-02).
|
|
800
|
+
safe_response = lambda do
|
|
801
|
+
if Tina4::Template.wants_json?(accept)
|
|
802
|
+
next [500, { "content-type" => "application/json" }, [JSON.generate(error_json_body(500, "Server Error", request_id))]]
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
body = begin
|
|
806
|
+
Tina4::Template.render_error(500, { "error_message" => "", "request_id" => request_id })
|
|
807
|
+
rescue StandardError
|
|
808
|
+
"500 Internal Server Error"
|
|
809
|
+
end
|
|
810
|
+
[500, { "content-type" => "text/html" }, [body]]
|
|
811
|
+
end
|
|
812
|
+
|
|
690
813
|
if dev_mode?
|
|
691
|
-
#
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
#
|
|
695
|
-
#
|
|
696
|
-
#
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
"
|
|
700
|
-
|
|
814
|
+
# OVERLAY-DEC-03: guard the dev-overlay render. The call site sits INSIDE
|
|
815
|
+
# this rescue, so if the overlay itself throws (a malformed frame, an
|
|
816
|
+
# unrenderable request value) it would double-fault out of dispatch. Wrap it
|
|
817
|
+
# and fall back to the same safe production page, so a broken overlay still
|
|
818
|
+
# yields a bounded 500 — never a crash. The overlay itself is unaffected by
|
|
819
|
+
# content negotiation (feature 126, out of scope here; CWE-209 stays locked).
|
|
820
|
+
begin
|
|
821
|
+
body = Tina4::ErrorOverlay.render_error_overlay(error, request: env)
|
|
822
|
+
return [500, { "content-type" => "text/html" }, [body]]
|
|
823
|
+
rescue StandardError => overlay_err
|
|
824
|
+
begin
|
|
825
|
+
Tina4::Log.warning(
|
|
826
|
+
"Error overlay render failed, serving the safe page: " \
|
|
827
|
+
"#{overlay_err.class}: #{overlay_err.message}"
|
|
828
|
+
)
|
|
829
|
+
rescue StandardError
|
|
830
|
+
# Log failures must never block the 500 render.
|
|
831
|
+
end
|
|
832
|
+
return safe_response.call
|
|
833
|
+
end
|
|
701
834
|
end
|
|
702
|
-
|
|
835
|
+
|
|
836
|
+
safe_response.call
|
|
703
837
|
end
|
|
704
838
|
|
|
839
|
+
# OVERLAY-DEC-04: unify the debug gate on the overlay module's is_debug_mode so
|
|
840
|
+
# the error-overlay gate (and every other dev gate that reads dev_mode?) has ONE
|
|
841
|
+
# definition, instead of recomputing Env.is_truthy(TINA4_DEBUG) separately.
|
|
705
842
|
def dev_mode?
|
|
706
|
-
Tina4::
|
|
843
|
+
Tina4::ErrorOverlay.is_debug_mode
|
|
707
844
|
end
|
|
708
845
|
|
|
709
846
|
# Whether to emit a per-request log line (v3.13.14). TINA4_LOG_REQUESTS
|
|
@@ -780,9 +917,15 @@ module Tina4
|
|
|
780
917
|
|
|
781
918
|
def inject_dev_overlay(body, request_info, ai_port: false)
|
|
782
919
|
version = Tina4::VERSION
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
920
|
+
# DEVADMIN-DEC-04 (feature 127): the toolbar is injected into EVERY
|
|
921
|
+
# text/html response (including 404s), so the reflected request
|
|
922
|
+
# method/path/matched-pattern MUST be HTML-escaped or a crafted path
|
|
923
|
+
# reflects <script> that runs in the dev-server origin and can then drive
|
|
924
|
+
# every ungated /__dev mutation route. (Parity with PHP htmlspecialchars
|
|
925
|
+
# and the Python master html.escape; Ruby was reflecting them raw.)
|
|
926
|
+
method = CGI.escapeHTML(request_info[:method].to_s)
|
|
927
|
+
path = CGI.escapeHTML(request_info[:path].to_s)
|
|
928
|
+
matched_pattern = CGI.escapeHTML(request_info[:matched_pattern].to_s)
|
|
786
929
|
request_id = Tina4::Log.get_request_id || "-"
|
|
787
930
|
route_count = Tina4::Router.routes.length
|
|
788
931
|
|
|
@@ -1062,12 +1205,11 @@ module Tina4
|
|
|
1062
1205
|
end
|
|
1063
1206
|
|
|
1064
1207
|
def self._read_rack_body(env)
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
body
|
|
1208
|
+
# Route through the capped reader so the running per-chunk upload cap is
|
|
1209
|
+
# enforced on the form-token / body path too: an over-limit body raises
|
|
1210
|
+
# PayloadTooLarge (rescued into 413 by dispatch_pipeline) instead of being
|
|
1211
|
+
# read whole.
|
|
1212
|
+
Tina4::Request.read_stream_capped(env["rack.input"])
|
|
1071
1213
|
end
|
|
1072
1214
|
|
|
1073
1215
|
# Extract a formToken from the request body.
|
data/lib/tina4/realtime.rb
CHANGED
|
@@ -301,7 +301,12 @@ module Tina4
|
|
|
301
301
|
body = request.body
|
|
302
302
|
return body[name] if body.is_a?(Hash) && body.key?(name)
|
|
303
303
|
|
|
304
|
-
|
|
304
|
+
# Its only caller is on POST #{paths['files']}, a route with no path
|
|
305
|
+
# segments — a `request.params` fallback here would only ever have
|
|
306
|
+
# matched a route param that this path can never have, so it is not
|
|
307
|
+
# carried forward now that `params` is route-only (REQ-PARAM-POLLUTION,
|
|
308
|
+
# 3.13.99): body, then the query string, is the complete real source list.
|
|
309
|
+
request.query && request.query[name]
|
|
305
310
|
end
|
|
306
311
|
|
|
307
312
|
def parse_json(data)
|