Pharos 0.7.23
Modern Web Framework & Web App Hosting Service.
Loading...
Searching...
No Matches
native_static_serve.c
Go to the documentation of this file.
1/**
2 * @file
3 * @brief Implements native static artifact serving helpers.
4 */
5
7
10#include "native_http_log.h"
12#include "native_support.h"
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17
18#ifdef _WIN32
19#define native_static_serve_strdup _strdup
20#else
21#define native_static_serve_strdup strdup
22#endif
23
24int native_serve_static_file(NativeConnection *connection, const char *root, const char *path, const HttpRequest *request, const char *log_path) {
25 char *relative = NULL;
26 char *file_path = NULL;
27 char *content_type = NULL;
28 FILE *file = NULL;
29 int status = 500;
30 const char *path_part = path;
31 const char *query = strchr(path, '?');
32 size_t path_len = query != NULL ? (size_t)(query - path) : strlen(path);
33 long long content_length = 0;
34 if (strstr(path, "..") != NULL) {
35 native_send_text_response(connection, 400, "Bad Request", "blocked path traversal\n");
36 status = 400;
37 goto cleanup;
38 }
39 relative = native_dispatch_response_file_for_request(root, request != NULL && request->method != NULL ? request->method : "GET", path_part);
40 if (relative == NULL) {
41 if (path_len == 0 || (path_len == 1 && path[0] == '/')) {
42 relative = native_static_serve_strdup("index.html");
43 } else if (path[path_len - 1] == '/') {
44 relative = (char *)malloc(path_len + strlen("index.html"));
45 if (relative != NULL) {
46 memcpy(relative, path_part + 1, path_len - 1);
47 memcpy(relative + path_len - 1, "index.html", strlen("index.html") + 1);
48 }
49 } else {
50 relative = dup_range(path_part + 1, path_len - 1);
51 }
52 }
53 if (relative == NULL) {
54 native_send_text_response(connection, 500, "Internal Server Error", "memory allocation failed\n");
55 status = 500;
56 goto cleanup;
57 }
58 file_path = path_join(root, relative);
59 if (file_path == NULL) {
60 native_send_text_response(connection, 500, "Internal Server Error", "memory allocation failed\n");
61 status = 500;
62 goto cleanup;
63 }
64 if (path_is_directory(file_path)) {
65 char *dir_index = path_join(file_path, "index.html");
66 free(file_path);
67 file_path = dir_index;
68 }
69 if (file_path == NULL || !path_exists(file_path)) {
70 native_send_text_response(connection, 404, "Not Found", "Not Found");
71 status = 404;
72 goto cleanup;
73 }
74 file = fopen(file_path, "rb");
75 if (file == NULL) {
76 native_send_text_response(connection, 404, "Not Found", "Not Found");
77 status = 404;
78 goto cleanup;
79 }
80 if (fseek(file, 0, SEEK_END) != 0) {
81 native_send_text_response(connection, 500, "Internal Server Error", "failed to read static artifact\n");
82 status = 500;
83 goto cleanup;
84 }
85 content_length = (long long)ftell(file);
86 if (content_length < 0 || fseek(file, 0, SEEK_SET) != 0) {
87 native_send_text_response(connection, 500, "Internal Server Error", "failed to read static artifact\n");
88 status = 500;
89 goto cleanup;
90 }
91 content_type = mime_type_for_path(file_path);
92 if (content_type == NULL) {
93 native_send_text_response(connection, 500, "Internal Server Error", "mime type failure\n");
94 status = 500;
95 goto cleanup;
96 }
97 if (starts_with(path_part, "/api/") && strcmp(content_type, "application/octet-stream") == 0) {
98 free(content_type);
99 content_type = native_static_serve_strdup("application/json");
100 if (content_type == NULL) {
101 native_send_text_response(connection, 500, "Internal Server Error", "mime type failure\n");
102 status = 500;
103 goto cleanup;
104 }
105 }
107 connection,
108 "HTTP/1.1 200 OK\r\n"
109 "Content-Type: %s\r\n"
110 "Content-Length: %lld\r\n"
111 "Connection: close\r\n"
112 "\r\n",
113 content_type,
114 content_length
115 ) != 0) {
116 status = 500;
117 goto cleanup;
118 }
119 {
120 char chunk[4096];
121 size_t bytes_read;
122 while ((bytes_read = fread(chunk, 1, sizeof(chunk), file)) > 0) {
123 if (native_connection_write(connection, chunk, bytes_read) != 0) {
124 status = -1;
125 goto cleanup;
126 }
127 }
128 }
129 status = 200;
130
131cleanup:
132 if (status >= 200 && status < 400) {
133 native_write_access_log(log_path, request, path, status);
134 } else if (status > 0) {
135 native_write_error_log(log_path, NULL, "app", request, path, http_status_text_native(status), "http_response", path, NULL);
136 }
137 free(relative);
138 free(file_path);
139 free(content_type);
140 if (file != NULL) {
141 fclose(file);
142 }
143 return status;
144}
int native_connection_write(NativeConnection *connection, const void *buffer, size_t len)
Writes bytes to a native connection.
char * native_dispatch_response_file_for_request(const char *root, const char *method, const char *path)
Resolves the response artifact file for one request using the dispatch table.
Declares dispatch-table request path helpers.
void native_write_access_log(const char *log_path, const HttpRequest *request, const char *request_path, int status_code)
Writes one access log entry for a completed HTTP request.
void native_write_error_log(const char *log_path, const char *error_report_path, const char *app_id, const HttpRequest *request, const char *request_path, const char *status_text, const char *reason, const char *resource_path, const char *detail_text)
Writes one structured error log entry and its paired JSONL error report.
Declares native access log and error log helpers.
char * mime_type_for_path(const char *path)
Resolves the MIME type that should be used for a file path.
const char * http_status_text_native(int status_code)
Returns the canonical reason phrase for one HTTP status code.
Declares HTTP logging and response formatting helpers.
void native_send_text_response(NativeConnection *connection, int status, const char *status_text, const char *body)
Sends a plain-text HTTP response with explicit content length.
int native_connection_printf(NativeConnection *connection, const char *format,...)
Writes formatted bytes to a native connection.
Declares native HTTP response writing helpers.
#define native_static_serve_strdup
int native_serve_static_file(NativeConnection *connection, const char *root, const char *path, const HttpRequest *request, const char *log_path)
Serves one static artifact response from a root directory.
Declares native static artifact serving helpers.
char * path_join(const char *left, const char *right)
Joins two path segments using the native path separator.
char * dup_range(const char *start, size_t len)
Duplicates a byte range into a newly allocated NUL-terminated string.
int path_is_directory(const char *path)
Tests whether a filesystem path refers to a directory.
int starts_with(const char *value, const char *prefix)
Tests whether one string starts with another string.
int path_exists(const char *path)
Tests whether a filesystem path currently exists.
Declares shared low-level support helpers used across the Pharos native runtime.
Parsed HTTP request fields extracted from a native connection.
Accepted client connection and its resolved transport metadata.