// ---------------------------------------------------------------------- // File: WebDAVHandler.cc // Author: Justin Lewis Salmon - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2013 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ /*----------------------------------------------------------------------------*/ #include "mgm/http/HttpServer.hh" #include "mgm/http/webdav/WebDAVHandler.hh" #include "mgm/http/webdav/PropFindResponse.hh" #include "mgm/http/webdav/PropPatchResponse.hh" #include "mgm/http/webdav/LockResponse.hh" #include "mgm/XrdMgmOfs.hh" #include "mgm/Stat.hh" #include "common/http/PlainHttpResponse.hh" #include "common/http/OwnCloud.hh" #include "common/Logging.hh" #ifdef __clang__ #pragma clang diagnostic ignored "-Wformat-security" #endif /*----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ EOSMGMNAMESPACE_BEGIN /*----------------------------------------------------------------------------*/ bool WebDAVHandler::Matches(const std::string& meth, HeaderMap& headers) { int method = ParseMethodString(meth); if (method == PROPFIND || method == PROPPATCH || method == MKCOL || method == COPY || method == MOVE || method == LOCK || method == UNLOCK) { eos_static_debug("msg=\"matched webdav protocol for request\""); return true; } else { return false; } } /*----------------------------------------------------------------------------*/ void WebDAVHandler::HandleRequest(eos::common::HttpRequest* request) { eos_static_debug("msg=\"handling webdav request\""); eos::common::HttpResponse* response = 0; request->AddEosApp(); int meth = ParseMethodString(request->GetMethod()); switch (meth) { case PROPFIND: gOFS->MgmStats.Add("Http-PROPFIND", mVirtualIdentity->uid, mVirtualIdentity->gid, 1); response = new PropFindResponse(request, mVirtualIdentity); break; case PROPPATCH: gOFS->MgmStats.Add("Http-PROPPATCH", mVirtualIdentity->uid, mVirtualIdentity->gid, 1); response = new PropPatchResponse(request, mVirtualIdentity); break; case MKCOL: gOFS->MgmStats.Add("Http-MKCOL", mVirtualIdentity->uid, mVirtualIdentity->gid, 1); response = MkCol(request); break; case COPY: gOFS->MgmStats.Add("Http-COPY", mVirtualIdentity->uid, mVirtualIdentity->gid, 1); response = Copy(request); break; case MOVE: gOFS->MgmStats.Add("Http-MOVE", mVirtualIdentity->uid, mVirtualIdentity->gid, 1); response = Move(request); break; case LOCK: gOFS->MgmStats.Add("Http-LOCK", mVirtualIdentity->uid, mVirtualIdentity->gid, 1); response = new LockResponse(request, mVirtualIdentity); break; case UNLOCK: gOFS->MgmStats.Add("Http-UNLOCK", mVirtualIdentity->uid, mVirtualIdentity->gid, 1); response = new eos::common::PlainHttpResponse(); response->SetResponseCode(eos::common::HttpResponse::NO_CONTENT); break; default: response = new eos::common::PlainHttpResponse(); response->SetResponseCode(eos::common::HttpResponse::BAD_REQUEST); break; } mHttpResponse = response->BuildResponse(request); } /*----------------------------------------------------------------------------*/ eos::common::HttpResponse* WebDAVHandler::MkCol(eos::common::HttpRequest* request) { eos::common::HttpResponse* response = 0; XrdSecEntity client; std::string url = request->GetUrl(); eos_static_info("method=MKCOL path=%s", url.c_str()); client.name = const_cast(mVirtualIdentity->name.c_str()); client.host = const_cast(mVirtualIdentity->host.c_str()); client.tident = const_cast(mVirtualIdentity->tident.c_str()); snprintf(client.prot, sizeof(client.prot) - 1, "%s", mVirtualIdentity->prot.c_str()); if (!request->GetUrl().size()) { response = HttpServer::HttpError("path name required", response->BAD_REQUEST); } else if (*request->GetBodySize() != 0) { // we do not support request bodies with MKCOL requests response = HttpServer::HttpError("request body not supported", response->UNSUPPORTED_MEDIA_TYPE); } else { XrdSfsMode mode = 0; int rc = 0; XrdOucErrInfo error(mVirtualIdentity->tident.c_str()); ino_t new_inode; rc = gOFS->mkdir(request->GetUrl().c_str(), mode, error, &client, (const char*) 0, &new_inode); if (rc != SFS_OK) { if (rc == SFS_ERROR) { if (error.getErrInfo() == EEXIST) { // directory exists response = HttpServer::HttpError(error.getErrText(), response->METHOD_NOT_ALLOWED); } else if (error.getErrInfo() == ENOENT) { // parent directory does not exist response = HttpServer::HttpError(error.getErrText(), response->CONFLICT); } else if (error.getErrInfo() == EPERM) { // not permitted response = HttpServer::HttpError(error.getErrText(), response->FORBIDDEN); } else if (error.getErrInfo() == ENOSPC) { // no space left response = HttpServer::HttpError(error.getErrText(), response->INSUFFICIENT_STORAGE); } else { // some other error response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } } else if (rc == SFS_REDIRECT) { // redirection response = HttpServer::HttpRedirect(request->GetUrl(), error.getErrText(), error.getErrInfo(), false); } else if (rc == SFS_STALL) { // stall response = HttpServer::HttpStall(error.getErrText(), error.getErrInfo()); } else { // something unexpected response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } } else { // everything went well response = new eos::common::PlainHttpResponse(); char sinode[16]; snprintf(sinode, sizeof(sinode), "%llu", (unsigned long long) new_inode); response->AddHeader("OC-FileId", sinode); response->SetResponseCode(response->CREATED); } } return response; } /*----------------------------------------------------------------------------*/ eos::common::HttpResponse* WebDAVHandler::Move(eos::common::HttpRequest* request) { eos::common::HttpResponse* response = 0; XrdOucString prot, port; std::string destination; const char* dest = nullptr; if (!(dest = eos::common::StringConversion::ParseUrl( request->GetHeaders()["destination"].c_str(), prot, port))) { eos_static_warning("could not process destination header=%s", request->GetHeaders()["destination"].c_str()); } destination = dest ? dest : ""; char encode_destination[1024]; snprintf(encode_destination, sizeof(encode_destination), "%s", destination.c_str()); char decode_destination[1024]; decode_destination[0] = 0; if (destination.length() < sizeof(decode_destination)) { ::dav_uri_decode(encode_destination, decode_destination); destination = decode_destination; } // owncloud protocol patch XrdOucString spath = destination.c_str(); eos::common::OwnCloud::OwnCloudRemapping(spath, request); eos::common::OwnCloud::ReplaceRemotePhp(spath); destination = spath.c_str(); eos_static_info("method=MOVE src=\"%s\", dest=\"%s\"", request->GetUrl().c_str(), destination.c_str()); XrdSecEntity client; client.name = const_cast(mVirtualIdentity->name.c_str()); client.host = const_cast(mVirtualIdentity->host.c_str()); client.tident = const_cast(mVirtualIdentity->tident.c_str()); snprintf(client.prot, sizeof(client.prot) - 1, "%s", mVirtualIdentity->prot.c_str()); if (!request->GetUrl().size()) { response = HttpServer::HttpError("source path required", response->BAD_REQUEST); } else if (!destination.size()) { response = HttpServer::HttpError("destination invalid", response->BAD_REQUEST); } else if (request->GetUrl() == destination) { response = HttpServer::HttpError("destination must be different from source", response->FORBIDDEN); } else { int rc = 0; XrdOucErrInfo error(mVirtualIdentity->tident.c_str()); rc = gOFS->rename(request->GetUrl().c_str(), destination.c_str(), error, *mVirtualIdentity, 0, 0, false); if (rc != SFS_OK) { if (rc == SFS_ERROR) { if (error.getErrInfo() == EEXIST) { // resource exists // webdav specifies to overwrite by default if the special header is not set to F if ((!request->GetHeaders().count("overwrite")) || (request->GetHeaders()["overwrite"] == "T")) { // force the rename struct stat buf; gOFS->_stat(request->GetUrl().c_str(), &buf, error, *mVirtualIdentity, ""); ProcCommand cmd; XrdOucString info = "mgm.cmd=rm&mgm.path="; info += destination.c_str(); if (S_ISDIR(buf.st_mode)) { info += "&mgm.option=r"; } cmd.open("/proc/user", info.c_str(), *mVirtualIdentity, &error); cmd.close(); rc = cmd.GetRetc(); if (rc != SFS_OK) { // something went wrong while deleting the destination response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } else { // try the rename again rc = gOFS->rename(request->GetUrl().c_str(), destination.c_str(), error, *mVirtualIdentity, 0, 0, false); if (rc != SFS_OK) { // something went wrong with the second rename response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } else { // it worked! response = new eos::common::PlainHttpResponse(); response->SetResponseCode(response->NO_CONTENT); } } } else { // directory exists but we are not overwriting response = HttpServer::HttpError(error.getErrText(), response->PRECONDITION_FAILED); } } else if (error.getErrInfo() == ENOENT) { // parent directory does not exist response = HttpServer::HttpError(error.getErrText(), response->CONFLICT); } else if (error.getErrInfo() == EPERM) { // not permitted response = HttpServer::HttpError(error.getErrText(), response->FORBIDDEN); } else { // some other error response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } } else if (rc == SFS_REDIRECT) { // redirection response = HttpServer::HttpRedirect(request->GetUrl(), error.getErrText(), error.getErrInfo(), false); } else if (rc == SFS_STALL) { // stall response = HttpServer::HttpStall(error.getErrText(), error.getErrInfo()); } else { // something unexpected response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } } else { // everything went well response = new eos::common::PlainHttpResponse(); response->SetResponseCode(response->CREATED); if (!request->GetHeaders().count("cbox-skip-location-on-move")) { response->AddHeader("Location", request->GetHeaders()["destination"].c_str()); } } } return response; } /*----------------------------------------------------------------------------*/ eos::common::HttpResponse* WebDAVHandler::Copy(eos::common::HttpRequest* request) { eos::common::HttpResponse* response = 0; XrdOucString prot, port; std::string destination; const char* dest = nullptr; if (!(dest = eos::common::StringConversion::ParseUrl( request->GetHeaders()["destination"].c_str(), prot, port))) { eos_static_warning("could not process destination header=%s", request->GetHeaders()["destination"].c_str()); } destination = dest ? dest : ""; eos_static_info("method=COPY src=\"%s\", dest=\"%s\"", request->GetUrl().c_str(), destination.c_str()); XrdSecEntity client; client.name = const_cast(mVirtualIdentity->name.c_str()); client.host = const_cast(mVirtualIdentity->host.c_str()); client.tident = const_cast(mVirtualIdentity->tident.c_str()); snprintf(client.prot, sizeof(client.prot) - 1, "%s", mVirtualIdentity->prot.c_str()); if (!request->GetUrl().size()) { response = HttpServer::HttpError("source path required", response->BAD_REQUEST); } else if (!destination.size()) { response = HttpServer::HttpError("destination invalid", response->BAD_REQUEST); } else if (request->GetUrl() == destination) { response = HttpServer::HttpError("destination must be different from source", response->FORBIDDEN); } else { int rc = 0; XrdOucErrInfo error; ProcCommand cmd; XrdOucString info = "mgm.cmd=file&mgm.subcmd=copy"; info += "&mgm.path="; info += request->GetUrl().c_str(); info += "&mgm.file.target="; info += destination.c_str(); info += "&eos.ruid="; info += mVirtualIdentity->uid_string.c_str(); info += "&eos.rgid="; info += mVirtualIdentity->gid_string.c_str(); eos_static_debug("cmd=%s", info.c_str()); cmd.open("/proc/user", info.c_str(), *mVirtualIdentity, &error); cmd.close(); rc = cmd.GetRetc(); eos_static_debug("ret=%d", rc); if (rc != SFS_OK) { if (rc == EEXIST) { // resource exists if ((!request->GetHeaders().count("overwrite")) || (request->GetHeaders()["overwrite"] == "T")) { // force overwrite info += "&mgm.file.option=f"; eos_static_debug("overwriting: cmd=%s", info.c_str()); cmd.open("/proc/user", info.c_str(), *mVirtualIdentity, &error); cmd.close(); rc = cmd.GetRetc(); eos_static_debug("ret=%d", rc); if (rc != 0) { // something went wrong with the overwrite response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } else { // it worked! response = new eos::common::PlainHttpResponse(); response->SetResponseCode(response->NO_CONTENT); } } else { // resource exists but we are not overwriting response = HttpServer::HttpError(error.getErrText(), response->PRECONDITION_FAILED); } } else if (rc == ENOENT) { // parent directory does not exist response = HttpServer::HttpError(error.getErrText(), response->CONFLICT); } else if (rc == EPERM) { // not permitted response = HttpServer::HttpError(error.getErrText(), response->FORBIDDEN); } else { // some other error response = HttpServer::HttpError(error.getErrText(), error.getErrInfo()); } } else { // everything went well response = new eos::common::PlainHttpResponse(); response->SetResponseCode(response->CREATED); } } return response; } /*----------------------------------------------------------------------------*/ EOSMGMNAMESPACE_END