package uploads import ( "bytes" "context" "crypto/sha256" "encoding/hex" "io" "os" "path/filepath" "time" "emperror.dev/errors" "lmika.dev/lmika/weiro/models" ) type NewPendingRequest struct { FileSize int64 `json:"size"` Filename string `json:"name"` MIMEType string `json:"type"` } func (s *Service) NewPending(ctx context.Context, req NewPendingRequest) (models.PendingUpload, error) { site, user, err := s.fetchSiteAndUser(ctx) if err != nil { return models.PendingUpload{}, err } pending := models.PendingUpload{ GUID: models.NewNanoID(), SiteID: site.ID, UserID: user.ID, FileSize: req.FileSize, Filename: req.Filename, MIMEType: req.MIMEType, UploadStarted: time.Now(), } if err := s.db.SavePendingUpload(ctx, &pending); err != nil { return models.PendingUpload{}, err } if err := os.MkdirAll(s.pendingDir, 0755); err != nil { return models.PendingUpload{}, err } pendingDataFile, err := os.Create(filepath.Join(s.pendingDir, pending.GUID+".upload")) if err != nil { return models.PendingUpload{}, err } return pending, pendingDataFile.Close() } func (s *Service) WriteToPending(ctx context.Context, pendingGUID string, data []byte) error { site, user, err := s.fetchSiteAndUser(ctx) if err != nil { return err } pu, err := s.db.SelectPendingUploadByGUID(ctx, pendingGUID) if err != nil { return err } else if pu.SiteID != site.ID || pu.UserID != user.ID { return errors.New("invalid pending upload") } pendingDataFilename := filepath.Join(s.pendingDir, pu.GUID+".upload") if _, err := os.Stat(pendingDataFilename); err != nil { return err } pendingDataFile, err := os.OpenFile(pendingDataFilename, os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return err } defer pendingDataFile.Close() pendingDataFile.Seek(0, io.SeekEnd) if _, err := pendingDataFile.Write(data); err != nil { return err } return nil } func (s *Service) FinalizePending(ctx context.Context, pendingGUID string, expectedHash string) error { site, user, err := s.fetchSiteAndUser(ctx) if err != nil { return err } pu, err := s.db.SelectPendingUploadByGUID(ctx, pendingGUID) if err != nil { return err } else if pu.SiteID != site.ID || pu.UserID != user.ID { return errors.New("invalid pending upload") } expectedHashBytes, err := hex.DecodeString(expectedHash) if err != nil { return err } pendingDataFilename := filepath.Join(s.pendingDir, pu.GUID+".upload") if _, err := os.Stat(pendingDataFilename); err != nil { return err } pendingDataFile, err := os.Open(pendingDataFilename) if err != nil { return err } defer pendingDataFile.Close() shaSum := sha256.New() if _, err := io.Copy(shaSum, pendingDataFile); err != nil { return err } if !bytes.Equal(shaSum.Sum(nil), expectedHashBytes) { return errors.New("hash mismatch") } return nil }