Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f94a813d79 | ||
|
|
ca785f5648 | ||
|
|
91757752b8 | ||
|
|
bb2c5e200c | ||
|
|
8474b8eb02 | ||
|
|
cf6e52bcc5 | ||
|
|
3a8f528693 | ||
|
|
ed22579bed |
@@ -26,7 +26,10 @@ type Attachment struct {
|
|||||||
SizeHuman string `json:"-"` // template-only: human-readable size
|
SizeHuman string `json:"-"` // template-only: human-readable size
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxFilenameLen = 255
|
// MaxFilenameLen caps stored attachment filenames (bytes) to bound DB
|
||||||
|
// rows and Content-Disposition echoes. 128 keeps names readable while
|
||||||
|
// stopping filename-bloat abuse; longer names truncate.
|
||||||
|
const MaxFilenameLen = 128
|
||||||
|
|
||||||
// ErrFileTooLarge is returned when an attachment exceeds the per-file cap.
|
// ErrFileTooLarge is returned when an attachment exceeds the per-file cap.
|
||||||
var ErrFileTooLarge = errors.New("file too large")
|
var ErrFileTooLarge = errors.New("file too large")
|
||||||
|
|||||||
@@ -105,4 +105,9 @@ func TestSanitizeFilename(t *testing.T) {
|
|||||||
if got := SanitizeFilename(long); len(got) != MaxFilenameLen {
|
if got := SanitizeFilename(long); len(got) != MaxFilenameLen {
|
||||||
t.Errorf("long name len = %d want %d", len(got), MaxFilenameLen)
|
t.Errorf("long name len = %d want %d", len(got), MaxFilenameLen)
|
||||||
}
|
}
|
||||||
|
// issue #248: a 250-char multipart filename must truncate to the cap
|
||||||
|
repro := strings.Repeat("b", 246) + ".txt"
|
||||||
|
if got := SanitizeFilename(repro); len(got) != MaxFilenameLen {
|
||||||
|
t.Errorf("repro name len = %d want %d", len(got), MaxFilenameLen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ var reservedSlugs = map[string]bool{
|
|||||||
"api": true, "raw": true, "can": true, "cans": true, "public": true,
|
"api": true, "raw": true, "can": true, "cans": true, "public": true,
|
||||||
"history": true, "static": true, "assets": true, "favicon.ico": true,
|
"history": true, "static": true, "assets": true, "favicon.ico": true,
|
||||||
"new": true, "login": true, "logout": true, "admin": true, "settings": true,
|
"new": true, "login": true, "logout": true, "admin": true, "settings": true,
|
||||||
|
"mine": true, "unlock": true, "guess": true, "f": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalidSlug = errors.New("custom slug must be 1-64 chars: letters, digits, dash, underscore; must start with letter or digit")
|
var ErrInvalidSlug = errors.New("custom slug must be 1-64 chars: letters, digits, dash, underscore; must start with letter or digit")
|
||||||
|
|||||||
@@ -299,8 +299,14 @@ html[data-wrap] .float { overflow-x: hidden; }
|
|||||||
.code-head .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
|
.code-head .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
|
||||||
.code {
|
.code {
|
||||||
font-family: var(--font-mono); font-size: var(--code-fs); line-height: var(--code-lh);
|
font-family: var(--font-mono); font-size: var(--code-fs); line-height: var(--code-lh);
|
||||||
padding: 14px 0; display: flex; overflow-x: auto;
|
padding: 14px 0; display: flex; overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
/* #261: horizontal scroll must live on the codebody, not the .code flex
|
||||||
|
container — a container-level scroll takes the gutter with it when the
|
||||||
|
user scrolls long lines. The gutter sits OUTSIDE the scroll container and
|
||||||
|
stays visible; the codebody shrinks to the remaining space and scrolls
|
||||||
|
(min-width: 0 lets it shrink below its content width inside the flex row). */
|
||||||
|
.code .codebody { flex: 1 1 auto; min-width: 0; overflow-x: auto; }
|
||||||
/* #167: the gutter must not drive the flex layout — its content width
|
/* #167: the gutter must not drive the flex layout — its content width
|
||||||
(row count × number width) shrinks the code column, which re-wraps lines,
|
(row count × number width) shrinks the code column, which re-wraps lines,
|
||||||
which grows the gutter: a feedback loop. Pin the gutter with a fixed
|
which grows the gutter: a feedback loop. Pin the gutter with a fixed
|
||||||
@@ -310,7 +316,7 @@ html[data-wrap] .float { overflow-x: hidden; }
|
|||||||
.code .gutter { flex-shrink: 0; }
|
.code .gutter { flex-shrink: 0; }
|
||||||
/* gutter/code share line metrics; the editor gutter keeps its own padding (#50) */
|
/* gutter/code share line metrics; the editor gutter keeps its own padding (#50) */
|
||||||
.code .gutter { padding-top: 0; padding-bottom: 0; }
|
.code .gutter { padding-top: 0; padding-bottom: 0; }
|
||||||
.codebody { padding: 0 18px; white-space: pre; }
|
.codebody { padding: 0 18px; white-space: pre; overflow-x: auto; }
|
||||||
/* #167: each logical line is its own block so offsetTop identifies its first visual row */
|
/* #167: each logical line is its own block so offsetTop identifies its first visual row */
|
||||||
.codeline { display: block; }
|
.codeline { display: block; }
|
||||||
/* #167 rev: gutter number spans must stack one per visual row (wrap on) */
|
/* #167 rev: gutter number spans must stack one per visual row (wrap on) */
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if not .AttachmentImage}}
|
{{if not .Attachment}}
|
||||||
<div class="float">
|
<div class="float">
|
||||||
<div class="code" id="code"><div class="gutter" id="gutter">{{.Gutter}}</div><div class="codebody" id="codebody">{{.ContentHTML}}</div></div>
|
<div class="code" id="code"><div class="gutter" id="gutter">{{.Gutter}}</div><div class="codebody" id="codebody">{{.ContentHTML}}</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user