diff --git a/cups/Makefile b/cups/Makefile index cfec01a14..ec2eafba2 100644 --- a/cups/Makefile +++ b/cups/Makefile @@ -82,6 +82,7 @@ TESTOBJS = \ testoptions.o \ testpwg.o \ testraster.o \ + teststring.o \ testtestpage.o \ testthreads.o \ tlscheck.o @@ -169,6 +170,7 @@ UNITTARGETS = \ testoptions \ testpwg \ testraster \ + teststring \ testtestpage \ testthreads \ tlscheck @@ -241,6 +243,9 @@ test: $(UNITTARGETS) echo Running raster API tests... ./testraster 2>>test.log ./testtestpage 2>>test.log + echo "" + echo Running string API tests... + ./teststring 2>>test.log rm -rf test.log test-debug.log .testssl fuzz: $(UNITTARGETS) @@ -644,6 +649,16 @@ testraster: testraster.o $(LIBCUPS_STATIC) $(CODE_SIGN) $(CSFLAGS) $@ +# +# teststring (dependency on static CUPS library is intentional) +# + +teststring: teststring.o $(LIBCUPS_STATIC) + echo Linking $@... + $(CC) $(LDFLAGS) $(OPTIM) -o $@ teststring.o $(LIBCUPS_STATIC) $(LIBS) + $(CODE_SIGN) $(CSFLAGS) $@ + + # # testtestpage (dependency on static CUPS library is intentional) # diff --git a/cups/string.c b/cups/string.c index efd93d919..ae9113224 100644 --- a/cups/string.c +++ b/cups/string.c @@ -371,18 +371,28 @@ cupsFormatStringv( case 'c' : // Character or character array bytes += width; - if (bufptr < bufend) + if (width <= 1) { - if (width <= 1) - { - *bufptr++ = (char)va_arg(ap, int); - } - else + // Single character - consume the argument even when the buffer is + // full so the remaining arguments stay in sync... + int ch = va_arg(ap, int); // Character + + if (bufptr < bufend) + *bufptr++ = (char)ch; + } + else + { + // Character array - consume the argument even when the buffer is + // full so the remaining arguments stay in sync... + const char *cptr = va_arg(ap, char *); + // Character array pointer + + if (bufptr < bufend) { if ((bufptr + width) > bufend) width = (int)(bufend - bufptr); - memcpy(bufptr, va_arg(ap, char *), (size_t)width); + memcpy(bufptr, cptr, (size_t)width); bufptr += width; } } diff --git a/cups/teststring.c b/cups/teststring.c new file mode 100644 index 000000000..03010c842 --- /dev/null +++ b/cups/teststring.c @@ -0,0 +1,44 @@ +// +// String API unit test program for CUPS. +// +// Copyright © 2026 by OpenPrinting. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +#include "string-private.h" +#include "test-internal.h" +#include "cups.h" + + +// +// 'main()' - Main entry. +// + +int // O - Exit status +main(void) +{ + char buffer[64], // Large output buffer + small[2]; // Buffer too small to hold output + ssize_t large_len, // Required length with a large buffer + small_len; // Required length with a small buffer + + + // Basic formatting... + testBegin("cupsFormatString('%%s %%d')"); + large_len = cupsFormatString(buffer, sizeof(buffer), "%s %d", "test", 42); + testEndMessage(large_len == 7 && !strcmp(buffer, "test 42"), "\"%s\"", buffer); + + // The returned "bytes needed" value must not depend on the output buffer + // size. A "%c" conversion followed by more arguments used to drop its + // argument when the buffer was already full, so every later argument was + // read from the wrong place (and a following "%s"/"%p" dereferenced a + // non-pointer value). + testBegin("cupsFormatString('%%c') argument sync"); + large_len = cupsFormatString(buffer, sizeof(buffer), "X%c%d", 'A', 999999); + small_len = cupsFormatString(small, sizeof(small), "X%c%d", 'A', 999999); + testEndMessage(large_len == small_len, "large=%d small=%d", (int)large_len, (int)small_len); + + return (testsPassed ? 0 : 1); +}