Problem/Motivation

TikaExtractor::extract() runs Tika with shell_exec() and treats a NULL return value as "Tika Extractor is not available."

shell_exec() returns NULL both when the command fails and when the command succeeds with empty stdout. A file that Tika can read but that contains no text (for example a JPEG without OCR text) therefore throws. FilesExtractor logs Error extracting text from file @file_id for @entity_type @entity_id. Exception: Tika Extractor is not available.

The settings form can still report that Tika can be reached, because that check only runs java -jar tika.jar -V and looks at the exit code. Reproduced with 10.0.11. The same shell_exec() / is_null() check is still present in 10.0.12.

Steps to reproduce

  1. Configure the Tika app extractor with a working Java binary and tika-app jar.
  2. On the settings form, confirm the message that Tika can be reached and executed.
  3. From the command line, run Tika in text mode against a JPEG that contains no extractable text. Tika exits 0, stdout is empty, and stderr may only contain an INFO line about inline images.
  4. Index that file through Search API Attachments.
  5. The log shows Tika Extractor is not available, even though Tika ran successfully and simply found no text.

Proposed resolution

Read stdout with proc_open() and the process exit code. Throw only when stdout cannot be read, or when stdout is empty and the exit code is not 0. A non-zero exit that still produced text is returned unchanged, matching the previous shell_exec() behavior. Successful text output stays byte-for-byte identical.

Drupal and Symfony Process already require proc_open(), so this does not add a new PHP function requirement.

diff --git a/src/Plugin/search_api_attachments/TikaExtractor.php b/src/Plugin/search_api_attachments/TikaExtractor.php
--- a/src/Plugin/search_api_attachments/TikaExtractor.php
+++ b/src/Plugin/search_api_attachments/TikaExtractor.php
@@ -67,11 +67,19 @@
     if ($this->configuration['debug_mode']) {
       $cmd .= ' 2>/dev/null';
     }
-    $output = shell_exec($cmd);
-    if (is_null($output)) {
+    // shell_exec() returns NULL both on failure and on empty stdout, e.g. an
+    // image without text. Read stdout as-is and check the exit code instead.
+    $process = proc_open($cmd, [1 => ['pipe', 'w']], $pipes);
+    if (!is_resource($process)) {
+      throw new \Exception('Tika Extractor is not available.');
+    }
+    $output = stream_get_contents($pipes[1]);
+    fclose($pipes[1]);
+    $return_code = proc_close($process);
+    if ($output === FALSE || ($output === '' && $return_code !== 0)) {
       throw new \Exception('Tika Extractor is not available.');
     }
     return $output;
   }

Remaining tasks

  • Review
  • Merge request against 10.0.x

User interface changes

None.

API changes

None.

Data model changes

None.

CommentFileSizeAuthor
#4 3628601-empty_stdout.patch1.4 KBizus
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

djschoone created an issue. See original summary.

izus’s picture

Status: Needs review » Needs work

hi, thanks for the finding

i'm not sure we can assume proc_open is available for all sites using the module
core itself does some if (!\function_exists('proc_open')) { and only assumes it's available for starting a webserver in /Core/Command/ServerCommand.php (production sites avoid that)

i wonder if we can correct it differently

izus’s picture

StatusFileSize
new1.4 KB

here is a patch with a suggestion that doesn't use proc_open
please test it

izus’s picture

Status: Needs work » Fixed

this is now merged
it is part of another commit here https://git.drupalcode.org/project/search_api_attachments/-/commit/2c426...

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.