package main import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "io" "mime" "os" "path/filepath" "sort" "strings" "time" ) // Share is a file the agent published for the clients to view // (reports, screenshots, generated pages...). type Share struct { ID string `json:"id"` Name string `json:"name"` Note string `json:"note,omitempty"` Session string `json:"session,omitempty"` Size int64 `json:"size"` Mime string `json:"mime"` Created time.Time `json:"created"` } type ShareStore struct { dir string cfg *Config } func NewShareStore(cfg *Config) (*ShareStore, error) { if err := os.MkdirAll(cfg.ShareDir, 0o755); err != nil { return nil, err } return &ShareStore{dir: cfg.ShareDir, cfg: cfg}, nil } func (s *ShareStore) metaPath(id string) string { return filepath.Join(s.dir, id+".meta.json") } func (s *ShareStore) filePath(id, name string) string { return filepath.Join(s.dir, id+"_"+filepath.Base(name)) } // Add stores content under a fresh id and writes a metadata sidecar. func (s *ShareStore) Add(name, note, session string, r io.Reader) (*Share, error) { name = filepath.Base(name) if !s.cfg.extAllowed(name) { return nil, fmt.Errorf("filtyp tillåts inte: %s (tillåtna: %s)", filepath.Ext(name), strings.Join(s.cfg.ShareExts, " ")) } idb := make([]byte, 6) if _, err := rand.Read(idb); err != nil { return nil, err } id := hex.EncodeToString(idb) f, err := os.Create(s.filePath(id, name)) if err != nil { return nil, err } size, err := io.Copy(f, r) f.Close() if err != nil { os.Remove(s.filePath(id, name)) return nil, err } sh := &Share{ ID: id, Name: name, Note: note, Session: session, Size: size, Mime: mime.TypeByExtension(strings.ToLower(filepath.Ext(name))), Created: time.Now(), } meta, _ := json.MarshalIndent(sh, "", " ") if err := os.WriteFile(s.metaPath(id), meta, 0o644); err != nil { return nil, err } return sh, nil } func (s *ShareStore) Get(id string) (*Share, error) { data, err := os.ReadFile(s.metaPath(id)) if err != nil { return nil, err } var sh Share if err := json.Unmarshal(data, &sh); err != nil { return nil, err } return &sh, nil } func (s *ShareStore) List() []*Share { matches, _ := filepath.Glob(filepath.Join(s.dir, "*.meta.json")) var out []*Share for _, m := range matches { data, err := os.ReadFile(m) if err != nil { continue } var sh Share if json.Unmarshal(data, &sh) == nil { out = append(out, &sh) } } sort.Slice(out, func(i, j int) bool { return out[i].Created.After(out[j].Created) }) return out } func (s *ShareStore) Delete(id string) error { sh, err := s.Get(id) if err != nil { return err } os.Remove(s.filePath(sh.ID, sh.Name)) return os.Remove(s.metaPath(id)) } // Path returns the on-disk path for serving. func (s *ShareStore) Path(sh *Share) string { return s.filePath(sh.ID, sh.Name) }