Compare commits
2 Commits
e86c93cb73
...
472fa25bae
| Author | SHA1 | Date | |
|---|---|---|---|
| 472fa25bae | |||
| aab4f12fc4 |
+135
-135
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Kanban</title>
|
||||
<script type="module" crossorigin src="/assets/index-ZZWeneKP.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DT3pghXY.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-b0xjFtx2.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -197,7 +197,6 @@ func handleUploadCardFile(db *DB, workdir string) http.HandlerFunc {
|
||||
}
|
||||
|
||||
fname := safeFilename(header.Filename)
|
||||
fileID := newID()
|
||||
storedPath := filepath.Join(dir, randomFilePrefix()+"__"+fname)
|
||||
|
||||
out, err := os.Create(storedPath)
|
||||
@@ -237,16 +236,12 @@ func handleUploadCardFile(db *DB, workdir string) http.HandlerFunc {
|
||||
|
||||
actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
|
||||
|
||||
// Use the random-prefixed path on disk but a stable file id in the DB.
|
||||
cf, err := db.CreateCardFile(cardID, actor, fname, mimeType, storedPath, source, written)
|
||||
if err != nil {
|
||||
os.Remove(storedPath)
|
||||
serverError(w, err)
|
||||
return
|
||||
}
|
||||
// We generated newID() in CreateCardFile; align the on-disk filename with that id
|
||||
// is not required since stored_path is what we serve from.
|
||||
_ = fileID
|
||||
|
||||
infra.HTTPJSONResponse(w, http.StatusCreated, cf)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,24 @@ type Token = ImgToken | LinkToken | TextToken;
|
||||
|
||||
const TOKEN_RE = /(!\[([^\]\n]*)\]\(([^)\s]+)\))|(\[([^\]\n]+)\]\(([^)\s]+)\))/g;
|
||||
|
||||
// Allow only safe URL schemes. Reject javascript:, data:text/html, vbscript:, etc.
|
||||
// Accepts: absolute http(s), protocol-relative //, and same-origin paths (/...).
|
||||
function safeURL(url: string): string | null {
|
||||
const u = url.trim();
|
||||
if (u.startsWith("/")) return u;
|
||||
if (/^https?:\/\//i.test(u)) return u;
|
||||
return null;
|
||||
}
|
||||
|
||||
// data: scheme is allowed only when the MIME prefix is image/.
|
||||
function safeImageURL(url: string): string | null {
|
||||
const safe = safeURL(url);
|
||||
if (safe) return safe;
|
||||
const u = url.trim();
|
||||
if (/^data:image\/[a-z0-9.+-]+(;[a-z0-9-]+=[^,]+)*;base64,/i.test(u)) return u;
|
||||
return null;
|
||||
}
|
||||
|
||||
function tokenize(input: string): Token[] {
|
||||
const out: Token[] = [];
|
||||
let last = 0;
|
||||
@@ -24,9 +42,19 @@ function tokenize(input: string): Token[] {
|
||||
out.push({ kind: "text", value: input.slice(last, m.index) });
|
||||
}
|
||||
if (m[1]) {
|
||||
out.push({ kind: "img", alt: m[2] || "", url: m[3] });
|
||||
const url = safeImageURL(m[3]);
|
||||
if (url) {
|
||||
out.push({ kind: "img", alt: m[2] || "", url });
|
||||
} else {
|
||||
out.push({ kind: "text", value: m[0] });
|
||||
}
|
||||
} else if (m[4]) {
|
||||
out.push({ kind: "link", label: m[5], url: m[6] });
|
||||
const url = safeURL(m[6]);
|
||||
if (url) {
|
||||
out.push({ kind: "link", label: m[5], url });
|
||||
} else {
|
||||
out.push({ kind: "text", value: m[0] });
|
||||
}
|
||||
}
|
||||
last = TOKEN_RE.lastIndex;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user