OSDN Git Service

add package
[bytom/vapor.git] / vendor / github.com / ipfs / go-ipfs-files / multipartfile.go
diff --git a/vendor/github.com/ipfs/go-ipfs-files/multipartfile.go b/vendor/github.com/ipfs/go-ipfs-files/multipartfile.go
new file mode 100644 (file)
index 0000000..1768165
--- /dev/null
@@ -0,0 +1,236 @@
+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{}