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 <ctime>
27
28#include <tinyxml.h>
29
30using namespace XrdClHttp;
31
32// OPTIONS information is available.
33//
34// Reconfigure curl handle, as necessary, to use PROPFIND
35void
37{
38 auto &instance = VerbsCache::Instance();
39 auto target = m_headers.GetLocation();
40 auto verbs = instance.Get(target.empty() ? m_url : target);
41 if (verbs.IsSet(VerbsCache::HttpVerb::kPROPFIND)) {
42 curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, "PROPFIND");
43 m_headers_list.emplace_back("Depth", "0");
44 curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
45 m_is_propfind = true;
46 } else {
47 m_is_propfind = false;
48 curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 1L);
49 }
50}
51
53CurlStatOp::Redirect(std::string &target)
54{
55 auto headers = m_headers;
56 auto result = CurlOperation::Redirect(target);
58 return result;
59 }
60 auto &instance = VerbsCache::Instance();
61 auto verbs = instance.Get(target);
62 if (verbs.IsSet(VerbsCache::HttpVerb::kUnset)) {
63 m_headers = std::move(headers);
65 }
66
67 if (verbs.IsSet(VerbsCache::HttpVerb::kPROPFIND)) {
68 curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, "PROPFIND");
69 m_headers_list.emplace_back("Depth", "0");
70 curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
71 m_is_propfind = true;
72 } else {
73 m_is_propfind = false;
74 curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 1L);
75 }
77}
78
79bool
81{
82 if (!CurlOperation::Setup(curl, worker)) return false;
83 curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlStatOp::WriteCallback);
84 curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, this);
85
86 auto &instance = VerbsCache::Instance();
87 auto verbs = instance.Get(m_url);
88 if (verbs.IsSet(VerbsCache::HttpVerb::kPROPFIND)) {
89 curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, "PROPFIND");
90 m_headers_list.emplace_back("Depth", "0");
91 curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
92 m_is_propfind = true;
93 } else {
94 curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 1L);
95 }
96 return true;
97}
98
99void
101{
102 if (m_curl == nullptr) return;
103 curl_easy_setopt(m_curl.get(), CURLOPT_NOBODY, 0L);
104 if (m_is_propfind) {
105 curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, nullptr);
106 }
107 curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, nullptr);
108 curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, nullptr);
110}
111
112size_t
113CurlStatOp::WriteCallback(char *buffer, size_t size, size_t nitems, void *this_ptr)
114{
115 auto me = static_cast<CurlStatOp*>(this_ptr);
116 if (me->m_is_propfind) {
117 if (size * nitems + me->m_response.size() > 1'000'000) {
118 me->m_logger->Error(kLogXrdClHttp, "Response too large for PROPFIND operation");
119 return 0;
120 }
121 me->UpdateBytes(size * nitems);
122 me->m_response.append(buffer, size * nitems);
123 }
124 return size * nitems;
125}
126
127std::pair<int64_t, bool>
128CurlStatOp::ParseProp(TiXmlElement *prop) {
129 if (prop == nullptr) {
130 return {-1, false};
131 }
132 for (auto child = prop->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
133 if (!strcasecmp(child->Value(), "D:getcontentlength") || !strcasecmp(child->Value(), "lp1:getcontentlength")) {
134 auto len = child->GetText();
135 if (len) {
136 m_length = std::stoll(len);
137 }
138 } else if (!strcasecmp(child->Value(), "D:resourcetype") || !strcasecmp(child->Value(), "lp1:resourcetype")) {
139 m_is_dir = child->FirstChildElement("D:collection") != nullptr;
140 } else if (!strcasecmp(child->Value(), "D:getlastmodified") || !strcasecmp(child->Value(), "lp1:getlastmodified")) {
141 auto lastmod = child->GetText();
142 if (lastmod) {
143 struct tm tm;
144 if (strptime(lastmod, "%a, %d %b %Y %H:%M:%S", &tm) != nullptr) {
145 m_modtime = timegm(&tm);
146 }
147 }
148 }
149 }
150 if (m_length < 0 && m_is_dir) {
151 // Don't require length for directories; fake it as zero
152 m_length = 0;
153 }
154 return {m_length, m_is_dir};
155}
156
157std::pair<int64_t, bool>
159 if (!m_is_propfind) {
160 m_length = m_headers.GetContentLength();
161 return {m_length, false};
162 }
163 if (m_length >= 0) {
164 return {m_length, m_is_dir};
165 }
166
167 TiXmlDocument doc;
168 doc.Parse(m_response.c_str());
169 if (doc.Error()) {
170 m_logger->Error(kLogXrdClHttp, "Failed to parse XML response: %s", m_response.substr(0, 1024).c_str());
171 return {-1, false};
172 }
173
174 auto elem = doc.RootElement();
175 if (strcasecmp(elem->Value(), "D:multistatus")) {
176 m_logger->Error(kLogXrdClHttp, "Unexpected XML response: %s", m_response.substr(0, 1024).c_str());
177 return {-1, false};
178 }
179 auto found_response = false;
180 for (auto response = elem->FirstChildElement(); response != nullptr; response = response->NextSiblingElement()) {
181 if (!strcasecmp(response->Value(), "D:response")) {
182 found_response = true;
183 elem = response;
184 break;
185 }
186 }
187 if (!found_response) {
188 m_logger->Error(kLogXrdClHttp, "Failed to find response element in XML response: %s", m_response.substr(0, 1024).c_str());
189 return {-1, false};
190 }
191 for (auto child = elem->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
192 if (strcasecmp(child->Value(), "D:propstat")) {
193 continue;
194 }
195 for (auto prop = child->FirstChildElement(); prop != nullptr; prop = prop->NextSiblingElement()) {
196 if (!strcasecmp(prop->Value(), "D:prop")) {
197 return ParseProp(prop);
198 }
199 }
200 }
201 m_logger->Error(kLogXrdClHttp, "Failed to find properties in XML response: %s", m_response.substr(0, 1024).c_str());
202 return {-1, false};
203}
204
206{
207 auto &instance = VerbsCache::Instance();
208 auto verbs = instance.Get(m_url);
209 return verbs.IsSet(VerbsCache::HttpVerb::kUnset);
210}
211
213{
214 SuccessImpl(true);
215}
216
217void
219{
220 SetDone(false);
221 m_logger->Debug(kLogXrdClHttp, "CurlStatOp::Success");
222 if (m_handler == nullptr) {return;}
223 XrdCl::AnyObject *obj = nullptr;
224 if (returnObj) {
225 auto [size, isdir] = GetStatInfo();
226 if (size < 0) {
227 m_logger->Error(kLogXrdClHttp, "Failed to get stat info for %s", m_url.c_str());
228 Fail(XrdCl::errErrorResponse, kXR_FSError, "Server responded without object size");
229 return;
230 }
231 if (m_is_propfind) {
232 m_logger->Debug(kLogXrdClHttp, "Successful propfind operation on %s (size %lld, isdir %d)", m_url.c_str(), static_cast<long long>(size), isdir);
233 } else {
234 m_logger->Debug(kLogXrdClHttp, "Successful stat operation on %s (size %lld)", m_url.c_str(), static_cast<long long>(size));
235 }
236
237 time_t mtime = m_is_propfind ? m_modtime : m_headers.GetModTime();
238 if (mtime < 0) {
239 mtime = time(NULL);
240 }
241 XrdCl::StatInfo *stat_info;
242 if (m_response_info){
243 auto info = new XrdClHttp::StatResponse("nobody", size,
245 info->SetResponseInfo(MoveResponseInfo());
246 stat_info = info;
247 } else {
248 stat_info = new XrdCl::StatInfo("nobody", size,
250 }
251 obj = new XrdCl::AnyObject();
252 obj->Set(stat_info);
253 } else if (m_response_info) {
254 auto info = new XrdClHttp::OpenResponseInfo();
255 info->SetResponseInfo(MoveResponseInfo());
256 obj = new XrdCl::AnyObject();
257 obj->Set(info);
258 }
259
260 auto handle = m_handler;
261 m_handler = nullptr;
262 handle->HandleResponse(new XrdCl::XRootDStatus(), obj);
263}
@ kXR_FSError
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
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.
@ IsReadable
Read access is allowed.
@ IsDir
This is a directory.
const uint64_t kLogXrdClHttp
const uint16_t errErrorResponse