Re: Fix: Bug 687560 "Invalid PDF if /BP pdfmarks withnon-unique /_objdef"

"SaGS" <[email protected]> Mon, 1 Nov 2004 19:33:22 +0200
Newsgroups gmane.comp.printing.ghostscript.patches
Message-ID <[email protected]>
Hello,

Attached is a new version of the patch for bug 687560 "Invalid PDF if /BP
pdfmarks with non-unique /_objdef". I have removed pdf_obj_ref_reused() and
all changes to pdf_enter_substream(), pdf_open_aside() and
pdf_alloc_aside(). Instead, the new code does quite some "surgery" on
cos_object_ts.

_______________________________________________
gs-code-review mailing list
[email protected]
http://www.ghostscript.com/mailman/listinfo/gs-code-review
Bug687560-v3.diff (application/octet-stream, 17 KB)
Index: gdevpdfm.c
===================================================================
RCS file: /cvs/ghostscript/gs/src/gdevpdfm.c,v
retrieving revision 1.47
diff -u -r1.47 gdevpdfm.c
--- gdevpdfm.c	7 Oct 2004 05:18:34 -0000	1.47
+++ gdevpdfm.c	1 Nov 2004 05:24:06 -0000
@@ -341,6 +341,49 @@
     return pdf_put_filters(cos_stream_dict(pco), pdev, pco->input_strm, &fnames);
 }
 
+/* Ensure object has a pdf_resource_t attached */
+private int
+pdf_obj_addresource (gx_device_pdf *pdev, pdf_resource_type_t rtype, cos_object_t *pco)
+{
+    int code;
+    if (pco->pres == 0) {
+	/* (let pdf_alloc_aside() be the only one to handle pdf_resource_t creation) */
+	code = pdf_alloc_aside(pdev, PDF_RESOURCE_CHAIN(pdev, rtype, gs_no_id),
+				pdf_resource_type_structs[rtype], &pco->pres, -1);
+	if (code < 0)
+	    return code;
+	COS_FREE (pco->pres->object,"pdf_obj_addresource()");
+	pco->pres->object = pco;
+	if (pco->id) pdf_reserve_object_id (pdev, pco->pres, pco->id);
+    }
+    return 0;
+}
+
+/* Create a unique distill-time objname for driver's internal use */
+private int
+pdf_unique_objname (char *objname)
+{
+    static long n_objname = 0; /* (no need to be context-specific) */
+    /* Notes:
+     * - Names generated by different pdf_unique_objname() calls are different because
+     *   n_objname is incremented with each call.
+     * - All names generated by pdf_unique_objname() are different from all names coming
+     *   from PostScript code because they are not enclosed in "{}".
+     */
+    return sprintf (objname,"?%ld",n_objname++);
+}
+
+/* Contants that define various methods to deal with duplicate distill-time names */
+#define PDF_NAMECOLLISION_NONAME   0x0001 /* not a named object (so no collision possible)  */
+#define PDF_NAMECOLLISION_NONE     0x0002 /* no name collision				    */
+#define PDF_NAMECOLLISION_ERRCODE  0x0004 /* error, "return code"			    */
+#define PDF_NAMECOLLISION_ERRTYPE  0x0008 /* "return_error(gs_error_typecheck)"		    */
+#define PDF_NAMECOLLISION_ERRRANGE 0x0010 /* "return_error(gs_error_rangecheck)"	    */
+#define PDF_NAMECOLLISION_REBUILD  0x0020 /* "rebuid" the same object, discard old copy	    */
+#define PDF_NAMECOLLISION_KEEPOLD  0x0040 /* keep old copy, discard new one		    */
+#define PDF_NAMECOLLISION_BACKUP   0x0080 /* "backup" previous copy, then create a new obj  */
+#define PDF_NAMECOLLISION_FWDOBJ   0x0100 /* false collision due to a forward reference	    */
+
 /* ---------------- Miscellaneous pdfmarks ---------------- */
 
 /*
@@ -1052,27 +1095,174 @@
     cos_stream_t *pcs;
     cos_value_t value;
     int code;
+    int oper; /* one or more of the PDF_NAMECOLLISION_* values */
+    cos_object_t *pcoprev;
 
     code = pdf_open_page(pdev, PDF_IN_STREAM);
     if (code < 0)
 	return code;
-    code = pdf_enter_substream(pdev, resourceXObject, gs_no_id, &pres, true, 
-		pdev->CompressFonts /* Have no better switch*/);
-    if (code < 0)
-	return code;
-    pcs = (cos_stream_t *)pres->object;
-    pdev->substream_Resources = cos_dict_alloc(pdev, "start_XObject");
-    if (!pdev->substream_Resources)
-	return_error(gs_error_VMerror);
-    code = cos_dict_put(pdev->local_named_objects, objname->data,
-			    objname->size, cos_object_value(&value, pres->object));
-    if (code < 0)
-	return code;
-    pres->named = true;
-    pres->where_used = 0;	/* initially not used */
-    pcs->pres = pres;
-    *ppcs = pcs;
-    return 0;
+
+    oper = 0;
+    if (objname) {
+	/* check for duplicate name and see which methods are suitable for resolving the conflict */
+	switch (code = pdf_find_named(pdev, objname, &pcoprev)) {
+	    case gs_error_undefined:	/* new name */
+		oper = PDF_NAMECOLLISION_NONE;
+		break;
+	    case 0:			/* duplicate name */
+		if (cos_type(pcoprev) == cos_type_generic)
+		    /* (assumes named generic objects in pdev->local_named_objects are 
+			created only as a result of finding forward references) */
+		    oper = PDF_NAMECOLLISION_FWDOBJ | PDF_NAMECOLLISION_REBUILD;
+		else if (cos_type(pcoprev) != cos_type_stream)
+		    /* do not accept changing the type of an object */
+		    oper = PDF_NAMECOLLISION_ERRTYPE;
+		else if (pcoprev->is_graphics && pcoprev->is_open)
+		    /* do not accept redefining a Form XObject while it is being built */
+		    oper = PDF_NAMECOLLISION_ERRRANGE;
+		else
+		    oper = PDF_NAMECOLLISION_KEEPOLD | PDF_NAMECOLLISION_REBUILD | PDF_NAMECOLLISION_BACKUP;
+		break;
+	    default:			/* misc errors */
+		oper = PDF_NAMECOLLISION_ERRCODE;
+		break;
+	}
+    } else
+	oper = PDF_NAMECOLLISION_NONAME;
+    /* cannot rebuild an object that is already written */
+    if ((oper &PDF_NAMECOLLISION_REBUILD) && pcoprev->written)
+	oper &= ~PDF_NAMECOLLISION_REBUILD;
+    /* favor PDF_NAMECOLLISION_FWDOBJ over PDF_NAMECOLLISION_REBUILD */
+    if (oper &PDF_NAMECOLLISION_FWDOBJ)
+	oper &= ~PDF_NAMECOLLISION_REBUILD;
+    /* favor PDF_NAMECOLLISION_REBUILD over PDF_NAMECOLLISION_KEEPOLD over PDF_NAMECOLLISION_BACKUP */
+    if (oper &PDF_NAMECOLLISION_REBUILD)
+	oper &= ~(PDF_NAMECOLLISION_BACKUP|PDF_NAMECOLLISION_KEEPOLD);
+    if (oper &PDF_NAMECOLLISION_BACKUP)
+	oper &= ~(PDF_NAMECOLLISION_KEEPOLD);
+    /* the various methods of resolving name conflicts */
+    switch (oper) {
+	case PDF_NAMECOLLISION_ERRCODE:
+	    return code;
+	case PDF_NAMECOLLISION_ERRTYPE:
+	    return_error(gs_error_typecheck);
+	case PDF_NAMECOLLISION_ERRRANGE:
+	    return_error(gs_error_rangecheck);
+	default:
+	    /* should never happen; if it does, the decision code above is incomplete */
+	    return_error(gs_error_unknownerror);
+	case PDF_NAMECOLLISION_FWDOBJ:
+	    /* false collision due to a forward reference, resolved by "continuing" with
+	     * the old object from where pdf_replace_names() left off
+	     * - convert the old object into a stream
+	     * - allocate a pdf_resource_t if not already done
+	     * - continue as for PDF_NAMECOLLISION_REBUILD
+	     */
+	    if ((code = cos_become (pcoprev, cos_type_stream))                < 0 ||
+		(code = pdf_obj_addresource (pdev, resourceXObject, pcoprev)) < 0    )
+		return code;
+	    pcoprev->is_graphics = true;
+	    pres = pcoprev->pres;
+	    pres->named = true;
+	    pres->where_used = 0;	/* initially not used */
+	    oper = PDF_NAMECOLLISION_REBUILD; /* continue as for "rebuilding" the object...*/
+	case PDF_NAMECOLLISION_REBUILD:	{
+	    /* "rebuid" the same object, replacing the old copy
+	     * - create a new stream+resource, without an id
+	     * - do some "surgery" to keep the contents from the newly allocated stream
+	     *   and everything else (id, resource) from the old one
+	     * - "reopen" the old object
+	     * - take care the contents to be written to the old stream object
+	     * - free what's not needed any more (new resource, new emptied stream)
+	     * - do not add the object to pdev->local_named_objects, it's already there
+	     * - pcoprev->named/where_used already in place
+	     */
+	    code = pdf_enter_substream(pdev, resourceXObject, gs_no_id, &pres, false,
+				       pdev->CompressFonts /* Have no better switch*/);
+	    if (code < 0)
+		return code;
+	    if ((code = cos_stream_move_all ((cos_stream_t *)pcoprev,(cos_stream_t *)pres->object)) < 0)
+		return code;
+	    pcoprev->is_open = pres->object->is_open;
+	    cos_write_stream_redir (pdev->strm, (cos_stream_t *)pcoprev);
+	    if ((code = pdf_cancel_resource (pdev,pres,resourceXObject)) < 0)
+		return code;
+	    pdev->substream_Resources = cos_dict_alloc(pdev, "start_XObject(REBUILD.substreamresources)");
+	    if (!pdev->substream_Resources)
+		return_error(gs_error_VMerror);
+	    *ppcs = (cos_stream_t *)pcoprev;
+	    return 0;
+	    }
+	case PDF_NAMECOLLISION_KEEPOLD:
+	    /* keep old copy, discard new one
+	     * - create a "temporary object" without an ID
+	     * - do not add the object to pdev->local_named_objects, because:
+	     *	    - adding it removes the old object
+	     *	    - it does not have an ID, so it must not be accessible by name since 
+	     *	      we cannot create references ("n 0 R") to it
+	     * - pdfmark_EP() will discard the object
+	     */
+	    code = pdf_enter_substream(pdev, resourceXObject, gs_no_id, &pres, false,
+			pdev->CompressFonts /* Have no better switch*/);
+	    if (code < 0)
+		return code;
+	    pcs = (cos_stream_t *)pres->object;
+	    pdev->substream_Resources = cos_dict_alloc(pdev, "start_XObject(KEEPOLD)");
+	    if (!pdev->substream_Resources)
+		return_error(gs_error_VMerror);
+	    pres->named = true;
+	    pres->where_used = 0;	/* initially not used */
+	    pcs->pres = pres;
+	    *ppcs = pcs;
+	    return 0;
+	case PDF_NAMECOLLISION_BACKUP:
+	    /* "backup" previous copy, then create a new object
+	     * (an object name is treated like a variable: pdfmarks that create objects
+	     * "assign" a (new) value to it, and each reference to the object takes
+	     * into account the most recent value (in execution order))
+	     * - replace the key in pdev->local_named_objects with a unique one
+	     * - continue as if no name conflict
+	     */
+	    {	char unique[1+10+1]; /* '?' + long + '\0' */
+	    code = cos_dict_rename(pdev->local_named_objects,
+				   objname->data,objname->size,
+				   (const byte *)unique,pdf_unique_objname(unique));
+	    if (code < 0)
+		return code;
+	    }
+	    oper = PDF_NAMECOLLISION_NONE; /* continue as for PDF_NAMECOLLISION_NONE... */
+	case PDF_NAMECOLLISION_NONAME:
+	   /* object does not have a name
+	    * - same as PDF_NAMECOLLISION_NONE, except the object is not registered
+	    *   in pdev->local_named_objects.
+	    */
+	case PDF_NAMECOLLISION_NONE:
+	   /* no name conflict
+	    * - create the object normally and initialize all its fields
+	    * - do assign the object an id
+	    * - register the object in pdev->local_named_objects
+	    */
+	    code = pdf_enter_substream(pdev, resourceXObject, gs_no_id, &pres, true,
+				       pdev->CompressFonts /* Have no better switch*/);
+	    if (code < 0)
+		return code;
+	    pcs = (cos_stream_t *)pres->object;
+	    pdev->substream_Resources = cos_dict_alloc(pdev, "start_XObject(NONAMECOLLISION)");
+	    if (!pdev->substream_Resources)
+		return_error(gs_error_VMerror);
+	    if (!(oper &PDF_NAMECOLLISION_NONAME))
+		code = cos_dict_put(pdev->local_named_objects, objname->data,
+				    objname->size, cos_object_value(&value, pres->object));
+	    else
+		DO_NOTHING;
+	    if (code < 0)
+		return code;
+	    pres->named = !(oper &PDF_NAMECOLLISION_NONAME);
+	    pres->where_used = 0;	/* initially not used */
+	    pcs->pres = pres;
+	    *ppcs = pcs;
+	    return 0;
+    }
 }
 
 /* PS pdfmark */
@@ -1463,13 +1653,23 @@
 	   const gs_matrix * pctm, const gs_param_string * no_objname)
 {
     int code;
+    pdf_resource_t *pres_cancel;
 
-    code = pdf_add_procsets(pdev->substream_Resources, pdev->procsets);
-    if (code < 0)
-	return code;
+    pres_cancel = pdev->accumulating_substream_resource;
+    if (pres_cancel->object->id > 0) pres_cancel = NULL;
+    if (!pres_cancel) {
+	code = pdf_add_procsets(pdev->substream_Resources, pdev->procsets);
+	if (code < 0)
+	    return code;
+    }
     code = pdf_exit_substream(pdev);
     if (code < 0)
 	return code;
+    if (pres_cancel) {
+	code = pdf_cancel_resource(pdev, pres_cancel, resourceXObject);
+	if (code < 0)
+	    return code;
+	}
     return 0;
 }
 
@@ -1483,10 +1683,28 @@
 
     if (count != 1)
 	return_error(gs_error_rangecheck);
-    if ((code = pdf_get_named(pdev, &pairs[0], cos_type_stream, &pco)) < 0)
+    if ((code = pdf_refer_named(pdev, &pairs[0], &pco)) < 0)
+	/* misc errors */
 	return code;
-    if (pco->is_open || !pco->is_graphics)
-	return_error(gs_error_rangecheck);
+    if (cos_type(pco) == cos_type_generic) {
+	/* forward reference
+	 * - use the object without additional checks
+	 * - be sure it has an associated pdf_resource_t structure, since we need to 
+	 *   store some information in object->pres->where_used
+	 * - don't turn it into a stream object (let it a generic one), since the object
+	 *   is not yet created (from the PostScript code point of view) and references 
+	 *   to it are still "forward"
+	 */
+	if ((code = pdf_obj_addresource (pdev, resourceXObject, pco)) < 0)
+	    return code;
+	pco->pres->named = true;
+	pco->is_graphics = true;
+    } else if (cos_type(pco) == cos_type_stream) {
+	/* object exists as a stream */
+	if (!pco->is_graphics || pco->is_open)
+	    return_error(gs_error_rangecheck);
+    } else
+	return_error(gs_error_typecheck);
     code = pdf_open_contents(pdev, PDF_IN_STREAM);
     if (code < 0)
 	return code;
Index: gdevpdfo.c
===================================================================
RCS file: /cvs/ghostscript/gs/src/gdevpdfo.c,v
retrieving revision 1.30
diff -u -r1.30 gdevpdfo.c
--- gdevpdfo.c	8 Jun 2004 11:43:03 -0000	1.30
+++ gdevpdfo.c	1 Nov 2004 05:24:12 -0000
@@ -986,6 +986,36 @@
     return cos_dict_put_c_key(pcd, key, cos_c_string_value(&cvalue, value));
 }
 
+/* "Rename" a dict entry by changing the key without affecting the value */
+int
+cos_dict_rename(cos_dict_t *pcd, const byte *oldkey_data, uint oldkey_size,
+		  const byte *newkey_data, uint newkey_size)
+{
+    gs_memory_t *mem = COS_OBJECT_MEMORY(pcd);
+    cos_dict_element_t **ppcde = &pcd->elements;
+    cos_dict_element_t *next;
+    byte *copied_key_data;
+    int code;
+
+    while ((next = *ppcde) != 0)
+	if (bytes_compare(next->key.data, next->key.size, oldkey_data, oldkey_size))
+	    ppcde = &next->next;
+	else {
+	    copied_key_data = gs_alloc_string(mem, newkey_size,"cos_dict_rename(new key)");
+	    if (copied_key_data == 0)
+		return_error(gs_error_VMerror);
+	    memcpy(copied_key_data, newkey_data, newkey_size);
+	    if (next->owns_key)
+		gs_free_const_string(mem, next->key.data, next->key.size,
+					"cos_dict_rename(old key)");
+	    next->key.data = copied_key_data;
+	    next->key.size = newkey_size;
+	    next->owns_key = true;
+	    return 0;
+	}
+    return_error(gs_error_undefined);
+}
+
 /* Move all the elements from one dict to another. */
 int
 cos_dict_move_all(cos_dict_t *pcdto, cos_dict_t *pcdfrom)
@@ -1399,6 +1429,27 @@
     return code;
 }
 
+/* move all [dict] elements and [stream proper] contents from one stream to another
+ * - existing key-value pairs in the destination object are removed (this is different
+ *   from cos_dict_move_all()'s behaviour)
+ */
+int
+cos_stream_move_all(cos_stream_t *pcsto, cos_stream_t *pcsfrom)
+{
+    int code;
+    if (pcsto->input_strm)
+	sclose (pcsto->input_strm);
+    if ((code = cos_stream_release_pieces (pcsto)) < 0)
+	return code;
+    cos_stream_release (COS_OBJECT(pcsto), "cos_stream_move_all()");
+    if ((code = cos_dict_move_all (cos_stream_dict(pcsto),cos_stream_dict(pcsfrom))) < 0)
+	return code;
+    pcsto->pieces = pcsfrom->pieces, pcsfrom->pieces = 0;
+    pcsto->length = pcsfrom->length, pcsfrom->length = 0;
+    pcsto->input_strm = pcsfrom->input_strm, pcsfrom->input_strm = 0;
+    return 0;
+}
+
 /* Release the last contents piece of a stream object. */
 /* Warning : this function can't release pieces if another stream is written after them. */
 int
@@ -1501,6 +1552,20 @@
     gs_free_object(mem, s, cname);
     return 0;
 }
+/* redirect a cos_write_stream to a different cos_stream */
+cos_stream_t *
+cos_write_stream_redir (stream *s, cos_stream_t *pcs)
+{
+    cos_stream_t *pcsprev;
+    cos_write_stream_state_t *ss;
+
+    while(s->procs.process != cos_s_procs.process)
+	s = s->strm;
+    ss = (cos_write_stream_state_t *)s->state;
+    pcsprev = ss->pcs;
+    ss->pcs = pcs;
+    return pcsprev; /* return previous cos_stream */
+}
 
 /* Get cos stream from pipeline. */
 cos_stream_t *
Index: gdevpdfo.h
===================================================================
RCS file: /cvs/ghostscript/gs/src/gdevpdfo.h,v
retrieving revision 1.19
diff -u -r1.19 gdevpdfo.h
--- gdevpdfo.h	8 Jun 2004 11:43:04 -0000	1.19
+++ gdevpdfo.h	1 Nov 2004 05:24:14 -0000
@@ -251,12 +251,17 @@
 int cos_dict_put_string(cos_dict_t *, const byte *, uint, const byte *, uint);
 int cos_dict_put_string_copy(cos_dict_t *pcd, const char *key, const char *value);
 int cos_dict_put_c_strings(cos_dict_t *, const char *, const char *);
+/* rename an entry by changing the key without affecting the value */
+int cos_dict_rename(cos_dict_t *pcd, const byte *oldkey_data, uint oldkey_size,
+		  const byte *newkey_data, uint newkey_size);
 /* move all the elements from one dict to another */
 int cos_dict_move_all(cos_dict_t *, cos_dict_t *);
     /* stream */
 int cos_stream_add(cos_stream_t *, uint);
 int cos_stream_add_bytes(cos_stream_t *, const byte *, uint);
 int cos_stream_add_stream_contents(cos_stream_t *, stream *);
+/* move all [dict] elements and [stream proper] contents from one stream to another */
+int cos_stream_move_all(cos_stream_t *pcsto, cos_stream_t *pcsfrom);
 int cos_stream_release_pieces(cos_stream_t *pcs);
 cos_dict_t *cos_stream_dict(cos_stream_t *);
 
@@ -297,6 +302,8 @@
 stream *cos_write_stream_alloc(cos_stream_t *pcs, gx_device_pdf *pdev,
 			       client_name_t cname);
 
+/* redirect a cos_write_stream to a different cos_stream */
+cos_stream_t *cos_write_stream_redir (stream *s, cos_stream_t *pcs);
 /* Get cos stream from pipeline. */
 cos_stream_t * cos_stream_from_pipeline(stream *s);
 /* Get cos write stream from pipeline. */