ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest/*
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * Copyright 2014 ForgeRock AS.
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest *
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * The contents of this file are subject to the terms of the Common Development and
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * Distribution License (the License). You may not use this file except in compliance with the
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * License.
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest *
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * specific language governing permission and limitations under the License.
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest *
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * When distributing Covered Software, include this CDDL Header Notice in each file and include
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * Header, with the fields enclosed by brackets [] replaced by your own identifying
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * information: "Portions copyright [year] [name of copyright owner]".
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest */
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrestpackage com.sun.identity.shared.whitelist;
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrestimport org.testng.annotations.BeforeClass;
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrestimport org.testng.annotations.Test;
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrestimport java.net.MalformedURLException;
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrestimport java.util.HashMap;
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrestimport static org.fest.assertions.Assertions.assertThat;
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest/**
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest * Exercises the behaviour of {@link PrefixResourceName}.
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest */
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrestpublic class PrefixResourceNameTest {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest private ResourceName resourceName;
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @BeforeClass
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void setUp() {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest resourceName = new PrefixResourceName();
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest resourceName.initialize(new HashMap<String, String>());
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @Test
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void normaliseHandlesTrailingSlashes() throws MalformedURLException {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.canonicalize("http://a.b.c:80///ab///cd///")).isEqualTo("http://a.b.c:80/ab/cd/");
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.canonicalize("http://a.b.c:80///ab///cd")).isEqualTo("http://a.b.c:80/ab/cd");
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.canonicalize("//a.b.c///ab///cd///")).isEqualTo("/a.b.c/ab/cd/");
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.canonicalize("//a.b.c///ab///cd")).isEqualTo("/a.b.c/ab/cd");
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major @Test
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major public void normalizationOnlyAffectsPath() throws Exception {
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://a.b..c:80///ab/cd?index=hello/./world//../"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://a.b..c:80/ab/cd?index=hello/./world//../");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://a.b.c:80/./ab//cd/../?index=hello/./world//../"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://a.b.c:80/ab/?index=hello/./world//../");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://a.b.c:80/ab/../cd/.././ab///cd?index=hello/./world//../"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://a.b.c:80/ab/cd?index=hello/./world//../");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major }
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major @Test
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major public void normalizationShouldRemovePathNavigationSegments() throws Exception {
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/../zz"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/../zz?qq"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz?qq");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/./../zz"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/./../zz?qq"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz?qq");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/bb/cc/../cc/../../bb/../../zz"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/bb/cc/../cc/../../bb/../../zz?qq"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz?qq");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/bb/./../bb/.././bb/../../zz"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/bb/./../bb/.././bb/../../zz?qq"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz?qq");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/bb/././../bb/././../././../././zz"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/bb/././../bb/././../././../././zz?qq"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/zz?qq");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/../aa/ee.ff"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/aa/ee.ff");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/../aa/ee.ff?qq"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/aa/ee.ff?qq");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/./.././aa/ee.ff"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/aa/ee.ff");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major assertThat(resourceName.canonicalize("http://w.e.c:80/aa/./.././aa/ee.ff?qq"))
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major .isEqualTo("http://w.e.c:80/aa/ee.ff?qq");
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major }
7bd348faa967a61aa85a7f5d429cc6dfad2e1c30Peter Major
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @Test
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void verifyNullMatchBehaviour() {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare(null, null, true)).isEqualTo(ResourceMatch.EXACT_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare(null, "abc", true)).isEqualTo(ResourceMatch.NO_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("abc", null, true)).isEqualTo(ResourceMatch.NO_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @Test
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void verifyTrailingSlashMatchBehaviour() {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def", "/abc/def", true)).isEqualTo(ResourceMatch.EXACT_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def", "/abc/def/", true)).isEqualTo(ResourceMatch.SUB_RESOURCE_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/", "/abc/def", true)).isEqualTo(ResourceMatch.SUPER_RESOURCE_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @Test
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void verifyMultiLevelWildcardMatchBehaviour() {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/", "/abc/*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def", "/abc/*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/", "/abc/*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/hij", "/abc/*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc", "/abc*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abcdef", "/abc*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def", "/abc*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/", "/abc*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/hij", "/abc*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/ac", "/a*c", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc", "/a*c", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/a/c", "/a*c", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/ab/dc", "/a*c", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @Test
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void verifySingleLevelWildcardMatchBehaviour() {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest // Note: the single level wildcard is a lazy wildcard in that it does not
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest // consume the forward slash marking the end of level its matching.
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/", "/abc/-*-", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def", "/abc/-*-", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/", "/abc/-*-", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/hij", "/abc/-*-", true)).isEqualTo(ResourceMatch.SUPER_RESOURCE_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc", "/abc-*-", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abcdef", "/abc-*-", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def", "/abc-*-", true)).isEqualTo(ResourceMatch.SUPER_RESOURCE_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/", "/abc-*-", true)).isEqualTo(ResourceMatch.SUPER_RESOURCE_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/def/hij", "/abc-*-", true)).isEqualTo(ResourceMatch.SUPER_RESOURCE_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/ac", "/a-*-c", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc", "/a-*-c", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/ac/", "/a-*-c", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc/d", "/a-*-c", true)).isEqualTo(ResourceMatch.SUPER_RESOURCE_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/a/c", "/a-*-c", true)).isEqualTo(ResourceMatch.NO_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/ab/dc", "/a-*-c", true)).isEqualTo(ResourceMatch.NO_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @Test
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void ignoreWildcardMatchBehaviour() {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/axyzc/", "/a-*-c/", false)).isEqualTo(ResourceMatch.NO_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/a-*-c/", "/a-*-c/", false)).isEqualTo(ResourceMatch.EXACT_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/ax/yc/", "/a*c/", false)).isEqualTo(ResourceMatch.NO_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/a*c/", "/a*c/", false)).isEqualTo(ResourceMatch.EXACT_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest @Test
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest public void verifyQueryStringMatchBehaviour() {
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc?", "/abc?*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc?def", "/abc?*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abc?", "/abc*?*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest assertThat(resourceName.compare("/abcdef?a=b", "/abc*?*", true)).isEqualTo(ResourceMatch.WILDCARD_MATCH);
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest }
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest}
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest
ec7ecf2e3705ed7cc4265fa94db905646e23c553Andrew Forrest