Coverage Report

Created: 2026-08-07 11:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/service/http/http_client.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "service/http/http_client.h"
19
20
#include <absl/strings/str_split.h>
21
#include <glog/logging.h>
22
#include <unistd.h>
23
24
#include <memory>
25
#include <ostream>
26
27
#include "common/cast_set.h"
28
#include "common/config.h"
29
#include "common/status.h"
30
#include "io/fs/local_file_system.h"
31
#include "runtime/cluster_info.h"
32
#include "runtime/exec_env.h"
33
#include "service/http/http_headers.h"
34
#include "util/security.h"
35
#include "util/stack_util.h"
36
37
namespace doris {
38
class MultiFileSplitter {
39
public:
40
    MultiFileSplitter(std::string local_dir, std::unordered_set<std::string> expected_files)
41
1
            : _local_dir_path(std::move(local_dir)), _expected_files(std::move(expected_files)) {}
42
1
    ~MultiFileSplitter() {
43
1
        if (_fd >= 0) {
44
0
            close(_fd);
45
0
        }
46
47
1
        if (!_status.ok() && !downloaded_files.empty()) {
48
0
            LOG(WARNING) << "download files to " << _local_dir_path << " failed, try remove the "
49
0
                         << downloaded_files.size() << " downloaded files";
50
0
            for (const auto& file : downloaded_files) {
51
0
                remove(file.c_str());
52
0
            }
53
0
        }
54
1
    }
55
56
661
    bool append(const char* data, size_t length) {
57
        // Already failed.
58
661
        if (!_status.ok()) {
59
0
            return false;
60
0
        }
61
62
661
        std::string buf;
63
661
        if (!_buffer.empty()) {
64
0
            buf.swap(_buffer);
65
0
            buf.append(data, length);
66
0
            data = buf.data();
67
0
            length = buf.size();
68
0
        }
69
661
        return append_inner(data, length);
70
661
    }
71
72
1
    Status finish() {
73
1
        if (_status.ok()) {
74
1
            _status = finish_inner();
75
1
        }
76
77
1
        return _status;
78
1
    }
79
80
private:
81
661
    bool append_inner(const char* data, size_t length) {
82
1.39k
        while (length > 0) {
83
729
            int consumed = 0;
84
729
            if (_is_reading_header) {
85
35
                consumed = parse_header(data, length);
86
694
            } else {
87
694
                consumed = append_file(data, length);
88
694
            }
89
90
729
            if (consumed < 0) {
91
0
                return false;
92
0
            }
93
94
729
            DCHECK(consumed <= length);
95
729
            data += consumed;
96
729
            length -= consumed;
97
729
        }
98
661
        return true;
99
661
    }
100
101
35
    int parse_header(const char* data, size_t length) {
102
35
        DCHECK(_fd < 0);
103
104
35
        std::string_view buf(data, length);
105
35
        size_t pos = buf.find("\r\n\r\n");
106
35
        if (pos == std::string::npos) {
107
0
            _buffer.append(data, length);
108
0
            return static_cast<int>(length);
109
0
        }
110
111
        // header already read.
112
35
        _is_reading_header = false;
113
114
35
        bool has_file_name = false;
115
35
        bool has_file_size = false;
116
35
        std::string_view header = buf.substr(0, pos);
117
35
        std::vector<std::string> headers = absl::StrSplit(header, "\r\n", absl::SkipWhitespace());
118
70
        for (auto& s : headers) {
119
70
            size_t header_pos = s.find(':');
120
70
            if (header_pos == std::string::npos) {
121
0
                continue;
122
0
            }
123
70
            std::string_view header_view(s);
124
70
            std::string_view key = header_view.substr(0, header_pos);
125
70
            std::string_view value = header_view.substr(header_pos + 1);
126
70
            if (value.starts_with(' ')) {
127
70
                value.remove_prefix(std::min(value.find_first_not_of(' '), value.size()));
128
70
            }
129
70
            if (key == "File-Name") {
130
35
                _file_name = value;
131
35
                has_file_name = true;
132
35
            } else if (key == "Content-Length") {
133
35
                auto res = std::from_chars(value.data(), value.data() + value.size(), _file_size);
134
35
                if (res.ec != std::errc()) {
135
0
                    std::string error_msg = fmt::format("invalid content length: {}", value);
136
0
                    LOG(WARNING) << "download files to " << _local_dir_path
137
0
                                 << "failed, err=" << error_msg;
138
0
                    _status = Status::HttpError(std::move(error_msg));
139
0
                    return -1;
140
0
                }
141
35
                has_file_size = true;
142
35
            }
143
70
        }
144
145
35
        if (!has_file_name || !has_file_size) {
146
0
            std::string error_msg =
147
0
                    fmt::format("invalid multi part header, has file name: {}, has file size: {}",
148
0
                                has_file_name, has_file_size);
149
0
            LOG(WARNING) << "download files to " << _local_dir_path << "failed, err=" << error_msg;
150
0
            _status = Status::HttpError(std::move(error_msg));
151
0
            return -1;
152
0
        }
153
154
35
        if (!_expected_files.contains(_file_name)) {
155
0
            std::string error_msg = fmt::format("unexpected file: {}", _file_name);
156
0
            LOG(WARNING) << "download files to " << _local_dir_path << "failed, err=" << error_msg;
157
0
            _status = Status::HttpError(std::move(error_msg));
158
0
            return -1;
159
0
        }
160
161
35
        VLOG_DEBUG << "receive file " << _file_name << ", size " << _file_size;
162
163
35
        _written_size = 0;
164
35
        _local_file_path = fmt::format("{}/{}", _local_dir_path, _file_name);
165
35
        _fd = open(_local_file_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
166
35
        if (_fd < 0) {
167
0
            std::string error_msg = "fail to open file to write: " + _local_file_path;
168
0
            LOG(WARNING) << "download files to " << _local_dir_path << "failed, err=" << error_msg;
169
0
            _status = Status::IOError(std::move(error_msg));
170
0
            return -1;
171
0
        }
172
35
        downloaded_files.push_back(_local_file_path);
173
174
35
        return static_cast<int>(pos + 4);
175
35
    }
176
177
694
    int append_file(const char* data, size_t length) {
178
694
        DCHECK(_fd >= 0);
179
694
        DCHECK(_file_size >= _written_size);
180
181
694
        size_t write_size = std::min(length, _file_size - _written_size);
182
694
        if (write_size > 0 && write(_fd, data, write_size) < 0) {
183
0
            auto msg = fmt::format("write file failed, file={}, error={}", _local_file_path,
184
0
                                   strerror(errno));
185
0
            LOG(WARNING) << "download files to " << _local_dir_path << "failed, err=" << msg;
186
0
            _status = Status::HttpError(std::move(msg));
187
0
            return -1;
188
0
        }
189
190
694
        _written_size += write_size;
191
694
        if (_written_size == _file_size) {
192
            // This file has been downloaded, switch to the next one.
193
34
            switch_to_next_file();
194
34
        }
195
196
694
        return cast_set<int>(write_size);
197
694
    }
198
199
1
    Status finish_inner() {
200
1
        if (!_is_reading_header && _written_size == _file_size) {
201
1
            switch_to_next_file();
202
1
        }
203
204
1
        if (_fd >= 0) {
205
            // This file is not completely downloaded.
206
0
            close(_fd);
207
0
            _fd = -1;
208
0
            auto error_msg = fmt::format("file {} is not completely downloaded", _local_file_path);
209
0
            LOG(WARNING) << "download files to " << _local_dir_path << "failed, err=" << error_msg;
210
0
            return Status::HttpError(std::move(error_msg));
211
0
        }
212
213
1
        if (!_expected_files.empty()) {
214
0
            auto error_msg = fmt::format("not all files are downloaded, {} missing files",
215
0
                                         _expected_files.size());
216
0
            LOG(WARNING) << "download files to " << _local_dir_path << "failed, err=" << error_msg;
217
0
            return Status::HttpError(std::move(error_msg));
218
0
        }
219
220
1
        downloaded_files.clear();
221
1
        return Status::OK();
222
1
    }
223
224
35
    void switch_to_next_file() {
225
35
        DCHECK(_fd >= 0);
226
35
        DCHECK(_written_size == _file_size);
227
228
35
        close(_fd);
229
35
        _fd = -1;
230
35
        _expected_files.erase(_file_name);
231
35
        _is_reading_header = true;
232
35
    }
233
234
    const std::string _local_dir_path;
235
    std::string _buffer;
236
    std::unordered_set<std::string> _expected_files;
237
    Status _status;
238
239
    bool _is_reading_header = true;
240
    int _fd = -1;
241
    std::string _local_file_path;
242
    std::string _file_name;
243
    size_t _file_size = 0;
244
    size_t _written_size = 0;
245
    std::vector<std::string> downloaded_files;
246
};
247
248
0
static const char* header_error_msg(CURLHcode code) {
249
0
    switch (code) {
250
0
    case CURLHE_OK:
251
0
        return "OK";
252
0
    case CURLHE_BADINDEX:
253
0
        return "header exists but not with this index ";
254
0
    case CURLHE_MISSING:
255
0
        return "no such header exists";
256
0
    case CURLHE_NOHEADERS:
257
0
        return "no headers at all exist (yet)";
258
0
    case CURLHE_NOREQUEST:
259
0
        return "no request with this number was used";
260
0
    case CURLHE_OUT_OF_MEMORY:
261
0
        return "out of memory while processing";
262
0
    case CURLHE_BAD_ARGUMENT:
263
0
        return "a function argument was not okay";
264
0
    case CURLHE_NOT_BUILT_IN:
265
0
        return "curl_easy_header() was disabled in the build";
266
0
    default:
267
0
        return "unknown";
268
0
    }
269
0
}
270
271
129
HttpClient::HttpClient() = default;
272
273
129
HttpClient::~HttpClient() {
274
129
    if (_curl != nullptr) {
275
113
        curl_easy_cleanup(_curl);
276
113
        _curl = nullptr;
277
113
    }
278
129
    if (_header_list != nullptr) {
279
58
        curl_slist_free_all(_header_list);
280
58
        _header_list = nullptr;
281
58
    }
282
129
}
283
284
113
Status HttpClient::init(const std::string& url, bool set_fail_on_error) {
285
113
    if (_curl == nullptr) {
286
112
        _curl = curl_easy_init();
287
112
        if (_curl == nullptr) {
288
0
            return Status::InternalError("fail to initialize curl");
289
0
        }
290
112
    } else {
291
1
        curl_easy_reset(_curl);
292
1
    }
293
294
113
    if (_header_list != nullptr) {
295
0
        curl_slist_free_all(_header_list);
296
0
        _header_list = nullptr;
297
0
    }
298
    // set error_buf
299
113
    _error_buf[0] = 0;
300
113
    auto code = curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, _error_buf);
301
113
    if (code != CURLE_OK) {
302
0
        LOG(WARNING) << "fail to set CURLOPT_ERRORBUFFER, msg=" << _to_errmsg(code);
303
0
        return Status::InternalError("fail to set error buffer");
304
0
    }
305
    // forbid signals
306
113
    code = curl_easy_setopt(_curl, CURLOPT_NOSIGNAL, 1L);
307
113
    if (code != CURLE_OK) {
308
0
        LOG(WARNING) << "fail to set CURLOPT_NOSIGNAL, msg=" << _to_errmsg(code);
309
0
        return Status::InternalError("fail to set CURLOPT_NOSIGNAL");
310
0
    }
311
    // set fail on error
312
    // When this option is set to `1L` (enabled), libcurl will return an error directly
313
    // when encountering HTTP error codes (>= 400), without reading the body of the error response.
314
113
    if (set_fail_on_error) {
315
88
        code = curl_easy_setopt(_curl, CURLOPT_FAILONERROR, 1L);
316
88
        if (code != CURLE_OK) {
317
0
            LOG(WARNING) << "fail to set CURLOPT_FAILONERROR, msg=" << _to_errmsg(code);
318
0
            return Status::InternalError("fail to set CURLOPT_FAILONERROR");
319
0
        }
320
88
    }
321
    // set redirect
322
113
    code = curl_easy_setopt(_curl, CURLOPT_FOLLOWLOCATION, 1L);
323
113
    if (code != CURLE_OK) {
324
0
        LOG(WARNING) << "fail to set CURLOPT_FOLLOWLOCATION, msg=" << _to_errmsg(code);
325
0
        return Status::InternalError("fail to set CURLOPT_FOLLOWLOCATION");
326
0
    }
327
113
    code = curl_easy_setopt(_curl, CURLOPT_MAXREDIRS, 20);
328
113
    if (code != CURLE_OK) {
329
0
        LOG(WARNING) << "fail to set CURLOPT_MAXREDIRS, msg=" << _to_errmsg(code);
330
0
        return Status::InternalError("fail to set CURLOPT_MAXREDIRS");
331
0
    }
332
333
708
    curl_write_callback callback = [](char* buffer, size_t size, size_t nmemb, void* param) {
334
708
        auto* client = (HttpClient*)param;
335
708
        return client->on_response_data(buffer, size * nmemb);
336
708
    };
337
338
    // set callback function
339
113
    code = curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, callback);
340
113
    if (code != CURLE_OK) {
341
0
        LOG(WARNING) << "fail to set CURLOPT_WRITEFUNCTION, msg=" << _to_errmsg(code);
342
0
        return Status::InternalError("fail to set CURLOPT_WRITEFUNCTION");
343
0
    }
344
113
    code = curl_easy_setopt(_curl, CURLOPT_WRITEDATA, (void*)this);
345
113
    if (code != CURLE_OK) {
346
0
        LOG(WARNING) << "fail to set CURLOPT_WRITEDATA, msg=" << _to_errmsg(code);
347
0
        return Status::InternalError("fail to set CURLOPT_WRITEDATA");
348
0
    }
349
350
113
    std::string escaped_url;
351
113
    RETURN_IF_ERROR(_escape_url(url, &escaped_url));
352
    // set url
353
113
    code = curl_easy_setopt(_curl, CURLOPT_URL, escaped_url.c_str());
354
113
    if (code != CURLE_OK) {
355
0
        LOG(WARNING) << "failed to set CURLOPT_URL, errmsg=" << _to_errmsg(code);
356
0
        return Status::InternalError("fail to set CURLOPT_URL");
357
0
    }
358
359
#ifndef BE_TEST
360
    set_auth_token(ExecEnv::GetInstance()->cluster_info()->curr_auth_token);
361
#endif
362
113
    return Status::OK();
363
113
}
364
365
112
void HttpClient::set_method(HttpMethod method) {
366
112
    _method = method;
367
112
    switch (method) {
368
49
    case GET:
369
49
        curl_easy_setopt(_curl, CURLOPT_HTTPGET, 1L);
370
49
        return;
371
0
    case PUT:
372
0
        curl_easy_setopt(_curl, CURLOPT_UPLOAD, 1L);
373
0
        return;
374
56
    case POST:
375
56
        curl_easy_setopt(_curl, CURLOPT_POST, 1L);
376
56
        return;
377
0
    case DELETE:
378
0
        curl_easy_setopt(_curl, CURLOPT_CUSTOMREQUEST, "DELETE");
379
0
        return;
380
7
    case HEAD:
381
7
        curl_easy_setopt(_curl, CURLOPT_NOBODY, 1L);
382
7
        return;
383
0
    case OPTIONS:
384
0
        curl_easy_setopt(_curl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
385
0
        return;
386
0
    default:
387
0
        return;
388
112
    }
389
112
}
390
391
3
void HttpClient::set_speed_limit() {
392
3
    curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_LIMIT, config::download_low_speed_limit_kbps * 1024);
393
3
    curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_TIME, config::download_low_speed_time);
394
3
    curl_easy_setopt(_curl, CURLOPT_MAX_RECV_SPEED_LARGE, config::max_download_speed_kbps * 1024);
395
3
}
396
397
708
size_t HttpClient::on_response_data(const void* data, size_t length) {
398
708
    if (*_callback != nullptr) {
399
708
        bool is_continue = (*_callback)(data, length);
400
708
        if (!is_continue) {
401
0
            return -1;
402
0
        }
403
708
    }
404
708
    return length;
405
708
}
406
407
16
Status HttpClient::execute_post_request(const std::string& payload, std::string* response) {
408
16
    set_method(POST);
409
16
    set_payload(payload);
410
16
    return execute(response);
411
16
}
412
413
0
Status HttpClient::execute_put_request(const std::string& payload, std::string* response) {
414
0
    set_payload(payload);
415
0
    curl_easy_setopt(_curl, CURLOPT_CUSTOMREQUEST, "PUT");
416
0
    return execute(response);
417
0
}
418
419
0
Status HttpClient::execute_delete_request(const std::string& payload, std::string* response) {
420
0
    set_method(DELETE);
421
0
    set_payload(payload);
422
0
    return execute(response);
423
0
}
424
425
113
Status HttpClient::execute(const std::function<bool(const void* data, size_t length)>& callback) {
426
113
    if (VLOG_DEBUG_IS_ON) {
427
0
        VLOG_DEBUG << "execute http " << to_method_desc(_method) << " request, url " << _get_url();
428
0
    }
429
113
    _callback = &callback;
430
113
    auto code = curl_easy_perform(_curl);
431
113
    if (code != CURLE_OK) {
432
63
        std::string url = mask_token(_get_url());
433
63
        LOG(WARNING) << "fail to execute HTTP client, errmsg=" << _to_errmsg(code)
434
63
                     << ", trace=" << get_stack_trace() << ", url=" << url;
435
63
        std::string errmsg = fmt::format("{}, url={}", _to_errmsg(code), url);
436
63
        return Status::HttpError(std::move(errmsg));
437
63
    }
438
50
    if (VLOG_DEBUG_IS_ON) {
439
0
        VLOG_DEBUG << "execute http " << to_method_desc(_method) << " request, url " << _get_url()
440
0
                   << " done";
441
0
    }
442
50
    return Status::OK();
443
113
}
444
445
3
Status HttpClient::get_content_md5(std::string* md5) const {
446
3
    struct curl_header* header_ptr;
447
3
    auto code = curl_easy_header(_curl, HttpHeaders::CONTENT_MD5, 0, CURLH_HEADER, 0, &header_ptr);
448
3
    if (code == CURLHE_MISSING || code == CURLHE_NOHEADERS) {
449
        // no such headers exists
450
1
        md5->clear();
451
1
        return Status::OK();
452
2
    } else if (code != CURLHE_OK) {
453
0
        auto msg = fmt::format("failed to get http header {}: {} ({})", HttpHeaders::CONTENT_MD5,
454
0
                               header_error_msg(code), code);
455
0
        LOG(WARNING) << msg << ", trace=" << get_stack_trace();
456
0
        return Status::HttpError(std::move(msg));
457
0
    }
458
459
2
    *md5 = header_ptr->value;
460
2
    return Status::OK();
461
3
}
462
463
2
Status HttpClient::download(const std::string& local_path) {
464
2
    set_method(GET);
465
2
    set_speed_limit();
466
467
    // remove the file if it exists, to avoid change the linked files unexpectedly
468
2
    bool exist = false;
469
2
    RETURN_IF_ERROR(io::global_local_filesystem()->exists(local_path, &exist));
470
2
    if (exist) {
471
0
        remove(local_path.c_str());
472
0
    }
473
474
2
    auto fp_closer = [](FILE* fp) { fclose(fp); };
475
2
    std::unique_ptr<FILE, decltype(fp_closer)> fp(fopen(local_path.c_str(), "w"), fp_closer);
476
2
    if (fp == nullptr) {
477
0
        LOG(WARNING) << "open file failed, file=" << local_path;
478
0
        return Status::InternalError("open file failed");
479
0
    }
480
2
    Status status;
481
2
    auto callback = [&status, &fp, &local_path](const void* data, size_t length) {
482
2
        auto res = fwrite(data, length, 1, fp.get());
483
2
        if (res != 1) {
484
0
            LOG(WARNING) << "fail to write data to file, file=" << local_path
485
0
                         << ", error=" << ferror(fp.get());
486
0
            status = Status::InternalError("fail to write data when download");
487
0
            return false;
488
0
        }
489
2
        return true;
490
2
    };
491
492
2
    if (auto s = execute(callback); !s.ok()) {
493
0
        status = s;
494
0
    }
495
2
    if (!status.ok()) {
496
0
        remove(local_path.c_str());
497
0
    }
498
2
    return status;
499
2
}
500
501
Status HttpClient::download_multi_files(const std::string& local_dir,
502
1
                                        const std::unordered_set<std::string>& expected_files) {
503
1
    set_speed_limit();
504
505
1
    MultiFileSplitter splitter(local_dir, expected_files);
506
661
    auto callback = [&](const void* data, size_t length) {
507
661
        return splitter.append(reinterpret_cast<const char*>(data), length);
508
661
    };
509
1
    if (auto s = execute(callback); !s.ok()) {
510
0
        return s;
511
0
    }
512
1
    return splitter.finish();
513
1
}
514
515
104
Status HttpClient::execute(std::string* response) {
516
104
    auto callback = [response](const void* data, size_t length) {
517
45
        response->append((char*)data, length);
518
45
        return true;
519
45
    };
520
104
    return execute(callback);
521
104
}
522
523
128
const char* HttpClient::_to_errmsg(CURLcode code) const {
524
128
    if (_error_buf[0] == 0) {
525
0
        return curl_easy_strerror(code);
526
0
    }
527
128
    return _error_buf;
528
128
}
529
530
63
const char* HttpClient::_get_url() const {
531
63
    const char* url = nullptr;
532
63
    curl_easy_getinfo(_curl, CURLINFO_EFFECTIVE_URL, &url);
533
63
    if (!url) {
534
0
        url = "<unknown>";
535
0
    }
536
63
    return url;
537
63
}
538
539
// execute remote call action with retry
540
Status HttpClient::execute(int retry_times, int sleep_time,
541
0
                           const std::function<Status(HttpClient*)>& callback) {
542
0
    Status status;
543
0
    for (int i = 0; i < retry_times; ++i) {
544
0
        status = callback(this);
545
0
        if (status.ok()) {
546
0
            auto http_status = get_http_status();
547
0
            if (http_status == 200) {
548
0
                return status;
549
0
            } else {
550
0
                std::string url = mask_token(_get_url());
551
0
                auto error_msg = fmt::format("http status code is not 200, code={}, url={}",
552
0
                                             http_status, url);
553
0
                LOG(WARNING) << error_msg;
554
0
                return Status::HttpError(error_msg);
555
0
            }
556
0
        }
557
0
        sleep(sleep_time);
558
0
    }
559
0
    return status;
560
0
}
561
562
Status HttpClient::execute_with_retry(int retry_times, int sleep_time,
563
22
                                      const std::function<Status(HttpClient*)>& callback) {
564
22
    Status status;
565
61
    for (int i = 0; i < retry_times; ++i) {
566
48
        HttpClient client;
567
48
        status = callback(&client);
568
48
        if (status.ok()) {
569
9
            auto http_status = client.get_http_status();
570
9
            if (http_status == 200) {
571
9
                return status;
572
9
            } else {
573
0
                std::string url = mask_token(client._get_url());
574
0
                auto error_msg = fmt::format("http status code is not 200, code={}, url={}",
575
0
                                             http_status, url);
576
0
                LOG(WARNING) << error_msg;
577
0
                return Status::HttpError(error_msg);
578
0
            }
579
9
        }
580
39
        sleep(sleep_time);
581
39
    }
582
13
    return status;
583
22
}
584
585
// http://example.com/page?param1=value1&param2=value+with+spaces#section
586
120
Status HttpClient::_escape_url(const std::string& url, std::string* escaped_url) {
587
120
    size_t query_pos = url.find('?');
588
120
    if (query_pos == std::string::npos) {
589
84
        *escaped_url = url;
590
84
        return Status::OK();
591
84
    }
592
36
    size_t fragment_pos = url.find('#');
593
36
    std::string query;
594
36
    std::string fragment;
595
596
36
    if (fragment_pos == std::string::npos) {
597
35
        query = url.substr(query_pos + 1, url.length() - query_pos - 1);
598
35
    } else {
599
1
        query = url.substr(query_pos + 1, fragment_pos - query_pos - 1);
600
1
        fragment = url.substr(fragment_pos, url.length() - fragment_pos);
601
1
    }
602
603
36
    std::string encoded_query;
604
36
    size_t ampersand_pos = query.find('&');
605
36
    size_t equal_pos;
606
607
36
    if (ampersand_pos == std::string::npos) {
608
18
        ampersand_pos = query.length();
609
18
    }
610
611
64
    while (true) {
612
64
        equal_pos = query.find('=');
613
64
        if (equal_pos != std::string::npos) {
614
61
            std::string key = query.substr(0, equal_pos);
615
61
            std::string value = query.substr(equal_pos + 1, ampersand_pos - equal_pos - 1);
616
617
61
            auto encoded_value = std::unique_ptr<char, decltype(&curl_free)>(
618
61
                    curl_easy_escape(_curl, value.c_str(), cast_set<int>(value.length())),
619
61
                    &curl_free);
620
61
            if (encoded_value) {
621
61
                encoded_query += key + "=" + std::string(encoded_value.get());
622
61
            } else {
623
0
                return Status::InternalError("escape url failed, url={}", url);
624
0
            }
625
61
        } else {
626
3
            encoded_query += query.substr(0, ampersand_pos);
627
3
        }
628
629
64
        if (ampersand_pos == query.length() || ampersand_pos == std::string::npos) {
630
36
            break;
631
36
        }
632
633
28
        encoded_query += "&";
634
28
        query = query.substr(ampersand_pos + 1);
635
28
        ampersand_pos = query.find('&');
636
28
    }
637
36
    *escaped_url = url.substr(0, query_pos + 1) + encoded_query + fragment;
638
36
    return Status::OK();
639
36
}
640
} // namespace doris