fix(notes): en note med tre prikker i titlen blev smidt væk ved importen
Release / Build and publish image (push) Failing after 9m54s
Release / Build and publish image (push) Failing after 9m54s
`skipFile` afviste ethvert navn, der indeholdt "..", for at stoppe en zip i at skrive uden for sin egen mappe. Det er den rigtige idé rettet mod det forkerte: det beskriver ikke en sti, der klatrer ud af arkivet, men ethvert filnavn med to prikker ved siden af hinanden. En ellipse i en overskrift er almindelig, og noten "Så blev det endelig jul! Stay tuned... ☕️" blev derfor aldrig oprettet. Lydløst, oven i købet: filen blev sprunget over, før den blev talt, så svaret sagde det samme, som hvis den var kommet med. Én note ud af tolv hundrede, og den blev kun fundet ved at tælle efter, hvor mange der kom ud i den anden ende — 1140 hvor der skulle stå 1141. Nu læses ".." som et stiled, ikke som to tegn, og en Markdown-fil, filteret afviser, tælles som sprunget over. En import, der taber noget, skal sige det. Og move-ikonet i sidebjælken er væk. ⠿ sad i den to pixels brede rende til venstre for teksten og lå oven i rækkens egen kant — idéen var rigtig, pladsen var det ikke. Elementet bliver: det er dét, der ejer trækket, og gjorde man i stedet hele rækken draggable, ville alt indeni også blive det, og en opgave trukket hen på I dag holdt op med at lande. Nu er håndtaget usynligt og lidt bredere, og hånden ved hover er hele forklaringen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9b4e7686f9
commit
2bd8e48aba
@@ -353,7 +353,16 @@ func (s *Server) handleImportNotes(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
for _, f := range zr.File {
|
||||
name := f.Name
|
||||
if f.FileInfo().IsDir() || skipFile(name) || !strings.EqualFold(path.Ext(name), ".md") {
|
||||
if f.FileInfo().IsDir() || !strings.EqualFold(path.Ext(name), ".md") {
|
||||
continue
|
||||
}
|
||||
// Counted, not passed over in silence. A Markdown file the filter refuses is
|
||||
// a note that did not arrive, and the caller is told the same number either
|
||||
// way unless it is said here — which is how one note went missing without
|
||||
// anything looking wrong.
|
||||
if skipFile(name) {
|
||||
s.log.Warn("import skipped a note file", "file", name)
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -409,10 +418,34 @@ func (s *Server) handleImportNotes(w http.ResponseWriter, r *http.Request) {
|
||||
// A zip made on a Mac carries a __MACOSX copy of every file, and importing those
|
||||
// would double a whole library with each copy full of binary rubbish.
|
||||
func skipFile(name string) bool {
|
||||
return strings.HasPrefix(name, "__MACOSX/") ||
|
||||
if strings.HasPrefix(name, "__MACOSX/") ||
|
||||
strings.HasPrefix(path.Base(name), "._") ||
|
||||
strings.HasPrefix(path.Base(name), ".") ||
|
||||
strings.Contains(name, "..")
|
||||
strings.HasPrefix(path.Base(name), ".") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Traversal, read as path segments rather than as a substring.
|
||||
//
|
||||
// This was `strings.Contains(name, "..")`, which is the right idea aimed at the
|
||||
// wrong thing: it does not describe a path that climbs out of the archive, it
|
||||
// describes any name with two dots next to each other anywhere in it. A note
|
||||
// called "Så blev det endelig jul! Stay tuned... ☕️" was therefore dropped —
|
||||
// silently, and without even counting as skipped, so the number the import
|
||||
// reported still looked right. One note in twelve hundred, found only by
|
||||
// counting what came out.
|
||||
//
|
||||
// An ellipsis in a title is ordinary. A segment that *is* "..", or a path that
|
||||
// starts at the root, is not — and those are what a zip uses to write outside
|
||||
// the folder it was unpacked into.
|
||||
if strings.HasPrefix(name, "/") {
|
||||
return true
|
||||
}
|
||||
for _, part := range strings.Split(name, "/") {
|
||||
if part == ".." {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// attachToNote records the files a note's text points at and rewrites the text to
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package httpapi
|
||||
|
||||
import "testing"
|
||||
|
||||
// En titel med tre prikker i er ikke et forsøg på at skrive uden for mappen.
|
||||
//
|
||||
// `strings.Contains(name, "..")` var den rigtige idé rettet mod det forkerte:
|
||||
// den beskriver ikke en sti, der klatrer ud af arkivet, men ethvert navn med to
|
||||
// prikker ved siden af hinanden. Noten "Så blev det endelig jul! Stay tuned... ☕️"
|
||||
// blev derfor droppet lydløst — én ud af tolv hundrede, og kun fundet ved at
|
||||
// tælle efter, hvor mange der kom ud.
|
||||
func TestSkipFileTakesEllipsesButRefusesTraversal(t *testing.T) {
|
||||
keep := []string{
|
||||
"Så blev det endelig jul! Stay tuned... ☕️.md",
|
||||
"Møde 12.8..md",
|
||||
"vedhaeftninger/1-2-Pasted Graphic.png",
|
||||
"noter/Referat....md",
|
||||
}
|
||||
for _, name := range keep {
|
||||
if skipFile(name) {
|
||||
t.Errorf("skipFile(%q) = true, men det er et almindeligt filnavn", name)
|
||||
}
|
||||
}
|
||||
|
||||
drop := []string{
|
||||
"../uden for.md",
|
||||
"noter/../../etc/passwd",
|
||||
"/etc/passwd",
|
||||
"__MACOSX/._noget",
|
||||
".skjult.md",
|
||||
"noter/._AppleDouble.md",
|
||||
}
|
||||
for _, name := range drop {
|
||||
if !skipFile(name) {
|
||||
t.Errorf("skipFile(%q) = false, men den skal afvises", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -508,14 +508,22 @@
|
||||
sidebar is already a drop target for tasks — a task dragged onto I dag
|
||||
stopped landing, because the row had claimed the gesture. A grip claims
|
||||
nothing until it is grabbed. -->
|
||||
<!-- Håndtaget har ingen prik i sig længere.
|
||||
⠿ sad i den to pixels brede rende til venstre for teksten og lå oven i
|
||||
rækkens egen kant — idéen var rigtig, pladsen var det ikke. Men elementet
|
||||
bliver: det er dét, der ejer trækket, og gjorde man i stedet hele rækken
|
||||
draggable, ville alt indeni også blive det — og en opgave trukket hen på
|
||||
I dag holdt op med at lande, fordi rækken havde taget gesten. Nu er
|
||||
håndtaget usynligt og lidt bredere, og hånden, der kommer ved hover,
|
||||
er hele forklaringen. -->
|
||||
{#snippet grip(key)}
|
||||
<span
|
||||
class="grip"
|
||||
draggable="true"
|
||||
ondragstart={(e) => onNavDragStart(e, key)}
|
||||
ondragend={clearNavDrag}
|
||||
aria-hidden="true">⠿</span
|
||||
>
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{/snippet}
|
||||
|
||||
{#snippet projectRow(project, sortable)}
|
||||
@@ -1004,19 +1012,15 @@
|
||||
so nothing moves when it appears. */
|
||||
.grip {
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
width: 12px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
color: var(--ink-faint);
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 14px;
|
||||
cursor: grab;
|
||||
opacity: 0;
|
||||
transition: opacity var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.navrow:hover .grip {
|
||||
opacity: 1;
|
||||
.grip:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.navrow > :global(a) {
|
||||
|
||||
Reference in New Issue
Block a user