feat: implemented hit testing, form field, content stream and base security hardening
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
// Resource limits for hardening against malicious / malformed PDFs.
|
||||
//
|
||||
// These guard the allocation-sizing arithmetic in the load and render paths
|
||||
// against integer overflow and pathological out-of-memory inputs (a 2-billion-pt
|
||||
// page, a million-page document, a multi-gigabyte raster). The ceilings are set
|
||||
// far above anything a legitimate document needs, so enforcing them never
|
||||
// rejects real files — they exist purely to turn "crash / OOM" into a clean,
|
||||
// recoverable error, which is exactly what a fuzzer needs to make progress.
|
||||
//
|
||||
// Header-only and dependency-free so the fuzz harness and the engine share one
|
||||
// source of truth.
|
||||
|
||||
#ifndef PDFENGINE_HARDENED_LIMITS_H
|
||||
#define PDFENGINE_HARDENED_LIMITS_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace pdfengine::limits {
|
||||
|
||||
// Largest input document we will even attempt to parse (1 GiB).
|
||||
inline constexpr std::uint64_t kMaxDocumentBytes = 1ull << 30;
|
||||
|
||||
// PDF hard-caps a page at 14,400 user units (200 in) per side; allow a very
|
||||
// generous multiple of that to tolerate odd-but-real documents.
|
||||
inline constexpr double kMaxPageDimensionPt = 200'000.0; // ~2,777 inches
|
||||
|
||||
// No legitimate document has this many pages; stops runaway iteration.
|
||||
inline constexpr int kMaxPageCount = 100'000;
|
||||
|
||||
// MAX_OBJECTS: ceiling on the number of content objects on a single page. Guards
|
||||
// against content-stream "object bombs" that would explode parsing/rendering
|
||||
// time and memory. No real page comes near this.
|
||||
inline constexpr int kMaxObjects = 5'000'000;
|
||||
|
||||
// Cap a single rasterised page at ~256 megapixels (≈1 GiB at 4 bytes/px). At
|
||||
// 96 dpi that is roughly a 16k × 16k page — well beyond any real render.
|
||||
inline constexpr std::int64_t kMaxRasterPixels = 256ll * 1024 * 1024;
|
||||
|
||||
// True if a raw page size (in points) is sane to render.
|
||||
inline constexpr bool pageDimensionsOk(double widthPt, double heightPt) noexcept {
|
||||
return widthPt > 0.0 && heightPt > 0.0 && widthPt <= kMaxPageDimensionPt &&
|
||||
heightPt <= kMaxPageDimensionPt;
|
||||
}
|
||||
|
||||
// True if a target raster (in pixels) fits the pixel budget without overflowing
|
||||
// the width*height*4 byte computation.
|
||||
inline constexpr bool rasterSizeOk(std::int64_t widthPx, std::int64_t heightPx) noexcept {
|
||||
if (widthPx <= 0 || heightPx <= 0) return false;
|
||||
if (widthPx > kMaxRasterPixels || heightPx > kMaxRasterPixels) return false;
|
||||
return widthPx * heightPx <= kMaxRasterPixels;
|
||||
}
|
||||
|
||||
inline constexpr bool documentSizeOk(std::uint64_t bytes) noexcept {
|
||||
return bytes > 0 && bytes <= kMaxDocumentBytes;
|
||||
}
|
||||
|
||||
inline constexpr bool pageCountOk(int pages) noexcept {
|
||||
return pages >= 0 && pages <= kMaxPageCount;
|
||||
}
|
||||
|
||||
inline constexpr bool objectCountOk(int objects) noexcept {
|
||||
return objects >= 0 && objects <= kMaxObjects;
|
||||
}
|
||||
|
||||
} // namespace pdfengine::limits
|
||||
|
||||
#endif // PDFENGINE_HARDENED_LIMITS_H
|
||||
@@ -53,6 +53,23 @@ struct GlyphBounds {
|
||||
double fontSize;
|
||||
};
|
||||
|
||||
// Result of a point hit-test against a page's glyphs (page-point space, top-left
|
||||
// origin — the same space GlyphBounds use).
|
||||
struct HitResult {
|
||||
int glyphIndex = -1; // glyph directly under the point in reading order, -1 if none
|
||||
int caret = 0; // nearest caret position (0..N) for selection anchoring
|
||||
int line = -1; // line band the point resolved to, -1 if the page has no text
|
||||
};
|
||||
|
||||
// A resolved text selection: a half-open glyph range plus its reconstructed text
|
||||
// and per-line union rectangles (for drawing the selection).
|
||||
struct TextSelection {
|
||||
int startGlyph = 0; // inclusive, reading order
|
||||
int endGlyph = 0; // exclusive
|
||||
std::string text;
|
||||
std::vector<GlyphBounds> rects; // per-line union rects (text field left empty)
|
||||
};
|
||||
|
||||
struct FontInfo {
|
||||
std::string fontName;
|
||||
std::string type; // "TrueType", "Type1", "CIDFontType0", "CIDFontType2"
|
||||
@@ -135,7 +152,21 @@ public:
|
||||
[[nodiscard]] virtual std::expected<std::string, EngineError> extractText() const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::expected<std::vector<GlyphBounds>, EngineError> extractTextWithBounds() const = 0;
|
||||
|
||||
|
||||
// Adobe-grade hit-testing & selection, layered on extractTextWithBounds().
|
||||
// Concrete (non-virtual) so every page implementation gets them for free.
|
||||
// Coordinates are in page-point space (top-left origin), matching GlyphBounds.
|
||||
|
||||
// Glyphs in reading order: clustered into lines top-to-bottom, left-to-right.
|
||||
[[nodiscard]] std::expected<std::vector<GlyphBounds>, EngineError> orderedGlyphs() const;
|
||||
|
||||
// Nearest glyph/caret to a point.
|
||||
[[nodiscard]] std::expected<HitResult, EngineError> hitGlyph(double x, double y) const;
|
||||
|
||||
// Reading-order selection between two points (e.g. drag anchor → focus).
|
||||
[[nodiscard]] std::expected<TextSelection, EngineError>
|
||||
selectRange(double ax, double ay, double bx, double by) const;
|
||||
|
||||
[[nodiscard]] virtual std::expected<PageModel, EngineError> extractDocumentModel() const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError> getFonts() const = 0;
|
||||
|
||||
Reference in New Issue
Block a user