Annotation of src/usr.bin/ftp/url.c, Revision 1.1
1.1 ! kmos 1: /*
! 2: * Copyright (c) 2017 Sunil Nimmagadda <sunil@openbsd.org>
! 3: *
! 4: * Permission to use, copy, modify, and distribute this software for any
! 5: * purpose with or without fee is hereby granted, provided that the above
! 6: * copyright notice and this permission notice appear in all copies.
! 7: *
! 8: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 9: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 10: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 11: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 12: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 13: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 14: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
! 15: */
! 16:
! 17: /*-
! 18: * Copyright (c) 1997 The NetBSD Foundation, Inc.
! 19: * All rights reserved.
! 20: *
! 21: * This code is derived from software contributed to The NetBSD Foundation
! 22: * by Jason Thorpe and Luke Mewburn.
! 23: *
! 24: * Redistribution and use in source and binary forms, with or without
! 25: * modification, are permitted provided that the following conditions
! 26: * are met:
! 27: * 1. Redistributions of source code must retain the above copyright
! 28: * notice, this list of conditions and the following disclaimer.
! 29: * 2. Redistributions in binary form must reproduce the above copyright
! 30: * notice, this list of conditions and the following disclaimer in the
! 31: * documentation and/or other materials provided with the distribution.
! 32: *
! 33: * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
! 34: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
! 35: * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
! 36: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
! 37: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
! 38: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
! 39: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
! 40: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
! 41: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
! 42: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
! 43: * POSSIBILITY OF SUCH DAMAGE.
! 44: */
! 45: #include <sys/types.h>
! 46:
! 47: #include <netinet/in.h>
! 48: #include <resolv.h>
! 49:
! 50: #include <ctype.h>
! 51: #include <err.h>
! 52: #include <stdio.h>
! 53: #include <stdlib.h>
! 54: #include <string.h>
! 55: #include <strings.h>
! 56:
! 57: #include "ftp.h"
! 58: #include "xmalloc.h"
! 59:
! 60: #define BASICAUTH_LEN 1024
! 61:
! 62: static void authority_parse(const char *, char **, char **, char **);
! 63: static int ipv6_parse(const char *, char **, char **);
! 64: static int unsafe_char(const char *);
! 65:
! 66: #ifndef NOSSL
! 67: const char *scheme_str[] = { "http:", "ftp:", "file:", "https:" };
! 68: const char *port_str[] = { "80", "21", NULL, "443" };
! 69: #else
! 70: const char *scheme_str[] = { "http:", "ftp:", "file:" };
! 71: const char *port_str[] = { "80", "21", NULL };
! 72: #endif
! 73:
! 74: int
! 75: scheme_lookup(const char *str)
! 76: {
! 77: size_t i;
! 78:
! 79: for (i = 0; i < nitems(scheme_str); i++)
! 80: if (strncasecmp(str, scheme_str[i], strlen(scheme_str[i])) == 0)
! 81: return i;
! 82:
! 83: return -1;
! 84: }
! 85:
! 86: static int
! 87: ipv6_parse(const char *str, char **host, char **port)
! 88: {
! 89: char *p;
! 90:
! 91: if ((p = strchr(str, ']')) == NULL) {
! 92: warnx("%s: invalid IPv6 address: %s", __func__, str);
! 93: return 1;
! 94: }
! 95:
! 96: *p++ = '\0';
! 97: if (strlen(str + 1) > 0)
! 98: *host = xstrdup(str + 1);
! 99:
! 100: if (*p == '\0')
! 101: return 0;
! 102:
! 103: if (*p++ != ':') {
! 104: warnx("%s: invalid port: %s", __func__, p);
! 105: free(*host);
! 106: return 1;
! 107: }
! 108:
! 109: if (strlen(p) > 0)
! 110: *port = xstrdup(p);
! 111:
! 112: return 0;
! 113: }
! 114:
! 115: static void
! 116: authority_parse(const char *str, char **host, char **port, char **basic_auth)
! 117: {
! 118: char *p;
! 119:
! 120: if ((p = strchr(str, '@')) != NULL) {
! 121: *basic_auth = xcalloc(1, BASICAUTH_LEN);
! 122: if (b64_ntop((unsigned char *)str, p - str,
! 123: *basic_auth, BASICAUTH_LEN) == -1)
! 124: errx(1, "base64 encode failed");
! 125:
! 126: str = ++p;
! 127: }
! 128:
! 129: if ((p = strchr(str, ':')) != NULL) {
! 130: *p++ = '\0';
! 131: if (strlen(p) > 0)
! 132: *port = xstrdup(p);
! 133: }
! 134:
! 135: if (strlen(str) > 0)
! 136: *host = xstrdup(str);
! 137: }
! 138:
! 139: struct url *
! 140: url_parse(const char *str)
! 141: {
! 142: struct url *url;
! 143: const char *p, *q;
! 144: char *basic_auth, *host, *port, *path, *s;
! 145: size_t len;
! 146: int ipliteral, scheme;
! 147:
! 148: p = str;
! 149: ipliteral = 0;
! 150: host = port = path = basic_auth = NULL;
! 151: while (isblank((unsigned char)*p))
! 152: p++;
! 153:
! 154: if ((q = strchr(p, ':')) == NULL) {
! 155: warnx("%s: scheme missing: %s", __func__, str);
! 156: return NULL;
! 157: }
! 158:
! 159: if ((scheme = scheme_lookup(p)) == -1) {
! 160: warnx("%s: invalid scheme: %s", __func__, p);
! 161: return NULL;
! 162: }
! 163:
! 164: p = ++q;
! 165: if (strncmp(p, "//", 2) != 0) {
! 166: if (scheme == S_FILE)
! 167: goto done;
! 168: else {
! 169: warnx("%s: invalid url: %s", __func__, str);
! 170: return NULL;
! 171: }
! 172: }
! 173:
! 174: p += 2;
! 175: len = strlen(p);
! 176: /* Authority terminated by a '/' if present */
! 177: if ((q = strchr(p, '/')) != NULL)
! 178: len = q - p;
! 179:
! 180: s = xstrndup(p, len);
! 181: if (*p == '[') {
! 182: if (ipv6_parse(s, &host, &port) != 0) {
! 183: free(s);
! 184: return NULL;
! 185: }
! 186: ipliteral = 1;
! 187: } else
! 188: authority_parse(s, &host, &port, &basic_auth);
! 189:
! 190: free(s);
! 191: if (port == NULL && scheme != S_FILE)
! 192: port = xstrdup(port_str[scheme]);
! 193:
! 194: done:
! 195: if (q != NULL)
! 196: path = xstrdup(q);
! 197:
! 198: if (io_debug) {
! 199: fprintf(stderr,
! 200: "scheme: %s\nhost: %s\nport: %s\npath: %s\n",
! 201: scheme_str[scheme], host, port, path);
! 202: }
! 203:
! 204: url = xcalloc(1, sizeof *url);
! 205: url->scheme = scheme;
! 206: url->host = host;
! 207: url->port = port;
! 208: url->path = path;
! 209: url->basic_auth = basic_auth;
! 210: url->ipliteral = ipliteral;
! 211: return url;
! 212: }
! 213:
! 214: void
! 215: url_free(struct url *url)
! 216: {
! 217: if (url == NULL)
! 218: return;
! 219:
! 220: free(url->host);
! 221: free(url->port);
! 222: free(url->path);
! 223: freezero(url->basic_auth, BASICAUTH_LEN);
! 224: free(url->fname);
! 225: free(url);
! 226: }
! 227:
! 228: void
! 229: url_connect(struct url *url, struct url *proxy, int timeout)
! 230: {
! 231: switch (url->scheme) {
! 232: case S_HTTP:
! 233: case S_HTTPS:
! 234: http_connect(url, proxy, timeout);
! 235: break;
! 236: case S_FTP:
! 237: ftp_connect(url, proxy, timeout);
! 238: break;
! 239: }
! 240: }
! 241:
! 242: struct url *
! 243: url_request(struct url *url, struct url *proxy, off_t *offset, off_t *sz)
! 244: {
! 245: switch (url->scheme) {
! 246: case S_HTTP:
! 247: case S_HTTPS:
! 248: return http_get(url, proxy, offset, sz);
! 249: case S_FTP:
! 250: return ftp_get(url, proxy, offset, sz);
! 251: case S_FILE:
! 252: return file_request(&child_ibuf, url, offset, sz);
! 253: }
! 254:
! 255: return NULL;
! 256: }
! 257:
! 258: void
! 259: url_save(struct url *url, FILE *dst_fp, off_t *offset)
! 260: {
! 261: switch (url->scheme) {
! 262: case S_HTTP:
! 263: case S_HTTPS:
! 264: http_save(url, dst_fp, offset);
! 265: break;
! 266: case S_FTP:
! 267: ftp_save(url, dst_fp, offset);
! 268: break;
! 269: case S_FILE:
! 270: file_save(url, dst_fp, offset);
! 271: break;
! 272: }
! 273: }
! 274:
! 275: void
! 276: url_close(struct url *url)
! 277: {
! 278: switch (url->scheme) {
! 279: case S_HTTP:
! 280: case S_HTTPS:
! 281: http_close(url);
! 282: break;
! 283: case S_FTP:
! 284: ftp_quit(url);
! 285: break;
! 286: }
! 287: }
! 288:
! 289: char *
! 290: url_str(struct url *url)
! 291: {
! 292: char *host, *str;
! 293: int custom_port;
! 294:
! 295: custom_port = strcmp(url->port, port_str[url->scheme]) ? 1 : 0;
! 296: if (url->ipliteral)
! 297: xasprintf(&host, "[%s]", url->host);
! 298: else
! 299: host = xstrdup(url->host);
! 300:
! 301: xasprintf(&str, "%s//%s%s%s%s",
! 302: scheme_str[url->scheme],
! 303: host,
! 304: custom_port ? ":" : "",
! 305: custom_port ? url->port : "",
! 306: url->path ? url->path : "/");
! 307:
! 308: free(host);
! 309: return str;
! 310: }
! 311:
! 312: /*
! 313: * Encode given URL, per RFC1738.
! 314: * Allocate and return string to the caller.
! 315: */
! 316: char *
! 317: url_encode(const char *path)
! 318: {
! 319: size_t i, length, new_length;
! 320: char *epath, *epathp;
! 321:
! 322: length = new_length = strlen(path);
! 323:
! 324: /*
! 325: * First pass:
! 326: * Count unsafe characters, and determine length of the
! 327: * final URL.
! 328: */
! 329: for (i = 0; i < length; i++)
! 330: if (unsafe_char(path + i))
! 331: new_length += 2;
! 332:
! 333: epath = epathp = xmalloc(new_length + 1); /* One more for '\0'. */
! 334:
! 335: /*
! 336: * Second pass:
! 337: * Encode, and copy final URL.
! 338: */
! 339: for (i = 0; i < length; i++)
! 340: if (unsafe_char(path + i)) {
! 341: snprintf(epathp, 4, "%%" "%02x",
! 342: (unsigned char)path[i]);
! 343: epathp += 3;
! 344: } else
! 345: *(epathp++) = path[i];
! 346:
! 347: *epathp = '\0';
! 348: return epath;
! 349: }
! 350:
! 351: /*
! 352: * Determine whether the character needs encoding, per RFC1738:
! 353: * - No corresponding graphic US-ASCII.
! 354: * - Unsafe characters.
! 355: */
! 356: static int
! 357: unsafe_char(const char *c0)
! 358: {
! 359: const char *unsafe_chars = " <>\"#{}|\\^~[]`";
! 360: const unsigned char *c = (const unsigned char *)c0;
! 361:
! 362: /*
! 363: * No corresponding graphic US-ASCII.
! 364: * Control characters and octets not used in US-ASCII.
! 365: */
! 366: return (iscntrl(*c) || !isascii(*c) ||
! 367:
! 368: /*
! 369: * Unsafe characters.
! 370: * '%' is also unsafe, if is not followed by two
! 371: * hexadecimal digits.
! 372: */
! 373: strchr(unsafe_chars, *c) != NULL ||
! 374: (*c == '%' && (!isxdigit(*++c) || !isxdigit(*++c))));
! 375: }
! 376:
! 377: void
! 378: log_request(const char *prefix, struct url *url, struct url *proxy)
! 379: {
! 380: char *host;
! 381: int custom_port;
! 382:
! 383: if (url->scheme == S_FILE)
! 384: return;
! 385:
! 386: custom_port = strcmp(url->port, port_str[url->scheme]) ? 1 : 0;
! 387: if (url->ipliteral)
! 388: xasprintf(&host, "[%s]", url->host);
! 389: else
! 390: host = xstrdup(url->host);
! 391:
! 392: if (proxy)
! 393: log_info("%s %s//%s%s%s%s"
! 394: " (via %s//%s%s%s)\n",
! 395: prefix,
! 396: scheme_str[url->scheme],
! 397: host,
! 398: custom_port ? ":" : "",
! 399: custom_port ? url->port : "",
! 400: url->path ? url->path : "",
! 401:
! 402: /* via proxy part */
! 403: (proxy->scheme == S_HTTP) ? "http" : "https",
! 404: proxy->host,
! 405: proxy->port ? ":" : "",
! 406: proxy->port ? proxy->port : "");
! 407: else
! 408: log_info("%s %s//%s%s%s%s\n",
! 409: prefix,
! 410: scheme_str[url->scheme],
! 411: host,
! 412: custom_port ? ":" : "",
! 413: custom_port ? url->port : "",
! 414: url->path ? url->path : "");
! 415:
! 416: free(host);
! 417: }