Class: FAIRChampionHarvester::Extruct
- Inherits:
-
Object
- Object
- FAIRChampionHarvester::Extruct
- Defined in:
- lib/extruct.rb
Constant Summary collapse
- BINARY_CONTENT_TYPE =
%r{ application/pdf|application/octet-stream| image/|audio/|video/| application/zip|application/gzip|application/x-tar| application/msword|application/vnd\. }ix- BINARY_MAGIC_BYTES =
/\A(%PDF|PK\x03\x04|GIF8|\x89PNG|\xFF\xD8\xFF|\xD0\xCF|\x1F\x8B)/n- TIMEOUT_SECONDS =
Hard ceiling, in seconds, on how long the external
extructprocess may run. The content-type and magic-byte checks above catch most binary responses, but not all of them (unreliable/missing headers, formats outside BINARY_MAGIC_BYTES, or just very large/slow-to-parse HTML) — without this, a single such request blocks the calling thread (and leaks an orphaned Python process) indefinitely. Configurable via EXTRUCT_TIMEOUT_SECONDS. ENV.fetch("EXTRUCT_TIMEOUT_SECONDS", 30).to_i
Class Method Summary collapse
Class Method Details
.do_extruct(meta, uri, content_type: nil, body_prefix: nil) ⇒ Object
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/extruct.rb', line 20 def self.do_extruct(, uri, content_type: nil, body_prefix: nil) if content_type&.match?(BINARY_CONTENT_TYPE) .comments << "INFO: Skipping extruct for #{uri} — " \ "binary content-type '#{content_type}' is not HTML-parseable.\n" return end if body_prefix&.match?(BINARY_MAGIC_BYTES) .comments << "INFO: Skipping extruct for #{uri} — binary file signature detected in response body.\n" return end .comments << "INFO: Using 'extruct' to try to extract metadata from return value (message body) of #{uri}.\n" warn "begin open3" # binding.pry # Pass argv as separate elements (not one interpolated string) so this # never goes through a shell — `uri` can be attacker/publisher-supplied # and must not be shell-interpolated — and prepend the `timeout` # coreutil so a hung/slow extruct process is forcibly killed (-k 5: # escalate to SIGKILL 5s after the initial SIGTERM if it's ignored) # instead of blocking this thread and leaking a child process forever. command_parts = Shellwords.split(FAIRChampionHarvester::Utils::ExtructCommand) stdout, stderr, status = Open3.capture3( "timeout", "-k", "5", TIMEOUT_SECONDS.to_s, *command_parts, uri ) warn "" # sleep 5 warn "open3 status: #{status} #{stdout}" if [124, 137].include?(status.exitstatus) .comments << "WARN: extruct timed out after #{TIMEOUT_SECONDS}s parsing #{uri} — skipping.\n" return end result = stderr # absurd that the output comes over stderr! LOL! # result = %x{#{FAIRChampionHarvester::Utils::ExtructCommand} #{uri} 2>&1} # $stderr.puts "\n\n\n\n\n\n\n#{result.class}\n\n#{result.to_s}\n\n#{@extruct_command} #{uri} 2>&1\n\n" # need to do some error checking here! if result.to_s.match(/(Failed\sto\sextract.*?)\n/) .comments << "WARN: extruct threw an error #{::Regexp.last_match(1)} when attempting to parse return value (message body) of #{uri}.\n" if result.to_s.match(/(ValueError:.*?)\n/) .comments << "WARN: extruct error was #{::Regexp.last_match(1)}\n" end elsif result.to_s.match(/^\s+?\{/) or result.to_s.match(/^\s+\[/) # this is JSON begin json = JSON.parse result rescue StandardError warn "json parsing failed! This is bad!\n" .comments << "INFO: the extruct tool found non-parseable data at #{uri}. Aborting attempt to read it\n" return end # $stderr.puts "\n\n\n\nFOUND JSON\n\n\n" # $stderr.puts "\n\n\n\nFOUND JSON-LD\n#{json["json-ld"]} content\n\n\n" .comments << "INFO: the extruct tool found parseable data at #{uri}\n" Core.parse_rdf(, json["json-ld"].to_json, "application/ld+json") if json["json-ld"].any? # RDF .merge_hash(json["microdata"].first) if json["microdata"].any? .merge_hash(json["microformat"].first) if json["microformat"].any? .merge_hash(json["opengraph"].first) if json["opengraph"].any? Core.parse_rdf(, json["rdfa"].to_json, "application/ld+json") if json["rdfa"].any? # RDF .merge_hash(json.first) if json.first.is_a? Hash else .comments << "WARN: the extruct tool failed to find parseable data at #{uri}\n" end end |