feat: implemented hit testing, form field, content stream and base security hardening

This commit is contained in:
Furqan-14
2026-06-11 19:19:01 +05:30
parent a4131d828e
commit 377a9044af
80 changed files with 5509 additions and 150 deletions
@@ -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
+32 -1
View File
@@ -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;