github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/old/camwebdav/main.go (about) 1 // +build THIS_IS_BROKEN 2 3 // The camwebdav binary is a WebDAV server to expose Camlistore as a 4 // filesystem that can be mounted from Windows (or other operating 5 // systems). 6 // 7 // It is currently broken and needs to be updated to use 8 // camlistore.org/pkg/fs. 9 package main 10 11 import ( 12 "bytes" 13 "encoding/xml" 14 "flag" 15 "io" 16 "io/ioutil" 17 "log" 18 "net/http" 19 "net/url" 20 "os" 21 "strings" 22 23 "camlistore.org/pkg/blob" 24 "camlistore.org/pkg/blobserver/localdisk" 25 "camlistore.org/pkg/cacher" 26 "camlistore.org/pkg/client" 27 "camlistore.org/pkg/fs" 28 29 // "camlistore.org/third_party/github.com/hanwen/go-fuse/fuse" 30 ) 31 32 var ( 33 f *fs.CamliFileSystem 34 davaddr = flag.String("davaddr", "", "WebDAV service address") 35 ) 36 37 // TODO(rh): tame copy/paste code from cammount 38 func main() { 39 client.AddFlags() 40 flag.Parse() 41 cacheDir, err := ioutil.TempDir("", "camlicache") 42 if err != nil { 43 log.Fatalf("Error creating temp cache directory: %v", err) 44 } 45 defer os.RemoveAll(cacheDir) 46 diskcache, err := localdisk.New(cacheDir) 47 if err != nil { 48 log.Fatalf("Error setting up local disk cache: %v", err) 49 } 50 if flag.NArg() != 1 { 51 log.Fatal("usage: camwebdav <blobref>") 52 } 53 br := blobref.Parse(flag.Arg(0)) 54 if br == nil { 55 log.Fatalf("%s was not a valid blobref.", flag.Arg(0)) 56 } 57 client := client.NewOrFail() 58 fetcher := cacher.NewCachingFetcher(diskcache, client) 59 60 f = fs.NewCamliFileSystem(fetcher, br) 61 http.HandleFunc("/", webdav) 62 err = http.ListenAndServe(*davaddr, nil) 63 if err != nil { 64 log.Fatalf("Error starting WebDAV server: %v", err) 65 } 66 } 67 68 func webdav(w http.ResponseWriter, r *http.Request) { 69 switch r.Method { 70 case "GET": 71 get(w, r) 72 case "OPTIONS": 73 w.Header().Set("DAV", "1") 74 case "PROPFIND": 75 propfind(w, r) 76 default: 77 w.WriteHeader(http.StatusBadRequest) 78 } 79 } 80 81 // sendHTTPStatus is an HTTP status code. 82 type sendHTTPStatus int 83 84 // senderr, when deferred, recovers panics of 85 // type sendHTTPStatus and writes the corresponding 86 // HTTP status to the response. If the value is not 87 // of type sendHTTPStatus, it re-panics. 88 func senderr(w http.ResponseWriter) { 89 err := recover() 90 if stat, ok := err.(sendHTTPStatus); ok { 91 w.WriteHeader(int(stat)) 92 } else if err != nil { 93 panic(err) 94 } 95 } 96 97 // GET Method 98 func get(w http.ResponseWriter, r *http.Request) { 99 defer senderr(w) 100 file, stat := f.Open(url2path(r.URL), uint32(os.O_RDONLY)) 101 102 checkerr(stat) 103 104 w.Header().Set("Content-Type", "application/octet-stream") 105 ff, err := file.(*fs.CamliFile).GetReader() 106 _, err = io.Copy(w, ff) 107 if err != nil { 108 log.Print("propfind: error writing response: %s", err) 109 } 110 } 111 112 // 9.1 PROPFIND Method 113 func propfind(w http.ResponseWriter, r *http.Request) { 114 defer senderr(w) 115 depth := r.Header.Get("Depth") 116 switch depth { 117 case "0", "1": 118 case /*implicit infinity*/ "", "infinity": 119 log.Print("propfind: unsupported depth of infinity") 120 panic(sendHTTPStatus(http.StatusForbidden)) 121 default: 122 log.Print("propfind: invalid Depth of", depth) 123 panic(sendHTTPStatus(http.StatusBadRequest)) 124 } 125 126 // TODO(rh) Empty body == allprop 127 128 // TODO(rh): allprop 129 var propsToFind []string 130 131 x := parsexml(r.Body) 132 x.muststart("propfind") 133 switch { 134 case x.start("propname"): 135 x.mustend("propname") 136 case x.start("allprop"): 137 x.mustend("allprop") 138 if x.start("include") { 139 // TODO(rh) parse elements 140 x.mustend("include") 141 } 142 case x.start("prop"): 143 propsToFind = parseprop(x) 144 x.mustend("prop") 145 } 146 x.mustend("propfind") 147 var files = []string{url2path(r.URL)} 148 if depth == "1" { 149 // TODO(rh) handle bad stat 150 files = append(files, ls(files[0])...) 151 } 152 153 var ms multistatus 154 for _, file := range files { 155 resp := &response{href: (*href)(path2url(file))} 156 attr, stat := f.GetAttr(file) // TODO(rh) better way? 157 158 checkerr(stat) 159 160 var props []xmler 161 for _, p := range propsToFind { 162 switch p { 163 case "creationdate": 164 props = append(props, creationdate(attr.Ctime)) 165 case "resourcetype": 166 props = append(props, resourcetype(attr.Mode&fuse.S_IFDIR == fuse.S_IFDIR)) 167 case "getcontentlength": 168 props = append(props, getcontentlength(attr.Size)) 169 case "getlastmodified": 170 props = append(props, getlastmodified(attr.Mtime)) 171 } 172 173 resp.body = propstats{{props, 200}} 174 } 175 ms = append(ms, resp) 176 } 177 178 var xmlbytes bytes.Buffer 179 ms.XML(&xmlbytes) 180 w.Header().Set("Content-Type", "application/xml; charset=UTF-8") 181 w.WriteHeader(207) // 207 Multi-Status 182 _, err := io.Copy(w, &xmlbytes) 183 if err != nil { 184 log.Print("propfind: error writing response: %s", err) 185 } 186 } 187 188 func checkerr(stat fuse.Status) { 189 switch stat { 190 case fuse.ENOENT: 191 panic(sendHTTPStatus(http.StatusNotFound)) 192 case fuse.OK: 193 default: 194 panic(sendHTTPStatus(http.StatusForbidden)) 195 } 196 } 197 198 func ls(path string) (paths []string) { 199 dirs, err := f.OpenDir(path) 200 201 checkerr(err) 202 203 for d := range dirs { 204 // TODO(rh) determine a proper way to join paths 205 if path != "" { 206 d.Name = path + "/" + d.Name 207 } 208 paths = append(paths, d.Name) 209 } 210 return 211 } 212 213 // TODO(rh) settle on an internal format for paths, and a better way to translate between paths and URLs 214 func url2path(url_ *url.URL) string { 215 return strings.Trim(url_.Path, "/") // TODO(rh) make not suck 216 } 217 218 func path2url(path string) *url.URL { 219 return &url.URL{Path: "/" + path} // TODO(rh) make not suck 220 } 221 222 func parseprop(x *xmlparser) (props []string) { 223 for { 224 el, ok := x.cur.(xml.StartElement) 225 if !ok { 226 break 227 } 228 props = append(props, el.Name.Local) 229 x.muststart(el.Name.Local) 230 x.mustend(el.Name.Local) 231 } 232 return 233 }