2012-12-17 04:41:06 +00:00
|
|
|
// +build THIS_IS_BROKEN
|
|
|
|
|
2011-08-07 01:46:37 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"encoding/xml"
|
2011-08-07 01:46:37 +00:00
|
|
|
"flag"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2011-08-07 01:46:37 +00:00
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"camlistore.org/pkg/blobref"
|
|
|
|
"camlistore.org/pkg/blobserver/localdisk"
|
|
|
|
"camlistore.org/pkg/cacher"
|
|
|
|
"camlistore.org/pkg/client"
|
|
|
|
"camlistore.org/pkg/fs"
|
2012-12-17 04:41:06 +00:00
|
|
|
|
|
|
|
// "camlistore.org/third_party/github.com/hanwen/go-fuse/fuse"
|
2011-08-07 01:46:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
f *fs.CamliFileSystem
|
|
|
|
davaddr = flag.String("davaddr", "", "WebDAV service address")
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO(rh): tame copy/paste code from cammount
|
|
|
|
func main() {
|
2011-10-11 00:38:18 +00:00
|
|
|
client.AddFlags()
|
2011-08-07 01:46:37 +00:00
|
|
|
flag.Parse()
|
|
|
|
cacheDir, err := ioutil.TempDir("", "camlicache")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error creating temp cache directory: %v", err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(cacheDir)
|
|
|
|
diskcache, err := localdisk.New(cacheDir)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error setting up local disk cache: %v", err)
|
|
|
|
}
|
|
|
|
if flag.NArg() != 1 {
|
|
|
|
log.Fatal("usage: camwebdav <blobref>")
|
|
|
|
}
|
|
|
|
br := blobref.Parse(flag.Arg(0))
|
|
|
|
if br == nil {
|
|
|
|
log.Fatalf("%s was not a valid blobref.", flag.Arg(0))
|
|
|
|
}
|
|
|
|
client := client.NewOrFail()
|
|
|
|
fetcher := cacher.NewCachingFetcher(diskcache, client)
|
|
|
|
|
|
|
|
f = fs.NewCamliFileSystem(fetcher, br)
|
|
|
|
http.HandleFunc("/", webdav)
|
|
|
|
err = http.ListenAndServe(*davaddr, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error starting WebDAV server: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func webdav(w http.ResponseWriter, r *http.Request) {
|
|
|
|
switch r.Method {
|
|
|
|
case "GET":
|
|
|
|
get(w, r)
|
|
|
|
case "OPTIONS":
|
|
|
|
w.Header().Set("DAV", "1")
|
|
|
|
case "PROPFIND":
|
|
|
|
propfind(w, r)
|
|
|
|
default:
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sendHTTPStatus is an HTTP status code.
|
|
|
|
type sendHTTPStatus int
|
|
|
|
|
|
|
|
// senderr, when deferred, recovers panics of
|
|
|
|
// type sendHTTPStatus and writes the corresponding
|
|
|
|
// HTTP status to the response. If the value is not
|
|
|
|
// of type sendHTTPStatus, it re-panics.
|
|
|
|
func senderr(w http.ResponseWriter) {
|
|
|
|
err := recover()
|
|
|
|
if stat, ok := err.(sendHTTPStatus); ok {
|
|
|
|
w.WriteHeader(int(stat))
|
|
|
|
} else if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GET Method
|
|
|
|
func get(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer senderr(w)
|
|
|
|
file, stat := f.Open(url2path(r.URL), uint32(os.O_RDONLY))
|
|
|
|
|
|
|
|
checkerr(stat)
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
|
|
ff, err := file.(*fs.CamliFile).GetReader()
|
|
|
|
_, err = io.Copy(w, ff)
|
|
|
|
if err != nil {
|
|
|
|
log.Print("propfind: error writing response: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 9.1 PROPFIND Method
|
|
|
|
func propfind(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer senderr(w)
|
|
|
|
depth := r.Header.Get("Depth")
|
|
|
|
switch depth {
|
|
|
|
case "0", "1":
|
|
|
|
case /*implicit infinity*/ "", "infinity":
|
|
|
|
log.Print("propfind: unsupported depth of infinity")
|
|
|
|
panic(sendHTTPStatus(http.StatusForbidden))
|
|
|
|
default:
|
|
|
|
log.Print("propfind: invalid Depth of", depth)
|
|
|
|
panic(sendHTTPStatus(http.StatusBadRequest))
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(rh) Empty body == allprop
|
|
|
|
|
|
|
|
// TODO(rh): allprop
|
|
|
|
var propsToFind []string
|
|
|
|
|
|
|
|
x := parsexml(r.Body)
|
|
|
|
x.muststart("propfind")
|
|
|
|
switch {
|
|
|
|
case x.start("propname"):
|
|
|
|
x.mustend("propname")
|
|
|
|
case x.start("allprop"):
|
|
|
|
x.mustend("allprop")
|
|
|
|
if x.start("include") {
|
|
|
|
// TODO(rh) parse elements
|
|
|
|
x.mustend("include")
|
|
|
|
}
|
|
|
|
case x.start("prop"):
|
|
|
|
propsToFind = parseprop(x)
|
|
|
|
x.mustend("prop")
|
|
|
|
}
|
|
|
|
x.mustend("propfind")
|
|
|
|
var files = []string{url2path(r.URL)}
|
|
|
|
if depth == "1" {
|
|
|
|
// TODO(rh) handle bad stat
|
|
|
|
files = append(files, ls(files[0])...)
|
|
|
|
}
|
|
|
|
|
|
|
|
var ms multistatus
|
|
|
|
for _, file := range files {
|
|
|
|
resp := &response{href: (*href)(path2url(file))}
|
|
|
|
attr, stat := f.GetAttr(file) // TODO(rh) better way?
|
|
|
|
|
|
|
|
checkerr(stat)
|
|
|
|
|
|
|
|
var props []xmler
|
|
|
|
for _, p := range propsToFind {
|
|
|
|
switch p {
|
|
|
|
case "creationdate":
|
|
|
|
props = append(props, creationdate(attr.Ctime))
|
|
|
|
case "resourcetype":
|
|
|
|
props = append(props, resourcetype(attr.Mode&fuse.S_IFDIR == fuse.S_IFDIR))
|
|
|
|
case "getcontentlength":
|
|
|
|
props = append(props, getcontentlength(attr.Size))
|
|
|
|
case "getlastmodified":
|
|
|
|
props = append(props, getlastmodified(attr.Mtime))
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.body = propstats{{props, 200}}
|
|
|
|
}
|
|
|
|
ms = append(ms, resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
var xmlbytes bytes.Buffer
|
|
|
|
ms.XML(&xmlbytes)
|
|
|
|
w.Header().Set("Content-Type", "application/xml; charset=UTF-8")
|
|
|
|
w.WriteHeader(207) // 207 Multi-Status
|
|
|
|
_, err := io.Copy(w, &xmlbytes)
|
|
|
|
if err != nil {
|
|
|
|
log.Print("propfind: error writing response: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkerr(stat fuse.Status) {
|
|
|
|
switch stat {
|
|
|
|
case fuse.ENOENT:
|
|
|
|
panic(sendHTTPStatus(http.StatusNotFound))
|
|
|
|
case fuse.OK:
|
|
|
|
default:
|
|
|
|
panic(sendHTTPStatus(http.StatusForbidden))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func ls(path string) (paths []string) {
|
|
|
|
dirs, err := f.OpenDir(path)
|
|
|
|
|
|
|
|
checkerr(err)
|
|
|
|
|
|
|
|
for d := range dirs {
|
|
|
|
// TODO(rh) determine a proper way to join paths
|
|
|
|
if path != "" {
|
|
|
|
d.Name = path + "/" + d.Name
|
|
|
|
}
|
|
|
|
paths = append(paths, d.Name)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(rh) settle on an internal format for paths, and a better way to translate between paths and URLs
|
2011-08-25 15:14:47 +00:00
|
|
|
func url2path(url_ *url.URL) string {
|
|
|
|
return strings.Trim(url_.Path, "/") // TODO(rh) make not suck
|
2011-08-07 01:46:37 +00:00
|
|
|
}
|
|
|
|
|
2011-08-25 15:14:47 +00:00
|
|
|
func path2url(path string) *url.URL {
|
|
|
|
return &url.URL{Path: "/" + path} // TODO(rh) make not suck
|
2011-08-07 01:46:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseprop(x *xmlparser) (props []string) {
|
|
|
|
for {
|
|
|
|
el, ok := x.cur.(xml.StartElement)
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
props = append(props, el.Name.Local)
|
|
|
|
x.muststart(el.Name.Local)
|
|
|
|
x.mustend(el.Name.Local)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|