package server import ( "os" "path" "serveg/internal/http" "serveg/internal/log" sh "serveg/internal/string_helper" ) const DateTimeTZ = "2006-01-02 15:04:05 UTC-07" func htmlEscape(s string) string { sb := sh.StringBuilder{Buf: make([]byte, 0, len(s))} for i := 0; i < len(s); i++ { b := s[i] switch b { case '&': sb.AddS("&") case '<': sb.AddS("<") case '>': sb.AddS(">") case '"': sb.AddS(""") case '\'': sb.AddS("'") default: sb.AddB(b) } } return sb.StringUnsafe() } type listingRow struct { name string // html-escaped url string // url-encoded mtime string // formatted with DateTimeTZ size string // formatted with HumanBytes isDir bool } func (conn *connection) serveDirListing(dirPath string) *http.Response { htmlBytes, err := genDirListingHtml(&conn.log, dirPath, conn.req.Url, conn.srv.Conf.HtmlTitle) if err != nil { return conn.createErrorResponse(false, http.StatusInternalServerError, err.Error()) } res := conn.http.CreateResponse(http.StatusOK, conn.req, false) res.AttachBodyBuf(htmlBytes, http.TextHtml) return res } func genDirListingHtml(logger *log.LoggerContext, dirPath, reqUrl, pageTitle string) ([]byte, error) { dirEntries, err := os.ReadDir(dirPath) if err != nil { return nil, err } var dirs, files []listingRow var estimatedLen = 460 // html for page without entries for _, e := range dirEntries { einf, err := e.Info() if err != nil { return nil, err } row := listingRow{ name: htmlEscape(e.Name()), url: http.PercentEcodeUrl(path.Join(reqUrl, e.Name())), mtime: einf.ModTime().Format(DateTimeTZ), isDir: e.IsDir(), } if row.isDir { row.size = "DIR" dirs = append(dirs, row) } else { row.size = sh.HumanBytes(einf.Size()) files = append(files, row) } estimatedLen += len(row.name) + len(row.url) + len(row.mtime) + len(row.size) estimatedLen += 100 // html for row } sb := sh.StringBuilder{Buf: make([]byte, 0, estimatedLen)} sb.AddS(`
| ../ |