UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2025 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 "location.h" 30 #include "config.h" 31 #include "request.h" 32 #include "rewrite.h" 33 #include "../util/util.h" 34 #include "../util/pblock.h" 35 #include "../util/pool.h" 36 #include "vserver.h" 37 #include <cx/linked_list.h> 38 #include <cx/array_list.h> 39 40 #define DIR_CHECK_ARGC(n) if(argc != n) { \ 41 log_ereport(LOG_FAILURE, "%s directive argc != %d", name.ptr, n); \ 42 return 1; \ 43 } 44 45 static void location_list_add(WSLocation *location, WSLocation *sub) { 46 void **begin = (void**)&location->children_begin; 47 void **end = (void**)&location->children_end; 48 cx_linked_list_add(begin, end, offsetof(WSLocation, prev), offsetof(WSLocation, next), sub); 49 } 50 51 static int add_location_config(ServerConfiguration *cfg, WSLocation *location, ConfigNode *dir) { 52 const CxAllocator *a = cfg->a; 53 54 cxmutstr name = dir->name; 55 int argc = serverconfig_directive_count_args(dir); 56 if(!cx_strcasecmp(name, "DirectoryIndex")) { 57 DIR_CHECK_ARGC(1); 58 location->config.set_dirindex = TRUE; 59 location->config.dirindex = util_getboolean_s(cx_strcast(dir->args->value), FALSE); 60 } else if(!cx_strcasecmp(name, "DocumentRoot")) { 61 DIR_CHECK_ARGC(1); 62 location->config.docroot = cx_strdup_a(a, dir->args->value); 63 } else if(!cx_strcasecmp(name, "AddACL")) { 64 DIR_CHECK_ARGC(1); 65 if(!location->config.acls) { 66 CxList *aclList = cxLinkedListCreate(cfg->a, NULL, CX_STORE_POINTERS); 67 if(!aclList) { 68 return 1; 69 } 70 location->config.acls = aclList; 71 cxmutstr aclName = dir->args->value; 72 if(cxListAdd(aclList, aclName.ptr)) { 73 return 1; 74 } 75 } 76 } else if(!cx_strcasecmp(name, "VFS")) { 77 DIR_CHECK_ARGC(1); 78 location->config.vfs = cx_strdup_a(a, dir->args->value); 79 VfsType *vfs = vfs_get_type(cx_strcast(location->config.vfs)); 80 if(!vfs) { 81 log_ereport(LOG_MISCONFIG, "unknown VFS type %s", location->config.vfs.ptr); 82 } 83 } else if(!cx_strcasecmp(name, "Location")) { 84 WSLocation *sub_location = cfg_location_get(cfg, dir); 85 if(!sub_location) { 86 return 1; 87 } 88 location_list_add(location, sub_location); 89 } else if(!cx_strcasecmp(name, "Rewrite")) { 90 cxmutstr regex = (cxmutstr){NULL, 0}; 91 cxmutstr url = (cxmutstr){NULL, 0}; 92 if(argc == 1) { 93 // no custom rewrite regex 94 url = dir->args->value; 95 } else if(argc == 2) { 96 // regex, url 97 regex = dir->args->value; 98 url = dir->args->next->value; 99 } else { 100 log_ereport(LOG_FAILURE, "rewrite [regex] <url>: illegal argc %d", argc); 101 return 1; 102 } 103 104 RewriteRule *rule = rewrite_rule_create(cfg, regex, url); 105 if(!rule) { 106 return 1; 107 } 108 109 if(!location->rewrite) { 110 location->rewrite = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); 111 if(!location->rewrite) { 112 return 1; 113 } 114 } 115 116 if(cxListAdd(location->rewrite, rule)) { 117 return 1; 118 } 119 } 120 121 return 0; 122 } 123 124 125 static void location_free_regex(WSLocation *loc) { 126 regfree(&loc->regex); 127 } 128 129 WSLocation* cfg_location_get(ServerConfiguration *cfg, ConfigNode *obj) { 130 const CxAllocator *a = cfg->a; 131 132 WSLocationMatch match = WS_LOCATION_MATCH_EXACT; 133 cxmutstr match_str; 134 135 int regex_flags = REG_EXTENDED; 136 int argc = serverconfig_directive_count_args(obj); 137 if(argc == 2) { 138 // arg0: match type 139 cxmutstr type_str = obj->args->value; 140 if(!cx_strcmp(type_str, "=")) { 141 // noop 142 } else if(!cx_strcmp(type_str, "^~")) { 143 match = WS_LOCATION_MATCH_PREFIX; 144 } else if(!cx_strcmp(type_str, "~")) { 145 match = WS_LOCATION_MATCH_CS_REGEX; 146 } else if(!cx_strcmp(type_str, "~*")) { 147 match = WS_LOCATION_MATCH_CI_REGEX; 148 regex_flags |= REG_ICASE; 149 } else { 150 log_ereport(LOG_FAILURE, "Location: unknown operator %.*s", (int)type_str.length, type_str.ptr); 151 return NULL; 152 } 153 154 // arg1: match_string 155 match_str = obj->args->next->value; 156 } else if(argc == 1) { 157 // arg0: match_string 158 match_str = obj->args->value; 159 } else if(argc > 2) { 160 log_ereport(LOG_FAILURE, "Location directive must have 0-2 args"); 161 return NULL; 162 } 163 164 WSLocation *location = cxZalloc(a, sizeof(WSLocation)); 165 location->match = match; 166 location->match_string = cx_strdup_a((a), match_str); 167 168 if(match == WS_LOCATION_MATCH_CS_REGEX || match == WS_LOCATION_MATCH_CI_REGEX) { 169 if(regcomp(&location->regex, match_str.ptr, regex_flags)) { 170 log_ereport(LOG_FAILURE, "Location: cannot compile regex pattern %s", match_str.ptr); 171 return NULL; 172 } 173 ScfgDestr destr; 174 destr.destr = (cx_destructor_func)location_free_regex; 175 destr.data = location; 176 if(cxListAdd(cfg->destr, &destr)) { 177 return NULL; 178 } 179 } 180 181 ConfigNode *dir = obj->children_begin; 182 while(dir) { 183 if(dir->type == CONFIG_NODE_OBJECT || dir->type == CONFIG_NODE_DIRECTIVE) { 184 if(add_location_config(cfg, location, dir)) { 185 log_ereport(LOG_FAILURE, "Location %s: abort", match_str.ptr); 186 return NULL; 187 } 188 } 189 dir = dir->next; 190 } 191 192 193 194 return location; 195 } 196 197 int location_apply_config(WSLocationConfig *target, WSLocation *loc) { 198 WSLocationConfig *src = &loc->config; 199 if(src->set_dirindex) { 200 target->set_dirindex = TRUE; 201 target->dirindex = src->dirindex; 202 } 203 if(src->set_forcetls) { 204 target->set_forcetls = TRUE; 205 target->forcetls = src->forcetls; 206 } 207 if(src->docroot.ptr) { 208 target->docroot = src->docroot; 209 } 210 if(src->name.ptr) { 211 target->name = src->name; 212 } 213 if(src->dav) { 214 target->dav = src->dav; 215 } 216 // TODO: ... 217 218 return 0; 219 } 220 221 int location_match(WSLocation *loc, cxstring uri, regmatch_t *match) { 222 if(loc->match == WS_LOCATION_MATCH_EXACT) { 223 return !cx_strcmp(loc->match_string, uri); 224 } else if(loc->match == WS_LOCATION_MATCH_PREFIX) { 225 return cx_strprefix(uri, loc->match_string); 226 } else { 227 return regexec(&loc->regex, uri.ptr, WS_LOCATION_NMATCH, match, 0) == 0; 228 } 229 return 0; 230 } 231 232 WSLocationConfig* location_match_and_get_config(pool_handle_t *pool, Request *rq, cxstring uri, WSLocation *loc) { 233 WSLocationConfig *config = pool_malloc(pool, sizeof(WSLocationConfig)); 234 if(!config) { 235 return NULL; 236 } 237 ZERO(config, sizeof(WSLocationConfig)); 238 for(int i=0;i<WS_LOCATION_NMATCH;i++) { 239 config->match[i].rm_so = -1; 240 config->match[i].rm_eo = -1; 241 } 242 243 const CxAllocator *a = pool_allocator(pool); 244 245 while(loc) { 246 if(location_match(loc, uri, config->match)) { 247 // apply rewrite rules 248 CxIterator iter = cxListIterator(loc->rewrite); 249 cx_foreach(RewriteRule *, rule, iter) { 250 char *new_url = NULL; 251 if(!rewrite_url(rule, config->match, WS_LOCATION_NMATCH, a, uri.ptr, &new_url)) { 252 pblock_removekey(pb_key_uri, rq->reqpb); 253 if(pblock_kvinsert(pb_key_uri, new_url, strlen(new_url), rq->reqpb) == NULL) { 254 return NULL; 255 } 256 } 257 } 258 259 if(location_apply_config(config, loc)) { 260 return NULL; 261 } 262 } 263 loc = loc->next; 264 } 265 266 267 return config; 268 } 269 270 WSLocationConfig* cfg_location_match(Session *sn, Request *rq) { 271 NSAPIRequest *req = (NSAPIRequest*)rq; 272 WSLocation *loc = req->vs->locations_begin; 273 char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb); 274 return location_match_and_get_config(sn->pool, rq, cx_str(uri), loc); 275 } 276