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  }