XRootD
XrdClHttpOpStat.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* Copyright (C) 2025, Pelican Project, Morgridge Institute for Research */
3 /* */
4 /* This file is part of the XrdClHttp client plugin for XRootD. */
5 /* */
6 /* XRootD is free software: you can redistribute it and/or modify it under */
7 /* the terms of the GNU Lesser General Public License as published by the */
8 /* Free Software Foundation, either version 3 of the License, or (at your */
9 /* option) any later version. */
10 /* */
11 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
12 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
13 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
14 /* License for more details. */
15 /* */
16 /* The copyright holder's institutional names and contributor's names may not */
17 /* be used to endorse or promote products derived from this software without */
18 /* specific prior written permission of the institution or contributor. */
19 /******************************************************************************/
20 
21 #include "XrdClHttpOps.hh"
22 #include "XrdClHttpResponses.hh"
23 
24 #include <XrdCl/XrdClLog.hh>
25 
26 #include <tinyxml.h>
27 
28 using namespace XrdClHttp;
29 
30 // OPTIONS information is available.
31 //
32 // Reconfigure curl handle, as necessary, to use PROPFIND
33 void
35 {
36  auto &instance = VerbsCache::Instance();
37  auto target = m_headers.GetLocation();
38  auto verbs = instance.Get(target.empty() ? m_url : target);
39  if (verbs.IsSet(VerbsCache::HttpVerb::kPROPFIND)) {
40  curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, "PROPFIND");
41  m_headers_list.emplace_back("Depth", "0");
42  curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
43  m_is_propfind = true;
44  } else {
45  m_is_propfind = false;
46  curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 1L);
47  }
48 }
49 
51 CurlStatOp::Redirect(std::string &target)
52 {
53  auto headers = m_headers;
54  auto result = CurlOperation::Redirect(target);
55  if (result == CurlOperation::RedirectAction::Fail) {
56  return result;
57  }
58  auto &instance = VerbsCache::Instance();
59  auto verbs = instance.Get(target);
60  if (verbs.IsSet(VerbsCache::HttpVerb::kUnset)) {
61  m_headers = std::move(headers);
62  return CurlOperation::RedirectAction::ReinvokeAfterAllow;
63  }
64 
65  if (verbs.IsSet(VerbsCache::HttpVerb::kPROPFIND)) {
66  curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, "PROPFIND");
67  m_headers_list.emplace_back("Depth", "0");
68  curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
69  m_is_propfind = true;
70  } else {
71  m_is_propfind = false;
72  curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 1L);
73  }
74  return CurlOperation::RedirectAction::Reinvoke;
75 }
76 
77 bool
79 {
80  if (!CurlOperation::Setup(curl, worker)) return false;
81  curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlStatOp::WriteCallback);
82  curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, this);
83 
84  auto &instance = VerbsCache::Instance();
85  auto verbs = instance.Get(m_url);
86  if (verbs.IsSet(VerbsCache::HttpVerb::kPROPFIND)) {
87  curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, "PROPFIND");
88  m_headers_list.emplace_back("Depth", "0");
89  curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
90  m_is_propfind = true;
91  } else {
92  curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 1L);
93  }
94  return true;
95 }
96 
97 void
99 {
100  if (m_curl == nullptr) return;
101  curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
102  if (m_is_propfind) {
103  curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, nullptr);
104  }
105  curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, nullptr);
106  curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, nullptr);
108 }
109 
110 size_t
111 CurlStatOp::WriteCallback(char *buffer, size_t size, size_t nitems, void *this_ptr)
112 {
113  auto me = static_cast<CurlStatOp*>(this_ptr);
114  if (me->m_is_propfind) {
115  if (size * nitems + me->m_response.size() > 1'000'000) {
116  me->m_logger->Error(kLogXrdClHttp, "Response too large for PROPFIND operation");
117  return 0;
118  }
119  me->UpdateBytes(size * nitems);
120  me->m_response.append(buffer, size * nitems);
121  }
122  return size * nitems;
123 }
124 
125 std::pair<int64_t, bool>
126 CurlStatOp::ParseProp(TiXmlElement *prop) {
127  if (prop == nullptr) {
128  return {-1, false};
129  }
130  for (auto child = prop->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
131  if (!strcasecmp(child->Value(), "D:getcontentlength") || !strcasecmp(child->Value(), "lp1:getcontentlength")) {
132  auto len = child->GetText();
133  if (len) {
134  m_length = std::stoll(len);
135  }
136  } else if (!strcasecmp(child->Value(), "D:resourcetype") || !strcasecmp(child->Value(), "lp1:resourcetype")) {
137  m_is_dir = child->FirstChildElement("D:collection") != nullptr;
138  }
139  }
140  if (m_length < 0 && m_is_dir) {
141  // Don't require length for directories; fake it as zero
142  m_length = 0;
143  }
144  return {m_length, m_is_dir};
145 }
146 
147 std::pair<int64_t, bool>
149  if (!m_is_propfind) {
150  m_length = m_headers.GetContentLength();
151  return {m_length, false};
152  }
153  if (m_length >= 0) {
154  return {m_length, m_is_dir};
155  }
156 
157  TiXmlDocument doc;
158  doc.Parse(m_response.c_str());
159  if (doc.Error()) {
160  m_logger->Error(kLogXrdClHttp, "Failed to parse XML response: %s", m_response.substr(0, 1024).c_str());
161  return {-1, false};
162  }
163 
164  auto elem = doc.RootElement();
165  if (strcasecmp(elem->Value(), "D:multistatus")) {
166  m_logger->Error(kLogXrdClHttp, "Unexpected XML response: %s", m_response.substr(0, 1024).c_str());
167  return {-1, false};
168  }
169  auto found_response = false;
170  for (auto response = elem->FirstChildElement(); response != nullptr; response = response->NextSiblingElement()) {
171  if (!strcasecmp(response->Value(), "D:response")) {
172  found_response = true;
173  elem = response;
174  break;
175  }
176  }
177  if (!found_response) {
178  m_logger->Error(kLogXrdClHttp, "Failed to find response element in XML response: %s", m_response.substr(0, 1024).c_str());
179  return {-1, false};
180  }
181  for (auto child = elem->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
182  if (strcasecmp(child->Value(), "D:propstat")) {
183  continue;
184  }
185  for (auto prop = child->FirstChildElement(); prop != nullptr; prop = prop->NextSiblingElement()) {
186  if (!strcasecmp(prop->Value(), "D:prop")) {
187  return ParseProp(prop);
188  }
189  }
190  }
191  m_logger->Error(kLogXrdClHttp, "Failed to find properties in XML response: %s", m_response.substr(0, 1024).c_str());
192  return {-1, false};
193 }
194 
196 {
197  auto &instance = VerbsCache::Instance();
198  auto verbs = instance.Get(m_url);
199  return verbs.IsSet(VerbsCache::HttpVerb::kUnset);
200 }
201 
203 {
204  SuccessImpl(true);
205 }
206 
207 void
208 CurlStatOp::SuccessImpl(bool returnObj)
209 {
210  SetDone(false);
211  m_logger->Debug(kLogXrdClHttp, "CurlStatOp::Success");
212  if (m_handler == nullptr) {return;}
213  XrdCl::AnyObject *obj = nullptr;
214  if (returnObj) {
215  auto [size, isdir] = GetStatInfo();
216  if (size < 0) {
217  m_logger->Error(kLogXrdClHttp, "Failed to get stat info for %s", m_url.c_str());
218  Fail(XrdCl::errErrorResponse, kXR_FSError, "Server responded without object size");
219  return;
220  }
221  if (m_is_propfind) {
222  m_logger->Debug(kLogXrdClHttp, "Successful propfind operation on %s (size %lld, isdir %d)", m_url.c_str(), static_cast<long long>(size), isdir);
223  } else {
224  m_logger->Debug(kLogXrdClHttp, "Successful stat operation on %s (size %lld)", m_url.c_str(), static_cast<long long>(size));
225  }
226 
227  XrdCl::StatInfo *stat_info;
228  if (m_response_info){
229  auto info = new XrdClHttp::StatResponse("nobody", size,
230  XrdCl::StatInfo::Flags::IsReadable | (isdir ? XrdCl::StatInfo::Flags::IsDir : 0), time(NULL));
231  info->SetResponseInfo(MoveResponseInfo());
232  stat_info = info;
233  } else {
234  stat_info = new XrdCl::StatInfo("nobody", size,
235  XrdCl::StatInfo::Flags::IsReadable | (isdir ? XrdCl::StatInfo::Flags::IsDir : 0), time(NULL));
236  }
237  obj = new XrdCl::AnyObject();
238  obj->Set(stat_info);
239  } else if (m_response_info) {
240  auto info = new XrdClHttp::OpenResponseInfo();
241  info->SetResponseInfo(MoveResponseInfo());
242  obj = new XrdCl::AnyObject();
243  obj->Set(info);
244  }
245 
246  auto handle = m_handler;
247  m_handler = nullptr;
248  handle->HandleResponse(new XrdCl::XRootDStatus(), obj);
249 }
@ kXR_FSError
Definition: XProtocol.hh:1037
static void child()
void CURL
void SetDone(bool has_failed)
const std::string m_url
std::unique_ptr< CURL, void(*)(CURL *)> m_curl
virtual void Fail(uint16_t errCode, uint32_t errNum, const std::string &)
virtual void ReleaseHandle()
std::vector< std::pair< std::string, std::string > > m_headers_list
virtual RedirectAction Redirect(std::string &target)
XrdCl::ResponseHandler * m_handler
std::unique_ptr< ResponseInfo > MoveResponseInfo()
virtual bool Setup(CURL *curl, CurlWorker &)
void ReleaseHandle() override
void SuccessImpl(bool returnObj)
virtual bool RequiresOptions() const override
bool Setup(CURL *curl, CurlWorker &) override
RedirectAction Redirect(std::string &target) override
std::pair< int64_t, bool > GetStatInfo()
virtual void OptionsDone() override
void Success() override
int64_t GetContentLength() const
const std::string & GetLocation() const
static VerbsCache & Instance()
void Set(Type object, bool own=true)
void Error(uint64_t topic, const char *format,...)
Report an error.
Definition: XrdClLog.cc:231
void Debug(uint64_t topic, const char *format,...)
Print a debug message.
Definition: XrdClLog.cc:282
virtual void HandleResponse(XRootDStatus *status, AnyObject *response)
Object stat info.
const uint16_t errErrorResponse
Definition: XrdClStatus.hh:105
const uint64_t kLogXrdClHttp