// Get implements the WebDAV GET method to download a file. func (s *svc) Get(w http.ResponseWriter, r *http.Request) { log := keys.MustGetLog(r) user := keys.MustGetUser(r) path := mux.Vars(r)["path"] info, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handleGetError(err, w, r) return } if info.Type != entities.ObjectTypeBLOB { log.Warn("object is not a blob") w.WriteHeader(http.StatusNotImplemented) return } reader, err := s.dataController.DownloadBLOB(user, path) if err != nil { s.handleGetError(err, w, r) return } w.Header().Add("Content-Type", info.MimeType) if _, err := io.Copy(w, reader); err != nil { log.WithError(err).Error("cannot write response body") } }
// Init retrieves the information about an object. func (s *svc) Init(w http.ResponseWriter, r *http.Request) { user := keys.MustGetUser(r) err := s.metaDataController.Init(user) if err != nil { s.handleInitError(err, w, r) return } }
// DeleteObject retrieves the information about an object. func (s *svc) DeleteObject(w http.ResponseWriter, r *http.Request) { path := mux.Vars(r)["path"] user := keys.MustGetUser(r) err := s.metaDataController.DeleteObject(user, path) if err != nil { s.handleDeleteObjectError(err, w, r) return } w.WriteHeader(http.StatusNoContent) }
// CreateTree creates a tree object. func (s *svc) CreateTree(w http.ResponseWriter, r *http.Request) { path := mux.Vars(r)["path"] user := keys.MustGetUser(r) err := s.metaDataController.CreateTree(user, path) if err != nil { s.handleCreateTreeError(err, w, r) return } w.WriteHeader(http.StatusCreated) }
// Head implements the WebDAV HEAD method. func (s *svc) Head(w http.ResponseWriter, r *http.Request) { user := keys.MustGetUser(r) path := mux.Vars(r)["path"] info, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handleHeadError(err, w, r) return } w.Header().Add("Content-Type", info.MimeType) }
// Move implements the WebDAV MOVE method. func (s *svc) Move(w http.ResponseWriter, r *http.Request) { user := keys.MustGetUser(r) path := mux.Vars(r)["path"] destination := r.Header.Get("Destination") overwrite := r.Header.Get("Overwrite") if destination == "" { w.WriteHeader(http.StatusBadRequest) return } destinationURL, err := url.ParseRequestURI(destination) if err != nil { w.WriteHeader(http.StatusBadRequest) return } overwrite = strings.ToUpper(overwrite) if overwrite == "" { overwrite = "T" } if overwrite != "T" && overwrite != "F" { w.WriteHeader(http.StatusBadRequest) return } // remove api base and service base to get real path dirs := s.conf.GetDirectives() toTrim := filepath.Join("/", dirs.Server.BaseURL, dirs.OCWebDAV.BaseURL) + "/remote.php/webdav/" destination = strings.TrimPrefix(destinationURL.Path, toTrim) err = s.metaDataController.MoveObject(user, path, destination) if err != nil { s.handleMoveError(err, w, r) return } info, err := s.metaDataController.ExamineObject(user, destination) if err != nil { s.handleMoveError(err, w, r) return } w.Header().Set("ETag", info.Extra.(ocsql.Extra).ETag) w.Header().Set("OC-FileId", info.Extra.(ocsql.Extra).ID) w.Header().Set("OC-ETag", info.Extra.(ocsql.Extra).ETag) // ownCloud want a 201 instead of 204 w.WriteHeader(http.StatusCreated) }
// ExamineObject retrieves the information about an object. func (s *svc) ExamineObject(w http.ResponseWriter, r *http.Request) { log := keys.MustGetLog(r) user := keys.MustGetUser(r) path := mux.Vars(r)["path"] oinfo, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handleExamineObjectError(err, w, r) return } if err := json.NewEncoder(w).Encode(oinfo); err != nil { log.WithError(err).Error("cannot encode") } }
// Put uploads a blob to user tree. func (s *svc) Put(w http.ResponseWriter, r *http.Request) { if r.Body == nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } user := keys.MustGetUser(r) log := keys.MustGetLog(r) if s.requestHasContentRange(r) { log.Warning("Content-Range header is not accepted on PUT") http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented) return } if s.requestSuffersFinderProblem(r) { s.handlerFinderRequest(w, r) } path := mux.Vars(r)["path"] info, err := s.metaDataController.ExamineObject(user, path) // if err is not found it is okay to continue if err != nil { if !s.isNotFoundError(err) { s.handlePutError(err, w, r) return } } if info != nil && info.Type != entities.ObjectTypeBLOB { log.Warn("object is not a blob") w.WriteHeader(http.StatusConflict) return } readCloser := http.MaxBytesReader(w, r.Body, int64(s.conf.GetDirectives().WebDAV.UploadMaxFileSize)) if err := s.dataController.UploadBLOB(user, path, readCloser, ""); err != nil { s.handlePutError(err, w, r) return } // if object did not exist, http code is 201, else 204. if info == nil { w.WriteHeader(http.StatusCreated) return } w.WriteHeader(http.StatusNoContent) return }
// Upload uploads a blob to user tree. func (s *svc) Upload(w http.ResponseWriter, r *http.Request) { if r.Body == nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } user := keys.MustGetUser(r) path := mux.Vars(r)["path"] clientChecksum := s.getClientChecksum(r) readCloser := http.MaxBytesReader(w, r.Body, int64(s.conf.GetDirectives().Data.UploadMaxFileSize)) if err := s.dataController.UploadBLOB(user, path, readCloser, clientChecksum); err != nil { s.handleUploadError(err, w, r) return } w.WriteHeader(http.StatusCreated) }
// Get implements the WebDAV GET method to download a file. func (s *svc) Get(w http.ResponseWriter, r *http.Request) { log := keys.MustGetLog(r) user := keys.MustGetUser(r) path := mux.Vars(r)["path"] info, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handleGetError(err, w, r) return } if info.Type != entities.ObjectTypeBLOB { log.Warn("object is not a blob") w.WriteHeader(http.StatusNotImplemented) return } reader, err := s.dataController.DownloadBLOB(user, path) if err != nil { s.handleGetError(err, w, r) return } info, err = s.metaDataController.ExamineObject(user, path) if err != nil { s.handleGetError(err, w, r) return } w.Header().Set("Content-Type", info.MimeType) w.Header().Set("ETag", info.Extra.(ocsql.Extra).ETag) w.Header().Set("OC-FileId", info.Extra.(ocsql.Extra).ID) w.Header().Set("OC-ETag", info.Extra.(ocsql.Extra).ETag) t := time.Unix(info.ModTime/1000000000, info.ModTime%1000000000) lastModifiedString := t.Format(time.RFC1123) w.Header().Set("Last-Modified", lastModifiedString) w.WriteHeader(http.StatusOK) if info.Checksum != "" { w.Header().Set("OC-Checksum", info.Checksum) } if _, err := io.Copy(w, reader); err != nil { log.WithError(err).Error("cannot write response body") } }
// Download streams a file to the client. func (s *svc) Download(w http.ResponseWriter, r *http.Request) { log := keys.MustGetLog(r) user := keys.MustGetUser(r) path := mux.Vars(r)["path"] reader, err := s.dataController.DownloadBLOB(user, path) if err != nil { s.handleDownloadError(err, w, r) return } // add security headers w.Header().Add("X-Content-Type-Options", "nosniff") w.Header().Add("Content-Type", entities.ObjectTypeBLOBMimeType) w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename='%s'", filepath.Base(path))) if _, err := io.Copy(w, reader); err != nil { log.WithError(err).Error("cannot write response body") } }
// MoveObject retrieves the information about an object. func (s *svc) MoveObject(w http.ResponseWriter, r *http.Request) { sourcePath := mux.Vars(r)["path"] targetPath := r.URL.Query().Get("target") // targetPath may be url encoded, so we decode it first. targetPath, err := url.QueryUnescape(targetPath) if err != nil { s.handleMoveObjectError(err, w, r) return } user := keys.MustGetUser(r) err = s.metaDataController.MoveObject(user, sourcePath, targetPath) if err != nil { s.handleMoveObjectError(err, w, r) return } }
// Head implements the WebDAV HEAD method. func (s *svc) Head(w http.ResponseWriter, r *http.Request) { user := keys.MustGetUser(r) path := mux.Vars(r)["path"] info, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handleHeadError(err, w, r) return } w.Header().Add("Content-Type", info.MimeType) w.Header().Set("ETag", info.Extra.(ocsql.Extra).ETag) w.Header().Set("OC-FileId", info.Extra.(ocsql.Extra).ID) w.Header().Set("OC-ETag", info.Extra.(ocsql.Extra).ETag) t := time.Unix(info.ModTime/1000000000, info.ModTime%1000000000) lastModifiedString := t.Format(time.RFC1123) w.Header().Set("Last-Modified", lastModifiedString) w.WriteHeader(http.StatusOK) }
// Propfind implements the WebDAV PROPFIND method. func (s *svc) Propfind(w http.ResponseWriter, r *http.Request) { user := keys.MustGetUser(r) path := mux.Vars(r)["path"] var children bool depth := r.Header.Get("Depth") // TODO(labkode) Check default for infinity header if depth == "1" { children = true } var infos []*entities.ObjectInfo info, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handlePropfindError(err, w, r) return } infos = append(infos, info) if children && info.Type == entities.ObjectTypeTree { childrenInfos, err := s.metaDataController.ListTree(user, path) if err != nil { s.handlePropfindError(err, w, r) return } infos = append(infos, childrenInfos...) } infosInXML, err := s.infosToXML(infos) if err != nil { s.handlePropfindError(err, w, r) return } w.Header().Set("DAV", "1, 3, extended-mkcol") w.Header().Set("Content-Type", "application/xml; charset=utf-8") w.WriteHeader(207) w.Write([]byte(infosInXML)) }
// Move implements the WebDAV MOVE method. func (s *svc) Move(w http.ResponseWriter, r *http.Request) { user := keys.MustGetUser(r) path := mux.Vars(r)["path"] destination := r.Header.Get("Destination") overwrite := r.Header.Get("Overwrite") if destination == "" { w.WriteHeader(http.StatusBadRequest) return } destinationURL, err := url.ParseRequestURI(destination) if err != nil { w.WriteHeader(http.StatusBadRequest) return } overwrite = strings.ToUpper(overwrite) if overwrite == "" { overwrite = "T" } if overwrite != "T" && overwrite != "F" { w.WriteHeader(http.StatusBadRequest) return } // remove api base and service base to get real path dirs := s.conf.GetDirectives() toTrim := filepath.Join("/", dirs.Server.BaseURL, dirs.WebDAV.BaseURL) + "/home/" destination = strings.TrimPrefix(destinationURL.Path, toTrim) err = s.metaDataController.MoveObject(user, path, destination) if err != nil { s.handleMoveError(err, w, r) return } w.WriteHeader(http.StatusNoContent) }
// Options implements the WebDAV GET method to download a file. func (s *svc) Options(w http.ResponseWriter, r *http.Request) { user := keys.MustGetUser(r) path := mux.Vars(r)["path"] info, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handleOptionsError(err, w, r) return } allow := "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY," allow += " MOVE, UNLOCK, PROPFIND" if info.Type == entities.ObjectTypeBLOB { allow += ", PUT" } w.Header().Set("Allow", allow) w.Header().Set("DAV", "1, 2") w.Header().Set("MS-Author-Via", "DAV") w.WriteHeader(http.StatusOK) return }
func (s *svc) PutChunked(w http.ResponseWriter, r *http.Request) { if r.Body == nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } user := keys.MustGetUser(r) log := keys.MustGetLog(r) path := mux.Vars(r)["path"] chunkInfo, err := getChunkBLOBInfo(path) if err != nil { log.WithError(err).WithField("pathspec", path).Error("cannot obtain chunk info from pathspec") w.WriteHeader(http.StatusInternalServerError) return } log.WithField("chunk", chunkInfo).Debug("chunk info") chunkTempFilename, chunkTempFile, err := s.createChunkTempFile() if err != nil { log.WithError(err).Error("cannot create chunk temp file") w.WriteHeader(http.StatusInternalServerError) return } defer chunkTempFile.Close() readCloser := http.MaxBytesReader(w, r.Body, int64(s.conf.GetDirectives().WebDAV.UploadMaxFileSize)) if _, err := io.Copy(chunkTempFile, readCloser); err != nil { s.handlePutError(err, w, r) return } // force close of the file here because if it is the last chunk to // assemble the big file we need all the chunks closed if err = chunkTempFile.Close(); err != nil { s.handlePutError(err, w, r) return } chunkFolderName, err := s.getChunkFolderName(chunkInfo) if err != nil { s.handlePutError(err, w, r) return } log.WithField("chunkfolder", chunkFolderName).Debug("chunk folder info") chunkTarget := helpers.SecureJoin(chunkFolderName, fmt.Sprintf("%d", chunkInfo.currentChunk)) if err = os.Rename(chunkTempFilename, chunkTarget); err != nil { s.handlePutError(err, w, r) return } log.WithField("chunktarget", chunkTarget).Debug("chunk target info") // Check that all chunks are uploaded. // This is very inefficient, the server has to check that it has all the // chunks after each uploaded chunk. // A two-phase upload like DropBox is better, because the server will // assembly the chunks when the client asks for it. chunkFolder, err := os.Open(chunkFolderName) if err != nil { s.handlePutError(err, w, r) return } defer chunkFolder.Close() // read all the chunks inside the chunk folder; -1 == all chunks, err := chunkFolder.Readdir(-1) if err != nil { s.handlePutError(err, w, r) return } log.WithField("chunk count", len(chunks)).Debug("current amount of chunks") // there is still some chunks to be uploaded so we stop here if len(chunks) < int(chunkInfo.totalChunks) { log.Debug("chunk is not final") w.WriteHeader(http.StatusCreated) return } assembledFileName, assembledFile, err := s.createChunkTempFile() if err != nil { s.handlePutError(err, w, r) return } defer assembledFile.Close() log.WithField("assembledfile", assembledFileName).Debug("assembled file info") // walk all chunks and append to assembled file for i := range chunks { target := helpers.SecureJoin(chunkFolderName, fmt.Sprintf("%d", i)) chunk, err := os.Open(target) if err != nil { s.handlePutError(err, w, r) return } if _, err = io.Copy(assembledFile, chunk); err != nil { s.handlePutError(err, w, r) return } log.WithField("chunk", target).WithField("assembledfile", assembledFileName).Debug("chunk appended to assembled file") // we close the chunk here because if the assemnled file contains hundreds of chunks // we will end up with hundreds of open file descriptors if err = chunk.Close(); err != nil { s.handlePutError(err, w, r) return } } // at this point the assembled file is complete // so we free space removing the chunks folder defer func() { if err = os.RemoveAll(chunkFolderName); err != nil { log.WithError(err).Error("cannot remove chunk folder after recontruction") } }() // when writing to the assembled file the write pointer points to the end of the file // so we need to seek it to the beginning if _, err = assembledFile.Seek(0, 0); err != nil { s.handlePutError(err, w, r) return } log.WithField("pathspec", chunkInfo.pathSpec).Debug("upload chunk to final destination") if err = s.dataController.UploadBLOB(user, chunkInfo.pathSpec, assembledFile, ""); err != nil { s.handlePutError(err, w, r) return } info, err := s.metaDataController.ExamineObject(user, chunkInfo.pathSpec) if err != nil { s.handlePutError(err, w, r) return } w.Header().Add("Content-Type", info.MimeType) w.Header().Set("ETag", info.Extra.(ocsql.Extra).ETag) w.Header().Set("OC-FileId", info.Extra.(ocsql.Extra).ID) w.Header().Set("OC-ETag", info.Extra.(ocsql.Extra).ETag) t := time.Unix(info.ModTime/1000000000, info.ModTime%1000000000) lastModifiedString := t.Format(time.RFC1123) w.Header().Set("Last-Modified", lastModifiedString) w.Header().Set("X-OC-MTime", "accepted") // if object did not exist, http code is 201, else 204. if info == nil { w.WriteHeader(http.StatusCreated) return } w.WriteHeader(http.StatusNoContent) }
// Put uploads a blob to user tree. func (s *svc) Put(w http.ResponseWriter, r *http.Request) { if r.Body == nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } user := keys.MustGetUser(r) log := keys.MustGetLog(r) path := mux.Vars(r)["path"] // if request is a chunk upload we handle it in another method isChunked, err := s.isChunkedUpload(path) if err != nil { log.WithError(err).WithField("pathspec", path).Error("cannot apply chunk regex") w.WriteHeader(http.StatusInternalServerError) return } if isChunked { log.Info("upload is chunked") s.PutChunked(w, r) return } if s.requestHasContentRange(r) { log.Warning("Content-Range header is not accepted on PUT") w.WriteHeader(http.StatusNotImplemented) return } if s.requestSuffersFinderProblem(r) { if err := s.handleFinderRequest(w, r); err != nil { return } } info, err := s.metaDataController.ExamineObject(user, path) // if err is not found it is okay to continue if err != nil { if !s.isNotFoundError(err) { s.handlePutError(err, w, r) return } } if info != nil && info.Type != entities.ObjectTypeBLOB { log.Warn("object is not a blob") w.WriteHeader(http.StatusConflict) return } // if If-Match header contains an Etag we need to check it against the ETag from the server // so see if they match or not. If they do not match, StatusPreconditionFailed is returned if info != nil { clientETag := r.Header.Get("If-Match") serverETag := info.Extra.(ocsql.Extra).ETag if clientETag != "" { if err := s.handleIfMatchHeader(clientETag, serverETag, w, r); err != nil { return } } } readCloser := http.MaxBytesReader(w, r.Body, int64(s.conf.GetDirectives().WebDAV.UploadMaxFileSize)) if err := s.dataController.UploadBLOB(user, path, readCloser, ""); err != nil { s.handlePutError(err, w, r) return } newInfo, err := s.metaDataController.ExamineObject(user, path) if err != nil { s.handlePutError(err, w, r) return } w.Header().Add("Content-Type", newInfo.MimeType) w.Header().Set("ETag", newInfo.Extra.(ocsql.Extra).ETag) w.Header().Set("OC-FileId", newInfo.Extra.(ocsql.Extra).ID) w.Header().Set("OC-ETag", newInfo.Extra.(ocsql.Extra).ETag) t := time.Unix(newInfo.ModTime/1000000000, newInfo.ModTime%1000000000) lastModifiedString := t.Format(time.RFC1123) w.Header().Set("Last-Modified", lastModifiedString) w.Header().Set("X-OC-MTime", "accepted") // if object did not exist, http code is 201, else 204. if info == nil { w.WriteHeader(http.StatusCreated) return } w.WriteHeader(http.StatusNoContent) return }