UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2026 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "proxy.h" 30 31 #include <sys/socket.h> 32 #include <arpa/inet.h> 33 #include <ctype.h> 34 #include <string.h> 35 36 #include "../util/pblock.h" 37 #include "../proxy/httpclient.h" 38 39 // Gets the uri part from an http request line 40 static cxstring get_uri_from_clfreq(const char *clfreq) { 41 cxstring uri = { NULL, 0 }; 42 43 const char *begin = NULL; 44 const char *str = clfreq; 45 for(;*str != '\0';str++) { 46 if(*str < 33) { 47 if(begin) { 48 str++; 49 break; 50 } 51 } else { 52 if(!begin) { 53 begin = str; 54 } 55 } 56 } 57 58 begin = NULL; 59 for(;*str != '\0';str++) { 60 if(*str > 32) { 61 if(!begin) { 62 begin = str; 63 } 64 } else { 65 if(begin) { 66 break; 67 } 68 } 69 } 70 71 if(begin && *str != '\0') { 72 return cx_strn(begin, str-begin); 73 } 74 75 return uri; 76 77 } 78 79 typedef struct ProxyRequest { 80 Session *sn; 81 Request *rq; 82 83 /* 84 * request header rewrite map 85 * name: header name 86 * value: header value or an empty string, if the header should be removed 87 */ 88 pblock *request_header_rewrite; 89 90 /* 91 * response header rewrite map 92 * name: header name 93 * value: header value or an empty string, if the header should be removed 94 */ 95 pblock *response_header_rewrite; 96 97 /* 98 * Has the response started (proxy_response_start called) 99 */ 100 int response_started; 101 } ProxyRequest; 102 103 static int proxy_response_start(HttpClient *client, int status, char *message, void *userdata) { 104 ProxyRequest *proxy = userdata; 105 106 HeaderArray *headers = client->response_headers; 107 while(headers) { 108 for(int i=0;i<headers->len;i++) { 109 cxmutstr name = headers->headers[i].name; 110 cxmutstr value = headers->headers[i].value; 111 // NSAPI uses lower case header names internally 112 for(int c=0;c<name.length;c++) { 113 name.ptr[c] = tolower(name.ptr[c]); 114 } 115 // HttpClient does not 0-terminate strings 116 name.ptr[name.length] = 0; 117 // check if this header should be modified 118 char *rewrite = pblock_findval(name.ptr, proxy->response_header_rewrite); 119 if(rewrite) { 120 value = cx_mutstr(rewrite); 121 if(value.length == 0) { 122 // empty header value -> skip 123 continue; 124 } 125 } 126 127 // add header to response 128 pblock_nvlinsert(name.ptr, name.length, value.ptr, value.length, proxy->rq->srvhdrs); 129 } 130 headers = headers->next; 131 } 132 133 protocol_status(proxy->sn, proxy->rq, status, message); 134 protocol_start_response(proxy->sn, proxy->rq); 135 proxy->response_started = 1; 136 137 return 0; 138 } 139 140 static void proxy_response_finished(HttpClient *client, void *userdata) { 141 ProxyRequest *proxy = userdata; 142 143 int ret = REQ_PROCEED; 144 if(!proxy->response_started) { 145 protocol_status(proxy->sn, proxy->rq, 502, NULL); 146 ret = REQ_ABORTED; 147 } 148 149 http_client_free(client); 150 151 nsapi_function_return(proxy->sn, proxy->rq, ret); 152 } 153 154 static ssize_t proxy_request_read(HttpClient *client, void *buf, size_t nbytes, void *userdata) { 155 ProxyRequest *proxy = userdata; 156 int ret = netbuf_getbytes(proxy->sn->inbuf, buf, nbytes); 157 if(ret == NETBUF_EOF) { 158 ret = 0; 159 } 160 // TODO: handle errors 161 return ret; 162 } 163 164 static ssize_t proxy_response_write(HttpClient *client, void *buf, size_t nbytes, void *userdata) { 165 ProxyRequest *proxy = userdata; 166 ssize_t ret = net_write(proxy->sn->csd, buf, nbytes); 167 // TODO: handle errors 168 return ret; 169 } 170 171 int http_reverse_proxy_service(pblock *param, Session *sn, Request *rq) { 172 EventHandler *ev = sn->ev; 173 const char *method = pblock_findkeyval(pb_key_method, rq->reqpb); 174 const char *clfreq = pblock_findkeyval(pb_key_clf_request, rq->reqpb); 175 176 cxstring uri = get_uri_from_clfreq(clfreq); 177 if(uri.length == 0) { 178 return REQ_ABORTED; 179 } 180 181 // remove some response headers, that were previously set by ObjectType 182 // or other SAFs 183 pblock_removekey(pb_key_content_type, rq->srvhdrs); 184 185 ProxyRequest *proxy = pool_malloc(sn->pool, sizeof(ProxyRequest)); 186 proxy->sn = sn; 187 proxy->rq = rq; 188 proxy->request_header_rewrite = pblock_create_pool(sn->pool, 16); 189 proxy->response_header_rewrite = pblock_create_pool(sn->pool, 16); 190 proxy->response_started = 0; 191 192 // Some request/response headers should be removed or altered 193 // An empty string means, the header should be removed 194 pblock_nvinsert("host", "", proxy->request_header_rewrite); 195 pblock_nvinsert("connection", "", proxy->request_header_rewrite); 196 pblock_nvinsert("transfer-encoding", "", proxy->request_header_rewrite); 197 pblock_nvinsert("content-length", "", proxy->request_header_rewrite); 198 pblock_nvinsert("server", "", proxy->response_header_rewrite); 199 pblock_nvinsert("connection", "", proxy->response_header_rewrite); 200 201 // setup HttpClient 202 HttpClient *client = http_client_new(ev); 203 if(!client) { 204 return REQ_ABORTED; 205 } 206 207 if(http_client_set_method(client, method)) { 208 http_client_free(client); 209 return REQ_ABORTED; 210 } 211 212 if(http_client_set_uri_len(client, uri.ptr, uri.length)) { 213 http_client_free(client); 214 return REQ_ABORTED; 215 } 216 217 // test address 218 struct sockaddr_in address; 219 inet_pton(AF_INET, "127.0.0.1", &address.sin_addr); 220 address.sin_family = AF_INET; 221 address.sin_port = htons(8080); 222 http_client_set_addr(client, (struct sockaddr*)&address, sizeof(address)); 223 http_client_add_request_header(client, cx_mutstr("host"), cx_mutstr("localhost:8080")); 224 225 // add request headers to the client 226 CxIterator i = pblock_iterator(rq->headers); 227 cx_foreach(pb_entry*, entry, i) { 228 cxmutstr header_value; 229 char *rewrite_header = pblock_findval(entry->param->name, proxy->request_header_rewrite); 230 if(rewrite_header) { 231 if(!strcmp(entry->param->name, "transfer-encoding")) { 232 if(!strcmp(entry->param->value, "chunked")) { 233 // enable chunked transfer encoding 234 if(http_client_enable_chunked_transfer_encoding(client)) { 235 http_client_free(client); 236 return REQ_ABORTED; 237 } 238 continue; 239 } 240 } else if(!strcmp(entry->param->name, "content-length")) { 241 long long contentlength; 242 if(!cx_strtoll(cx_str(entry->param->value), &contentlength, 10)) { 243 if(http_client_set_content_length(client, contentlength)) { 244 http_client_free(client); 245 return REQ_ABORTED; 246 } 247 } else { 248 // illegal content-length 249 protocol_status(sn, rq, 400, NULL); 250 http_client_free(client); 251 return REQ_ABORTED; 252 } 253 continue; 254 } else { 255 // static header rewrite or remove header if it is empty 256 header_value = cx_mutstr(rewrite_header); 257 if(header_value.length == 0) { 258 continue; 259 } 260 } 261 } else { 262 header_value = cx_mutstr(entry->param->value); 263 } 264 265 if(http_client_add_request_header(client, cx_mutstr(entry->param->name), header_value)) { 266 http_client_free(client); 267 return REQ_ABORTED; 268 } 269 } 270 271 client->request_body_read = proxy_request_read; 272 client->request_body_read_userdata = proxy; 273 client->response_start = proxy_response_start; 274 client->response_start_userdata = proxy; 275 client->response_body_write = proxy_response_write; 276 client->response_body_write_userdata = proxy; 277 278 //net_setnonblock(sn->csd, 1); 279 if(http_client_start(client)) { 280 //net_setnonblock(sn->csd, 0); 281 http_client_free(client); 282 return REQ_ABORTED; 283 } 284 285 return REQ_PROCESSING; 286 } 287 288 289 290 /* --------------------------------- Tests --------------------------------- */ 291 292 static CX_TEST(test_safs_proxy_get_uri_from_clfreq) { 293 CX_TEST_DO { 294 cxstring ret; 295 296 ret = get_uri_from_clfreq("GET /uri HTTP/1.1"); 297 CX_TEST_ASSERT(!cx_strcmp(ret, "/uri")); 298 ret = get_uri_from_clfreq("G / HTTP/1.1"); 299 CX_TEST_ASSERT(!cx_strcmp(ret, "/")); 300 ret = get_uri_from_clfreq("POST /test%20/path HTTP/1.1"); 301 CX_TEST_ASSERT(!cx_strcmp(ret, "/test%20/path")); 302 ret = get_uri_from_clfreq(" GET /leading_space HTTP/1.1"); 303 CX_TEST_ASSERT(!cx_strcmp(ret, "/leading_space")); 304 ret = get_uri_from_clfreq(" PROPFIND /space2 HTTP/1.1"); 305 CX_TEST_ASSERT(!cx_strcmp(ret, "/space2")); 306 ret = get_uri_from_clfreq("HEAD /trailing_space HTTP/1.1"); 307 CX_TEST_ASSERT(!cx_strcmp(ret, "/trailing_space")); 308 ret = get_uri_from_clfreq(" GET /space3 HTTP/1.1 "); 309 CX_TEST_ASSERT(!cx_strcmp(ret, "/space3")); 310 311 // fail test 312 ret = get_uri_from_clfreq(""); 313 CX_TEST_ASSERT(ret.ptr == NULL); 314 ret = get_uri_from_clfreq(" "); 315 CX_TEST_ASSERT(ret.ptr == NULL); 316 ret = get_uri_from_clfreq("GET"); 317 CX_TEST_ASSERT(ret.ptr == NULL); 318 ret = get_uri_from_clfreq("GET /path"); 319 CX_TEST_ASSERT(ret.ptr == NULL); 320 ret = get_uri_from_clfreq(" /path2/ "); 321 CX_TEST_ASSERT(ret.ptr == NULL); 322 } 323 } 324 325 void http_reverse_proxy_add_tests(CxTestSuite *suite) { 326 cx_test_register(suite, test_safs_proxy_get_uri_from_clfreq); 327 } 328