diff --git a/.gitignore b/.gitignore
index 5c7cbab..521cb63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,4 @@
*.swn
# Runtime-python
-*.pyc
+*.pyc
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e78f507..3772901 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,7 +13,8 @@ set(CATKIN_DEPENDENCIES
mrs_msgs
mrs_robot_diagnostics
mrs_lib
- mrs_mission_manager
+ iroc_mission_handler
+ iroc_fleet_manager
actionlib
)
@@ -33,7 +34,6 @@ catkin_package(
include_directories(
INCLUDE_DIRS include
${catkin_INCLUDE_DIRS}
- include
)
# IROCBridge
@@ -41,7 +41,6 @@ include_directories(
## Declare a C++ library
add_library(IROCBridge
src/iroc_bridge.cpp
- src/json_var_parser.cpp
)
add_dependencies(IROCBridge
diff --git a/README.md b/README.md
index fa7819d..0b27d92 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# iroc_bridge
-
+ * Documentation: [Signaling Protocol Specification.](https://fly4future.github.io/iroc_bridge/)
## Functionality
* Once activated, the nodelet listens to the `uavX/mrs_uav_status/uav_status` topic and translates the messages from ROS to a JSON format.
diff --git a/config/config.yaml b/config/config.yaml
index f572be7..7349757 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -1,2 +1,8 @@
main_timer_rate: 100.0 # [Hz]
no_message_timeout: 5.0 # [s]
+http_server_threads: 8
+
+remote_control_limits:
+ max_linear_speed: 1.0 # [m/s]
+ max_heading_rate: 0.8 # [rad/s]
+
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..30016d7
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,751 @@
+# Signaling Protocol Specification
+
+This document outlines the signaling protocol IROC Bridge package uses to communicate with clients.
+Designed to communicate between the web client and ROS.
+
+## HTTP API
+
+You can use the HTTP API to send requests to interact with the robots and receive status information.
+The requests and responses are in JSON format.
+
+### Robot control
+
+Endpoints for controlling the robots.
+
+- `GET`
+ **/robots**
+
+ List available robots.
+
+- `POST`
+ **/robots/{_robot_name_}/takeoff**
+
+ Command takeoff (single)
+
+- `POST`
+ **/robots/takeoff**
+
+ Takeoff all robots
+
+- `POST`
+ **/robots/{_robot_name_}/hover**
+
+ Command hover (single)
+
+- `POST`
+ **/robots/hover**
+
+ Hover all robots
+
+- `POST`
+ **/robots/{_robot_name_}/land**
+
+ Command land (single)
+
+- `POST`
+ **/robots/land**
+
+ Land all robots
+
+- `POST`
+ **/robots/{_robot_name_}/home**
+
+ Command land home (single)
+
+- `POST`
+ **/robots/home**
+
+ Land home all robots
+
+
+### Environment setup
+
+Endpoints for controlling the robot's environment.
+ > **NOTE** \
+ > Each step in this sequence depends on the successful completion of the previous step. Please ensure that you first initialize the origin, then the borders, and finally the obstacles, in that exact order.
+>
+
+
+
+
+
+
+ Environment setup sequence diagram
+
+
+- `POST`
+ **/safety-area/origin**
+
+ Set the world origin.
+
+
+
+ Bodyraw (json)
+
+
+ We currently only support `frame_id` in LATLON (`id`: 0)
+
+ ```json
+ {
+ "frame_id": 0,
+ "x": 47.397978,
+ "y": 8.545299
+ }
+ ```
+
+
+
+- `POST`
+ **/safety-area/borders**
+
+ Set the safety area borders.
+
+
+
+ Bodyraw (json)
+
+
+ ```json
+ {
+ "points": [
+ {
+ "x": 47.39776,
+ "y": 8.545254
+ },
+ {
+ "x": 47.397719,
+ "y": 8.545436
+ },
+ {
+ "x": 47.397601,
+ "y": 8.545367
+ },
+ {
+ "x": 47.397657,
+ "y": 8.545191
+ }
+ ],
+ "height_id": 1,
+ "max_z": 347,
+ "min_z": 343
+ }
+ ```
+
+
+
+- `POST`
+ **/safety-area/obstacles**
+
+ Set the safety area obstacles.
+
+
+
+ Bodyraw (json)
+
+
+ ```json
+ {
+ "points": [
+ {
+ "x": 47.39776,
+ "y": 8.545254
+ },
+ {
+ "x": 47.397719,
+ "y": 8.545436
+ },
+ {
+ "x": 47.397601,
+ "y": 8.545367
+ },
+ {
+ "x": 47.397657,
+ "y": 8.545191
+ }
+ ],
+ "height_id": 1,
+ "max_z": 347,
+ "min_z": 343
+ }
+ ```
+
+
+
+### Missions
+
+The missions are handled by `IROC Fleet Manager`: node responsible of sending the mission to the robots, monitoring their progress and sending the aggregated information to the `IROC Bridge`.
+
+
+
+
+
+
+
+ Mission Sequence Diagram
+
+
+- `POST`
+ **/mission/waypoints**
+
+ Set the waypoints for the mission.
+
+
+
+ Bodyraw (json)
+
+
+ ```json
+ {
+ "mission": [
+ {
+ "robot_name": "uav1",
+ "frame_id": 0,
+ "height_id": 0,
+ "points": [
+ { "x": 10, "y": 10, "z": 2, "heading": 1 },
+ { "x": -10, "y": 10, "z": 2, "heading": 3 }
+ ],
+ "terminal_action": 0
+ },
+ {
+ "robot_name": "uav2",
+ "frame_id": 0,
+ "height_id": 0,
+ "points": [
+ { "x": 20, "y": 5, "z": 3, "heading": 0 }
+ ],
+ "terminal_action": 0
+ }
+ ]
+ }
+ ```
+
+
+
+- `POST`
+ **/mission/coverage**
+
+ Set a coverage mission.
+
+
+
+ Bodyraw (json)
+
+
+ ```json
+ {
+ "robots": [
+ "uav1",
+ "uav2"
+ ],
+ "search_area": [
+ {"x": 47.397978, "y": 8.545299},
+ {"x": 47.397848, "y": 8.545872},
+ {"x": 47.397551, "y": 8.545720},
+ {"x": 47.397699, "y": 8.545129}
+ ],
+ "height_id": 0,
+ "height": 10,
+ "terminal_action": 0
+ }
+ ```
+
+
+
+- `POST`
+ **/mission/autonomy-test**
+
+ Set the autonomy test mission.
+
+
+
+ Bodyraw (json)
+
+
+ ```json
+ {
+ "robot_name": "uav1",
+ "segment_length": 2
+ }
+ ```
+
+
+#### Mission Response Examples
+
+1. Successful mission upload
+
+
+
+ Body example response (json)
+
+- Code: 201 Created
+
+- Example value:
+
+```json
+{
+ "message":"Mission uploaded successfully",
+ "success":true,
+ "robot_results":[
+ {
+ "robot_name":"uav1",
+ "success":true,
+ "message":"Robot received the mission successfully"
+ },
+ {
+ "message":"Robot received the mission successfully",
+ "success":true,
+ "robot_name":"uav2"
+ }
+ ]
+}
+```
+
+
+
+2. Uploading mission failure due to safety area validation.
+
+
+
+ Body example response (json)
+
+- Code: 400 Bad Request
+
+- Example value:
+
+```json
+{
+ "message":"Failure starting robot clients.",
+ "success":false,
+ "robot_results":[
+ {
+ "robot_name":"uav1",
+ "success":true,
+ "message":"Mission on robot: uav1 was successfully processed"
+ },
+ {
+ "message":"Unvalid trajectory for uav2, trajectory is outside of safety area",
+ "success":false,
+ "robot_name":"uav2"
+ }
+ ]
+}
+```
+
+
+
+3. Uploading mission failure due to loaded mission in server.
+
+
+
+ Body example response (json)
+
+- Code: 409 Conflict
+
+- Example value:
+
+```json
+{
+ "message": "Mission is already running. Terminate the previous one, or wait until it is finished"
+}
+```
+
+
+
+#### Mission Control Endpoints
+
+We support for both fleet-wide and individual robot mission control.
+
+##### Fleet Mission Control:
+
+These endpoints control the mission status for all assigned robots at once: \
+
+- `POST`
+ **/mission/start**
+
+ Start the mission for all robots.
+
+- `POST`
+ **/mission/pause**
+
+ Pause the mission for all robots.
+
+- `POST`
+ **/mission/stop**
+
+ Stop the mission for all robots.
+
+
+##### Robot Mission Control:
+
+You can also control individual mission robots using these endpoints:
+
+- `POST`
+ **/robots/{_robot_name_}/mission/start**
+
+ Start the mission for a specific robot.
+
+
+ > **NOTE** \
+ > Starting a mission for a single robot will activate that robot while the others remain in a waiting state. You can later use the `/mission/start` endpoint to activate the remaining robots and continue the mission.
+
+- `POST`
+ **/robots/{_robot_name_}/mission/pause**
+
+ Pause the mission for a specific robot.
+
+- `POST`
+ **/robots/{_robot_name_}/mission/stop**
+
+ Stop the mission for a specific robot.
+
+ > **NOTE** \
+ > Stopping the mission for a single robot will also abort the overall mission and stop all other robots. This behavior is intentional, as the mission assumes the participation of all assigned robots.
+
+#### Feedback
+
+During an active mission, the feedback message is broadcasted to the connected clients through a WebSocket in the `/telemetry` path.
+
+- `onmessage`
+ **Waypoint Mission and Autonomy Test Feedback.**
+
+
+ Messageraw (json)
+
+
+ ```json
+ {
+ "type": "WaypointMissionFeedback",
+ "progress": 0.75,
+ "mission_state": "IN_PROGRESS",
+ "message": "EXECUTING",
+ "robots": [
+ {
+ "robot_name": "uav1",
+ "message": "EXECUTING",
+ "mission_progress": 0.6,
+ "current_goal": 2,
+ "distance_to_goal": 15.3,
+ "goal_estimated_arrival_time": 30,
+ "goal_progress": 0.8,
+ "distance_to_finish": 50.2,
+ "finish_estimated_arrival_time": 50
+ },
+ {
+ "robot_name": "uav2",
+ "message": "EXECUTING",
+ "mission_progress": 0.45,
+ "current_goal": 1,
+ "distance_to_goal": 5.7,
+ "goal_estimated_arrival_time": 30,
+ "goal_progress": 0.95,
+ "distance_to_finish": 75.8,
+ "finish_estimated_arrival_time": 50
+ }
+ ]
+ }
+ ```
+
+
+
+> **NOTE** \
+> Autonomy test follows the same structure as the waypoint mission feedback, but it will always contain only one robot.
+
+#### Result
+
+When a mission is finished, the result message will be sent to
+
+`POST`
+**http://server:8000/api/missions/result**
+
+Send the result of the mission.
+
+
+
+
+ Bodyraw (json)
+
+
+```json
+{
+ "success": true,
+ "message": "All robots finished successfully",
+ "robot_results": [
+ {
+ "robot_name": "uav1",
+ "success": true,
+ "message": "Robot finished successfully"
+ },
+ {
+ "robot_name": "uav2",
+ "success": true,
+ "message": "Robot finished successfully"
+ }
+ ]
+}
+```
+
+
+
+## WebSocket API
+
+You can use the WebSocket API to receive robots telemetry and send requests to control the robots.
+
+### Telemetry
+
+Robot's data and status can be received periodically in the `/telemetry` path.
+
+- `onmessage`
+ **General Robot Info**
+
+
+ Messageraw (json)
+
+
+ ```json
+ {
+ "errors": [],
+ "type": "GeneralRobotInfo",
+ "ready_to_start": 1,
+ "problems_preventing_start": [],
+ "battery_state": {
+ "wh_drained": -1,
+ "percentage": -1,
+ "voltage": -1
+ },
+ "robot_type": 0,
+ "robot_name": "uav2"
+ }
+ ```
+
+
+
+- `onmessage`
+ **State Estimation Info**
+
+
+ Messageraw (json)
+
+
+ ````json
+ {
+ "type": "StateEstimationInfo",
+ "switchable_estimators": [
+ "gps_baro",
+ "gps_garmin"
+ ],
+ "velocity": {
+ "angular": {
+ "z": 0,
+ "y": 0,
+ "x": 0
+ },
+ "linear": {
+ "z": 4.6765261112091244e-21,
+ "y": 0,
+ "x": 0
+ }
+ },
+ "global_pose": {
+ "heading": 1.02729905983773,
+ "altitude": 340,
+ "longitude": 8.545800727209587,
+ "latitude": 47.39776586900617
+ },
+ "local_pose": {
+ "z": 0.059999996605801006,
+ "heading": 1.02729905983773,
+ "y": 2.4504742256806935,
+ "x": 15.614331170562465
+ },
+ "current_estimator": "gps_baro",
+ "above_ground_level_height": 0.059999996605801,
+ "running_estimators": [
+ "gps_baro",
+ "gps_garmin"
+ ],
+ "acceleration": {
+ "angular": {
+ "z": 0,
+ "y": 0,
+ "x": 0
+ },
+ "linear": {
+ "z": 1.0095692646347513e-18,
+ "y": 0,
+ "x": 0
+ }
+ },
+ "estimation_frame": "uav2/gps_garmin_origin",
+ "robot_name": "uav2"
+ }
+ ````
+
+
+- `onmessage`
+ **Control Info**
+
+
+ Messageraw (json)
+
+
+ ````json
+ {
+ "type": "ControlInfo",
+ "thrust": null,
+ "available_trackers": [],
+ "active_tracker": "unknown",
+ "available_controllers": [],
+ "active_controller": "unknown",
+ "robot_name": "uav2"
+ }
+ ````
+
+
+- `onmessage`
+ **Collision Avoidance Info**
+
+
+ Messageraw (json)
+
+
+ ````json
+ {
+ "type": "CollisionAvoidanceInfo",
+ "other_robots_visible": [
+ "uav1"
+ ],
+ "collision_avoidance_enabled": 1,
+ "avoiding_collision": 0,
+ "robot_name": "uav2"
+ }
+ ````
+
+
+- `onmessage`
+ **UAV Info**
+
+
+ Messageraw (json)
+
+
+ ```json
+ {
+ "mass_nominal": null,
+ "type": "UavInfo",
+ "flight_duration": 0,
+ "flight_state": "OFFBOARD",
+ "offboard": 1,
+ "armed": 1,
+ "robot_name": "uav2"
+ }
+ ```
+
+
+
+- `onmessage`
+ **System Health Info**
+
+
+ Messageraw (json)
+
+
+ ```json
+ {
+ "free_ram": 22.789223,
+ "robot_name": "uav2",
+ "cpu_load": 10.102389,
+ "mag_strength": null,
+ "total_ram": 30.061069,
+ "type": "SystemHealthInfo",
+ "mag_uncertainty": null,
+ "free_hdd": 1393,
+ "state_estimation_rate": 20.080807,
+ "hw_api_rate": 99.019608,
+ "control_manager_rate": 0.990196,
+ "gnss_uncertainty": 0,
+ "node_cpu_loads": [
+ ["/uav2/hw_api", 1.09215],
+ ["/uav2/constraint_manager", 1.09215],
+ ["/uav2/control_manager", 1.09215],
+ ["/uav2/estimation_manager", 0]
+ ],
+ "available_sensors": [
+ {
+ "name": "pixhawk",
+ "status": "NOT_IMPLEMENTED",
+ "ready": 1,
+ "rate": -1
+ },
+ {
+ "rate": -1,
+ "ready": 1,
+ "status": "NOT_IMPLEMENTED",
+ "name": "garmin_down"
+ }
+ ]
+ }
+ ```
+
+
+
+### Robot remote control
+
+You can use the WebSocket API to control the robots in the `/rc` path.
+
+- `onmessage`
+ **Message**
+
+ Similar to a ping websocket message.
+
+
+
+ Messageraw (json)
+
+
+ ```json
+ {
+ "command": "message",
+ "data": "Hello, World!"
+ }
+ ```
+
+
+
+- `onmessage`
+ **Movement**
+
+ To control the UAV, it receives normalized linear (`x`, `y`, `z`) and angular (`yaw`) velocities.
+
+
+
+ Messageraw (json)
+
+
+ ```json
+ {
+ "command": "move",
+ "robot_name": "uav1",
+ "data": {
+ "x": 1.0,
+ "y": -0.5,
+ "z": 0,
+ "heading": 1.0
+ }
+ }
+ ```
+
+
+
+## Camera stream using WebRTC
+
+The features for the camera streaming are available, and the setup can be tested by starting the simulator with the camera argument for that will start the gazebo simulator:
+
+```sh
+./start --camera
+```
+
+This will start the WebRTC server and allow the camera stream to be visualized on port `9090` of the server.
+
+> **NOTE** \
+> Please follow the instructions for the installation of dependencies in the [webrtc_ros](https://github.com/fly4future/webrtc_ros) repository. A detailed example of how the integration can be done is [here](https://github.com/fly4future/webrtc_ros/blob/develop/web/TUTORIAL.md).
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000..434f671
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,14 @@
+title: IROC bridge
+description: Signaling protocol specification
+markdown: GFM
+remote_theme: jekyll/minima@master
+highlighter: rouge
+minima:
+ skin: "auto"
+ hide_site_feed_link: true
+ social_links:
+ - title: GitHub
+ icon: github
+ url: "https://github.com/fly4future/iroc_bridge"
+titles_from_headings:
+ strip_title: true # Remove double title
diff --git a/docs/img/environment_sequence_bright.svg b/docs/img/environment_sequence_bright.svg
new file mode 100644
index 0000000..81e494c
--- /dev/null
+++ b/docs/img/environment_sequence_bright.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/img/environment_sequence_dark.svg b/docs/img/environment_sequence_dark.svg
new file mode 100644
index 0000000..e864cac
--- /dev/null
+++ b/docs/img/environment_sequence_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/img/sequence_diagram.svg b/docs/img/sequence_diagram.svg
new file mode 100644
index 0000000..274a954
--- /dev/null
+++ b/docs/img/sequence_diagram.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/img/sequence_diagram_dark.svg b/docs/img/sequence_diagram_dark.svg
new file mode 100644
index 0000000..f408d2f
--- /dev/null
+++ b/docs/img/sequence_diagram_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/include/crow.h b/include/crow.h
new file mode 100644
index 0000000..e2afd8a
--- /dev/null
+++ b/include/crow.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "crow/query_string.h"
+#include "crow/http_parser_merged.h"
+#include "crow/ci_map.h"
+#include "crow/TinySHA1.hpp"
+#include "crow/settings.h"
+#include "crow/socket_adaptors.h"
+#include "crow/json.h"
+#include "crow/mustache.h"
+#include "crow/logging.h"
+#include "crow/task_timer.h"
+#include "crow/utility.h"
+#include "crow/common.h"
+#include "crow/http_request.h"
+#include "crow/websocket.h"
+#include "crow/parser.h"
+#include "crow/http_response.h"
+#include "crow/multipart.h"
+#include "crow/multipart_view.h"
+#include "crow/routing.h"
+#include "crow/middleware.h"
+#include "crow/middleware_context.h"
+#include "crow/compression.h"
+#include "crow/http_connection.h"
+#include "crow/http_server.h"
+#include "crow/app.h"
diff --git a/include/crow/TinySHA1.hpp b/include/crow/TinySHA1.hpp
new file mode 100644
index 0000000..5cb6463
--- /dev/null
+++ b/include/crow/TinySHA1.hpp
@@ -0,0 +1,213 @@
+/*
+ * SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1
+ *
+ * Copyright (c) 2012-22 SAURAV MOHAPATRA
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * \file TinySHA1.hpp
+ * \author SAURAV MOHAPATRA
+ * \date 2012-22
+ * \brief TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based
+ * on the implementation in boost::uuid::details.
+ *
+ * In this file are defined:
+ * - sha1::SHA1
+ */
+#ifndef _TINY_SHA1_HPP_
+#define _TINY_SHA1_HPP_
+#include
+#include
+#include
+#include
+
+/**
+ * \namespace sha1
+ * \brief Here is defined the SHA1 class
+ */
+namespace sha1
+{
+ /**
+ * \class SHA1
+ * \brief A tiny SHA1 algorithm implementation used internally in the
+ * Crow server (specifically in crow/websocket.h).
+ */
+ class SHA1
+ {
+ public:
+ typedef uint32_t digest32_t[5];
+ typedef uint8_t digest8_t[20];
+ inline static uint32_t LeftRotate(uint32_t value, size_t count) {
+ return (value << count) ^ (value >> (32-count));
+ }
+ SHA1(){ reset(); }
+ virtual ~SHA1() {}
+ SHA1(const SHA1& s) { *this = s; }
+ const SHA1& operator = (const SHA1& s) {
+ memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t));
+ memcpy(m_block, s.m_block, 64);
+ m_blockByteIndex = s.m_blockByteIndex;
+ m_byteCount = s.m_byteCount;
+ return *this;
+ }
+ SHA1& reset() {
+ m_digest[0] = 0x67452301;
+ m_digest[1] = 0xEFCDAB89;
+ m_digest[2] = 0x98BADCFE;
+ m_digest[3] = 0x10325476;
+ m_digest[4] = 0xC3D2E1F0;
+ m_blockByteIndex = 0;
+ m_byteCount = 0;
+ return *this;
+ }
+ SHA1& processByte(uint8_t octet) {
+ this->m_block[this->m_blockByteIndex++] = octet;
+ ++this->m_byteCount;
+ if(m_blockByteIndex == 64) {
+ this->m_blockByteIndex = 0;
+ processBlock();
+ }
+ return *this;
+ }
+ SHA1& processBlock(const void* const start, const void* const end) {
+ const uint8_t* begin = static_cast(start);
+ const uint8_t* finish = static_cast(end);
+ while(begin != finish) {
+ processByte(*begin);
+ begin++;
+ }
+ return *this;
+ }
+ SHA1& processBytes(const void* const data, size_t len) {
+ const uint8_t* block = static_cast(data);
+ processBlock(block, block + len);
+ return *this;
+ }
+ const uint32_t* getDigest(digest32_t digest) {
+ size_t bitCount = this->m_byteCount * 8;
+ processByte(0x80);
+ if (this->m_blockByteIndex > 56) {
+ while (m_blockByteIndex != 0) {
+ processByte(0);
+ }
+ while (m_blockByteIndex < 56) {
+ processByte(0);
+ }
+ } else {
+ while (m_blockByteIndex < 56) {
+ processByte(0);
+ }
+ }
+ processByte(0);
+ processByte(0);
+ processByte(0);
+ processByte(0);
+ processByte( static_cast((bitCount>>24) & 0xFF));
+ processByte( static_cast((bitCount>>16) & 0xFF));
+ processByte( static_cast((bitCount>>8 ) & 0xFF));
+ processByte( static_cast((bitCount) & 0xFF));
+
+ memcpy(digest, m_digest, 5 * sizeof(uint32_t));
+ return digest;
+ }
+ const uint8_t* getDigestBytes(digest8_t digest) {
+ digest32_t d32;
+ getDigest(d32);
+ size_t di = 0;
+ digest[di++] = ((d32[0] >> 24) & 0xFF);
+ digest[di++] = ((d32[0] >> 16) & 0xFF);
+ digest[di++] = ((d32[0] >> 8) & 0xFF);
+ digest[di++] = ((d32[0]) & 0xFF);
+
+ digest[di++] = ((d32[1] >> 24) & 0xFF);
+ digest[di++] = ((d32[1] >> 16) & 0xFF);
+ digest[di++] = ((d32[1] >> 8) & 0xFF);
+ digest[di++] = ((d32[1]) & 0xFF);
+
+ digest[di++] = ((d32[2] >> 24) & 0xFF);
+ digest[di++] = ((d32[2] >> 16) & 0xFF);
+ digest[di++] = ((d32[2] >> 8) & 0xFF);
+ digest[di++] = ((d32[2]) & 0xFF);
+
+ digest[di++] = ((d32[3] >> 24) & 0xFF);
+ digest[di++] = ((d32[3] >> 16) & 0xFF);
+ digest[di++] = ((d32[3] >> 8) & 0xFF);
+ digest[di++] = ((d32[3]) & 0xFF);
+
+ digest[di++] = ((d32[4] >> 24) & 0xFF);
+ digest[di++] = ((d32[4] >> 16) & 0xFF);
+ digest[di++] = ((d32[4] >> 8) & 0xFF);
+ digest[di++] = ((d32[4]) & 0xFF);
+ return digest;
+ }
+
+ protected:
+ void processBlock() {
+ uint32_t w[80];
+ for (size_t i = 0; i < 16; i++) {
+ w[i] = (m_block[i*4 + 0] << 24);
+ w[i] |= (m_block[i*4 + 1] << 16);
+ w[i] |= (m_block[i*4 + 2] << 8);
+ w[i] |= (m_block[i*4 + 3]);
+ }
+ for (size_t i = 16; i < 80; i++) {
+ w[i] = LeftRotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1);
+ }
+
+ uint32_t a = m_digest[0];
+ uint32_t b = m_digest[1];
+ uint32_t c = m_digest[2];
+ uint32_t d = m_digest[3];
+ uint32_t e = m_digest[4];
+
+ for (std::size_t i=0; i<80; ++i) {
+ uint32_t f = 0;
+ uint32_t k = 0;
+
+ if (i<20) {
+ f = (b & c) | (~b & d);
+ k = 0x5A827999;
+ } else if (i<40) {
+ f = b ^ c ^ d;
+ k = 0x6ED9EBA1;
+ } else if (i<60) {
+ f = (b & c) | (b & d) | (c & d);
+ k = 0x8F1BBCDC;
+ } else {
+ f = b ^ c ^ d;
+ k = 0xCA62C1D6;
+ }
+ uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i];
+ e = d;
+ d = c;
+ c = LeftRotate(b, 30);
+ b = a;
+ a = temp;
+ }
+
+ m_digest[0] += a;
+ m_digest[1] += b;
+ m_digest[2] += c;
+ m_digest[3] += d;
+ m_digest[4] += e;
+ }
+ private:
+ digest32_t m_digest;
+ uint8_t m_block[64];
+ size_t m_blockByteIndex;
+ size_t m_byteCount;
+ };
+}
+#endif
diff --git a/include/crow/app.h b/include/crow/app.h
new file mode 100644
index 0000000..4119859
--- /dev/null
+++ b/include/crow/app.h
@@ -0,0 +1,804 @@
+/**
+ * \file crow/app.h
+ * \brief This file includes the definition of the crow::Crow class,
+ * the crow::App and crow::SimpleApp aliases, and some macros.
+ *
+ * In this file are defined:
+ * - crow::Crow
+ * - crow::App
+ * - crow::SimpleApp
+ * - \ref CROW_ROUTE
+ * - \ref CROW_BP_ROUTE
+ * - \ref CROW_WEBSOCKET_ROUTE
+ * - \ref CROW_MIDDLEWARES
+ * - \ref CROW_CATCHALL_ROUTE
+ * - \ref CROW_BP_CATCHALL_ROUTE
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "crow/version.h"
+#include "crow/settings.h"
+#include "crow/logging.h"
+#include "crow/utility.h"
+#include "crow/routing.h"
+#include "crow/middleware_context.h"
+#include "crow/http_request.h"
+#include "crow/http_server.h"
+#include "crow/task_timer.h"
+#include "crow/websocket.h"
+#ifdef CROW_ENABLE_COMPRESSION
+#include "crow/compression.h"
+#endif // #ifdef CROW_ENABLE_COMPRESSION
+
+
+#ifdef CROW_MSVC_WORKAROUND
+
+#define CROW_ROUTE(app, url) app.route_dynamic(url) // See the documentation in the comment below.
+#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_dynamic(url) // See the documentation in the comment below.
+
+#else // #ifdef CROW_MSVC_WORKAROUND
+
+/**
+ * \def CROW_ROUTE(app, url)
+ * \brief Creates a route for app using a rule.
+ *
+ * It use crow::Crow::route_dynamic or crow::Crow::route to define
+ * a rule for your application. It's usage is like this:
+ *
+ * ```cpp
+ * auto app = crow::SimpleApp(); // or crow::App()
+ * CROW_ROUTE(app, "/")
+ * ([](){
+ * return "
Hello, world!
";
+ * });
+ * ```
+ *
+ * This is the recommended way to define routes in a crow application.
+ * \see [Page of guide "Routes"](https://crowcpp.org/master/guides/routes/).
+ */
+#define CROW_ROUTE(app, url) app.template route(url)
+
+/**
+ * \def CROW_BP_ROUTE(blueprint, url)
+ * \brief Creates a route for a blueprint using a rule.
+ *
+ * It may use crow::Blueprint::new_rule_dynamic or
+ * crow::Blueprint::new_rule_tagged to define a new rule for
+ * an given blueprint. It's usage is similar
+ * to CROW_ROUTE macro:
+ *
+ * ```cpp
+ * crow::Blueprint my_bp();
+ * CROW_BP_ROUTE(my_bp, "/")
+ * ([](){
+ * return "
Hello, world!
";
+ * });
+ * ```
+ *
+ * This is the recommended way to define routes in a crow blueprint
+ * because of its compile-time capabilities.
+ *
+ * \see [Page of the guide "Blueprints"](https://crowcpp.org/master/guides/blueprints/).
+ */
+#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged(url)
+
+/**
+ * \def CROW_WEBSOCKET_ROUTE(app, url)
+ * \brief Defines WebSocket route for app.
+ *
+ * It binds a WebSocket route to app. Easy solution to implement
+ * WebSockets in your app. The usage syntax of this macro is
+ * like this:
+ *
+ * ```cpp
+ * auto app = crow::SimpleApp(); // or crow::App()
+ * CROW_WEBSOCKET_ROUTE(app, "/ws")
+ * .onopen([&](crow::websocket::connection& conn){
+ * do_something();
+ * })
+ * .onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t){
+ * do_something();
+ * })
+ * .onmessage([&](crow::websocket::connection&, const std::string& data, bool is_binary){
+ * if (is_binary)
+ * do_something(data);
+ * else
+ * do_something_else(data);
+ * });
+ * ```
+ *
+ * \see [Page of the guide "WebSockets"](https://crowcpp.org/master/guides/websockets/).
+ */
+#define CROW_WEBSOCKET_ROUTE(app, url) app.route(url).websocket::type>(&app)
+
+/**
+ * \def CROW_MIDDLEWARES(app, ...)
+ * \brief Enable a Middleware for an specific route in app
+ * or blueprint.
+ *
+ * It defines the usage of a Middleware in one route. And it
+ * can be used in both crow::SimpleApp (and crow::App) instances and
+ * crow::Blueprint. Its usage syntax is like this:
+ *
+ * ```cpp
+ * auto app = crow::SimpleApp(); // or crow::App()
+ * CROW_ROUTE(app, "/with_middleware")
+ * .CROW_MIDDLEWARES(app, LocalMiddleware) // Can be used more than one
+ * ([]() { // middleware.
+ * return "Hello world!";
+ * });
+ * ```
+ *
+ * \see [Page of the guide "Middlewares"](https://crowcpp.org/master/guides/middleware/).
+ */
+#define CROW_MIDDLEWARES(app, ...) template middlewares::type, __VA_ARGS__>()
+
+#endif // #ifdef CROW_MSVC_WORKAROUND
+
+/**
+ * \def CROW_CATCHALL_ROUTE(app)
+ * \brief Defines a custom catchall route for app using a
+ * custom rule.
+ *
+ * It defines a handler when the client make a request for an
+ * undefined route. Instead of just reply with a `404` status
+ * code (default behavior), you can define a custom handler
+ * using this macro.
+ *
+ * \see [Page of the guide "Routes" (Catchall routes)](https://crowcpp.org/master/guides/routes/#catchall-routes).
+ */
+#define CROW_CATCHALL_ROUTE(app) app.catchall_route()
+
+/**
+ * \def CROW_BP_CATCHALL_ROUTE(blueprint)
+ * \brief Defines a custom catchall route for blueprint
+ * using a custom rule.
+ *
+ * It defines a handler when the client make a request for an
+ * undefined route in the blueprint.
+ *
+ * \see [Page of the guide "Blueprint" (Define a custom Catchall route)](https://crowcpp.org/master/guides/blueprints/#define-a-custom-catchall-route).
+ */
+#define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule()
+
+
+/**
+ * \namespace crow
+ * \brief The main namespace of the library. In this namespace
+ * is defined the most important classes and functions of the
+ * library.
+ *
+ * Within this namespace, the Crow class, Router class, Connection
+ * class, and other are defined.
+ */
+namespace crow
+{
+#ifdef CROW_ENABLE_SSL
+ using ssl_context_t = asio::ssl::context;
+#endif
+ /**
+ * \class Crow
+ * \brief The main server application class.
+ *
+ * Use crow::SimpleApp or crow::App instead of
+ * directly instantiate this class.
+ */
+ template
+ class Crow
+ {
+ public:
+ /// \brief This is the crow application
+ using self_t = Crow;
+
+ /// \brief The HTTP server
+ using server_t = Server;
+
+#ifdef CROW_ENABLE_SSL
+ /// \brief An HTTP server that runs on SSL with an SSLAdaptor
+ using ssl_server_t = Server;
+#endif
+ Crow()
+ {}
+
+ /// \brief Construct Crow with a subset of middleware
+ template
+ Crow(Ts&&... ts):
+ middlewares_(make_middleware_tuple(std::forward(ts)...))
+ {}
+
+ /// \brief Process an Upgrade request
+ ///
+ /// Currently used to upgrade an HTTP connection to a WebSocket connection
+ template
+ void handle_upgrade(const request& req, response& res, Adaptor&& adaptor)
+ {
+ router_.handle_upgrade(req, res, adaptor);
+ }
+
+ /// \brief Process only the method and URL of a request and provide a route (or an error response)
+ std::unique_ptr handle_initial(request& req, response& res)
+ {
+ return router_.handle_initial(req, res);
+ }
+
+ /// \brief Process the fully parsed request and generate a response for it
+ void handle(request& req, response& res, std::unique_ptr& found)
+ {
+ router_.handle(req, res, *found);
+ }
+
+ /// \brief Process a fully parsed request from start to finish (primarily used for debugging)
+ void handle_full(request& req, response& res)
+ {
+ auto found = handle_initial(req, res);
+ if (found->rule_index)
+ handle(req, res, found);
+ }
+
+ /// \brief Create a dynamic route using a rule (**Use CROW_ROUTE instead**)
+ DynamicRule& route_dynamic(const std::string& rule)
+ {
+ return router_.new_rule_dynamic(rule);
+ }
+
+ /// \brief Create a route using a rule (**Use CROW_ROUTE instead**)
+ template
+ auto route(const std::string& rule)
+ -> typename std::invoke_result), Router, const std::string&>::type
+ {
+ return router_.new_rule_tagged(rule);
+ }
+
+ /// \brief Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**)
+ CatchallRule& catchall_route()
+ {
+ return router_.catchall_rule();
+ }
+
+ /// \brief Set the default max payload size for websockets
+ self_t& websocket_max_payload(uint64_t max_payload)
+ {
+ max_payload_ = max_payload;
+ return *this;
+ }
+
+ /// \brief Get the default max payload size for websockets
+ uint64_t websocket_max_payload()
+ {
+ return max_payload_;
+ }
+
+ self_t& signal_clear()
+ {
+ signals_.clear();
+ return *this;
+ }
+
+ self_t& signal_add(int signal_number)
+ {
+ signals_.push_back(signal_number);
+ return *this;
+ }
+
+ std::vector signals()
+ {
+ return signals_;
+ }
+
+ /// \brief Set the port that Crow will handle requests on
+ self_t& port(std::uint16_t port)
+ {
+ port_ = port;
+ return *this;
+ }
+
+ /// \brief Get the port that Crow will handle requests on
+ std::uint16_t port() const
+ {
+ if (!server_started_)
+ {
+ return port_;
+ }
+#ifdef CROW_ENABLE_SSL
+ if (ssl_used_)
+ {
+ return ssl_server_->port();
+ }
+ else
+#endif
+ {
+ return server_->port();
+ }
+ }
+
+ /// \brief Set the connection timeout in seconds (default is 5)
+ self_t& timeout(std::uint8_t timeout)
+ {
+ timeout_ = timeout;
+ return *this;
+ }
+
+ /// \brief Set the server name
+ self_t& server_name(std::string server_name)
+ {
+ server_name_ = server_name;
+ return *this;
+ }
+
+ /// \brief The IP address that Crow will handle requests on (default is 0.0.0.0)
+ self_t& bindaddr(std::string bindaddr)
+ {
+ bindaddr_ = bindaddr;
+ return *this;
+ }
+
+ /// \brief Get the address that Crow will handle requests on
+ std::string bindaddr()
+ {
+ return bindaddr_;
+ }
+
+ /// \brief Run the server on multiple threads using all available threads
+ self_t& multithreaded()
+ {
+ return concurrency(std::thread::hardware_concurrency());
+ }
+
+ /// \brief Run the server on multiple threads using a specific number
+ self_t& concurrency(std::uint16_t concurrency)
+ {
+ if (concurrency < 2) // Crow can have a minimum of 2 threads running
+ concurrency = 2;
+ concurrency_ = concurrency;
+ return *this;
+ }
+
+ /// \brief Get the number of threads that server is using
+ std::uint16_t concurrency() const
+ {
+ return concurrency_;
+ }
+
+ /// \brief Set the server's log level
+ ///
+ /// Possible values are:
+ /// - crow::LogLevel::Debug (0)
+ /// - crow::LogLevel::Info (1)
+ /// - crow::LogLevel::Warning (2)
+ /// - crow::LogLevel::Error (3)
+ /// - crow::LogLevel::Critical (4)
+ self_t& loglevel(LogLevel level)
+ {
+ crow::logger::setLogLevel(level);
+ return *this;
+ }
+
+ /// \brief Set the response body size (in bytes) beyond which Crow automatically streams responses (Default is 1MiB)
+ ///
+ /// Any streamed response is unaffected by Crow's timer, and therefore won't timeout before a response is fully sent.
+ self_t& stream_threshold(size_t threshold)
+ {
+ res_stream_threshold_ = threshold;
+ return *this;
+ }
+
+ /// \brief Get the response body size (in bytes) beyond which Crow automatically streams responses
+ size_t& stream_threshold()
+ {
+ return res_stream_threshold_;
+ }
+
+
+ self_t& register_blueprint(Blueprint& blueprint)
+ {
+ router_.register_blueprint(blueprint);
+ return *this;
+ }
+
+ /// \brief Set the function to call to handle uncaught exceptions generated in routes (Default generates error 500).
+ ///
+ /// The function must have the following signature: void(crow::response&).
+ /// It must set the response passed in argument to the function, which will be sent back to the client.
+ /// See Router::default_exception_handler() for the default implementation.
+ template
+ self_t& exception_handler(Func&& f)
+ {
+ router_.exception_handler() = std::forward(f);
+ return *this;
+ }
+
+ std::function& exception_handler()
+ {
+ return router_.exception_handler();
+ }
+
+ /// \brief Set a custom duration and function to run on every tick
+ template
+ self_t& tick(Duration d, Func f)
+ {
+ tick_interval_ = std::chrono::duration_cast(d);
+ tick_function_ = f;
+ return *this;
+ }
+
+#ifdef CROW_ENABLE_COMPRESSION
+
+ self_t& use_compression(compression::algorithm algorithm)
+ {
+ comp_algorithm_ = algorithm;
+ compression_used_ = true;
+ return *this;
+ }
+
+ compression::algorithm compression_algorithm()
+ {
+ return comp_algorithm_;
+ }
+
+ bool compression_used() const
+ {
+ return compression_used_;
+ }
+#endif
+
+ /// \brief Apply blueprints
+ void add_blueprint()
+ {
+#if defined(__APPLE__) || defined(__MACH__)
+ if (router_.blueprints().empty()) return;
+#endif
+
+ for (Blueprint* bp : router_.blueprints())
+ {
+ if (bp->static_dir().empty()) {
+ CROW_LOG_ERROR << "Blueprint " << bp->prefix() << " and its sub-blueprints ignored due to empty static directory.";
+ continue;
+ }
+ auto static_dir_ = crow::utility::normalize_path(bp->static_dir());
+
+ bp->new_rule_tagged(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) {
+ utility::sanitize_filename(file_path_partial);
+ res.set_static_file_info_unsafe(static_dir_ + file_path_partial);
+ res.end();
+ });
+ }
+
+ router_.validate_bp();
+ }
+
+ /// \brief Go through the rules, upgrade them if possible, and add them to the list of rules
+ void add_static_dir()
+ {
+ if (are_static_routes_added()) return;
+ auto static_dir_ = crow::utility::normalize_path(CROW_STATIC_DIRECTORY);
+
+ route(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) {
+ utility::sanitize_filename(file_path_partial);
+ res.set_static_file_info_unsafe(static_dir_ + file_path_partial);
+ res.end();
+ });
+ set_static_routes_added();
+ }
+
+ /// \brief A wrapper for `validate()` in the router
+ void validate()
+ {
+ router_.validate();
+ }
+
+ /// \brief Run the server
+ void run()
+ {
+#ifndef CROW_DISABLE_STATIC_DIR
+ add_blueprint();
+ add_static_dir();
+#endif
+ validate();
+
+ error_code ec;
+ asio::ip::address addr = asio::ip::make_address(bindaddr_,ec);
+ if (ec){
+ CROW_LOG_ERROR << ec.message() << " - Can not create valid ip address from string: \"" << bindaddr_ << "\"";
+ return;
+ }
+ tcp::endpoint endpoint(addr, port_);
+#ifdef CROW_ENABLE_SSL
+ if (ssl_used_)
+ {
+ router_.using_ssl = true;
+ ssl_server_ = std::move(std::unique_ptr(new ssl_server_t(this, endpoint, server_name_, &middlewares_, concurrency_, timeout_, &ssl_context_)));
+ ssl_server_->set_tick_function(tick_interval_, tick_function_);
+ ssl_server_->signal_clear();
+ for (auto snum : signals_)
+ {
+ ssl_server_->signal_add(snum);
+ }
+ notify_server_start();
+ ssl_server_->run();
+ }
+ else
+#endif
+ {
+ server_ = std::move(std::unique_ptr(new server_t(this, endpoint, server_name_, &middlewares_, concurrency_, timeout_, nullptr)));
+ server_->set_tick_function(tick_interval_, tick_function_);
+ for (auto snum : signals_)
+ {
+ server_->signal_add(snum);
+ }
+ notify_server_start();
+ server_->run();
+ }
+ }
+
+ /// \brief Non-blocking version of \ref run()
+ ///
+ /// The output from this method needs to be saved into a variable!
+ /// Otherwise the call will be made on the same thread.
+ std::future run_async()
+ {
+ return std::async(std::launch::async, [&] {
+ this->run();
+ });
+ }
+
+ /// \brief Stop the server
+ void stop()
+ {
+#ifdef CROW_ENABLE_SSL
+ if (ssl_used_)
+ {
+ if (ssl_server_) { ssl_server_->stop(); }
+ }
+ else
+#endif
+ {
+ // TODO(EDev): Move these 6 lines to a method in http_server.
+ std::vector websockets_to_close = websockets_;
+ for (auto websocket : websockets_to_close)
+ {
+ CROW_LOG_INFO << "Quitting Websocket: " << websocket;
+ websocket->close("Server Application Terminated");
+ }
+ if (server_) { server_->stop(); }
+ }
+ }
+
+ void add_websocket(crow::websocket::connection* conn)
+ {
+ websockets_.push_back(conn);
+ }
+
+ void remove_websocket(crow::websocket::connection* conn)
+ {
+ websockets_.erase(std::remove(websockets_.begin(), websockets_.end(), conn), websockets_.end());
+ }
+
+ /// \brief Print the routing paths defined for each HTTP method
+ void debug_print()
+ {
+ CROW_LOG_DEBUG << "Routing:";
+ router_.debug_print();
+ }
+
+
+#ifdef CROW_ENABLE_SSL
+
+ /// \brief Use certificate and key files for SSL
+ self_t& ssl_file(const std::string& crt_filename, const std::string& key_filename)
+ {
+ ssl_used_ = true;
+ ssl_context_.set_verify_mode(asio::ssl::verify_peer);
+ ssl_context_.set_verify_mode(asio::ssl::verify_client_once);
+ ssl_context_.use_certificate_file(crt_filename, ssl_context_t::pem);
+ ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem);
+ ssl_context_.set_options(
+ asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3);
+ return *this;
+ }
+
+ /// \brief Use `.pem` file for SSL
+ self_t& ssl_file(const std::string& pem_filename)
+ {
+ ssl_used_ = true;
+ ssl_context_.set_verify_mode(asio::ssl::verify_peer);
+ ssl_context_.set_verify_mode(asio::ssl::verify_client_once);
+ ssl_context_.load_verify_file(pem_filename);
+ ssl_context_.set_options(
+ asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3);
+ return *this;
+ }
+
+ /// \brief Use certificate chain and key files for SSL
+ self_t& ssl_chainfile(const std::string& crt_filename, const std::string& key_filename)
+ {
+ ssl_used_ = true;
+ ssl_context_.set_verify_mode(asio::ssl::verify_peer);
+ ssl_context_.set_verify_mode(asio::ssl::verify_client_once);
+ ssl_context_.use_certificate_chain_file(crt_filename);
+ ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem);
+ ssl_context_.set_options(
+ asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3);
+ return *this;
+ }
+
+ self_t& ssl(asio::ssl::context&& ctx)
+ {
+ ssl_used_ = true;
+ ssl_context_ = std::move(ctx);
+ return *this;
+ }
+
+ bool ssl_used() const
+ {
+ return ssl_used_;
+ }
+#else
+
+ template
+ self_t& ssl_file(T&&, Remain&&...)
+ {
+ // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
+ static_assert(
+ // make static_assert dependent to T; always false
+ std::is_base_of::value,
+ "Define CROW_ENABLE_SSL to enable ssl support.");
+ return *this;
+ }
+
+ template
+ self_t& ssl_chainfile(T&&, Remain&&...)
+ {
+ // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
+ static_assert(
+ // make static_assert dependent to T; always false
+ std::is_base_of::value,
+ "Define CROW_ENABLE_SSL to enable ssl support.");
+ return *this;
+ }
+
+ template
+ self_t& ssl(T&&)
+ {
+ // We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
+ static_assert(
+ // make static_assert dependent to T; always false
+ std::is_base_of::value,
+ "Define CROW_ENABLE_SSL to enable ssl support.");
+ return *this;
+ }
+
+ bool ssl_used() const
+ {
+ return false;
+ }
+#endif
+
+ // middleware
+ using context_t = detail::context;
+ using mw_container_t = std::tuple;
+ template
+ typename T::context& get_context(const request& req)
+ {
+ static_assert(black_magic::contains::value, "App doesn't have the specified middleware type.");
+ auto& ctx = *reinterpret_cast(req.middleware_context);
+ return ctx.template get();
+ }
+
+ template
+ T& get_middleware()
+ {
+ return utility::get_element_by_type(middlewares_);
+ }
+
+ /// \brief Wait until the server has properly started
+ std::cv_status wait_for_server_start(std::chrono::milliseconds wait_timeout = std::chrono::milliseconds(3000))
+ {
+ std::cv_status status = std::cv_status::no_timeout;
+ auto wait_until = std::chrono::steady_clock::now() + wait_timeout;
+ {
+ std::unique_lock lock(start_mutex_);
+ while (!server_started_ && (status == std::cv_status::no_timeout))
+ {
+ status = cv_started_.wait_until(lock, wait_until);
+ }
+ }
+
+ if (status == std::cv_status::no_timeout)
+ {
+ if (server_)
+ {
+ status = server_->wait_for_start(wait_until);
+ }
+#ifdef CROW_ENABLE_SSL
+ else if (ssl_server_)
+ {
+ status = ssl_server_->wait_for_start(wait_until);
+ }
+#endif
+ }
+ return status;
+ }
+
+ private:
+ template
+ std::tuple make_middleware_tuple(Ts&&... ts)
+ {
+ auto fwd = std::forward_as_tuple((ts)...);
+ return std::make_tuple(
+ std::forward(
+ black_magic::tuple_extract(fwd))...);
+ }
+
+ /// \brief Notify anything using \ref wait_for_server_start() to proceed
+ void notify_server_start()
+ {
+ std::unique_lock lock(start_mutex_);
+ server_started_ = true;
+ cv_started_.notify_all();
+ }
+
+ void set_static_routes_added() {
+ static_routes_added_ = true;
+ }
+
+ bool are_static_routes_added() {
+ return static_routes_added_;
+ }
+
+ private:
+ std::uint8_t timeout_{5};
+ uint16_t port_ = 80;
+ uint16_t concurrency_ = 2;
+ uint64_t max_payload_{UINT64_MAX};
+ std::string server_name_ = std::string("Crow/") + VERSION;
+ std::string bindaddr_ = "0.0.0.0";
+ size_t res_stream_threshold_ = 1048576;
+ Router router_;
+ bool static_routes_added_{false};
+
+#ifdef CROW_ENABLE_COMPRESSION
+ compression::algorithm comp_algorithm_;
+ bool compression_used_{false};
+#endif
+
+ std::chrono::milliseconds tick_interval_;
+ std::function tick_function_;
+
+ std::tuple middlewares_;
+
+#ifdef CROW_ENABLE_SSL
+ std::unique_ptr ssl_server_;
+ bool ssl_used_{false};
+ ssl_context_t ssl_context_{asio::ssl::context::sslv23};
+#endif
+
+ std::unique_ptr server_;
+
+ std::vector signals_{SIGINT, SIGTERM};
+
+ bool server_started_{false};
+ std::condition_variable cv_started_;
+ std::mutex start_mutex_;
+ std::vector websockets_;
+ };
+
+ /// \brief Alias of Crow. Useful if you want
+ /// a instance of an Crow application that require Middlewares
+ template
+ using App = Crow;
+
+ /// \brief Alias of Crow<>. Useful if you want a instance of
+ /// an Crow application that doesn't require of Middlewares
+ using SimpleApp = Crow<>;
+} // namespace crow
diff --git a/include/crow/ci_map.h b/include/crow/ci_map.h
new file mode 100644
index 0000000..7196259
--- /dev/null
+++ b/include/crow/ci_map.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "crow/utility.h"
+
+namespace crow
+{
+ /// Hashing function for ci_map (unordered_multimap).
+ struct ci_hash
+ {
+ size_t operator()(const std::string_view key) const
+ {
+ std::size_t seed = 0;
+ std::locale locale;
+
+ for (auto c : key)
+ hash_combine(seed, std::toupper(c, locale));
+
+ return seed;
+ }
+
+ private:
+ static inline void hash_combine(std::size_t& seed, char v)
+ {
+ std::hash hasher;
+ seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+ }
+ };
+
+ /// Equals function for ci_map (unordered_multimap).
+ struct ci_key_eq
+ {
+ bool operator()(const std::string_view l, const std::string_view r) const
+ {
+ return utility::string_equals(l, r);
+ }
+ };
+
+ using ci_map = std::unordered_multimap;
+} // namespace crow
diff --git a/include/crow/common.h b/include/crow/common.h
new file mode 100644
index 0000000..8da94ae
--- /dev/null
+++ b/include/crow/common.h
@@ -0,0 +1,355 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include "crow/utility.h"
+
+namespace crow
+{
+ const char cr = '\r';
+ const char lf = '\n';
+ const std::string crlf("\r\n");
+
+ enum class HTTPMethod : char
+ {
+#ifndef DELETE
+ DELETE = 0,
+ GET,
+ HEAD,
+ POST,
+ PUT,
+
+ CONNECT,
+ OPTIONS,
+ TRACE,
+
+ PATCH,
+ PURGE,
+
+ COPY,
+ LOCK,
+ MKCOL,
+ MOVE,
+ PROPFIND,
+ PROPPATCH,
+ SEARCH,
+ UNLOCK,
+ BIND,
+ REBIND,
+ UNBIND,
+ ACL,
+
+ REPORT,
+ MKACTIVITY,
+ CHECKOUT,
+ MERGE,
+
+ MSEARCH,
+ NOTIFY,
+ SUBSCRIBE,
+ UNSUBSCRIBE,
+
+ MKCALENDAR,
+
+ LINK,
+ UNLINK,
+
+ SOURCE,
+#endif
+
+ Delete = 0,
+ Get,
+ Head,
+ Post,
+ Put,
+
+ Connect,
+ Options,
+ Trace,
+
+ Patch,
+ Purge,
+
+ Copy,
+ Lock,
+ MkCol,
+ Move,
+ Propfind,
+ Proppatch,
+ Search,
+ Unlock,
+ Bind,
+ Rebind,
+ Unbind,
+ Acl,
+
+ Report,
+ MkActivity,
+ Checkout,
+ Merge,
+
+ MSearch,
+ Notify,
+ Subscribe,
+ Unsubscribe,
+
+ MkCalendar,
+
+ Link,
+ Unlink,
+
+ Source,
+
+
+ InternalMethodCount,
+ // should not add an item below this line: used for array count
+ };
+
+ constexpr const char* method_strings[] =
+ {
+ "DELETE",
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+
+ "CONNECT",
+ "OPTIONS",
+ "TRACE",
+
+ "PATCH",
+ "PURGE",
+
+ "COPY",
+ "LOCK",
+ "MKCOL",
+ "MOVE",
+ "PROPFIND",
+ "PROPPATCH",
+ "SEARCH",
+ "UNLOCK",
+ "BIND",
+ "REBIND",
+ "UNBIND",
+ "ACL",
+
+ "REPORT",
+ "MKACTIVITY",
+ "CHECKOUT",
+ "MERGE",
+
+ "M-SEARCH",
+ "NOTIFY",
+ "SUBSCRIBE",
+ "UNSUBSCRIBE",
+
+ "MKCALENDAR",
+
+ "LINK",
+ "UNLINK",
+
+ "SOURCE"};
+
+
+ inline std::string method_name(HTTPMethod method)
+ {
+ if (CROW_LIKELY(method < HTTPMethod::InternalMethodCount))
+ {
+ return method_strings[(unsigned char)method];
+ }
+ return "invalid";
+ }
+
+ // clang-format off
+
+ enum status
+ {
+ CONTINUE = 100,
+ SWITCHING_PROTOCOLS = 101,
+
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NON_AUTHORITATIVE_INFORMATION = 203,
+ NO_CONTENT = 204,
+ RESET_CONTENT = 205,
+ PARTIAL_CONTENT = 206,
+
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY = 301,
+ FOUND = 302,
+ SEE_OTHER = 303,
+ NOT_MODIFIED = 304,
+ TEMPORARY_REDIRECT = 307,
+ PERMANENT_REDIRECT = 308,
+
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ METHOD_NOT_ALLOWED = 405,
+ NOT_ACCEPTABLE = 406,
+ PROXY_AUTHENTICATION_REQUIRED = 407,
+ CONFLICT = 409,
+ GONE = 410,
+ PAYLOAD_TOO_LARGE = 413,
+ UNSUPPORTED_MEDIA_TYPE = 415,
+ RANGE_NOT_SATISFIABLE = 416,
+ EXPECTATION_FAILED = 417,
+ PRECONDITION_REQUIRED = 428,
+ TOO_MANY_REQUESTS = 429,
+ UNAVAILABLE_FOR_LEGAL_REASONS = 451,
+
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+ BAD_GATEWAY = 502,
+ SERVICE_UNAVAILABLE = 503,
+ GATEWAY_TIMEOUT = 504,
+ VARIANT_ALSO_NEGOTIATES = 506
+ };
+
+ // clang-format on
+
+ enum class ParamType : char
+ {
+ INT,
+ UINT,
+ DOUBLE,
+ STRING,
+ PATH,
+
+ MAX
+ };
+
+ /// @cond SKIP
+ struct routing_params
+ {
+ std::vector int_params;
+ std::vector uint_params;
+ std::vector double_params;
+ std::vector string_params;
+
+ void debug_print() const
+ {
+ std::cerr << "routing_params" << std::endl;
+ for (auto i : int_params)
+ std::cerr << i << ", ";
+ std::cerr << std::endl;
+ for (auto i : uint_params)
+ std::cerr << i << ", ";
+ std::cerr << std::endl;
+ for (auto i : double_params)
+ std::cerr << i << ", ";
+ std::cerr << std::endl;
+ for (auto& i : string_params)
+ std::cerr << i << ", ";
+ std::cerr << std::endl;
+ }
+
+ template
+ T get(unsigned) const;
+ };
+
+ template<>
+ inline int64_t routing_params::get(unsigned index) const
+ {
+ return int_params[index];
+ }
+
+ template<>
+ inline uint64_t routing_params::get(unsigned index) const
+ {
+ return uint_params[index];
+ }
+
+ template<>
+ inline double routing_params::get(unsigned index) const
+ {
+ return double_params[index];
+ }
+
+ template<>
+ inline std::string routing_params::get(unsigned index) const
+ {
+ return string_params[index];
+ }
+ /// @endcond
+
+ struct routing_handle_result
+ {
+ uint16_t rule_index;
+ std::vector blueprint_indices;
+ routing_params r_params;
+ HTTPMethod method;
+
+ routing_handle_result() {}
+
+ routing_handle_result(uint16_t rule_index_, std::vector blueprint_indices_, routing_params r_params_):
+ rule_index(rule_index_),
+ blueprint_indices(blueprint_indices_),
+ r_params(r_params_) {}
+
+ routing_handle_result(uint16_t rule_index_, std::vector blueprint_indices_, routing_params r_params_, HTTPMethod method_):
+ rule_index(rule_index_),
+ blueprint_indices(blueprint_indices_),
+ r_params(r_params_),
+ method(method_) {}
+ };
+} // namespace crow
+
+// clang-format off
+#ifndef CROW_MSVC_WORKAROUND
+constexpr crow::HTTPMethod method_from_string(const char* str)
+{
+ return crow::black_magic::is_equ_p(str, "GET", 3) ? crow::HTTPMethod::Get :
+ crow::black_magic::is_equ_p(str, "DELETE", 6) ? crow::HTTPMethod::Delete :
+ crow::black_magic::is_equ_p(str, "HEAD", 4) ? crow::HTTPMethod::Head :
+ crow::black_magic::is_equ_p(str, "POST", 4) ? crow::HTTPMethod::Post :
+ crow::black_magic::is_equ_p(str, "PUT", 3) ? crow::HTTPMethod::Put :
+
+ crow::black_magic::is_equ_p(str, "OPTIONS", 7) ? crow::HTTPMethod::Options :
+ crow::black_magic::is_equ_p(str, "CONNECT", 7) ? crow::HTTPMethod::Connect :
+ crow::black_magic::is_equ_p(str, "TRACE", 5) ? crow::HTTPMethod::Trace :
+
+ crow::black_magic::is_equ_p(str, "PATCH", 5) ? crow::HTTPMethod::Patch :
+ crow::black_magic::is_equ_p(str, "PURGE", 5) ? crow::HTTPMethod::Purge :
+ crow::black_magic::is_equ_p(str, "COPY", 4) ? crow::HTTPMethod::Copy :
+ crow::black_magic::is_equ_p(str, "LOCK", 4) ? crow::HTTPMethod::Lock :
+ crow::black_magic::is_equ_p(str, "MKCOL", 5) ? crow::HTTPMethod::MkCol :
+ crow::black_magic::is_equ_p(str, "MOVE", 4) ? crow::HTTPMethod::Move :
+ crow::black_magic::is_equ_p(str, "PROPFIND", 8) ? crow::HTTPMethod::Propfind :
+ crow::black_magic::is_equ_p(str, "PROPPATCH", 9) ? crow::HTTPMethod::Proppatch :
+ crow::black_magic::is_equ_p(str, "SEARCH", 6) ? crow::HTTPMethod::Search :
+ crow::black_magic::is_equ_p(str, "UNLOCK", 6) ? crow::HTTPMethod::Unlock :
+ crow::black_magic::is_equ_p(str, "BIND", 4) ? crow::HTTPMethod::Bind :
+ crow::black_magic::is_equ_p(str, "REBIND", 6) ? crow::HTTPMethod::Rebind :
+ crow::black_magic::is_equ_p(str, "UNBIND", 6) ? crow::HTTPMethod::Unbind :
+ crow::black_magic::is_equ_p(str, "ACL", 3) ? crow::HTTPMethod::Acl :
+
+ crow::black_magic::is_equ_p(str, "REPORT", 6) ? crow::HTTPMethod::Report :
+ crow::black_magic::is_equ_p(str, "MKACTIVITY", 10) ? crow::HTTPMethod::MkActivity :
+ crow::black_magic::is_equ_p(str, "CHECKOUT", 8) ? crow::HTTPMethod::Checkout :
+ crow::black_magic::is_equ_p(str, "MERGE", 5) ? crow::HTTPMethod::Merge :
+
+ crow::black_magic::is_equ_p(str, "MSEARCH", 7) ? crow::HTTPMethod::MSearch :
+ crow::black_magic::is_equ_p(str, "NOTIFY", 6) ? crow::HTTPMethod::Notify :
+ crow::black_magic::is_equ_p(str, "SUBSCRIBE", 9) ? crow::HTTPMethod::Subscribe :
+ crow::black_magic::is_equ_p(str, "UNSUBSCRIBE", 11) ? crow::HTTPMethod::Unsubscribe :
+
+ crow::black_magic::is_equ_p(str, "MKCALENDAR", 10) ? crow::HTTPMethod::MkCalendar :
+
+ crow::black_magic::is_equ_p(str, "LINK", 4) ? crow::HTTPMethod::Link :
+ crow::black_magic::is_equ_p(str, "UNLINK", 6) ? crow::HTTPMethod::Unlink :
+
+ crow::black_magic::is_equ_p(str, "SOURCE", 6) ? crow::HTTPMethod::Source :
+ throw std::runtime_error("invalid http method");
+}
+
+constexpr crow::HTTPMethod operator"" _method(const char* str, size_t /*len*/)
+{
+ return method_from_string( str );
+}
+#endif
+// clang-format on
diff --git a/include/crow/compression.h b/include/crow/compression.h
new file mode 100644
index 0000000..5cac299
--- /dev/null
+++ b/include/crow/compression.h
@@ -0,0 +1,99 @@
+#ifdef CROW_ENABLE_COMPRESSION
+#pragma once
+
+#include
+#include
+
+// http://zlib.net/manual.html
+namespace crow // NOTE: Already documented in "crow/app.h"
+{
+ namespace compression
+ {
+ // Values used in the 'windowBits' parameter for deflateInit2.
+ enum algorithm
+ {
+ // 15 is the default value for deflate
+ DEFLATE = 15,
+ // windowBits can also be greater than 15 for optional gzip encoding.
+ // Add 16 to windowBits to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper.
+ GZIP = 15 | 16,
+ };
+
+ inline std::string compress_string(std::string const& str, algorithm algo)
+ {
+ std::string compressed_str;
+ z_stream stream{};
+ // Initialize with the default values
+ if (::deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, algo, 8, Z_DEFAULT_STRATEGY) == Z_OK)
+ {
+ char buffer[8192];
+
+ stream.avail_in = str.size();
+ // zlib does not take a const pointer. The data is not altered.
+ stream.next_in = const_cast(reinterpret_cast(str.c_str()));
+
+ int code = Z_OK;
+ do
+ {
+ stream.avail_out = sizeof(buffer);
+ stream.next_out = reinterpret_cast(&buffer[0]);
+
+ code = ::deflate(&stream, Z_FINISH);
+ // Successful and non-fatal error code returned by deflate when used with Z_FINISH flush
+ if (code == Z_OK || code == Z_STREAM_END)
+ {
+ std::copy(&buffer[0], &buffer[sizeof(buffer) - stream.avail_out], std::back_inserter(compressed_str));
+ }
+
+ } while (code == Z_OK);
+
+ if (code != Z_STREAM_END)
+ compressed_str.clear();
+
+ ::deflateEnd(&stream);
+ }
+
+ return compressed_str;
+ }
+
+ inline std::string decompress_string(std::string const& deflated_string)
+ {
+ std::string inflated_string;
+ Bytef tmp[8192];
+
+ z_stream zstream{};
+ zstream.avail_in = deflated_string.size();
+ // Nasty const_cast but zlib won't alter its contents
+ zstream.next_in = const_cast(reinterpret_cast(deflated_string.c_str()));
+ // Initialize with automatic header detection, for gzip support
+ if (::inflateInit2(&zstream, MAX_WBITS | 32) == Z_OK)
+ {
+ do
+ {
+ zstream.avail_out = sizeof(tmp);
+ zstream.next_out = &tmp[0];
+
+ auto ret = ::inflate(&zstream, Z_NO_FLUSH);
+ if (ret == Z_OK || ret == Z_STREAM_END)
+ {
+ std::copy(&tmp[0], &tmp[sizeof(tmp) - zstream.avail_out], std::back_inserter(inflated_string));
+ }
+ else
+ {
+ // Something went wrong with inflate; make sure we return an empty string
+ inflated_string.clear();
+ break;
+ }
+
+ } while (zstream.avail_out == 0);
+
+ // Free zlib's internal memory
+ ::inflateEnd(&zstream);
+ }
+
+ return inflated_string;
+ }
+ } // namespace compression
+} // namespace crow
+
+#endif
diff --git a/include/crow/exceptions.h b/include/crow/exceptions.h
new file mode 100644
index 0000000..63a6e8e
--- /dev/null
+++ b/include/crow/exceptions.h
@@ -0,0 +1,14 @@
+#pragma once
+#include
+
+namespace crow
+{
+ struct bad_request : public std::runtime_error
+ {
+ bad_request(const std::string& what_arg)
+ : std::runtime_error(what_arg) {}
+
+ bad_request(const char* what_arg)
+ : std::runtime_error(what_arg) {}
+ };
+}
\ No newline at end of file
diff --git a/include/crow/http_connection.h b/include/crow/http_connection.h
new file mode 100644
index 0000000..e931392
--- /dev/null
+++ b/include/crow/http_connection.h
@@ -0,0 +1,629 @@
+#pragma once
+
+#ifdef CROW_USE_BOOST
+#include
+#else
+#ifndef ASIO_STANDALONE
+#define ASIO_STANDALONE
+#endif
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+
+#include "crow/http_parser_merged.h"
+#include "crow/common.h"
+#include "crow/compression.h"
+#include "crow/http_response.h"
+#include "crow/logging.h"
+#include "crow/middleware.h"
+#include "crow/middleware_context.h"
+#include "crow/parser.h"
+#include "crow/settings.h"
+#include "crow/socket_adaptors.h"
+#include "crow/task_timer.h"
+#include "crow/utility.h"
+
+namespace crow
+{
+#ifdef CROW_USE_BOOST
+ namespace asio = boost::asio;
+ using error_code = boost::system::error_code;
+#else
+ using error_code = asio::error_code;
+#endif
+ using tcp = asio::ip::tcp;
+
+#ifdef CROW_ENABLE_DEBUG
+ static std::atomic connectionCount;
+#endif
+
+ /// An HTTP connection.
+ template
+ class Connection : public std::enable_shared_from_this>
+ {
+ friend struct crow::response;
+
+ public:
+ Connection(
+ asio::io_context& io_context,
+ Handler* handler,
+ const std::string& server_name,
+ std::tuple* middlewares,
+ std::function& get_cached_date_str_f,
+ detail::task_timer& task_timer,
+ typename Adaptor::context* adaptor_ctx_,
+ std::atomic& queue_length):
+ adaptor_(io_context, adaptor_ctx_),
+ handler_(handler),
+ parser_(this),
+ req_(parser_.req),
+ server_name_(server_name),
+ middlewares_(middlewares),
+ get_cached_date_str(get_cached_date_str_f),
+ task_timer_(task_timer),
+ res_stream_threshold_(handler->stream_threshold()),
+ queue_length_(queue_length)
+ {
+#ifdef CROW_ENABLE_DEBUG
+ connectionCount++;
+ CROW_LOG_DEBUG << "Connection (" << this << ") allocated, total: " << connectionCount;
+#endif
+ }
+
+ ~Connection()
+ {
+#ifdef CROW_ENABLE_DEBUG
+ connectionCount--;
+ CROW_LOG_DEBUG << "Connection (" << this << ") freed, total: " << connectionCount;
+#endif
+ }
+
+ /// The TCP socket on top of which the connection is established.
+ decltype(std::declval().raw_socket())& socket()
+ {
+ return adaptor_.raw_socket();
+ }
+
+ void start()
+ {
+ auto self = this->shared_from_this();
+ adaptor_.start([self](const error_code& ec) {
+ if (!ec)
+ {
+ self->start_deadline();
+ self->parser_.clear();
+
+ self->do_read();
+ }
+ else
+ {
+ CROW_LOG_ERROR << "Could not start adaptor: " << ec.message();
+ }
+ });
+ }
+
+ void handle_url()
+ {
+ routing_handle_result_ = handler_->handle_initial(req_, res);
+ // if no route is found for the request method, return the response without parsing or processing anything further.
+ if (!routing_handle_result_->rule_index)
+ {
+ parser_.done();
+ need_to_call_after_handlers_ = true;
+ complete_request();
+ }
+ }
+
+ void handle_header()
+ {
+ // HTTP 1.1 Expect: 100-continue
+ if (req_.http_ver_major == 1 && req_.http_ver_minor == 1 && get_header_value(req_.headers, "expect") == "100-continue")
+ {
+ continue_requested = true;
+ buffers_.clear();
+ static std::string expect_100_continue = "HTTP/1.1 100 Continue\r\n\r\n";
+ buffers_.emplace_back(expect_100_continue.data(), expect_100_continue.size());
+ do_write_sync(buffers_);
+ }
+ }
+
+ void handle()
+ {
+ // TODO(EDev): cancel_deadline_timer should be looked into, it might be a good idea to add it to handle_url() and then restart the timer once everything passes
+ cancel_deadline_timer();
+ bool is_invalid_request = false;
+ add_keep_alive_ = false;
+
+ // Create context
+ ctx_ = detail::context();
+ req_.middleware_context = static_cast(&ctx_);
+ req_.middleware_container = static_cast(middlewares_);
+ req_.io_context = &adaptor_.get_io_context();
+
+ req_.remote_ip_address = adaptor_.remote_endpoint().address().to_string();
+
+ add_keep_alive_ = req_.keep_alive;
+ close_connection_ = req_.close_connection;
+
+ if (req_.check_version(1, 1)) // HTTP/1.1
+ {
+ if (!req_.headers.count("host"))
+ {
+ is_invalid_request = true;
+ res = response(400);
+ }
+ else if (req_.upgrade)
+ {
+ // h2 or h2c headers
+ if (req_.get_header_value("upgrade").find("h2")==0)
+ {
+ // TODO(ipkn): HTTP/2
+ // currently, ignore upgrade header
+ }
+ else
+ {
+
+ detail::middleware_call_helper({}, *middlewares_, req_, res, ctx_);
+ close_connection_ = true;
+ handler_->handle_upgrade(req_, res, std::move(adaptor_));
+ return;
+ }
+ }
+ }
+
+ CROW_LOG_INFO << "Request: " << utility::lexical_cast(adaptor_.remote_endpoint()) << " " << this << " HTTP/" << (char)(req_.http_ver_major + '0') << "." << (char)(req_.http_ver_minor + '0') << ' ' << method_name(req_.method) << " " << req_.url;
+
+
+ need_to_call_after_handlers_ = false;
+ if (!is_invalid_request)
+ {
+ res.complete_request_handler_ = nullptr;
+ auto self = this->shared_from_this();
+ res.is_alive_helper_ = [self]() -> bool {
+ return self->adaptor_.is_open();
+ };
+
+ detail::middleware_call_helper({}, *middlewares_, req_, res, ctx_);
+
+ if (!res.completed_)
+ {
+ res.complete_request_handler_ = [self] {
+ self->complete_request();
+ };
+ need_to_call_after_handlers_ = true;
+ handler_->handle(req_, res, routing_handle_result_);
+ if (add_keep_alive_)
+ res.set_header("connection", "Keep-Alive");
+ }
+ else
+ {
+ complete_request();
+ }
+ }
+ else
+ {
+ complete_request();
+ }
+ }
+
+ /// Call the after handle middleware and send the write the response to the connection.
+ void complete_request()
+ {
+ CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_;
+ res.is_alive_helper_ = nullptr;
+
+ if (need_to_call_after_handlers_)
+ {
+ need_to_call_after_handlers_ = false;
+
+ // call all after_handler of middlewares
+ detail::after_handlers_call_helper<
+ detail::middleware_call_criteria_only_global,
+ (static_cast(sizeof...(Middlewares)) - 1),
+ decltype(ctx_),
+ decltype(*middlewares_)>({}, *middlewares_, ctx_, req_, res);
+ }
+#ifdef CROW_ENABLE_COMPRESSION
+ if (!res.body.empty() && handler_->compression_used())
+ {
+ std::string accept_encoding = req_.get_header_value("Accept-Encoding");
+ if (!accept_encoding.empty() && res.compressed)
+ {
+ switch (handler_->compression_algorithm())
+ {
+ case compression::DEFLATE:
+ if (accept_encoding.find("deflate") != std::string::npos)
+ {
+ res.body = compression::compress_string(res.body, compression::algorithm::DEFLATE);
+ res.set_header("Content-Encoding", "deflate");
+ }
+ break;
+ case compression::GZIP:
+ if (accept_encoding.find("gzip") != std::string::npos)
+ {
+ res.body = compression::compress_string(res.body, compression::algorithm::GZIP);
+ res.set_header("Content-Encoding", "gzip");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+#endif
+
+ prepare_buffers();
+
+ if (res.is_static_type())
+ {
+ do_write_static();
+ }
+ else
+ {
+ do_write_general();
+ }
+ }
+
+ private:
+ void prepare_buffers()
+ {
+ res.complete_request_handler_ = nullptr;
+ res.is_alive_helper_ = nullptr;
+
+ if (!adaptor_.is_open())
+ {
+ //CROW_LOG_DEBUG << this << " delete (socket is closed) " << is_reading << ' ' << is_writing;
+ //delete this;
+ return;
+ }
+ // TODO(EDev): HTTP version in status codes should be dynamic
+ // Keep in sync with common.h/status
+ static std::unordered_map statusCodes = {
+ {status::CONTINUE, "HTTP/1.1 100 Continue\r\n"},
+ {status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"},
+
+ {status::OK, "HTTP/1.1 200 OK\r\n"},
+ {status::CREATED, "HTTP/1.1 201 Created\r\n"},
+ {status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"},
+ {status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"},
+ {status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"},
+ {status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"},
+ {status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"},
+
+ {status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"},
+ {status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"},
+ {status::FOUND, "HTTP/1.1 302 Found\r\n"},
+ {status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"},
+ {status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"},
+ {status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"},
+ {status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"},
+
+ {status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"},
+ {status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"},
+ {status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
+ {status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
+ {status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
+ {status::NOT_ACCEPTABLE, "HTTP/1.1 406 Not Acceptable\r\n"},
+ {status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
+ {status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
+ {status::GONE, "HTTP/1.1 410 Gone\r\n"},
+ {status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"},
+ {status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"},
+ {status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"},
+ {status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"},
+ {status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"},
+ {status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"},
+ {status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"},
+
+ {status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"},
+ {status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"},
+ {status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"},
+ {status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"},
+ {status::GATEWAY_TIMEOUT, "HTTP/1.1 504 Gateway Timeout\r\n"},
+ {status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"},
+ };
+
+ static const std::string seperator = ": ";
+
+ buffers_.clear();
+ buffers_.reserve(4 * (res.headers.size() + 5) + 3);
+
+ if (!statusCodes.count(res.code))
+ {
+ CROW_LOG_WARNING << this << " status code "
+ << "(" << res.code << ")"
+ << " not defined, returning 500 instead";
+ res.code = 500;
+ }
+
+ auto& status = statusCodes.find(res.code)->second;
+ buffers_.emplace_back(status.data(), status.size());
+
+ if (res.code >= 400 && res.body.empty())
+ res.body = statusCodes[res.code].substr(9);
+
+ for (auto& kv : res.headers)
+ {
+ buffers_.emplace_back(kv.first.data(), kv.first.size());
+ buffers_.emplace_back(seperator.data(), seperator.size());
+ buffers_.emplace_back(kv.second.data(), kv.second.size());
+ buffers_.emplace_back(crlf.data(), crlf.size());
+ }
+
+ if (!res.manual_length_header && !res.headers.count("content-length"))
+ {
+ content_length_ = std::to_string(res.body.size());
+ static std::string content_length_tag = "Content-Length: ";
+ buffers_.emplace_back(content_length_tag.data(), content_length_tag.size());
+ buffers_.emplace_back(content_length_.data(), content_length_.size());
+ buffers_.emplace_back(crlf.data(), crlf.size());
+ }
+ if (!res.headers.count("server"))
+ {
+ static std::string server_tag = "Server: ";
+ buffers_.emplace_back(server_tag.data(), server_tag.size());
+ buffers_.emplace_back(server_name_.data(), server_name_.size());
+ buffers_.emplace_back(crlf.data(), crlf.size());
+ }
+ if (!res.headers.count("date"))
+ {
+ static std::string date_tag = "Date: ";
+ date_str_ = get_cached_date_str();
+ buffers_.emplace_back(date_tag.data(), date_tag.size());
+ buffers_.emplace_back(date_str_.data(), date_str_.size());
+ buffers_.emplace_back(crlf.data(), crlf.size());
+ }
+ if (add_keep_alive_)
+ {
+ static std::string keep_alive_tag = "Connection: Keep-Alive";
+ buffers_.emplace_back(keep_alive_tag.data(), keep_alive_tag.size());
+ buffers_.emplace_back(crlf.data(), crlf.size());
+ }
+
+ buffers_.emplace_back(crlf.data(), crlf.size());
+ }
+
+ void do_write_static()
+ {
+ asio::write(adaptor_.socket(), buffers_);
+
+ if (res.file_info.statResult == 0)
+ {
+ std::ifstream is(res.file_info.path.c_str(), std::ios::in | std::ios::binary);
+ std::vector buffers{1};
+ char buf[16384];
+ is.read(buf, sizeof(buf));
+ while (is.gcount() > 0)
+ {
+ buffers[0] = asio::buffer(buf, is.gcount());
+ do_write_sync(buffers);
+ is.read(buf, sizeof(buf));
+ }
+ }
+ if (close_connection_)
+ {
+ adaptor_.shutdown_readwrite();
+ adaptor_.close();
+ CROW_LOG_DEBUG << this << " from write (static)";
+ }
+
+ res.end();
+ res.clear();
+ buffers_.clear();
+ parser_.clear();
+ }
+
+ void do_write_general()
+ {
+ if (res.body.length() < res_stream_threshold_)
+ {
+ res_body_copy_.swap(res.body);
+ buffers_.emplace_back(res_body_copy_.data(), res_body_copy_.size());
+
+ do_write_sync(buffers_);
+
+ if (need_to_start_read_after_complete_)
+ {
+ need_to_start_read_after_complete_ = false;
+ start_deadline();
+ do_read();
+ }
+ }
+ else
+ {
+ asio::write(adaptor_.socket(), buffers_); // Write the response start / headers
+ cancel_deadline_timer();
+ if (res.body.length() > 0)
+ {
+ std::vector buffers{1};
+ const uint8_t* data = reinterpret_cast(res.body.data());
+ size_t length = res.body.length();
+ for (size_t transferred = 0; transferred < length;)
+ {
+ size_t to_transfer = CROW_MIN(16384UL, length - transferred);
+ buffers[0] = asio::const_buffer(data + transferred, to_transfer);
+ do_write_sync(buffers);
+ transferred += to_transfer;
+ }
+ }
+ if (close_connection_)
+ {
+ adaptor_.shutdown_readwrite();
+ adaptor_.close();
+ CROW_LOG_DEBUG << this << " from write (res_stream)";
+ }
+
+ res.end();
+ res.clear();
+ buffers_.clear();
+ parser_.clear();
+ }
+ }
+
+ void do_read()
+ {
+ auto self = this->shared_from_this();
+ adaptor_.socket().async_read_some(
+ asio::buffer(buffer_),
+ [self](const error_code& ec, std::size_t bytes_transferred) {
+ bool error_while_reading = true;
+ if (!ec)
+ {
+ bool ret = self->parser_.feed(self->buffer_.data(), bytes_transferred);
+ if (ret && self->adaptor_.is_open())
+ {
+ error_while_reading = false;
+ }
+ }
+
+ if (error_while_reading)
+ {
+ self->cancel_deadline_timer();
+ self->parser_.done();
+ self->adaptor_.shutdown_read();
+ self->adaptor_.close();
+ CROW_LOG_DEBUG << self << " from read(1) with description: \"" << http_errno_description(static_cast(self->parser_.http_errno)) << '\"';
+ }
+ else if (self->close_connection_)
+ {
+ self->cancel_deadline_timer();
+ self->parser_.done();
+ // adaptor will close after write
+ }
+ else if (!self->need_to_call_after_handlers_)
+ {
+ self->start_deadline();
+ self->do_read();
+ }
+ else
+ {
+ // res will be completed later by user
+ self->need_to_start_read_after_complete_ = true;
+ }
+ });
+ }
+
+ void do_write()
+ {
+ auto self = this->shared_from_this();
+ asio::async_write(
+ adaptor_.socket(), buffers_,
+ [self](const error_code& ec, std::size_t /*bytes_transferred*/) {
+ self->res.clear();
+ self->res_body_copy_.clear();
+ if (!self->continue_requested)
+ {
+ self->parser_.clear();
+ }
+ else
+ {
+ self->continue_requested = false;
+ }
+
+ if (!ec)
+ {
+ if (self->close_connection_)
+ {
+ self->adaptor_.shutdown_write();
+ self->adaptor_.close();
+ CROW_LOG_DEBUG << self << " from write(1)";
+ }
+ }
+ else
+ {
+ CROW_LOG_DEBUG << self << " from write(2)";
+ }
+ });
+ }
+
+ inline void do_write_sync(std::vector& buffers)
+ {
+ error_code ec;
+ asio::write(adaptor_.socket(), buffers, ec);
+
+ this->res.clear();
+ this->res_body_copy_.clear();
+ if (this->continue_requested)
+ {
+ this->continue_requested = false;
+ }
+ else
+ {
+ this->parser_.clear();
+ }
+
+ if (ec)
+ {
+ CROW_LOG_ERROR << ec << " - happened while sending buffers";
+ CROW_LOG_DEBUG << this << " from write (sync)(2)";
+ }
+ }
+
+ void cancel_deadline_timer()
+ {
+ CROW_LOG_DEBUG << this << " timer cancelled: " << &task_timer_ << ' ' << task_id_;
+ task_timer_.cancel(task_id_);
+ }
+
+ void start_deadline(/*int timeout = 5*/)
+ {
+ cancel_deadline_timer();
+
+ auto self = this->shared_from_this();
+ task_id_ = task_timer_.schedule([self] {
+ if (!self->adaptor_.is_open())
+ {
+ return;
+ }
+ self->adaptor_.shutdown_readwrite();
+ self->adaptor_.close();
+ });
+ CROW_LOG_DEBUG << this << " timer added: " << &task_timer_ << ' ' << task_id_;
+ }
+
+ private:
+ Adaptor adaptor_;
+ Handler* handler_;
+
+ std::array buffer_;
+
+ HTTPParser parser_;
+ std::unique_ptr routing_handle_result_;
+ request& req_;
+ response res;
+
+ bool close_connection_ = false;
+
+ const std::string& server_name_;
+ std::vector buffers_;
+
+ std::string content_length_;
+ std::string date_str_;
+ std::string res_body_copy_;
+
+ detail::task_timer::identifier_type task_id_{};
+
+ bool continue_requested{};
+ bool need_to_call_after_handlers_{};
+ bool need_to_start_read_after_complete_{};
+ bool add_keep_alive_{};
+
+ std::tuple* middlewares_;
+ detail::context ctx_;
+
+ std::function& get_cached_date_str;
+ detail::task_timer& task_timer_;
+
+ size_t res_stream_threshold_;
+
+ std::atomic& queue_length_;
+ };
+
+} // namespace crow
diff --git a/include/crow/http_parser_merged.h b/include/crow/http_parser_merged.h
new file mode 100644
index 0000000..0efe0df
--- /dev/null
+++ b/include/crow/http_parser_merged.h
@@ -0,0 +1,2015 @@
+/* merged revision: 5b951d74bd66ec9d38448e0a85b1cf8b85d97db3 */
+/* updated to : e13b274770da9b82a1085dec29182acfea72e7a7 (beyond v2.9.5) */
+/* commits not included:
+ * 091ebb87783a58b249062540bbea07de2a11e9cf
+ * 6132d1fefa03f769a3979355d1f5da0b8889cad2
+ * 7ba312397c2a6c851a4b5efe6c1603b1e1bda6ff
+ * d7675453a6c03180572f084e95eea0d02df39164
+ * dff604db203986e532e5a679bafd0e7382c6bdd9 (Might be useful to actually add [upgrade requests with a body])
+ * e01811e7f4894d7f0f7f4bd8492cccec6f6b4038 (related to above)
+ * 05525c5fde1fc562481f6ae08fa7056185325daf (also related to above)
+ * 350258965909f249f9c59823aac240313e0d0120 (cannot be implemented due to upgrade)
+ */
+
+// clang-format off
+#pragma once
+extern "C" {
+#include
+#if defined(_WIN32) && !defined(__MINGW32__) && \
+ (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
+#include
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#elif (defined(__sun) || defined(__sun__)) && defined(__SunOS_5_9)
+#include
+#else
+#include
+#endif
+#include
+#include
+#include
+#include
+}
+
+#include "crow/common.h"
+namespace crow
+{
+/* Maximium header size allowed. If the macro is not defined
+ * before including this header then the default is used. To
+ * change the maximum header size, define the macro in the build
+ * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove
+ * the effective limit on the size of the header, define the macro
+ * to a very large number (e.g. -DCROW_HTTP_MAX_HEADER_SIZE=0x7fffffff)
+ */
+#ifndef CROW_HTTP_MAX_HEADER_SIZE
+# define CROW_HTTP_MAX_HEADER_SIZE (80*1024)
+#endif
+
+typedef struct http_parser http_parser;
+typedef struct http_parser_settings http_parser_settings;
+
+/* Callbacks should return non-zero to indicate an error. The parser will
+ * then halt execution.
+ *
+ * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
+ * returning '1' from on_headers_complete will tell the parser that it
+ * should not expect a body. This is used when receiving a response to a
+ * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
+ * chunked' headers that indicate the presence of a body.
+ *
+ * Returning `2` from on_headers_complete will tell parser that it should not
+ * expect neither a body nor any futher responses on this connection. This is
+ * useful for handling responses to a CONNECT request which may not contain
+ * `Upgrade` or `Connection: upgrade` headers.
+ *
+ * http_data_cb does not return data chunks. It will be called arbitrarally
+ * many times for each string. E.G. you might get 10 callbacks for "on_url"
+ * each providing just a few characters more data.
+ */
+typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
+typedef int (*http_cb) (http_parser*);
+
+
+/* Flag values for http_parser.flags field */
+enum http_connection_flags // This is basically 7 booleans placed into 1 integer. Uses 4 bytes instead of n bytes (7 currently).
+ { F_CHUNKED = 1 << 0 // 00000000 00000000 00000000 00000001
+ , F_CONNECTION_KEEP_ALIVE = 1 << 1 // 00000000 00000000 00000000 00000010
+ , F_CONNECTION_CLOSE = 1 << 2 // 00000000 00000000 00000000 00000100
+ , F_TRAILING = 1 << 3 // 00000000 00000000 00000000 00001000
+ , F_UPGRADE = 1 << 4 // 00000000 00000000 00000000 00010000
+ , F_SKIPBODY = 1 << 5 // 00000000 00000000 00000000 00100000
+ , F_CONTENTLENGTH = 1 << 6 // 00000000 00000000 00000000 01000000
+ };
+
+
+/* Map for errno-related constants
+ *
+ * The provided argument should be a macro that takes 2 arguments.
+ */
+#define CROW_HTTP_ERRNO_MAP(CROW_XX) \
+ /* No error */ \
+ CROW_XX(OK, "success") \
+ \
+ /* Callback-related errors */ \
+ CROW_XX(CB_message_begin, "the on_message_begin callback failed") \
+ CROW_XX(CB_method, "the on_method callback failed") \
+ CROW_XX(CB_url, "the \"on_url\" callback failed") \
+ CROW_XX(CB_header_field, "the \"on_header_field\" callback failed") \
+ CROW_XX(CB_header_value, "the \"on_header_value\" callback failed") \
+ CROW_XX(CB_headers_complete, "the \"on_headers_complete\" callback failed") \
+ CROW_XX(CB_body, "the \"on_body\" callback failed") \
+ CROW_XX(CB_message_complete, "the \"on_message_complete\" callback failed") \
+ CROW_XX(CB_status, "the \"on_status\" callback failed") \
+ \
+ /* Parsing-related errors */ \
+ CROW_XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
+ CROW_XX(HEADER_OVERFLOW, "too many header bytes seen; overflow detected") \
+ CROW_XX(CLOSED_CONNECTION, "data received after completed connection: close message") \
+ CROW_XX(INVALID_VERSION, "invalid HTTP version") \
+ CROW_XX(INVALID_STATUS, "invalid HTTP status code") \
+ CROW_XX(INVALID_METHOD, "invalid HTTP method") \
+ CROW_XX(INVALID_URL, "invalid URL") \
+ CROW_XX(INVALID_HOST, "invalid host") \
+ CROW_XX(INVALID_PORT, "invalid port") \
+ CROW_XX(INVALID_PATH, "invalid path") \
+ CROW_XX(INVALID_QUERY_STRING, "invalid query string") \
+ CROW_XX(INVALID_FRAGMENT, "invalid fragment") \
+ CROW_XX(LF_EXPECTED, "LF character expected") \
+ CROW_XX(INVALID_HEADER_TOKEN, "invalid character in header") \
+ CROW_XX(INVALID_CONTENT_LENGTH, "invalid character in content-length header") \
+ CROW_XX(UNEXPECTED_CONTENT_LENGTH, "unexpected content-length header") \
+ CROW_XX(INVALID_CHUNK_SIZE, "invalid character in chunk size header") \
+ CROW_XX(INVALID_CONSTANT, "invalid constant string") \
+ CROW_XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state") \
+ CROW_XX(STRICT, "strict mode assertion failed") \
+ CROW_XX(UNKNOWN, "an unknown error occurred") \
+ CROW_XX(INVALID_TRANSFER_ENCODING, "request has invalid transfer-encoding") \
+
+
+/* Define CHPE_* values for each errno value above */
+#define CROW_HTTP_ERRNO_GEN(n, s) CHPE_##n,
+enum http_errno {
+ CROW_HTTP_ERRNO_MAP(CROW_HTTP_ERRNO_GEN)
+};
+#undef CROW_HTTP_ERRNO_GEN
+
+
+/* Get an http_errno value from an http_parser */
+#define CROW_HTTP_PARSER_ERRNO(p) ((enum http_errno)(p)->http_errno)
+
+
+ struct http_parser
+ {
+ /** PRIVATE **/
+ unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */
+ unsigned int state : 8; /* enum state from http_parser.c */
+ unsigned int header_state : 7; /* enum header_state from http_parser.c */
+ unsigned int index : 5; /* index into current matcher */
+ unsigned int uses_transfer_encoding : 1; /* Transfer-Encoding header is present */
+ unsigned int allow_chunked_length : 1; /* Allow headers with both `Content-Length` and `Transfer-Encoding: chunked` set */
+ unsigned int lenient_http_headers : 1;
+
+ uint32_t nread; /* # bytes read in various scenarios */
+ uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one) if no Content-Length header. */
+ unsigned long qs_point;
+
+ /** READ-ONLY **/
+ unsigned char http_major;
+ unsigned char http_minor;
+ unsigned int method : 8; /* requests only */
+ unsigned int http_errno : 7;
+
+ /* 1 = Upgrade header was present and the parser has exited because of that.
+ * 0 = No upgrade header present.
+ * Should be checked when http_parser_execute() returns in addition to
+ * error checking.
+ */
+ unsigned int upgrade : 1;
+
+ /** PUBLIC **/
+ void* data; /* A pointer to get hook to the "connection" or "socket" object */
+ };
+
+
+ struct http_parser_settings
+ {
+ http_cb on_message_begin;
+ http_cb on_method;
+ http_data_cb on_url;
+ http_data_cb on_header_field;
+ http_data_cb on_header_value;
+ http_cb on_headers_complete;
+ http_data_cb on_body;
+ http_cb on_message_complete;
+ };
+
+
+
+// SOURCE (.c) CODE
+static uint32_t max_header_size = CROW_HTTP_MAX_HEADER_SIZE;
+
+#ifndef CROW_ULLONG_MAX
+# define CROW_ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
+#endif
+
+#ifndef CROW_MIN
+# define CROW_MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef CROW_ARRAY_SIZE
+# define CROW_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#ifndef CROW_BIT_AT
+# define CROW_BIT_AT(a, i) \
+ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
+ (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#define CROW_SET_ERRNO(e) \
+do { \
+ parser->nread = nread; \
+ parser->http_errno = (e); \
+} while(0)
+
+/* Run the notify callback FOR, returning ER if it fails */
+#define CROW_CALLBACK_NOTIFY_(FOR, ER) \
+do { \
+ assert(CROW_HTTP_PARSER_ERRNO(parser) == CHPE_OK); \
+ \
+ if (CROW_LIKELY(settings->on_##FOR)) { \
+ if (CROW_UNLIKELY(0 != settings->on_##FOR(parser))) { \
+ CROW_SET_ERRNO(CHPE_CB_##FOR); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (CROW_UNLIKELY(CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK)) { \
+ return (ER); \
+ } \
+ } \
+} while (0)
+
+/* Run the notify callback FOR and consume the current byte */
+#define CROW_CALLBACK_NOTIFY(FOR) CROW_CALLBACK_NOTIFY_(FOR, p - data + 1)
+
+/* Run the notify callback FOR and don't consume the current byte */
+#define CROW_CALLBACK_NOTIFY_NOADVANCE(FOR) CROW_CALLBACK_NOTIFY_(FOR, p - data)
+
+/* Run data callback FOR with LEN bytes, returning ER if it fails */
+#define CROW_CALLBACK_DATA_(FOR, LEN, ER) \
+do { \
+ assert(CROW_HTTP_PARSER_ERRNO(parser) == CHPE_OK); \
+ \
+ if (FOR##_mark) { \
+ if (CROW_LIKELY(settings->on_##FOR)) { \
+ if (CROW_UNLIKELY(0 != \
+ settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \
+ CROW_SET_ERRNO(CHPE_CB_##FOR); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (CROW_UNLIKELY(CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK)) {\
+ return (ER); \
+ } \
+ } \
+ FOR##_mark = NULL; \
+ } \
+} while (0)
+
+/* Run the data callback FOR and consume the current byte */
+#define CROW_CALLBACK_DATA(FOR) \
+ CROW_CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
+
+/* Run the data callback FOR and don't consume the current byte */
+#define CROW_CALLBACK_DATA_NOADVANCE(FOR) \
+ CROW_CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
+
+/* Set the mark FOR; non-destructive if mark is already set */
+#define CROW_MARK(FOR) \
+do { \
+ if (!FOR##_mark) { \
+ FOR##_mark = p; \
+ } \
+} while (0)
+
+/* Don't allow the total size of the HTTP headers (including the status
+ * line) to exceed max_header_size. This check is here to protect
+ * embedders against denial-of-service attacks where the attacker feeds
+ * us a never-ending header that the embedder keeps buffering.
+ *
+ * This check is arguably the responsibility of embedders but we're doing
+ * it on the embedder's behalf because most won't bother and this way we
+ * make the web a little safer. max_header_size is still far bigger
+ * than any reasonable request or response so this should never affect
+ * day-to-day operation.
+ */
+#define CROW_COUNT_HEADER_SIZE(V) \
+do { \
+ nread += (uint32_t)(V); \
+ if (CROW_UNLIKELY(nread > max_header_size)) { \
+ CROW_SET_ERRNO(CHPE_HEADER_OVERFLOW); \
+ goto error; \
+ } \
+} while (0)
+#define CROW_REEXECUTE() \
+ goto reexecute; \
+
+#define CROW_PROXY_CONNECTION "proxy-connection"
+#define CROW_CONNECTION "connection"
+#define CROW_CONTENT_LENGTH "content-length"
+#define CROW_TRANSFER_ENCODING "transfer-encoding"
+#define CROW_UPGRADE "upgrade"
+#define CROW_CHUNKED "chunked"
+#define CROW_KEEP_ALIVE "keep-alive"
+#define CROW_CLOSE "close"
+
+
+
+ enum state
+ {
+ s_dead = 1 /* important that this is > 0 */
+
+ ,
+ s_start_req
+
+ ,
+ s_req_method,
+ s_req_spaces_before_url,
+ s_req_schema,
+ s_req_schema_slash,
+ s_req_schema_slash_slash,
+ s_req_server_start,
+ s_req_server, // }
+ s_req_server_with_at, // |
+ s_req_path, // | The parser recognizes how to switch between these states,
+ s_req_query_string_start, // | however it doesn't process them any differently.
+ s_req_query_string, // }
+ s_req_http_start,
+ s_req_http_H,
+ s_req_http_HT,
+ s_req_http_HTT,
+ s_req_http_HTTP,
+ s_req_http_I,
+ s_req_http_IC,
+ s_req_http_major,
+ s_req_http_dot,
+ s_req_http_minor,
+ s_req_http_end,
+ s_req_line_almost_done
+
+ ,
+ s_header_field_start,
+ s_header_field,
+ s_header_value_discard_ws,
+ s_header_value_discard_ws_almost_done,
+ s_header_value_discard_lws,
+ s_header_value_start,
+ s_header_value,
+ s_header_value_lws
+
+ ,
+ s_header_almost_done
+
+ ,
+ s_chunk_size_start,
+ s_chunk_size,
+ s_chunk_parameters,
+ s_chunk_size_almost_done
+
+ ,
+ s_headers_almost_done,
+ s_headers_done
+
+ /* Important: 's_headers_done' must be the last 'header' state. All
+ * states beyond this must be 'body' states. It is used for overflow
+ * checking. See the CROW_PARSING_HEADER() macro.
+ */
+
+ ,
+ s_chunk_data,
+ s_chunk_data_almost_done,
+ s_chunk_data_done
+
+ ,
+ s_body_identity,
+ s_body_identity_eof
+
+ ,
+ s_message_done
+ };
+
+
+#define CROW_PARSING_HEADER(state) (state <= s_headers_done)
+
+
+enum header_states
+ { h_general = 0
+ , h_C
+ , h_CO
+ , h_CON
+
+ , h_matching_connection
+ , h_matching_proxy_connection
+ , h_matching_content_length
+ , h_matching_transfer_encoding
+ , h_matching_upgrade
+
+ , h_connection
+ , h_content_length
+ , h_content_length_num
+ , h_content_length_ws
+ , h_transfer_encoding
+ , h_upgrade
+
+ , h_matching_transfer_encoding_token_start
+ , h_matching_transfer_encoding_chunked
+ , h_matching_transfer_encoding_token
+
+ , h_matching_connection_keep_alive
+ , h_matching_connection_close
+
+ , h_transfer_encoding_chunked
+ , h_connection_keep_alive
+ , h_connection_close
+ };
+
+enum http_host_state
+ {
+ s_http_host_dead = 1
+ , s_http_userinfo_start
+ , s_http_userinfo
+ , s_http_host_start
+ , s_http_host_v6_start
+ , s_http_host
+ , s_http_host_v6
+ , s_http_host_v6_end
+ , s_http_host_v6_zone_start
+ , s_http_host_v6_zone
+ , s_http_host_port_start
+ , s_http_host_port
+};
+
+/* Macros for character classes; depends on strict-mode */
+#define CROW_LOWER(c) (unsigned char)(c | 0x20)
+#define CROW_IS_ALPHA(c) (CROW_LOWER(c) >= 'a' && CROW_LOWER(c) <= 'z')
+#define CROW_IS_NUM(c) ((c) >= '0' && (c) <= '9')
+#define CROW_IS_ALPHANUM(c) (CROW_IS_ALPHA(c) || CROW_IS_NUM(c))
+//#define CROW_IS_HEX(c) (CROW_IS_NUM(c) || (CROW_LOWER(c) >= 'a' && CROW_LOWER(c) <= 'f'))
+#define CROW_IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
+ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+ (c) == ')')
+#define CROW_IS_USERINFO_CHAR(c) (CROW_IS_ALPHANUM(c) || CROW_IS_MARK(c) || (c) == '%' || \
+ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+ (c) == '$' || (c) == ',')
+
+#define CROW_TOKEN(c) (tokens[(unsigned char)c])
+#define CROW_IS_URL_CHAR(c) (CROW_BIT_AT(normal_url_char, (unsigned char)c))
+//#define CROW_IS_HOST_CHAR(c) (CROW_IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
+
+ /**
+ * Verify that a char is a valid visible (printable) US-ASCII
+ * character or %x80-FF
+ **/
+#define CROW_IS_HEADER_CHAR(ch) \
+ (ch == cr || ch == lf || ch == 9 || ((unsigned char)ch > 31 && ch != 127))
+
+#define CROW_start_state s_start_req
+
+# define CROW_STRICT_CHECK(cond) \
+do { \
+ if (cond) { \
+ CROW_SET_ERRNO(CHPE_STRICT); \
+ goto error; \
+ } \
+} while (0)
+#define CROW_NEW_MESSAGE() (CROW_start_state)
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+inline enum state
+parse_url_char(enum state s, const char ch, http_parser *parser, const char* url_mark, const char* p)
+{
+# define CROW_T(v) 0
+
+
+static const uint8_t normal_url_char[32] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0 |CROW_T(2)| 0 | 0 |CROW_T(16)| 0 | 0 | 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 80 P 81 Q 82 R 83 S 84 CROW_T 85 U 86 V 87 W */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
+
+#undef CROW_T
+
+ if (ch == ' ' || ch == '\r' || ch == '\n') {
+ return s_dead;
+ }
+ if (ch == '\t' || ch == '\f') {
+ return s_dead;
+ }
+
+ switch (s) {
+ case s_req_spaces_before_url:
+ /* Proxied requests are followed by scheme of an absolute URI (alpha).
+ * All methods except CONNECT are followed by '/' or '*'.
+ */
+
+ if (ch == '/' || ch == '*') {
+ return s_req_path;
+ }
+
+ if (CROW_IS_ALPHA(ch)) {
+ return s_req_schema;
+ }
+
+ break;
+
+ case s_req_schema:
+ if (CROW_IS_ALPHA(ch)) {
+ return s;
+ }
+
+ if (ch == ':') {
+ return s_req_schema_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash:
+ if (ch == '/') {
+ return s_req_schema_slash_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash_slash:
+ if (ch == '/') {
+ return s_req_server_start;
+ }
+
+ break;
+
+ case s_req_server_with_at:
+ if (ch == '@') {
+ return s_dead;
+ }
+
+ /* fall through */
+ case s_req_server_start:
+ case s_req_server:
+ if (ch == '/') {
+ return s_req_path;
+ }
+
+ if (ch == '?') {
+ parser->qs_point = p - url_mark;
+ return s_req_query_string_start;
+ }
+
+ if (ch == '@') {
+ return s_req_server_with_at;
+ }
+
+ if (CROW_IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+ return s_req_server;
+ }
+
+ break;
+
+ case s_req_path:
+ if (CROW_IS_URL_CHAR(ch)) {
+ return s;
+ }
+ else if (ch == '?')
+ {
+ parser->qs_point = p - url_mark;
+ return s_req_query_string_start;
+ }
+
+ break;
+
+ case s_req_query_string_start:
+ case s_req_query_string:
+ if (CROW_IS_URL_CHAR(ch)) {
+ return s_req_query_string;
+ }
+ else if (ch == '?')
+ {
+ return s_req_query_string;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* We should never fall out of the switch above unless there's an error */
+ return s_dead;
+}
+
+inline size_t http_parser_execute (http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len)
+{
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ * token = 1*
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0, '!', 0, '#', '$', '%', '&', '\'',
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 0, 0, '*', '+', 0, '-', '.', 0,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ '0', '1', '2', '3', '4', '5', '6', '7',
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ '8', '9', 0, 0, 0, 0, 0, 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 'x', 'y', 'z', 0, 0, 0, '^', '_',
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 'x', 'y', 'z', 0, '|', 0, '~', 0 };
+
+
+static const int8_t unhex[256] =
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ };
+
+
+
+ char c, ch;
+ int8_t unhex_val;
+ const char *p = data;
+ const char *header_field_mark = 0;
+ const char *header_value_mark = 0;
+ const char *url_mark = 0;
+ const char *url_start_mark = 0;
+ const char *body_mark = 0;
+ const unsigned int lenient = parser->lenient_http_headers;
+ const unsigned int allow_chunked_length = parser->allow_chunked_length;
+
+ uint32_t nread = parser->nread;
+
+ /* We're in an error state. Don't bother doing anything. */
+ if (CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK) {
+ return 0;
+ }
+
+ if (len == 0) {
+ switch (parser->state) {
+ case s_body_identity_eof:
+ /* Use of CROW_CALLBACK_NOTIFY() here would erroneously return 1 byte read if we got paused. */
+ CROW_CALLBACK_NOTIFY_NOADVANCE(message_complete);
+ return 0;
+
+ case s_dead:
+ case s_start_req:
+ return 0;
+
+ default:
+ CROW_SET_ERRNO(CHPE_INVALID_EOF_STATE);
+ return 1;
+ }
+ }
+
+
+ if (parser->state == s_header_field)
+ header_field_mark = data;
+ if (parser->state == s_header_value)
+ header_value_mark = data;
+ switch (parser->state) {
+ case s_req_path:
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ url_mark = data;
+ break;
+ default:
+ break;
+ }
+
+ for (p=data; p != data + len; p++) {
+ ch = *p;
+
+ if (CROW_PARSING_HEADER(parser->state))
+ CROW_COUNT_HEADER_SIZE(1);
+
+reexecute:
+ switch (parser->state) {
+
+ case s_dead:
+ /* this state is used after a 'Connection: close' message
+ * the parser will error out if it reads another message
+ */
+ if (CROW_LIKELY(ch == cr || ch == lf))
+ break;
+
+ CROW_SET_ERRNO(CHPE_CLOSED_CONNECTION);
+ goto error;
+
+ case s_start_req:
+ {
+ if (ch == cr || ch == lf)
+ break;
+ parser->flags = 0;
+ parser->uses_transfer_encoding = 0;
+ parser->content_length = CROW_ULLONG_MAX;
+
+ if (CROW_UNLIKELY(!CROW_IS_ALPHA(ch))) {
+ CROW_SET_ERRNO(CHPE_INVALID_METHOD);
+ goto error;
+ }
+
+ parser->method = 0;
+ parser->index = 1;
+ switch (ch) {
+ case 'A': parser->method = (unsigned)HTTPMethod::Acl; break;
+ case 'B': parser->method = (unsigned)HTTPMethod::Bind; break;
+ case 'C': parser->method = (unsigned)HTTPMethod::Connect; /* or COPY, CHECKOUT */ break;
+ case 'D': parser->method = (unsigned)HTTPMethod::Delete; break;
+ case 'G': parser->method = (unsigned)HTTPMethod::Get; break;
+ case 'H': parser->method = (unsigned)HTTPMethod::Head; break;
+ case 'L': parser->method = (unsigned)HTTPMethod::Lock; /* or LINK */ break;
+ case 'M': parser->method = (unsigned)HTTPMethod::MkCol; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;
+ case 'N': parser->method = (unsigned)HTTPMethod::Notify; break;
+ case 'O': parser->method = (unsigned)HTTPMethod::Options; break;
+ case 'P': parser->method = (unsigned)HTTPMethod::Post; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break;
+ case 'R': parser->method = (unsigned)HTTPMethod::Report; /* or REBIND */ break;
+ case 'S': parser->method = (unsigned)HTTPMethod::Subscribe; /* or SEARCH, SOURCE */ break;
+ case 'T': parser->method = (unsigned)HTTPMethod::Trace; break;
+ case 'U': parser->method = (unsigned)HTTPMethod::Unlock; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
+ default:
+ CROW_SET_ERRNO(CHPE_INVALID_METHOD);
+ goto error;
+ }
+ parser->state = s_req_method;
+
+ CROW_CALLBACK_NOTIFY(message_begin);
+
+ break;
+ }
+
+ case s_req_method:
+ {
+ const char *matcher;
+ if (CROW_UNLIKELY(ch == '\0')) {
+ CROW_SET_ERRNO(CHPE_INVALID_METHOD);
+ goto error;
+ }
+
+ matcher = method_strings[parser->method];
+ if (ch == ' ' && matcher[parser->index] == '\0') {
+ parser->state = s_req_spaces_before_url;
+ } else if (ch == matcher[parser->index]) {
+ ; /* nada */
+ } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') {
+
+ switch (parser->method << 16 | parser->index << 8 | ch) {
+#define CROW_XX(meth, pos, ch, new_meth) \
+ case ((unsigned)HTTPMethod::meth << 16 | pos << 8 | ch): \
+ parser->method = (unsigned)HTTPMethod::new_meth; break;
+
+ CROW_XX(Post, 1, 'U', Put)
+ CROW_XX(Post, 1, 'A', Patch)
+ CROW_XX(Post, 1, 'R', Propfind)
+ CROW_XX(Put, 2, 'R', Purge)
+ CROW_XX(Connect, 1, 'H', Checkout)
+ CROW_XX(Connect, 2, 'P', Copy)
+ CROW_XX(MkCol, 1, 'O', Move)
+ CROW_XX(MkCol, 1, 'E', Merge)
+ CROW_XX(MkCol, 1, '-', MSearch)
+ CROW_XX(MkCol, 2, 'A', MkActivity)
+ CROW_XX(MkCol, 3, 'A', MkCalendar)
+ CROW_XX(Subscribe, 1, 'E', Search)
+ CROW_XX(Subscribe, 1, 'O', Source)
+ CROW_XX(Report, 2, 'B', Rebind)
+ CROW_XX(Propfind, 4, 'P', Proppatch)
+ CROW_XX(Lock, 1, 'I', Link)
+ CROW_XX(Unlock, 2, 'S', Unsubscribe)
+ CROW_XX(Unlock, 2, 'B', Unbind)
+ CROW_XX(Unlock, 3, 'I', Unlink)
+#undef CROW_XX
+ default:
+ CROW_SET_ERRNO(CHPE_INVALID_METHOD);
+ goto error;
+ }
+ } else {
+ CROW_SET_ERRNO(CHPE_INVALID_METHOD);
+ goto error;
+ }
+
+ CROW_CALLBACK_NOTIFY_NOADVANCE(method);
+
+ ++parser->index;
+ break;
+ }
+
+ case s_req_spaces_before_url:
+ {
+ if (ch == ' ') break;
+
+ CROW_MARK(url);
+ CROW_MARK(url_start);
+ if (parser->method == (unsigned)HTTPMethod::Connect) {
+ parser->state = s_req_server_start;
+ }
+
+ parser->state = parse_url_char(static_cast(parser->state), ch, parser, url_start_mark, p);
+ if (CROW_UNLIKELY(parser->state == s_dead)) {
+ CROW_SET_ERRNO(CHPE_INVALID_URL);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ {
+ switch (ch) {
+ /* No whitespace allowed here */
+ case ' ':
+ case cr:
+ case lf:
+ CROW_SET_ERRNO(CHPE_INVALID_URL);
+ goto error;
+ default:
+ parser->state = parse_url_char(static_cast(parser->state), ch, parser, url_start_mark, p);
+ if (CROW_UNLIKELY(parser->state == s_dead)) {
+ CROW_SET_ERRNO(CHPE_INVALID_URL);
+ goto error;
+ }
+ }
+
+ break;
+ }
+
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_path:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ {
+ switch (ch) {
+ case ' ':
+ parser->state = s_req_http_start;
+ CROW_CALLBACK_DATA(url);
+ break;
+ case cr: // No space after URL means no HTTP version. Which means the request is using HTTP/0.9
+ case lf:
+ if (CROW_UNLIKELY(parser->method != (unsigned)HTTPMethod::Get)) // HTTP/0.9 doesn't define any method other than GET
+ {
+ parser->state = s_dead;
+ CROW_SET_ERRNO(CHPE_INVALID_VERSION);
+ goto error;
+ }
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ parser->state = (ch == cr) ?
+ s_req_line_almost_done :
+ s_header_field_start;
+ CROW_CALLBACK_DATA(url);
+ break;
+ default:
+ parser->state = parse_url_char(static_cast(parser->state), ch, parser, url_start_mark, p);
+ if (CROW_UNLIKELY(parser->state == s_dead)) {
+ CROW_SET_ERRNO(CHPE_INVALID_URL);
+ goto error;
+ }
+ }
+ break;
+ }
+
+ case s_req_http_start:
+ switch (ch) {
+ case ' ':
+ break;
+ case 'H':
+ parser->state = s_req_http_H;
+ break;
+ case 'I':
+ if (parser->method == (unsigned)HTTPMethod::Source) {
+ parser->state = s_req_http_I;
+ break;
+ }
+ /* fall through */
+ default:
+ CROW_SET_ERRNO(CHPE_INVALID_CONSTANT);
+ goto error;
+ }
+ break;
+
+ case s_req_http_H:
+ CROW_STRICT_CHECK(ch != 'T');
+ parser->state = s_req_http_HT;
+ break;
+
+ case s_req_http_HT:
+ CROW_STRICT_CHECK(ch != 'T');
+ parser->state = s_req_http_HTT;
+ break;
+
+ case s_req_http_HTT:
+ CROW_STRICT_CHECK(ch != 'P');
+ parser->state = s_req_http_HTTP;
+ break;
+
+ case s_req_http_I:
+ CROW_STRICT_CHECK(ch != 'C');
+ parser->state = s_req_http_IC;
+ break;
+
+ case s_req_http_IC:
+ CROW_STRICT_CHECK(ch != 'E');
+ parser->state = s_req_http_HTTP; /* Treat "ICE" as "HTTP". */
+ break;
+
+ case s_req_http_HTTP:
+ CROW_STRICT_CHECK(ch != '/');
+ parser->state = s_req_http_major;
+ break;
+
+ /* dot */
+ case s_req_http_major:
+ if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) {
+ CROW_SET_ERRNO(CHPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major = ch - '0';
+ parser->state = s_req_http_dot;
+ break;
+
+ case s_req_http_dot:
+ {
+ if (CROW_UNLIKELY(ch != '.')) {
+ CROW_SET_ERRNO(CHPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->state = s_req_http_minor;
+ break;
+ }
+
+ /* minor HTTP version */
+ case s_req_http_minor:
+ if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) {
+ CROW_SET_ERRNO(CHPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor = ch - '0';
+ parser->state = s_req_http_end;
+ break;
+
+ /* end of request line */
+ case s_req_http_end:
+ {
+ if (ch == cr) {
+ parser->state = s_req_line_almost_done;
+ break;
+ }
+
+ if (ch == lf) {
+ parser->state = s_header_field_start;
+ break;
+ }
+
+ CROW_SET_ERRNO(CHPE_INVALID_VERSION);
+ goto error;
+ break;
+ }
+
+ /* end of request line */
+ case s_req_line_almost_done:
+ {
+ if (CROW_UNLIKELY(ch != lf)) {
+ CROW_SET_ERRNO(CHPE_LF_EXPECTED);
+ goto error;
+ }
+
+ parser->state = s_header_field_start;
+ break;
+ }
+
+ case s_header_field_start:
+ {
+ if (ch == cr) {
+ parser->state = s_headers_almost_done;
+ break;
+ }
+
+ if (ch == lf) {
+ /* they might be just sending \n instead of \r\n so this would be
+ * the second \n to denote the end of headers*/
+ parser->state = s_headers_almost_done;
+ CROW_REEXECUTE();
+ }
+
+ c = CROW_TOKEN(ch);
+
+ if (CROW_UNLIKELY(!c)) {
+ CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ CROW_MARK(header_field);
+
+ parser->index = 0;
+ parser->state = s_header_field;
+
+ switch (c) {
+ case 'c':
+ parser->header_state = h_C;
+ break;
+
+ case 'p':
+ parser->header_state = h_matching_proxy_connection;
+ break;
+
+ case 't':
+ parser->header_state = h_matching_transfer_encoding;
+ break;
+
+ case 'u':
+ parser->header_state = h_matching_upgrade;
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_field:
+ {
+ const char* start = p;
+ for (; p != data + len; p++) {
+ ch = *p;
+ c = CROW_TOKEN(ch);
+
+ if (!c)
+ break;
+
+ switch (parser->header_state) {
+ case h_general: {
+ size_t left = data + len - p;
+ const char* pe = p + CROW_MIN(left, max_header_size);
+ while (p+1 < pe && CROW_TOKEN(p[1])) {
+ p++;
+ }
+ break;
+ }
+
+ case h_C:
+ parser->index++;
+ parser->header_state = (c == 'o' ? h_CO : h_general);
+ break;
+
+ case h_CO:
+ parser->index++;
+ parser->header_state = (c == 'n' ? h_CON : h_general);
+ break;
+
+ case h_CON:
+ parser->index++;
+ switch (c) {
+ case 'n':
+ parser->header_state = h_matching_connection;
+ break;
+ case 't':
+ parser->header_state = h_matching_content_length;
+ break;
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+
+ /* connection */
+
+ case h_matching_connection:
+ parser->index++;
+ if (parser->index > sizeof(CROW_CONNECTION)-1 || c != CROW_CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CROW_CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* proxy-connection */
+
+ case h_matching_proxy_connection:
+ parser->index++;
+ if (parser->index > sizeof(CROW_PROXY_CONNECTION)-1 || c != CROW_PROXY_CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CROW_PROXY_CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* content-length */
+
+ case h_matching_content_length:
+ parser->index++;
+ if (parser->index > sizeof(CROW_CONTENT_LENGTH)-1 || c != CROW_CONTENT_LENGTH[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CROW_CONTENT_LENGTH)-2) {
+ parser->header_state = h_content_length;
+ }
+ break;
+
+ /* transfer-encoding */
+
+ case h_matching_transfer_encoding:
+ parser->index++;
+ if (parser->index > sizeof(CROW_TRANSFER_ENCODING)-1 || c != CROW_TRANSFER_ENCODING[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CROW_TRANSFER_ENCODING)-2) {
+ parser->header_state = h_transfer_encoding;
+ parser->uses_transfer_encoding = 1;
+ }
+ break;
+
+ /* upgrade */
+
+ case h_matching_upgrade:
+ parser->index++;
+ if (parser->index > sizeof(CROW_UPGRADE)-1 || c != CROW_UPGRADE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CROW_UPGRADE)-2) {
+ parser->header_state = h_upgrade;
+ }
+ break;
+
+ case h_connection:
+ case h_content_length:
+ case h_transfer_encoding:
+ case h_upgrade:
+ if (ch != ' ') parser->header_state = h_general;
+ break;
+
+ default:
+ assert(0 && "Unknown header_state");
+ break;
+ }
+ }
+
+ if (p == data + len) {
+ --p;
+ CROW_COUNT_HEADER_SIZE(p - start);
+ break;
+ }
+
+ CROW_COUNT_HEADER_SIZE(p - start);
+
+ if (ch == ':') {
+ parser->state = s_header_value_discard_ws;
+ CROW_CALLBACK_DATA(header_field);
+ break;
+ }
+/* RFC-7230 Sec 3.2.4 expressly forbids line-folding in header field-names.
+ if (ch == cr) {
+ parser->state = s_header_almost_done;
+ CROW_CALLBACK_DATA(header_field);
+ break;
+ }
+
+ if (ch == lf) {
+ parser->state = s_header_field_start;
+ CROW_CALLBACK_DATA(header_field);
+ break;
+ }
+*/
+ CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ case s_header_value_discard_ws:
+ if (ch == ' ' || ch == '\t') break;
+
+ if (ch == cr) {
+ parser->state = s_header_value_discard_ws_almost_done;
+ break;
+ }
+
+ if (ch == lf) {
+ parser->state = s_header_value_discard_lws;
+ break;
+ }
+
+ /* fall through */
+
+ case s_header_value_start:
+ {
+ CROW_MARK(header_value);
+
+ parser->state = s_header_value;
+ parser->index = 0;
+
+ c = CROW_LOWER(ch);
+
+ switch (parser->header_state) {
+ case h_upgrade:
+ // Crow does not support HTTP/2 at the moment.
+ // According to the RFC https://datatracker.ietf.org/doc/html/rfc7540#section-3.2
+ // "A server that does not support HTTP/2 can respond to the request as though the Upgrade header field were absent"
+ // => `F_UPGRADE` is not set if the header starts by "h2".
+ // This prevents the parser from skipping the request body.
+ if (ch != 'h' || p+1 == (data + len) || *(p+1) != '2') {
+ parser->flags |= F_UPGRADE;
+ }
+ parser->header_state = h_general;
+ break;
+
+ case h_transfer_encoding:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if ('c' == c) {
+ parser->header_state = h_matching_transfer_encoding_chunked;
+ } else {
+ parser->header_state = h_matching_transfer_encoding_token;
+ }
+ break;
+
+ /* Multi-value `Transfer-Encoding` header */
+ case h_matching_transfer_encoding_token_start:
+ break;
+
+ case h_content_length:
+ if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) {
+ CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ if (parser->flags & F_CONTENTLENGTH) {
+ CROW_SET_ERRNO(CHPE_UNEXPECTED_CONTENT_LENGTH);
+ goto error;
+ }
+ parser->flags |= F_CONTENTLENGTH;
+ parser->content_length = ch - '0';
+ parser->header_state = h_content_length_num;
+ break;
+
+ /* when obsolete line folding is encountered for content length
+ * continue to the s_header_value state */
+ case h_content_length_ws:
+ break;
+
+ case h_connection:
+ /* looking for 'Connection: keep-alive' */
+ if (c == 'k') {
+ parser->header_state = h_matching_connection_keep_alive;
+ /* looking for 'Connection: close' */
+ } else if (c == 'c') {
+ parser->header_state = h_matching_connection_close;
+ } else if (c == ' ' || c == '\t') {
+ /* Skip lws */
+ } else {
+ parser->header_state = h_general;
+ }
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_value:
+ {
+ const char* start = p;
+ enum header_states h_state = static_cast(parser->header_state);
+ for (; p != data + len; p++) {
+ ch = *p;
+
+ if (ch == cr) {
+ parser->state = s_header_almost_done;
+ parser->header_state = h_state;
+ CROW_CALLBACK_DATA(header_value);
+ break;
+ }
+
+ if (ch == lf) {
+ parser->state = s_header_almost_done;
+ CROW_COUNT_HEADER_SIZE(p - start);
+ parser->header_state = h_state;
+ CROW_CALLBACK_DATA_NOADVANCE(header_value);
+ CROW_REEXECUTE();
+ }
+
+ if (!lenient && !CROW_IS_HEADER_CHAR(ch)) {
+ CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ c = CROW_LOWER(ch);
+
+ switch (h_state) {
+ case h_general:
+ {
+ size_t left = data + len - p;
+ const char* pe = p + CROW_MIN(left, max_header_size);
+
+ for (; p != pe; p++) {
+ ch = *p;
+ if (ch == cr || ch == lf) {
+ --p;
+ break;
+ }
+ if (!lenient && !CROW_IS_HEADER_CHAR(ch)) {
+ CROW_SET_ERRNO(CHPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+ }
+ if (p == data + len)
+ --p;
+ break;
+ }
+
+ case h_connection:
+ case h_transfer_encoding:
+ assert(0 && "Shouldn't get here.");
+ break;
+
+ case h_content_length:
+ if (ch == ' ') break;
+ h_state = h_content_length_num;
+ /* fall through */
+
+ case h_content_length_num:
+ {
+ uint64_t t;
+
+ if (ch == ' ') {
+ h_state = h_content_length_ws;
+ break;
+ }
+
+ if (CROW_UNLIKELY(!CROW_IS_NUM(ch))) {
+ CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH);
+ parser->header_state = h_state;
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 10;
+ t += ch - '0';
+
+ /* Overflow? Test against a conservative limit for simplicity. */
+ if (CROW_UNLIKELY((CROW_ULLONG_MAX - 10) / 10 < parser->content_length)) {
+ CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH);
+ parser->header_state = h_state;
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ case h_content_length_ws:
+ if (ch == ' ') break;
+ CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH);
+ parser->header_state = h_state;
+ goto error;
+
+ /* Transfer-Encoding: chunked */
+ case h_matching_transfer_encoding_token_start:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if ('c' == c) {
+ h_state = h_matching_transfer_encoding_chunked;
+ } else if (CROW_TOKEN(c)) {
+ /* TODO(indutny): similar code below does this, but why?
+ * At the very least it seems to be inconsistent given that
+ * h_matching_transfer_encoding_token does not check for
+ * `STRICT_TOKEN`
+ */
+ h_state = h_matching_transfer_encoding_token;
+ } else if (c == ' ' || c == '\t') {
+ /* Skip lws */
+ } else {
+ h_state = h_general;
+ }
+ break;
+
+ case h_matching_transfer_encoding_chunked:
+ parser->index++;
+ if (parser->index > sizeof(CROW_CHUNKED)-1 || c != CROW_CHUNKED[parser->index]) {
+ h_state = h_matching_transfer_encoding_token;
+ } else if (parser->index == sizeof(CROW_CHUNKED)-2) {
+ h_state = h_transfer_encoding_chunked;
+ }
+ break;
+
+ case h_matching_transfer_encoding_token:
+ if (ch == ',') {
+ h_state = h_matching_transfer_encoding_token_start;
+ parser->index = 0;
+ }
+ break;
+
+ /* looking for 'Connection: keep-alive' */
+ case h_matching_connection_keep_alive:
+ parser->index++;
+ if (parser->index > sizeof(CROW_KEEP_ALIVE)-1 || c != CROW_KEEP_ALIVE[parser->index]) {
+ h_state = h_general;
+ } else if (parser->index == sizeof(CROW_KEEP_ALIVE)-2) {
+ h_state = h_connection_keep_alive;
+ }
+ break;
+
+ /* looking for 'Connection: close' */
+ case h_matching_connection_close:
+ parser->index++;
+ if (parser->index > sizeof(CROW_CLOSE)-1 || c != CROW_CLOSE[parser->index]) {
+ h_state = h_general;
+ } else if (parser->index == sizeof(CROW_CLOSE)-2) {
+ h_state = h_connection_close;
+ }
+ break;
+
+ // Edited from original (because of commits that werent included)
+ case h_transfer_encoding_chunked:
+ if (ch != ' ') h_state = h_matching_transfer_encoding_token;
+ break;
+ case h_connection_keep_alive:
+ case h_connection_close:
+ if (ch != ' ') h_state = h_general;
+ break;
+
+ default:
+ parser->state = s_header_value;
+ h_state = h_general;
+ break;
+ }
+ }
+ parser->header_state = h_state;
+
+
+ if (p == data + len)
+ --p;
+
+ CROW_COUNT_HEADER_SIZE(p - start);
+ break;
+ }
+
+ case s_header_almost_done:
+ {
+ if (CROW_UNLIKELY(ch != lf)) {
+ CROW_SET_ERRNO(CHPE_LF_EXPECTED);
+ goto error;
+ }
+
+ parser->state = s_header_value_lws;
+ break;
+ }
+
+ case s_header_value_lws:
+ {
+ if (ch == ' ' || ch == '\t') {
+ if (parser->header_state == h_content_length_num) {
+ /* treat obsolete line folding as space */
+ parser->header_state = h_content_length_ws;
+ }
+ parser->state = s_header_value_start;
+ CROW_REEXECUTE();
+ }
+
+ /* finished the header */
+ switch (parser->header_state) {
+ case h_connection_keep_alive:
+ parser->flags |= F_CONNECTION_KEEP_ALIVE;
+ break;
+ case h_connection_close:
+ parser->flags |= F_CONNECTION_CLOSE;
+ break;
+ case h_transfer_encoding_chunked:
+ parser->flags |= F_CHUNKED;
+ break;
+ default:
+ break;
+ }
+
+ parser->state = s_header_field_start;
+ CROW_REEXECUTE();
+ }
+
+ case s_header_value_discard_ws_almost_done:
+ {
+ CROW_STRICT_CHECK(ch != lf);
+ parser->state = s_header_value_discard_lws;
+ break;
+ }
+
+ case s_header_value_discard_lws:
+ {
+ if (ch == ' ' || ch == '\t') {
+ parser->state = s_header_value_discard_ws;
+ break;
+ } else {
+ /* header value was empty */
+ CROW_MARK(header_value);
+ parser->state = s_header_field_start;
+ CROW_CALLBACK_DATA_NOADVANCE(header_value);
+ CROW_REEXECUTE();
+ }
+ }
+
+ case s_headers_almost_done:
+ {
+ CROW_STRICT_CHECK(ch != lf);
+
+ if (parser->flags & F_TRAILING) {
+ /* End of a chunked request */
+ CROW_CALLBACK_NOTIFY(message_complete);
+ break;
+ }
+
+ /* Cannot use transfer-encoding and a content-length header together
+ per the HTTP specification. (RFC 7230 Section 3.3.3) */
+ if ((parser->uses_transfer_encoding == 1) &&
+ (parser->flags & F_CONTENTLENGTH)) {
+ /* Allow it for lenient parsing as long as `Transfer-Encoding` is
+ * not `chunked` or allow_length_with_encoding is set
+ */
+ if (parser->flags & F_CHUNKED) {
+ if (!allow_chunked_length) {
+ CROW_SET_ERRNO(CHPE_UNEXPECTED_CONTENT_LENGTH);
+ goto error;
+ }
+ } else if (!lenient) {
+ CROW_SET_ERRNO(CHPE_UNEXPECTED_CONTENT_LENGTH);
+ goto error;
+ }
+ }
+
+ parser->state = s_headers_done;
+
+ /* Set this here so that on_headers_complete() callbacks can see it */
+ parser->upgrade =
+ (parser->flags & F_UPGRADE || parser->method == (unsigned)HTTPMethod::Connect);
+
+ /* Here we call the headers_complete callback. This is somewhat
+ * different than other callbacks because if the user returns 1, we
+ * will interpret that as saying that this message has no body. This
+ * is needed for the annoying case of recieving a response to a HEAD
+ * request.
+ *
+ * We'd like to use CROW_CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+ * we have to simulate it by handling a change in errno below.
+ */
+ if (settings->on_headers_complete) {
+ switch (settings->on_headers_complete(parser)) {
+ case 0:
+ break;
+
+ case 2:
+ parser->upgrade = 1;
+ //break;
+
+ /* fall through */
+ case 1:
+ parser->flags |= F_SKIPBODY;
+ break;
+
+ default:
+ CROW_SET_ERRNO(CHPE_CB_headers_complete);
+ parser->nread = nread;
+ return p - data; /* Error */
+ }
+ }
+
+ if (CROW_HTTP_PARSER_ERRNO(parser) != CHPE_OK) {
+ parser->nread = nread;
+ return p - data;
+ }
+
+ CROW_REEXECUTE();
+ }
+
+ case s_headers_done:
+ {
+ CROW_STRICT_CHECK(ch != lf);
+
+ parser->nread = 0;
+ nread = 0;
+
+ /* Exit, the rest of the connect is in a different protocol. */
+ if (parser->upgrade) {
+ CROW_CALLBACK_NOTIFY(message_complete);
+ parser->nread = nread;
+ return (p - data) + 1;
+ }
+
+ if (parser->flags & F_SKIPBODY) {
+ CROW_CALLBACK_NOTIFY(message_complete);
+ } else if (parser->flags & F_CHUNKED) {
+ /* chunked encoding - ignore Content-Length header,
+ * prepare for a chunk */
+ parser->state = s_chunk_size_start;
+ }
+ else if (parser->uses_transfer_encoding == 1)
+ {
+ if (!lenient)
+ {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field
+ * is present in a request and the chunked transfer coding is not
+ * the final encoding, the message body length cannot be determined
+ * reliably; the server MUST respond with the 400 (Bad Request)
+ * status code and then close the connection.
+ */
+ CROW_SET_ERRNO(CHPE_INVALID_TRANSFER_ENCODING);
+ parser->nread = nread;
+ return (p - data); /* Error */
+ }
+ else
+ {
+ /* RFC 7230 3.3.3 */
+
+ /* If a Transfer-Encoding header field is present in a response and
+ * the chunked transfer coding is not the final encoding, the
+ * message body length is determined by reading the connection until
+ * it is closed by the server.
+ */
+ parser->state = s_body_identity_eof;
+ }
+ }
+ else
+ {
+ if (parser->content_length == 0)
+ {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ CROW_CALLBACK_NOTIFY(message_complete);
+ }
+ else if (parser->content_length != CROW_ULLONG_MAX)
+ {
+ /* Content-Length header given and non-zero */
+ parser->state = s_body_identity;
+ }
+ else
+ {
+ /* Assume content-length 0 - read the next */
+ CROW_CALLBACK_NOTIFY(message_complete);
+ }
+ }
+
+ break;
+ }
+
+ case s_body_identity:
+ {
+ uint64_t to_read = CROW_MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->content_length != 0
+ && parser->content_length != CROW_ULLONG_MAX);
+
+ /* The difference between advancing content_length and p is because
+ * the latter will automaticaly advance on the next loop iteration.
+ * Further, if content_length ends up at 0, we want to see the last
+ * byte again for our message complete callback.
+ */
+ CROW_MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ parser->state = s_message_done;
+
+ /* Mimic CROW_CALLBACK_DATA_NOADVANCE() but with one extra byte.
+ *
+ * The alternative to doing this is to wait for the next byte to
+ * trigger the data callback, just as in every other case. The
+ * problem with this is that this makes it difficult for the test
+ * harness to distinguish between complete-on-EOF and
+ * complete-on-length. It's not clear that this distinction is
+ * important for applications, but let's keep it for now.
+ */
+ CROW_CALLBACK_DATA_(body, p - body_mark + 1, p - data);
+ CROW_REEXECUTE();
+ }
+
+ break;
+ }
+
+ /* read until EOF */
+ case s_body_identity_eof:
+ CROW_MARK(body);
+ p = data + len - 1;
+
+ break;
+
+ case s_message_done:
+ CROW_CALLBACK_NOTIFY(message_complete);
+ break;
+
+ case s_chunk_size_start:
+ {
+ assert(nread == 1);
+ assert(parser->flags & F_CHUNKED);
+
+ unhex_val = unhex[static_cast(ch)];
+ if (CROW_UNLIKELY(unhex_val == -1)) {
+ CROW_SET_ERRNO(CHPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ parser->content_length = unhex_val;
+ parser->state = s_chunk_size;
+ break;
+ }
+
+ case s_chunk_size:
+ {
+ uint64_t t;
+
+ assert(parser->flags & F_CHUNKED);
+
+ if (ch == cr) {
+ parser->state = s_chunk_size_almost_done;
+ break;
+ }
+
+ unhex_val = unhex[static_cast(ch)];
+
+ if (unhex_val == -1) {
+ if (ch == ';' || ch == ' ') {
+ parser->state = s_chunk_parameters;
+ break;
+ }
+
+ CROW_SET_ERRNO(CHPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 16;
+ t += unhex_val;
+
+ /* Overflow? Test against a conservative limit for simplicity. */
+ if (CROW_UNLIKELY((CROW_ULLONG_MAX - 16) / 16 < parser->content_length)) {
+ CROW_SET_ERRNO(CHPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ case s_chunk_parameters:
+ {
+ assert(parser->flags & F_CHUNKED);
+ /* just ignore this shit. TODO check for overflow */
+ if (ch == cr) {
+ parser->state = s_chunk_size_almost_done;
+ break;
+ }
+ break;
+ }
+
+ case s_chunk_size_almost_done:
+ {
+ assert(parser->flags & F_CHUNKED);
+ CROW_STRICT_CHECK(ch != lf);
+
+ parser->nread = 0;
+ nread = 0;
+
+ if (parser->content_length == 0) {
+ parser->flags |= F_TRAILING;
+ parser->state = s_header_field_start;
+ } else {
+ parser->state = s_chunk_data;
+ }
+ break;
+ }
+
+ case s_chunk_data:
+ {
+ uint64_t to_read = CROW_MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length != 0
+ && parser->content_length != CROW_ULLONG_MAX);
+
+ /* See the explanation in s_body_identity for why the content
+ * length and data pointers are managed this way.
+ */
+ CROW_MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ parser->state = s_chunk_data_almost_done;
+ }
+
+ break;
+ }
+
+ case s_chunk_data_almost_done:
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length == 0);
+ CROW_STRICT_CHECK(ch != cr);
+ parser->state = s_chunk_data_done;
+ CROW_CALLBACK_DATA(body);
+ break;
+
+ case s_chunk_data_done:
+ assert(parser->flags & F_CHUNKED);
+ CROW_STRICT_CHECK(ch != lf);
+ parser->nread = 0;
+ nread = 0;
+ parser->state = s_chunk_size_start;
+ break;
+
+ default:
+ assert(0 && "unhandled state");
+ CROW_SET_ERRNO(CHPE_INVALID_INTERNAL_STATE);
+ goto error;
+ }
+ }
+
+ /* Run callbacks for any marks that we have leftover after we ran out of
+ * bytes. There should be at most one of these set, so it's OK to invoke
+ * them in series (unset marks will not result in callbacks).
+ *
+ * We use the NOADVANCE() variety of callbacks here because 'p' has already
+ * overflowed 'data' and this allows us to correct for the off-by-one that
+ * we'd otherwise have (since CROW_CALLBACK_DATA() is meant to be run with a 'p'
+ * value that's in-bounds).
+ */
+
+ assert(((header_field_mark ? 1 : 0) +
+ (header_value_mark ? 1 : 0) +
+ (url_mark ? 1 : 0) +
+ (body_mark ? 1 : 0)) <= 1);
+
+ CROW_CALLBACK_DATA_NOADVANCE(header_field);
+ CROW_CALLBACK_DATA_NOADVANCE(header_value);
+ CROW_CALLBACK_DATA_NOADVANCE(url);
+ CROW_CALLBACK_DATA_NOADVANCE(body);
+
+ parser->nread = nread;
+ return len;
+
+error:
+ if (CROW_HTTP_PARSER_ERRNO(parser) == CHPE_OK) {
+ CROW_SET_ERRNO(CHPE_UNKNOWN);
+ }
+
+ parser->nread = nread;
+ return (p - data);
+}
+
+inline void
+ http_parser_init(http_parser* parser)
+{
+ void *data = parser->data; /* preserve application data */
+ memset(parser, 0, sizeof(*parser));
+ parser->data = data;
+ parser->state = s_start_req;
+ parser->http_errno = CHPE_OK;
+}
+
+/* Return a string name of the given error */
+inline const char *
+http_errno_name(enum http_errno err) {
+/* Map errno values to strings for human-readable output */
+#define CROW_HTTP_STRERROR_GEN(n, s) { "CHPE_" #n, s },
+static struct {
+ const char *name;
+ const char *description;
+} http_strerror_tab[] = {
+ CROW_HTTP_ERRNO_MAP(CROW_HTTP_STRERROR_GEN)
+};
+#undef CROW_HTTP_STRERROR_GEN
+ assert(((size_t) err) < CROW_ARRAY_SIZE(http_strerror_tab));
+ return http_strerror_tab[err].name;
+}
+
+/* Return a string description of the given error */
+inline const char *
+http_errno_description(enum http_errno err) {
+/* Map errno values to strings for human-readable output */
+#define CROW_HTTP_STRERROR_GEN(n, s) { "CHPE_" #n, s },
+static struct {
+ const char *name;
+ const char *description;
+} http_strerror_tab[] = {
+ CROW_HTTP_ERRNO_MAP(CROW_HTTP_STRERROR_GEN)
+};
+#undef CROW_HTTP_STRERROR_GEN
+ assert(((size_t) err) < CROW_ARRAY_SIZE(http_strerror_tab));
+ return http_strerror_tab[err].description;
+}
+
+/* Checks if this is the final chunk of the body. */
+inline int
+http_body_is_final(const struct http_parser *parser) {
+ return parser->state == s_message_done;
+}
+
+/* Change the maximum header size provided at compile time. */
+inline void
+http_parser_set_max_header_size(uint32_t size) {
+ max_header_size = size;
+}
+
+#undef CROW_HTTP_ERRNO_MAP
+#undef CROW_SET_ERRNO
+#undef CROW_CALLBACK_NOTIFY_
+#undef CROW_CALLBACK_NOTIFY
+#undef CROW_CALLBACK_NOTIFY_NOADVANCE
+#undef CROW_CALLBACK_DATA_
+#undef CROW_CALLBACK_DATA
+#undef CROW_CALLBACK_DATA_NOADVANCE
+#undef CROW_MARK
+#undef CROW_PROXY_CONNECTION
+#undef CROW_CONNECTION
+#undef CROW_CONTENT_LENGTH
+#undef CROW_TRANSFER_ENCODING
+#undef CROW_UPGRADE
+#undef CROW_CHUNKED
+#undef CROW_KEEP_ALIVE
+#undef CROW_CLOSE
+#undef CROW_PARSING_HEADER
+#undef CROW_LOWER
+#undef CROW_IS_ALPHA
+#undef CROW_IS_NUM
+#undef CROW_IS_ALPHANUM
+//#undef CROW_IS_HEX
+#undef CROW_IS_MARK
+#undef CROW_IS_USERINFO_CHAR
+#undef CROW_TOKEN
+#undef CROW_IS_URL_CHAR
+//#undef CROW_IS_HOST_CHAR
+#undef CROW_STRICT_CHECK
+
+}
+
+// clang-format on
diff --git a/include/crow/http_request.h b/include/crow/http_request.h
new file mode 100644
index 0000000..bd65c07
--- /dev/null
+++ b/include/crow/http_request.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#ifdef CROW_USE_BOOST
+#include
+#else
+#ifndef ASIO_STANDALONE
+#define ASIO_STANDALONE
+#endif
+#include
+#endif
+
+#include "crow/common.h"
+#include "crow/ci_map.h"
+#include "crow/query_string.h"
+
+namespace crow // NOTE: Already documented in "crow/app.h"
+{
+#ifdef CROW_USE_BOOST
+ namespace asio = boost::asio;
+#endif
+
+ /// Find and return the value associated with the key. (returns an empty string if nothing is found)
+ template
+ inline const std::string& get_header_value(const T& headers, const std::string& key)
+ {
+ if (headers.count(key))
+ {
+ return headers.find(key)->second;
+ }
+ static std::string empty;
+ return empty;
+ }
+
+ /// An HTTP request.
+ struct request
+ {
+ HTTPMethod method;
+ std::string raw_url; ///< The full URL containing the `?` and URL parameters.
+ std::string url; ///< The endpoint without any parameters.
+ query_string url_params; ///< The parameters associated with the request. (everything after the `?` in the URL)
+ ci_map headers;
+ std::string body;
+ std::string remote_ip_address; ///< The IP address from which the request was sent.
+ unsigned char http_ver_major, http_ver_minor;
+ bool keep_alive, ///< Whether or not the server should send a `connection: Keep-Alive` header to the client.
+ close_connection, ///< Whether or not the server should shut down the TCP connection once a response is sent.
+ upgrade; ///< Whether or noth the server should change the HTTP connection to a different connection.
+
+ void* middleware_context{};
+ void* middleware_container{};
+ asio::io_context* io_context{};
+
+ /// Construct an empty request. (sets the method to `GET`)
+ request():
+ method(HTTPMethod::Get)
+ {}
+
+ /// Construct a request with all values assigned.
+ request(HTTPMethod method_, std::string raw_url_, std::string url_, query_string url_params_, ci_map headers_, std::string body_, unsigned char http_major, unsigned char http_minor, bool has_keep_alive, bool has_close_connection, bool is_upgrade):
+ method(method_), raw_url(std::move(raw_url_)), url(std::move(url_)), url_params(std::move(url_params_)), headers(std::move(headers_)), body(std::move(body_)), http_ver_major(http_major), http_ver_minor(http_minor), keep_alive(has_keep_alive), close_connection(has_close_connection), upgrade(is_upgrade)
+ {}
+
+ void add_header(std::string key, std::string value)
+ {
+ headers.emplace(std::move(key), std::move(value));
+ }
+
+ const std::string& get_header_value(const std::string& key) const
+ {
+ return crow::get_header_value(headers, key);
+ }
+
+ bool check_version(unsigned char major, unsigned char minor) const
+ {
+ return http_ver_major == major && http_ver_minor == minor;
+ }
+
+ /// Get the body as parameters in QS format.
+
+ ///
+ /// This is meant to be used with requests of type "application/x-www-form-urlencoded"
+ const query_string get_body_params() const
+ {
+ return query_string(body, false);
+ }
+
+ /// Send data to whoever made this request with a completion handler and return immediately.
+ template
+ void post(CompletionHandler handler)
+ {
+ asio::post(io_context, handler);
+ }
+
+ /// Send data to whoever made this request with a completion handler.
+ template
+ void dispatch(CompletionHandler handler)
+ {
+ asio::dispatch(io_context, handler);
+ }
+ };
+} // namespace crow
diff --git a/include/crow/http_response.h b/include/crow/http_response.h
new file mode 100644
index 0000000..e235025
--- /dev/null
+++ b/include/crow/http_response.h
@@ -0,0 +1,327 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+// S_ISREG is not defined for windows
+// This defines it like suggested in https://stackoverflow.com/a/62371749
+#if defined(_MSC_VER)
+#define _CRT_INTERNAL_NONSTDC_NAMES 1
+#endif
+#include
+#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
+#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+
+#include "crow/http_request.h"
+#include "crow/ci_map.h"
+#include "crow/socket_adaptors.h"
+#include "crow/logging.h"
+#include "crow/mime_types.h"
+#include "crow/returnable.h"
+
+
+namespace crow
+{
+ template
+ class Connection;
+
+ class Router;
+
+ /// HTTP response
+ struct response
+ {
+ template
+ friend class crow::Connection;
+
+ friend class Router;
+
+ int code{200}; ///< The Status code for the response.
+ std::string body; ///< The actual payload containing the response data.
+ ci_map headers; ///< HTTP headers.
+
+#ifdef CROW_ENABLE_COMPRESSION
+ bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
+#endif
+ bool skip_body = false; ///< Whether this is a response to a HEAD request.
+ bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
+
+ /// Set the value of an existing header in the response.
+ void set_header(std::string key, std::string value)
+ {
+ headers.erase(key);
+ headers.emplace(std::move(key), std::move(value));
+ }
+
+ /// Add a new header to the response.
+ void add_header(std::string key, std::string value)
+ {
+ headers.emplace(std::move(key), std::move(value));
+ }
+
+ const std::string& get_header_value(const std::string& key)
+ {
+ return crow::get_header_value(headers, key);
+ }
+
+ // naive validation of a mime-type string
+ static bool validate_mime_type(const std::string& candidate) noexcept
+ {
+ // Here we simply check that the candidate type starts with
+ // a valid parent type, and has at least one character afterwards.
+ std::array valid_parent_types = {
+ "application/", "audio/", "font/", "example/",
+ "image/", "message/", "model/", "multipart/",
+ "text/", "video/"};
+ for (const std::string& parent : valid_parent_types)
+ {
+ // ensure the candidate is *longer* than the parent,
+ // to avoid unnecessary string comparison and to
+ // reject zero-length subtypes.
+ if (candidate.size() <= parent.size())
+ {
+ continue;
+ }
+ // strncmp is used rather than substr to avoid allocation,
+ // but a string_view approach would be better if Crow
+ // migrates to C++17.
+ if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Find the mime type from the content type either by lookup,
+ // or by the content type itself, if it is a valid a mime type.
+ // Defaults to text/plain.
+ static std::string get_mime_type(const std::string& contentType)
+ {
+ const auto mimeTypeIterator = mime_types.find(contentType);
+ if (mimeTypeIterator != mime_types.end())
+ {
+ return mimeTypeIterator->second;
+ }
+ else if (validate_mime_type(contentType))
+ {
+ return contentType;
+ }
+ else
+ {
+ CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain.";
+ return "text/plain";
+ }
+ }
+
+
+ // clang-format off
+ response() {}
+ explicit response(int code_) : code(code_) {}
+ response(std::string body_) : body(std::move(body_)) {}
+ response(int code_, std::string body_) : code(code_), body(std::move(body_)) {}
+ // clang-format on
+ response(returnable&& value)
+ {
+ body = value.dump();
+ set_header("Content-Type", value.content_type);
+ }
+ response(returnable& value)
+ {
+ body = value.dump();
+ set_header("Content-Type", value.content_type);
+ }
+ response(int code_, returnable& value):
+ code(code_)
+ {
+ body = value.dump();
+ set_header("Content-Type", value.content_type);
+ }
+ response(int code_, returnable&& value):
+ code(code_), body(value.dump())
+ {
+ set_header("Content-Type", std::move(value.content_type));
+ }
+
+ response(response&& r)
+ {
+ *this = std::move(r);
+ }
+
+ response(std::string contentType, std::string body_):
+ body(std::move(body_))
+ {
+ set_header("Content-Type", get_mime_type(contentType));
+ }
+
+ response(int code_, std::string contentType, std::string body_):
+ code(code_), body(std::move(body_))
+ {
+ set_header("Content-Type", get_mime_type(contentType));
+ }
+
+ response& operator=(const response& r) = delete;
+
+ response& operator=(response&& r) noexcept
+ {
+ body = std::move(r.body);
+ code = r.code;
+ headers = std::move(r.headers);
+ completed_ = r.completed_;
+ file_info = std::move(r.file_info);
+ return *this;
+ }
+
+ /// Check if the response has completed (whether response.end() has been called)
+ bool is_completed() const noexcept
+ {
+ return completed_;
+ }
+
+ void clear()
+ {
+ body.clear();
+ code = 200;
+ headers.clear();
+ completed_ = false;
+ file_info = static_file_info{};
+ }
+
+ /// Return a "Temporary Redirect" response.
+
+ ///
+ /// Location can either be a route or a full URL.
+ void redirect(const std::string& location)
+ {
+ code = 307;
+ set_header("Location", location);
+ }
+
+ /// Return a "Permanent Redirect" response.
+
+ ///
+ /// Location can either be a route or a full URL.
+ void redirect_perm(const std::string& location)
+ {
+ code = 308;
+ set_header("Location", location);
+ }
+
+ /// Return a "Found (Moved Temporarily)" response.
+
+ ///
+ /// Location can either be a route or a full URL.
+ void moved(const std::string& location)
+ {
+ code = 302;
+ set_header("Location", location);
+ }
+
+ /// Return a "Moved Permanently" response.
+
+ ///
+ /// Location can either be a route or a full URL.
+ void moved_perm(const std::string& location)
+ {
+ code = 301;
+ set_header("Location", location);
+ }
+
+ void write(const std::string& body_part)
+ {
+ body += body_part;
+ }
+
+ /// Set the response completion flag and call the handler (to send the response).
+ void end()
+ {
+ if (!completed_)
+ {
+ completed_ = true;
+ if (skip_body)
+ {
+ set_header("Content-Length", std::to_string(body.size()));
+ body = "";
+ manual_length_header = true;
+ }
+ if (complete_request_handler_)
+ {
+ complete_request_handler_();
+ manual_length_header = false;
+ skip_body = false;
+ }
+ }
+ }
+
+ /// Same as end() except it adds a body part right before ending.
+ void end(const std::string& body_part)
+ {
+ body += body_part;
+ end();
+ }
+
+ /// Check if the connection is still alive (usually by checking the socket status).
+ bool is_alive()
+ {
+ return is_alive_helper_ && is_alive_helper_();
+ }
+
+ /// Check whether the response has a static file defined.
+ bool is_static_type()
+ {
+ return file_info.path.size();
+ }
+
+ /// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
+
+ ///
+ /// Either a static file or a string body can be returned as 1 response.
+ struct static_file_info
+ {
+ std::string path = "";
+ struct stat statbuf;
+ int statResult;
+ };
+
+ /// Return a static file as the response body
+ void set_static_file_info(std::string path)
+ {
+ utility::sanitize_filename(path);
+ set_static_file_info_unsafe(path);
+ }
+
+ /// Return a static file as the response body without sanitizing the path (use set_static_file_info instead)
+ void set_static_file_info_unsafe(std::string path)
+ {
+ file_info.path = path;
+ file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
+#ifdef CROW_ENABLE_COMPRESSION
+ compressed = false;
+#endif
+ if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode))
+ {
+ std::size_t last_dot = path.find_last_of('.');
+ std::string extension = path.substr(last_dot + 1);
+ code = 200;
+ this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
+
+ if (!extension.empty())
+ {
+ this->add_header("Content-Type", get_mime_type(extension));
+ }
+ }
+ else
+ {
+ code = 404;
+ file_info.path.clear();
+ }
+ }
+
+ private:
+ bool completed_{};
+ std::function complete_request_handler_;
+ std::function is_alive_helper_;
+ static_file_info file_info;
+ };
+} // namespace crow
diff --git a/include/crow/http_server.h b/include/crow/http_server.h
new file mode 100644
index 0000000..12ff372
--- /dev/null
+++ b/include/crow/http_server.h
@@ -0,0 +1,310 @@
+#pragma once
+
+#ifdef CROW_USE_BOOST
+#include
+#ifdef CROW_ENABLE_SSL
+#include
+#endif
+#else
+#ifndef ASIO_STANDALONE
+#define ASIO_STANDALONE
+#endif
+#include
+#ifdef CROW_ENABLE_SSL
+#include
+#endif
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "crow/version.h"
+#include "crow/http_connection.h"
+#include "crow/logging.h"
+#include "crow/task_timer.h"
+
+
+namespace crow // NOTE: Already documented in "crow/app.h"
+{
+#ifdef CROW_USE_BOOST
+ namespace asio = boost::asio;
+ using error_code = boost::system::error_code;
+#else
+ using error_code = asio::error_code;
+#endif
+ using tcp = asio::ip::tcp;
+
+ template
+ class Server
+ {
+ public:
+ Server(Handler* handler,
+ const tcp::endpoint& endpoint,
+ std::string server_name = std::string("Crow/") + VERSION,
+ std::tuple* middlewares = nullptr,
+ uint16_t concurrency = 1,
+ uint8_t timeout = 5,
+ typename Adaptor::context* adaptor_ctx = nullptr):
+ acceptor_(io_context_,endpoint),
+ signals_(io_context_),
+ tick_timer_(io_context_),
+ handler_(handler),
+ concurrency_(concurrency),
+ timeout_(timeout),
+ server_name_(server_name),
+ task_queue_length_pool_(concurrency_ - 1),
+ middlewares_(middlewares),
+ adaptor_ctx_(adaptor_ctx)
+ {}
+
+ void set_tick_function(std::chrono::milliseconds d, std::function f)
+ {
+ tick_interval_ = d;
+ tick_function_ = f;
+ }
+
+ void on_tick()
+ {
+ tick_function_();
+ tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count()));
+ tick_timer_.async_wait([this](const error_code& ec) {
+ if (ec)
+ return;
+ on_tick();
+ });
+ }
+
+ void run()
+ {
+ uint16_t worker_thread_count = concurrency_ - 1;
+ for (int i = 0; i < worker_thread_count; i++)
+ io_context_pool_.emplace_back(new asio::io_context());
+ get_cached_date_str_pool_.resize(worker_thread_count);
+ task_timer_pool_.resize(worker_thread_count);
+
+ std::vector> v;
+ std::atomic init_count(0);
+ for (uint16_t i = 0; i < worker_thread_count; i++)
+ v.push_back(
+ std::async(
+ std::launch::async, [this, i, &init_count] {
+ // thread local date string get function
+ auto last = std::chrono::steady_clock::now();
+
+ std::string date_str;
+ auto update_date_str = [&] {
+ auto last_time_t = time(0);
+ tm my_tm;
+
+#if defined(_MSC_VER) || defined(__MINGW32__)
+ gmtime_s(&my_tm, &last_time_t);
+#else
+ gmtime_r(&last_time_t, &my_tm);
+#endif
+ date_str.resize(100);
+ size_t date_str_sz = strftime(&date_str[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &my_tm);
+ date_str.resize(date_str_sz);
+ };
+ update_date_str();
+ get_cached_date_str_pool_[i] = [&]() -> std::string {
+ if (std::chrono::steady_clock::now() - last >= std::chrono::seconds(1))
+ {
+ last = std::chrono::steady_clock::now();
+ update_date_str();
+ }
+ return date_str;
+ };
+
+ // initializing task timers
+ detail::task_timer task_timer(*io_context_pool_[i]);
+ task_timer.set_default_timeout(timeout_);
+ task_timer_pool_[i] = &task_timer;
+ task_queue_length_pool_[i] = 0;
+
+ init_count++;
+ while (1)
+ {
+ try
+ {
+ if (io_context_pool_[i]->run() == 0)
+ {
+ // when io_service.run returns 0, there are no more works to do.
+ break;
+ }
+ }
+ catch (std::exception& e)
+ {
+ CROW_LOG_ERROR << "Worker Crash: An uncaught exception occurred: " << e.what();
+ }
+ }
+ }));
+
+ if (tick_function_ && tick_interval_.count() > 0)
+ {
+ tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count()));
+ tick_timer_.async_wait(
+ [this](const error_code& ec) {
+ if (ec)
+ return;
+ on_tick();
+ });
+ }
+
+ handler_->port(acceptor_.local_endpoint().port());
+
+
+ CROW_LOG_INFO << server_name_
+ << " server is running at " << (handler_->ssl_used() ? "https://" : "http://")
+ << acceptor_.local_endpoint().address() << ":" << acceptor_.local_endpoint().port() << " using " << concurrency_ << " threads";
+ CROW_LOG_INFO << "Call `app.loglevel(crow::LogLevel::Warning)` to hide Info level logs.";
+
+ signals_.async_wait(
+ [&](const error_code& /*error*/, int /*signal_number*/) {
+ stop();
+ });
+
+ while (worker_thread_count != init_count)
+ std::this_thread::yield();
+
+ do_accept();
+
+ std::thread(
+ [this] {
+ notify_start();
+ io_context_.run();
+ CROW_LOG_INFO << "Exiting.";
+ })
+ .join();
+ }
+
+ void stop()
+ {
+ shutting_down_ = true; // Prevent the acceptor from taking new connections
+ for (auto& io_context : io_context_pool_)
+ {
+ if (io_context != nullptr)
+ {
+ CROW_LOG_INFO << "Closing IO service " << &io_context;
+ io_context->stop(); // Close all io_services (and HTTP connections)
+ }
+ }
+
+ CROW_LOG_INFO << "Closing main IO service (" << &io_context_ << ')';
+ io_context_.stop(); // Close main io_service
+ }
+
+ uint16_t port() const {
+ return acceptor_.local_endpoint().port();
+ }
+
+ /// Wait until the server has properly started or until timeout
+ std::cv_status wait_for_start(std::chrono::steady_clock::time_point wait_until)
+ {
+ std::unique_lock lock(start_mutex_);
+
+ std::cv_status status = std::cv_status::no_timeout;
+ while (!server_started_ && ( status==std::cv_status::no_timeout ))
+ status = cv_started_.wait_until(lock,wait_until);
+ return status;
+ }
+
+ void signal_clear()
+ {
+ signals_.clear();
+ }
+
+ void signal_add(int signal_number)
+ {
+ signals_.add(signal_number);
+ }
+
+ private:
+ uint16_t pick_io_context_idx()
+ {
+ uint16_t min_queue_idx = 0;
+
+ // TODO improve load balancing
+ // size_t is used here to avoid the security issue https://codeql.github.com/codeql-query-help/cpp/cpp-comparison-with-wider-type/
+ // even though the max value of this can be only uint16_t as concurrency is uint16_t.
+ for (size_t i = 1; i < task_queue_length_pool_.size() && task_queue_length_pool_[min_queue_idx] > 0; i++)
+ // No need to check other io_services if the current one has no tasks
+ {
+ if (task_queue_length_pool_[i] < task_queue_length_pool_[min_queue_idx])
+ min_queue_idx = i;
+ }
+ return min_queue_idx;
+ }
+
+ void do_accept()
+ {
+ if (!shutting_down_)
+ {
+ uint16_t context_idx = pick_io_context_idx();
+ asio::io_context& ic = *io_context_pool_[context_idx];
+ task_queue_length_pool_[context_idx]++;
+ CROW_LOG_DEBUG << &ic << " {" << context_idx << "} queue length: " << task_queue_length_pool_[context_idx];
+
+ auto p = std::make_shared>(
+ ic, handler_, server_name_, middlewares_,
+ get_cached_date_str_pool_[context_idx], *task_timer_pool_[context_idx], adaptor_ctx_, task_queue_length_pool_[context_idx]);
+
+ acceptor_.async_accept(
+ p->socket(),
+ [this, p, &ic, context_idx](error_code ec) {
+ if (!ec)
+ {
+ asio::post(ic,
+ [p] {
+ p->start();
+ });
+ }
+ else
+ {
+ task_queue_length_pool_[context_idx]--;
+ CROW_LOG_DEBUG << &ic << " {" << context_idx << "} queue length: " << task_queue_length_pool_[context_idx];
+ }
+ do_accept();
+ });
+ }
+ }
+
+ /// Notify anything using `wait_for_start()` to proceed
+ void notify_start()
+ {
+ std::unique_lock lock(start_mutex_);
+ server_started_ = true;
+ cv_started_.notify_all();
+ }
+
+ private:
+ std::vector> io_context_pool_;
+ asio::io_context io_context_;
+ std::vector task_timer_pool_;
+ std::vector> get_cached_date_str_pool_;
+ tcp::acceptor acceptor_;
+ bool shutting_down_ = false;
+ bool server_started_{false};
+ std::condition_variable cv_started_;
+ std::mutex start_mutex_;
+ asio::signal_set signals_;
+
+ asio::basic_waitable_timer tick_timer_;
+
+ Handler* handler_;
+ uint16_t concurrency_{2};
+ std::uint8_t timeout_;
+ std::string server_name_;
+ std::vector> task_queue_length_pool_;
+
+ std::chrono::milliseconds tick_interval_;
+ std::function tick_function_;
+
+ std::tuple* middlewares_;
+
+ typename Adaptor::context* adaptor_ctx_;
+ };
+} // namespace crow
diff --git a/include/crow/json.h b/include/crow/json.h
new file mode 100644
index 0000000..bc98a5d
--- /dev/null
+++ b/include/crow/json.h
@@ -0,0 +1,2086 @@
+#pragma once
+
+//#define CROW_JSON_NO_ERROR_CHECK
+//#define CROW_JSON_USE_MAP
+
+#include
+#ifdef CROW_JSON_USE_MAP
+#include