feat(bookfile): add Contains and OpenReaderAt utilities
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
package bookfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenReaderAt opens a book file and returns a ReaderAt + size.
|
||||||
|
// Consolidates the 3 places that open a book file + stat + get ReaderAt.
|
||||||
|
func OpenReaderAt(root, rel string) (*os.File, int64, error) {
|
||||||
|
abs := filepath.Join(root, filepath.FromSlash(rel))
|
||||||
|
f, err := os.Open(abs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
st, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return f, st.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether child is inside parent using EvalSymlinks semantics.
|
||||||
|
// Falls back to Clean if EvalSymlinks fails (e.g., path doesn't exist yet).
|
||||||
|
func Contains(parent, child string) bool {
|
||||||
|
p, err := filepath.EvalSymlinks(parent)
|
||||||
|
if err != nil {
|
||||||
|
p = filepath.Clean(parent)
|
||||||
|
}
|
||||||
|
c, err := filepath.EvalSymlinks(child)
|
||||||
|
if err != nil {
|
||||||
|
c = filepath.Clean(child)
|
||||||
|
}
|
||||||
|
return c == p || strings.HasPrefix(c, p+string(os.PathSeparator))
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package bookfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContains(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
sub := filepath.Join(dir, "sub")
|
||||||
|
if err := os.MkdirAll(sub, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !Contains(dir, sub) {
|
||||||
|
t.Fatal("sub should be inside dir")
|
||||||
|
}
|
||||||
|
if Contains(sub, dir) {
|
||||||
|
t.Fatal("dir should not be inside sub")
|
||||||
|
}
|
||||||
|
if Contains(dir, "/completely/different") {
|
||||||
|
t.Fatal("unrelated path should not be inside dir")
|
||||||
|
}
|
||||||
|
// Same path.
|
||||||
|
if !Contains(dir, dir) {
|
||||||
|
t.Fatal("dir should contain itself")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenReaderAt(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
// Create a test file.
|
||||||
|
path := filepath.Join(dir, "test.txt")
|
||||||
|
if err := os.WriteFile(path, []byte("hello"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, size, err := OpenReaderAt(dir, "test.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if size != 5 {
|
||||||
|
t.Fatalf("size = %d, want 5", size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-existent file.
|
||||||
|
_, _, err = OpenReaderAt(dir, "nope.txt")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for non-existent file")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user