XRootD
Loading...
Searching...
No Matches
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
28using namespace XrdClHttp;
29
30// OPTIONS information is available.
31//
32// Reconfigure curl handle, as necessary, to use PROPFIND
33void
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
51CurlStatOp::Redirect(std::string &target)
52{
53 auto headers = m_headers;
54 auto result = CurlOperation::Redirect(target);
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);
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 }
75}
76
77bool
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
97void
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
110size_t
111CurlStatOp::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
125std::pair<int64_t, bool>
126CurlStatOp::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
147std::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
207void
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,
231 info->SetResponseInfo(MoveResponseInfo());
232 stat_info = info;
233 } else {
234 stat_info = new XrdCl::StatInfo("nobody", size,
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
static void child()
void CURL
void SetDone(bool has_failed)
std::unique_ptr< ResponseInfo > MoveResponseInfo()
const std::string m_url
std::unique_ptr< CURL, void(*)(CURL *)> m_curl
virtual void ReleaseHandle()
std::vector< std::pair< std::string, std::string > > m_headers_list
virtual void Fail(uint16_t errCode, uint32_t errNum, const std::string &)
virtual RedirectAction Redirect(std::string &target)
XrdCl::ResponseHandler * m_handler
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
static VerbsCache & Instance()
void Set(Type object, bool own=true)
Object stat info.
@ IsReadable
Read access is allowed.
@ IsDir
This is a directory.
const uint64_t kLogXrdClHttp
const uint16_t errErrorResponse