Initial DDC release
This commit is contained in:
commit
434f6441da
57 files changed
+4606
No files matched your search
Executable
+106
@@ -0,0 +1,106 @@
|
||||
package extract
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type entryWriter struct {
|
||||
ctx context.Context
|
||||
root string
|
||||
limits Limits
|
||||
progress Progress
|
||||
written int64
|
||||
entries int
|
||||
}
|
||||
|
||||
func (writer *entryWriter) writeDirectory(name string) error {
|
||||
target, err := writer.prepare(name)
|
||||
if err != nil || target == "" {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(target, 0o755)
|
||||
}
|
||||
|
||||
func (writer *entryWriter) writeFile(name string, content io.Reader) error {
|
||||
target, err := writer.prepare(name)
|
||||
if err != nil || target == "" {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remaining := writer.limits.MaxBytes - writer.written
|
||||
source := &progressReader{ctx: writer.ctx, reader: io.LimitReader(content, remaining+1), progress: writer.progress}
|
||||
copied, copyErr := io.Copy(file, source)
|
||||
closeErr := file.Close()
|
||||
writer.written += copied
|
||||
|
||||
if copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
if writer.written > writer.limits.MaxBytes {
|
||||
return ErrTooLarge
|
||||
}
|
||||
return closeErr
|
||||
}
|
||||
|
||||
func (writer *entryWriter) prepare(name string) (string, error) {
|
||||
writer.entries++
|
||||
if writer.entries > writer.limits.MaxEntries {
|
||||
return "", ErrTooManyEntries
|
||||
}
|
||||
if err := writer.ctx.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return writer.resolve(name)
|
||||
}
|
||||
|
||||
func (writer *entryWriter) resolve(name string) (string, error) {
|
||||
normalized := strings.ReplaceAll(name, "\\", "/")
|
||||
if strings.HasPrefix(normalized, "/") {
|
||||
return "", fmt.Errorf("%w: %s", ErrUnsafeEntry, name)
|
||||
}
|
||||
|
||||
cleaned := filepath.Clean(filepath.FromSlash(normalized))
|
||||
if cleaned == "." {
|
||||
return "", nil
|
||||
}
|
||||
if !filepath.IsLocal(cleaned) {
|
||||
return "", fmt.Errorf("%w: %s", ErrUnsafeEntry, name)
|
||||
}
|
||||
|
||||
target := filepath.Join(writer.root, cleaned)
|
||||
relative, err := filepath.Rel(writer.root, target)
|
||||
if err != nil || !filepath.IsLocal(relative) {
|
||||
return "", fmt.Errorf("%w: %s", ErrUnsafeEntry, name)
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
type progressReader struct {
|
||||
ctx context.Context
|
||||
reader io.Reader
|
||||
progress Progress
|
||||
}
|
||||
|
||||
func (reader *progressReader) Read(buffer []byte) (int, error) {
|
||||
if err := reader.ctx.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count, err := reader.reader.Read(buffer)
|
||||
if count > 0 {
|
||||
reader.progress.Add(int64(count))
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
Reference in new issue
Block a user