yuarel.c
1 /** 2 * Copyright (C) 2016,2017 Jack Engqvist Johansson 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 */ 22 #include <stdlib.h> 23 #include <stdio.h> 24 #include <string.h> 25 #include "yuarel.h" 26 27 /** 28 * Parse a non null terminated string into an integer. 29 * 30 * str: the string containing the number. 31 * len: Number of characters to parse. 32 */ 33 static inline int 34 natoi(const char *str, size_t len) 35 { 36 size_t i, r = 0; 37 for (i = 0; i < len; i++) { 38 r *= 10; 39 r += str[i] - '0'; 40 } 41 42 return r; 43 } 44 45 /** 46 * Check if a URL is relative (no scheme and hostname). 47 * 48 * url: the string containing the URL to check. 49 * 50 * Returns 1 if relative, otherwise 0. 51 */ 52 static inline int 53 is_relative(const char *url) 54 { 55 return (*url == '/') ? 1 : 0; 56 } 57 58 /** 59 * Parse the scheme of a URL by inserting a null terminator after the scheme. 60 * 61 * str: the string containing the URL to parse. Will be modified. 62 * 63 * Returns a pointer to the hostname on success, otherwise NULL. 64 */ 65 static inline char * 66 parse_scheme(char *str) 67 { 68 char *s; 69 70 /* If not found or first in string, return error */ 71 s = strchr(str, ':'); 72 if (s == NULL || s == str) { 73 return NULL; 74 } 75 76 /* If not followed by two slashes, return error */ 77 if (s[1] == '\0' || s[1] != '/' || s[2] == '\0' || s[2] != '/') { 78 return NULL; 79 } 80 81 *s = '\0'; // Replace ':' with NULL 82 83 return s + 3; 84 } 85 86 /** 87 * Find a character in a string, replace it with '\0' and return the next 88 * character in the string. 89 * 90 * str: the string to search in. 91 * find: the character to search for. 92 * 93 * Returns a pointer to the character after the one to search for. If not 94 * found, NULL is returned. 95 */ 96 static inline char * 97 find_and_terminate(char *str, char find) 98 { 99 str = strchr(str, find); 100 if (NULL == str) { 101 return NULL; 102 } 103 104 *str = '\0'; 105 return str + 1; 106 } 107 108 /* Yes, the following functions could be implemented as preprocessor macros 109 instead of inline functions, but I think that this approach will be more 110 clean in this case. */ 111 static inline char * 112 find_fragment(char *str) 113 { 114 return find_and_terminate(str, '#'); 115 } 116 117 static inline char * 118 find_query(char *str) 119 { 120 return find_and_terminate(str, '?'); 121 } 122 123 static inline char * 124 find_path(char *str) 125 { 126 return find_and_terminate(str, '/'); 127 } 128 129 /** 130 * Parse a URL string to a struct. 131 * 132 * url: pointer to the struct where to store the parsed URL parts. 133 * u: the string containing the URL to be parsed. 134 * 135 * Returns 0 on success, otherwise -1. 136 */ 137 int 138 yuarel_parse(struct yuarel *url, char *u) 139 { 140 if (NULL == url || NULL == u) { 141 return -1; 142 } 143 144 memset(url, 0, sizeof (struct yuarel)); 145 146 /* (Fragment) */ 147 url->fragment = find_fragment(u); 148 149 /* (Query) */ 150 url->query = find_query(u); 151 152 /* Relative URL? Parse scheme and hostname */ 153 if (!is_relative(u)) { 154 /* Scheme */ 155 url->scheme = u; 156 u = parse_scheme(u); 157 if (u == NULL) { 158 return -1; 159 } 160 161 /* Host */ 162 if ('\0' == *u) { 163 return -1; 164 } 165 url->host = u; 166 167 /* (Path) */ 168 url->path = find_path(u); 169 170 /* (Credentials) */ 171 u = strchr(url->host, '@'); 172 if (NULL != u) { 173 /* Missing credentials? */ 174 if (u == url->host) { 175 return -1; 176 } 177 178 url->username = url->host; 179 url->host = u + 1; 180 *u = '\0'; 181 182 u = strchr(url->username, ':'); 183 if (NULL != u) { 184 url->password = u + 1; 185 *u = '\0'; 186 } 187 } 188 189 /* Missing hostname? */ 190 if ('\0' == *url->host) { 191 return -1; 192 } 193 194 /* (Port) */ 195 u = strchr(url->host, ':'); 196 if (NULL != u && (NULL == url->path || u < url->path)) { 197 *(u++) = '\0'; 198 if ('\0' == *u) { 199 return -1; 200 } 201 202 if (url->path) { 203 url->port = natoi(u, url->path - u - 1); 204 } else { 205 url->port = atoi(u); 206 } 207 } 208 209 /* Missing hostname? */ 210 if ('\0' == *url->host) { 211 return -1; 212 } 213 } else { 214 /* (Path) */ 215 url->path = find_path(u); 216 } 217 218 return 0; 219 } 220 221 /** 222 * Split a path into several strings. 223 * 224 * No data is copied, the slashed are used as null terminators and then 225 * pointers to each path part will be stored in **parts. Double slashes will be 226 * treated as one. 227 * 228 * path: the path to split. 229 * parts: a pointer to an array of (char *) where to store the result. 230 * max_parts: max number of parts to parse. 231 */ 232 int 233 yuarel_split_path(char *path, char **parts, int max_parts) 234 { 235 int i = 0; 236 237 if (NULL == path || '\0' == *path) { 238 return -1; 239 } 240 241 do { 242 /* Forward to after slashes */ 243 while (*path == '/') path++; 244 245 if ('\0' == *path) { 246 break; 247 } 248 249 parts[i++] = path; 250 251 path = strchr(path, '/'); 252 if (NULL == path) { 253 break; 254 } 255 256 *(path++) = '\0'; 257 } while (i < max_parts); 258 259 return i; 260 } 261 262 int 263 yuarel_parse_query(char *query, char delimiter, struct yuarel_param *params, int max_params) 264 { 265 int i = 0; 266 267 if (NULL == query || '\0' == *query) { 268 return -1; 269 } 270 271 params[i++].key = query; 272 while (i < max_params && NULL != (query = strchr(query, delimiter))) { 273 *query = '\0'; 274 params[i].key = ++query; 275 params[i].val = NULL; 276 277 /* Go back and split previous param */ 278 if (i > 0) { 279 if ((params[i - 1].val = strchr(params[i - 1].key, '=')) != NULL) { 280 *(params[i - 1].val)++ = '\0'; 281 } 282 } 283 i++; 284 } 285 286 /* Go back and split last param */ 287 if ((params[i - 1].val = strchr(params[i - 1].key, '=')) != NULL) { 288 *(params[i - 1].val)++ = '\0'; 289 } 290 291 return i; 292 }