39std::string urlquote(
const std::string input) {
41 output.reserve(3 * input.size());
42 for (
char val : input) {
43 if ((val >= 48 && val <= 57) ||
44 (val >= 65 && val <= 90) ||
45 (val >= 97 && val <= 122) ||
46 (val == 95 || val == 46 || val == 45 || val == 126 ||
51 output +=
"%" + std::to_string(val);
59 StatHandler(
const std::string &path,
const std::string &s3_url, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t timeout, XrdCl::Log &log) :
62 m_header_callout(header_callout),
68 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response)
override;
72 XrdCl::ResponseHandler *m_handler{
nullptr};
73 XrdClHttp::HeaderCallout *m_header_callout{
nullptr};
85 StatHandlerDirectory(XrdCl::ResponseHandler *handler) :
89 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response)
override;
92 XrdCl::ResponseHandler *m_handler{
nullptr};
98 DirListResponseHandler(
bool existence_check,
const std::string &url,
const std::string &prefix, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t expiry, XrdCl::Log &log) :
99 m_existence_check(existence_check),
101 m_header_callout(header_callout),
104 m_host(Factory::ExtractHostname(url)),
109 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response)
override;
115 bool m_existence_check;
118 XrdClHttp::HeaderCallout *m_header_callout{
nullptr};
120 std::string m_prefix;
123 std::unique_ptr<XrdCl::DirectoryList> dirlist{
new XrdCl::DirectoryList()};
125 XrdCl::ResponseHandler *m_handler{
nullptr};
126 XrdCl::Log &m_logger;
132 MkdirHandler(XrdCl::File *file, XrdCl::ResponseHandler *handler, time_t timeout) :
133 m_expiry(time(NULL) + (timeout ? timeout : 30)),
138 virtual void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response)
override;
142 bool m_started_close{
false};
143 std::unique_ptr<XrdCl::File> m_file;
144 XrdCl::ResponseHandler *m_handler{
nullptr};
149 std::unique_ptr<StatHandler> self(
this);
150 std::unique_ptr<XrdCl::AnyObject> response_holder(response_raw);
151 std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
154 if (m_handler) {
return m_handler->
HandleResponse(status.release(), response_holder.release());}
159 if (m_handler) {
return m_handler->
HandleResponse(status.release(), response_holder.release());}
165 std::string https_url, err_msg;
172 obj = obj.substr(0, obj.find(
'?'));
173 if (!obj.empty() && obj.back() !=
'/') {
176 auto query_loc = https_url.find(
'?');
177 https_url += (query_loc == std::string::npos) ?
"?" :
"&";
178 https_url +=
"list-type=2&delimiter=/&encoding-type=url";
181 https_url +=
"&prefix=" + urlquote(obj);
184 auto expiry = time(NULL) + m_timeout;
189 new DirListResponseHandler(
190 true, https_url, obj, m_header_callout,
new StatHandlerDirectory(m_handler), expiry, m_logger
195 if (m_handler)
return m_handler->
HandleResponse(
new XrdCl::XRootDStatus(st), response_holder.release());
201StatHandlerDirectory::HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) {
202 std::unique_ptr<StatHandlerDirectory> self(
this);
208 if (!status || !status->
IsOK()) {
213 XrdCl::DirectoryList *list =
nullptr;
216 if (list && !sentinel.empty()) {
217 for (
auto it = list->
Begin(); it != list->
End(); ++it) {
218 if ((*it)->GetName() != sentinel) {
221 auto *info = (*it)->GetStatInfo();
223 mtime =
static_cast<time_t
>(info->GetModTime());
230 auto obj =
new XrdCl::AnyObject();
237DirListResponseHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw) {
238 std::unique_ptr<DirListResponseHandler> self(
this);
239 std::unique_ptr<XrdCl::AnyObject> response(response_raw);
240 std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
244 if (!status || !status->
IsOK()) {
245 return m_handler->
HandleResponse(status.release(), response.release());
249 m_logger.
Error(
kLogXrdClS3,
"Directory listing returned without any response object.");
253 XrdCl::Buffer *buffer =
nullptr;
254 response->
Get(buffer);
256 m_logger.
Error(
kLogXrdClS3,
"Directory listing response object was not a buffer.");
263 doc.Parse(buffer_str.c_str());
265 std::string errMsg =
"Error when parsing S3 endpoint's listing response: " + std::string(doc.ErrorDesc());
270 auto elem = doc.RootElement();
271 if (strcmp(elem->Value(),
"ListBucketResult")) {
273 "S3 ListBucket response is not rooted with ListBucketResult element"),
nullptr);
311 bool isTruncated =
false;
313 bool found_prefix_marker =
false;
314 auto relativeName = [&](std::string_view encoded) {
316 if (!m_prefix.empty() &&
317 name.compare(0, m_prefix.size(), m_prefix) == 0) {
318 name.erase(0, m_prefix.size());
320 if (!name.empty() && name.back() ==
'/') {
325 for (
auto child = elem->FirstChildElement();
child !=
nullptr;
327 if (!strcmp(
child->Value(),
"IsTruncated")) {
328 auto text =
child->GetText();
329 if (!strcasecmp(text,
"true")) {
331 }
else if (!strcasecmp(text,
"false")) {
334 }
else if (!strcmp(
child->Value(),
"CommonPrefixes")) {
335 auto prefix =
child->FirstChildElement(
"Prefix");
336 if (prefix !=
nullptr) {
337 auto prefixChar = prefix->GetText();
338 if (prefixChar !=
nullptr) {
340 if (!prefixStr.empty()) {
341 auto name = relativeName(prefixStr);
343 found_prefix_marker =
true;
349 new XrdCl::DirectoryList::ListEntry(
350 m_host, std::move(name),
new XrdCl::StatInfo(
351 "nobody", 4096, flags, 0)));
356 }
else if (!strcmp(
child->Value(),
"Contents")) {
357 std::string_view keyStr;
358 auto key =
child->FirstChildElement(
"Key");
359 if (key !=
nullptr) {
360 auto keyChar = key->GetText();
361 if (keyChar !=
nullptr) {
365 if (keyStr.empty()) {
368 auto name = relativeName(keyStr);
370 found_prefix_marker =
true;
371 if (m_existence_check)
break;
375 bool goodSize =
false;
376 auto sizeElem =
child->FirstChildElement(
"Size");
377 if (sizeElem !=
nullptr) {
378 auto sizeChar = sizeElem->GetText();
379 if (sizeChar !=
nullptr && *sizeChar) {
380 auto res = std::from_chars(sizeChar, sizeChar + strlen(sizeChar), size);
381 if (res.ec == std::errc()) {
386 auto lastModifiedElem =
child->FirstChildElement(
"LastModified");
387 time_t lastModified = 0;
388 if (lastModifiedElem !=
nullptr) {
389 auto lastModifiedChar = lastModifiedElem->GetText();
390 if (lastModifiedChar !=
nullptr) {
393 if (strptime(lastModifiedChar,
"%Y-%m-%dT%H:%M:%S", &tm) !=
nullptr) {
395 lastModified = timegm(&tm);
402 new XrdCl::DirectoryList::ListEntry(
403 m_host, std::move(name),
new XrdCl::StatInfo(
404 "nobody", size, flags, lastModified)));
406 }
else if (!strcmp(
child->Value(),
"NextContinuationToken")) {
407 auto ctChar =
child->GetText();
413 const bool directory_exists = found_prefix_marker || dirlist->
GetSize();
418 if (!isTruncated || (m_existence_check && (directory_exists || m_prefix.empty()))) {
422 if (!directory_exists && !m_prefix.empty()) {
429 auto object =
new XrdCl::AnyObject();
430 object->Set(dirlist.release());
432 new XrdCl::XRootDStatus{},
440 auto url = m_url +
"&continuation-token=" + ct;
443 time_t now = time(NULL);
444 if (now >= m_expiry) {
452 auto st =
DownloadUrl(url, m_header_callout,
this, m_expiry - now);
463MkdirHandler::HandleResponse(XrdCl::XRootDStatus *status_raw, XrdCl::AnyObject *response_raw)
465 std::unique_ptr<MkdirHandler> self(
this);
466 std::unique_ptr<XrdCl::XRootDStatus> status(status_raw);
467 std::unique_ptr<XrdCl::AnyObject> response(response_raw);
469 if (!status || !status->
IsOK() || m_started_close) {
470 if (m_handler) m_handler->
HandleResponse(status.release(), response.release());
474 time_t now = time(NULL);
475 if (now >= m_expiry) {
484 m_started_close =
true;
485 auto st = m_file->
Close(
this, m_expiry - now);
487 if (m_handler) m_handler->
HandleResponse(status.release(), response.release());
501 m_url.SetParams(map);
503 m_logger->Debug(
kLogXrdClS3,
"S3 filesystem constructed with URL: %s.",
504 m_url.GetURL().c_str());
515 std::string https_url, err_msg;
521 obj = obj.substr(0, obj.find(
'?'));
522 if (!obj.empty() && obj.back() !=
'/') {
525 auto query_loc = https_url.find(
'?');
526 https_url += (query_loc == std::string::npos) ?
"?" :
"&";
527 https_url +=
"list-type=2&delimiter=/&encoding-type=url";
530 https_url +=
"&prefix=" + urlquote(obj);
541 auto expiry = time(NULL) + timeout;
546 new DirListResponseHandler(
547 false, https_url, obj, &m_header_callout, handler, expiry, *m_logger
553std::pair<XrdCl::XRootDStatus, XrdCl::FileSystem*>
554Filesystem::GetFSHandle(
const std::string &path) {
556 std::string https_url, err_msg;
560 auto loc = https_url.find(
'/', 8);
561 if (loc == std::string::npos) {
564 auto endpoint = https_url.substr(0, loc);
566 std::shared_lock lock(m_handles_mutex);
567 auto iter = m_handles.find(endpoint);
568 if (iter != m_handles.end()) {
569 return std::make_pair(XrdCl::XRootDStatus{}, iter->second);
576 std::unique_lock lock(m_handles_mutex);
577 auto iter = m_handles.find(endpoint);
578 if (iter != m_handles.end()) {
579 return std::make_pair(XrdCl::XRootDStatus{}, iter->second);
581 auto fs =
new XrdCl::FileSystem(url);
582 std::stringstream ss;
583 ss << std::hex << reinterpret_cast<long long>(&m_header_callout);
584 if (!fs->SetProperty(
"XrdClHttpHeaderCallout", ss.str())) {
588 m_handles[endpoint] = fs;
590 return std::make_pair(XrdCl::XRootDStatus{}, fs);
595 std::string &value)
const
597 std::unique_lock lock(m_properties_mutex);
598 const auto p = m_properties.find(name);
599 if (p == std::end(m_properties)) {
614 auto [st, fs] = GetFSHandle(cleaned_path);
618 return fs->Locate(cleaned_path, flags, handler, timeout);
629 if (sentinel.empty()) {
633 auto loc = input_path.find(
'?');
634 auto path = input_path.substr(0, loc);
635 if (!path.empty() && path[path.size() - 1] !=
'/') path +=
"/";
637 if (loc != std::string::npos) {
638 path += input_path.substr(loc);
642 std::string https_url, err_msg;
650 auto callout_loc =
reinterpret_cast<long long>(&m_header_callout);
651 size_t buf_size = 16;
652 char callout_buf[buf_size];
653 std::to_chars_result result = std::to_chars(callout_buf, callout_buf + buf_size - 1, callout_loc, 16);
654 if (result.ec == std::errc{}) {
655 std::string callout_str(callout_buf, result.ptr - callout_buf);
656 http_file->
SetProperty(
"XrdClHttpHeaderCallout", callout_str);
659 MkdirHandler *mkdirHandler =
new MkdirHandler(http_file, handler, timeout);
674 auto [st, fs] = GetFSHandle(cleaned_path);
680 return fs->Query(queryCode, cleanedArg, handler, timeout);
690 auto [st, fs] = GetFSHandle(cleaned_path);
694 return fs->Rm(cleaned_path, handler, timeout);
703 if (sentinel.empty()) {
707 auto loc = input_path.find(
'?');
708 auto path = input_path.substr(0, loc);
709 if (!path.empty() && path[path.size() - 1] !=
'/') path +=
"/";
711 if (loc != std::string::npos) {
712 path += input_path.substr(loc);
714 return Rm(path, handler, timeout);
720 const std::string &value)
722 std::unique_lock lock(m_properties_mutex);
723 m_properties[name] = value;
733 std::string https_url, err_msg, obj;
738 auto query = obj.find(
'?');
739 if (query != std::string::npos) {
744 auto query_loc = https_url.find(
'?');
745 https_url += (query_loc == std::string::npos) ?
"?" :
"&";
746 https_url +=
"list-type=2&delimiter=/&encoding-type=url";
747 auto expiry = time(NULL) + timeout;
751 new DirListResponseHandler(
752 true, https_url,
"", &m_header_callout,
new StatHandlerDirectory(handler), expiry, *m_logger
757 auto [st, fs] = GetFSHandle(cleaned_path);
761 return fs->Stat(cleaned_path,
new StatHandler(cleaned_path, m_url.GetURL(), &m_header_callout, handler, timeout, *m_logger), timeout);
764std::shared_ptr<XrdClHttp::HeaderCallout::HeaderList>
765Filesystem::S3HeaderCallout::GetHeaders(
const std::string &verb,
766 const std::string &url,
769 std::string auth_token, err_msg;
770 std::shared_ptr<HeaderList> header_list(
new HeaderList(headers));
772 header_list->emplace_back(
"Authorization", auth_token);
774 m_parent.m_logger->
Error(
kLogXrdClS3,
"Failed to generate V4 signature: %s", err_msg.c_str());
static std::string CleanObjectName(const std::string &object)
static bool GenerateHttpUrl(const std::string &s3_url, std::string &https_url, std::string *obj_result, std::string &err_msg)
static bool GenerateV4Signature(const std::string &url, const std::string &verb, std::vector< std::pair< std::string, std::string > > &headers, std::string &auth_token, std::string &err_msg)
static std::string_view TrimView(const std::string_view str)
static const std::string & GetMkdirSentinel()
virtual XrdCl::XRootDStatus MkDir(const std::string &path, XrdCl::MkDirFlags::Flags flags, XrdCl::Access::Mode mode, XrdCl::ResponseHandler *handler, time_t timeout) override
Filesystem(const std::string &, XrdCl::Log *log)
virtual bool SetProperty(const std::string &name, const std::string &value) override
virtual XrdCl::XRootDStatus DirList(const std::string &path, XrdCl::DirListFlags::Flags flags, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Rm(const std::string &path, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Stat(const std::string &path, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual XrdCl::XRootDStatus Locate(const std::string &path, XrdCl::OpenFlags::Flags flags, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual ~Filesystem() noexcept
virtual XrdCl::XRootDStatus RmDir(const std::string &path, XrdCl::ResponseHandler *handler, time_t timeout) override
virtual bool GetProperty(const std::string &name, std::string &value) const override
virtual XrdCl::XRootDStatus Query(XrdCl::QueryCode::Code queryCode, const XrdCl::Buffer &arg, XrdCl::ResponseHandler *handler, time_t timeout) override
void Get(Type &object)
Retrieve the object being held.
Binary blob representation.
void FromString(const std::string str)
Fill the buffer from a string.
const char * GetBuffer(uint32_t offset=0) const
Get the message buffer.
uint32_t GetSize() const
Get the size of the message.
std::string ToString() const
Convert the buffer to a string.
static Env * GetEnv()
Get default client environment.
void Add(ListEntry *entry)
Add an entry to the list - takes ownership.
uint32_t GetSize() const
Get the size of the listing.
Iterator End()
Get the end iterator.
Iterator Begin()
Get the begin iterator.
bool GetInt(const std::string &key, int &value)
XRootDStatus Open(const std::string &url, OpenFlags::Flags flags, Access::Mode mode, ResponseHandler *handler, time_t timeout=0) XRD_WARN_UNUSED_RESULT
XRootDStatus Close(ResponseHandler *handler, time_t timeout=0) XRD_WARN_UNUSED_RESULT
bool SetProperty(const std::string &name, const std::string &value)
void Error(uint64_t topic, const char *format,...)
Report an error.
Handle an async response.
virtual void HandleResponse(XRootDStatus *status, AnyObject *response)
@ IsReadable
Read access is allowed.
@ IsDir
This is a directory.
@ XBitSet
Executable/searchable bit set.
std::map< std::string, std::string > ParamsMap
bool FromString(const std::string &url)
Parse a string and fill the URL fields.
std::string GetURL() const
Get the URL.
static std::string UrlDecode(const std::string &input)
static std::string JoinUrl(const std::string &base, const std::string &path)
XrdCl::XRootDStatus DownloadUrl(const std::string &url, XrdClHttp::HeaderCallout *header_callout, XrdCl::ResponseHandler *handler, time_t timeout)
const uint64_t kLogXrdClS3
const uint16_t errInvalidAddr
const uint16_t errErrorResponse
const uint16_t errOperationExpired
const uint16_t errNotImplemented
Operation is not implemented.
const uint16_t stError
An error occurred that could potentially be retried.
const uint16_t errInvalidResponse
const int DefaultRequestTimeout
Flags
Open flags, may be or'd when appropriate.
@ Write
Open only for writing.
Code
XRootD query request codes.
@ XAttr
Query file extended attributes.
@ Checksum
Query file checksum.
bool IsOK() const
We're fine.