cairo/test/pdf-large-objstm.c
calixteman fc35ffb77e Fix invalid pdf generation with too many objects in object stream
Fixes cairo issue #907.

In the xref table, the generation number is limited to 65535 (2^16 - 1)
and it's used to identify objects in object streams.
When generating a pdf with more than 65536 objects in object streams,
the generation number overflows and restarts at 0, which causes
invalid references in the xref table.
2025-11-16 22:37:53 +01:00

178 lines
4.6 KiB
C

/*
* Copyright © 2023 Adrian Johnson
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Author: Adrian Johnson <ajohnson@redneon.com>
*/
#include "cairo-test.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* __unix__ */
#endif
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <cairo.h>
#include <cairo-pdf.h>
/* Test PDF with a large number of objects to ensure that
* object streams are correctly handled.
*/
#define BASENAME "pdf-large-objstm"
static void
draw_link(cairo_t *cr)
{
cairo_tag_begin(cr, CAIRO_TAG_LINK, "uri='http://www.mozilla.org/'");
cairo_move_to (cr, 0, 0);
cairo_show_text (cr, "a");
cairo_tag_end(cr, CAIRO_TAG_LINK);
}
#ifdef HAVE_MMAP
static cairo_test_status_t
check_contains_string(cairo_test_context_t *ctx, const void *hay, size_t size, const char *needle)
{
if (memmem(hay, size, needle, strlen(needle)))
return CAIRO_TEST_SUCCESS;
cairo_test_log (ctx, "Failed to find expected string in generated PDF: %s\n", needle);
return CAIRO_TEST_FAILURE;
}
#endif
static cairo_test_status_t
check_created_pdf(cairo_test_context_t *ctx, const char* filename)
{
cairo_test_status_t result = CAIRO_TEST_SUCCESS;
int fd;
struct stat st;
#ifdef HAVE_MMAP
void *contents;
#endif
fd = open(filename, O_RDONLY, 0);
if (fd < 0) {
cairo_test_log (ctx, "Failed to open generated PDF file %s: %s\n", filename, strerror(errno));
return CAIRO_TEST_FAILURE;
}
if (fstat(fd, &st) == -1)
{
cairo_test_log (ctx, "Failed to stat generated PDF file %s: %s\n", filename, strerror(errno));
close(fd);
return CAIRO_TEST_FAILURE;
}
if (st.st_size == 0)
{
cairo_test_log (ctx, "Generated PDF file %s is empty\n", filename);
close(fd);
return CAIRO_TEST_FAILURE;
}
#ifdef HAVE_MMAP
printf("Checking generated PDF file %s\n", filename);
contents = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (contents == NULL)
{
cairo_test_log (ctx, "Failed to mmap generated PDF file %s: %s\n", filename, strerror(errno));
close(fd);
return CAIRO_TEST_FAILURE;
}
/* check metadata */
result |= check_contains_string(ctx, contents, st.st_size, "/N 65536");
result |= check_contains_string(ctx, contents, st.st_size, "/N 14470");
munmap(contents, st.st_size);
#endif
close(fd);
return result;
}
static cairo_test_status_t
create_pdf (cairo_test_context_t *ctx)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_test_status_t result;
char *filename;
const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
xasprintf (&filename, "%s/%s.pdf", path, BASENAME);
surface = cairo_pdf_surface_create (filename, 100, 100);
cr = cairo_create(surface);
for (int i = 0; i < 40000; i++) {
draw_link(cr);
}
cairo_show_page (cr);
cairo_surface_destroy (surface);
cairo_destroy (cr);
result = check_created_pdf(ctx, filename);
free (filename);
return result;
}
static cairo_test_status_t
preamble (cairo_test_context_t *ctx)
{
cairo_test_status_t result;
if (! cairo_test_is_target_enabled (ctx, "pdf"))
return CAIRO_TEST_UNTESTED;
result = create_pdf (ctx);
return result;
}
CAIRO_TEST (pdf_large_objstm,
"Test with a lot of PDF objects",
"source", /* keywords */
NULL, /* requirements */
100, 100,
preamble, NULL)