/ libpkg / yuarel.c
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  }