| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "http/utils.h" |
| |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <ostream> |
| #include <vector> |
| |
| #include "common/config.h" |
| #include "common/logging.h" |
| #include "common/status.h" |
| #include "common/utils.h" |
| #include "http/http_channel.h" |
| #include "http/http_common.h" |
| #include "http/http_headers.h" |
| #include "http/http_method.h" |
| #include "http/http_request.h" |
| #include "http/http_status.h" |
| #include "io/fs/file_system.h" |
| #include "io/fs/local_file_system.h" |
| #include "olap/wal/wal_manager.h" |
| #include "runtime/exec_env.h" |
| #include "util/path_util.h" |
| #include "util/url_coding.h" |
| |
| namespace doris { |
| |
| std::string encode_basic_auth(const std::string& user, const std::string& passwd) { |
| std::string auth = user + ":" + passwd; |
| std::string encoded_auth; |
| base64_encode(auth, &encoded_auth); |
| static std::string s_prefix = "Basic "; |
| return s_prefix + encoded_auth; |
| } |
| |
| bool parse_basic_auth(const HttpRequest& req, std::string* user, std::string* passwd) { |
| const char k_basic[] = "Basic "; |
| auto& auth = req.header(HttpHeaders::AUTHORIZATION); |
| if (auth.compare(0, sizeof(k_basic) - 1, k_basic, sizeof(k_basic) - 1) != 0) { |
| return false; |
| } |
| std::string encoded_str = auth.substr(sizeof(k_basic) - 1); |
| std::string decoded_auth; |
| if (!base64_decode(encoded_str, &decoded_auth)) { |
| return false; |
| } |
| auto pos = decoded_auth.find(':'); |
| if (pos == std::string::npos) { |
| return false; |
| } |
| user->assign(decoded_auth.c_str(), pos); |
| passwd->assign(decoded_auth.c_str() + pos + 1); |
| |
| return true; |
| } |
| |
| bool parse_basic_auth(const HttpRequest& req, AuthInfo* auth) { |
| auto& token = req.header("token"); |
| auto& auth_code = req.header(HTTP_AUTH_CODE); |
| if (!token.empty()) { |
| auth->token = token; |
| } else if (!auth_code.empty()) { |
| auth->auth_code = std::stoll(auth_code); |
| } else { |
| std::string full_user; |
| if (!parse_basic_auth(req, &full_user, &auth->passwd)) { |
| return false; |
| } |
| auto pos = full_user.find('@'); |
| if (pos != std::string::npos) { |
| auth->user.assign(full_user.data(), pos); |
| auth->cluster.assign(full_user.data() + pos + 1); |
| } else { |
| auth->user = full_user; |
| } |
| } |
| |
| // set user ip |
| if (req.remote_host() != nullptr) { |
| auth->user_ip.assign(req.remote_host()); |
| } else { |
| auth->user_ip.assign(""); |
| } |
| |
| return true; |
| } |
| |
| // Do a simple decision, only deal a few type |
| std::string get_content_type(const std::string& file_name) { |
| std::string file_ext = path_util::file_extension(file_name); |
| VLOG_TRACE << "file_name: " << file_name << "; file extension: [" << file_ext << "]"; |
| if (file_ext == std::string(".html") || file_ext == std::string(".htm")) { |
| return std::string("text/html; charset=utf-8"); |
| } else if (file_ext == std::string(".js")) { |
| return std::string("application/javascript; charset=utf-8"); |
| } else if (file_ext == std::string(".css")) { |
| return std::string("text/css; charset=utf-8"); |
| } else if (file_ext == std::string(".txt")) { |
| return std::string("text/plain; charset=utf-8"); |
| } else if (file_ext == std::string(".png")) { |
| return std::string("image/png"); |
| } else if (file_ext == std::string(".ico")) { |
| return std::string("image/x-icon"); |
| } else { |
| return "text/plain; charset=utf-8"; |
| } |
| return ""; |
| } |
| |
| void do_file_response(const std::string& file_path, HttpRequest* req, |
| bufferevent_rate_limit_group* rate_limit_group) { |
| if (file_path.find("..") != std::string::npos) { |
| LOG(WARNING) << "Not allowed to read relative path: " << file_path; |
| HttpChannel::send_error(req, HttpStatus::FORBIDDEN); |
| return; |
| } |
| |
| // read file content and send response |
| int fd = open(file_path.c_str(), O_RDONLY); |
| if (fd < 0) { |
| LOG(WARNING) << "Failed to open file: " << file_path; |
| HttpChannel::send_error(req, HttpStatus::NOT_FOUND); |
| return; |
| } |
| struct stat st; |
| auto res = fstat(fd, &st); |
| if (res < 0) { |
| close(fd); |
| LOG(WARNING) << "Failed to open file: " << file_path; |
| HttpChannel::send_error(req, HttpStatus::NOT_FOUND); |
| return; |
| } |
| |
| int64_t file_size = st.st_size; |
| |
| // TODO(lingbin): process "IF_MODIFIED_SINCE" header |
| // TODO(lingbin): process "RANGE" header |
| const std::string& range_header = req->header(HttpHeaders::RANGE); |
| if (!range_header.empty()) { |
| // analyse range header |
| } |
| |
| req->add_output_header(HttpHeaders::CONTENT_TYPE, get_content_type(file_path).c_str()); |
| |
| if (req->method() == HttpMethod::HEAD) { |
| close(fd); |
| req->add_output_header(HttpHeaders::CONTENT_LENGTH, std::to_string(file_size).c_str()); |
| HttpChannel::send_reply(req); |
| return; |
| } |
| |
| HttpChannel::send_file(req, fd, 0, file_size, rate_limit_group); |
| } |
| |
| void do_dir_response(const std::string& dir_path, HttpRequest* req) { |
| bool exists = true; |
| std::vector<io::FileInfo> files; |
| Status st = io::global_local_filesystem()->list(dir_path, true, &files, &exists); |
| if (!st.ok()) { |
| LOG(WARNING) << "Failed to scan dir. " << st; |
| HttpChannel::send_error(req, HttpStatus::INTERNAL_SERVER_ERROR); |
| } |
| |
| const std::string FILE_DELIMITER_IN_DIR_RESPONSE = "\n"; |
| |
| std::stringstream result; |
| for (auto& file : files) { |
| result << file.file_name << FILE_DELIMITER_IN_DIR_RESPONSE; |
| } |
| |
| std::string result_str = result.str(); |
| HttpChannel::send_reply(req, result_str); |
| } |
| |
| bool load_size_smaller_than_wal_limit(HttpRequest* req) { |
| // 1. req->header(HttpHeaders::CONTENT_LENGTH) will return streamload content length. If it is empty or equels to 0, it means this streamload |
| // is a chunked streamload and we are not sure its size. |
| // 2. if streamload content length is too large, like larger than 80% of the WAL constrain. |
| // |
| // This two cases, we are not certain that the Write-Ahead Logging (WAL) constraints allow for writing down |
| // these blocks within the limited space. So we need to set group_commit = false to avoid dead lock. |
| if (!req->header(HttpHeaders::CONTENT_LENGTH).empty()) { |
| size_t body_bytes = std::stol(req->header(HttpHeaders::CONTENT_LENGTH)); |
| size_t max_available_size = ExecEnv::GetInstance()->wal_mgr()->get_max_available_size(); |
| return (body_bytes != 0 && body_bytes < 0.8 * max_available_size); |
| } else { |
| return false; |
| } |
| } |
| |
| } // namespace doris |