text: // 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 <dsn/http/http_server.h>
  #include <gtest/gtest.h>
  
  #include "http/http_message_parser.h"
  #include "http/builtin_http_calls.h"
  #include "http/http_call_registry.h"
  
  namespace dsn {
  
  TEST(http_server, parse_url)
  {
      struct test_case
      {
          std::string url;
  
          error_code err;
          std::string path;
      } tests[] = {
          {"http://127.0.0.1:34601", ERR_OK, ""},
          {"http://127.0.0.1:34601/", ERR_OK, ""},
          {"http://127.0.0.1:34601///", ERR_OK, ""},
          {"http://127.0.0.1:34601/threads", ERR_OK, "threads"},
          {"http://127.0.0.1:34601/threads/?detail", ERR_OK, "threads"},
          {"http://127.0.0.1:34601//pprof/heap/", ERR_OK, "pprof/heap"},
          {"http://127.0.0.1:34601//pprof///heap?detailed=true", ERR_OK, "pprof/heap"},
          {"http://127.0.0.1:34601/pprof/heap/arg/", ERR_OK, "pprof/heap/arg"},
          {"http://127.0.0.1:34601/pprof///heap///arg/", ERR_OK, "pprof/heap/arg"},
      };
  
      for (auto tt : tests) {
          ref_ptr<message_ex> m = message_ex::create_receive_message_with_standalone_header(
              blob::create_from_bytes(std::string("POST")));
          m->buffers.emplace_back(blob::create_from_bytes(std::string(tt.url)));
          m->buffers.resize(HTTP_MSG_BUFFERS_NUM);
  
          auto res = http_request::parse(m.get());
          if (res.is_ok()) {
              ASSERT_EQ(res.get_value().path, tt.path) << tt.url;
          } else {
              ASSERT_EQ(res.get_error().code(), tt.err);
          }
      }
  }
  
  TEST(bultin_http_calls_test, get_help)
  {
      for (const auto &call : http_call_registry::instance().list_all_calls()) {
          http_call_registry::instance().remove(call->path);
      }
  
      register_http_call("")
          .with_callback(
              [](const http_request &req, http_response &resp) { get_help_handler(req, resp); })
          .with_help("ip:port/");
  
      http_request req;
      http_response resp;
      get_help_handler(req, resp);
      ASSERT_EQ(resp.status_code, http_status_code::ok);
      ASSERT_EQ(resp.body, "{\"/\":\"ip:port/\"}\n");
  
      register_http_call("recentStartTime")
          .with_callback([](const http_request &req, http_response &resp) {
              get_recent_start_time_handler(req, resp);
          })
          .with_help("ip:port/recentStartTime");
  
      get_help_handler(req, resp);
      ASSERT_EQ(resp.body, "{\"/\":\"ip:port/\",\"/recentStartTime\":\"ip:port/recentStartTime\"}\n");
  
      for (const auto &call : http_call_registry::instance().list_all_calls()) {
          http_call_registry::instance().remove(call->path);
      }
  }
  
  class http_message_parser_test : public testing::Test
  {
  public:
      void parse_bad_request()
      {
          // not complete in normal way
          std::string http_request = "GET "
                                     "/ HTTP/1.1\r\n";
  
          message_reader reader(64);
          char *buf = reader.read_buffer_ptr(http_request.size());
          memcpy(buf, http_request.data(), http_request.size());
          reader.mark_read(http_request.size());
  
          http_message_parser parser;
          int read_next = 0;
          message_ex *msg = parser.get_message_on_receive(&reader, read_next);
          ASSERT_EQ(msg, nullptr);
          ASSERT_EQ(parser._stage, HTTP_ON_URL); // url parsed
          ASSERT_EQ(parser._parsed_length, http_request.size());
          ASSERT_EQ(parser._url, "/");
          ASSERT_NE(parser._current_message, nullptr);
          ASSERT_NE(read_next, -1);
  
          // normal request
          const char http_request2[] = "GET / HTTP/1.1\r\n"
                                       "Host: baidu.com\r\n"
                                       "Accept: */*\r\n"
                                       "\r\n";
          buf = reader.read_buffer_ptr(sizeof(http_request2));
          memcpy(buf, http_request2, sizeof(http_request2));
          reader.mark_read(sizeof(http_request2));
  
          msg = parser.get_message_on_receive(&reader, read_next);
          ASSERT_EQ(msg, nullptr);
          ASSERT_EQ(read_next, -1);
      }
  
      void parse_multiple_requests()
      {
          std::string http_request = std::string("GET /") + " HTTP/1.1\r\n\r\n";
          std::string requests;
          for (int i = 0; i < 100; i++) {
              requests += http_request;
          }
  
          message_reader reader(64);
          char *buf = reader.read_buffer_ptr(requests.size());
          memcpy(buf, requests.data(), requests.size());
          reader.mark_read(requests.size());
  
          http_message_parser parser;
          int read_next = 0;
  
          for (int i = 0; i < 100; i++) {
              message_ptr msg = parser.get_message_on_receive(&reader, read_next);
              ASSERT_NE(msg, nullptr);
              ASSERT_EQ(msg->hdr_format, NET_HDR_HTTP);
              ASSERT_EQ(msg->header->hdr_type, http_method::HTTP_METHOD_GET);
              ASSERT_EQ(msg->header->context.u.is_request, 1);
              ASSERT_EQ(msg->buffers.size(), HTTP_MSG_BUFFERS_NUM);
              ASSERT_EQ(msg->buffers[2].size(), 1); // url
  
              // ensure states are reset
              ASSERT_EQ(parser._current_message, nullptr);
              ASSERT_EQ(parser._stage, HTTP_INVALID);
              ASSERT_EQ(parser._parsed_length, 0);
              ASSERT_EQ(parser._received_messages.size(), 100 - i - 1);
          }
  
          ASSERT_EQ(parser._received_messages.size(), 0);
      }
  };
  
  TEST_F(http_message_parser_test, parse_request)
  {
      std::string http_request = "POST /path/file.html?sdfsdf=sdfs&sldf1=sdf HTTP/1.1\r\n"
                                 "User-Agent: HTTPTool/1.0  \r\n" // intended ending spaces
                                 "Content-Type: json\r\n"
                                 "Content-Length: 19\r\n"
                                 "Log-ID: 456\r\n"
                                 "Host: myhost\r\n"
                                 "Correlation-ID: 123\r\n"
                                 "Authorization: test\r\n"
                                 "Accept: */*\r\n"
                                 "\r\n"
                                 "Message Body sdfsdf\r\n";
  
      message_reader reader(64);
      char *buf = reader.read_buffer_ptr(http_request.size());
      memcpy(buf, http_request.data(), http_request.size());
      reader.mark_read(http_request.size());
  
      http_message_parser parser;
      int read_next = 0;
      message_ptr msg = parser.get_message_on_receive(&reader, read_next);
      ASSERT_NE(msg, nullptr);
  
      ASSERT_EQ(msg->hdr_format, NET_HDR_HTTP);
      ASSERT_EQ(msg->header->hdr_type, http_method::HTTP_METHOD_POST);
      ASSERT_EQ(msg->header->context.u.is_request, 1);
      ASSERT_EQ(msg->buffers.size(), HTTP_MSG_BUFFERS_NUM);
      ASSERT_EQ(msg->buffers[1].to_string(), "Message Body sdfsdf"); // body
      ASSERT_EQ(                                                     // url
          msg->buffers[2].to_string(),
          std::string("/path/file.html?sdfsdf=sdfs&sldf1=sdf"));
      ASSERT_EQ(msg->buffers[3].to_string(), std::string("json"));
  }
  
  TEST_F(http_message_parser_test, eof)
  {
      std::string http_request =
          "GET "
          "/CloudApiControl/HttpServer/telematics/v3/"
          "weather?location=%E6%B5%B7%E5%8D%97%E7%9C%81%E7%9B%B4%E8%BE%96%E5%8E%BF%E7%BA%A7%E8%A1%8C%"
          "E6%94%BF%E5%8D%95%E4%BD%8D&output=json&ak=0l3FSP6qA0WbOzGRaafbmczS HTTP/1.1\r\n"
          "X-Host: api.map.baidu.com\r\n"
          "X-Forwarded-Proto: http\r\n"
          "Host: api.map.baidu.com\r\n"
          "User-Agent: IME/Android/4.4.2/N80.QHD.LT.X10.V3/N80.QHD.LT.X10.V3.20150812.031915\r\n"
          "Accept: application/json\r\n"
          "Accept-Charset: UTF-8,*;q=0.5\r\n"
          "Accept-Encoding: deflate,sdch\r\n"
          "Accept-Language: zh-CN,en-US;q=0.8,zh;q=0.6\r\n"
          "Bfe-Atk: NORMAL_BROWSER\r\n"
          "Bfe_logid: 8767802212038413243\r\n"
          "Bfeip: 10.26.124.40\r\n"
          "CLIENTIP: 119.29.102.26\r\n"
          "CLIENTPORT: 59863\r\n"
          "Cache-Control: max-age=0\r\n"
          "Content-Type: application/json;charset=utf8\r\n"
          "X-Forwarded-For: 119.29.102.26\r\n"
          "X-Forwarded-Port: 59863\r\n"
          "X-Ime-Imei: 35629601890905\r\n"
          "X_BD_LOGID: 3959476981\r\n"
          "X_BD_LOGID64: 16815814797661447369\r\n"
          "X_BD_PRODUCT: map\r\n"
          "X_BD_SUBSYS: apimap\r\n\r\n";
  
      message_reader reader(64);
      char *buf = reader.read_buffer_ptr(http_request.size());
      memcpy(buf, http_request.data(), http_request.size());
      reader.mark_read(http_request.size());
  
      http_message_parser parser;
      int read_next = 0;
      message_ptr msg = parser.get_message_on_receive(&reader, read_next);
      ASSERT_NE(msg, nullptr);
  
      ASSERT_EQ(msg->hdr_format, NET_HDR_HTTP);
      ASSERT_EQ(msg->header->hdr_type, http_method::HTTP_METHOD_GET);
      ASSERT_EQ(msg->header->context.u.is_request, 1);
      ASSERT_EQ(msg->buffers.size(), HTTP_MSG_BUFFERS_NUM);
      ASSERT_EQ(msg->buffers[1].to_string(), ""); // body
      ASSERT_EQ(                                  // url
          msg->buffers[2].to_string(),
          std::string("/CloudApiControl/HttpServer/telematics/v3/"
                      "weather?location=%E6%B5%B7%E5%8D%97%E7%9C%81%E7%9B%B4%E8%BE%96%E5%8E%BF%E7%BA%"
                      "A7%E8%A1%8C%"
                      "E6%94%BF%E5%8D%95%E4%BD%8D&output=json&ak=0l3FSP6qA0WbOzGRaafbmczS"));
      ASSERT_EQ(msg->buffers[3].to_string(), std::string("application/json;charset=utf8"));
  }
  
  TEST_F(http_message_parser_test, parse_bad_request) { parse_bad_request(); }
  
  TEST_F(http_message_parser_test, parse_multiple_requests) { parse_multiple_requests(); }
  
  TEST_F(http_message_parser_test, parse_long_url)
  {
      std::string http_request = "GET /" + std::string(4096, 'a') + " HTTP/1.1\r\n\r\n";
  
      message_reader reader(64);
      char *buf = reader.read_buffer_ptr(http_request.size());
      memcpy(buf, http_request.data(), http_request.size());
      reader.mark_read(http_request.size());
  
      http_message_parser parser;
      int read_next = 0;
      message_ptr msg = parser.get_message_on_receive(&reader, read_next);
      ASSERT_NE(msg, nullptr);
      ASSERT_EQ(msg->hdr_format, NET_HDR_HTTP);
      ASSERT_EQ(msg->header->hdr_type, http_method::HTTP_METHOD_GET);
      ASSERT_EQ(msg->header->context.u.is_request, 1);
      ASSERT_EQ(msg->buffers.size(), HTTP_MSG_BUFFERS_NUM);
      ASSERT_EQ(msg->buffers[2].size(), 4097); // url
  }
  
  TEST_F(http_message_parser_test, parse_query_params)
  {
      struct test_case
      {
          std::string url;
  
          error_code err;
          std::unordered_map<std::string, std::string> result;
      } tests[] = {
          {"http://127.0.0.1:34601?query1", ERR_OK, {{"query1", ""}}},
          {"http://127.0.0.1:34601?query1=", ERR_OK, {{"query1", ""}}},
          {"http://127.0.0.1:34601?query1=value1", ERR_OK, {{"query1", "value1"}}},
          {"http://127.0.0.1:34601?=", ERR_OK, {{"", ""}}},
          {"http://127.0.0.1:34601?", ERR_OK, {}},
  
          {"http://127.0.0.1:34601?query1=value1&query2=value2",
           ERR_OK,
           {{"query1", "value1"}, {"query2", "value2"}}},
  
          {"http://127.0.0.1:34601?query1=value1&query2",
           ERR_OK,
           {{"query1", "value1"}, {"query2", ""}}},
      };
  
      for (auto tt : tests) {
          ref_ptr<message_ex> m = message_ex::create_receive_message_with_standalone_header(
              blob::create_from_bytes(std::string("POST")));
          m->buffers.emplace_back(blob::create_from_bytes(std::string(tt.url)));
          m->buffers.resize(HTTP_MSG_BUFFERS_NUM);
  
          auto res = http_request::parse(m.get());
          if (res.is_ok()) {
              ASSERT_EQ(res.get_value().query_args, tt.result) << tt.url;
          } else {
              ASSERT_EQ(res.get_error().code(), tt.err);
          }
      }
  }
  
  } // namespace dsn
  