Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bd8e48aba |
@@ -353,7 +353,16 @@ func (s *Server) handleImportNotes(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
for _, f := range zr.File {
|
for _, f := range zr.File {
|
||||||
name := f.Name
|
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
|
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
|
// 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.
|
// would double a whole library with each copy full of binary rubbish.
|
||||||
func skipFile(name string) bool {
|
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.HasPrefix(path.Base(name), ".") ||
|
strings.HasPrefix(path.Base(name), ".") {
|
||||||
strings.Contains(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
|
// 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
|
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
|
stopped landing, because the row had claimed the gesture. A grip claims
|
||||||
nothing until it is grabbed. -->
|
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)}
|
{#snippet grip(key)}
|
||||||
<span
|
<span
|
||||||
class="grip"
|
class="grip"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
ondragstart={(e) => onNavDragStart(e, key)}
|
ondragstart={(e) => onNavDragStart(e, key)}
|
||||||
ondragend={clearNavDrag}
|
ondragend={clearNavDrag}
|
||||||
aria-hidden="true">⠿</span
|
aria-hidden="true"
|
||||||
>
|
></span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet projectRow(project, sortable)}
|
{#snippet projectRow(project, sortable)}
|
||||||
@@ -1004,19 +1012,15 @@
|
|||||||
so nothing moves when it appears. */
|
so nothing moves when it appears. */
|
||||||
.grip {
|
.grip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -2px;
|
left: 0;
|
||||||
width: 12px;
|
top: 0;
|
||||||
text-align: center;
|
bottom: 0;
|
||||||
font-size: 10px;
|
width: 14px;
|
||||||
line-height: 1;
|
|
||||||
color: var(--ink-faint);
|
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
opacity: 0;
|
|
||||||
transition: opacity var(--fast) var(--ease);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navrow:hover .grip {
|
.grip:active {
|
||||||
opacity: 1;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navrow > :global(a) {
|
.navrow > :global(a) {
|
||||||
|
|||||||
Reference in New Issue
Block a user