MetadataInternalKeyGuardTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tika.metadata;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import org.junit.jupiter.api.Test;

/** Reserved Tika-native ({@code tk:}) keys can't be overwritten by String writes, only via Property. */
public class MetadataInternalKeyGuardTest {

    @Test
    public void testLegacyXTikaPrefixStaysReserved() {
        Metadata metadata = new Metadata();
        // pre-4.0.0 prefix stays reserved so a crafted file can't forge it during the 4.x window
        metadata.add(TikaCoreProperties.LEGACY_TIKA_META_PREFIX + "Parsed-By", "org.evil.FakeParser");
        assertNull(metadata.get(TikaCoreProperties.LEGACY_TIKA_META_PREFIX + "Parsed-By"),
                "legacy X-TIKA: String write must still be dropped");
    }

    @Test
    public void testStringWriteToInternalKeyIsDropped() {
        Metadata metadata = new Metadata();
        // hostile scrape
        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "injected");
        assertNull(metadata.get(TikaCoreProperties.TIKA_CONTENT),
                "String write to an internal key must be dropped");
        assertNull(metadata.get(TikaCoreProperties.TIKA_CONTENT.getName()));
    }

    @Test
    public void testStringAddToInternalMultiValueKeyIsDropped() {
        Metadata metadata = new Metadata();
        metadata.add(TikaCoreProperties.TIKA_PARSED_BY.getName(), "org.evil.FakeParser");
        assertArrayEquals(new String[0], metadata.getValues(TikaCoreProperties.TIKA_PARSED_BY),
                "String add to an internal key must be dropped");
    }

    @Test
    public void testStringWriteCannotOverwriteTrustedInternalValue() {
        Metadata metadata = new Metadata();
        metadata.set(TikaCoreProperties.TIKA_CONTENT, "trusted");
        // String-path attempt must not clobber
        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "injected");
        assertEquals("trusted", metadata.get(TikaCoreProperties.TIKA_CONTENT));
    }

    @Test
    public void testPropertyWriteToInternalKeyStillWorks() {
        Metadata metadata = new Metadata();
        metadata.set(TikaCoreProperties.TIKA_CONTENT, "legit");
        assertEquals("legit", metadata.get(TikaCoreProperties.TIKA_CONTENT));

        metadata.add(TikaCoreProperties.TIKA_PARSED_BY, "p1");
        metadata.add(TikaCoreProperties.TIKA_PARSED_BY, "p2");
        assertArrayEquals(new String[] {"p1", "p2"},
                metadata.getValues(TikaCoreProperties.TIKA_PARSED_BY));
    }

    @Test
    public void testNonReservedStringKeysAreUnaffected() {
        Metadata metadata = new Metadata();
        // unregistered key passes through
        metadata.set("my:customKey", "value");
        assertEquals("value", metadata.get("my:customKey"));

        // a registered non-reserved property (dc:description) written by name still works
        metadata.set(TikaCoreProperties.DESCRIPTION.getName(), "hello");
        assertEquals("hello", metadata.get(TikaCoreProperties.DESCRIPTION));
    }

    @Test
    public void testNullNameDoesNotThrow() {
        Metadata metadata = new Metadata();
        // no NPE on a null name
        metadata.set((String) null, "x");
    }

    @Test
    public void testReconstructPreservesRegisteredReservedKey() {
        Metadata metadata = new Metadata();
        metadata.reconstruct(TikaCoreProperties.TIKA_CONTENT.getName(), "the content", false);
        assertEquals("the content", metadata.get(TikaCoreProperties.TIKA_CONTENT));

        metadata.reconstruct(TikaCoreProperties.TIKA_PARSED_BY.getName(), "p1", true);
        metadata.reconstruct(TikaCoreProperties.TIKA_PARSED_BY.getName(), "p2", true);
        assertArrayEquals(new String[] {"p1", "p2"},
                metadata.getValues(TikaCoreProperties.TIKA_PARSED_BY));
    }

    @Test
    public void testReconstructPreservesUnregisteredReservedKey() {
        Metadata metadata = new Metadata();
        String unregistered = TikaCoreProperties.TIKA_META_PREFIX + "noSuchRegisteredProperty";
        assertNull(Property.get(unregistered), "precondition: key must be unregistered");

        metadata.set(unregistered, "dropped");
        assertNull(metadata.get(unregistered));

        metadata.reconstruct(unregistered, "kept", false);
        assertEquals("kept", metadata.get(unregistered));

        metadata.reconstruct(unregistered, "kept2", true);
        assertArrayEquals(new String[] {"kept", "kept2"}, metadata.getValues(unregistered));
    }

    @Test
    public void testTrustedModeAllowsReservedStringWrites() {
        Metadata metadata = new Metadata();
        metadata.setTrusted(true);
        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "trusted");
        assertEquals("trusted", metadata.get(TikaCoreProperties.TIKA_CONTENT));

        metadata.setTrusted(false);
        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "blocked");
        assertEquals("trusted", metadata.get(TikaCoreProperties.TIKA_CONTENT));
    }
}