// uri.h - Minimal URI (de)serializer // Ethan Werner // // URI Parser and Serializer // // The uri_t structure is intended to be used as a black box. The primary use // of the header as a whole is to enable the efficient creation of // related URIs. // // Usage Notes // The internally held strings should remain opaque and only interacted with // through the given functions // // Any string interacting with a uri_t is copied upon both entry and // exit, thus, all strings except those must be (de)allocated by the user. // // Thread safety: Not thread-safe. External synchronization required. // Limitations: uri_parse only accepts absolute URIs with a scheme. Relative // URIs (e.g., "/path/to/resource") will cause uri_parse to return NULL. #ifndef URI_H #define URI_H typedef enum { BUILD, SCHEME, USERINFO, HOST, PORT, PATH, QUERY, FRAGMENT, URI_END } uri_element_t; typedef struct uri_t { char *data[URI_END]; } uri_t; uri_t *uri_init(void); void uri_free(uri_t *); void uri_set(uri_t *, uri_element_t, char const *); char *uri_get(uri_t *); uri_t *uri_parse(char const *); char *uri_remove(uri_t *, uri_element_t); void uri_print(uri_t const *); void uri_print_elements(uri_t const *); void uri_build(uri_t *); #endif // URI_H #ifdef URI_IMPLEMENTATION #include #include #include #include static void uri_copy_string(char **ptr, char const *source); uri_t *uri_init(void) { uri_t *u = calloc(1, sizeof(uri_t)); if (!u) return NULL; return u; } void uri_free(uri_t *u) { if (u == NULL) return; for(int i = 0; i < URI_END; i++) { if(u->data[i]) free(u->data[i]); } free(u); } static void uri_copy_string(char **ptr, char const *source) { if (!ptr) return; if (*ptr) free(*ptr); if(source) { size_t n = strlen(source); if (n == SIZE_MAX) return; *ptr = malloc(n + 1); if (!*ptr) return; memcpy(*ptr, source, n); (*ptr)[n] = '\0'; } else { *ptr = NULL; } } void uri_set(uri_t *u, uri_element_t e, char const *data) { if (u == NULL) return; if ((int)e < 0 || (int)e >= URI_END) return; uri_copy_string(&u->data[e], data); } char *uri_get(uri_t *u) { if (u == NULL) return NULL; uri_build(u); char *c = NULL; uri_copy_string(&c, u->data[BUILD]); return c; } char *uri_remove(uri_t *u, uri_element_t e) { if (u == NULL) return NULL; if ((int)e < 0 || (int)e >= URI_END) return NULL; char *data = u->data[e]; u->data[e] = NULL; return data; } void uri_build(uri_t *u) { if (u == NULL) return; size_t n = 0; if(u->data[BUILD]) free(u->data[BUILD]); u->data[BUILD] = NULL; // Length of built string { size_t s; if(u->data[SCHEME]) { s = strlen(u->data[SCHEME]) + 1; if (n > SIZE_MAX - s) { u->data[BUILD] = NULL; return; } n += s; } if(u->data[USERINFO]) { s = strlen(u->data[USERINFO]) + 1; if (n > SIZE_MAX - s) { u->data[BUILD] = NULL; return; } n += s; } if(u->data[HOST]) { s = strlen(u->data[HOST]) + 2; if (n > SIZE_MAX - s) { u->data[BUILD] = NULL; return; } n += s; } if(u->data[PORT]) { s = strlen(u->data[PORT]) + 1; if (n > SIZE_MAX - s) { u->data[BUILD] = NULL; return; } n += s; } if(u->data[PATH]) { s = strlen(u->data[PATH]); if (n > SIZE_MAX - s) { u->data[BUILD] = NULL; return; } n += s; } if(u->data[QUERY]) { s = strlen(u->data[QUERY]) + 1; if (n > SIZE_MAX - s) { u->data[BUILD] = NULL; return; } n += s; } if(u->data[FRAGMENT]) { s = strlen(u->data[FRAGMENT]) + 1; if (n > SIZE_MAX - s) { u->data[BUILD] = NULL; return; } n += s; } } if (n == SIZE_MAX) return; char *build = malloc(n + 1); if (!build) return; build[0] = '\0'; size_t pos = 0; // Copy strings to BUILD if(u->data[SCHEME]) { size_t len = strlen(u->data[SCHEME]); memcpy(build + pos, u->data[SCHEME], len); pos += len; build[pos++] = ':'; } if(u->data[HOST]) { memcpy(build + pos, "//", 2); pos += 2; if(u->data[USERINFO]) { size_t len = strlen(u->data[USERINFO]); memcpy(build + pos, u->data[USERINFO], len); pos += len; build[pos++] = '@'; } size_t len = strlen(u->data[HOST]); memcpy(build + pos, u->data[HOST], len); pos += len; if(u->data[PORT]) { build[pos++] = ':'; len = strlen(u->data[PORT]); memcpy(build + pos, u->data[PORT], len); pos += len; } } if(u->data[PATH]) { size_t len = strlen(u->data[PATH]); memcpy(build + pos, u->data[PATH], len); pos += len; } if(u->data[QUERY]) { build[pos++] = '?'; size_t len = strlen(u->data[QUERY]); memcpy(build + pos, u->data[QUERY], len); pos += len; } if(u->data[FRAGMENT]) { build[pos++] = '#'; size_t len = strlen(u->data[FRAGMENT]); memcpy(build + pos, u->data[FRAGMENT], len); pos += len; } build[pos] = '\0'; u->data[BUILD] = build; } static int uri_set_range(uri_t *u, uri_element_t e, char const *from, char const *to) { if (u == NULL || from == NULL || to == NULL) return 0; if ((int)e < 0 || (int)e >= URI_END) return 0; if (to <= from) return 0; size_t len = (size_t)(to - from); if (len == SIZE_MAX) return 0; char *s = malloc(len + 1); if (!s) return 0; s[len] = 0; memcpy(s, from, len); if(u->data[e]) free(u->data[e]); u->data[e] = s; return 1; } uri_t *uri_parse(char const *uri_s) { if (uri_s == NULL) return NULL; uri_t *uri = calloc(1, sizeof(uri_t)); if (!uri) return NULL; char const *current = uri_s; char const *working; // SCHEME working = strchr(current, ':'); if(!working) { uri_free(uri); return NULL; } if (!uri_set_range(uri, SCHEME, current, working)) { uri_free(uri); return NULL; } current = ++working; // AUTHORITY if(working[0] == '/' && working[1] == '/') { current = working += 2; char const *at = strchr(working, '@'); if (at) { if (!uri_set_range(uri, USERINFO, current, at)) { uri_free(uri); return NULL; } current = at + 1; char const *host_end = strpbrk(current, ":/"); if (!host_end) host_end = current + strlen(current); if (!uri_set_range(uri, HOST, current, host_end)) { uri_free(uri); return NULL; } working = host_end; } else { while(*working && *working != ':' && *working != '/') working++; if (!uri_set_range(uri, HOST, current, working)) { uri_free(uri); return NULL; } } if(!*working) { uri_build(uri); return uri; } // PORT if(*working == ':') { current = ++working; while(*working && *working != '/') working++; if (!uri_set_range(uri, PORT, current, working)) { uri_free(uri); return NULL; } } if(!*working) { uri_build(uri); return uri; } current = working; while(*working && *working != '?' && *working != '#') working++; } else { // No authority section - scan for query/fragment delimiters while(*working && *working != '?' && *working != '#') working++; } // PATH if (!uri_set_range(uri, PATH, current, working)) { uri_free(uri); return NULL; } if(!*working) { uri_build(uri); return uri; } current = working; // QUERY if(*working == '?') { char const *query_start = ++working; while(*working && *working != '#') working++; if (!uri_set_range(uri, QUERY, query_start, working)) { uri_free(uri); return NULL; } current = working; } // FRAGMENT if(*working == '#') { char const *frag_start = ++working; while(*working) working++; if (!uri_set_range(uri, FRAGMENT, frag_start, working)) { uri_free(uri); return NULL; } } uri_build(uri); return uri; } void uri_print(uri_t const *u) { if (u == NULL) return; if(u->data[BUILD]) fputs(u->data[BUILD], stdout); } void uri_print_elements(uri_t const *u) { if (u == NULL) return; for(int i = 0; i < URI_END; i++) { printf("%d", i); if(u->data[i]) { printf(" - %s", u->data[i]); } putchar('\n'); } } #endif // URI_IMPLEMENTATION