• Go Monk
  • Posts
  • Dot file hiding file server

Dot file hiding file server

A dialogue on Go interface embedding

The Scene: A quiet terminal room in the Temple of Code. A young apprentice, Novus, approaches the weathered yet kind mentor, Vetus. A scroll of Go code clutched tightly in hand.

Novus: I found this scroll buried in the library. It seems to guard against dot files, but its ways are strange. Will you help me decipher it?

Vetus (smiling): Let us journey through the scroll together.

Novus (reading aloud):

package main

import (
	"io"
	"io/fs"
	"log"
	"net/http"
	"strings"
)

Vetus: A fine start. All Go code consists of one or more packages - this scroll being a part of the executable main package. It calls upon the powers of I/O, file systems, logging, HTTP... and the strings of old. This tale is clearly one of web servers and files.

Now, what is the first function inscribed?

Novus: It is thus:

func containsDotFile(name string) bool {
	parts := strings.Split(name, "/")
	for _, part := range parts {
		if strings.HasPrefix(part, ".") {
			return true
		}
	}
	return false
}

Vetus: A seeker of secrets, that one. containsDotFile walks a path of slashes, peering into each segment of a file path. If any begins with a dot — the mark of the hidden — it raises the banner of truth: “Yes, a dot file lies here!”

Novus: So it recognizes hidden things merely by their names?

Vetus: Indeed. In Unix lands, a dot at the front renders a file invisible to casual eyes. Wise web servers must tread carefully.

Novus (continues reading):

type dotFileHidingFile struct {
	http.File
}

This part puzzles me. Why is http.File interface inside this custom type? It’s not given a name...

Vetus (chuckling): You’ve discovered embedding, a quiet power in Go. In this case it's interface embedding and it resembles struct embedding. By wrapping http.File anonymously in the new type dotFileHidingFile, we say: “We are like http.File, but we carry more.”

Novus: So all the methods of http.File still apply?

Vetus (eyes twinkling): Yes. The new type inherits the interface of its embedded field. But watch this next spell carefully:

func (f dotFileHidingFile) Readdir(n int) (fis []fs.FileInfo, err error) {
	files, err := f.File.Readdir(n)
	for _, file := range files {
		if !strings.HasPrefix(file.Name(), ".") {
			fis = append(fis, file)
		}
	}
	if err == nil && n > 0 && len(fis) == 0 {
		err = io.EOF
	}
	return
}

Here, we override the Readdir method — just this one — to hide files that begin with dots. A clever enchantment. It calls upon the original list of files, then prunes away the ones starting with a dot. If the request was to read, say, five files, but all were hidden... it gently returns an end-of-file signal. Polite, but firm.

Novus: So even a directory full of secrets appears empty to the unaware?

Vetus: Just so. What of the filesystem itself?

Novus: We come to this construct:

type dotFileHidingFileSystem struct {
	http.FileSystem
}

Vetus: A filesystem that wears a mask. It borrows the shape of the true one, but speaks with its own rules. What does it do when asked to open a path?

Novus:

func (fsys dotFileHidingFileSystem) Open(name string) (http.File, error) {
	if containsDotFile(name) {
		return nil, fs.ErrPermission
	}
	file, err := fsys.FileSystem.Open(name)
	if err != nil {
		return nil, err
	}
	return dotFileHidingFile{file}, err
}

Vetus: Just as I thought. When a seeker asks for a file with dots in its ancestry, like localhost:8080/.config or even localhost:8080/tmp/.config the filesystem refuses, saying: “You have no permission.” A 403, in HTTP tongue.

But if the name is clean, it hands back a cloaked file — one that hides dotfiles from view.

Novus: I see now. A guardian at every gate, from root to leaf.

And then... the final invocation.

func main() {
	fsys := dotFileHidingFileSystem{http.Dir(".")}
	http.Handle("/", http.FileServer(fsys))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Vetus: A file server, humble but vigilant. It shares the contents of the current directory through the profane port 8080. Yet not all may pass. Hidden files — passwords, config incantations, or ancient notes — are kept from prying eyes.

Novus (bowing): Thank you, Vetus. I understand now. This code protects the temple's secret scrolls from idle wanderers.