diff --git a/database.go b/database.go index ff77a31..3b347b5 100644 --- a/database.go +++ b/database.go @@ -4,12 +4,39 @@ import ( "io/fs" "log" "os" + "slices" "strings" + + "golang.org/x/text/search" ) type Database struct { Keys []string Entries map[string]string + matcher *search.Matcher +} + +func (db *Database) search(query string) []string { // returns keys (entry names) + results := []string{} + // compile patterns + queryPatterns := []*search.Pattern{} + for _, q := range strings.Split(query, " ") { // per word + queryPatterns = append(queryPatterns, db.matcher.CompileString(q)) + } + // search + for _, k := range db.Keys { + patternsFound := 0 + for _, p := range queryPatterns { + if s, _ := p.IndexString(db.Entries[k]); s != -1 { + patternsFound++ // this pattern was found + } + } + if patternsFound == len(queryPatterns) && !slices.Contains(results, k) { + // if all patterns were found, add the key (entry name) to the list + results = append(results, k) + } + } + return results } func BuildDB(directory string) Database { @@ -19,14 +46,16 @@ func BuildDB(directory string) Database { var keys []string entries := map[string]string{} // get files in directory and read them - directory = strings.TrimRight(directory, "/") // we don't need that last / -> if '/' is used as directory, you are dumb. + directory = strings.TrimRight(directory, "/") // we don't need that last /, don't use the root directory / entriesDirFs := os.DirFS(directory) keys, err := fs.Glob(entriesDirFs, "*") if err != nil { logger.Panicln(err) } for _, k := range keys { contentB, err := os.ReadFile(directory + "/" + k) if err != nil { logger.Panicln(err) } - entries[k] = string(contentB) + content := string(contentB) + entries[k] = content } - return Database{Keys: keys, Entries: entries} + matcher := search.New(ContentLanguage, search.IgnoreCase, search.IgnoreDiacritics) + return Database{Keys: keys, Entries: entries, matcher: matcher} } diff --git a/go.mod b/go.mod index 510bb9a..baa456d 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/ChaoticByte/plaintext-encyclopedia go 1.22.4 + +require golang.org/x/text v0.17.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8d2179e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/main.go b/main.go index b3abde5..368deb4 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "log" "net/http" "os" + "path" "strings" ) @@ -28,16 +29,13 @@ func loadTemplate() { func handleApplication(w http.ResponseWriter, req *http.Request) { var entry string var err error - entryName := strings.Trim(req.URL.Path, "/") - if entryName != "" { - if strings.Contains(entryName, "/") || strings.Contains(entryName, "..") { - // path traversal - logger.Println("Possible path traversal attempt from", req.RemoteAddr, "to", entryName) - w.WriteHeader(http.StatusForbidden) - return - } + entryName := path.Base(req.URL.Path) + if entryName != "/" { // load entry entry = db.Entries[entryName] + if entry == "" { // redirect if entry doesn't exist (or is empty) + http.Redirect(w, req, "/", http.StatusTemporaryRedirect) + } } err = appTemplate.ExecuteTemplate( w, "app", @@ -45,6 +43,16 @@ func handleApplication(w http.ResponseWriter, req *http.Request) { if err != nil { logger.Println(err) } } +func handleSearchAPI(w http.ResponseWriter, req *http.Request) { + searchQuery := path.Base(req.URL.Path) + if searchQuery == "" { + w.WriteHeader(http.StatusBadRequest) + return + } + results := db.search(searchQuery) + w.Write([]byte(strings.Join(results, "\n"))) +} + func main() { // get logger logger = log.Default() @@ -57,6 +65,7 @@ func main() { http.Handle("/static/", staticHandler) // handle application http.HandleFunc("/", handleApplication) + http.HandleFunc("/search/", handleSearchAPI) // start server logger.Println("Starting server on", ServerListen) err := http.ListenAndServe(ServerListen, nil) diff --git a/public/index.html b/public/index.html index ea79cbf..9ae6588 100644 --- a/public/index.html +++ b/public/index.html @@ -17,11 +17,16 @@ {{- else -}} -