+++ /dev/null
-package files
-
-import (
- "io"
- "io/ioutil"
- "mime"
- "mime/multipart"
- "net/url"
- "path"
- "strings"
-)
-
-const (
- multipartFormdataType = "multipart/form-data"
- multipartMixedType = "multipart/mixed"
-
- applicationDirectory = "application/x-directory"
- applicationSymlink = "application/symlink"
- applicationFile = "application/octet-stream"
-
- contentTypeHeader = "Content-Type"
-)
-
-type multipartDirectory struct {
- path string
- walker *multipartWalker
-
- // part is the part describing the directory. It's nil when implicit.
- part *multipart.Part
-}
-
-type multipartWalker struct {
- part *multipart.Part
- reader *multipart.Reader
-}
-
-func (m *multipartWalker) consumePart() {
- m.part = nil
-}
-
-func (m *multipartWalker) getPart() (*multipart.Part, error) {
- if m.part != nil {
- return m.part, nil
- }
- if m.reader == nil {
- return nil, io.EOF
- }
-
- var err error
- m.part, err = m.reader.NextPart()
- if err == io.EOF {
- m.reader = nil
- }
- return m.part, err
-}
-
-func NewFileFromPartReader(reader *multipart.Reader, mediatype string) (Directory, error) {
- if !isDirectory(mediatype) {
- return nil, ErrNotDirectory
- }
-
- return &multipartDirectory{
- path: "/",
- walker: &multipartWalker{
- reader: reader,
- },
- }, nil
-}
-
-func (w *multipartWalker) nextFile() (Node, error) {
- part, err := w.getPart()
- if err != nil {
- return nil, err
- }
- w.consumePart()
-
- contentType := part.Header.Get(contentTypeHeader)
- switch contentType {
- case applicationSymlink:
- out, err := ioutil.ReadAll(part)
- if err != nil {
- return nil, err
- }
-
- return NewLinkFile(string(out), nil), nil
- case "": // default to application/octet-stream
- fallthrough
- case applicationFile:
- return &ReaderFile{
- reader: part,
- abspath: part.Header.Get("abspath"),
- }, nil
- }
-
- mediatype, _, err := mime.ParseMediaType(contentType)
- if err != nil {
- return nil, err
- }
-
- if !isDirectory(mediatype) {
- return &ReaderFile{
- reader: part,
- abspath: part.Header.Get("abspath"),
- }, nil
- }
-
- return &multipartDirectory{
- part: part,
- path: fileName(part),
- walker: w,
- }, nil
-}
-
-// fileName returns a normalized filename from a part.
-func fileName(part *multipart.Part) string {
- filename := part.FileName()
- if escaped, err := url.QueryUnescape(filename); err == nil {
- filename = escaped
- } // if there is a unescape error, just treat the name as unescaped
-
- return path.Clean("/" + filename)
-}
-
-// dirName appends a slash to the end of the filename, if not present.
-// expects a _cleaned_ path.
-func dirName(filename string) string {
- if !strings.HasSuffix(filename, "/") {
- filename += "/"
- }
- return filename
-}
-
-// isDirectory checks if the media type is a valid directory media type.
-func isDirectory(mediatype string) bool {
- return mediatype == multipartFormdataType || mediatype == applicationDirectory
-}
-
-// isChild checks if child is a child of parent directory.
-// expects a _cleaned_ path.
-func isChild(child, parent string) bool {
- return strings.HasPrefix(child, dirName(parent))
-}
-
-// makeRelative makes the child path relative to the parent path.
-// expects a _cleaned_ path.
-func makeRelative(child, parent string) string {
- return strings.TrimPrefix(child, dirName(parent))
-}
-
-type multipartIterator struct {
- f *multipartDirectory
-
- curFile Node
- curName string
- err error
-}
-
-func (it *multipartIterator) Name() string {
- return it.curName
-}
-
-func (it *multipartIterator) Node() Node {
- return it.curFile
-}
-
-func (it *multipartIterator) Next() bool {
- if it.f.walker.reader == nil || it.err != nil {
- return false
- }
- var part *multipart.Part
- for {
- part, it.err = it.f.walker.getPart()
- if it.err != nil {
- return false
- }
-
- name := fileName(part)
-
- // Is the file in a different directory?
- if !isChild(name, it.f.path) {
- return false
- }
-
- // Have we already entered this directory?
- if it.curName != "" && isChild(name, path.Join(it.f.path, it.curName)) {
- it.f.walker.consumePart()
- continue
- }
-
- // Make the path relative to the current directory.
- name = makeRelative(name, it.f.path)
-
- // Check if we need to create a fake directory (more than one
- // path component).
- if idx := strings.IndexByte(name, '/'); idx >= 0 {
- it.curName = name[:idx]
- it.curFile = &multipartDirectory{
- path: path.Join(it.f.path, it.curName),
- walker: it.f.walker,
- }
- return true
- }
- it.curName = name
-
- // Finally, advance to the next file.
- it.curFile, it.err = it.f.walker.nextFile()
-
- return it.err == nil
- }
-}
-
-func (it *multipartIterator) Err() error {
- // We use EOF to signal that this iterator is done. That way, we don't
- // need to check every time `Next` is called.
- if it.err == io.EOF {
- return nil
- }
- return it.err
-}
-
-func (f *multipartDirectory) Entries() DirIterator {
- return &multipartIterator{f: f}
-}
-
-func (f *multipartDirectory) Close() error {
- if f.part != nil {
- return f.part.Close()
- }
- return nil
-}
-
-func (f *multipartDirectory) Size() (int64, error) {
- return 0, ErrNotSupported
-}
-
-var _ Directory = &multipartDirectory{}