package main import ( "encoding/json" "fmt" "net/http" "os" "os/exec" "path/filepath" "strings" "time" ) func jailPath(wwwdata string, path string) (string, error) { base, err := filepath.Abs(wwwdata) if err != nil { return "", err } // Join and clean the path joined := filepath.Join(base, path) clean := filepath.Clean(joined) // Ensure it's inside base, otherwise clamp to base if !strings.HasPrefix(clean+string(os.PathSeparator), base+string(os.PathSeparator)) { return base, nil } return clean, nil } func main() { wwwdata, err := os.Getwd() if err != nil { fmt.Fprintf(os.Stderr, "Error getting current working directory: %v\n", err) os.Exit(1) } if len(os.Args) > 1 { wwwdata = os.Args[1] } fmt.Fprintf(os.Stderr, "Serving files from: %s\n", wwwdata) // Wildcard route to handle all GET requests http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path // Log time, method, path, ip, user-agent to stderr fmt.Fprintf(os.Stderr, "%s - %s %s - %s - %s\n", time.Now().Format(time.RFC3339), r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent()) if path[len(path)-1] == '/' { path += "index.typ" } // Convert the path to absolute path filePath, err := jailPath(wwwdata, path) if err != nil { http.Error(w, "Internal Server Error: Failed to jail request", http.StatusInternalServerError) return } // Check if the file exists and is not a directory info, err := os.Stat(filePath) if os.IsNotExist(err) || info.IsDir() { // Check if a 404.typ file exists in the root directory path404 := filepath.Join(wwwdata, "404.typ") if _, err := os.Stat(path404); err == nil { filePath = path404 w.WriteHeader(http.StatusNotFound) } else { http.NotFound(w, r) return } } headers := make(map[string]string) for k, v := range r.Header { headers[strings.ToLower(k)] = strings.Join(v[:], ",") } query := make(map[string]string) for k, v := range r.URL.Query() { query[k] = strings.Join(v[:], ",") } // Format request data as json for typst input data := struct { Method string `json:"method"` Path string `json:"path"` Headers map[string]string `json:"headers"` Query map[string]string `json:"query"` Timestamp string `json:"timestamp"` Ip string `json:"ip,omitempty"` Host string `json:"host,omitempty"` }{ Method: r.Method, Path: r.URL.Path, Headers: headers, Query: query, Timestamp: time.Now().Format(time.RFC3339), Ip: r.RemoteAddr, Host: r.Host, } jsonData, err := json.Marshal(data) if err != nil { http.Error(w, "Internal Server Error: Failed to parse request data", http.StatusInternalServerError) return } jsonString := string(jsonData) fontsdir := filepath.Join(wwwdata, "fonts") // Compile the response cmd := exec.Command("typst", "compile", filePath, "--font-path", fontsdir, "--input", "request="+jsonString, "-") // Read stdout and send it to the response cmd.Stdout = w cmd.Stderr = os.Stderr w.Header().Set("Content-Type", "application/pdf") if err := cmd.Run(); err != nil { http.Error(w, "Internal Server Error: Failed to compile document", http.StatusInternalServerError) return } }) http.ListenAndServe(":8080", nil) }