Pharos 0.7.23
Modern Web Framework & Web App Hosting Service.
Loading...
Searching...
No Matches
ucal_static_server.c
Go to the documentation of this file.
1/**
2 * @file
3 * @brief Provides a minimal loopback static server for serving UCAL build artifacts and route-mapped files directly from a build root.
4 */
5
6#include <arpa/inet.h>
7#include <errno.h>
8#include <netinet/in.h>
9#include <signal.h>
10#include <stddef.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <sys/socket.h>
15#include <sys/stat.h>
16#include <sys/types.h>
17#include <unistd.h>
18
19static int send_all(int fd, const char *buf, size_t len) {
20 while (len > 0) {
21 ssize_t n = send(fd, buf, len, 0);
22 if (n <= 0) return -1;
23 buf += (size_t)n;
24 len -= (size_t)n;
25 }
26 return 0;
27}
28
29static char *read_file(const char *path, size_t *len_out) {
30 FILE *f = fopen(path, "rb");
31 long n;
32 char *buf;
33 if (!f) return NULL;
34 if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
35 n = ftell(f);
36 if (n < 0) { fclose(f); return NULL; }
37 if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return NULL; }
38 buf = (char *)malloc((size_t)n + 1);
39 if (!buf) { fclose(f); return NULL; }
40 if (fread(buf, 1, (size_t)n, f) != (size_t)n) { free(buf); fclose(f); return NULL; }
41 fclose(f);
42 buf[n] = '\0';
43 *len_out = (size_t)n;
44 return buf;
45}
46
47static int path_has_parent_reference(const char *path) {
48 return strstr(path, "..") != NULL;
49}
50
51static const char *content_type_for_path(const char *path) {
52 const char *ext = strrchr(path, '.');
53 if (!ext) return "application/octet-stream";
54 if (strcmp(ext, ".html") == 0) return "text/html; charset=utf-8";
55 if (strcmp(ext, ".json") == 0) return "application/json";
56 if (strcmp(ext, ".css") == 0) return "text/css; charset=utf-8";
57 if (strcmp(ext, ".js") == 0) return "application/javascript; charset=utf-8";
58 if (strcmp(ext, ".svg") == 0) return "image/svg+xml";
59 if (strcmp(ext, ".png") == 0) return "image/png";
60 if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg";
61 if (strcmp(ext, ".gif") == 0) return "image/gif";
62 if (strcmp(ext, ".webp") == 0) return "image/webp";
63 if (strcmp(ext, ".ico") == 0) return "image/x-icon";
64 return "application/octet-stream";
65}
66
67static int resolve_direct_asset(const char *root, const char *path,
68 char *file_path, size_t file_path_len,
69 char *content_type, size_t content_type_len) {
70 const char *relative = path[0] == '/' ? path + 1 : path;
71 if (!(strncmp(path, "/static/", 8) == 0 || strncmp(path, "/media/", 7) == 0)) return 0;
72 if (path_has_parent_reference(relative)) return -1;
73 if (snprintf(file_path, file_path_len, "%s/%s", root, relative) >= (int)file_path_len) return -1;
74 if (snprintf(content_type, content_type_len, "%s", content_type_for_path(file_path)) >= (int)content_type_len) return -1;
75 return 1;
76}
77
78static int lookup_route_map(const char *root, const char *path,
79 char *artifact, size_t artifact_len,
80 char *content_type, size_t content_type_len) {
81 char map_path[2048];
82 char line[4096];
83 FILE *f;
84 if (snprintf(map_path, sizeof(map_path), "%s/runtime-route-map.tsv", root) >= (int)sizeof(map_path)) return 0;
85 f = fopen(map_path, "rb");
86 if (!f) return 0;
87 while (fgets(line, sizeof(line), f)) {
88 char *method = line;
89 char *route_path = strchr(method, '\t');
90 char *route_artifact;
91 char *route_type;
92 char *status;
93 if (!route_path) continue;
94 *route_path++ = '\0';
95 route_artifact = strchr(route_path, '\t');
96 if (!route_artifact) continue;
97 *route_artifact++ = '\0';
98 route_type = strchr(route_artifact, '\t');
99 if (!route_type) continue;
100 *route_type++ = '\0';
101 status = strchr(route_type, '\t');
102 if (!status) continue;
103 *status++ = '\0';
104 status[strcspn(status, "\r\n")] = '\0';
105 if (strcmp(method, "GET") == 0 && strcmp(route_path, path) == 0) {
106 if (snprintf(artifact, artifact_len, "%s", route_artifact) >= (int)artifact_len ||
107 snprintf(content_type, content_type_len, "%s", route_type) >= (int)content_type_len) {
108 fclose(f);
109 return 0;
110 }
111 fclose(f);
112 return 1;
113 }
114 }
115 fclose(f);
116 return 0;
117}
118
119/**
120 * @brief Serves one loopback client request from the UCAL build root.
121 */
122static void handle_client(int client, const char *root) {
123 char req[4096];
124 char method[16];
125 char path[1024];
126 char content_type[128];
127 char rel[1024];
128 char file_path[2048];
129 char header[512];
130 char *body;
131 int direct_asset_status;
132 size_t body_len = 0;
133 ssize_t got = recv(client, req, sizeof(req) - 1, 0);
134 if (got <= 0) return;
135 req[got] = '\0';
136 method[0] = '\0';
137 path[0] = '\0';
138 if (sscanf(req, "%15s %1023s", method, path) != 2 || strcmp(method, "GET") != 0) {
139 const char *bad = "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
140 (void)send_all(client, bad, strlen(bad));
141 return;
142 }
143 direct_asset_status = resolve_direct_asset(root, path, file_path, sizeof(file_path), content_type, sizeof(content_type));
144 if (direct_asset_status < 0) {
145 const char *bad = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
146 (void)send_all(client, bad, strlen(bad));
147 return;
148 }
149 if (direct_asset_status == 0) {
150 if (!lookup_route_map(root, path, rel, sizeof(rel), content_type, sizeof(content_type))) {
151 const char *not_found = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
152 (void)send_all(client, not_found, strlen(not_found));
153 return;
154 }
155 if (snprintf(file_path, sizeof(file_path), "%s/%s", root, rel) >= (int)sizeof(file_path)) {
156 const char *too_long = "HTTP/1.1 414 URI Too Long\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
157 (void)send_all(client, too_long, strlen(too_long));
158 return;
159 }
160 }
161 body = read_file(file_path, &body_len);
162 if (!body) {
163 const char *missing = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
164 (void)send_all(client, missing, strlen(missing));
165 return;
166 }
167 (void)snprintf(header, sizeof(header),
168 "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n",
169 content_type, body_len);
170 (void)send_all(client, header, strlen(header));
171 (void)send_all(client, body, body_len);
172 free(body);
173}
174
175/**
176 * @brief Starts the minimal UCAL loopback static server.
177 */
178int main(int argc, char **argv) {
179 const char *root = argc > 1 ? argv[1] : ".";
180 int port = argc > 2 ? atoi(argv[2]) : 19090;
181 int max_requests = argc > 3 ? atoi(argv[3]) : 3;
182 int server_fd;
183 int opt = 1;
184 struct sockaddr_in addr;
185 if (port <= 0 || port > 65535) return 64;
186 if (max_requests <= 0) max_requests = 3;
187 signal(SIGPIPE, SIG_IGN);
188 server_fd = socket(AF_INET, SOCK_STREAM, 0);
189 if (server_fd < 0) return 70;
190 (void)setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
191 memset(&addr, 0, sizeof(addr));
192 addr.sin_family = AF_INET;
193 addr.sin_port = htons((uint16_t)port);
194 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
195 if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) return 71;
196 if (listen(server_fd, 16) != 0) return 72;
197 for (int i = 0; i < max_requests; i++) {
198 int client = accept(server_fd, NULL, NULL);
199 if (client < 0) {
200 if (errno == EINTR) { i--; continue; }
201 break;
202 }
203 handle_client(client, root);
204 close(client);
205 }
206 close(server_fd);
207 return 0;
208}
static int lookup_route_map(const char *root, const char *path, char *artifact, size_t artifact_len, char *content_type, size_t content_type_len)
int main(int argc, char **argv)
Starts the minimal UCAL loopback static server.
static void handle_client(int client, const char *root)
Serves one loopback client request from the UCAL build root.
static int resolve_direct_asset(const char *root, const char *path, char *file_path, size_t file_path_len, char *content_type, size_t content_type_len)
static int send_all(int fd, const char *buf, size_t len)
static int path_has_parent_reference(const char *path)
static const char * content_type_for_path(const char *path)
static char * read_file(const char *path, size_t *len_out)
const char size_t len
Definition yyjson.h:8364