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