/* * Copyright (C) 2014, 2016 J. Ali Harlow * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include "razor.h" #include "razor-internal.h" #include "uri.h" static int valid_unicode(unsigned unicode) { /* * Within the U+0000..U+10FFFF range defined by RFC3629 * but not in the U+D800..U+DFFF range prohibited in UTF-8. */ return unicode < 0xD800 || (unicode >= 0xE000 && unicode < 0x110000); } char *razor_path_from_parsed_uri(const struct razor_uri *ru, struct razor_error **error) { int continuation_bytes = 0; char *path, *p, *s, *uri; unsigned char c; unsigned unicode; if (!ru->scheme) { uri = razor_uri_recompose(ru); razor_set_error(error, RAZOR_GENERAL_ERROR, RAZOR_GENERAL_ERROR_BAD_URI, uri, "URI does not include a scheme"); free(uri); return NULL; } if (strcmp(ru->scheme, "file")) { uri = razor_uri_recompose(ru); razor_set_error(error, RAZOR_GENERAL_ERROR, RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, uri, "Not a file URI"); free(uri); return NULL; } if (ru->host && *ru->host && strcmp(ru->host, "localhost") || ru->userinfo || ru->port) { uri = razor_uri_recompose(ru); razor_set_error(error, RAZOR_GENERAL_ERROR, RAZOR_GENERAL_ERROR_UNSUPPORTED_URI, uri, "URI refers to a non-local file"); free(uri); return NULL; } s = ru->path; #ifdef MSWIN_API /* * Under MS-Windows, a path of /c:/xxx maps to c:/xxx * Note that PathCreateFromUrl converts / to \ as well. */ if (s[0] == '/' && is_alpha(s[1]) && s[2] == ':' && s[3] == '/') s++; #endif p = path = malloc(strlen(s) + 1); while (*s) { if (*s >= 0x7F || *s < 0x20) break; else if (*s != '%') { if (continuation_bytes) break; else *p++ = *s++; } else { c = pchar_get_char(s); #ifdef MSWIN_API if (c == '/' || c == '\\') #else if (c == '/') #endif break; else if (!continuation_bytes) { if (c >= 0xF5 || c == 0xC0 || c == 0xC1) break; else if (c >= 0xF0) { unicode = c & 7; continuation_bytes = 3; } else if (c >= 0xE0) { unicode = c & 3; continuation_bytes = 2; } else if (c >= 0xC0) { unicode = c & 1; continuation_bytes = 1; } } else if ((c & 0xC0) != 0x80) break; else { unicode <<= 6; unicode |= (c & 0x3F); if (!--continuation_bytes && !valid_unicode(unicode)) break; } *p++ = c; s += 3; } } if (*s || continuation_bytes) { uri = razor_uri_recompose(ru); razor_set_error(error, RAZOR_GENERAL_ERROR, RAZOR_GENERAL_ERROR_BAD_URI, uri, "Illegal character in file URI path"); free(uri); free(path); return NULL; } *p++ = '\0'; return realloc(path, p - path); } RAZOR_EXPORT char *razor_path_from_uri(const char *uri, struct razor_error **error) { struct razor_uri ru; char *path; if (razor_uri_parse(&ru, uri, error)) return NULL; path = razor_path_from_parsed_uri(&ru, error); razor_uri_destroy(&ru); return path; } RAZOR_EXPORT char *razor_path_to_uri(const char *path) { char *uri, *s; const char *p; int check_dotdot, len; struct razor_uri ru; uri = malloc(5 + (4 - 3) + 4 + 3 * strlen(path) + 1); strcpy(uri, "file:"); s = uri + 5; #ifdef MSWIN_API check_dotdot = path[0] != '/' && path[0] != '\\'; #else check_dotdot = path[0] != '/'; #endif p = path; #ifdef MSWIN_API /* * Under MS-Windows, c:/xxx maps to a path of /c:/xxx * Relative paths that include a drive letter (eg., c:xxx) * can't be handled directly and have to be converted * to absolute form. */ if (is_alpha(p[0]) && p[1] == ':') { if (p[2] == '/' || p[2] == '\\') { *s++ = '/'; *s++ = p[0]; *s++ = ':'; *s++ = '/'; p += 3; /* * We need to take care that ".." segments don't remove * the drive letter (eg., c:/../xxx -> file:/c:/../xxx * which normalizes to file:/xxx). */ check_dotdot = 2; } else { s = razor_abspath(p); uri = razor_path_to_uri(s); free(s); return uri; } } #endif /* * Relative paths are complicated. URIs can't have dot segments * so these will be removed during normalization. That often does * the right thing, but where a relative path traverses up the * tree then the result is a URI that points to somewhere quite * different to path: eg., file:../dir normalizes to file:dir * We solve this by inserting a sentinel segment at the beginning. * If the segment is still present after normalization, then it * can just be removed. If it is missing, then we need to create * an absolute path and redo the conversion. */ if (check_dotdot) { *s++ = '%'; *s++ = '2'; *s++ = 'F'; *s++ = '/'; } while(*p) { if (*p == '/' || is_unreserved(*p) || is_sub_delim(*p) || *p == ':' || *p == '@') *s++ = *p; #ifdef MSWIN_API else if (*p == '\\') *s++ = '/'; #endif else { *s++ = '%'; *s++ = "0123456789ABCDEF"[(*(unsigned char *)p)/16]; *s++ = "0123456789ABCDEF"[(*(unsigned char *)p)%16]; } p++; } *s++ = '\0'; if (razor_uri_parse(&ru, uri, NULL) < 0) { free(uri); return NULL; } free(uri); razor_uri_normalize(&ru); uri = razor_uri_recompose(&ru); razor_uri_destroy(&ru); if (check_dotdot == 2) { s = strdup("file:/x:/%2F/"); s[6] = path[0]; if (str_has_prefix(uri, s)) { free(s); memmove(uri + 5 + 3, uri + 9 + 3, strlen(uri + 9 + 3) + 1); uri = realloc(uri, strlen(uri) + 1); } else { free(s); free(uri); s = razor_abspath(path); uri = razor_path_to_uri(s); free(s); } } else if (check_dotdot) { if (str_has_prefix(uri, "file:%2F/")) { memmove(uri + 5, uri + 9, strlen(uri + 9) + 1); uri = realloc(uri, strlen(uri) + 1); } else { free(uri); s = razor_abspath(path); uri = razor_path_to_uri(s); free(s); } } return uri; } RAZOR_EXPORT char * razor_path_relative_to_uri(const char *uri, const char *path, struct razor_error **error) { char *rel_uri, *result; /* Strictly wrong if uri isn't a file URI, but probably okay */ rel_uri = razor_path_to_uri(path); result = razor_resolve_uri_root(uri, rel_uri + 5, 1, error); free(rel_uri); return result; }