This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch fix-testforasync-invalidpathexception
in repository https://gitbox.apache.org/repos/asf/tika.git

commit 09e3adfd743733bbbadbc5e818734b5b6c0c29e1
Author: tallison <[email protected]>
AuthorDate: Mon Aug 17 17:36:13 2026 -0400

    TIKA-4808 - remove access to the network parser from cli
---
 CHANGES.txt                                        |  22 ++-
 docs/modules/ROOT/pages/using-tika/cli/index.adoc  |  12 +-
 .../src/main/java/org/apache/tika/cli/TikaCLI.java |  20 +--
 .../java/org/apache/tika/parser/NetworkParser.java | 188 ---------------------
 .../org/apache/tika/parser/NetworkParserTest.java  | 135 ---------------
 5 files changed, 30 insertions(+), 347 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 558f6d9b81..c8f38747dc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,14 +2,20 @@ Release 4.0.0 - ???
 
   BREAKING CHANGES
 
-   * tika-app: the inline short forms -eX (output encoding), -pX (document
-     password) and -c<uri> (network client) were removed from standard mode.
-     Use --encoding=X, --password=X and --client=<uri>. These were the only
-     short flags that consumed an inline value, and matching them by prefix
-     meant a long name written with one dash was silently swallowed:
-     -config=tika.json set the network-client URI to "onfig=tika.json" and
-     loaded no config file, with no error. Every single-dash long name is now
-     rejected with a message naming the two-dash form (TIKA-4808).
+   * tika-app: the inline short forms -eX (output encoding) and -pX (document
+     password) were removed from standard mode. Use --encoding=X and
+     --password=X. These were the only short flags that consumed an inline
+     value, and matching them by prefix meant a long name written with one
+     dash was silently swallowed: -config=tika.json set the password to
+     "onfig=tika.json" and loaded no config file, with no error. Every
+     single-dash long name is now rejected with a message naming the
+     two-dash form (TIKA-4808).
+
+   * tika-core/tika-app: NetworkParser and tika-app's -c/--client=<uri>
+     network-client mode were removed. It dispatched raw sockets
+     (including a bare telnet:// scheme) to an arbitrary user-supplied
+     host with no auth or TLS enforcement; use tika-server instead
+     (TIKA-4808).
 
    * Metadata's reserved tk: (and legacy X-TIKA:) namespace is now a trust
      boundary for String-keyed writes. A String write to a reserved key throws
diff --git a/docs/modules/ROOT/pages/using-tika/cli/index.adoc 
b/docs/modules/ROOT/pages/using-tika/cli/index.adoc
index cef8b5849b..121a1e3e12 100644
--- a/docs/modules/ROOT/pages/using-tika/cli/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/cli/index.adoc
@@ -64,10 +64,14 @@ Writing a long name with a single dash is always an error — 
`-input`, `-config
 `-encoding=UTF-8` and the like are rejected with a message naming the two-dash 
form. No short
 flag takes an inline value, so nothing is silently consumed as a flag's 
argument.
 
-NOTE: In 4.0.0 the inline short forms `-eX` (encoding), `-pX` (password) and 
`-c<uri>`
-(network client) were removed from standard mode. Use `--encoding=X`, 
`--password=X` and
-`--client=<uri>`. Scripts using the old forms now fail with an 
unrecognized-option error
-rather than silently misparsing.
+NOTE: In 4.0.0 the inline short forms `-eX` (encoding) and `-pX` (password) 
were removed
+from standard mode. Use `--encoding=X` and `--password=X`. Scripts using the 
old forms now
+fail with an unrecognized-option error rather than silently misparsing.
+
+NOTE: In 4.0.0 network-client mode (`-c<uri>` / `--client=<uri>`, backed by
+`NetworkParser`) was removed entirely. It dispatched raw sockets -- including 
a bare
+`telnet://` scheme -- to an arbitrary user-supplied host with no 
authentication or TLS
+enforcement. Use tika-server instead.
 
 `-T` means different things in the two modes, so read it in context: 
`--text-main` in standard
 mode, `--timeoutMillis=<ms>` in pipes mode. `-p` (`--pluginsDir`) and `-c` 
(`--config`) exist
diff --git a/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java 
b/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
index c6a31730e7..1089229760 100644
--- a/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
+++ b/tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
@@ -32,7 +32,6 @@ import java.io.Serializable;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
 import java.lang.reflect.Field;
-import java.net.URI;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
@@ -91,7 +90,6 @@ import org.apache.tika.mime.MimeTypeException;
 import org.apache.tika.mime.MimeTypes;
 import org.apache.tika.parser.AutoDetectParser;
 import org.apache.tika.parser.CompositeParser;
-import org.apache.tika.parser.NetworkParser;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
 import org.apache.tika.parser.ParserDecorator;
@@ -136,7 +134,6 @@ public class TikaCLI {
     private TikaLoader tikaLoader;
     private String configFilePath;
     private boolean recursiveJSON = false;
-    private URI networkURI = null;
     /**
      * Output character encoding, or <code>null</code> for platform default
      */
@@ -447,8 +444,13 @@ public class TikaCLI {
         }
 
         if (args.length == 2) {
-            if (Files.isDirectory(Paths.get(args[0]))) {
-                return true;
+            try {
+                if (Files.isDirectory(Paths.get(args[0]))) {
+                    return true;
+                }
+            } catch (InvalidPathException e) {
+                // Not a valid path (e.g. a URL passed as a raw single-dash
+                // arg on Windows) -- fall through to the other checks.
             }
         }
 
@@ -607,8 +609,6 @@ public class TikaCLI {
             maxEmbeddedCount = 
Integer.parseInt(arg.substring("--maxEmbeddedCount=".length()));
         } else if (arg.equals("-r") || arg.equals("--pretty-print")) {
             prettyPrint = true;
-        } else if (arg.startsWith("--client=")) {
-            networkURI = new URI(arg.substring("--client=".length()));
         } else {
             // Any arg that reaches here is either "-" (stdin), an existing
             // file, a URL, or an unknown/typo'd flag. The default fallthrough
@@ -1023,11 +1023,7 @@ public class TikaCLI {
                 Files.deleteIfExists(tempConfig);
             }
         }
-        if (networkURI != null) {
-            parser = new NetworkParser(networkURI);
-        } else {
-            parser = tikaLoader.loadAutoDetectParser();
-        }
+        parser = tikaLoader.loadAutoDetectParser();
 
         // Load configs from tika-config.json and merge into existing context
         // (preserves EmbeddedDocumentExtractor and other items set before 
configure())
diff --git a/tika-core/src/main/java/org/apache/tika/parser/NetworkParser.java 
b/tika-core/src/main/java/org/apache/tika/parser/NetworkParser.java
deleted file mode 100644
index f6bae48eef..0000000000
--- a/tika-core/src/main/java/org/apache/tika/parser/NetworkParser.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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.parser;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Collections;
-import java.util.Set;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.function.IOSupplier;
-import org.apache.commons.io.input.CloseShieldInputStream;
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import org.apache.tika.exception.TikaException;
-import org.apache.tika.io.TikaInputStream;
-import org.apache.tika.metadata.KeyPrefix;
-import org.apache.tika.metadata.Metadata;
-import org.apache.tika.mime.MediaType;
-import org.apache.tika.sax.TaggedContentHandler;
-import org.apache.tika.sax.TeeContentHandler;
-import org.apache.tika.utils.XMLReaderUtils;
-
-
-public class NetworkParser implements Parser {
-
-    // meta/@name in the remote parse service's XML response: naming a 
service's own inferred
-    // field is tool provenance, not file provenance (contrast 
dif:/gdal:/hdf:, which read names
-    // straight out of the file being parsed).
-    private static final KeyPrefix NETWORK =
-            KeyPrefix.tool("network:", "meta/@name from a remote parse 
service's XML response");
-
-    private final URI uri;
-
-    private final Set<MediaType> supportedTypes;
-
-    public NetworkParser(URI uri, Set<MediaType> supportedTypes) {
-        this.uri = uri;
-        this.supportedTypes = supportedTypes;
-    }
-
-    public NetworkParser(URI uri) {
-        this(uri, Collections.singleton(MediaType.OCTET_STREAM));
-    }
-
-    public Set<MediaType> getSupportedTypes(ParseContext context) {
-        return supportedTypes;
-    }
-
-    public void parse(TikaInputStream tis, ContentHandler handler, Metadata 
metadata,
-                      ParseContext context) throws IOException, SAXException, 
TikaException {
-        if ("telnet".equals(uri.getScheme())) {
-            try (Socket socket = new Socket(uri.getHost(), uri.getPort())) {
-                new ParsingTask(tis, new 
FilterOutputStream(socket.getOutputStream()) {
-                    @Override
-                    public void close() throws IOException {
-                        socket.shutdownOutput();
-                    }
-                }).parse(socket::getInputStream, handler, metadata, context);
-            }
-        } else {
-            URL url = uri.toURL();
-            URLConnection connection = url.openConnection();
-            connection.setDoOutput(true);
-            connection.connect();
-            // getInputStream() must not be called until the request is 
written (see
-            // ParsingTask.parse() javadoc) -- get the output stream first.
-            OutputStream output = connection.getOutputStream();
-            new ParsingTask(tis, output)
-                    .parse(connection::getInputStream, handler, metadata, 
context);
-        }
-
-    }
-
-    private static class ParsingTask implements Runnable {
-
-        private final TikaInputStream input;
-
-        private final OutputStream output;
-
-        private volatile Exception exception = null;
-
-        public ParsingTask(TikaInputStream input, OutputStream output) {
-            this.input = input;
-            this.output = output;
-        }
-
-        /**
-         * @param streamSupplier opens the response stream -- called only 
after the writer
-         *                       thread has started, so a supplier that blocks 
until the request
-         *                       is fully sent (e.g. {@code 
URLConnection#getInputStream}) doesn't
-         *                       deadlock waiting on a write that hasn't begun 
yet
-         */
-        public void parse(IOSupplier<InputStream> streamSupplier, 
ContentHandler handler,
-                          Metadata metadata, ParseContext context)
-                throws IOException, SAXException, TikaException {
-            Thread thread = new Thread(this, "Tika network parser");
-            thread.start();
-
-            TaggedContentHandler tagged =
-                    new TaggedContentHandler(handler);
-            // shield the real stream from the SAX parser's own close(); the 
try-with-resources
-            // below is what actually closes it, once parsing is done.
-            try (InputStream stream = streamSupplier.get()) {
-                XMLReaderUtils
-                        .parseSAX(CloseShieldInputStream.wrap(stream),
-                                new TeeContentHandler(tagged, new 
MetaHandler(metadata)), context);
-            } catch (SAXException e) {
-                tagged.throwIfCauseOf(e);
-                throw new TikaException("Invalid network parser output", e);
-            } catch (IOException e) {
-                throw new TikaException("Unable to read network parser 
output", e);
-            } finally {
-                try {
-                    thread.join(1000);
-                } catch (InterruptedException e) {
-                    throw new TikaException("Network parser interrupted", e);
-                }
-
-                if (exception != null) {
-                    input.throwIfCauseOf(exception);
-                    throw new TikaException("Unexpected network parser error", 
exception);
-                }
-            }
-        }
-
-        //----------------------------------------------------------<Runnable>
-
-        public void run() {
-            try {
-                try {
-                    IOUtils.copy(input, output);
-                } finally {
-                    output.close();
-                }
-            } catch (Exception e) {
-                exception = e;
-            }
-        }
-
-    }
-
-    private static class MetaHandler extends DefaultHandler {
-
-        private final Metadata metadata;
-
-        public MetaHandler(Metadata metadata) {
-            this.metadata = metadata;
-        }
-
-        @Override
-        public void startElement(String uri, String localName, String qName, 
Attributes attributes)
-                throws SAXException {
-            if ("http://www.w3.org/1999/xhtml".equals(uri) && 
"meta".equals(localName)) {
-                String name = attributes.getValue("", "name");
-                String content = attributes.getValue("", "content");
-                if (name != null && content != null) {
-                    metadata.add(NETWORK, name, content);
-                }
-            }
-        }
-
-    }
-
-}
diff --git 
a/tika-core/src/test/java/org/apache/tika/parser/NetworkParserTest.java 
b/tika-core/src/test/java/org/apache/tika/parser/NetworkParserTest.java
deleted file mode 100644
index a59bfaf7a6..0000000000
--- a/tika-core/src/test/java/org/apache/tika/parser/NetworkParserTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.parser;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.junit.jupiter.api.Test;
-import org.xml.sax.helpers.DefaultHandler;
-
-import org.apache.tika.io.TikaInputStream;
-import org.apache.tika.metadata.Metadata;
-import org.apache.tika.mime.MediaType;
-
-/**
- * {@code meta/@name} in the remote service's XML response is tool-derived 
text, so
- * {@link NetworkParser} routes it through the {@code network:} {@link
- * org.apache.tika.metadata.KeyPrefix}. No test resource fixture is needed or 
wanted here: a
- * loopback {@link ServerSocket} stands in for the remote parse service (raw 
sockets only --
- * {@code com.sun.net.httpserver} is forbiddenapis-banned as a non-portable 
internal JDK class).
- * TIKA-4816.
- */
-public class NetworkParserTest {
-
-    private static final String RESPONSE_XML = "<html 
xmlns=\"http://www.w3.org/1999/xhtml\";>"
-            + "<head><meta name=\"foo\" 
content=\"bar\"/></head><body/></html>";
-
-    @Test
-    public void telnetSchemeRoutesMetaNameThroughNetworkKeyPrefix() throws 
Exception {
-        try (ServerSocket server = new ServerSocket(0)) {
-            Thread serverThread = new Thread(() -> {
-                try (Socket accepted = server.accept()) {
-                    accepted.getInputStream().readAllBytes();   // client 
half-closes after writing
-                    
accepted.getOutputStream().write(RESPONSE_XML.getBytes(StandardCharsets.UTF_8));
-                } catch (Exception ignore) {
-                    // surfaced indirectly: the client-side assertions below 
fail instead
-                }
-            }, "test-network-parser-telnet-server");
-            serverThread.start();
-
-            URI uri = URI.create("telnet://localhost:" + 
server.getLocalPort());
-            assertMetaNameRoutedThroughNetworkKeyPrefix(uri);
-            serverThread.join(5000);
-        }
-    }
-
-    @Test
-    public void httpSchemeRoutesMetaNameThroughNetworkKeyPrefix() throws 
Exception {
-        // Also exercises the getOutputStream()-before-getInputStream() 
ordering fix: the
-        // original code called URLConnection#getInputStream() before the 
request had been
-        // written, which deadlocks/fails against any real HTTP server 
(TIKA-4816).
-        try (ServerSocket server = new ServerSocket(0)) {
-            Thread serverThread = new Thread(() -> {
-                try (Socket accepted = server.accept()) {
-                    // HttpURLConnection keeps the connection open for 
keep-alive (never
-                    // half-closes), so -- unlike the telnet server above -- 
draining to EOF
-                    // would hang forever; read exactly the declared request 
body instead.
-                    readHttpRequest(accepted.getInputStream());
-                    byte[] body = 
RESPONSE_XML.getBytes(StandardCharsets.UTF_8);
-                    OutputStream out = accepted.getOutputStream();
-                    out.write(("HTTP/1.1 200 OK\r\n"
-                            + "Content-Type: text/xml; charset=utf-8\r\n"
-                            + "Content-Length: " + body.length + "\r\n"
-                            + "Connection: 
close\r\n\r\n").getBytes(StandardCharsets.UTF_8));
-                    out.write(body);
-                    out.flush();
-                } catch (Exception ignore) {
-                    // surfaced indirectly: the client-side assertions below 
fail instead
-                }
-            }, "test-network-parser-http-server");
-            serverThread.start();
-
-            URI uri = URI.create("http://localhost:"; + server.getLocalPort() + 
"/");
-            assertMetaNameRoutedThroughNetworkKeyPrefix(uri);
-            serverThread.join(5000);
-        }
-    }
-
-    private static final Pattern CONTENT_LENGTH =
-            Pattern.compile("(?i)content-length:\\s*(\\d+)");
-
-    /** Reads a minimal HTTP request (headers, then the declared 
Content-Length body bytes) off
-     * {@code in} without closing it and without needing the client to signal 
EOF. */
-    private static void readHttpRequest(InputStream in) throws Exception {
-        ByteArrayOutputStream headerBytes = new ByteArrayOutputStream();
-        int trailingCrLfCrLf = 0;   // count of the last 4 bytes matching 
"\r\n\r\n" so far
-        int[] terminator = {'\r', '\n', '\r', '\n'};
-        int b;
-        while (trailingCrLfCrLf < terminator.length && (b = in.read()) != -1) {
-            headerBytes.write(b);
-            trailingCrLfCrLf = (b == terminator[trailingCrLfCrLf]) ? 
trailingCrLfCrLf + 1
-                    : (b == '\r' ? 1 : 0);
-        }
-        Matcher m = 
CONTENT_LENGTH.matcher(headerBytes.toString(StandardCharsets.UTF_8));
-        int contentLength = m.find() ? Integer.parseInt(m.group(1)) : 0;
-        in.readNBytes(contentLength);
-    }
-
-    private void assertMetaNameRoutedThroughNetworkKeyPrefix(URI uri) throws 
Exception {
-        NetworkParser parser = new NetworkParser(uri, 
Collections.singleton(MediaType.OCTET_STREAM));
-        Metadata metadata = new Metadata();
-        try (TikaInputStream tis =
-                TikaInputStream.get("posted 
document".getBytes(StandardCharsets.UTF_8))) {
-            parser.parse(tis, new DefaultHandler(), metadata, new 
ParseContext());
-        }
-
-        assertEquals("bar", metadata.get("network:foo"));
-        assertNull(metadata.get("foo"), "the unprefixed legacy key must not 
appear");
-    }
-}

Reply via email to