1 /* 2 * Copyright 2001-2026 The OpenSSL Project Authors. All Rights Reserved. 3 * 4 * Licensed under the Apache License 2.0 (the "License"). You may not use 5 * this file except in compliance with the License. You can obtain a copy 6 * in the file LICENSE in the source distribution or at 7 * https://www.openssl.org/source/license.html 8 */ 9 10 #include <stdio.h> /* for sscanf() */ 11 #include <string.h> 12 #include <openssl/http.h> 13 #include <openssl/httperr.h> 14 #include <openssl/bio.h> /* for BIO_snprintf() */ 15 #include <openssl/err.h> 16 #include "internal/cryptlib.h" /* for ossl_assert() */ 17 #ifndef OPENSSL_NO_SOCK 18 #include "internal/bio_addr.h" /* for NI_MAXHOST */ 19 #endif 20 #ifndef NI_MAXHOST 21 #define NI_MAXHOST 255 22 #endif 23 #include "crypto/ctype.h" /* for ossl_isspace() */ 24 25 static void init_pstring(char **pstr) 26 { 27 if (pstr != NULL) { 28 *pstr = NULL; 29 } 30 } 31 32 static void init_pint(int *pint) 33 { 34 if (pint != NULL) { 35 *pint = 0; 36 } 37 } 38 39 static int copy_substring(char **dest, const char *start, const char *end) 40 { 41 return dest == NULL 42 || (*dest = OPENSSL_strndup(start, end - start)) != NULL; 43 } 44 45 static void free_pstring(char **pstr) 46 { 47 if (pstr != NULL) { 48 OPENSSL_free(*pstr); 49 *pstr = NULL; 50 } 51 } 52 53 int OSSL_parse_url(const char *url, char **pscheme, char **puser, char **phost, 54 char **pport, int *pport_num, 55 char **ppath, char **pquery, char **pfrag) 56 { 57 const char *p, *tmp; 58 const char *authority_end; 59 const char *scheme, *scheme_end; 60 const char *user, *user_end; 61 const char *host, *host_end; 62 const char *port, *port_end; 63 unsigned int portnum = 0; 64 const char *path, *path_end; 65 const char *query, *query_end; 66 const char *frag, *frag_end; 67 68 init_pstring(pscheme); 69 init_pstring(puser); 70 init_pstring(phost); 71 init_pstring(pport); 72 init_pint(pport_num); 73 init_pstring(ppath); 74 init_pstring(pfrag); 75 init_pstring(pquery); 76 77 if (url == NULL) { 78 ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); 79 return 0; 80 } 81 82 /* check for optional prefix "<scheme>://" */ 83 scheme = scheme_end = url; 84 p = strstr(url, "://"); 85 if (p == NULL) { 86 p = url; 87 } else { 88 scheme_end = p; 89 if (scheme_end == scheme) 90 goto parse_err; 91 p += strlen("://"); 92 } 93 94 /* parse optional "userinfo@" */ 95 user = user_end = host = p; 96 authority_end = strpbrk(p, "/?#"); 97 if (authority_end == NULL) 98 authority_end = p + strlen(p); 99 host = memchr(p, '@', authority_end - p); 100 if (host != NULL) 101 user_end = host++; 102 else 103 host = p; 104 105 /* parse hostname/address as far as needed here */ 106 if (host[0] == '[') { 107 /* IPv6 literal, which may include ':' */ 108 host_end = strchr(host + 1, ']'); 109 if (host_end == NULL) 110 goto parse_err; 111 p = ++host_end; 112 } else { 113 /* look for start of optional port, path, query, or fragment */ 114 host_end = strpbrk(host, ":/?#"); 115 if (host_end == NULL) /* the remaining string is just the hostname */ 116 host_end = host + strlen(host); 117 p = host_end; 118 } 119 120 /* parse optional port specification starting with ':' */ 121 port = "0"; /* default */ 122 if (*p == ':') 123 port = ++p; 124 /* remaining port spec handling is also done for the default values */ 125 /* make sure a decimal port number is given */ 126 if (sscanf(port, "%u", &portnum) <= 0 || portnum > 65535) { 127 ERR_raise_data(ERR_LIB_HTTP, HTTP_R_INVALID_PORT_NUMBER, "%s", port); 128 goto err; 129 } 130 for (port_end = port; '0' <= *port_end && *port_end <= '9'; port_end++) 131 ; 132 if (port == p) /* port was given explicitly */ 133 p += port_end - port; 134 135 /* check for optional path starting with '/' or '?'. Else must start '#' */ 136 path = p; 137 if (*path != '\0' && *path != '/' && *path != '?' && *path != '#') { 138 ERR_raise(ERR_LIB_HTTP, HTTP_R_INVALID_URL_PATH); 139 goto parse_err; 140 } 141 path_end = query = query_end = frag = frag_end = path + strlen(path); 142 143 /* parse optional "?query" */ 144 tmp = strchr(p, '?'); 145 if (tmp != NULL) { 146 p = tmp; 147 if (pquery != NULL) { 148 path_end = p; 149 query = p + 1; 150 } 151 } 152 153 /* parse optional "#fragment" */ 154 tmp = strchr(p, '#'); 155 if (tmp != NULL) { 156 if (query == path_end) /* we did not record a query component */ 157 path_end = tmp; 158 query_end = tmp; 159 frag = tmp + 1; 160 } 161 162 if (!copy_substring(pscheme, scheme, scheme_end) 163 || !copy_substring(phost, host, host_end) 164 || !copy_substring(pport, port, port_end) 165 || !copy_substring(puser, user, user_end) 166 || !copy_substring(pquery, query, query_end) 167 || !copy_substring(pfrag, frag, frag_end)) 168 goto err; 169 if (pport_num != NULL) 170 *pport_num = (int)portnum; 171 if (*path == '/') { 172 if (!copy_substring(ppath, path, path_end)) 173 goto err; 174 } else if (ppath != NULL) { /* must prepend '/' */ 175 size_t buflen = 1 + path_end - path + 1; 176 177 if ((*ppath = OPENSSL_malloc(buflen)) == NULL) 178 goto err; 179 BIO_snprintf(*ppath, buflen, "/%s", path); 180 } 181 return 1; 182 183 parse_err: 184 ERR_raise(ERR_LIB_HTTP, HTTP_R_ERROR_PARSING_URL); 185 186 err: 187 free_pstring(pscheme); 188 free_pstring(puser); 189 free_pstring(phost); 190 free_pstring(pport); 191 free_pstring(ppath); 192 free_pstring(pquery); 193 free_pstring(pfrag); 194 return 0; 195 } 196 197 #ifndef OPENSSL_NO_HTTP 198 199 int OSSL_HTTP_parse_url(const char *url, int *pssl, char **puser, char **phost, 200 char **pport, int *pport_num, 201 char **ppath, char **pquery, char **pfrag) 202 { 203 char *scheme, *port; 204 int ssl = 0, portnum; 205 206 init_pstring(pport); 207 if (pssl != NULL) 208 *pssl = 0; 209 if (!OSSL_parse_url(url, &scheme, puser, phost, &port, pport_num, 210 ppath, pquery, pfrag)) 211 return 0; 212 213 /* check for optional HTTP scheme "http[s]" */ 214 if (strcmp(scheme, OSSL_HTTPS_NAME) == 0) { 215 ssl = 1; 216 if (pssl != NULL) 217 *pssl = ssl; 218 } else if (*scheme != '\0' && strcmp(scheme, OSSL_HTTP_NAME) != 0) { 219 ERR_raise(ERR_LIB_HTTP, HTTP_R_INVALID_URL_SCHEME); 220 OPENSSL_free(scheme); 221 OPENSSL_free(port); 222 goto err; 223 } 224 OPENSSL_free(scheme); 225 226 if (strcmp(port, "0") == 0) { 227 /* set default port */ 228 OPENSSL_free(port); 229 port = ssl ? OSSL_HTTPS_PORT : OSSL_HTTP_PORT; 230 if (!ossl_assert(sscanf(port, "%d", &portnum) == 1)) 231 goto err; 232 if (pport_num != NULL) 233 *pport_num = portnum; 234 if (pport != NULL) { 235 *pport = OPENSSL_strdup(port); 236 if (*pport == NULL) 237 goto err; 238 } 239 } else { 240 if (pport != NULL) 241 *pport = port; 242 else 243 OPENSSL_free(port); 244 } 245 return 1; 246 247 err: 248 free_pstring(puser); 249 free_pstring(phost); 250 free_pstring(ppath); 251 free_pstring(pquery); 252 free_pstring(pfrag); 253 return 0; 254 } 255 256 /* Respect no_proxy, taking default value from environment variable(s) */ 257 static int use_proxy(const char *no_proxy, const char *server) 258 { 259 size_t sl; 260 const char *found = NULL; 261 char host[NI_MAXHOST]; 262 263 if (!ossl_assert(server != NULL)) 264 return 0; 265 sl = strlen(server); 266 if (sl >= 2 && sl < sizeof(host) + 2 && server[0] == '[' && server[sl - 1] == ']') { 267 /* strip leading '[' and trailing ']' from escaped IPv6 address */ 268 sl -= 2; 269 strncpy(host, server + 1, sl); 270 host[sl] = '\0'; 271 server = host; 272 } 273 274 if (sl == 0) 275 return 1; 276 277 /* 278 * using environment variable names, both lowercase and uppercase variants, 279 * compatible with other HTTP client implementations like wget, curl and git 280 */ 281 if (no_proxy == NULL) 282 no_proxy = ossl_safe_getenv("no_proxy"); 283 if (no_proxy == NULL) 284 no_proxy = ossl_safe_getenv(OPENSSL_NO_PROXY); 285 286 if (no_proxy != NULL) 287 found = strstr(no_proxy, server); 288 while (found != NULL 289 && ((found != no_proxy && !ossl_isspace(found[-1]) && found[-1] != ',') 290 || (found[sl] != '\0' && !ossl_isspace(found[sl]) && found[sl] != ','))) 291 found = strstr(found + 1, server); 292 return found == NULL; 293 } 294 295 /* Take default value from environment variable(s), respect no_proxy */ 296 const char *OSSL_HTTP_adapt_proxy(const char *proxy, const char *no_proxy, 297 const char *server, int use_ssl) 298 { 299 /* 300 * using environment variable names, both lowercase and uppercase variants, 301 * compatible with other HTTP client implementations like wget, curl and git 302 */ 303 if (proxy == NULL) 304 proxy = ossl_safe_getenv(use_ssl ? "https_proxy" : "http_proxy"); 305 if (proxy == NULL) 306 proxy = ossl_safe_getenv(use_ssl ? OPENSSL_HTTPS_PROXY : OPENSSL_HTTP_PROXY); 307 308 if (proxy == NULL || *proxy == '\0' || !use_proxy(no_proxy, server)) 309 return NULL; 310 return proxy; 311 } 312 313 #endif /* !defined(OPENSSL_NO_HTTP) */ 314