package handler import ( "html/template" "net/url" "sort" "strconv" "strings" "time" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "github.com/docker/go-units" "github.com/valyala/fasthttp" "go.uber.org/zap" ) const ( dateFormat = "02-01-2006 15:04" attrOID = "OID" attrCreated = "Created" attrFileName = "FileName" attrSize = "Size" ) type ( BrowsePageData struct { BucketName, Prefix string Objects []ResponseObject } ResponseObject struct { OID string Created string FileName string Size string IsDir bool } ) func parseTimestamp(tstamp string) (time.Time, error) { millis, err := strconv.ParseInt(tstamp, 10, 64) if err != nil { return time.Time{}, err } return time.UnixMilli(millis), nil } func NewResponseObject(nodes map[string]string) ResponseObject { return ResponseObject{ OID: nodes[attrOID], Created: nodes[attrCreated], FileName: nodes[attrFileName], Size: nodes[attrSize], IsDir: nodes[attrOID] == "", } } func formatTimestamp(strdate string) string { date, err := parseTimestamp(strdate) if err != nil || date.IsZero() { return "" } return date.Format(dateFormat) } func formatSize(strsize string) string { size, err := strconv.ParseFloat(strsize, 64) if err != nil { return "0B" } return units.HumanSize(size) } func parentDir(prefix string) string { index := strings.LastIndex(prefix, "/") if index == -1 { return prefix } return prefix[index:] } func trimPrefix(encPrefix string) string { prefix, err := url.PathUnescape(encPrefix) if err != nil { return "" } slashIndex := strings.LastIndex(prefix, "/") if slashIndex == -1 { return "" } return prefix[:slashIndex] } func urlencode(prefix, filename string) string { var res strings.Builder path := filename if prefix != "" { path = strings.Join([]string{prefix, filename}, "/") } prefixParts := strings.Split(path, "/") for _, prefixPart := range prefixParts { prefixPart = "/" + url.PathEscape(prefixPart) if prefixPart == "/." || prefixPart == "/.." { prefixPart = url.PathEscape(prefixPart) } res.WriteString(prefixPart) } return res.String() } func (h *Handler) browseObjects(c *fasthttp.RequestCtx, bucketInfo *data.BucketInfo, prefix string) { ctx := utils.GetContextFromRequest(c) reqLog := utils.GetReqLogOrDefault(ctx, h.log) log := reqLog.With(zap.String("bucket", bucketInfo.Name)) nodes, err := h.listObjects(ctx, bucketInfo, prefix) if err != nil { logAndSendBucketError(c, log, err) return } respObjects := make([]ResponseObject, len(nodes)) for i, node := range nodes { respObjects[i] = NewResponseObject(node) } sort.Slice(respObjects, func(i, j int) bool { if respObjects[i].IsDir == respObjects[j].IsDir { return respObjects[i].FileName < respObjects[j].FileName } return respObjects[i].IsDir }) indexTemplate := h.config.IndexPageTemplate() tmpl, err := template.New("index").Funcs(template.FuncMap{ "formatTimestamp": formatTimestamp, "formatSize": formatSize, "trimPrefix": trimPrefix, "urlencode": urlencode, "parentDir": parentDir, }).Parse(indexTemplate) if err != nil { logAndSendBucketError(c, log, err) return } if err = tmpl.Execute(c, &BrowsePageData{ BucketName: bucketInfo.Name, Prefix: prefix, Objects: respObjects, }); err != nil { logAndSendBucketError(c, log, err) return } }