/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 * SPDX-License-Identifier: curl
 *
 ***************************************************************************/
#include "unitcheck.h"
#include "urlapi-int.h"
#include "curlx/dynbuf.h"

static CURLcode test_unit1675(const char *arg)
{
  UNITTEST_BEGIN_SIMPLE

  /* Test ipv4_normalize */
  {
    struct dynbuf host;
    int fails = 0;
    unsigned int i;
    struct ipv4_test {
      const char *in;
      const char *out;
    };
    static const struct ipv4_test tests[] = {
      { "0x.0x.0x.0x", NULL }, /* invalid hex */
      { "0x.0x.0x", NULL }, /* invalid hex */
      { "0x.0x", NULL }, /* invalid hex */
      { "0x", NULL }, /* invalid hex */
      { "0", "0.0.0.0" },
      { "00", "0.0.0.0" },
      { "00000000000", "0.0.0.0" },
      { "127.0.0.1", "127.0.0.1" },
      { "0177.0.0.1", "127.0.0.1" },
      { "00177.0.0.1", "127.0.0.1" },
      { "0x7f.0.0.1", "127.0.0.1" },
      { "0x07f.0.0.1", "127.0.0.1" },
      { "1", "0.0.0.1" },
      { "010", "0.0.0.8" },
      { "001", "0.0.0.1" },
      { "127", "0.0.0.127" },
      { "127.1", "127.0.0.1" },
      { "127.0.1", "127.0.0.1" },
      { "1.16777215", "1.255.255.255" },
      { "1.16777216", NULL }, /* overflow */
      { "1.1.65535", "1.1.255.255" },
      { "1.1.65536", NULL }, /* overflow */
      { "0x7f000001", "127.0.0.1" },
      { "0x7F000001", "127.0.0.1" },
      { "0x7g000001", NULL }, /* bad hex */
      { "2130706433", "127.0.0.1" },
      { "017700000001", "127.0.0.1" },
      { "000000000017700000001", "127.0.0.1" },
      { "192.168.0.1", "192.168.0.1" },
      { "0300.0250.0000.0001", "192.168.0.1" },
      { "0xc0.0xa8.0.1", "192.168.0.1" },
      { "0xc0a80001", "192.168.0.1" },
      { "3232235521", "192.168.0.1" },
      { "4294967294", "255.255.255.254" },
      { "4294967295", "255.255.255.255" },
      { "037777777777", "255.255.255.255" },
      { "0xFFFFFFFF", "255.255.255.255" },
      { "0xFFFFFfff", "255.255.255.255" },
      { "1.2.3.4.5", NULL }, /* too many parts */
      { "256.0.0.1", NULL }, /* overflow */
      { "1.256.0.1", NULL }, /* overflow */
      { "1.1.256.1", NULL }, /* overflow */
      { "1.0.0.256", NULL }, /* overflow */
      { "0x100.0.0.1", NULL }, /* overflow */
      { "1.0x100.0.1", NULL }, /* overflow */
      { "1.1.0x100.1", NULL }, /* overflow */
      { "1.1.1.0x100", NULL }, /* overflow */
      { "0400.0.0.1", NULL }, /* overflow */
      { "4.0400.0.1", NULL }, /* overflow */
      { "4.4.0400.1", NULL }, /* overflow */
      { "4.4.4.0400", NULL }, /* overflow */
      { "4294967296", NULL }, /* overflow */
      { "040000000000", NULL }, /* overflow */
      { "0x100000000", NULL }, /* overflow */
      { "1.2.3.-4", NULL }, /* negative */
      { "1.2.-3.4", NULL }, /* negative */
      { "1.-2.3.4", NULL }, /* negative */
      { "-1.2.3.4", NULL }, /* negative */
      { "-12", NULL }, /* negative */
      { "-12.1", NULL }, /* negative */
      { "-12.2.3", NULL }, /* negative */
      { " 1.2.3.4", NULL }, /* space */
      { "1. 2.3.4", NULL }, /* space */
      { "1.2. 3.4", NULL }, /* space */
      { "1.2.3. 4", NULL }, /* space */
    };

    curlx_dyn_init(&host, 256);
    for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
      int rc;
      curlx_dyn_reset(&host);
      if(curlx_dyn_add(&host, tests[i].in)) {
        curlx_dyn_free(&host);
        return CURLE_OUT_OF_MEMORY;
      }
      rc = ipv4_normalize(&host);
      if(tests[i].out) {
        if((rc != HOST_IPV4) ||
           strcmp(curlx_dyn_ptr(&host), tests[i].out)) {
          curl_mfprintf(stderr, "ipv4_normalize('%s') failed: "
                        "expected '%s', got '%s'\n",
                        tests[i].in, tests[i].out, curlx_dyn_ptr(&host));
          fails++;
        }
      }
      else if(rc == HOST_IPV4) {
        curl_mfprintf(stderr, "ipv4_normalize('%s') succeeded unexpectedly:"
                      " got '%s'\n",
                      tests[i].in, curlx_dyn_ptr(&host));
        fails++;
      }
    }
    curlx_dyn_free(&host);
    abort_if(fails, "ipv4_normalize tests failed");
  }

  /* Test urlencode_str */
  {
    struct dynbuf out;
    int fails = 0;
    unsigned int i;
    struct urlencode_test {
      const char *in;
      bool relative;
      unsigned int query;
      const char *out;
    };
    static const struct urlencode_test tests[] = {
      { "http://leave\x01/hello\x01world", FALSE, QUERY_NO,
        "http://leave\x01/hello%01world" },
      { "http://leave/hello\x01world", FALSE, QUERY_NO,
        "http://leave/hello%01world" },
      { "http://le ave/hello\x01world", FALSE, QUERY_NO,
        "http://le ave/hello%01world" },
      { "hello\x01world", TRUE, QUERY_NO, "hello%01world" },
      { "hello\xf0world", TRUE, QUERY_NO, "hello%F0world" },
      { "hello world", TRUE, QUERY_NO, "hello%20world" },
      { "hello%20world", TRUE, QUERY_NO, "hello%20world" },
      { "hello world", TRUE, QUERY_YES, "hello+world" },
      { "a+b c", TRUE, QUERY_NO, "a+b%20c" },
      { "a%20b%20c", TRUE, QUERY_NO, "a%20b%20c" },
      { "a%aab%aac", TRUE, QUERY_NO, "a%AAb%AAc" },
      { "a%aab%AAc", TRUE, QUERY_NO, "a%AAb%AAc" },
      { "w%w%x", TRUE, QUERY_NO, "w%w%x" },
      { "w%wf%xf", TRUE, QUERY_NO, "w%wf%xf" },
      { "w%fw%fw", TRUE, QUERY_NO, "w%fw%fw" },
      { "a+b c", TRUE, QUERY_YES, "a+b+c" },
      { "/foo/bar", TRUE, QUERY_NO, "/foo/bar" },
      { "/foo/bar", TRUE, QUERY_YES, "/foo/bar" },
      { "/foo/ bar", TRUE, QUERY_NO, "/foo/%20bar" },
      { "/foo/ bar", TRUE, QUERY_YES, "/foo/+bar" },
      { "~-._", TRUE, QUERY_NO, "~-._" },
      { "~-._", TRUE, QUERY_YES, "~-._" },
      { "foo bar?foo bar", TRUE, QUERY_NO, "foo%20bar?foo%20bar" },
      { "foo bar?foo bar", TRUE, QUERY_NOT_YET, "foo%20bar?foo+bar" },
    };

    curlx_dyn_init(&out, 256);
    for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
      CURLUcode uc;
      curlx_dyn_reset(&out);
      uc = urlencode_str(&out, tests[i].in, strlen(tests[i].in),
                         tests[i].relative, tests[i].query);
      if(uc || strcmp(curlx_dyn_ptr(&out), tests[i].out)) {
        curl_mfprintf(stderr, "urlencode_str('%s', query=%u) failed:"
                      " expected '%s', got '%s'\n",
                      tests[i].in, tests[i].query, tests[i].out,
                      uc ? "error" : curlx_dyn_ptr(&out));
        fails++;
      }
    }
    curlx_dyn_free(&out);
    abort_if(fails, "urlencode_str tests failed");
  }

  /* Test ipv6_parse */
  {
    struct Curl_URL u;
    int fails = 0;
    unsigned int i;
    struct ipv6_test {
      const char *in;
      const char *out_host;
      const char *out_zone;
    };
    static const struct ipv6_test tests[] = {
      { "[::1]", "[::1]", NULL },
      { "[fe80::1%eth0]", "[fe80::1]", "eth0" },
      { "[fe80::1%25eth0]", "[fe80::1]", "eth0" },
      { "[::1", NULL, NULL }, /* missing bracket */
      { "[]", NULL, NULL }, /* empty */
    };

    for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
      CURLUcode uc;
      char hostname[256];
      memset(&u, 0, sizeof(u));
      curlx_strcopy(hostname, sizeof(hostname),
                    tests[i].in, strlen(tests[i].in));
      uc = ipv6_parse(&u, hostname, strlen(hostname));
      if(tests[i].out_host) {
        if(uc || strcmp(hostname, tests[i].out_host)) {
          curl_mfprintf(stderr, "ipv6_parse('%s') host failed:"
                        " expected '%s', got '%s'\n",
                        tests[i].in, tests[i].out_host,
                        uc ? "error" : hostname);
          fails++;
        }
        if(!uc && tests[i].out_zone &&
           (!u.zoneid || strcmp(u.zoneid, tests[i].out_zone))) {
          curl_mfprintf(stderr, "ipv6_parse('%s') zone failed:"
                        " expected '%s', got '%s'\n",
                        tests[i].in, tests[i].out_zone,
                        u.zoneid ? u.zoneid : "(null)");
          fails++;
        }
      }
      else if(!uc) {
        curl_mfprintf(stderr, "ipv6_parse('%s') succeeded unexpectedly\n",
                      tests[i].in);
        fails++;
      }
      curlx_free(u.host);
      curlx_free(u.zoneid);
    }
    abort_if(fails, "ipv6_parse tests failed");
  }

  /* Test parse_file */
  {
    CURLU *u;
    const char *path;
    size_t pathlen;
    int fails = 0;
    unsigned int i;
    struct file_test {
      const char *in;
      const char *out_path;
      bool fine;
    };
    static const struct file_test tests[] = {
      { "file:///etc/hosts", "/etc/hosts", TRUE },
      { "file://localhost/etc/hosts", "/etc/hosts", TRUE },
      { "file://apple/etc/hosts", "/etc/hosts", FALSE },
      { "file:foo", NULL, FALSE },
      { "file:./", NULL, FALSE },
      { "file:?q", NULL, FALSE },
      { "file:#f", NULL, FALSE },
#ifdef _WIN32
      { "file:///c:/windows/system32", "c:/windows/system32", TRUE },
      { "file://localhost/c:/windows/system32", "c:/windows/system32", TRUE },
#endif
    };

    for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
      CURLUcode uc;
      u = curl_url();
      if(!u)
        return CURLE_OUT_OF_MEMORY;

      uc = parse_file(tests[i].in, strlen(tests[i].in), u, &path, &pathlen);
      if(!tests[i].fine && !uc) {
        curl_mfprintf(stderr, "Unexpectedly fine for input '%s'\n",
                      tests[i].in);
        fails++;
      }
      if(tests[i].fine &&
         (uc ||
          strncmp(path, tests[i].out_path, pathlen) ||
          strlen(tests[i].out_path) != pathlen)) {
        curl_mfprintf(stderr, "parse_file('%s') failed:"
                      " expected path '%s'; got path '%.*s'\n",
                      tests[i].in, tests[i].out_path,
                      (int)pathlen, path);
        fails++;
      }
      curl_url_cleanup(u);
    }
    abort_if(fails, "parse_file tests failed");
  }

  /* Test same origin check. For now, we can only do that when
   * schemes are supported by libcurl. */
#ifndef CURL_DISABLE_HTTP
  {
    CURLU *base, *href;
    int fails = 0;
    unsigned int i;
    bool match;
    struct origin_test {
      const char *base;
      const char *scheme;
      const char *host;
      const char *port;
      const char *path;
      bool expect_match;
    };
    static const struct origin_test tests[] = {
      { "http://host:123/x", "http", "host", "123", "/y", TRUE },
      { "http://host:123/x", NULL, "host", "123", "/y", TRUE },
      { "http://host:123/x", NULL, NULL, NULL, "/y", TRUE },
      { "http://host:80/x", "http", "host", "123", "/y", FALSE },
      { "http://host:80/x", "http", "host", NULL, "/y", TRUE },
      { "http://host/x", "http", "host", "80", "/y", TRUE },
#ifdef USE_SSL
      { "http://host:123/x", "https", "host", "123", "/y", FALSE },
      { "https://host/x", "http", "host", "443", "/y", FALSE },
      { "https://host/x", "https", "host", "443", "/y", TRUE },
#endif
    };

    for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
      CURLUcode uc;
      base = curl_url();
      href = curl_url();
      if(!base || !href) {
        curl_mfprintf(stderr, "%u: failed to allocate memory\n", i);
        fails++;
        goto loop_end;
      }
      uc = curl_url_set(base, CURLUPART_URL, tests[i].base, 0);
      if(uc) {
        curl_mfprintf(stderr, "failed to parse %u base %s -> %d\n", i,
                      tests[i].base, (int)uc);
        fails++;
        goto loop_end;
      }
      if(tests[i].scheme)
        uc = curl_url_set(href, CURLUPART_SCHEME, tests[i].scheme, 0);
      if(!uc && tests[i].host)
        uc = curl_url_set(href, CURLUPART_HOST, tests[i].host, 0);
      if(!uc && tests[i].port)
        uc = curl_url_set(href, CURLUPART_PORT, tests[i].port, 0);
      if(!uc && tests[i].path)
        uc = curl_url_set(href, CURLUPART_PATH, tests[i].path, 0);
      if(uc) {
        curl_mfprintf(stderr, "failed to parse %u href %s://%s:%s%s -> %d\n",
                      i, tests[i].scheme, tests[i].host, tests[i].port,
                      tests[i].path, (int)uc);
        fails++;
        goto loop_end;
      }

      match = Curl_url_same_origin(base, href);
      if(match != tests[i].expect_match) {
        curl_mfprintf(stderr, "ERROR: %u base %s and href %s://%s:%s%s %s\n",
                      i, tests[i].base, tests[i].scheme, tests[i].host,
                      tests[i].port, tests[i].path,
                      match ? "matched" : "did not match");
        fails++;
      }

loop_end:
      curl_url_cleanup(base);
      curl_url_cleanup(href);
    }
    abort_if(fails, "same_origin tests failed");
  }
#endif /* !CURL_DISABLE_HTTP */

  /* Test parse_hostname_login */
  {
    struct Curl_URL u;
    int fails = 0;
    unsigned int i;
    struct test {
      CURLUcode uc;
      const char *in;
      const char *scheme;
      unsigned int flags;
      const char *user;
      const char *password;
      const char *options;
      size_t offset;
    };
    static const struct test tests[] = {
      { CURLUE_OK, "foo:bar@host", NULL, 0, "foo", "bar", "o", 8 },
      { CURLUE_OK, "foo:bar;abc@host", "imap", 0, "foo", "bar", "abc", 12 },
      { CURLUE_OK, "foo:bar;abc@host", NULL, 0, "foo", "bar;abc", "o", 12 },
      { CURLUE_USER_NOT_ALLOWED, "foo:bar@host", NULL, CURLU_DISALLOW_USER,
        NULL, NULL, NULL, 0 },
      { CURLUE_OK, "host", NULL, 0, NULL, NULL, NULL, 0 },
    };

    for(i = 0; i < CURL_ARRAYSIZE(tests); i++) {
      CURLUcode uc;
      size_t offset = 0;
      memset(&u, 0, sizeof(u));
      u.scheme = CURL_UNCONST(tests[i].scheme);
      u.user = curlx_strdup("u");
      u.password = curlx_strdup("p");
      u.options = curlx_strdup("o");
      uc = parse_hostname_login(&u, tests[i].in, strlen(tests[i].in),
                                tests[i].flags, &offset);
      if(uc != tests[i].uc ||
         !!u.user != !!tests[i].user ||
         (u.user && tests[i].user &&
          strcmp(u.user, tests[i].user)) ||
         !!u.password != !!tests[i].password ||
         (u.password && tests[i].password &&
          strcmp(u.password, tests[i].password)) ||
         !!u.options != !!tests[i].options ||
         (u.options && tests[i].options &&
          strcmp(u.options, tests[i].options)) ||
         offset != tests[i].offset) {
        curl_mfprintf(stderr, "%u: parse_hostname_login('%s') host failed:"
                      " expected '%d/%s/%s/%s/%zu', got '%d/%s/%s/%s/%zu'\n",
                      i, tests[i].in, (int)tests[i].uc, tests[i].user,
                      tests[i].password, tests[i].options, tests[i].offset,
                      (int)uc, u.user, u.password, u.options, offset);
        fails++;
      }
      curlx_safefree(u.user);
      curlx_safefree(u.password);
      curlx_safefree(u.options);
    }
    abort_if(fails, "parse_hostname_login tests failed");
  }

  UNITTEST_END_SIMPLE
}
