summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorMichael R Sweet <michael.r.sweet@gmail.com>2019-04-26 11:05:27 -0400
committerMichael R Sweet <michael.r.sweet@gmail.com>2019-04-26 11:05:27 -0400
commitf1ac9f5889e6a9a8996041ab46e6ca1e0cb8a238 (patch)
treeb28d47f3580aa4ea386b7a09c6c6340f16cdbd80 /tools
parenta76950482b3327f096e7188234173d739101aef1 (diff)
downloadcups-f1ac9f5889e6a9a8996041ab46e6ca1e0cb8a238.tar.gz
Move example ipptool files to the examples directory.
Build tools directory for IPP tools.
Diffstat (limited to 'tools')
-rw-r--r--tools/Dependencies29
-rw-r--r--tools/Makefile243
-rw-r--r--tools/dither.h68
-rw-r--r--tools/ippevecommon.h26
-rw-r--r--tools/ippevepcl.c530
-rw-r--r--tools/ippeveprinter.c8080
-rw-r--r--tools/ippeveps.c1138
-rw-r--r--tools/ippfind.c2847
-rw-r--r--tools/ipptool.c5122
-rw-r--r--tools/printer-png.h447
-rw-r--r--tools/printer.opacitybin0 -> 42248 bytes
-rw-r--r--tools/printer.pngbin0 -> 7079 bytes
12 files changed, 18530 insertions, 0 deletions
diff --git a/tools/Dependencies b/tools/Dependencies
new file mode 100644
index 000000000..9a1043df8
--- /dev/null
+++ b/tools/Dependencies
@@ -0,0 +1,29 @@
+ippevepcl.o: ippevepcl.c ippevecommon.h ../cups/cups.h ../cups/file.h \
+ ../cups/versioning.h ../cups/ipp.h ../cups/http.h ../cups/array.h \
+ ../cups/language.h ../cups/pwg.h ../cups/raster.h \
+ ../cups/string-private.h ../config.h dither.h
+ippeveprinter.o: ippeveprinter.c ../cups/cups-private.h \
+ ../cups/string-private.h ../config.h ../cups/versioning.h \
+ ../cups/array-private.h ../cups/array.h ../cups/ipp-private.h \
+ ../cups/cups.h ../cups/file.h ../cups/ipp.h ../cups/http.h \
+ ../cups/language.h ../cups/pwg.h ../cups/http-private.h \
+ ../cups/language-private.h ../cups/transcode.h ../cups/pwg-private.h \
+ ../cups/thread-private.h ../cups/ppd-private.h ../cups/ppd.h \
+ ../cups/raster.h printer-png.h
+ippeveps.o: ippeveps.c ippevecommon.h ../cups/cups.h ../cups/file.h \
+ ../cups/versioning.h ../cups/ipp.h ../cups/http.h ../cups/array.h \
+ ../cups/language.h ../cups/pwg.h ../cups/raster.h \
+ ../cups/string-private.h ../config.h ../cups/ppd-private.h \
+ ../cups/ppd.h ../cups/pwg-private.h
+ippfind.o: ippfind.c ../cups/cups-private.h ../cups/string-private.h \
+ ../config.h ../cups/versioning.h ../cups/array-private.h \
+ ../cups/array.h ../cups/ipp-private.h ../cups/cups.h ../cups/file.h \
+ ../cups/ipp.h ../cups/http.h ../cups/language.h ../cups/pwg.h \
+ ../cups/http-private.h ../cups/language-private.h ../cups/transcode.h \
+ ../cups/pwg-private.h ../cups/thread-private.h
+ipptool.o: ipptool.c ../cups/cups-private.h ../cups/string-private.h \
+ ../config.h ../cups/versioning.h ../cups/array-private.h \
+ ../cups/array.h ../cups/ipp-private.h ../cups/cups.h ../cups/file.h \
+ ../cups/ipp.h ../cups/http.h ../cups/language.h ../cups/pwg.h \
+ ../cups/http-private.h ../cups/language-private.h ../cups/transcode.h \
+ ../cups/pwg-private.h ../cups/thread-private.h
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644
index 000000000..33ae0dfc9
--- /dev/null
+++ b/tools/Makefile
@@ -0,0 +1,243 @@
+#
+# IPP tools makefile for CUPS.
+#
+# Copyright © 2007-2019 by Apple Inc.
+# Copyright © 1997-2006 by Easy Software Products, all rights reserved.
+#
+# Licensed under Apache License v2.0. See the file "LICENSE" for more
+# information.
+#
+
+include ../Makedefs
+
+
+OBJS = \
+ ippevepcl.o \
+ ippeveprinter.o \
+ ippeveps.o \
+ ippfind.o \
+ ipptool.o
+IPPTOOLS = \
+ ippeveprinter \
+ $(IPPFIND_BIN) \
+ ipptool
+TARGETS = \
+ $(IPPEVECOMMANDS) \
+ $(IPPTOOLS) \
+ $(LOCALTARGET)
+
+
+#
+# Make all targets...
+#
+
+all: $(TARGETS)
+
+
+#
+# Make library targets...
+#
+
+libs:
+
+
+#
+# Make unit tests...
+#
+
+unittests:
+
+
+#
+# Clean all object files...
+#
+
+clean:
+ $(RM) $(IPPTOOLS) $(IPPEVECOMMANDS) $(OBJS)
+ $(RM) ippeveprinter-static ippfind-static ipptool-static
+
+
+#
+# Update dependencies (without system header dependencies...)
+#
+
+depend:
+ $(CC) -MM $(ALL_CFLAGS) $(OBJS:.o=.c) >Dependencies
+
+
+#
+# Install all targets...
+#
+
+install: all install-data install-headers install-libs install-exec
+
+
+#
+# Install data files...
+#
+
+install-data:
+
+
+#
+# Install programs...
+#
+
+install-exec:
+ echo Installing IPP tools in $(BINDIR)...
+ $(INSTALL_DIR) -m 755 $(BINDIR)
+ for file in $(IPPTOOLS); do \
+ $(INSTALL_BIN) $$file $(BINDIR); \
+ done
+ echo Installing printer commands in $(SERVERBIN)/ippeveprinter...
+ $(INSTALL_DIR) -m 755 $(SERVERBIN)/ippeveprinter
+ for file in $(IPPEVECOMMANDS); do \
+ $(INSTALL_BIN) $$file $(SERVERBIN)/ippeveprinter; \
+ done
+ if test "x$(SYMROOT)" != "x"; then \
+ $(INSTALL_DIR) $(SYMROOT); \
+ for file in $(IPPTOOLS) $(IPPEVECOMMANDS); do
+ cp $$file $(SYMROOT); \
+ dsymutil $(SYMROOT)/$$file; \
+ done; \
+ fi
+
+
+#
+# Install headers...
+#
+
+install-headers:
+
+
+#
+# Install libraries...
+#
+
+install-libs:
+
+
+#
+# Unnstall all targets...
+#
+
+uninstall:
+ echo Uninstalling IPP tools from $(BINDIR)...
+ for file in $(IPPTOOLS); do \
+ $(RM) $(BINDIR)/$$file; \
+ done
+ -$(RMDIR) $(BINDIR)
+ echo Uninstalling print commands from $(SERVERBIN)/ippeveprinter...
+ for file in $(IPPEVECOMMANDS); do \
+ $(RM) $(SERVERBIN)/ippeveprinter/$$file; \
+ done
+ -$(RMDIR) $(SERVERBIN)/ippeveprinter
+
+
+#
+# Local programs (not built when cross-compiling...)
+#
+
+local: ippeveprinter-static ipptool-static
+
+
+#
+# ippeveprinter
+#
+
+ippeveprinter: ippeveprinter.o ../cups/$(LIBCUPSSTATIC)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ippeveprinter.o $(LIBS)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# ippeveprinter-static
+#
+
+ippeveprinter-static: ippeveprinter.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ippeveprinter.o ../cups/$(LIBCUPSSTATIC) \
+ $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# ippevepcl
+#
+
+ippevepcl: ippevepcl.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ippevepcl.o $(LIBS)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# ippeveps
+#
+
+ippeveps: ippeveps.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ippeveps.o $(LIBS)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# ippfind
+#
+
+ippfind: ippfind.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ippfind.o $(LIBS)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# ippfind-static
+#
+
+ippfind-static: ippfind.o ../cups/$(LIBCUPSSTATIC)
+ echo Linking $@
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ippfind.o ../cups/$(LIBCUPSSTATIC) \
+ $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# ipptool
+#
+
+ipptool: ipptool.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ipptool.o $(LIBS)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# ipptool-static
+#
+
+ipptool-static: ipptool.o ../cups/$(LIBCUPSSTATIC)
+ echo Linking $@...
+ $(LD_CC) $(ALL_LDFLAGS) -o $@ ipptool.o ../cups/$(LIBCUPSSTATIC) \
+ $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ)
+ $(CODE_SIGN) -s "$(CODE_SIGN_IDENTITY)" $@
+
+
+#
+# Generate the header containing the data for printer.png...
+#
+
+pngheader:
+ echo "Generating printer-png.h from printer.png..."
+ echo "static const unsigned char printer_png[] =" >printer-png.h
+ echo "{" >>printer-png.h
+ od -t x1 printer.png | cut -b12- | awk '{printf(" "); for (i = 1; i <= NF; i ++) printf("0x%s,", $$i); print "";}' >>printer-png.h
+ echo "};" >>printer-png.h
+
+
+#
+# Dependencies...
+#
+
+include Dependencies
diff --git a/tools/dither.h b/tools/dither.h
new file mode 100644
index 000000000..e723d4e60
--- /dev/null
+++ b/tools/dither.h
@@ -0,0 +1,68 @@
+/* Simple ordered dither threshold matrix */
+const unsigned char threshold[64][64] =
+{
+ { 0, 235, 138, 22, 45, 98, 217, 2, 45, 98, 7, 185, 45, 97, 6, 192, 110, 37, 110, 37, 97, 45, 18, 148, 109, 37, 97, 45, 7, 185, 45, 97, 6, 192, 109, 37, 57, 82, 165, 12, 26, 130, 190, 6, 119, 31, 97, 45, 179, 8, 109, 37, 97, 45, 140, 22, 109, 37, 57, 82, 9, 177, 24, 135 },
+ { 109, 37, 57, 82, 20, 143, 57, 82, 146, 19, 57, 82, 15, 158, 87, 53, 20, 144, 13, 162, 138, 22, 53, 87, 162, 13, 138, 22, 57, 82, 22, 140, 57, 82, 153, 16, 119, 31, 109, 37, 109, 37, 57, 81, 19, 147, 213, 2, 53, 87, 21, 142, 13, 164, 53, 87, 187, 6, 152, 17, 119, 32, 110, 37 },
+ { 24, 135, 12, 167, 97, 45, 17, 151, 45, 97, 32, 119, 26, 130, 119, 32, 81, 58, 30, 121, 81, 58, 181, 8, 119, 32, 97, 45, 17, 151, 45, 97, 161, 14, 45, 97, 213, 2, 135, 24, 7, 183, 138, 22, 109, 37, 58, 81, 121, 30, 109, 37, 58, 81, 30, 121, 58, 81, 127, 27, 58, 81, 165, 12 },
+ { 58, 81, 31, 119, 5, 197, 58, 81, 9, 175, 206, 3, 58, 81, 10, 174, 222, 1, 45, 97, 206, 3, 124, 29, 58, 81, 4, 200, 58, 81, 2, 217, 58, 81, 27, 127, 108, 37, 58, 81, 30, 121, 58, 81, 167, 12, 25, 132, 15, 158, 5, 197, 135, 24, 206, 3, 142, 21, 45, 97, 203, 3, 119, 32 },
+ { 6, 190, 108, 38, 97, 45, 143, 20, 108, 38, 97, 45, 148, 18, 108, 38, 58, 81, 148, 18, 119, 32, 97, 45, 16, 153, 97, 45, 143, 20, 46, 97, 32, 119, 6, 187, 25, 132, 13, 164, 46, 97, 1, 222, 119, 32, 108, 38, 108, 38, 108, 38, 108, 38, 108, 38, 58, 81, 8, 179, 123, 29, 97, 46 },
+ { 58, 81, 161, 14, 147, 19, 53, 87, 22, 138, 14, 159, 87, 53, 22, 138, 14, 159, 108, 38, 87, 53, 177, 9, 53, 87, 14, 161, 58, 81, 22, 137, 12, 167, 108, 38, 108, 38, 81, 58, 153, 16, 123, 29, 53, 87, 187, 6, 162, 13, 132, 25, 11, 168, 25, 132, 12, 165, 119, 32, 81, 58, 14, 161 },
+ { 140, 22, 108, 38, 81, 58, 8, 181, 108, 38, 97, 46, 8, 181, 58, 81, 31, 119, 7, 185, 147, 19, 58, 81, 127, 27, 97, 46, 8, 179, 108, 38, 58, 81, 21, 142, 13, 162, 5, 194, 108, 38, 58, 81, 15, 156, 87, 53, 30, 121, 108, 38, 108, 38, 58, 81, 127, 27, 97, 46, 16, 153, 97, 46 },
+ { 58, 81, 2, 217, 123, 29, 36, 111, 1, 227, 138, 22, 97, 46, 4, 203, 108, 38, 108, 38, 97, 46, 23, 137, 222, 1, 112, 36, 58, 81, 6, 192, 137, 23, 53, 87, 30, 121, 58, 80, 23, 137, 183, 7, 36, 111, 26, 130, 59, 80, 0, 235, 130, 26, 192, 6, 46, 97, 222, 1, 53, 87, 194, 5 },
+ { 17, 151, 46, 97, 172, 10, 59, 80, 27, 127, 59, 80, 18, 148, 59, 80, 172, 10, 24, 135, 4, 200, 80, 59, 36, 112, 167, 12, 137, 23, 108, 38, 97, 46, 0, 235, 46, 97, 144, 20, 46, 97, 127, 27, 53, 87, 3, 206, 147, 19, 108, 38, 108, 38, 80, 59, 151, 17, 80, 59, 27, 127, 96, 46 },
+ { 58, 81, 126, 27, 96, 46, 15, 156, 46, 97, 172, 10, 46, 96, 14, 159, 119, 32, 59, 80, 32, 119, 156, 15, 53, 87, 27, 127, 59, 80, 21, 142, 10, 172, 80, 59, 177, 9, 80, 59, 2, 213, 59, 80, 14, 161, 108, 38, 59, 79, 23, 137, 11, 168, 137, 23, 46, 96, 32, 119, 13, 164, 140, 22 },
+ { 6, 190, 152, 17, 112, 36, 59, 79, 6, 187, 59, 79, 30, 121, 108, 38, 96, 46, 15, 156, 46, 96, 29, 123, 183, 7, 46, 96, 209, 3, 46, 96, 32, 118, 26, 130, 96, 46, 15, 158, 46, 96, 16, 153, 46, 96, 32, 118, 10, 172, 108, 38, 108, 38, 59, 79, 179, 8, 141, 21, 59, 79, 32, 119 },
+ { 87, 53, 36, 112, 203, 4, 137, 23, 96, 46, 23, 137, 4, 200, 24, 135, 235, 0, 87, 53, 8, 179, 108, 38, 87, 53, 15, 156, 53, 87, 156, 15, 59, 79, 156, 15, 119, 32, 59, 79, 30, 121, 59, 79, 6, 190, 151, 17, 59, 79, 187, 6, 26, 130, 3, 206, 108, 38, 96, 46, 206, 3, 110, 37 },
+ { 142, 21, 59, 79, 28, 126, 59, 79, 144, 20, 59, 79, 119, 32, 60, 79, 32, 118, 27, 129, 87, 53, 3, 209, 118, 32, 60, 79, 126, 28, 96, 46, 5, 194, 46, 96, 197, 5, 134, 24, 7, 183, 137, 23, 46, 96, 36, 112, 28, 126, 108, 38, 108, 38, 60, 79, 148, 18, 118, 32, 60, 79, 167, 12 },
+ { 37, 109, 174, 10, 46, 95, 10, 174, 47, 95, 170, 11, 47, 95, 9, 177, 47, 95, 14, 159, 118, 32, 60, 79, 170, 11, 134, 24, 183, 7, 112, 36, 79, 60, 140, 22, 108, 38, 107, 38, 107, 39, 79, 60, 17, 149, 60, 79, 213, 2, 24, 135, 177, 9, 159, 14, 47, 95, 9, 175, 137, 23, 47, 95 },
+ { 0, 235, 60, 79, 18, 149, 87, 53, 217, 2, 53, 87, 156, 15, 86, 53, 16, 153, 54, 86, 167, 12, 137, 23, 107, 39, 60, 79, 36, 112, 172, 10, 147, 19, 47, 95, 19, 147, 9, 175, 132, 25, 1, 227, 36, 110, 9, 175, 112, 36, 60, 79, 30, 121, 60, 79, 126, 28, 112, 36, 60, 79, 19, 147 },
+ { 37, 109, 140, 22, 47, 95, 30, 121, 60, 78, 30, 121, 61, 78, 32, 118, 61, 78, 200, 4, 112, 36, 61, 78, 19, 147, 227, 1, 47, 95, 27, 129, 86, 54, 2, 217, 54, 86, 28, 126, 61, 78, 126, 28, 61, 78, 129, 26, 95, 47, 15, 158, 47, 95, 20, 144, 1, 222, 95, 47, 6, 190, 47, 95 },
+ { 13, 164, 61, 78, 4, 200, 162, 13, 24, 134, 183, 7, 24, 134, 213, 2, 142, 21, 123, 29, 95, 47, 192, 6, 107, 39, 86, 54, 153, 16, 54, 86, 30, 121, 61, 78, 30, 121, 95, 47, 170, 11, 47, 95, 10, 172, 47, 94, 179, 8, 61, 78, 4, 200, 107, 39, 61, 78, 22, 140, 61, 78, 23, 137 },
+ { 37, 109, 31, 120, 107, 39, 107, 39, 107, 39, 107, 39, 61, 78, 121, 30, 107, 39, 61, 78, 19, 146, 86, 54, 23, 137, 13, 164, 61, 77, 200, 4, 159, 14, 131, 25, 13, 164, 4, 200, 86, 54, 18, 148, 54, 86, 203, 4, 94, 47, 19, 146, 47, 94, 23, 137, 13, 161, 47, 94, 17, 151, 47, 94 },
+ { 26, 131, 6, 187, 158, 15, 26, 131, 4, 203, 26, 131, 177, 9, 47, 94, 14, 159, 10, 174, 94, 47, 151, 17, 61, 77, 32, 118, 27, 129, 118, 32, 107, 39, 107, 39, 86, 54, 126, 28, 118, 32, 61, 77, 31, 120, 77, 61, 126, 28, 86, 54, 14, 161, 107, 39, 77, 61, 6, 190, 61, 77, 3, 206 },
+ { 107, 39, 61, 77, 121, 30, 86, 54, 121, 30, 107, 39, 61, 77, 16, 153, 107, 39, 86, 54, 1, 222, 94, 47, 8, 179, 47, 94, 174, 10, 54, 86, 11, 168, 5, 194, 144, 20, 86, 54, 17, 152, 3, 209, 162, 13, 134, 24, 12, 167, 123, 29, 61, 77, 7, 185, 136, 23, 47, 94, 32, 118, 27, 127 },
+ { 164, 13, 144, 20, 94, 47, 174, 10, 61, 77, 11, 170, 123, 29, 37, 110, 5, 194, 123, 29, 86, 54, 20, 143, 61, 77, 3, 206, 61, 77, 18, 148, 118, 32, 85, 54, 32, 118, 177, 9, 85, 55, 28, 126, 106, 39, 106, 39, 61, 77, 0, 235, 118, 32, 106, 39, 61, 77, 19, 147, 177, 9, 81, 58 },
+ { 106, 39, 77, 61, 222, 1, 94, 47, 18, 149, 48, 93, 227, 1, 85, 55, 29, 123, 168, 11, 112, 36, 85, 55, 33, 117, 27, 129, 117, 33, 106, 39, 94, 47, 149, 18, 77, 61, 27, 129, 117, 33, 61, 77, 20, 144, 6, 190, 117, 33, 55, 85, 159, 14, 26, 131, 3, 209, 117, 32, 94, 47, 16, 153 },
+ { 5, 194, 123, 29, 55, 85, 19, 146, 62, 77, 126, 28, 37, 110, 156, 15, 62, 77, 36, 112, 17, 151, 194, 5, 151, 17, 77, 62, 17, 152, 2, 217, 175, 9, 93, 47, 227, 0, 48, 93, 6, 192, 168, 11, 117, 33, 77, 62, 161, 14, 140, 22, 106, 39, 106, 39, 106, 39, 62, 77, 28, 126, 81, 58 },
+ { 47, 94, 153, 16, 123, 29, 85, 55, 5, 197, 152, 17, 62, 77, 29, 123, 10, 174, 48, 93, 28, 126, 106, 39, 93, 48, 8, 181, 106, 39, 62, 77, 33, 117, 144, 20, 62, 77, 18, 148, 106, 39, 106, 39, 93, 48, 18, 147, 48, 93, 33, 117, 192, 6, 164, 13, 131, 26, 14, 158, 227, 1, 140, 22 },
+ { 13, 164, 85, 55, 10, 174, 117, 33, 106, 39, 93, 48, 10, 172, 106, 39, 62, 77, 2, 209, 62, 77, 161, 14, 142, 21, 62, 77, 33, 117, 143, 20, 106, 39, 93, 48, 22, 138, 48, 93, 158, 15, 131, 26, 1, 217, 85, 55, 200, 4, 106, 39, 105, 39, 105, 40, 105, 40, 105, 40, 105, 40, 87, 53 },
+ { 85, 55, 120, 31, 62, 77, 6, 187, 142, 21, 123, 29, 85, 55, 5, 194, 123, 29, 37, 110, 179, 8, 117, 33, 85, 55, 23, 136, 6, 192, 62, 77, 10, 174, 5, 197, 62, 77, 8, 179, 105, 40, 105, 40, 85, 55, 120, 31, 62, 77, 167, 12, 26, 131, 15, 158, 5, 194, 168, 11, 147, 19, 118, 32 },
+ { 135, 24, 3, 209, 136, 23, 105, 40, 62, 76, 209, 3, 123, 29, 63, 76, 15, 155, 63, 76, 27, 129, 93, 48, 0, 235, 105, 40, 93, 48, 17, 151, 48, 93, 33, 116, 16, 155, 37, 110, 4, 203, 162, 13, 24, 134, 11, 168, 136, 23, 105, 40, 105, 40, 105, 40, 63, 76, 126, 28, 85, 55, 6, 190 },
+ { 37, 109, 33, 117, 63, 76, 21, 142, 12, 165, 48, 93, 16, 155, 123, 29, 93, 48, 11, 170, 48, 93, 16, 155, 55, 85, 170, 11, 136, 23, 63, 76, 146, 19, 63, 76, 27, 129, 76, 63, 27, 129, 116, 33, 76, 63, 33, 117, 63, 76, 19, 147, 217, 2, 168, 11, 142, 21, 48, 93, 120, 31, 80, 59 },
+ { 175, 9, 93, 49, 10, 172, 105, 40, 63, 76, 125, 28, 85, 55, 8, 179, 117, 33, 84, 55, 197, 5, 55, 84, 27, 129, 116, 33, 93, 49, 213, 2, 49, 93, 11, 170, 49, 93, 172, 10, 105, 40, 93, 49, 5, 194, 49, 93, 192, 6, 116, 33, 63, 76, 120, 31, 63, 76, 3, 206, 162, 13, 26, 131 },
+ { 37, 109, 18, 149, 63, 76, 0, 235, 131, 26, 187, 6, 116, 33, 84, 55, 1, 222, 140, 22, 63, 76, 33, 116, 8, 179, 84, 55, 158, 15, 55, 84, 27, 129, 84, 55, 217, 2, 55, 84, 20, 144, 10, 174, 55, 84, 149, 18, 122, 29, 93, 49, 20, 143, 49, 93, 15, 158, 116, 33, 105, 40, 80, 59 },
+ { 138, 22, 93, 49, 22, 138, 105, 40, 105, 40, 63, 76, 159, 14, 136, 23, 63, 75, 33, 116, 164, 13, 144, 20, 93, 49, 5, 194, 63, 75, 33, 116, 185, 7, 116, 33, 75, 63, 31, 120, 104, 40, 75, 63, 31, 120, 104, 41, 75, 63, 181, 8, 84, 55, 6, 192, 104, 41, 84, 55, 21, 142, 217, 2 },
+ { 84, 55, 197, 5, 55, 84, 19, 146, 13, 164, 140, 22, 104, 41, 93, 49, 18, 149, 49, 93, 28, 125, 63, 75, 125, 28, 93, 49, 21, 141, 11, 168, 63, 75, 165, 12, 24, 134, 15, 158, 209, 3, 26, 131, 15, 158, 1, 227, 144, 20, 49, 93, 16, 153, 63, 75, 23, 136, 10, 172, 63, 75, 120, 30 },
+ { 14, 161, 75, 63, 177, 8, 116, 33, 75, 63, 33, 116, 2, 213, 175, 9, 63, 75, 190, 6, 92, 49, 2, 213, 152, 17, 111, 36, 63, 75, 36, 112, 27, 129, 111, 36, 104, 41, 104, 41, 104, 41, 104, 41, 104, 41, 104, 41, 75, 63, 125, 28, 92, 49, 14, 161, 104, 41, 92, 49, 7, 185, 49, 92 },
+ { 47, 94, 144, 20, 122, 30, 92, 49, 5, 197, 49, 92, 28, 125, 92, 49, 143, 20, 49, 92, 18, 149, 75, 63, 36, 112, 11, 168, 222, 1, 49, 92, 203, 4, 55, 84, 190, 6, 162, 13, 26, 130, 190, 6, 164, 13, 26, 130, 13, 162, 206, 3, 122, 30, 84, 55, 0, 235, 122, 30, 84, 55, 16, 152 },
+ { 6, 192, 116, 33, 75, 63, 16, 155, 63, 74, 15, 156, 64, 74, 20, 143, 84, 55, 22, 138, 92, 49, 143, 20, 49, 92, 27, 129, 55, 84, 156, 15, 56, 84, 16, 155, 116, 33, 104, 41, 104, 41, 104, 41, 104, 41, 104, 41, 103, 41, 64, 74, 181, 8, 115, 33, 64, 74, 152, 17, 122, 30, 80, 59 },
+ { 103, 41, 92, 49, 213, 2, 49, 92, 28, 125, 92, 49, 6, 185, 92, 49, 3, 206, 64, 74, 7, 185, 64, 74, 190, 6, 64, 74, 31, 120, 64, 74, 33, 117, 103, 41, 64, 74, 20, 144, 8, 181, 24, 134, 15, 158, 5, 197, 141, 21, 122, 30, 56, 84, 19, 146, 13, 164, 50, 91, 203, 4, 112, 36 },
+ { 24, 134, 12, 167, 56, 84, 34, 115, 172, 10, 136, 23, 64, 74, 18, 149, 92, 49, 18, 149, 91, 49, 15, 156, 50, 91, 20, 144, 7, 183, 24, 134, 183, 7, 24, 134, 235, 0, 50, 91, 31, 120, 64, 74, 125, 28, 115, 34, 64, 74, 161, 14, 115, 34, 103, 41, 64, 74, 125, 28, 37, 110, 165, 12 },
+ { 103, 41, 64, 74, 23, 136, 185, 7, 103, 41, 91, 50, 20, 143, 50, 91, 22, 138, 56, 84, 31, 120, 64, 74, 31, 120, 103, 41, 103, 41, 64, 74, 31, 120, 103, 41, 64, 74, 125, 28, 91, 50, 217, 2, 103, 41, 91, 50, 10, 174, 50, 91, 22, 140, 200, 4, 134, 24, 7, 183, 65, 74, 28, 126 },
+ { 227, 1, 146, 19, 103, 41, 65, 74, 21, 142, 227, 1, 65, 74, 6, 190, 65, 74, 9, 175, 0, 235, 24, 134, 206, 3, 162, 13, 130, 26, 213, 2, 50, 91, 13, 162, 131, 26, 185, 7, 152, 17, 65, 74, 170, 11, 144, 20, 56, 84, 1, 222, 103, 41, 103, 41, 103, 41, 91, 50, 175, 9, 50, 91 },
+ { 49, 93, 33, 116, 6, 187, 136, 23, 50, 91, 33, 116, 18, 149, 50, 91, 19, 147, 103, 41, 103, 41, 102, 41, 102, 42, 102, 42, 102, 42, 65, 74, 21, 141, 102, 42, 102, 42, 65, 74, 125, 28, 115, 34, 102, 42, 65, 74, 31, 119, 65, 74, 167, 12, 26, 130, 12, 165, 115, 34, 84, 56, 151, 17 },
+ { 148, 18, 102, 42, 102, 42, 84, 56, 174, 10, 102, 42, 65, 74, 22, 140, 65, 74, 159, 14, 131, 26, 11, 168, 26, 131, 15, 158, 7, 185, 159, 14, 50, 91, 5, 197, 168, 11, 141, 21, 50, 91, 179, 8, 24, 134, 4, 203, 162, 13, 142, 21, 102, 42, 102, 42, 65, 74, 2, 217, 140, 22, 80, 59 },
+ { 49, 92, 9, 175, 24, 133, 3, 209, 65, 74, 19, 146, 185, 7, 90, 50, 3, 209, 102, 42, 65, 74, 31, 120, 102, 42, 65, 74, 31, 120, 65, 73, 34, 115, 27, 129, 115, 34, 56, 84, 203, 4, 111, 36, 65, 73, 125, 28, 102, 42, 65, 73, 23, 136, 192, 6, 136, 23, 102, 42, 90, 50, 6, 187 },
+ { 31, 119, 102, 42, 102, 42, 90, 50, 138, 22, 102, 42, 65, 73, 125, 28, 65, 73, 21, 141, 194, 5, 50, 90, 192, 6, 141, 21, 50, 90, 21, 141, 6, 187, 102, 42, 65, 73, 16, 155, 122, 30, 90, 50, 14, 161, 50, 90, 34, 115, 200, 4, 102, 42, 102, 42, 84, 56, 18, 149, 115, 34, 79, 59 },
+ { 209, 3, 25, 133, 6, 187, 146, 19, 65, 73, 203, 4, 134, 24, 11, 168, 111, 36, 102, 42, 65, 73, 19, 147, 102, 42, 65, 73, 1, 227, 102, 42, 65, 73, 19, 146, 217, 2, 115, 34, 56, 84, 17, 152, 73, 65, 177, 9, 151, 17, 73, 65, 165, 12, 131, 26, 175, 9, 73, 65, 9, 174, 24, 134 },
+ { 33, 117, 102, 42, 65, 73, 34, 115, 11, 170, 115, 34, 65, 73, 36, 111, 181, 7, 168, 11, 136, 23, 50, 90, 161, 14, 146, 19, 50, 90, 159, 14, 136, 23, 102, 42, 102, 42, 73, 65, 7, 183, 90, 50, 1, 227, 50, 90, 36, 111, 27, 129, 115, 34, 72, 65, 34, 115, 27, 127, 111, 36, 79, 59 },
+ { 50, 90, 164, 13, 141, 21, 102, 42, 102, 42, 90, 50, 15, 156, 50, 90, 27, 127, 115, 34, 66, 72, 2, 217, 102, 42, 66, 72, 22, 138, 102, 42, 66, 72, 167, 12, 133, 25, 12, 165, 37, 110, 28, 125, 72, 66, 143, 20, 72, 66, 179, 8, 90, 50, 235, 0, 51, 90, 203, 4, 90, 50, 7, 183 },
+ { 17, 152, 101, 42, 66, 72, 235, 0, 25, 133, 8, 181, 66, 72, 222, 1, 101, 43, 90, 50, 17, 152, 51, 90, 23, 136, 185, 7, 51, 90, 9, 175, 203, 4, 114, 34, 72, 66, 125, 28, 72, 66, 172, 10, 146, 19, 90, 51, 209, 3, 51, 90, 153, 16, 56, 84, 16, 155, 56, 84, 16, 155, 87, 53 },
+ { 84, 56, 6, 192, 151, 17, 114, 35, 72, 66, 35, 114, 27, 127, 37, 110, 17, 152, 197, 5, 84, 56, 14, 161, 101, 43, 72, 66, 18, 149, 72, 66, 125, 28, 90, 51, 190, 6, 51, 90, 3, 206, 51, 90, 35, 114, 11, 170, 66, 72, 159, 14, 66, 72, 28, 125, 66, 72, 31, 119, 66, 72, 127, 27 },
+ { 111, 36, 66, 72, 124, 28, 90, 51, 16, 155, 51, 90, 16, 155, 66, 72, 29, 124, 66, 72, 124, 29, 66, 72, 5, 192, 136, 23, 51, 90, 144, 19, 51, 90, 149, 18, 56, 83, 18, 149, 66, 72, 142, 20, 101, 43, 84, 56, 31, 119, 83, 56, 35, 114, 170, 10, 25, 133, 183, 7, 133, 25, 1, 222 },
+ { 172, 10, 141, 21, 51, 90, 170, 11, 83, 56, 4, 200, 89, 51, 11, 170, 52, 89, 21, 141, 183, 7, 114, 35, 101, 43, 66, 72, 2, 213, 66, 72, 8, 179, 66, 72, 22, 140, 52, 89, 147, 19, 52, 89, 203, 4, 133, 25, 8, 181, 25, 133, 4, 200, 101, 43, 66, 72, 114, 35, 83, 56, 36, 111 },
+ { 28, 126, 66, 72, 3, 206, 37, 110, 29, 124, 72, 66, 31, 119, 71, 66, 4, 203, 101, 43, 67, 71, 167, 12, 25, 133, 12, 165, 52, 89, 29, 124, 89, 52, 0, 235, 52, 89, 177, 9, 56, 83, 11, 170, 114, 35, 67, 71, 35, 114, 101, 43, 67, 71, 35, 114, 2, 213, 52, 89, 177, 9, 79, 59 },
+ { 51, 90, 15, 156, 122, 30, 67, 71, 8, 181, 133, 25, 185, 7, 141, 21, 52, 89, 16, 155, 114, 35, 101, 43, 101, 43, 67, 71, 36, 111, 165, 12, 136, 23, 56, 83, 31, 119, 67, 71, 35, 114, 127, 27, 89, 52, 15, 156, 52, 89, 167, 12, 133, 25, 12, 165, 83, 56, 16, 155, 89, 52, 15, 156 },
+ { 6, 192, 101, 43, 89, 52, 148, 18, 101, 43, 101, 43, 101, 43, 67, 71, 124, 29, 67, 71, 0, 235, 25, 133, 200, 4, 133, 25, 183, 7, 101, 43, 67, 71, 19, 146, 187, 6, 133, 25, 217, 2, 67, 71, 8, 181, 67, 71, 1, 222, 114, 35, 101, 43, 67, 71, 31, 119, 67, 71, 31, 119, 79, 59 },
+ { 51, 90, 23, 135, 10, 172, 67, 71, 24, 135, 222, 1, 26, 130, 15, 158, 10, 174, 114, 35, 100, 43, 67, 71, 31, 119, 67, 70, 35, 114, 21, 141, 5, 194, 114, 35, 100, 43, 100, 43, 89, 52, 16, 155, 52, 89, 27, 127, 122, 30, 56, 83, 197, 5, 132, 25, 8, 181, 25, 133, 206, 3, 137, 23 },
+ { 18, 149, 100, 43, 89, 52, 194, 5, 100, 43, 100, 43, 100, 43, 100, 43, 68, 70, 7, 185, 26, 131, 175, 9, 52, 88, 11, 170, 100, 43, 100, 43, 100, 43, 68, 70, 21, 141, 12, 165, 111, 36, 68, 70, 35, 113, 16, 155, 56, 83, 16, 155, 113, 35, 68, 70, 35, 113, 68, 70, 113, 35, 79, 59 },
+ { 52, 88, 227, 1, 135, 24, 68, 70, 167, 12, 26, 130, 13, 162, 209, 3, 135, 24, 100, 43, 100, 43, 83, 56, 18, 148, 56, 83, 1, 222, 162, 13, 132, 25, 12, 167, 52, 88, 35, 113, 7, 183, 25, 133, 4, 203, 56, 83, 175, 9, 122, 30, 88, 52, 15, 156, 52, 88, 168, 11, 52, 88, 181, 8 },
+ { 27, 129, 113, 35, 88, 52, 20, 143, 100, 43, 100, 43, 100, 43, 99, 43, 68, 70, 19, 146, 5, 194, 113, 35, 68, 70, 35, 113, 29, 124, 99, 44, 99, 44, 68, 70, 2, 213, 68, 70, 29, 124, 99, 44, 68, 70, 31, 119, 99, 44, 70, 68, 177, 9, 70, 68, 227, 1, 83, 56, 16, 153, 56, 83 },
+ { 170, 11, 70, 68, 10, 174, 68, 70, 19, 146, 197, 5, 158, 15, 130, 26, 175, 9, 113, 35, 56, 83, 21, 140, 3, 209, 152, 17, 56, 83, 21, 141, 4, 197, 122, 30, 88, 52, 16, 153, 52, 88, 165, 12, 132, 25, 183, 7, 25, 132, 213, 2, 88, 52, 143, 20, 52, 88, 119, 31, 68, 70, 32, 118 },
+ { 88, 52, 19, 147, 88, 52, 213, 2, 99, 44, 99, 44, 99, 44, 99, 44, 99, 44, 68, 70, 161, 14, 111, 36, 99, 44, 68, 70, 7, 183, 99, 44, 70, 68, 16, 153, 122, 30, 70, 68, 5, 197, 113, 35, 68, 70, 35, 113, 68, 70, 35, 113, 29, 124, 68, 70, 24, 135, 187, 6, 25, 132, 3, 209 },
+ { 31, 119, 70, 68, 124, 29, 82, 57, 21, 141, 13, 164, 132, 25, 187, 6, 25, 132, 217, 2, 122, 30, 70, 68, 18, 148, 122, 30, 37, 110, 16, 153, 122, 30, 56, 82, 10, 172, 113, 35, 99, 44, 88, 52, 0, 235, 52, 88, 14, 159, 52, 88, 17, 152, 5, 194, 99, 44, 99, 44, 99, 44, 79, 60 },
+ { 13, 162, 200, 4, 167, 11, 111, 36, 68, 70, 119, 31, 68, 70, 119, 31, 98, 44, 98, 45, 87, 52, 6, 190, 53, 87, 9, 177, 57, 82, 30, 122, 8, 179, 113, 35, 69, 68, 7, 185, 132, 25, 12, 164, 57, 82, 17, 152, 69, 69, 177, 9, 69, 69, 35, 113, 165, 12, 26, 130, 11, 168, 132, 25 },
+ { 37, 109, 69, 69, 36, 111, 17, 151, 190, 6, 53, 87, 1, 227, 53, 87, 10, 174, 130, 26, 14, 159, 69, 69, 20, 143, 69, 69, 206, 3, 98, 45, 69, 69, 1, 227, 142, 21, 98, 45, 98, 45, 69, 69, 124, 29, 37, 110, 5, 197, 87, 53, 20, 143, 53, 87, 29, 124, 98, 45, 98, 45, 79, 60 },
+ { 26, 130, 14, 159, 53, 87, 29, 124, 82, 57, 18, 148, 82, 57, 18, 148, 98, 45, 98, 45, 87, 53, 138, 22, 53, 87, 124, 29, 37, 110, 165, 12, 142, 21, 98, 45, 69, 69, 24, 135, 3, 209, 132, 25, 181, 8, 82, 57, 27, 127, 113, 35, 82, 57, 177, 9, 57, 82, 213, 2, 25, 132, 6, 187 },
+ { 37, 109, 61, 78, 8, 179, 61, 78, 20, 143, 66, 72, 22, 138, 66, 72, 24, 135, 4, 200, 175, 9, 72, 66, 1, 227, 151, 17, 72, 66, 29, 124, 68, 70, 24, 135, 181, 8, 98, 45, 98, 45, 98, 45, 68, 70, 18, 148, 70, 68, 17, 151, 222, 1, 70, 68, 18, 148, 112, 35, 98, 45, 78, 61 }
+};
diff --git a/tools/ippevecommon.h b/tools/ippevecommon.h
new file mode 100644
index 000000000..627cc74e0
--- /dev/null
+++ b/tools/ippevecommon.h
@@ -0,0 +1,26 @@
+/*
+ * Common header for IPP Everywhere printer commands for CUPS.
+ *
+ * Copyright © 2019 by Apple Inc.
+ *
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more
+ * information.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups.h>
+#include <cups/raster.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <cups/string-private.h>
+
+
+/*
+ * Prototypes...
+ */
diff --git a/tools/ippevepcl.c b/tools/ippevepcl.c
new file mode 100644
index 000000000..f15ea51d7
--- /dev/null
+++ b/tools/ippevepcl.c
@@ -0,0 +1,530 @@
+/*
+ * Generic HP PCL printer command for ippeveprinter/CUPS.
+ *
+ * Copyright © 2019 by Apple Inc.
+ *
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more
+ * information.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include "ippevecommon.h"
+#include "dither.h"
+
+
+/*
+ * Local globals...
+ */
+
+static unsigned pcl_bottom, /* Bottom line */
+ pcl_left, /* Left offset in line */
+ pcl_right, /* Right offset in line */
+ pcl_top, /* Top line */
+ pcl_blanks; /* Number of blank lines to skip */
+static unsigned char pcl_white, /* White color */
+ *pcl_line, /* Line buffer */
+ *pcl_comp; /* Compression buffer */
+
+/*
+ * Local functions...
+ */
+
+static void pcl_end_page(cups_page_header2_t *header, unsigned page);
+static void pcl_start_page(cups_page_header2_t *header, unsigned page);
+static int pcl_to_pcl(const char *filename);
+static void pcl_write_line(cups_page_header2_t *header, unsigned y, const unsigned char *line);
+static int raster_to_pcl(const char *filename);
+
+
+/*
+ * 'main()' - Main entry for PCL printer command.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line arguments */
+ char *argv[]) /* I - Command-line arguments */
+{
+ const char *content_type; /* Content type to print */
+
+
+ /*
+ * Print it...
+ */
+
+ if (argc > 2)
+ {
+ fputs("ERROR: Too many arguments supplied, aborting.\n", stderr);
+ return (1);
+ }
+ else if ((content_type = getenv("CONTENT_TYPE")) == NULL)
+ {
+ fputs("ERROR: CONTENT_TYPE environment variable not set, aborting.\n", stderr);
+ return (1);
+ }
+ else if (!strcasecmp(content_type, "application/vnd.hp-pcl"))
+ {
+ return (pcl_to_pcl(argv[1]));
+ }
+ else if (!strcasecmp(content_type, "image/pwg-raster") || !strcasecmp(content_type, "image/urf"))
+ {
+ return (raster_to_pcl(argv[1]));
+ }
+ else
+ {
+ fprintf(stderr, "ERROR: CONTENT_TYPE %s not supported.\n", content_type);
+ return (1);
+ }
+}
+
+
+/*
+ * 'pcl_end_page()' - End of PCL page.
+ */
+
+static void
+pcl_end_page(
+ cups_page_header2_t *header, /* I - Page header */
+ unsigned page) /* I - Current page */
+{
+ /*
+ * End graphics...
+ */
+
+ fputs("\033*r0B", stdout);
+
+ /*
+ * Formfeed as needed...
+ */
+
+ if (!(header->Duplex && (page & 1)))
+ putchar('\f');
+
+ /*
+ * Free the output buffers...
+ */
+
+ free(pcl_line);
+ free(pcl_comp);
+}
+
+
+/*
+ * 'pcl_start_page()' - Start a PCL page.
+ */
+
+static void
+pcl_start_page(
+ cups_page_header2_t *header, /* I - Page header */
+ unsigned page) /* I - Current page */
+{
+ /*
+ * Setup margins to be 1/6" top and bottom and 1/4" or .135" on the
+ * left and right.
+ */
+
+ pcl_top = header->HWResolution[1] / 6;
+ pcl_bottom = header->cupsHeight - header->HWResolution[1] / 6 - 1;
+
+ if (header->PageSize[1] == 842)
+ {
+ /* A4 gets special side margins to expose an 8" print area */
+ pcl_left = (header->cupsWidth - 8 * header->HWResolution[0]) / 2;
+ pcl_right = pcl_left + 8 * header->HWResolution[0] - 1;
+ }
+ else
+ {
+ /* All other sizes get 1/4" margins */
+ pcl_left = header->HWResolution[0] / 4;
+ pcl_right = header->cupsWidth - header->HWResolution[0] / 4 - 1;
+ }
+
+ if (!header->Duplex || (page & 1))
+ {
+ /*
+ * Set the media size...
+ */
+
+ printf("\033&l12D\033&k12H"); /* Set 12 LPI, 10 CPI */
+ printf("\033&l0O"); /* Set portrait orientation */
+
+ switch (header->PageSize[1])
+ {
+ case 540 : /* Monarch Envelope */
+ printf("\033&l80A");
+ break;
+
+ case 595 : /* A5 */
+ printf("\033&l25A");
+ break;
+
+ case 624 : /* DL Envelope */
+ printf("\033&l90A");
+ break;
+
+ case 649 : /* C5 Envelope */
+ printf("\033&l91A");
+ break;
+
+ case 684 : /* COM-10 Envelope */
+ printf("\033&l81A");
+ break;
+
+ case 709 : /* B5 Envelope */
+ printf("\033&l100A");
+ break;
+
+ case 756 : /* Executive */
+ printf("\033&l1A");
+ break;
+
+ case 792 : /* Letter */
+ printf("\033&l2A");
+ break;
+
+ case 842 : /* A4 */
+ printf("\033&l26A");
+ break;
+
+ case 1008 : /* Legal */
+ printf("\033&l3A");
+ break;
+
+ case 1191 : /* A3 */
+ printf("\033&l27A");
+ break;
+
+ case 1224 : /* Tabloid */
+ printf("\033&l6A");
+ break;
+ }
+
+ /*
+ * Set top margin and turn off perforation skip...
+ */
+
+ printf("\033&l%uE\033&l0L", 12 * pcl_top / header->HWResolution[1]);
+
+ if (header->Duplex)
+ {
+ int mode = header->Duplex ? 1 + header->Tumble != 0 : 0;
+
+ printf("\033&l%dS", mode); /* Set duplex mode */
+ }
+ }
+ else if (header->Duplex)
+ printf("\033&a2G"); /* Print on back side */
+
+ /*
+ * Set graphics mode...
+ */
+
+ printf("\033*t%uR", header->HWResolution[0]);
+ /* Set resolution */
+ printf("\033*r%uS", pcl_right - pcl_left + 1);
+ /* Set width */
+ printf("\033*r%uT", pcl_bottom - pcl_top + 1);
+ /* Set height */
+ printf("\033&a0H\033&a%uV", 720 * pcl_top / header->HWResolution[1]);
+ /* Set position */
+
+ printf("\033*b2M"); /* Use PackBits compression */
+ printf("\033*r1A"); /* Start graphics */
+
+ /*
+ * Allocate the output buffers...
+ */
+
+ pcl_white = header->cupsBitsPerColor == 1 ? 0 : 255;
+ pcl_blanks = 0;
+ pcl_line = malloc(header->cupsWidth / 8 + 1);
+ pcl_comp = malloc(2 * header->cupsBytesPerLine + 2);
+
+ fprintf(stderr, "ATTR: job-impressions-completed=%d\n", page);
+}
+
+
+/*
+ * 'pcl_to_pcl()' - Pass through PCL data.
+ */
+
+static int /* O - Exit status */
+pcl_to_pcl(const char *filename) /* I - File to print or NULL for stdin */
+{
+ int fd; /* File to read from */
+ char buffer[65536]; /* Copy buffer */
+ ssize_t bytes; /* Bytes to write */
+
+
+ /*
+ * Open the input file...
+ */
+
+ if (filename)
+ {
+ if ((fd = open(filename, O_RDONLY)) < 0)
+ {
+ fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
+ return (1);
+ }
+ }
+ else
+ {
+ fd = 0;
+ }
+
+ fputs("ATTR: job-impressions=unknown\n", stderr);
+
+ /*
+ * Copy to stdout...
+ */
+
+ while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
+ write(1, buffer, (size_t)bytes);
+
+ /*
+ * Close the input file...
+ */
+
+ if (fd > 0)
+ close(fd);
+
+ return (0);
+}
+
+
+/*
+ * 'pcl_write_line()' - Write a line of raster data.
+ */
+
+static void
+pcl_write_line(
+ cups_page_header2_t *header, /* I - Raster information */
+ unsigned y, /* I - Line number */
+ const unsigned char *line) /* I - Pixels on line */
+{
+ unsigned x; /* Column number */
+ unsigned char bit, /* Current bit */
+ byte, /* Current byte */
+ *outptr, /* Pointer into output buffer */
+ *outend, /* End of output buffer */
+ *start, /* Start of sequence */
+ *compptr; /* Pointer into compression buffer */
+ unsigned count; /* Count of bytes for output */
+ const unsigned char *ditherline; /* Pointer into dither table */
+
+
+ if (line[0] == pcl_white && !memcmp(line, line + 1, header->cupsBytesPerLine - 1))
+ {
+ /*
+ * Skip blank line...
+ */
+
+ pcl_blanks ++;
+ return;
+ }
+
+ if (header->cupsBitsPerPixel == 1)
+ {
+ /*
+ * B&W bitmap data can be used directly...
+ */
+
+ outend = (unsigned char *)line + (pcl_right + 7) / 8;
+ outptr = (unsigned char *)line + pcl_left / 8;
+ }
+ else
+ {
+ /*
+ * Dither 8-bit grayscale to B&W...
+ */
+
+ y &= 63;
+ ditherline = threshold[y];
+
+ for (x = pcl_left, bit = 128, byte = 0, outptr = pcl_line; x <= pcl_right; x ++, line ++)
+ {
+ if (*line <= ditherline[x & 63])
+ byte |= bit;
+
+ if (bit == 1)
+ {
+ *outptr++ = byte;
+ byte = 0;
+ bit = 128;
+ }
+ else
+ bit >>= 1;
+ }
+
+ if (bit != 128)
+ *outptr++ = byte;
+
+ outend = outptr;
+ outptr = pcl_line;
+ }
+
+ /*
+ * Apply compression...
+ */
+
+ compptr = pcl_comp;
+
+ while (outptr < outend)
+ {
+ if ((outptr + 1) >= outend)
+ {
+ /*
+ * Single byte on the end...
+ */
+
+ *compptr++ = 0x00;
+ *compptr++ = *outptr++;
+ }
+ else if (outptr[0] == outptr[1])
+ {
+ /*
+ * Repeated sequence...
+ */
+
+ outptr ++;
+ count = 2;
+
+ while (outptr < (outend - 1) &&
+ outptr[0] == outptr[1] &&
+ count < 127)
+ {
+ outptr ++;
+ count ++;
+ }
+
+ *compptr++ = (unsigned char)(257 - count);
+ *compptr++ = *outptr++;
+ }
+ else
+ {
+ /*
+ * Non-repeated sequence...
+ */
+
+ start = outptr;
+ outptr ++;
+ count = 1;
+
+ while (outptr < (outend - 1) &&
+ outptr[0] != outptr[1] &&
+ count < 127)
+ {
+ outptr ++;
+ count ++;
+ }
+
+ *compptr++ = (unsigned char)(count - 1);
+
+ memcpy(compptr, start, count);
+ compptr += count;
+ }
+ }
+
+ /*
+ * Output the line...
+ */
+
+ if (pcl_blanks > 0)
+ {
+ /*
+ * Skip blank lines first...
+ */
+
+ printf("\033*b%dY", pcl_blanks);
+ pcl_blanks = 0;
+ }
+
+ printf("\033*b%dW", (int)(compptr - pcl_comp));
+ fwrite(pcl_comp, 1, (size_t)(compptr - pcl_comp), stdout);
+}
+
+
+/*
+ * 'raster_to_pcl()' - Convert raster data to PCL.
+ */
+
+static int /* O - Exit status */
+raster_to_pcl(const char *filename) /* I - File to print (NULL for stdin) */
+{
+ int fd; /* Input file */
+ cups_raster_t *ras; /* Raster stream */
+ cups_page_header2_t header; /* Page header */
+ unsigned page = 0, /* Current page */
+ y; /* Current line */
+ unsigned char *line; /* Line buffer */
+
+
+
+ /*
+ * Open the input file...
+ */
+
+ if (filename)
+ {
+ if ((fd = open(filename, O_RDONLY)) < 0)
+ {
+ fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
+ return (1);
+ }
+ }
+ else
+ {
+ fd = 0;
+ }
+
+ /*
+ * Open the raster stream and send pages...
+ */
+
+ if ((ras = cupsRasterOpen(fd, CUPS_RASTER_READ)) == NULL)
+ {
+ fputs("ERROR: Unable to read raster data, aborting.\n", stderr);
+ return (1);
+ }
+
+ fputs("\033E", stdout);
+
+ while (cupsRasterReadHeader2(ras, &header))
+ {
+ page ++;
+
+ if (header.cupsColorSpace != CUPS_CSPACE_W && header.cupsColorSpace != CUPS_CSPACE_SW && header.cupsColorSpace != CUPS_CSPACE_K)
+ {
+ fputs("ERROR: Unsupported color space, aborting.\n", stderr);
+ break;
+ }
+ else if (header.cupsBitsPerColor != 1 && header.cupsBitsPerColor != 8)
+ {
+ fputs("ERROR: Unsupported bit depth, aborting.\n", stderr);
+ break;
+ }
+
+ line = malloc(header.cupsBytesPerLine);
+
+ pcl_start_page(&header, page);
+ for (y = 0; y < header.cupsHeight; y ++)
+ {
+ if (cupsRasterReadPixels(ras, line, header.cupsBytesPerLine))
+ pcl_write_line(&header, y, line);
+ else
+ break;
+ }
+ pcl_end_page(&header, page);
+
+ free(line);
+ }
+
+ cupsRasterClose(ras);
+
+ fprintf(stderr, "ATTR: job-impressions=%d\n", page);
+
+ return (0);
+}
diff --git a/tools/ippeveprinter.c b/tools/ippeveprinter.c
new file mode 100644
index 000000000..177e9a61a
--- /dev/null
+++ b/tools/ippeveprinter.c
@@ -0,0 +1,8080 @@
+/*
+ * IPP Everywhere printer application for CUPS.
+ *
+ * Copyright © 2010-2019 by Apple Inc.
+ *
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more
+ * information.º
+ *
+ * Note: This program began life as the "ippserver" sample code that first
+ * appeared in CUPS 1.4. The name has been changed in order to distinguish it
+ * from the PWG's much more ambitious "ippserver" program, which supports
+ * different kinds of IPP services and multiple services per instance - the
+ * "ippeveprinter" program exposes a single print service conforming to the
+ * current IPP Everywhere specification, thus the new name.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups-private.h>
+#if !CUPS_LITE
+# include <cups/ppd-private.h>
+#endif /* !CUPS_LITE */
+
+#include <limits.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <fcntl.h>
+# include <io.h>
+# include <process.h>
+# define WEXITSTATUS(s) (s)
+# include <winsock2.h>
+typedef ULONG nfds_t;
+# define poll WSAPoll
+#else
+extern char **environ;
+
+# include <sys/fcntl.h>
+# include <sys/wait.h>
+# include <poll.h>
+#endif /* _WIN32 */
+
+#ifdef HAVE_DNSSD
+# include <dns_sd.h>
+#elif defined(HAVE_AVAHI)
+# include <avahi-client/client.h>
+# include <avahi-client/publish.h>
+# include <avahi-common/error.h>
+# include <avahi-common/thread-watch.h>
+#endif /* HAVE_DNSSD */
+#ifdef HAVE_SYS_MOUNT_H
+# include <sys/mount.h>
+#endif /* HAVE_SYS_MOUNT_H */
+#ifdef HAVE_SYS_STATFS_H
+# include <sys/statfs.h>
+#endif /* HAVE_SYS_STATFS_H */
+#ifdef HAVE_SYS_STATVFS_H
+# include <sys/statvfs.h>
+#endif /* HAVE_SYS_STATVFS_H */
+#ifdef HAVE_SYS_VFS_H
+# include <sys/vfs.h>
+#endif /* HAVE_SYS_VFS_H */
+
+#include "printer-png.h"
+
+
+/*
+ * Constants...
+ */
+
+enum ippeve_preason_e /* printer-state-reasons bit values */
+{
+ IPPEVE_PREASON_NONE = 0x0000, /* none */
+ IPPEVE_PREASON_OTHER = 0x0001, /* other */
+ IPPEVE_PREASON_COVER_OPEN = 0x0002, /* cover-open */
+ IPPEVE_PREASON_INPUT_TRAY_MISSING = 0x0004,
+ /* input-tray-missing */
+ IPPEVE_PREASON_MARKER_SUPPLY_EMPTY = 0x0008,
+ /* marker-supply-empty */
+ IPPEVE_PREASON_MARKER_SUPPLY_LOW = 0x0010,
+ /* marker-supply-low */
+ IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL = 0x0020,
+ /* marker-waste-almost-full */
+ IPPEVE_PREASON_MARKER_WASTE_FULL = 0x0040,
+ /* marker-waste-full */
+ IPPEVE_PREASON_MEDIA_EMPTY = 0x0080, /* media-empty */
+ IPPEVE_PREASON_MEDIA_JAM = 0x0100, /* media-jam */
+ IPPEVE_PREASON_MEDIA_LOW = 0x0200, /* media-low */
+ IPPEVE_PREASON_MEDIA_NEEDED = 0x0400, /* media-needed */
+ IPPEVE_PREASON_MOVING_TO_PAUSED = 0x0800,
+ /* moving-to-paused */
+ IPPEVE_PREASON_PAUSED = 0x1000, /* paused */
+ IPPEVE_PREASON_SPOOL_AREA_FULL = 0x2000,/* spool-area-full */
+ IPPEVE_PREASON_TONER_EMPTY = 0x4000, /* toner-empty */
+ IPPEVE_PREASON_TONER_LOW = 0x8000 /* toner-low */
+};
+typedef unsigned int ippeve_preason_t; /* Bitfield for printer-state-reasons */
+static const char * const ippeve_preason_strings[] =
+{ /* Strings for each bit */
+ /* "none" is implied for no bits set */
+ "other",
+ "cover-open",
+ "input-tray-missing",
+ "marker-supply-empty",
+ "marker-supply-low",
+ "marker-waste-almost-full",
+ "marker-waste-full",
+ "media-empty",
+ "media-jam",
+ "media-low",
+ "media-needed",
+ "moving-to-paused",
+ "paused",
+ "spool-area-full",
+ "toner-empty",
+ "toner-low"
+};
+
+
+/*
+ * URL scheme for web resources...
+ */
+
+#ifdef HAVE_SSL
+# define WEB_SCHEME "https"
+#else
+# define WEB_SCHEME "http"
+#endif /* HAVE_SSL */
+
+
+/*
+ * Structures...
+ */
+
+#ifdef HAVE_DNSSD
+typedef DNSServiceRef ippeve_srv_t; /* Service reference */
+typedef TXTRecordRef ippeve_txt_t; /* TXT record */
+
+#elif defined(HAVE_AVAHI)
+typedef AvahiEntryGroup *ippeve_srv_t; /* Service reference */
+typedef AvahiStringList *ippeve_txt_t; /* TXT record */
+
+#else
+typedef void *ippeve_srv_t; /* Service reference */
+typedef void *ippeve_txt_t; /* TXT record */
+#endif /* HAVE_DNSSD */
+
+typedef struct ippeve_filter_s /**** Attribute filter ****/
+{
+ cups_array_t *ra; /* Requested attributes */
+ ipp_tag_t group_tag; /* Group to copy */
+} ippeve_filter_t;
+
+typedef struct ippeve_job_s ippeve_job_t;
+
+typedef struct ippeve_printer_s /**** Printer data ****/
+{
+ /* TODO: One IPv4 and one IPv6 listener are really not sufficient */
+ int ipv4, /* IPv4 listener */
+ ipv6; /* IPv6 listener */
+ ippeve_srv_t ipp_ref, /* Bonjour IPP service */
+ ipps_ref, /* Bonjour IPPS service */
+ http_ref, /* Bonjour HTTP service */
+ printer_ref; /* Bonjour LPD service */
+ char *dnssd_name, /* printer-dnssd-name */
+ *name, /* printer-name */
+ *icon, /* Icon filename */
+ *directory, /* Spool directory */
+ *hostname, /* Hostname */
+ *uri, /* printer-uri-supported */
+ *device_uri, /* Device URI (if any) */
+#if !CUPS_LITE
+ *ppdfile, /* PPD file (if any) */
+#endif /* !CUPS_LITE */
+ *command; /* Command to run with job file */
+ int port; /* Port */
+ int web_forms; /* Enable web interface forms? */
+ size_t urilen; /* Length of printer URI */
+ ipp_t *attrs; /* Static attributes */
+ time_t start_time; /* Startup time */
+ time_t config_time; /* printer-config-change-time */
+ ipp_pstate_t state; /* printer-state value */
+ ippeve_preason_t state_reasons; /* printer-state-reasons values */
+ time_t state_time; /* printer-state-change-time */
+ cups_array_t *jobs; /* Jobs */
+ ippeve_job_t *active_job; /* Current active/pending job */
+ int next_job_id; /* Next job-id value */
+ _cups_rwlock_t rwlock; /* Printer lock */
+} ippeve_printer_t;
+
+struct ippeve_job_s /**** Job data ****/
+{
+ int id; /* Job ID */
+ const char *name, /* job-name */
+ *username, /* job-originating-user-name */
+ *format; /* document-format */
+ ipp_jstate_t state; /* job-state value */
+ char *message; /* job-state-message value */
+ int msglevel; /* job-state-message log level (0=error, 1=info) */
+ time_t created, /* time-at-creation value */
+ processing, /* time-at-processing value */
+ completed; /* time-at-completed value */
+ int impressions, /* job-impressions value */
+ impcompleted; /* job-impressions-completed value */
+ ipp_t *attrs; /* Static attributes */
+ int cancel; /* Non-zero when job canceled */
+ char *filename; /* Print file name */
+ int fd; /* Print file descriptor */
+ ippeve_printer_t *printer; /* Printer */
+};
+
+typedef struct ippeve_client_s /**** Client data ****/
+{
+ http_t *http; /* HTTP connection */
+ ipp_t *request, /* IPP request */
+ *response; /* IPP response */
+ time_t start; /* Request start time */
+ http_state_t operation; /* Request operation */
+ ipp_op_t operation_id; /* IPP operation-id */
+ char uri[1024], /* Request URI */
+ *options; /* URI options */
+ http_addr_t addr; /* Client address */
+ char hostname[256]; /* Client hostname */
+ ippeve_printer_t *printer; /* Printer */
+ ippeve_job_t *job; /* Current job, if any */
+} ippeve_client_t;
+
+
+/*
+ * Local functions...
+ */
+
+static void clean_jobs(ippeve_printer_t *printer);
+static int compare_jobs(ippeve_job_t *a, ippeve_job_t *b);
+static void copy_attributes(ipp_t *to, ipp_t *from, cups_array_t *ra, ipp_tag_t group_tag, int quickcopy);
+static void copy_job_attributes(ippeve_client_t *client, ippeve_job_t *job, cups_array_t *ra);
+static ippeve_client_t *create_client(ippeve_printer_t *printer, int sock);
+static ippeve_job_t *create_job(ippeve_client_t *client);
+static int create_job_file(ippeve_job_t *job, char *fname, size_t fnamesize, const char *dir, const char *ext);
+static int create_listener(const char *name, int port, int family);
+static ipp_t *create_media_col(const char *media, const char *source, const char *type, int width, int length, int bottom, int left, int right, int top);
+static ipp_t *create_media_size(int width, int length);
+static ippeve_printer_t *create_printer(const char *servername, int serverport, const char *name, const char *location, const char *icon, cups_array_t *docformats, const char *subtypes, const char *directory, const char *command, const char *device_uri, ipp_t *attrs);
+static void debug_attributes(const char *title, ipp_t *ipp, int response);
+static void delete_client(ippeve_client_t *client);
+static void delete_job(ippeve_job_t *job);
+static void delete_printer(ippeve_printer_t *printer);
+#ifdef HAVE_DNSSD
+static void DNSSD_API dnssd_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, ippeve_printer_t *printer);
+#elif defined(HAVE_AVAHI)
+static void dnssd_callback(AvahiEntryGroup *p, AvahiEntryGroupState state, void *context);
+static void dnssd_client_cb(AvahiClient *c, AvahiClientState state, void *userdata);
+#endif /* HAVE_DNSSD */
+static void dnssd_init(void);
+static int filter_cb(ippeve_filter_t *filter, ipp_t *dst, ipp_attribute_t *attr);
+static ippeve_job_t *find_job(ippeve_client_t *client);
+static void finish_document_data(ippeve_client_t *client, ippeve_job_t *job);
+static void finish_document_uri(ippeve_client_t *client, ippeve_job_t *job);
+static void html_escape(ippeve_client_t *client, const char *s, size_t slen);
+static void html_footer(ippeve_client_t *client);
+static void html_header(ippeve_client_t *client, const char *title, int refresh);
+static void html_printf(ippeve_client_t *client, const char *format, ...) _CUPS_FORMAT(2, 3);
+static void ipp_cancel_job(ippeve_client_t *client);
+static void ipp_close_job(ippeve_client_t *client);
+static void ipp_create_job(ippeve_client_t *client);
+static void ipp_get_job_attributes(ippeve_client_t *client);
+static void ipp_get_jobs(ippeve_client_t *client);
+static void ipp_get_printer_attributes(ippeve_client_t *client);
+static void ipp_identify_printer(ippeve_client_t *client);
+static void ipp_print_job(ippeve_client_t *client);
+static void ipp_print_uri(ippeve_client_t *client);
+static void ipp_send_document(ippeve_client_t *client);
+static void ipp_send_uri(ippeve_client_t *client);
+static void ipp_validate_job(ippeve_client_t *client);
+static ipp_t *load_ippserver_attributes(const char *servername, int serverport, const char *filename, cups_array_t *docformats);
+static ipp_t *load_legacy_attributes(const char *make, const char *model, int ppm, int ppm_color, int duplex, cups_array_t *docformats);
+#if !CUPS_LITE
+static ipp_t *load_ppd_attributes(const char *ppdfile, cups_array_t *docformats);
+#endif /* !CUPS_LITE */
+static int parse_options(ippeve_client_t *client, cups_option_t **options);
+static void process_attr_message(ippeve_job_t *job, char *message);
+static void *process_client(ippeve_client_t *client);
+static int process_http(ippeve_client_t *client);
+static int process_ipp(ippeve_client_t *client);
+static void *process_job(ippeve_job_t *job);
+static void process_state_message(ippeve_job_t *job, char *message);
+static int register_printer(ippeve_printer_t *printer, const char *subtypes);
+static int respond_http(ippeve_client_t *client, http_status_t code, const char *content_coding, const char *type, size_t length);
+static void respond_ipp(ippeve_client_t *client, ipp_status_t status, const char *message, ...) _CUPS_FORMAT(3, 4);
+static void respond_unsupported(ippeve_client_t *client, ipp_attribute_t *attr);
+static void run_printer(ippeve_printer_t *printer);
+static int show_media(ippeve_client_t *client);
+static int show_status(ippeve_client_t *client);
+static int show_supplies(ippeve_client_t *client);
+static char *time_string(time_t tv, char *buffer, size_t bufsize);
+static void usage(int status) _CUPS_NORETURN;
+static int valid_doc_attributes(ippeve_client_t *client);
+static int valid_job_attributes(ippeve_client_t *client);
+
+
+/*
+ * Globals...
+ */
+
+#ifdef HAVE_DNSSD
+static DNSServiceRef DNSSDMaster = NULL;
+#elif defined(HAVE_AVAHI)
+static AvahiThreadedPoll *DNSSDMaster = NULL;
+static AvahiClient *DNSSDClient = NULL;
+#endif /* HAVE_DNSSD */
+
+static int KeepFiles = 0, /* Keep spooled job files? */
+ MaxVersion = 20,/* Maximum IPP version (20 = 2.0, 11 = 1.1, etc.) */
+ Verbosity = 0; /* Verbosity level */
+
+
+/*
+ * 'main()' - Main entry to the sample server.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line args */
+ char *argv[]) /* I - Command-line arguments */
+{
+ int i; /* Looping var */
+ const char *opt, /* Current option character */
+ *attrfile = NULL, /* ippserver attributes file */
+ *command = NULL, /* Command to run with job files */
+ *device_uri = NULL, /* Device URI */
+ *icon = NULL, /* Icon file */
+#ifdef HAVE_SSL
+ *keypath = NULL, /* Keychain path */
+#endif /* HAVE_SSL */
+ *location = "", /* Location of printer */
+ *make = "Example", /* Manufacturer */
+ *model = "Printer", /* Model */
+ *name = NULL, /* Printer name */
+#if !CUPS_LITE
+ *ppdfile = NULL, /* PPD file */
+#endif /* !CUPS_LITE */
+ *subtypes = "_print"; /* DNS-SD service subtype */
+ int legacy = 0, /* Legacy mode? */
+ duplex = 0, /* Duplex mode */
+ ppm = 10, /* Pages per minute for mono */
+ ppm_color = 0, /* Pages per minute for color */
+ web_forms = 1; /* Enable web site forms? */
+ ipp_t *attrs = NULL; /* Printer attributes */
+ char directory[1024] = ""; /* Spool directory */
+ cups_array_t *docformats = NULL; /* Supported formats */
+ const char *servername = NULL; /* Server host name */
+ int serverport = 0; /* Server port number (0 = auto) */
+ ippeve_printer_t *printer; /* Printer object */
+
+
+ /*
+ * Parse command-line arguments...
+ */
+
+ for (i = 1; i < argc; i ++)
+ {
+ if (!strcmp(argv[i], "--help"))
+ {
+ usage(0);
+ }
+ else if (!strcmp(argv[i], "--no-web-forms"))
+ {
+ web_forms = 0;
+ }
+ else if (!strcmp(argv[i], "--version"))
+ {
+ puts(CUPS_SVERSION);
+ return (0);
+ }
+ else if (!strncmp(argv[i], "--", 2))
+ {
+ _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), argv[0], argv[i]);
+ usage(1);
+ }
+ else if (argv[i][0] == '-')
+ {
+ for (opt = argv[i] + 1; *opt; opt ++)
+ {
+ switch (*opt)
+ {
+ case '2' : /* -2 (enable 2-sided printing) */
+ duplex = 1;
+ legacy = 1;
+ break;
+
+ case 'D' : /* -D device-uri */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ device_uri = argv[i];
+ break;
+
+#ifdef HAVE_SSL
+ case 'K' : /* -K keypath */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ keypath = argv[i];
+ break;
+#endif /* HAVE_SSL */
+
+ case 'M' : /* -M manufacturer */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ make = argv[i];
+ legacy = 1;
+ break;
+
+#if !CUPS_LITE
+ case 'P' : /* -P filename.ppd */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ ppdfile = argv[i];
+ break;
+#endif /* !CUPS_LITE */
+
+ case 'V' : /* -V max-version */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ if (!strcmp(argv[i], "2.0"))
+ MaxVersion = 20;
+ else if (!strcmp(argv[i], "1.1"))
+ MaxVersion = 11;
+ else
+ usage(1);
+ break;
+
+ case 'a' : /* -a attributes-file */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ attrfile = argv[i];
+ break;
+
+ case 'c' : /* -c command */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ command = argv[i];
+ break;
+
+ case 'd' : /* -d spool-directory */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ strlcpy(directory, argv[i], sizeof(directory));
+ break;
+
+ case 'f' : /* -f type/subtype[,...] */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ docformats = _cupsArrayNewStrings(argv[i], ',');
+ legacy = 1;
+ break;
+
+ case 'i' : /* -i icon.png */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ icon = argv[i];
+ break;
+
+ case 'k' : /* -k (keep files) */
+ KeepFiles = 1;
+ break;
+
+ case 'l' : /* -l location */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ location = argv[i];
+ break;
+
+ case 'm' : /* -m model */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ model = argv[i];
+ legacy = 1;
+ break;
+
+ case 'n' : /* -n hostname */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ servername = argv[i];
+ break;
+
+ case 'p' : /* -p port */
+ i ++;
+ if (i >= argc || !isdigit(argv[i][0] & 255))
+ usage(1);
+
+ serverport = atoi(argv[i]);
+ break;
+
+ case 'r' : /* -r subtype */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ subtypes = argv[i];
+ break;
+
+ case 's' : /* -s speed[,color-speed] */
+ i ++;
+ if (i >= argc)
+ usage(1);
+
+ if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1)
+ usage(1);
+
+ legacy = 1;
+ break;
+
+ case 'v' : /* -v (be verbose) */
+ Verbosity ++;
+ break;
+
+ default : /* Unknown */
+ _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), argv[0], *opt);
+ usage(1);
+ }
+ }
+ }
+ else if (!name)
+ {
+ name = argv[i];
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), argv[0], argv[i]);
+ usage(1);
+ }
+ }
+
+ if (!name)
+ usage(1);
+
+#if CUPS_LITE
+ if (attrfile != NULL && legacy)
+ usage(1);
+#else
+ if (((ppdfile != NULL) + (attrfile != NULL) + legacy) > 1)
+ usage(1);
+#endif /* CUPS_LITE */
+
+ /*
+ * Apply defaults as needed...
+ */
+
+ if (!serverport)
+ {
+#ifdef _WIN32
+ /*
+ * Windows is almost always used as a single user system, so use a default
+ * port number of 8631.
+ */
+
+ serverport = 8631;
+
+#else
+ /*
+ * Use 8000 + UID mod 1000 for the default port number...
+ */
+
+ serverport = 8000 + ((int)getuid() % 1000);
+#endif /* _WIN32 */
+
+ _cupsLangPrintf(stderr, _("Listening on port %d."), serverport);
+ }
+
+ if (!directory[0])
+ {
+ const char *tmpdir; /* Temporary directory */
+
+#ifdef _WIN32
+ if ((tmpdir = getenv("TEMP")) == NULL)
+ tmpdir = "C:/TEMP";
+#elif defined(__APPLE__) && TARGET_OS_OSX
+ if ((tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = "/private/tmp";
+#else
+ if ((tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = "/tmp";
+#endif /* _WIN32 */
+
+ snprintf(directory, sizeof(directory), "%s/ippeveprinter.%d", tmpdir, (int)getpid());
+
+ if (mkdir(directory, 0755) && errno != EEXIST)
+ {
+ _cupsLangPrintf(stderr, _("Unable to create spool directory \"%s\": %s"), directory, strerror(errno));
+ usage(1);
+ }
+
+ if (Verbosity)
+ _cupsLangPrintf(stderr, _("Using spool directory \"%s\"."), directory);
+ }
+
+#ifdef HAVE_SSL
+ cupsSetServerCredentials(keypath, servername, 1);
+#endif /* HAVE_SSL */
+
+ /*
+ * Initialize DNS-SD...
+ */
+
+ dnssd_init();
+
+ /*
+ * Create the printer...
+ */
+
+ if (!docformats)
+ docformats = _cupsArrayNewStrings(ppm_color > 0 ? "image/jpeg,image/pwg-raster,image/urf": "image/pwg-raster,image/urf", ',');
+
+ if (attrfile)
+ attrs = load_ippserver_attributes(servername, serverport, attrfile, docformats);
+#if !CUPS_LITE
+ else if (ppdfile)
+ {
+ attrs = load_ppd_attributes(ppdfile, docformats);
+
+ if (!command)
+ command = "ippeveps";
+ }
+#endif /* !CUPS_LITE */
+ else
+ attrs = load_legacy_attributes(make, model, ppm, ppm_color, duplex, docformats);
+
+ if ((printer = create_printer(servername, serverport, name, location, icon, docformats, subtypes, directory, command, device_uri, attrs)) == NULL)
+ return (1);
+
+ printer->web_forms = web_forms;
+
+#if !CUPS_LITE
+ if (ppdfile)
+ printer->ppdfile = strdup(ppdfile);
+#endif /* !CUPS_LITE */
+
+ /*
+ * Run the print service...
+ */
+
+ run_printer(printer);
+
+ /*
+ * Destroy the printer and exit...
+ */
+
+ delete_printer(printer);
+
+ return (0);
+}
+
+
+/*
+ * 'clean_jobs()' - Clean out old (completed) jobs.
+ */
+
+static void
+clean_jobs(ippeve_printer_t *printer) /* I - Printer */
+{
+ ippeve_job_t *job; /* Current job */
+ time_t cleantime; /* Clean time */
+
+
+ if (cupsArrayCount(printer->jobs) == 0)
+ return;
+
+ cleantime = time(NULL) - 60;
+
+ _cupsRWLockWrite(&(printer->rwlock));
+ for (job = (ippeve_job_t *)cupsArrayFirst(printer->jobs);
+ job;
+ job = (ippeve_job_t *)cupsArrayNext(printer->jobs))
+ if (job->completed && job->completed < cleantime)
+ {
+ cupsArrayRemove(printer->jobs, job);
+ delete_job(job);
+ }
+ else
+ break;
+ _cupsRWUnlock(&(printer->rwlock));
+}
+
+
+/*
+ * 'compare_jobs()' - Compare two jobs.
+ */
+
+static int /* O - Result of comparison */
+compare_jobs(ippeve_job_t *a, /* I - First job */
+ ippeve_job_t *b) /* I - Second job */
+{
+ return (b->id - a->id);
+}
+
+
+/*
+ * 'copy_attributes()' - Copy attributes from one request to another.
+ */
+
+static void
+copy_attributes(ipp_t *to, /* I - Destination request */
+ ipp_t *from, /* I - Source request */
+ cups_array_t *ra, /* I - Requested attributes */
+ ipp_tag_t group_tag, /* I - Group to copy */
+ int quickcopy) /* I - Do a quick copy? */
+{
+ ippeve_filter_t filter; /* Filter data */
+
+
+ filter.ra = ra;
+ filter.group_tag = group_tag;
+
+ ippCopyAttributes(to, from, quickcopy, (ipp_copycb_t)filter_cb, &filter);
+}
+
+
+/*
+ * 'copy_job_attrs()' - Copy job attributes to the response.
+ */
+
+static void
+copy_job_attributes(
+ ippeve_client_t *client, /* I - Client */
+ ippeve_job_t *job, /* I - Job */
+ cups_array_t *ra) /* I - requested-attributes */
+{
+ copy_attributes(client->response, job->attrs, ra, IPP_TAG_JOB, 0);
+
+ if (!ra || cupsArrayFind(ra, "date-time-at-completed"))
+ {
+ if (job->completed)
+ ippAddDate(client->response, IPP_TAG_JOB, "date-time-at-completed", ippTimeToDate(job->completed));
+ else
+ ippAddOutOfBand(client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-completed");
+ }
+
+ if (!ra || cupsArrayFind(ra, "date-time-at-processing"))
+ {
+ if (job->processing)
+ ippAddDate(client->response, IPP_TAG_JOB, "date-time-at-processing", ippTimeToDate(job->processing));
+ else
+ ippAddOutOfBand(client->response, IPP_TAG_JOB, IPP_TAG_NOVALUE, "date-time-at-processing");
+ }
+
+ if (!ra || cupsArrayFind(ra, "job-impressions"))
+ ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions", job->impressions);
+
+ if (!ra || cupsArrayFind(ra, "job-impressions-completed"))
+ ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions-completed", job->impcompleted);
+
+ if (!ra || cupsArrayFind(ra, "job-printer-up-time"))
+ ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-printer-up-time", (int)(time(NULL) - client->printer->start_time));
+
+ if (!ra || cupsArrayFind(ra, "job-state"))
+ ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", (int)job->state);
+
+ if (!ra || cupsArrayFind(ra, "job-state-message"))
+ {
+ if (job->message)
+ {
+ ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_TEXT, "job-state-message", NULL, job->message);
+ }
+ else
+ {
+ switch (job->state)
+ {
+ case IPP_JSTATE_PENDING :
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job pending.");
+ break;
+
+ case IPP_JSTATE_HELD :
+ if (job->fd >= 0)
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job incoming.");
+ else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO))
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job held.");
+ else
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job created.");
+ break;
+
+ case IPP_JSTATE_PROCESSING :
+ if (job->cancel)
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job canceling.");
+ else
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job printing.");
+ break;
+
+ case IPP_JSTATE_STOPPED :
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job stopped.");
+ break;
+
+ case IPP_JSTATE_CANCELED :
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job canceled.");
+ break;
+
+ case IPP_JSTATE_ABORTED :
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job aborted.");
+ break;
+
+ case IPP_JSTATE_COMPLETED :
+ ippAddString(client->response, IPP_TAG_JOB, IPP_CONST_TAG(IPP_TAG_TEXT), "job-state-message", NULL, "Job completed.");
+ break;
+ }
+ }
+ }
+
+ if (!ra || cupsArrayFind(ra, "job-state-reasons"))
+ {
+ switch (job->state)
+ {
+ case IPP_JSTATE_PENDING :
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
+ NULL, "none");
+ break;
+
+ case IPP_JSTATE_HELD :
+ if (job->fd >= 0)
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD),
+ "job-state-reasons", NULL, "job-incoming");
+ else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO))
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD),
+ "job-state-reasons", NULL, "job-hold-until-specified");
+ else
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD),
+ "job-state-reasons", NULL, "job-data-insufficient");
+ break;
+
+ case IPP_JSTATE_PROCESSING :
+ if (job->cancel)
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD),
+ "job-state-reasons", NULL, "processing-to-stop-point");
+ else
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD),
+ "job-state-reasons", NULL, "job-printing");
+ break;
+
+ case IPP_JSTATE_STOPPED :
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
+ NULL, "job-stopped");
+ break;
+
+ case IPP_JSTATE_CANCELED :
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
+ NULL, "job-canceled-by-user");
+ break;
+
+ case IPP_JSTATE_ABORTED :
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
+ NULL, "aborted-by-system");
+ break;
+
+ case IPP_JSTATE_COMPLETED :
+ ippAddString(client->response, IPP_TAG_JOB,
+ IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
+ NULL, "job-completed-successfully");
+ break;
+ }
+ }
+
+ if (!ra || cupsArrayFind(ra, "time-at-completed"))
+ ippAddInteger(client->response, IPP_TAG_JOB,
+ job->completed ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
+ "time-at-completed", (int)(job->completed - client->printer->start_time));
+
+ if (!ra || cupsArrayFind(ra, "time-at-processing"))
+ ippAddInteger(client->response, IPP_TAG_JOB,
+ job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
+ "time-at-processing", (int)(job->processing - client->printer->start_time));
+}
+
+
+/*
+ * 'create_client()' - Accept a new network connection and create a client
+ * object.
+ */
+
+static ippeve_client_t * /* O - Client */
+create_client(ippeve_printer_t *printer, /* I - Printer */
+ int sock) /* I - Listen socket */
+{
+ ippeve_client_t *client; /* Client */
+
+
+ if ((client = calloc(1, sizeof(ippeve_client_t))) == NULL)
+ {
+ perror("Unable to allocate memory for client");
+ return (NULL);
+ }
+
+ client->printer = printer;
+
+ /*
+ * Accept the client and get the remote address...
+ */
+
+ if ((client->http = httpAcceptConnection(sock, 1)) == NULL)
+ {
+ perror("Unable to accept client connection");
+
+ free(client);
+
+ return (NULL);
+ }
+
+ httpGetHostname(client->http, client->hostname, sizeof(client->hostname));
+
+ if (Verbosity)
+ fprintf(stderr, "Accepted connection from %s\n", client->hostname);
+
+ return (client);
+}
+
+
+/*
+ * 'create_job()' - Create a new job object from a Print-Job or Create-Job
+ * request.
+ */
+
+static ippeve_job_t * /* O - Job */
+create_job(ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* Job */
+ ipp_attribute_t *attr; /* Job attribute */
+ char uri[1024], /* job-uri value */
+ uuid[64]; /* job-uuid value */
+
+
+ _cupsRWLockWrite(&(client->printer->rwlock));
+ if (client->printer->active_job &&
+ client->printer->active_job->state < IPP_JSTATE_CANCELED)
+ {
+ /*
+ * Only accept a single job at a time...
+ */
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+ return (NULL);
+ }
+
+ /*
+ * Allocate and initialize the job object...
+ */
+
+ if ((job = calloc(1, sizeof(ippeve_job_t))) == NULL)
+ {
+ perror("Unable to allocate memory for job");
+ return (NULL);
+ }
+
+ job->printer = client->printer;
+ job->attrs = ippNew();
+ job->state = IPP_JSTATE_HELD;
+ job->fd = -1;
+
+ /*
+ * Copy all of the job attributes...
+ */
+
+ copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0);
+
+ /*
+ * Get the requesting-user-name, document format, and priority...
+ */
+
+ if ((attr = ippFindAttribute(client->request, "requesting-user-name", IPP_TAG_NAME)) != NULL)
+ job->username = ippGetString(attr, 0, NULL);
+ else
+ job->username = "anonymous";
+
+ ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-originating-user-name", NULL, job->username);
+
+ if (ippGetOperation(client->request) != IPP_OP_CREATE_JOB)
+ {
+ if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL)
+ job->format = ippGetString(attr, 0, NULL);
+ else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL)
+ job->format = ippGetString(attr, 0, NULL);
+ else
+ job->format = "application/octet-stream";
+ }
+
+ if ((attr = ippFindAttribute(client->request, "job-impressions", IPP_TAG_INTEGER)) != NULL)
+ job->impressions = ippGetInteger(attr, 0);
+
+ if ((attr = ippFindAttribute(client->request, "job-name", IPP_TAG_NAME)) != NULL)
+ job->name = ippGetString(attr, 0, NULL);
+
+ /*
+ * Add job description attributes and add to the jobs array...
+ */
+
+ job->id = client->printer->next_job_id ++;
+
+ snprintf(uri, sizeof(uri), "%s/%d", client->printer->uri, job->id);
+ httpAssembleUUID(client->printer->hostname, client->printer->port, client->printer->name, job->id, uuid, sizeof(uuid));
+
+ ippAddDate(job->attrs, IPP_TAG_JOB, "date-time-at-creation", ippTimeToDate(time(&job->created)));
+ ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
+ ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri);
+ ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uuid", NULL, uuid);
+ if ((attr = ippFindAttribute(client->request, "printer-uri", IPP_TAG_URI)) != NULL)
+ ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, ippGetString(attr, 0, NULL));
+ else
+ ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, client->printer->uri);
+ ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", (int)(job->created - client->printer->start_time));
+
+ cupsArrayAdd(client->printer->jobs, job);
+ client->printer->active_job = job;
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ return (job);
+}
+
+
+/*
+ * 'create_job_file()' - Create a file for the document in a job.
+ */
+
+static int /* O - File descriptor or -1 on error */
+create_job_file(
+ ippeve_job_t *job, /* I - Job */
+ char *fname, /* I - Filename buffer */
+ size_t fnamesize, /* I - Size of filename buffer */
+ const char *directory, /* I - Directory to store in */
+ const char *ext) /* I - Extension (`NULL` for default) */
+{
+ char name[256], /* "Safe" filename */
+ *nameptr; /* Pointer into filename */
+ const char *job_name; /* job-name value */
+
+
+ /*
+ * Make a name from the job-name attribute...
+ */
+
+ if ((job_name = ippGetString(ippFindAttribute(job->attrs, "job-name", IPP_TAG_NAME), 0, NULL)) == NULL)
+ job_name = "untitled";
+
+ for (nameptr = name; *job_name && nameptr < (name + sizeof(name) - 1); job_name ++)
+ {
+ if (isalnum(*job_name & 255) || *job_name == '-')
+ {
+ *nameptr++ = (char)tolower(*job_name & 255);
+ }
+ else
+ {
+ *nameptr++ = '_';
+
+ while (job_name[1] && !isalnum(job_name[1] & 255) && job_name[1] != '-')
+ job_name ++;
+ }
+ }
+
+ *nameptr = '\0';
+
+ /*
+ * Figure out the extension...
+ */
+
+ if (!ext)
+ {
+ if (!strcasecmp(job->format, "image/jpeg"))
+ ext = "jpg";
+ else if (!strcasecmp(job->format, "image/png"))
+ ext = "png";
+ else if (!strcasecmp(job->format, "image/pwg-raster"))
+ ext = "pwg";
+ else if (!strcasecmp(job->format, "image/urf"))
+ ext = "urf";
+ else if (!strcasecmp(job->format, "application/pdf"))
+ ext = "pdf";
+ else if (!strcasecmp(job->format, "application/postscript"))
+ ext = "ps";
+ else if (!strcasecmp(job->format, "application/vnd.hp-pcl"))
+ ext = "pcl";
+ else
+ ext = "dat";
+ }
+
+ /*
+ * Create a filename with the job-id, job-name, and document-format (extension)...
+ */
+
+ snprintf(fname, fnamesize, "%s/%d-%s.%s", directory, job->id, name, ext);
+
+ return (open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0666));
+}
+
+
+/*
+ * 'create_listener()' - Create a listener socket.
+ */
+
+static int /* O - Listener socket or -1 on error */
+create_listener(const char *name, /* I - Host name (`NULL` for any address) */
+ int port, /* I - Port number */
+ int family) /* I - Address family */
+{
+ int sock; /* Listener socket */
+ http_addrlist_t *addrlist; /* Listen address */
+ char service[255]; /* Service port */
+
+
+ snprintf(service, sizeof(service), "%d", port);
+ if ((addrlist = httpAddrGetList(name, family, service)) == NULL)
+ return (-1);
+
+ sock = httpAddrListen(&(addrlist->addr), port);
+
+ httpAddrFreeList(addrlist);
+
+ return (sock);
+}
+
+
+/*
+ * 'create_media_col()' - Create a media-col value.
+ */
+
+static ipp_t * /* O - media-col collection */
+create_media_col(const char *media, /* I - Media name */
+ const char *source, /* I - Media source, if any */
+ const char *type, /* I - Media type, if any */
+ int width, /* I - x-dimension in 2540ths */
+ int length, /* I - y-dimension in 2540ths */
+ int bottom, /* I - Bottom margin in 2540ths */
+ int left, /* I - Left margin in 2540ths */
+ int right, /* I - Right margin in 2540ths */
+ int top) /* I - Top margin in 2540ths */
+{
+ ipp_t *media_col = ippNew(), /* media-col value */
+ *media_size = create_media_size(width, length);
+ /* media-size value */
+ char media_key[256]; /* media-key value */
+ const char *media_key_suffix = ""; /* media-key suffix */
+
+
+ if (bottom == 0 && left == 0 && right == 0 && top == 0)
+ media_key_suffix = "_borderless";
+
+ if (type && source)
+ snprintf(media_key, sizeof(media_key), "%s_%s_%s%s", media, source, type, media_key_suffix);
+ else if (type)
+ snprintf(media_key, sizeof(media_key), "%s__%s%s", media, type, media_key_suffix);
+ else if (source)
+ snprintf(media_key, sizeof(media_key), "%s_%s%s", media, source, media_key_suffix);
+ else
+ snprintf(media_key, sizeof(media_key), "%s%s", media, media_key_suffix);
+
+ ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL, media_key);
+ ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size);
+ ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-size-name", NULL, media);
+ if (bottom >= 0)
+ ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin", bottom);
+ if (left >= 0)
+ ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", left);
+ if (right >= 0)
+ ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin", right);
+ if (top >= 0)
+ ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", top);
+ if (source)
+ ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source", NULL, source);
+ if (type)
+ ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type", NULL, type);
+
+ ippDelete(media_size);
+
+ return (media_col);
+}
+
+
+/*
+ * 'create_media_size()' - Create a media-size value.
+ */
+
+static ipp_t * /* O - media-col collection */
+create_media_size(int width, /* I - x-dimension in 2540ths */
+ int length) /* I - y-dimension in 2540ths */
+{
+ ipp_t *media_size = ippNew(); /* media-size value */
+
+
+ ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension", width);
+ ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension", length);
+
+ return (media_size);
+}
+
+
+/*
+ * 'create_printer()' - Create, register, and listen for connections to a
+ * printer object.
+ */
+
+static ippeve_printer_t * /* O - Printer */
+create_printer(
+ const char *servername, /* I - Server hostname (NULL for default) */
+ int serverport, /* I - Server port */
+ const char *name, /* I - printer-name */
+ const char *location, /* I - printer-location */
+ const char *icon, /* I - printer-icons */
+ cups_array_t *docformats, /* I - document-format-supported */
+ const char *subtypes, /* I - Bonjour service subtype(s) */
+ const char *directory, /* I - Spool directory */
+ const char *command, /* I - Command to run on job files, if any */
+ const char *device_uri, /* I - Output device, if any */
+ ipp_t *attrs) /* I - Capability attributes */
+{
+ ippeve_printer_t *printer; /* Printer */
+ int i; /* Looping var */
+#ifndef _WIN32
+ char path[1024]; /* Full path to command */
+#endif /* !_WIN32 */
+ char uri[1024], /* Printer URI */
+#ifdef HAVE_SSL
+ securi[1024], /* Secure printer URI */
+ *uris[2], /* All URIs */
+#endif /* HAVE_SSL */
+ icons[1024], /* printer-icons URI */
+ adminurl[1024], /* printer-more-info URI */
+ supplyurl[1024],/* printer-supply-info-uri URI */
+ uuid[128]; /* printer-uuid */
+ int k_supported; /* Maximum file size supported */
+ int num_formats; /* Number of supported document formats */
+ const char *formats[100], /* Supported document formats */
+ *format; /* Current format */
+ int num_job_attrs; /* Number of supported job attributes */
+ const char *job_attrs[100];/* Job attributes */
+ char xxx_supported[256];
+ /* Name of -supported attribute */
+ _cups_globals_t *cg = _cupsGlobals();
+ /* Global path values */
+#ifdef HAVE_STATVFS
+ struct statvfs spoolinfo; /* FS info for spool directory */
+ double spoolsize; /* FS size */
+#elif defined(HAVE_STATFS)
+ struct statfs spoolinfo; /* FS info for spool directory */
+ double spoolsize; /* FS size */
+#endif /* HAVE_STATVFS */
+ static const char * const versions[] =/* ipp-versions-supported values */
+ {
+ "1.1",
+ "2.0"
+ };
+ static const char * const features[] =/* ipp-features-supported values */
+ {
+ "ipp-everywhere"
+ };
+ static const int ops[] = /* operations-supported values */
+ {
+ IPP_OP_PRINT_JOB,
+ IPP_OP_PRINT_URI,
+ IPP_OP_VALIDATE_JOB,
+ IPP_OP_CREATE_JOB,
+ IPP_OP_SEND_DOCUMENT,
+ IPP_OP_SEND_URI,
+ IPP_OP_CANCEL_JOB,
+ IPP_OP_GET_JOB_ATTRIBUTES,
+ IPP_OP_GET_JOBS,
+ IPP_OP_GET_PRINTER_ATTRIBUTES,
+ IPP_OP_CANCEL_MY_JOBS,
+ IPP_OP_CLOSE_JOB,
+ IPP_OP_IDENTIFY_PRINTER
+ };
+ static const char * const charsets[] =/* charset-supported values */
+ {
+ "us-ascii",
+ "utf-8"
+ };
+ static const char * const compressions[] =/* compression-supported values */
+ {
+#ifdef HAVE_LIBZ
+ "deflate",
+ "gzip",
+#endif /* HAVE_LIBZ */
+ "none"
+ };
+ static const char * const identify_actions[] =
+ {
+ "display",
+ "sound"
+ };
+ static const char * const job_creation[] =
+ { /* job-creation-attributes-supported values */
+ "copies",
+ "document-password",
+ "finishings",
+ "finishings-col",
+ "job-password",
+ "job-password-encryption",
+ "orientation-requested",
+ "output-bin",
+ "overrides",
+ "page-ranges",
+ "print-color-mode",
+ "print-content-optimize",
+ "print-rendering-intent",
+ "print-quality",
+ "printer-resolution",
+ "sides"
+ };
+ static const char * const media_col_supported[] =
+ { /* media-col-supported values */
+ "media-bottom-margin",
+ "media-left-margin",
+ "media-right-margin",
+ "media-size",
+ "media-size-name",
+ "media-source",
+ "media-top-margin",
+ "media-type"
+ };
+ static const char * const multiple_document_handling[] =
+ { /* multiple-document-handling-supported values */
+ "separate-documents-uncollated-copies",
+ "separate-documents-collated-copies"
+ };
+ static const char * const reference_uri_schemes_supported[] =
+ { /* reference-uri-schemes-supported */
+ "file",
+ "ftp",
+ "http"
+#ifdef HAVE_SSL
+ , "https"
+#endif /* HAVE_SSL */
+ };
+#ifdef HAVE_SSL
+ static const char * const uri_authentication_supported[] =
+ { /* uri-authentication-supported values */
+ "none",
+ "none"
+ };
+ static const char * const uri_security_supported[] =
+ { /* uri-security-supported values */
+ "none",
+ "tls"
+ };
+#endif /* HAVE_SSL */
+ static const char * const which_jobs[] =
+ { /* which-jobs-supported values */
+ "completed",
+ "not-completed",
+ "aborted",
+ "all",
+ "canceled",
+ "pending",
+ "pending-held",
+ "processing",
+ "processing-stopped"
+ };
+
+
+#ifndef _WIN32
+ /*
+ * If a command was specified, make sure it exists and is executable...
+ */
+
+ if (command)
+ {
+ if (*command == '/' || !strncmp(command, "./", 2))
+ {
+ if (access(command, X_OK))
+ {
+ _cupsLangPrintf(stderr, _("Unable to execute command \"%s\": %s"), command, strerror(errno));
+ return (NULL);
+ }
+ }
+ else
+ {
+ snprintf(path, sizeof(path), "%s/ippeveprinter/%s", cg->cups_serverbin, command);
+
+ if (access(command, X_OK))
+ {
+ _cupsLangPrintf(stderr, _("Unable to execute command \"%s\": %s"), command, strerror(errno));
+ return (NULL);
+ }
+
+ command = path;
+ }
+ }
+#endif /* !_WIN32 */
+
+ /*
+ * Allocate memory for the printer...
+ */
+
+ if ((printer = calloc(1, sizeof(ippeve_printer_t))) == NULL)
+ {
+ _cupsLangPrintError(NULL, _("Unable to allocate memory for printer"));
+ return (NULL);
+ }
+
+ printer->ipv4 = -1;
+ printer->ipv6 = -1;
+ printer->name = strdup(name);
+ printer->dnssd_name = strdup(name);
+ printer->command = command ? strdup(command) : NULL;
+ printer->device_uri = device_uri ? strdup(device_uri) : NULL;
+ printer->directory = strdup(directory);
+ printer->icon = icon ? strdup(icon) : NULL;
+ printer->port = serverport;
+ printer->start_time = time(NULL);
+ printer->config_time = printer->start_time;
+ printer->state = IPP_PSTATE_IDLE;
+ printer->state_reasons = IPPEVE_PREASON_NONE;
+ printer->state_time = printer->start_time;
+ printer->jobs = cupsArrayNew((cups_array_func_t)compare_jobs, NULL);
+ printer->next_job_id = 1;
+
+ if (servername)
+ {
+ printer->hostname = strdup(servername);
+ }
+ else
+ {
+ char temp[1024]; /* Temporary string */
+
+ printer->hostname = strdup(httpGetHostname(NULL, temp, sizeof(temp)));
+ }
+
+ _cupsRWInit(&(printer->rwlock));
+
+ /*
+ * Create the listener sockets...
+ */
+
+ if ((printer->ipv4 = create_listener(servername, printer->port, AF_INET)) < 0)
+ {
+ perror("Unable to create IPv4 listener");
+ goto bad_printer;
+ }
+
+ if ((printer->ipv6 = create_listener(servername, printer->port, AF_INET6)) < 0)
+ {
+ perror("Unable to create IPv6 listener");
+ goto bad_printer;
+ }
+
+ /*
+ * Prepare URI values for the printer attributes...
+ */
+
+ httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, printer->hostname, printer->port, "/ipp/print");
+ printer->uri = strdup(uri);
+ printer->urilen = strlen(uri);
+
+#ifdef HAVE_SSL
+ httpAssembleURI(HTTP_URI_CODING_ALL, securi, sizeof(securi), "ipps", NULL, printer->hostname, printer->port, "/ipp/print");
+#endif /* HAVE_SSL */
+
+ httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), WEB_SCHEME, NULL, printer->hostname, printer->port, "/icon.png");
+ httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/");
+ httpAssembleURI(HTTP_URI_CODING_ALL, supplyurl, sizeof(supplyurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/supplies");
+ httpAssembleUUID(printer->hostname, serverport, name, 0, uuid, sizeof(uuid));
+
+ if (Verbosity)
+ {
+ fprintf(stderr, "printer-more-info=\"%s\"\n", adminurl);
+ fprintf(stderr, "printer-supply-info-uri=\"%s\"\n", supplyurl);
+#ifdef HAVE_SSL
+ fprintf(stderr, "printer-uri=\"%s\"\n", uri);
+#else
+ fprintf(stderr, "printer-uri=\"%s\",\"%s\"\n", uri, securi);
+#endif /* HAVE_SSL */
+ }
+
+ /*
+ * Get the maximum spool size based on the size of the filesystem used for
+ * the spool directory. If the host OS doesn't support the statfs call
+ * or the filesystem is larger than 2TiB, always report INT_MAX.
+ */
+
+#ifdef HAVE_STATVFS
+ if (statvfs(printer->directory, &spoolinfo))
+ k_supported = INT_MAX;
+ else if ((spoolsize = (double)spoolinfo.f_frsize *
+ spoolinfo.f_blocks / 1024) > INT_MAX)
+ k_supported = INT_MAX;
+ else
+ k_supported = (int)spoolsize;
+
+#elif defined(HAVE_STATFS)
+ if (statfs(printer->directory, &spoolinfo))
+ k_supported = INT_MAX;
+ else if ((spoolsize = (double)spoolinfo.f_bsize *
+ spoolinfo.f_blocks / 1024) > INT_MAX)
+ k_supported = INT_MAX;
+ else
+ k_supported = (int)spoolsize;
+
+#else
+ k_supported = INT_MAX;
+#endif /* HAVE_STATVFS */
+
+ /*
+ * Assemble the final list of document formats...
+ */
+
+ if (!cupsArrayFind(docformats, (void *)"application/octet-stream"))
+ cupsArrayAdd(docformats, (void *)"application/octet-stream");
+
+ for (num_formats = 0, format = (const char *)cupsArrayFirst(docformats); format && num_formats < (int)(sizeof(formats) / sizeof(formats[0])); format = (const char *)cupsArrayNext(docformats))
+ formats[num_formats ++] = format;
+
+ /*
+ * Get the list of attributes that can be used when creating a job...
+ */
+
+ num_job_attrs = 0;
+ job_attrs[num_job_attrs ++] = "ipp-attribute-fidelity";
+ job_attrs[num_job_attrs ++] = "job-name";
+ job_attrs[num_job_attrs ++] = "job-priority";
+ job_attrs[num_job_attrs ++] = "media";
+ job_attrs[num_job_attrs ++] = "media-col";
+ job_attrs[num_job_attrs ++] = "multiple-document-handling";
+
+ for (i = 0; i < (int)(sizeof(job_creation) / sizeof(job_creation[0])) && num_job_attrs < (int)(sizeof(job_attrs) / sizeof(job_attrs[0])); i ++)
+ {
+ snprintf(xxx_supported, sizeof(xxx_supported), "%s-supported", job_creation[i]);
+ if (ippFindAttribute(attrs, xxx_supported, IPP_TAG_ZERO))
+ job_attrs[num_job_attrs ++] = job_creation[i];
+ }
+
+ /*
+ * Fill out the rest of the printer attributes.
+ */
+
+ printer->attrs = attrs;
+
+ /* charset-configured */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-configured", NULL, "utf-8");
+
+ /* charset-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-supported", sizeof(charsets) / sizeof(charsets[0]), NULL, charsets);
+
+ /* compression-supported */
+ if (!ippFindAttribute(printer->attrs, "compression-supported", IPP_TAG_ZERO))
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "compression-supported", (int)(sizeof(compressions) / sizeof(compressions[0])), NULL, compressions);
+
+ /* document-format-default */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_MIMETYPE), "document-format-default", NULL, "application/octet-stream");
+
+ /* document-format-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, "document-format-supported", num_formats, NULL, formats);
+
+ /* generated-natural-language-supported */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_LANGUAGE), "generated-natural-language-supported", NULL, "en");
+
+ /* identify-actions-default */
+ ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-default", NULL, "sound");
+
+ /* identify-actions-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-supported", sizeof(identify_actions) / sizeof(identify_actions[0]), NULL, identify_actions);
+
+ /* ipp-features-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-features-supported", sizeof(features) / sizeof(features[0]), NULL, features);
+
+ /* ipp-versions-supported */
+ if (MaxVersion == 11)
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", NULL, "1.1");
+ else
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", (int)(sizeof(versions) / sizeof(versions[0])), NULL, versions);
+
+ /* job-creation-attributes-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-creation-attributes-supported", num_job_attrs, NULL, job_attrs);
+
+ /* job-ids-supported */
+ ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-ids-supported", 1);
+
+ /* job-k-octets-supported */
+ ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0, k_supported);
+
+ /* job-priority-default */
+ ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-default", 50);
+
+ /* job-priority-supported */
+ ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-supported", 1);
+
+ /* job-sheets-default */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-default", NULL, "none");
+
+ /* job-sheets-supported */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-supported", NULL, "none");
+
+ /* media-col-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-col-supported", (int)(sizeof(media_col_supported) / sizeof(media_col_supported[0])), NULL, media_col_supported);
+
+ /* multiple-document-handling-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-document-handling-supported", sizeof(multiple_document_handling) / sizeof(multiple_document_handling[0]), NULL, multiple_document_handling);
+
+ /* multiple-document-jobs-supported */
+ ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "multiple-document-jobs-supported", 0);
+
+ /* multiple-operation-time-out */
+ ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "multiple-operation-time-out", 60);
+
+ /* multiple-operation-time-out-action */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-operation-time-out-action", NULL, "abort-job");
+
+ /* natural-language-configured */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_LANGUAGE), "natural-language-configured", NULL, "en");
+
+ /* operations-supported */
+ ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "operations-supported", sizeof(ops) / sizeof(ops[0]), ops);
+
+ /* pdl-override-supported */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pdl-override-supported", NULL, "attempted");
+
+ /* preferred-attributes-supported */
+ ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "preferred-attributes-supported", 0);
+
+ /* printer-get-attributes-supported */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-get-attributes-supported", NULL, "document-format");
+
+ /* printer-geo-location */
+ ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_UNKNOWN, "printer-geo-location");
+
+ /* printer-icons */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-icons", NULL, icons);
+
+ /* printer-is-accepting-jobs */
+ ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1);
+
+ /* printer-info */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL, name);
+
+ /* printer-location */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-location", NULL, location);
+
+ /* printer-more-info */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-more-info", NULL, adminurl);
+
+ /* printer-name */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", NULL, name);
+
+ /* printer-organization */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organization", NULL, "");
+
+ /* printer-organizational-unit */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organizational-unit", NULL, "");
+
+ /* printer-supply-info-uri */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-supply-info-uri", NULL, supplyurl);
+
+ /* printer-uri-supported */
+#ifdef HAVE_SSL
+ uris[0] = uri;
+ uris[1] = securi;
+
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", 2, NULL, (const char **)uris);
+
+#else
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", NULL, uri);
+#endif /* HAVE_SSL */
+
+ /* printer-uuid */
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uuid", NULL, uuid);
+
+ /* reference-uri-scheme-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_URISCHEME), "reference-uri-schemes-supported", (int)(sizeof(reference_uri_schemes_supported) / sizeof(reference_uri_schemes_supported[0])), NULL, reference_uri_schemes_supported);
+
+ /* uri-authentication-supported */
+#ifdef HAVE_SSL
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", 2, NULL, uri_authentication_supported);
+#else
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", NULL, "none");
+#endif /* HAVE_SSL */
+
+ /* uri-security-supported */
+#ifdef HAVE_SSL
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-security-supported", 2, NULL, uri_security_supported);
+#else
+ ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-security-supported", NULL, "none");
+#endif /* HAVE_SSL */
+
+ /* which-jobs-supported */
+ ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "which-jobs-supported", sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs);
+
+ debug_attributes("Printer", printer->attrs, 0);
+
+ /*
+ * Register the printer with Bonjour...
+ */
+
+ if (!register_printer(printer, subtypes))
+ goto bad_printer;
+
+ /*
+ * Return it!
+ */
+
+ return (printer);
+
+
+ /*
+ * If we get here we were unable to create the printer...
+ */
+
+ bad_printer:
+
+ delete_printer(printer);
+
+ return (NULL);
+}
+
+
+/*
+ * 'debug_attributes()' - Print attributes in a request or response.
+ */
+
+static void
+debug_attributes(const char *title, /* I - Title */
+ ipp_t *ipp, /* I - Request/response */
+ int type) /* I - 0 = object, 1 = request, 2 = response */
+{
+ ipp_tag_t group_tag; /* Current group */
+ ipp_attribute_t *attr; /* Current attribute */
+ char buffer[2048]; /* String buffer for value */
+ int major, minor; /* Version */
+
+
+ if (Verbosity <= 1)
+ return;
+
+ fprintf(stderr, "%s:\n", title);
+ major = ippGetVersion(ipp, &minor);
+ fprintf(stderr, " version=%d.%d\n", major, minor);
+ if (type == 1)
+ fprintf(stderr, " operation-id=%s(%04x)\n",
+ ippOpString(ippGetOperation(ipp)), ippGetOperation(ipp));
+ else if (type == 2)
+ fprintf(stderr, " status-code=%s(%04x)\n",
+ ippErrorString(ippGetStatusCode(ipp)), ippGetStatusCode(ipp));
+ fprintf(stderr, " request-id=%d\n\n", ippGetRequestId(ipp));
+
+ for (attr = ippFirstAttribute(ipp), group_tag = IPP_TAG_ZERO;
+ attr;
+ attr = ippNextAttribute(ipp))
+ {
+ if (ippGetGroupTag(attr) != group_tag)
+ {
+ group_tag = ippGetGroupTag(attr);
+ fprintf(stderr, " %s\n", ippTagString(group_tag));
+ }
+
+ if (ippGetName(attr))
+ {
+ ippAttributeString(attr, buffer, sizeof(buffer));
+ fprintf(stderr, " %s (%s%s) %s\n", ippGetName(attr),
+ ippGetCount(attr) > 1 ? "1setOf " : "",
+ ippTagString(ippGetValueTag(attr)), buffer);
+ }
+ }
+}
+
+
+/*
+ * 'delete_client()' - Close the socket and free all memory used by a client
+ * object.
+ */
+
+static void
+delete_client(ippeve_client_t *client) /* I - Client */
+{
+ if (Verbosity)
+ fprintf(stderr, "Closing connection from %s\n", client->hostname);
+
+ /*
+ * Flush pending writes before closing...
+ */
+
+ httpFlushWrite(client->http);
+
+ /*
+ * Free memory...
+ */
+
+ httpClose(client->http);
+
+ ippDelete(client->request);
+ ippDelete(client->response);
+
+ free(client);
+}
+
+
+/*
+ * 'delete_job()' - Remove from the printer and free all memory used by a job
+ * object.
+ */
+
+static void
+delete_job(ippeve_job_t *job) /* I - Job */
+{
+ if (Verbosity)
+ fprintf(stderr, "[Job %d] Removing job from history.\n", job->id);
+
+ ippDelete(job->attrs);
+
+ if (job->message)
+ free(job->message);
+
+ if (job->filename)
+ {
+ if (!KeepFiles)
+ unlink(job->filename);
+
+ free(job->filename);
+ }
+
+ free(job);
+}
+
+
+/*
+ * 'delete_printer()' - Unregister, close listen sockets, and free all memory
+ * used by a printer object.
+ */
+
+static void
+delete_printer(ippeve_printer_t *printer) /* I - Printer */
+{
+ if (printer->ipv4 >= 0)
+ close(printer->ipv4);
+
+ if (printer->ipv6 >= 0)
+ close(printer->ipv6);
+
+#if HAVE_DNSSD
+ if (printer->printer_ref)
+ DNSServiceRefDeallocate(printer->printer_ref);
+ if (printer->ipp_ref)
+ DNSServiceRefDeallocate(printer->ipp_ref);
+ if (printer->ipps_ref)
+ DNSServiceRefDeallocate(printer->ipps_ref);
+ if (printer->http_ref)
+ DNSServiceRefDeallocate(printer->http_ref);
+#elif defined(HAVE_AVAHI)
+ avahi_threaded_poll_lock(DNSSDMaster);
+
+ if (printer->printer_ref)
+ avahi_entry_group_free(printer->printer_ref);
+ if (printer->ipp_ref)
+ avahi_entry_group_free(printer->ipp_ref);
+ if (printer->ipps_ref)
+ avahi_entry_group_free(printer->ipps_ref);
+ if (printer->http_ref)
+ avahi_entry_group_free(printer->http_ref);
+
+ avahi_threaded_poll_unlock(DNSSDMaster);
+#endif /* HAVE_DNSSD */
+
+ if (printer->dnssd_name)
+ free(printer->dnssd_name);
+ if (printer->name)
+ free(printer->name);
+ if (printer->icon)
+ free(printer->icon);
+ if (printer->command)
+ free(printer->command);
+ if (printer->device_uri)
+ free(printer->device_uri);
+#if !CUPS_LITE
+ if (printer->ppdfile)
+ free(printer->ppdfile);
+#endif /* !CUPS_LITE */
+ if (printer->directory)
+ free(printer->directory);
+ if (printer->hostname)
+ free(printer->hostname);
+ if (printer->uri)
+ free(printer->uri);
+
+ ippDelete(printer->attrs);
+ cupsArrayDelete(printer->jobs);
+
+ free(printer);
+}
+
+
+#ifdef HAVE_DNSSD
+/*
+ * 'dnssd_callback()' - Handle Bonjour registration events.
+ */
+
+static void DNSSD_API
+dnssd_callback(
+ DNSServiceRef sdRef, /* I - Service reference */
+ DNSServiceFlags flags, /* I - Status flags */
+ DNSServiceErrorType errorCode, /* I - Error, if any */
+ const char *name, /* I - Service name */
+ const char *regtype, /* I - Service type */
+ const char *domain, /* I - Domain for service */
+ ippeve_printer_t *printer) /* I - Printer */
+{
+ (void)sdRef;
+ (void)flags;
+ (void)domain;
+
+ if (errorCode)
+ {
+ fprintf(stderr, "DNSServiceRegister for %s failed with error %d.\n", regtype, (int)errorCode);
+ return;
+ }
+ else if (strcasecmp(name, printer->dnssd_name))
+ {
+ if (Verbosity)
+ fprintf(stderr, "Now using DNS-SD service name \"%s\".\n", name);
+
+ /* No lock needed since only the main thread accesses/changes this */
+ free(printer->dnssd_name);
+ printer->dnssd_name = strdup(name);
+ }
+}
+
+
+#elif defined(HAVE_AVAHI)
+/*
+ * 'dnssd_callback()' - Handle Bonjour registration events.
+ */
+
+static void
+dnssd_callback(
+ AvahiEntryGroup *srv, /* I - Service */
+ AvahiEntryGroupState state, /* I - Registration state */
+ void *context) /* I - Printer */
+{
+ (void)srv;
+ (void)state;
+ (void)context;
+}
+
+
+/*
+ * 'dnssd_client_cb()' - Client callback for Avahi.
+ *
+ * Called whenever the client or server state changes...
+ */
+
+static void
+dnssd_client_cb(
+ AvahiClient *c, /* I - Client */
+ AvahiClientState state, /* I - Current state */
+ void *userdata) /* I - User data (unused) */
+{
+ (void)userdata;
+
+ if (!c)
+ return;
+
+ switch (state)
+ {
+ default :
+ fprintf(stderr, "Ignored Avahi state %d.\n", state);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED)
+ {
+ fputs("Avahi server crashed, exiting.\n", stderr);
+ exit(1);
+ }
+ break;
+ }
+}
+#endif /* HAVE_DNSSD */
+
+
+/*
+ * 'dnssd_init()' - Initialize the DNS-SD service connections...
+ */
+
+static void
+dnssd_init(void)
+{
+#ifdef HAVE_DNSSD
+ if (DNSServiceCreateConnection(&DNSSDMaster) != kDNSServiceErr_NoError)
+ {
+ fputs("Error: Unable to initialize Bonjour.\n", stderr);
+ exit(1);
+ }
+
+#elif defined(HAVE_AVAHI)
+ int error; /* Error code, if any */
+
+ if ((DNSSDMaster = avahi_threaded_poll_new()) == NULL)
+ {
+ fputs("Error: Unable to initialize Bonjour.\n", stderr);
+ exit(1);
+ }
+
+ if ((DNSSDClient = avahi_client_new(avahi_threaded_poll_get(DNSSDMaster), AVAHI_CLIENT_NO_FAIL, dnssd_client_cb, NULL, &error)) == NULL)
+ {
+ fputs("Error: Unable to initialize Bonjour.\n", stderr);
+ exit(1);
+ }
+
+ avahi_threaded_poll_start(DNSSDMaster);
+#endif /* HAVE_DNSSD */
+}
+
+
+/*
+ * 'filter_cb()' - Filter printer attributes based on the requested array.
+ */
+
+static int /* O - 1 to copy, 0 to ignore */
+filter_cb(ippeve_filter_t *filter, /* I - Filter parameters */
+ ipp_t *dst, /* I - Destination (unused) */
+ ipp_attribute_t *attr) /* I - Source attribute */
+{
+ /*
+ * Filter attributes as needed...
+ */
+
+#ifndef _WIN32 /* Avoid MS compiler bug */
+ (void)dst;
+#endif /* !_WIN32 */
+
+ ipp_tag_t group = ippGetGroupTag(attr);
+ const char *name = ippGetName(attr);
+
+ if ((filter->group_tag != IPP_TAG_ZERO && group != filter->group_tag && group != IPP_TAG_ZERO) || !name || (!strcmp(name, "media-col-database") && !cupsArrayFind(filter->ra, (void *)name)))
+ return (0);
+
+ return (!filter->ra || cupsArrayFind(filter->ra, (void *)name) != NULL);
+}
+
+
+/*
+ * 'find_job()' - Find a job specified in a request.
+ */
+
+static ippeve_job_t * /* O - Job or NULL */
+find_job(ippeve_client_t *client) /* I - Client */
+{
+ ipp_attribute_t *attr; /* job-id or job-uri attribute */
+ ippeve_job_t key, /* Job search key */
+ *job; /* Matching job, if any */
+
+
+ if ((attr = ippFindAttribute(client->request, "job-uri", IPP_TAG_URI)) != NULL)
+ {
+ const char *uri = ippGetString(attr, 0, NULL);
+
+ if (!strncmp(uri, client->printer->uri, client->printer->urilen) &&
+ uri[client->printer->urilen] == '/')
+ key.id = atoi(uri + client->printer->urilen + 1);
+ else
+ return (NULL);
+ }
+ else if ((attr = ippFindAttribute(client->request, "job-id", IPP_TAG_INTEGER)) != NULL)
+ key.id = ippGetInteger(attr, 0);
+
+ _cupsRWLockRead(&(client->printer->rwlock));
+ job = (ippeve_job_t *)cupsArrayFind(client->printer->jobs, &key);
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ return (job);
+}
+
+
+/*
+ * 'finish_document()' - Finish receiving a document file and start processing.
+ */
+
+static void
+finish_document_data(
+ ippeve_client_t *client, /* I - Client */
+ ippeve_job_t *job) /* I - Job */
+{
+ char filename[1024], /* Filename buffer */
+ buffer[4096]; /* Copy buffer */
+ ssize_t bytes; /* Bytes read */
+ cups_array_t *ra; /* Attributes to send in response */
+ _cups_thread_t t; /* Thread */
+
+
+ /*
+ * Create a file for the request data...
+ *
+ * TODO: Update code to support piping large raster data to the print command.
+ */
+
+ if ((job->fd = create_job_file(job, filename, sizeof(filename), client->printer->directory, NULL)) < 0)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror(errno));
+
+ goto abort_job;
+ }
+
+ if (Verbosity)
+ fprintf(stderr, "Created job file \"%s\", format \"%s\".\n", filename, job->format);
+
+ while ((bytes = httpRead2(client->http, buffer, sizeof(buffer))) > 0)
+ {
+ if (write(job->fd, buffer, (size_t)bytes) < bytes)
+ {
+ int error = errno; /* Write error */
+
+ close(job->fd);
+ job->fd = -1;
+
+ unlink(filename);
+
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error));
+
+ goto abort_job;
+ }
+ }
+
+ if (bytes < 0)
+ {
+ /*
+ * Got an error while reading the print data, so abort this job.
+ */
+
+ close(job->fd);
+ job->fd = -1;
+
+ unlink(filename);
+
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to read print file.");
+
+ goto abort_job;
+ }
+
+ if (close(job->fd))
+ {
+ int error = errno; /* Write error */
+
+ job->fd = -1;
+
+ unlink(filename);
+
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error));
+
+ goto abort_job;
+ }
+
+ job->fd = -1;
+ job->filename = strdup(filename);
+ job->state = IPP_JSTATE_PENDING;
+
+ /*
+ * Process the job...
+ */
+
+ t = _cupsThreadCreate((_cups_thread_func_t)process_job, job);
+
+ if (t)
+ {
+ _cupsThreadDetach(t);
+ }
+ else
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job.");
+ goto abort_job;
+ }
+
+ /*
+ * Return the job info...
+ */
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+
+ ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+ cupsArrayAdd(ra, "job-id");
+ cupsArrayAdd(ra, "job-state");
+ cupsArrayAdd(ra, "job-state-message");
+ cupsArrayAdd(ra, "job-state-reasons");
+ cupsArrayAdd(ra, "job-uri");
+
+ copy_job_attributes(client, job, ra);
+ cupsArrayDelete(ra);
+ return;
+
+ /*
+ * If we get here we had to abort the job...
+ */
+
+ abort_job:
+
+ job->state = IPP_JSTATE_ABORTED;
+ job->completed = time(NULL);
+
+ ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+ cupsArrayAdd(ra, "job-id");
+ cupsArrayAdd(ra, "job-state");
+ cupsArrayAdd(ra, "job-state-reasons");
+ cupsArrayAdd(ra, "job-uri");
+
+ copy_job_attributes(client, job, ra);
+ cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'finish_uri()' - Finish fetching a document URI and start processing.
+ */
+
+static void
+finish_document_uri(
+ ippeve_client_t *client, /* I - Client */
+ ippeve_job_t *job) /* I - Job */
+{
+ ipp_attribute_t *uri; /* document-uri */
+ char scheme[256], /* URI scheme */
+ userpass[256], /* Username and password info */
+ hostname[256], /* Hostname */
+ resource[1024]; /* Resource path */
+ int port; /* Port number */
+ http_uri_status_t uri_status; /* URI decode status */
+ http_encryption_t encryption; /* Encryption to use, if any */
+ http_t *http; /* Connection for http/https URIs */
+ http_status_t status; /* Access status for http/https URIs */
+ int infile; /* Input file for local file URIs */
+ char filename[1024], /* Filename buffer */
+ buffer[4096]; /* Copy buffer */
+ ssize_t bytes; /* Bytes read */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_array_t *ra; /* Attributes to send in response */
+
+
+ /*
+ * Do we have a file to print?
+ */
+
+ if (httpGetState(client->http) == HTTP_STATE_POST_RECV)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Unexpected document data following request.");
+
+ goto abort_job;
+ }
+
+ /*
+ * Do we have a document URI?
+ */
+
+ if ((uri = ippFindAttribute(client->request, "document-uri", IPP_TAG_URI)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing document-uri.");
+
+ goto abort_job;
+ }
+
+ if (ippGetCount(uri) != 1)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Too many document-uri values.");
+
+ goto abort_job;
+ }
+
+ uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL),
+ scheme, sizeof(scheme), userpass,
+ sizeof(userpass), hostname, sizeof(hostname),
+ &port, resource, sizeof(resource));
+ if (uri_status < HTTP_URI_STATUS_OK)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad document-uri: %s", httpURIStatusString(uri_status));
+
+ goto abort_job;
+ }
+
+ if (strcmp(scheme, "file") &&
+#ifdef HAVE_SSL
+ strcmp(scheme, "https") &&
+#endif /* HAVE_SSL */
+ strcmp(scheme, "http"))
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_URI_SCHEME, "URI scheme \"%s\" not supported.", scheme);
+
+ goto abort_job;
+ }
+
+ if (!strcmp(scheme, "file") && access(resource, R_OK))
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to access URI: %s", strerror(errno));
+
+ goto abort_job;
+ }
+
+ /*
+ * Get the document format for the job...
+ */
+
+ _cupsRWLockWrite(&(client->printer->rwlock));
+
+ if ((attr = ippFindAttribute(job->attrs, "document-format", IPP_TAG_MIMETYPE)) != NULL)
+ job->format = ippGetString(attr, 0, NULL);
+ else
+ job->format = "application/octet-stream";
+
+ /*
+ * Create a file for the request data...
+ */
+
+ if ((job->fd = create_job_file(job, filename, sizeof(filename), client->printer->directory, NULL)) < 0)
+ {
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to create print file: %s", strerror(errno));
+
+ goto abort_job;
+ }
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ if (!strcmp(scheme, "file"))
+ {
+ if ((infile = open(resource, O_RDONLY)) < 0)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to access URI: %s", strerror(errno));
+
+ goto abort_job;
+ }
+
+ do
+ {
+ if ((bytes = read(infile, buffer, sizeof(buffer))) < 0 &&
+ (errno == EAGAIN || errno == EINTR))
+ {
+ bytes = 1;
+ }
+ else if (bytes > 0 && write(job->fd, buffer, (size_t)bytes) < bytes)
+ {
+ int error = errno; /* Write error */
+
+ close(job->fd);
+ job->fd = -1;
+
+ unlink(filename);
+ close(infile);
+
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error));
+
+ goto abort_job;
+ }
+ }
+ while (bytes > 0);
+
+ close(infile);
+ }
+ else
+ {
+#ifdef HAVE_SSL
+ if (port == 443 || !strcmp(scheme, "https"))
+ encryption = HTTP_ENCRYPTION_ALWAYS;
+ else
+#endif /* HAVE_SSL */
+ encryption = HTTP_ENCRYPTION_IF_REQUESTED;
+
+ if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption, 1, 30000, NULL)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to connect to %s: %s", hostname, cupsLastErrorString());
+
+ close(job->fd);
+ job->fd = -1;
+
+ unlink(filename);
+
+ goto abort_job;
+ }
+
+ httpClearFields(http);
+ httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en");
+ if (httpGet(http, resource))
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", strerror(errno));
+
+ close(job->fd);
+ job->fd = -1;
+
+ unlink(filename);
+ httpClose(http);
+
+ goto abort_job;
+ }
+
+ while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE);
+
+ if (status != HTTP_STATUS_OK)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS, "Unable to GET URI: %s", httpStatus(status));
+
+ close(job->fd);
+ job->fd = -1;
+
+ unlink(filename);
+ httpClose(http);
+
+ goto abort_job;
+ }
+
+ while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
+ {
+ if (write(job->fd, buffer, (size_t)bytes) < bytes)
+ {
+ int error = errno; /* Write error */
+
+ close(job->fd);
+ job->fd = -1;
+
+ unlink(filename);
+ httpClose(http);
+
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+ "Unable to write print file: %s", strerror(error));
+
+ goto abort_job;
+ }
+ }
+
+ httpClose(http);
+ }
+
+ if (close(job->fd))
+ {
+ int error = errno; /* Write error */
+
+ job->fd = -1;
+
+ unlink(filename);
+
+ respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to write print file: %s", strerror(error));
+
+ goto abort_job;
+ }
+
+ _cupsRWLockWrite(&(client->printer->rwlock));
+
+ job->fd = -1;
+ job->filename = strdup(filename);
+ job->state = IPP_JSTATE_PENDING;
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ /*
+ * Process the job...
+ */
+
+ process_job(job);
+
+ /*
+ * Return the job info...
+ */
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+
+ ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+ cupsArrayAdd(ra, "job-id");
+ cupsArrayAdd(ra, "job-state");
+ cupsArrayAdd(ra, "job-state-reasons");
+ cupsArrayAdd(ra, "job-uri");
+
+ copy_job_attributes(client, job, ra);
+ cupsArrayDelete(ra);
+ return;
+
+ /*
+ * If we get here we had to abort the job...
+ */
+
+ abort_job:
+
+ job->state = IPP_JSTATE_ABORTED;
+ job->completed = time(NULL);
+
+ ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+ cupsArrayAdd(ra, "job-id");
+ cupsArrayAdd(ra, "job-state");
+ cupsArrayAdd(ra, "job-state-reasons");
+ cupsArrayAdd(ra, "job-uri");
+
+ copy_job_attributes(client, job, ra);
+ cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'html_escape()' - Write a HTML-safe string.
+ */
+
+static void
+html_escape(ippeve_client_t *client, /* I - Client */
+ const char *s, /* I - String to write */
+ size_t slen) /* I - Number of characters to write */
+{
+ const char *start, /* Start of segment */
+ *end; /* End of string */
+
+
+ start = s;
+ end = s + (slen > 0 ? slen : strlen(s));
+
+ while (*s && s < end)
+ {
+ if (*s == '&' || *s == '<')
+ {
+ if (s > start)
+ httpWrite2(client->http, start, (size_t)(s - start));
+
+ if (*s == '&')
+ httpWrite2(client->http, "&amp;", 5);
+ else
+ httpWrite2(client->http, "&lt;", 4);
+
+ start = s + 1;
+ }
+
+ s ++;
+ }
+
+ if (s > start)
+ httpWrite2(client->http, start, (size_t)(s - start));
+}
+
+
+/*
+ * 'html_footer()' - Show the web interface footer.
+ *
+ * This function also writes the trailing 0-length chunk.
+ */
+
+static void
+html_footer(ippeve_client_t *client) /* I - Client */
+{
+ html_printf(client,
+ "</div>\n"
+ "</body>\n"
+ "</html>\n");
+ httpWrite2(client->http, "", 0);
+}
+
+
+/*
+ * 'html_header()' - Show the web interface header and title.
+ */
+
+static void
+html_header(ippeve_client_t *client, /* I - Client */
+ const char *title, /* I - Title */
+ int refresh) /* I - Refresh timer, if any */
+{
+ html_printf(client,
+ "<!doctype html>\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>%s</title>\n"
+ "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n"
+ "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n"
+ "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n", title);
+ if (refresh > 0)
+ html_printf(client, "<meta http-equiv=\"refresh\" content=\"%d\">\n", refresh);
+ html_printf(client,
+ "<meta name=\"viewport\" content=\"width=device-width\">\n"
+ "<style>\n"
+ "body { font-family: sans-serif; margin: 0; }\n"
+ "div.body { padding: 0px 10px 10px; }\n"
+ "span.badge { background: #090; border-radius: 5px; color: #fff; padding: 5px 10px; }\n"
+ "span.bar { box-shadow: 0px 1px 5px #333; font-size: 75%%; }\n"
+ "table.form { border-collapse: collapse; margin-left: auto; margin-right: auto; margin-top: 10px; width: auto; }\n"
+ "table.form td, table.form th { padding: 5px 2px; }\n"
+ "table.form td.meter { border-right: solid 1px #ccc; padding: 0px; width: 400px; }\n"
+ "table.form th { text-align: right; }\n"
+ "table.striped { border-bottom: solid thin black; border-collapse: collapse; width: 100%%; }\n"
+ "table.striped tr:nth-child(even) { background: #fcfcfc; }\n"
+ "table.striped tr:nth-child(odd) { background: #f0f0f0; }\n"
+ "table.striped th { background: white; border-bottom: solid thin black; text-align: left; vertical-align: bottom; }\n"
+ "table.striped td { margin: 0; padding: 5px; vertical-align: top; }\n"
+ "table.nav { border-collapse: collapse; width: 100%%; }\n"
+ "table.nav td { margin: 0; text-align: center; }\n"
+ "td.nav a, td.nav a:active, td.nav a:hover, td.nav a:hover:link, td.nav a:hover:link:visited, td.nav a:link, td.nav a:link:visited, td.nav a:visited { background: inherit; color: inherit; font-size: 80%%; text-decoration: none; }\n"
+ "td.nav { background: #333; color: #fff; padding: 4px 8px; width: 33%%; }\n"
+ "td.nav.sel { background: #fff; color: #000; font-weight: bold; }\n"
+ "td.nav:hover { background: #666; color: #fff; }\n"
+ "td.nav:active { background: #000; color: #ff0; }\n"
+ "</style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<table class=\"nav\"><tr>"
+ "<td class=\"nav%s\"><a href=\"/\">Status</a></td>"
+ "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>"
+ "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>"
+ "</tr></table>\n"
+ "<div class=\"body\">\n", !strcmp(client->uri, "/") ? " sel" : "", !strcmp(client->uri, "/supplies") ? " sel" : "", !strcmp(client->uri, "/media") ? " sel" : "");
+}
+
+
+/*
+ * 'html_printf()' - Send formatted text to the client, quoting as needed.
+ */
+
+static void
+html_printf(ippeve_client_t *client, /* I - Client */
+ const char *format, /* I - Printf-style format string */
+ ...) /* I - Additional arguments as needed */
+{
+ va_list ap; /* Pointer to arguments */
+ const char *start; /* Start of string */
+ char size, /* Size character (h, l, L) */
+ type; /* Format type character */
+ int width, /* Width of field */
+ prec; /* Number of characters of precision */
+ char tformat[100], /* Temporary format string for sprintf() */
+ *tptr, /* Pointer into temporary format */
+ temp[1024]; /* Buffer for formatted numbers */
+ char *s; /* Pointer to string */
+
+
+ /*
+ * Loop through the format string, formatting as needed...
+ */
+
+ va_start(ap, format);
+ start = format;
+
+ while (*format)
+ {
+ if (*format == '%')
+ {
+ if (format > start)
+ httpWrite2(client->http, start, (size_t)(format - start));
+
+ tptr = tformat;
+ *tptr++ = *format++;
+
+ if (*format == '%')
+ {
+ httpWrite2(client->http, "%", 1);
+ format ++;
+ start = format;
+ continue;
+ }
+ else if (strchr(" -+#\'", *format))
+ *tptr++ = *format++;
+
+ if (*format == '*')
+ {
+ /*
+ * Get width from argument...
+ */
+
+ format ++;
+ width = va_arg(ap, int);
+
+ snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", width);
+ tptr += strlen(tptr);
+ }
+ else
+ {
+ width = 0;
+
+ while (isdigit(*format & 255))
+ {
+ if (tptr < (tformat + sizeof(tformat) - 1))
+ *tptr++ = *format;
+
+ width = width * 10 + *format++ - '0';
+ }
+ }
+
+ if (*format == '.')
+ {
+ if (tptr < (tformat + sizeof(tformat) - 1))
+ *tptr++ = *format;
+
+ format ++;
+
+ if (*format == '*')
+ {
+ /*
+ * Get precision from argument...
+ */
+
+ format ++;
+ prec = va_arg(ap, int);
+
+ snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", prec);
+ tptr += strlen(tptr);
+ }
+ else
+ {
+ prec = 0;
+
+ while (isdigit(*format & 255))
+ {
+ if (tptr < (tformat + sizeof(tformat) - 1))
+ *tptr++ = *format;
+
+ prec = prec * 10 + *format++ - '0';
+ }
+ }
+ }
+
+ if (*format == 'l' && format[1] == 'l')
+ {
+ size = 'L';
+
+ if (tptr < (tformat + sizeof(tformat) - 2))
+ {
+ *tptr++ = 'l';
+ *tptr++ = 'l';
+ }
+
+ format += 2;
+ }
+ else if (*format == 'h' || *format == 'l' || *format == 'L')
+ {
+ if (tptr < (tformat + sizeof(tformat) - 1))
+ *tptr++ = *format;
+
+ size = *format++;
+ }
+ else
+ size = 0;
+
+
+ if (!*format)
+ {
+ start = format;
+ break;
+ }
+
+ if (tptr < (tformat + sizeof(tformat) - 1))
+ *tptr++ = *format;
+
+ type = *format++;
+ *tptr = '\0';
+ start = format;
+
+ switch (type)
+ {
+ case 'E' : /* Floating point formats */
+ case 'G' :
+ case 'e' :
+ case 'f' :
+ case 'g' :
+ if ((size_t)(width + 2) > sizeof(temp))
+ break;
+
+ sprintf(temp, tformat, va_arg(ap, double));
+
+ httpWrite2(client->http, temp, strlen(temp));
+ break;
+
+ case 'B' : /* Integer formats */
+ case 'X' :
+ case 'b' :
+ case 'd' :
+ case 'i' :
+ case 'o' :
+ case 'u' :
+ case 'x' :
+ if ((size_t)(width + 2) > sizeof(temp))
+ break;
+
+# ifdef HAVE_LONG_LONG
+ if (size == 'L')
+ sprintf(temp, tformat, va_arg(ap, long long));
+ else
+# endif /* HAVE_LONG_LONG */
+ if (size == 'l')
+ sprintf(temp, tformat, va_arg(ap, long));
+ else
+ sprintf(temp, tformat, va_arg(ap, int));
+
+ httpWrite2(client->http, temp, strlen(temp));
+ break;
+
+ case 'p' : /* Pointer value */
+ if ((size_t)(width + 2) > sizeof(temp))
+ break;
+
+ sprintf(temp, tformat, va_arg(ap, void *));
+
+ httpWrite2(client->http, temp, strlen(temp));
+ break;
+
+ case 'c' : /* Character or character array */
+ if (width <= 1)
+ {
+ temp[0] = (char)va_arg(ap, int);
+ temp[1] = '\0';
+ html_escape(client, temp, 1);
+ }
+ else
+ html_escape(client, va_arg(ap, char *), (size_t)width);
+ break;
+
+ case 's' : /* String */
+ if ((s = va_arg(ap, char *)) == NULL)
+ s = "(null)";
+
+ html_escape(client, s, strlen(s));
+ break;
+ }
+ }
+ else
+ format ++;
+ }
+
+ if (format > start)
+ httpWrite2(client->http, start, (size_t)(format - start));
+
+ va_end(ap);
+}
+
+
+/*
+ * 'ipp_cancel_job()' - Cancel a job.
+ */
+
+static void
+ipp_cancel_job(ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* Job information */
+
+
+ /*
+ * Get the job...
+ */
+
+ if ((job = find_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
+ return;
+ }
+
+ /*
+ * See if the job is already completed, canceled, or aborted; if so,
+ * we can't cancel...
+ */
+
+ switch (job->state)
+ {
+ case IPP_JSTATE_CANCELED :
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+ "Job #%d is already canceled - can\'t cancel.", job->id);
+ break;
+
+ case IPP_JSTATE_ABORTED :
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+ "Job #%d is already aborted - can\'t cancel.", job->id);
+ break;
+
+ case IPP_JSTATE_COMPLETED :
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+ "Job #%d is already completed - can\'t cancel.", job->id);
+ break;
+
+ default :
+ /*
+ * Cancel the job...
+ */
+
+ _cupsRWLockWrite(&(client->printer->rwlock));
+
+ if (job->state == IPP_JSTATE_PROCESSING ||
+ (job->state == IPP_JSTATE_HELD && job->fd >= 0))
+ job->cancel = 1;
+ else
+ {
+ job->state = IPP_JSTATE_CANCELED;
+ job->completed = time(NULL);
+ }
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+ break;
+ }
+}
+
+
+/*
+ * 'ipp_close_job()' - Close an open job.
+ */
+
+static void
+ipp_close_job(ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* Job information */
+
+
+ /*
+ * Get the job...
+ */
+
+ if ((job = find_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
+ return;
+ }
+
+ /*
+ * See if the job is already completed, canceled, or aborted; if so,
+ * we can't cancel...
+ */
+
+ switch (job->state)
+ {
+ case IPP_JSTATE_CANCELED :
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+ "Job #%d is canceled - can\'t close.", job->id);
+ break;
+
+ case IPP_JSTATE_ABORTED :
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+ "Job #%d is aborted - can\'t close.", job->id);
+ break;
+
+ case IPP_JSTATE_COMPLETED :
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+ "Job #%d is completed - can\'t close.", job->id);
+ break;
+
+ case IPP_JSTATE_PROCESSING :
+ case IPP_JSTATE_STOPPED :
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+ "Job #%d is already closed.", job->id);
+ break;
+
+ default :
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+ break;
+ }
+}
+
+
+/*
+ * 'ipp_create_job()' - Create a job object.
+ */
+
+static void
+ipp_create_job(ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* New job */
+ cups_array_t *ra; /* Attributes to send in response */
+
+
+ /*
+ * Validate print job attributes...
+ */
+
+ if (!valid_job_attributes(client))
+ {
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Do we have a file to print?
+ */
+
+ if (httpGetState(client->http) == HTTP_STATE_POST_RECV)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+ "Unexpected document data following request.");
+ return;
+ }
+
+ /*
+ * Create the job...
+ */
+
+ if ((job = create_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BUSY,
+ "Currently printing another job.");
+ return;
+ }
+
+ /*
+ * Return the job info...
+ */
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+
+ ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+ cupsArrayAdd(ra, "job-id");
+ cupsArrayAdd(ra, "job-state");
+ cupsArrayAdd(ra, "job-state-message");
+ cupsArrayAdd(ra, "job-state-reasons");
+ cupsArrayAdd(ra, "job-uri");
+
+ copy_job_attributes(client, job, ra);
+ cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_get_job_attributes()' - Get the attributes for a job object.
+ */
+
+static void
+ipp_get_job_attributes(
+ ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* Job */
+ cups_array_t *ra; /* requested-attributes */
+
+
+ if ((job = find_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job not found.");
+ return;
+ }
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+
+ ra = ippCreateRequestedArray(client->request);
+ copy_job_attributes(client, job, ra);
+ cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_get_jobs()' - Get a list of job objects.
+ */
+
+static void
+ipp_get_jobs(ippeve_client_t *client) /* I - Client */
+{
+ ipp_attribute_t *attr; /* Current attribute */
+ const char *which_jobs = NULL;
+ /* which-jobs values */
+ int job_comparison; /* Job comparison */
+ ipp_jstate_t job_state; /* job-state value */
+ int first_job_id, /* First job ID */
+ limit, /* Maximum number of jobs to return */
+ count; /* Number of jobs that match */
+ const char *username; /* Username */
+ ippeve_job_t *job; /* Current job pointer */
+ cups_array_t *ra; /* Requested attributes array */
+
+
+ /*
+ * See if the "which-jobs" attribute have been specified...
+ */
+
+ if ((attr = ippFindAttribute(client->request, "which-jobs",
+ IPP_TAG_KEYWORD)) != NULL)
+ {
+ which_jobs = ippGetString(attr, 0, NULL);
+ fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->hostname, which_jobs);
+ }
+
+ if (!which_jobs || !strcmp(which_jobs, "not-completed"))
+ {
+ job_comparison = -1;
+ job_state = IPP_JSTATE_STOPPED;
+ }
+ else if (!strcmp(which_jobs, "completed"))
+ {
+ job_comparison = 1;
+ job_state = IPP_JSTATE_CANCELED;
+ }
+ else if (!strcmp(which_jobs, "aborted"))
+ {
+ job_comparison = 0;
+ job_state = IPP_JSTATE_ABORTED;
+ }
+ else if (!strcmp(which_jobs, "all"))
+ {
+ job_comparison = 1;
+ job_state = IPP_JSTATE_PENDING;
+ }
+ else if (!strcmp(which_jobs, "canceled"))
+ {
+ job_comparison = 0;
+ job_state = IPP_JSTATE_CANCELED;
+ }
+ else if (!strcmp(which_jobs, "pending"))
+ {
+ job_comparison = 0;
+ job_state = IPP_JSTATE_PENDING;
+ }
+ else if (!strcmp(which_jobs, "pending-held"))
+ {
+ job_comparison = 0;
+ job_state = IPP_JSTATE_HELD;
+ }
+ else if (!strcmp(which_jobs, "processing"))
+ {
+ job_comparison = 0;
+ job_state = IPP_JSTATE_PROCESSING;
+ }
+ else if (!strcmp(which_jobs, "processing-stopped"))
+ {
+ job_comparison = 0;
+ job_state = IPP_JSTATE_STOPPED;
+ }
+ else
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES,
+ "The which-jobs value \"%s\" is not supported.", which_jobs);
+ ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
+ "which-jobs", NULL, which_jobs);
+ return;
+ }
+
+ /*
+ * See if they want to limit the number of jobs reported...
+ */
+
+ if ((attr = ippFindAttribute(client->request, "limit",
+ IPP_TAG_INTEGER)) != NULL)
+ {
+ limit = ippGetInteger(attr, 0);
+
+ fprintf(stderr, "%s Get-Jobs limit=%d", client->hostname, limit);
+ }
+ else
+ limit = 0;
+
+ if ((attr = ippFindAttribute(client->request, "first-job-id",
+ IPP_TAG_INTEGER)) != NULL)
+ {
+ first_job_id = ippGetInteger(attr, 0);
+
+ fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->hostname, first_job_id);
+ }
+ else
+ first_job_id = 1;
+
+ /*
+ * See if we only want to see jobs for a specific user...
+ */
+
+ username = NULL;
+
+ if ((attr = ippFindAttribute(client->request, "my-jobs",
+ IPP_TAG_BOOLEAN)) != NULL)
+ {
+ int my_jobs = ippGetBoolean(attr, 0);
+
+ fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->hostname, my_jobs ? "true" : "false");
+
+ if (my_jobs)
+ {
+ if ((attr = ippFindAttribute(client->request, "requesting-user-name",
+ IPP_TAG_NAME)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+ "Need requesting-user-name with my-jobs.");
+ return;
+ }
+
+ username = ippGetString(attr, 0, NULL);
+
+ fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n", client->hostname, username);
+ }
+ }
+
+ /*
+ * OK, build a list of jobs for this printer...
+ */
+
+ ra = ippCreateRequestedArray(client->request);
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+
+ _cupsRWLockRead(&(client->printer->rwlock));
+
+ for (count = 0, job = (ippeve_job_t *)cupsArrayFirst(client->printer->jobs);
+ (limit <= 0 || count < limit) && job;
+ job = (ippeve_job_t *)cupsArrayNext(client->printer->jobs))
+ {
+ /*
+ * Filter out jobs that don't match...
+ */
+
+ if ((job_comparison < 0 && job->state > job_state) ||
+ (job_comparison == 0 && job->state != job_state) ||
+ (job_comparison > 0 && job->state < job_state) ||
+ job->id < first_job_id ||
+ (username && job->username &&
+ strcasecmp(username, job->username)))
+ continue;
+
+ if (count > 0)
+ ippAddSeparator(client->response);
+
+ count ++;
+ copy_job_attributes(client, job, ra);
+ }
+
+ cupsArrayDelete(ra);
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+}
+
+
+/*
+ * 'ipp_get_printer_attributes()' - Get the attributes for a printer object.
+ */
+
+static void
+ipp_get_printer_attributes(
+ ippeve_client_t *client) /* I - Client */
+{
+ cups_array_t *ra; /* Requested attributes array */
+ ippeve_printer_t *printer; /* Printer */
+
+
+ /*
+ * Send the attributes...
+ */
+
+ ra = ippCreateRequestedArray(client->request);
+ printer = client->printer;
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+
+ _cupsRWLockRead(&(printer->rwlock));
+
+ copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO,
+ IPP_TAG_CUPS_CONST);
+
+ if (!ra || cupsArrayFind(ra, "printer-config-change-date-time"))
+ ippAddDate(client->response, IPP_TAG_PRINTER, "printer-config-change-date-time", ippTimeToDate(printer->config_time));
+
+ if (!ra || cupsArrayFind(ra, "printer-config-change-time"))
+ ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-config-change-time", (int)(printer->config_time - printer->start_time));
+
+ if (!ra || cupsArrayFind(ra, "printer-current-time"))
+ ippAddDate(client->response, IPP_TAG_PRINTER, "printer-current-time", ippTimeToDate(time(NULL)));
+
+
+ if (!ra || cupsArrayFind(ra, "printer-state"))
+ ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", (int)printer->state);
+
+ if (!ra || cupsArrayFind(ra, "printer-state-change-date-time"))
+ ippAddDate(client->response, IPP_TAG_PRINTER, "printer-state-change-date-time", ippTimeToDate(printer->state_time));
+
+ if (!ra || cupsArrayFind(ra, "printer-state-change-time"))
+ ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-state-change-time", (int)(printer->state_time - printer->start_time));
+
+ if (!ra || cupsArrayFind(ra, "printer-state-message"))
+ {
+ static const char * const messages[] = { "Idle.", "Printing.", "Stopped." };
+
+ ippAddString(client->response, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-state-message", NULL, messages[printer->state - IPP_PSTATE_IDLE]);
+ }
+
+ if (!ra || cupsArrayFind(ra, "printer-state-reasons"))
+ {
+ if (printer->state_reasons == IPPEVE_PREASON_NONE)
+ {
+ ippAddString(client->response, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-state-reasons", NULL, "none");
+ }
+ else
+ {
+ ipp_attribute_t *attr = NULL; /* printer-state-reasons */
+ ippeve_preason_t bit; /* Reason bit */
+ int i; /* Looping var */
+ char reason[32]; /* Reason string */
+
+ for (i = 0, bit = 1; i < (int)(sizeof(ippeve_preason_strings) / sizeof(ippeve_preason_strings[0])); i ++, bit *= 2)
+ {
+ if (printer->state_reasons & bit)
+ {
+ snprintf(reason, sizeof(reason), "%s-%s", ippeve_preason_strings[i], printer->state == IPP_PSTATE_IDLE ? "report" : printer->state == IPP_PSTATE_PROCESSING ? "warning" : "error");
+ if (attr)
+ ippSetString(client->response, &attr, ippGetCount(attr), reason);
+ else
+ attr = ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "printer-state-reasons", NULL, reason);
+ }
+ }
+ }
+ }
+
+ if (!ra || cupsArrayFind(ra, "printer-up-time"))
+ ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-up-time", (int)(time(NULL) - printer->start_time));
+
+ if (!ra || cupsArrayFind(ra, "queued-job-count"))
+ ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "queued-job-count", printer->active_job && printer->active_job->state < IPP_JSTATE_CANCELED);
+
+ _cupsRWUnlock(&(printer->rwlock));
+
+ cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_identify_printer()' - Beep or display a message.
+ */
+
+static void
+ipp_identify_printer(
+ ippeve_client_t *client) /* I - Client */
+{
+ ipp_attribute_t *actions, /* identify-actions */
+ *message; /* message */
+
+
+ actions = ippFindAttribute(client->request, "identify-actions", IPP_TAG_KEYWORD);
+ message = ippFindAttribute(client->request, "message", IPP_TAG_TEXT);
+
+ if (!actions || ippContainsString(actions, "sound"))
+ {
+ putchar(0x07);
+ fflush(stdout);
+ }
+
+ if (ippContainsString(actions, "display"))
+ printf("IDENTIFY from %s: %s\n", client->hostname, message ? ippGetString(message, 0, NULL) : "No message supplied");
+
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+}
+
+
+/*
+ * 'ipp_print_job()' - Create a job object with an attached document.
+ */
+
+static void
+ipp_print_job(ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* New job */
+
+
+ /*
+ * Validate print job attributes...
+ */
+
+ if (!valid_job_attributes(client))
+ {
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Do we have a file to print?
+ */
+
+ if (httpGetState(client->http) == HTTP_STATE_POST_SEND)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "No file in request.");
+ return;
+ }
+
+ /*
+ * Create the job...
+ */
+
+ if ((job = create_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BUSY, "Currently printing another job.");
+ return;
+ }
+
+ /*
+ * Then finish getting the document data and process things...
+ */
+
+ finish_document_data(client, job);
+}
+
+
+/*
+ * 'ipp_print_uri()' - Create a job object with a referenced document.
+ */
+
+static void
+ipp_print_uri(ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* New job */
+
+
+ /*
+ * Validate print job attributes...
+ */
+
+ if (!valid_job_attributes(client))
+ {
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Create the job...
+ */
+
+ if ((job = create_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BUSY, "Currently printing another job.");
+ return;
+ }
+
+ /*
+ * Then finish getting the document data and process things...
+ */
+
+ finish_document_uri(client, job);
+}
+
+
+/*
+ * 'ipp_send_document()' - Add an attached document to a job object created with
+ * Create-Job.
+ */
+
+static void
+ipp_send_document(
+ ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* Job information */
+ ipp_attribute_t *attr; /* Current attribute */
+
+
+ /*
+ * Get the job...
+ */
+
+ if ((job = find_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * See if we already have a document for this job or the job has already
+ * in a non-pending state...
+ */
+
+ if (job->state > IPP_JSTATE_HELD)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state.");
+ httpFlush(client->http);
+ return;
+ }
+ else if (job->filename || job->fd >= 0)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported.");
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Make sure we have the "last-document" operation attribute...
+ */
+
+ if ((attr = ippFindAttribute(client->request, "last-document", IPP_TAG_ZERO)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute.");
+ httpFlush(client->http);
+ return;
+ }
+ else if (ippGetGroupTag(attr) != IPP_TAG_OPERATION)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group.");
+ httpFlush(client->http);
+ return;
+ }
+ else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1 || !ippGetBoolean(attr, 0))
+ {
+ respond_unsupported(client, attr);
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Validate document attributes...
+ */
+
+ if (!valid_doc_attributes(client))
+ {
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Then finish getting the document data and process things...
+ */
+
+ _cupsRWLockWrite(&(client->printer->rwlock));
+
+ copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0);
+
+ if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL)
+ job->format = ippGetString(attr, 0, NULL);
+ else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL)
+ job->format = ippGetString(attr, 0, NULL);
+ else
+ job->format = "application/octet-stream";
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ finish_document_data(client, job);
+}
+
+
+/*
+ * 'ipp_send_uri()' - Add a referenced document to a job object created with
+ * Create-Job.
+ */
+
+static void
+ipp_send_uri(ippeve_client_t *client) /* I - Client */
+{
+ ippeve_job_t *job; /* Job information */
+ ipp_attribute_t *attr; /* Current attribute */
+
+
+ /*
+ * Get the job...
+ */
+
+ if ((job = find_job(client)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * See if we already have a document for this job or the job has already
+ * in a non-pending state...
+ */
+
+ if (job->state > IPP_JSTATE_HELD)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE, "Job is not in a pending state.");
+ httpFlush(client->http);
+ return;
+ }
+ else if (job->filename || job->fd >= 0)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED, "Multiple document jobs are not supported.");
+ httpFlush(client->http);
+ return;
+ }
+
+ if ((attr = ippFindAttribute(client->request, "last-document", IPP_TAG_ZERO)) == NULL)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing required last-document attribute.");
+ httpFlush(client->http);
+ return;
+ }
+ else if (ippGetGroupTag(attr) != IPP_TAG_OPERATION)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "The last-document attribute is not in the operation group.");
+ httpFlush(client->http);
+ return;
+ }
+ else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1 || !ippGetBoolean(attr, 0))
+ {
+ respond_unsupported(client, attr);
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Validate document attributes...
+ */
+
+ if (!valid_doc_attributes(client))
+ {
+ httpFlush(client->http);
+ return;
+ }
+
+ /*
+ * Then finish getting the document data and process things...
+ */
+
+ _cupsRWLockWrite(&(client->printer->rwlock));
+
+ copy_attributes(job->attrs, client->request, NULL, IPP_TAG_JOB, 0);
+
+ if ((attr = ippFindAttribute(job->attrs, "document-format-detected", IPP_TAG_MIMETYPE)) != NULL)
+ job->format = ippGetString(attr, 0, NULL);
+ else if ((attr = ippFindAttribute(job->attrs, "document-format-supplied", IPP_TAG_MIMETYPE)) != NULL)
+ job->format = ippGetString(attr, 0, NULL);
+ else
+ job->format = "application/octet-stream";
+
+ _cupsRWUnlock(&(client->printer->rwlock));
+
+ finish_document_uri(client, job);
+}
+
+
+/*
+ * 'ipp_validate_job()' - Validate job creation attributes.
+ */
+
+static void
+ipp_validate_job(ippeve_client_t *client) /* I - Client */
+{
+ if (valid_job_attributes(client))
+ respond_ipp(client, IPP_STATUS_OK, NULL);
+}
+
+
+/*
+ * 'ippserver_attr_cb()' - Determine whether an attribute should be loaded.
+ */
+
+static int /* O - 1 to use, 0 to ignore */
+ippserver_attr_cb(
+ _ipp_file_t *f, /* I - IPP file */
+ void *user_data, /* I - User data pointer (unused) */
+ const char *attr) /* I - Attribute name */
+{
+ int i, /* Current element */
+ result; /* Result of comparison */
+ static const char * const ignored[] =
+ { /* Ignored attributes */
+ "attributes-charset",
+ "attributes-natural-language",
+ "charset-configured",
+ "charset-supported",
+ "device-service-count",
+ "device-uuid",
+ "document-format-varying-attributes",
+ "generated-natural-language-supported",
+ "identify-actions-default",
+ "identify-actions-supported",
+ "ipp-features-supported",
+ "ipp-versions-supproted",
+ "ippget-event-life",
+ "job-hold-until-supported",
+ "job-hold-until-time-supported",
+ "job-ids-supported",
+ "job-k-octets-supported",
+ "job-settable-attributes-supported",
+ "multiple-document-jobs-supported",
+ "multiple-operation-time-out",
+ "multiple-operation-time-out-action",
+ "natural-language-configured",
+ "notify-attributes-supported",
+ "notify-events-default",
+ "notify-events-supported",
+ "notify-lease-duration-default",
+ "notify-lease-duration-supported",
+ "notify-max-events-supported",
+ "notify-pull-method-supported",
+ "operations-supported",
+ "printer-alert",
+ "printer-alert-description",
+ "printer-camera-image-uri",
+ "printer-charge-info",
+ "printer-charge-info-uri",
+ "printer-config-change-date-time",
+ "printer-config-change-time",
+ "printer-current-time",
+ "printer-detailed-status-messages",
+ "printer-dns-sd-name",
+ "printer-fax-log-uri",
+ "printer-get-attributes-supported",
+ "printer-icons",
+ "printer-id",
+ "printer-info",
+ "printer-is-accepting-jobs",
+ "printer-message-date-time",
+ "printer-message-from-operator",
+ "printer-message-time",
+ "printer-more-info",
+ "printer-service-type",
+ "printer-settable-attributes-supported",
+ "printer-state",
+ "printer-state-message",
+ "printer-state-reasons",
+ "printer-static-resource-directory-uri",
+ "printer-static-resource-k-octets-free",
+ "printer-static-resource-k-octets-supported",
+ "printer-strings-languages-supported",
+ "printer-strings-uri",
+ "printer-supply-info-uri",
+ "printer-up-time",
+ "printer-uri-supported",
+ "printer-xri-supported",
+ "queued-job-count",
+ "reference-uri-scheme-supported",
+ "uri-authentication-supported",
+ "uri-security-supported",
+ "which-jobs-supported",
+ "xri-authentication-supported",
+ "xri-security-supported",
+ "xri-uri-scheme-supported"
+ };
+
+
+ (void)f;
+ (void)user_data;
+
+ for (i = 0, result = 1; i < (int)(sizeof(ignored) / sizeof(ignored[0])); i ++)
+ {
+ if ((result = strcmp(attr, ignored[i])) <= 0)
+ break;
+ }
+
+ return (result != 0);
+}
+
+
+/*
+ * 'ippserver_error_cb()' - Log an error message.
+ */
+
+static int /* O - 1 to continue, 0 to stop */
+ippserver_error_cb(
+ _ipp_file_t *f, /* I - IPP file data */
+ void *user_data, /* I - User data pointer (unused) */
+ const char *error) /* I - Error message */
+{
+ (void)f;
+ (void)user_data;
+
+ _cupsLangPrintf(stderr, "%s\n", error);
+
+ return (1);
+}
+
+
+/*
+ * 'ippserver_token_cb()' - Process ippserver-specific config file tokens.
+ */
+
+static int /* O - 1 to continue, 0 to stop */
+ippserver_token_cb(
+ _ipp_file_t *f, /* I - IPP file data */
+ _ipp_vars_t *vars, /* I - IPP variables */
+ void *user_data, /* I - User data pointer (unused) */
+ const char *token) /* I - Current token */
+{
+ (void)vars;
+ (void)user_data;
+
+ if (!token)
+ {
+ /*
+ * NULL token means do the initial setup - create an empty IPP message and
+ * return...
+ */
+
+ f->attrs = ippNew();
+ f->group_tag = IPP_TAG_PRINTER;
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("Unknown directive \"%s\" on line %d of \"%s\" ignored."), token, f->linenum, f->filename);
+ }
+
+ return (1);
+}
+
+
+/*
+ * 'load_ippserver_attributes()' - Load IPP attributes from an ippserver file.
+ */
+
+static ipp_t * /* O - IPP attributes or `NULL` on error */
+load_ippserver_attributes(
+ const char *servername, /* I - Server name or `NULL` for default */
+ int serverport, /* I - Server port number */
+ const char *filename, /* I - ippserver attribute filename */
+ cups_array_t *docformats) /* I - document-format-supported values */
+{
+ ipp_t *attrs; /* IPP attributes */
+ _ipp_vars_t vars; /* IPP variables */
+ char temp[256]; /* Temporary string */
+
+
+ (void)docformats; /* for now */
+
+ /*
+ * Setup callbacks and variables for the printer configuration file...
+ *
+ * The following additional variables are supported:
+ *
+ * - SERVERNAME: The host name of the server.
+ * - SERVERPORT: The default port of the server.
+ */
+
+ _ippVarsInit(&vars, (_ipp_fattr_cb_t)ippserver_attr_cb, (_ipp_ferror_cb_t)ippserver_error_cb, (_ipp_ftoken_cb_t)ippserver_token_cb);
+
+ if (servername)
+ {
+ _ippVarsSet(&vars, "SERVERNAME", servername);
+ }
+ else
+ {
+ httpGetHostname(NULL, temp, sizeof(temp));
+ _ippVarsSet(&vars, "SERVERNAME", temp);
+ }
+
+ snprintf(temp, sizeof(temp), "%d", serverport);
+ _ippVarsSet(&vars, "SERVERPORT", temp);
+
+ /*
+ * Load attributes and values for the printer...
+ */
+
+ attrs = _ippFileParse(&vars, filename, NULL);
+
+ /*
+ * Free memory and return...
+ */
+
+ _ippVarsDeinit(&vars);
+
+ return (attrs);
+}
+
+
+/*
+ * 'load_legacy_attributes()' - Load IPP attributes using the old ippserver
+ * options.
+ */
+
+static ipp_t * /* O - IPP attributes or `NULL` on error */
+load_legacy_attributes(
+ const char *make, /* I - Manufacturer name */
+ const char *model, /* I - Model name */
+ int ppm, /* I - pages-per-minute */
+ int ppm_color, /* I - pages-per-minute-color */
+ int duplex, /* I - Duplex support? */
+ cups_array_t *docformats) /* I - document-format-supported values */
+{
+ int i; /* Looping var */
+ ipp_t *attrs, /* IPP attributes */
+ *col; /* Collection value */
+ ipp_attribute_t *attr; /* Current attribute */
+ char device_id[1024],/* printer-device-id */
+ *ptr, /* Pointer into device ID */
+ make_model[128];/* printer-make-and-model */
+ const char *format, /* Current document format */
+ *prefix; /* Prefix for device ID */
+ int num_media; /* Number of media */
+ const char * const *media; /* List of media */
+ int num_ready; /* Number of loaded media */
+ const char * const *ready; /* List of loaded media */
+ pwg_media_t *pwg; /* PWG media size information */
+ static const char * const media_supported[] =
+ { /* media-supported values */
+ "na_letter_8.5x11in", /* Letter */
+ "na_legal_8.5x14in", /* Legal */
+ "iso_a4_210x297mm", /* A4 */
+ "na_number-10_4.125x9.5in", /* #10 Envelope */
+ "iso_dl_110x220mm" /* DL Envelope */
+ };
+ static const char * const media_supported_color[] =
+ { /* media-supported values */
+ "na_letter_8.5x11in", /* Letter */
+ "na_legal_8.5x14in", /* Legal */
+ "iso_a4_210x297mm", /* A4 */
+ "na_number-10_4.125x9.5in", /* #10 Envelope */
+ "iso_dl_110x220mm", /* DL Envelope */
+ "na_index-3x5_3x5in", /* Photo 3x5 */
+ "oe_photo-l_3.5x5in", /* Photo L */
+ "na_index-4x6_4x6in", /* Photo 4x6 */
+ "iso_a6_105x148mm", /* A6 */
+ "na_5x7_5x7in" /* Photo 5x7 aka 2L */
+ "iso_a5_148x210mm", /* A5 */
+ };
+ static const char * const media_ready[] =
+ { /* media-ready values */
+ "na_letter_8.5x11in", /* Letter */
+ "na_number-10_4.125x9.5in" /* #10 */
+ };
+ static const char * const media_ready_color[] =
+ { /* media-ready values */
+ "na_letter_8.5x11in", /* Letter */
+ "na_index-4x6_4x6in" /* Photo 4x6 */
+ };
+ static const char * const media_source_supported[] =
+ { /* media-source-supported values */
+ "auto",
+ "main",
+ "manual",
+ "by-pass-tray" /* AKA multi-purpose tray */
+ };
+ static const char * const media_source_supported_color[] =
+ { /* media-source-supported values */
+ "auto",
+ "main",
+ "photo"
+ };
+ static const char * const media_type_supported[] =
+ { /* media-type-supported values */
+ "auto",
+ "cardstock",
+ "envelope",
+ "labels",
+ "other",
+ "stationery",
+ "stationery-letterhead",
+ "transparency"
+ };
+ static const char * const media_type_supported_color[] =
+ { /* media-type-supported values */
+ "auto",
+ "cardstock",
+ "envelope",
+ "labels",
+ "other",
+ "stationery",
+ "stationery-letterhead",
+ "transparency",
+ "photographic-glossy",
+ "photographic-high-gloss",
+ "photographic-matte",
+ "photographic-satin",
+ "photographic-semi-gloss"
+ };
+ static const int media_bottom_margin_supported[] =
+ { /* media-bottom-margin-supported values */
+ 635 /* 1/4" */
+ };
+ static const int media_bottom_margin_supported_color[] =
+ { /* media-bottom/top-margin-supported values */
+ 0, /* Borderless */
+ 1168 /* 0.46" (common HP inkjet bottom margin) */
+ };
+ static const int media_lr_margin_supported[] =
+ { /* media-left/right-margin-supported values */
+ 340, /* 3.4mm (historical HP PCL A4 margin) */
+ 635 /* 1/4" */
+ };
+ static const int media_lr_margin_supported_color[] =
+ { /* media-left/right-margin-supported values */
+ 0, /* Borderless */
+ 340, /* 3.4mm (historical HP PCL A4 margin) */
+ 635 /* 1/4" */
+ };
+ static const int media_top_margin_supported[] =
+ { /* media-top-margin-supported values */
+ 635 /* 1/4" */
+ };
+ static const int media_top_margin_supported_color[] =
+ { /* media-top/top-margin-supported values */
+ 0, /* Borderless */
+ 102 /* 0.04" (common HP inkjet top margin */
+ };
+ static const int orientation_requested_supported[4] =
+ { /* orientation-requested-supported values */
+ IPP_ORIENT_PORTRAIT,
+ IPP_ORIENT_LANDSCAPE,
+ IPP_ORIENT_REVERSE_LANDSCAPE,
+ IPP_ORIENT_REVERSE_PORTRAIT
+ };
+ static const char * const overrides_supported[] =
+ { /* overrides-supported values */
+ "document-numbers",
+ "media",
+ "media-col",
+ "orientation-requested",
+ "pages"
+ };
+ static const char * const print_color_mode_supported[] =
+ { /* print-color-mode-supported values */
+ "monochrome"
+ };
+ static const char * const print_color_mode_supported_color[] =
+ { /* print-color-mode-supported values */
+ "auto",
+ "color",
+ "monochrome"
+ };
+ static const int print_quality_supported[] =
+ { /* print-quality-supported values */
+ IPP_QUALITY_DRAFT,
+ IPP_QUALITY_NORMAL,
+ IPP_QUALITY_HIGH
+ };
+ static const char * const printer_input_tray[] =
+ { /* printer-input-tray values */
+ "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto",
+ "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=100;status=0;name=main",
+ "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=manual",
+ "type=sheetFeedAutoNonRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=by-pass-tray"
+ };
+ static const char * const printer_input_tray_color[] =
+ { /* printer-input-tray values */
+ "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto",
+ "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=-2;status=0;name=main",
+ "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=photo"
+ };
+ static const char * const printer_supply[] =
+ { /* printer-supply values */
+ "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
+ "maxcapacity=100;level=25;colorantname=unknown;",
+ "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
+ "maxcapacity=100;level=75;colorantname=black;"
+ };
+ static const char * const printer_supply_color[] =
+ { /* printer-supply values */
+ "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;"
+ "maxcapacity=100;level=25;colorantname=unknown;",
+ "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=75;colorantname=black;",
+ "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=50;colorantname=cyan;",
+ "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=33;colorantname=magenta;",
+ "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=67;colorantname=yellow;"
+ };
+ static const char * const printer_supply_description[] =
+ { /* printer-supply-description values */
+ "Toner Waste Tank",
+ "Black Toner"
+ };
+ static const char * const printer_supply_description_color[] =
+ { /* printer-supply-description values */
+ "Ink Waste Tank",
+ "Black Ink",
+ "Cyan Ink",
+ "Magenta Ink",
+ "Yellow Ink"
+ };
+ static const int pwg_raster_document_resolution_supported[] =
+ {
+ 300,
+ 600
+ };
+ static const char * const pwg_raster_document_type_supported[] =
+ {
+ "black_1",
+ "sgray_8"
+ };
+ static const char * const pwg_raster_document_type_supported_color[] =
+ {
+ "black_1",
+ "sgray_8",
+ "srgb_8",
+ "srgb_16"
+ };
+ static const char * const sides_supported[] =
+ { /* sides-supported values */
+ "one-sided",
+ "two-sided-long-edge",
+ "two-sided-short-edge"
+ };
+ static const char * const urf_supported[] =
+ { /* urf-supported values */
+ "CP1",
+ "IS1-4-5-19",
+ "MT1-2-3-4-5-6",
+ "RS600",
+ "V1.4",
+ "W8"
+ };
+ static const char * const urf_supported_color[] =
+ { /* urf-supported values */
+ "CP1",
+ "IS1-4-5-7-19",
+ "MT1-2-3-4-5-6-8-9-10-11-12-13",
+ "RS600",
+ "SRGB24",
+ "V1.4",
+ "W8"
+ };
+ static const char * const urf_supported_color_duplex[] =
+ { /* urf-supported values */
+ "CP1",
+ "IS1-4-5-7-19",
+ "MT1-2-3-4-5-6-8-9-10-11-12-13",
+ "RS600",
+ "SRGB24",
+ "V1.4",
+ "W8",
+ "DM3"
+ };
+ static const char * const urf_supported_duplex[] =
+ { /* urf-supported values */
+ "CP1",
+ "IS1-4-5-19",
+ "MT1-2-3-4-5-6",
+ "RS600",
+ "V1.4",
+ "W8",
+ "DM1"
+ };
+
+
+ attrs = ippNew();
+
+ if (ppm_color > 0)
+ {
+ num_media = (int)(sizeof(media_supported_color) / sizeof(media_supported_color[0]));
+ media = media_supported_color;
+ num_ready = (int)(sizeof(media_ready_color) / sizeof(media_ready_color[0]));
+ ready = media_ready_color;
+ }
+ else
+ {
+ num_media = (int)(sizeof(media_supported) / sizeof(media_supported[0]));
+ media = media_supported;
+ num_ready = (int)(sizeof(media_ready) / sizeof(media_ready[0]));
+ ready = media_ready;
+ }
+
+ /* color-supported */
+ ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported", ppm_color > 0);
+
+ /* copies-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1);
+
+ /* copies-supported */
+ ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1, (cupsArrayFind(docformats, (void *)"application/pdf") != NULL || cupsArrayFind(docformats, (void *)"image/jpeg") != NULL) ? 999 : 1);
+
+ /* document-password-supported */
+ if (cupsArrayFind(docformats, (void *)"application/pdf"))
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 1023);
+
+ /* finishings-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE);
+
+ /* finishings-supported */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", IPP_FINISHINGS_NONE);
+
+ /* media-bottom-margin-supported */
+ if (ppm_color > 0)
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", (int)(sizeof(media_bottom_margin_supported) / sizeof(media_bottom_margin_supported[0])), media_bottom_margin_supported);
+ else
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", (int)(sizeof(media_bottom_margin_supported_color) / sizeof(media_bottom_margin_supported_color[0])), media_bottom_margin_supported_color);
+
+ /* media-col-database and media-col-default */
+ attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database", num_media, NULL);
+ for (i = 0; i < num_media; i ++)
+ {
+ int bottom, left, /* media-xxx-margins */
+ right, top;
+ const char *source; /* media-source, if any */
+
+ pwg = pwgMediaForPWG(media[i]);
+
+ if (pwg->width < 21000 && pwg->length < 21000)
+ {
+ source = "photo"; /* Photo size media from photo tray */
+ bottom = /* Borderless margins */
+ left =
+ right =
+ top = 0;
+ }
+ else if (pwg->width < 21000)
+ {
+ source = "by-pass-tray"; /* Envelopes from multi-purpose tray */
+ bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
+ left = /* Left/right margins are standard */
+ right = media_lr_margin_supported[1];
+ top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
+ }
+ else if (pwg->width == 21000)
+ {
+ source = NULL; /* A4 from any tray */
+ bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
+ left = /* Left/right margins are reduced */
+ right = media_lr_margin_supported[0];
+ top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
+ }
+ else
+ {
+ source = NULL; /* Other size media from any tray */
+ bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
+ left = /* Left/right margins are standard */
+ right = media_lr_margin_supported[1];
+ top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
+ }
+
+ col = create_media_col(media[i], source, NULL, pwg->width, pwg->length, bottom, left, right, top);
+ ippSetCollection(attrs, &attr, i, col);
+
+ ippDelete(col);
+ }
+
+ /* media-col-default */
+ pwg = pwgMediaForPWG(ready[0]);
+
+ if (pwg->width == 21000)
+ col = create_media_col(ready[0], "main", "stationery", pwg->width, pwg->length, ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0], media_lr_margin_supported[0], media_lr_margin_supported[0], ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]);
+ else
+ col = create_media_col(ready[0], "main", "stationery", pwg->width, pwg->length, ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0], media_lr_margin_supported[1], media_lr_margin_supported[1], ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0]);
+
+ ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col);
+
+ ippDelete(col);
+
+ /* media-col-ready */
+ attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-ready", num_ready, NULL);
+ for (i = 0; i < num_ready; i ++)
+ {
+ int bottom, left, /* media-xxx-margins */
+ right, top;
+ const char *source, /* media-source */
+ *type; /* media-type */
+
+ pwg = pwgMediaForPWG(ready[i]);
+
+ if (pwg->width < 21000 && pwg->length < 21000)
+ {
+ source = "photo"; /* Photo size media from photo tray */
+ type = "photographic-glossy"; /* Glossy photo paper */
+ bottom = /* Borderless margins */
+ left =
+ right =
+ top = 0;
+ }
+ else if (pwg->width < 21000)
+ {
+ source = "by-pass-tray"; /* Envelopes from multi-purpose tray */
+ type = "envelope"; /* Envelope */
+ bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
+ left = /* Left/right margins are standard */
+ right = media_lr_margin_supported[1];
+ top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
+ }
+ else if (pwg->width == 21000)
+ {
+ source = "main"; /* A4 from main tray */
+ type = "stationery"; /* Plain paper */
+ bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
+ left = /* Left/right margins are reduced */
+ right = media_lr_margin_supported[0];
+ top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
+ }
+ else
+ {
+ source = "main"; /* A4 from main tray */
+ type = "stationery"; /* Plain paper */
+ bottom = ppm_color > 0 ? media_bottom_margin_supported_color[1] : media_bottom_margin_supported[0];
+ left = /* Left/right margins are standard */
+ right = media_lr_margin_supported[1];
+ top = ppm_color > 0 ? media_top_margin_supported_color[1] : media_top_margin_supported[0];
+ }
+
+ col = create_media_col(ready[i], source, type, pwg->width, pwg->length, bottom, left, right, top);
+ ippSetCollection(attrs, &attr, i, col);
+ ippDelete(col);
+ }
+
+ /* media-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-default", NULL, media[0]);
+
+ /* media-left/right-margin-supported */
+ if (ppm_color > 0)
+ {
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", (int)(sizeof(media_lr_margin_supported_color) / sizeof(media_lr_margin_supported_color[0])), media_lr_margin_supported_color);
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", (int)(sizeof(media_lr_margin_supported_color) / sizeof(media_lr_margin_supported_color[0])), media_lr_margin_supported_color);
+ }
+ else
+ {
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", (int)(sizeof(media_lr_margin_supported) / sizeof(media_lr_margin_supported[0])), media_lr_margin_supported);
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", (int)(sizeof(media_lr_margin_supported) / sizeof(media_lr_margin_supported[0])), media_lr_margin_supported);
+ }
+
+ /* media-ready */
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", num_ready, NULL, ready);
+
+ /* media-supported */
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-supported", num_media, NULL, media);
+
+ /* media-size-supported */
+ attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-size-supported", num_media, NULL);
+ for (i = 0; i < num_media; i ++)
+ {
+ pwg = pwgMediaForPWG(media[i]);
+ col = create_media_size(pwg->width, pwg->length);
+
+ ippSetCollection(attrs, &attr, i, col);
+ ippDelete(col);
+ }
+
+ /* media-source-supported */
+ if (ppm_color > 0)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported_color) / sizeof(media_source_supported_color[0])), NULL, media_source_supported_color);
+ else
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported) / sizeof(media_source_supported[0])), NULL, media_source_supported);
+
+ /* media-top-margin-supported */
+ if (ppm_color > 0)
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", (int)(sizeof(media_top_margin_supported) / sizeof(media_top_margin_supported[0])), media_top_margin_supported);
+ else
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", (int)(sizeof(media_top_margin_supported_color) / sizeof(media_top_margin_supported_color[0])), media_top_margin_supported_color);
+
+ /* media-type-supported */
+ if (ppm_color > 0)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported_color) / sizeof(media_type_supported_color[0])), NULL, media_type_supported_color);
+ else
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported) / sizeof(media_type_supported[0])), NULL, media_type_supported);
+
+ /* orientation-requested-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT);
+
+ /* orientation-requested-supported */
+ if (cupsArrayFind(docformats, (void *)"application/pdf") || cupsArrayFind(docformats, (void *)"image/jpeg"))
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", (int)(sizeof(orientation_requested_supported) / sizeof(orientation_requested_supported[0])), orientation_requested_supported);
+ else
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", IPP_ORIENT_PORTRAIT);
+
+ /* output-bin-default */
+ if (ppm_color > 0)
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-up");
+ else
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down");
+
+ /* output-bin-supported */
+ if (ppm_color > 0)
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-up");
+ else
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down");
+
+ /* overrides-supported */
+ if (cupsArrayFind(docformats, (void *)"application/pdf"))
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "overrides-supported", (int)(sizeof(overrides_supported) / sizeof(overrides_supported[0])), NULL, overrides_supported);
+
+ /* page-ranges-supported */
+ ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", cupsArrayFind(docformats, (void *)"application/pdf") != NULL);
+
+ /* pages-per-minute */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppm);
+
+ /* pages-per-minute-color */
+ if (ppm_color > 0)
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppm_color);
+
+ /* print-color-mode-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, ppm_color > 0 ? "auto" : "monochrome");
+
+ /* print-color-mode-supported */
+ if (ppm_color > 0)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported_color) / sizeof(print_color_mode_supported_color[0])), NULL, print_color_mode_supported_color);
+ else
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported);
+
+ /* print-content-optimize-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto");
+
+ /* print-content-optimize-supported */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto");
+
+ /* print-quality-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL);
+
+ /* print-quality-supported */
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported);
+
+ /* print-rendering-intent-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto");
+
+ /* print-rendering-intent-supported */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto");
+
+ /* printer-device-id */
+ snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;", make, model);
+ ptr = device_id + strlen(device_id);
+ prefix = "CMD:";
+ for (format = (const char *)cupsArrayFirst(docformats); format; format = (const char *)cupsArrayNext(docformats))
+ {
+ if (!strcasecmp(format, "application/pdf"))
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPDF", prefix);
+ else if (!strcasecmp(format, "application/postscript"))
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPS", prefix);
+ else if (!strcasecmp(format, "application/vnd.hp-PCL"))
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPCL", prefix);
+ else if (!strcasecmp(format, "image/jpeg"))
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sJPEG", prefix);
+ else if (!strcasecmp(format, "image/png"))
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPNG", prefix);
+ else if (!strcasecmp(format, "image/pwg-raster"))
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPWG", prefix);
+ else if (!strcasecmp(format, "image/urf"))
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sURF", prefix);
+ else
+ continue;
+
+ ptr += strlen(ptr);
+ prefix = ",";
+ }
+ if (ptr < (device_id + sizeof(device_id) - 1))
+ {
+ *ptr++ = ';';
+ *ptr = '\0';
+ }
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id);
+
+ /* printer-input-tray */
+ if (ppm_color > 0)
+ {
+ attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray_color[0], strlen(printer_input_tray_color[0]));
+ for (i = 1; i < (int)(sizeof(printer_input_tray_color) / sizeof(printer_input_tray_color[0])); i ++)
+ ippSetOctetString(attrs, &attr, i, printer_input_tray_color[i], strlen(printer_input_tray_color[i]));
+ }
+ else
+ {
+ attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray[0], strlen(printer_input_tray[0]));
+ for (i = 1; i < (int)(sizeof(printer_input_tray) / sizeof(printer_input_tray[0])); i ++)
+ ippSetOctetString(attrs, &attr, i, printer_input_tray[i], strlen(printer_input_tray[i]));
+ }
+
+ /* printer-make-and-model */
+ snprintf(make_model, sizeof(make_model), "%s %s", make, model);
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, make_model);
+
+ /* printer-resolution-default */
+ ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, 600, 600);
+
+ /* printer-resolution-supported */
+ ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600);
+
+ /* printer-supply and printer-supply-description */
+ if (ppm_color > 0)
+ {
+ attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], strlen(printer_supply_color[0]));
+ for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++)
+ ippSetOctetString(attrs, &attr, i, printer_supply_color[i], strlen(printer_supply_color[i]));
+
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description_color) / sizeof(printer_supply_description_color[0])), NULL, printer_supply_description_color);
+ }
+ else
+ {
+ attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], strlen(printer_supply[0]));
+ for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++)
+ ippSetOctetString(attrs, &attr, i, printer_supply[i], strlen(printer_supply[i]));
+
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description);
+ }
+
+ /* pwg-raster-document-xxx-supported */
+ if (cupsArrayFind(docformats, (void *)"image/pwg-raster"))
+ {
+ ippAddResolutions(attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", (int)(sizeof(pwg_raster_document_resolution_supported) / sizeof(pwg_raster_document_resolution_supported[0])), IPP_RES_PER_INCH, pwg_raster_document_resolution_supported, pwg_raster_document_resolution_supported);
+
+ if (ppm_color > 0 && duplex)
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "rotated");
+ else if (duplex)
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "normal");
+
+ if (ppm_color > 0)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported_color) / sizeof(pwg_raster_document_type_supported_color[0])), NULL, pwg_raster_document_type_supported_color);
+ else
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported);
+ }
+
+ /* sides-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided");
+
+ /* sides-supported */
+ if (duplex)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", (int)(sizeof(sides_supported) / sizeof(sides_supported[0])), NULL, sides_supported);
+ else
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided");
+
+ /* urf-supported */
+ if (cupsArrayFind(docformats, (void *)"image/urf"))
+ {
+ if (ppm_color > 0)
+ {
+ if (duplex)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_color_duplex) / sizeof(urf_supported_color_duplex[0])), NULL, urf_supported_color_duplex);
+ else
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_color) / sizeof(urf_supported_color[0])), NULL, urf_supported_color);
+ }
+ else if (duplex)
+ {
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported_duplex) / sizeof(urf_supported_duplex[0])), NULL, urf_supported_duplex);
+ }
+ else
+ {
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported) / sizeof(urf_supported[0])), NULL, urf_supported);
+ }
+ }
+
+ return (attrs);
+}
+
+
+#if !CUPS_LITE
+/*
+ * 'load_ppd_attributes()' - Load IPP attributes from a PPD file.
+ */
+
+static ipp_t * /* O - IPP attributes or `NULL` on error */
+load_ppd_attributes(
+ const char *ppdfile, /* I - PPD filename */
+ cups_array_t *docformats) /* I - document-format-supported values */
+{
+ int i, j; /* Looping vars */
+ ipp_t *attrs; /* Attributes */
+ ipp_attribute_t *attr; /* Current attribute */
+ ipp_t *col; /* Current collection value */
+ ppd_file_t *ppd; /* PPD data */
+ ppd_attr_t *ppd_attr; /* PPD attribute */
+ ppd_choice_t *ppd_choice; /* PPD choice */
+ ppd_size_t *ppd_size; /* Default PPD size */
+ pwg_size_t *pwg_size, /* Current PWG size */
+ *default_size = NULL; /* Default PWG size */
+ const char *default_source = NULL, /* Default media source */
+ *default_type = NULL; /* Default media type */
+ pwg_map_t *pwg_map; /* Mapping from PWG to PPD keywords */
+ _ppd_cache_t *pc; /* PPD cache */
+ _pwg_finishings_t *finishings; /* Current finishings value */
+ const char *template; /* Current finishings-template value */
+ int num_margins; /* Number of media-xxx-margin-supported values */
+ int margins[10]; /* media-xxx-margin-supported values */
+ int xres, /* Default horizontal resolution */
+ yres; /* Default vertical resolution */
+ int num_urf; /* Number of urf-supported values */
+ const char *urf[10]; /* urf-supported values */
+ char urf_rs[32]; /* RS value */
+ static const int orientation_requested_supported[4] =
+ { /* orientation-requested-supported values */
+ IPP_ORIENT_PORTRAIT,
+ IPP_ORIENT_LANDSCAPE,
+ IPP_ORIENT_REVERSE_LANDSCAPE,
+ IPP_ORIENT_REVERSE_PORTRAIT
+ };
+ static const char * const overrides_supported[] =
+ { /* overrides-supported */
+ "document-numbers",
+ "media",
+ "media-col",
+ "orientation-requested",
+ "pages"
+ };
+ static const char * const print_color_mode_supported[] =
+ { /* print-color-mode-supported values */
+ "monochrome"
+ };
+ static const char * const print_color_mode_supported_color[] =
+ { /* print-color-mode-supported values */
+ "auto",
+ "color",
+ "monochrome"
+ };
+ static const int print_quality_supported[] =
+ { /* print-quality-supported values */
+ IPP_QUALITY_DRAFT,
+ IPP_QUALITY_NORMAL,
+ IPP_QUALITY_HIGH
+ };
+ static const char * const printer_supply[] =
+ { /* printer-supply values */
+ "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
+ "maxcapacity=100;level=25;colorantname=unknown;",
+ "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
+ "maxcapacity=100;level=75;colorantname=black;"
+ };
+ static const char * const printer_supply_color[] =
+ { /* printer-supply values */
+ "index=1;class=receptacleThatIsFilled;type=wasteInk;unit=percent;"
+ "maxcapacity=100;level=25;colorantname=unknown;",
+ "index=2;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=75;colorantname=black;",
+ "index=3;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=50;colorantname=cyan;",
+ "index=4;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=33;colorantname=magenta;",
+ "index=5;class=supplyThatIsConsumed;type=ink;unit=percent;"
+ "maxcapacity=100;level=67;colorantname=yellow;"
+ };
+ static const char * const printer_supply_description[] =
+ { /* printer-supply-description values */
+ "Toner Waste Tank",
+ "Black Toner"
+ };
+ static const char * const printer_supply_description_color[] =
+ { /* printer-supply-description values */
+ "Ink Waste Tank",
+ "Black Ink",
+ "Cyan Ink",
+ "Magenta Ink",
+ "Yellow Ink"
+ };
+ static const char * const pwg_raster_document_type_supported[] =
+ {
+ "black_1",
+ "sgray_8"
+ };
+ static const char * const pwg_raster_document_type_supported_color[] =
+ {
+ "black_1",
+ "sgray_8",
+ "srgb_8",
+ "srgb_16"
+ };
+ static const char * const sides_supported[] =
+ { /* sides-supported values */
+ "one-sided",
+ "two-sided-long-edge",
+ "two-sided-short-edge"
+ };
+
+
+ /*
+ * Open the PPD file...
+ */
+
+ if ((ppd = ppdOpenFile(ppdfile)) == NULL)
+ {
+ ppd_status_t status; /* Load error */
+
+ status = ppdLastError(&i);
+ _cupsLangPrintf(stderr, _("ippeveprinter: Unable to open \"%s\": %s on line %d."), ppdfile, ppdErrorString(status), i);
+ return (NULL);
+ }
+
+ ppdMarkDefaults(ppd);
+
+ pc = _ppdCacheCreateWithPPD(ppd);
+
+ if ((ppd_size = ppdPageSize(ppd, NULL)) != NULL)
+ {
+ /*
+ * Look up default size...
+ */
+
+ for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+ {
+ if (!strcmp(pwg_size->map.ppd, ppd_size->name))
+ {
+ default_size = pwg_size;
+ break;
+ }
+ }
+ }
+
+ if (!default_size)
+ {
+ /*
+ * Default to A4 or Letter...
+ */
+
+ for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+ {
+ if (!strcmp(pwg_size->map.ppd, "Letter") || !strcmp(pwg_size->map.ppd, "A4"))
+ {
+ default_size = pwg_size;
+ break;
+ }
+ }
+
+ if (!default_size)
+ default_size = pc->sizes; /* Last resort: first size */
+ }
+
+ if ((ppd_choice = ppdFindMarkedChoice(ppd, "InputSlot")) != NULL)
+ default_source = _ppdCacheGetSource(pc, ppd_choice->choice);
+
+ if ((ppd_choice = ppdFindMarkedChoice(ppd, "MediaType")) != NULL)
+ default_source = _ppdCacheGetType(pc, ppd_choice->choice);
+
+ if ((ppd_attr = ppdFindAttr(ppd, "DefaultResolution", NULL)) != NULL)
+ {
+ /*
+ * Use the PPD-defined default resolution...
+ */
+
+ if ((i = sscanf(ppd_attr->value, "%dx%d", &xres, &yres)) == 1)
+ yres = xres;
+ else if (i < 0)
+ xres = yres = 300;
+ }
+ else
+ {
+ /*
+ * Use default of 300dpi...
+ */
+
+ xres = yres = 300;
+ }
+
+ snprintf(urf_rs, sizeof(urf_rs), "RS%d", yres < xres ? yres : xres);
+
+ num_urf = 0;
+ urf[num_urf ++] = "V1.4";
+ urf[num_urf ++] = "CP1";
+ urf[num_urf ++] = urf_rs;
+ urf[num_urf ++] = "W8";
+ if (pc->sides_2sided_long)
+ urf[num_urf ++] = "DM1";
+ if (ppd->color_device)
+ urf[num_urf ++] = "SRGB24";
+
+ /*
+ * PostScript printers accept PDF via one of the CUPS PDF to PostScript
+ * filters, along with PostScript (of course) and JPEG...
+ */
+
+ cupsArrayAdd(docformats, "application/pdf");
+ cupsArrayAdd(docformats, "application/postscript");
+ cupsArrayAdd(docformats, "image/jpeg");
+
+ /*
+ * Create the attributes...
+ */
+
+ attrs = ippNew();
+
+ /* color-supported */
+ ippAddBoolean(attrs, IPP_TAG_PRINTER, "color-supported", (char)ppd->color_device);
+
+ /* copies-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1);
+
+ /* copies-supported */
+ ippAddRange(attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999);
+
+ /* document-password-supported */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 127);
+
+ /* finishing-template-supported */
+ attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template-supported", cupsArrayCount(pc->templates) + 1, NULL, NULL);
+ ippSetString(attrs, &attr, 0, "none");
+ for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates))
+ ippSetString(attrs, &attr, i, template);
+
+ /* finishings-col-database */
+ attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-database", cupsArrayCount(pc->templates) + 1, NULL);
+
+ col = ippNew();
+ ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
+ ippSetCollection(attrs, &attr, 0, col);
+ ippDelete(col);
+
+ for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates))
+ {
+ col = ippNew();
+ ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template);
+ ippSetCollection(attrs, &attr, i, col);
+ ippDelete(col);
+ }
+
+ /* finishings-col-default */
+ col = ippNew();
+ ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
+ ippAddCollection(attrs, IPP_TAG_PRINTER, "finishings-col-default", col);
+ ippDelete(col);
+
+ /* finishings-col-ready */
+ attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "finishings-col-ready", cupsArrayCount(pc->templates) + 1, NULL);
+
+ col = ippNew();
+ ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, "none");
+ ippSetCollection(attrs, &attr, 0, col);
+ ippDelete(col);
+
+ for (i = 1, template = (const char *)cupsArrayFirst(pc->templates); template; i ++, template = (const char *)cupsArrayNext(pc->templates))
+ {
+ col = ippNew();
+ ippAddString(col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template);
+ ippSetCollection(attrs, &attr, i, col);
+ ippDelete(col);
+ }
+
+ /* finishings-col-supported */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishings-col-supported", NULL, "finishing-template");
+
+ /* finishings-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE);
+
+ /* finishings-ready */
+ attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-ready", cupsArrayCount(pc->finishings) + 1, NULL);
+ ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE);
+ for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings))
+ ippSetInteger(attrs, &attr, i, finishings->value);
+
+ /* finishings-supported */
+ attr = ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", cupsArrayCount(pc->finishings) + 1, NULL);
+ ippSetInteger(attrs, &attr, 0, IPP_FINISHINGS_NONE);
+ for (i = 1, finishings = (_pwg_finishings_t *)cupsArrayFirst(pc->finishings); finishings; i ++, finishings = (_pwg_finishings_t *)cupsArrayNext(pc->finishings))
+ ippSetInteger(attrs, &attr, i, finishings->value);
+
+ /* media-bottom-margin-supported */
+ for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ {
+ if (margins[j] == pwg_size->bottom)
+ break;
+ }
+
+ if (j >= num_margins)
+ margins[num_margins ++] = pwg_size->bottom;
+ }
+
+ for (i = 0; i < (num_margins - 1); i ++)
+ {
+ for (j = i + 1; j < num_margins; j ++)
+ {
+ if (margins[i] > margins[j])
+ {
+ int mtemp = margins[i];
+
+ margins[i] = margins[j];
+ margins[j] = mtemp;
+ }
+ }
+ }
+
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", num_margins, margins);
+
+ /* media-col-database */
+ attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database", pc->num_sizes, NULL);
+ for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+ {
+ col = create_media_col(pwg_size->map.pwg, NULL, NULL, pwg_size->width, pwg_size->length, pwg_size->bottom, pwg_size->left, pwg_size->right, pwg_size->top);
+ ippSetCollection(attrs, &attr, i, col);
+ ippDelete(col);
+ }
+
+ /* media-col-default */
+ col = create_media_col(default_size->map.pwg, default_source, default_type, default_size->width, default_size->length, default_size->bottom, default_size->left, default_size->right, default_size->top);
+ ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-default", col);
+ ippDelete(col);
+
+ /* media-col-ready */
+ col = create_media_col(default_size->map.pwg, default_source, default_type, default_size->width, default_size->length, default_size->bottom, default_size->left, default_size->right, default_size->top);
+ ippAddCollection(attrs, IPP_TAG_PRINTER, "media-col-ready", col);
+ ippDelete(col);
+
+ /* media-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-default", NULL, default_size->map.pwg);
+
+ /* media-left-margin-supported */
+ for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ {
+ if (margins[j] == pwg_size->left)
+ break;
+ }
+
+ if (j >= num_margins)
+ margins[num_margins ++] = pwg_size->left;
+ }
+
+ for (i = 0; i < (num_margins - 1); i ++)
+ {
+ for (j = i + 1; j < num_margins; j ++)
+ {
+ if (margins[i] > margins[j])
+ {
+ int mtemp = margins[i];
+
+ margins[i] = margins[j];
+ margins[j] = mtemp;
+ }
+ }
+ }
+
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", num_margins, margins);
+
+ /* media-ready */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, default_size->map.pwg);
+
+ /* media-right-margin-supported */
+ for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ {
+ if (margins[j] == pwg_size->right)
+ break;
+ }
+
+ if (j >= num_margins)
+ margins[num_margins ++] = pwg_size->right;
+ }
+
+ for (i = 0; i < (num_margins - 1); i ++)
+ {
+ for (j = i + 1; j < num_margins; j ++)
+ {
+ if (margins[i] > margins[j])
+ {
+ int mtemp = margins[i];
+
+ margins[i] = margins[j];
+ margins[j] = mtemp;
+ }
+ }
+ }
+
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", num_margins, margins);
+
+ /* media-supported */
+ attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-supported", pc->num_sizes, NULL, NULL);
+ for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+ ippSetString(attrs, &attr, i, pwg_size->map.pwg);
+
+ /* media-size-supported */
+ attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-size-supported", pc->num_sizes, NULL);
+ for (i = 0, pwg_size = pc->sizes; i < pc->num_sizes; i ++, pwg_size ++)
+ {
+ col = create_media_size(pwg_size->width, pwg_size->length);
+ ippSetCollection(attrs, &attr, i, col);
+ ippDelete(col);
+ }
+
+ /* media-source-supported */
+ if (pc->num_sources > 0)
+ {
+ attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source-supported", pc->num_sources, NULL, NULL);
+ for (i = 0, pwg_map = pc->sources; i < pc->num_sources; i ++, pwg_map ++)
+ ippSetString(attrs, &attr, i, pwg_map->pwg);
+ }
+ else
+ {
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", NULL, "auto");
+ }
+
+ /* media-top-margin-supported */
+ for (i = 0, num_margins = 0, pwg_size = pc->sizes; i < pc->num_sizes && num_margins < (int)(sizeof(margins) / sizeof(margins[0])); i ++, pwg_size ++)
+ {
+ for (j = 0; j < num_margins; j ++)
+ {
+ if (margins[j] == pwg_size->top)
+ break;
+ }
+
+ if (j >= num_margins)
+ margins[num_margins ++] = pwg_size->top;
+ }
+
+ for (i = 0; i < (num_margins - 1); i ++)
+ {
+ for (j = i + 1; j < num_margins; j ++)
+ {
+ if (margins[i] > margins[j])
+ {
+ int mtemp = margins[i];
+
+ margins[i] = margins[j];
+ margins[j] = mtemp;
+ }
+ }
+ }
+
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", num_margins, margins);
+
+ /* media-type-supported */
+ if (pc->num_types > 0)
+ {
+ attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type-supported", pc->num_types, NULL, NULL);
+ for (i = 0, pwg_map = pc->types; i < pc->num_types; i ++, pwg_map ++)
+ ippSetString(attrs, &attr, i, pwg_map->pwg);
+ }
+ else
+ {
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", NULL, "auto");
+ }
+
+ /* orientation-requested-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-default", IPP_ORIENT_PORTRAIT);
+
+ /* orientation-requested-supported */
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", (int)(sizeof(orientation_requested_supported) / sizeof(orientation_requested_supported[0])), orientation_requested_supported);
+
+ /* output-bin-default */
+ if (pc->num_bins > 0)
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-default", NULL, pc->bins->pwg);
+ else
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down");
+
+ /* output-bin-supported */
+ if (pc->num_bins > 0)
+ {
+ attr = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "output-bin-supported", pc->num_bins, NULL, NULL);
+ for (i = 0, pwg_map = pc->bins; i < pc->num_bins; i ++, pwg_map ++)
+ ippSetString(attrs, &attr, i, pwg_map->pwg);
+ }
+ else
+ {
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down");
+ }
+
+ /* overrides-supported */
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "overrides-supported", (int)(sizeof(overrides_supported) / sizeof(overrides_supported[0])), NULL, overrides_supported);
+
+ /* page-ranges-supported */
+ ippAddBoolean(attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1);
+
+ /* pages-per-minute */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute", ppd->throughput);
+
+ /* pages-per-minute-color */
+ if (ppd->color_device)
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "pages-per-minute-color", ppd->throughput);
+
+ /* print-color-mode-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, ppd->color_device ? "auto" : "monochrome");
+
+ /* print-color-mode-supported */
+ if (ppd->color_device)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported_color) / sizeof(print_color_mode_supported_color[0])), NULL, print_color_mode_supported_color);
+ else
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported);
+
+ /* print-content-optimize-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto");
+
+ /* print-content-optimize-supported */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto");
+
+ /* print-quality-default */
+ ippAddInteger(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL);
+
+ /* print-quality-supported */
+ ippAddIntegers(attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported);
+
+ /* print-rendering-intent-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto");
+
+ /* print-rendering-intent-supported */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto");
+
+ /* printer-device-id */
+ if ((ppd_attr = ppdFindAttr(ppd, "1284DeviceId", NULL)) != NULL)
+ {
+ /*
+ * Use the device ID string from the PPD...
+ */
+
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, ppd_attr->value);
+ }
+ else
+ {
+ /*
+ * Synthesize a device ID string...
+ */
+
+ char device_id[1024]; /* Device ID string */
+
+ snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;CMD:PS;", ppd->manufacturer, ppd->modelname);
+
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id);
+ }
+
+ /* printer-input-tray */
+ if (pc->num_sources > 0)
+ {
+ for (i = 0, attr = NULL; i < pc->num_sources; i ++)
+ {
+ char input_tray[1024]; /* printer-input-tray value */
+
+ if (!strcmp(pc->sources[i].pwg, "manual") || strstr(pc->sources[i].pwg, "-man") != NULL)
+ snprintf(input_tray, sizeof(input_tray), "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=%s", pc->sources[i].pwg);
+ else
+ snprintf(input_tray, sizeof(input_tray), "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=125;status=0;name=%s", pc->sources[i].pwg);
+
+ if (attr)
+ ippSetOctetString(attrs, &attr, i, input_tray, (int)strlen(input_tray));
+ else
+ attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", input_tray, (int)strlen(input_tray));
+ }
+ }
+ else
+ {
+ static const char *printer_input_tray = "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto";
+
+ ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray, (int)strlen(printer_input_tray));
+ }
+
+ /* printer-make-and-model */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, ppd->nickname);
+
+ /* printer-resolution-default */
+ ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, xres, yres);
+
+ /* printer-resolution-supported */
+ ippAddResolution(attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, xres, yres);
+
+ /* printer-supply and printer-supply-description */
+ if (ppd->color_device)
+ {
+ attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply_color[0], strlen(printer_supply_color[0]));
+ for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++)
+ ippSetOctetString(attrs, &attr, i, printer_supply_color[i], strlen(printer_supply_color[i]));
+
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description_color) / sizeof(printer_supply_description_color[0])), NULL, printer_supply_description_color);
+ }
+ else
+ {
+ attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], strlen(printer_supply[0]));
+ for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++)
+ ippSetOctetString(attrs, &attr, i, printer_supply[i], strlen(printer_supply[i]));
+
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description);
+ }
+
+ /* pwg-raster-document-xxx-supported */
+ if (cupsArrayFind(docformats, (void *)"image/pwg-raster"))
+ {
+ ippAddResolution(attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, xres, yres);
+
+ if (pc->sides_2sided_long)
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-sheet-back", NULL, "normal");
+
+ if (ppd->color_device)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported_color) / sizeof(pwg_raster_document_type_supported_color[0])), NULL, pwg_raster_document_type_supported_color);
+ else
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported);
+ }
+
+ /* sides-default */
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided");
+
+ /* sides-supported */
+ if (pc->sides_2sided_long)
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", (int)(sizeof(sides_supported) / sizeof(sides_supported[0])), NULL, sides_supported);
+ else
+ ippAddString(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", NULL, "one-sided");
+
+ /* urf-supported */
+ if (cupsArrayFind(docformats, (void *)"image/urf"))
+ ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", num_urf, NULL, urf);
+
+ /*
+ * Free the PPD file and return the attributes...
+ */
+
+ _ppdCacheDestroy(pc);
+
+ ppdClose(ppd);
+
+ return (attrs);
+}
+#endif /* !CUPS_LITE */
+
+
+/*
+ * 'parse_options()' - Parse URL options into CUPS options.
+ *
+ * The client->options string is destroyed by this function.
+ */
+
+static int /* O - Number of options */
+parse_options(ippeve_client_t *client, /* I - Client */
+ cups_option_t **options)/* O - Options */
+{
+ char *name, /* Name */
+ *value, /* Value */
+ *next; /* Next name=value pair */
+ int num_options = 0; /* Number of options */
+
+
+ *options = NULL;
+
+ for (name = client->options; name && *name; name = next)
+ {
+ if ((value = strchr(name, '=')) == NULL)
+ break;
+
+ *value++ = '\0';
+ if ((next = strchr(value, '&')) != NULL)
+ *next++ = '\0';
+
+ num_options = cupsAddOption(name, value, num_options, options);
+ }
+
+ return (num_options);
+}
+
+
+/*
+ * 'process_attr_message()' - Process an ATTR: message from a command.
+ */
+
+static void
+process_attr_message(
+ ippeve_job_t *job, /* I - Job */
+ char *message) /* I - Message */
+{
+ int i, /* Looping var */
+ num_options = 0; /* Number of name=value pairs */
+ cups_option_t *options = NULL, /* name=value pairs from message */
+ *option; /* Current option */
+ ipp_attribute_t *attr; /* Current attribute */
+
+
+ /*
+ * Grab attributes from the message line...
+ */
+
+ num_options = cupsParseOptions(message + 5, num_options, &options);
+
+ /*
+ * Loop through the options and record them in the printer or job objects...
+ */
+
+ for (i = num_options, option = options; i > 0; i --, option ++)
+ {
+ if (!strcmp(option->name, "job-impressions"))
+ {
+ /*
+ * Update job-impressions attribute...
+ */
+
+ job->impressions = atoi(option->value);
+ }
+ else if (!strcmp(option->name, "job-impressions-completed"))
+ {
+ /*
+ * Update job-impressions-completed attribute...
+ */
+
+ job->impcompleted = atoi(option->value);
+ }
+ else if (!strncmp(option->name, "marker-", 7) || !strcmp(option->name, "printer-alert") || !strcmp(option->name, "printer-alert-description") || !strcmp(option->name, "printer-supply") || !strcmp(option->name, "printer-supply-description"))
+ {
+ /*
+ * Update Printer Status attribute...
+ */
+
+ _cupsRWLockWrite(&job->printer->rwlock);
+
+ if ((attr = ippFindAttribute(job->printer->attrs, option->name, IPP_TAG_ZERO)) != NULL)
+ ippDeleteAttribute(job->printer->attrs, attr);
+
+ cupsEncodeOption(job->printer->attrs, IPP_TAG_PRINTER, option->name, option->value);
+
+ _cupsRWUnlock(&job->printer->rwlock);
+ }
+ else
+ {
+ /*
+ * Something else that isn't currently supported...
+ */
+
+ fprintf(stderr, "[Job %d] Ignoring update of attribute \"%s\" with value \"%s\".\n", job->id, option->name, option->value);
+ }
+ }
+
+ cupsFreeOptions(num_options, options);
+}
+
+
+/*
+ * 'process_client()' - Process client requests on a thread.
+ */
+
+static void * /* O - Exit status */
+process_client(ippeve_client_t *client) /* I - Client */
+{
+ /*
+ * Loop until we are out of requests or timeout (30 seconds)...
+ */
+
+#ifdef HAVE_SSL
+ int first_time = 1; /* First time request? */
+#endif /* HAVE_SSL */
+
+ while (httpWait(client->http, 30000))
+ {
+#ifdef HAVE_SSL
+ if (first_time)
+ {
+ /*
+ * See if we need to negotiate a TLS connection...
+ */
+
+ char buf[1]; /* First byte from client */
+
+ if (recv(httpGetFd(client->http), buf, 1, MSG_PEEK) == 1 && (!buf[0] || !strchr("DGHOPT", buf[0])))
+ {
+ fprintf(stderr, "%s Starting HTTPS session.\n", client->hostname);
+
+ if (httpEncryption(client->http, HTTP_ENCRYPTION_ALWAYS))
+ {
+ fprintf(stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsLastErrorString());
+ break;
+ }
+
+ fprintf(stderr, "%s Connection now encrypted.\n", client->hostname);
+ }
+
+ first_time = 0;
+ }
+#endif /* HAVE_SSL */
+
+ if (!process_http(client))
+ break;
+ }
+
+ /*
+ * Close the conection to the client and return...
+ */
+
+ delete_client(client);
+
+ return (NULL);
+}
+
+
+/*
+ * 'process_http()' - Process a HTTP request.
+ */
+
+int /* O - 1 on success, 0 on failure */
+process_http(ippeve_client_t *client) /* I - Client connection */
+{
+ char uri[1024]; /* URI */
+ http_state_t http_state; /* HTTP state */
+ http_status_t http_status; /* HTTP status */
+ ipp_state_t ipp_state; /* State of IPP transfer */
+ char scheme[32], /* Method/scheme */
+ userpass[128], /* Username:password */
+ hostname[HTTP_MAX_HOST];
+ /* Hostname */
+ int port; /* Port number */
+ static const char * const http_states[] =
+ { /* Strings for logging HTTP method */
+ "WAITING",
+ "OPTIONS",
+ "GET",
+ "GET_SEND",
+ "HEAD",
+ "POST",
+ "POST_RECV",
+ "POST_SEND",
+ "PUT",
+ "PUT_RECV",
+ "DELETE",
+ "TRACE",
+ "CONNECT",
+ "STATUS",
+ "UNKNOWN_METHOD",
+ "UNKNOWN_VERSION"
+ };
+
+
+ /*
+ * Clear state variables...
+ */
+
+ ippDelete(client->request);
+ ippDelete(client->response);
+
+ client->request = NULL;
+ client->response = NULL;
+ client->operation = HTTP_STATE_WAITING;
+
+ /*
+ * Read a request from the connection...
+ */
+
+ while ((http_state = httpReadRequest(client->http, uri,
+ sizeof(uri))) == HTTP_STATE_WAITING)
+ usleep(1);
+
+ /*
+ * Parse the request line...
+ */
+
+ if (http_state == HTTP_STATE_ERROR)
+ {
+ if (httpError(client->http) == EPIPE)
+ fprintf(stderr, "%s Client closed connection.\n", client->hostname);
+ else
+ fprintf(stderr, "%s Bad request line (%s).\n", client->hostname, strerror(httpError(client->http)));
+
+ return (0);
+ }
+ else if (http_state == HTTP_STATE_UNKNOWN_METHOD)
+ {
+ fprintf(stderr, "%s Bad/unknown operation.\n", client->hostname);
+ respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+ return (0);
+ }
+ else if (http_state == HTTP_STATE_UNKNOWN_VERSION)
+ {
+ fprintf(stderr, "%s Bad HTTP version.\n", client->hostname);
+ respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+ return (0);
+ }
+
+ fprintf(stderr, "%s %s %s\n", client->hostname, http_states[http_state], uri);
+
+ /*
+ * Separate the URI into its components...
+ */
+
+ if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme),
+ userpass, sizeof(userpass),
+ hostname, sizeof(hostname), &port,
+ client->uri, sizeof(client->uri)) < HTTP_URI_STATUS_OK &&
+ (http_state != HTTP_STATE_OPTIONS || strcmp(uri, "*")))
+ {
+ fprintf(stderr, "%s Bad URI \"%s\".\n", client->hostname, uri);
+ respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+ return (0);
+ }
+
+ if ((client->options = strchr(client->uri, '?')) != NULL)
+ *(client->options)++ = '\0';
+
+ /*
+ * Process the request...
+ */
+
+ client->start = time(NULL);
+ client->operation = httpGetState(client->http);
+
+ /*
+ * Parse incoming parameters until the status changes...
+ */
+
+ while ((http_status = httpUpdate(client->http)) == HTTP_STATUS_CONTINUE);
+
+ if (http_status != HTTP_STATUS_OK)
+ {
+ respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+ return (0);
+ }
+
+ if (!httpGetField(client->http, HTTP_FIELD_HOST)[0] &&
+ httpGetVersion(client->http) >= HTTP_VERSION_1_1)
+ {
+ /*
+ * HTTP/1.1 and higher require the "Host:" field...
+ */
+
+ respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+ return (0);
+ }
+
+ /*
+ * Handle HTTP Upgrade...
+ */
+
+ if (!strcasecmp(httpGetField(client->http, HTTP_FIELD_CONNECTION),
+ "Upgrade"))
+ {
+#ifdef HAVE_SSL
+ if (strstr(httpGetField(client->http, HTTP_FIELD_UPGRADE), "TLS/") != NULL && !httpIsEncrypted(client->http))
+ {
+ if (!respond_http(client, HTTP_STATUS_SWITCHING_PROTOCOLS, NULL, NULL, 0))
+ return (0);
+
+ fprintf(stderr, "%s Upgrading to encrypted connection.\n", client->hostname);
+
+ if (httpEncryption(client->http, HTTP_ENCRYPTION_REQUIRED))
+ {
+ fprintf(stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsLastErrorString());
+ return (0);
+ }
+
+ fprintf(stderr, "%s Connection now encrypted.\n", client->hostname);
+ }
+ else
+#endif /* HAVE_SSL */
+
+ if (!respond_http(client, HTTP_STATUS_NOT_IMPLEMENTED, NULL, NULL, 0))
+ return (0);
+ }
+
+ /*
+ * Handle HTTP Expect...
+ */
+
+ if (httpGetExpect(client->http) &&
+ (client->operation == HTTP_STATE_POST ||
+ client->operation == HTTP_STATE_PUT))
+ {
+ if (httpGetExpect(client->http) == HTTP_STATUS_CONTINUE)
+ {
+ /*
+ * Send 100-continue header...
+ */
+
+ if (!respond_http(client, HTTP_STATUS_CONTINUE, NULL, NULL, 0))
+ return (0);
+ }
+ else
+ {
+ /*
+ * Send 417-expectation-failed header...
+ */
+
+ if (!respond_http(client, HTTP_STATUS_EXPECTATION_FAILED, NULL, NULL, 0))
+ return (0);
+ }
+ }
+
+ /*
+ * Handle new transfers...
+ */
+
+ switch (client->operation)
+ {
+ case HTTP_STATE_OPTIONS :
+ /*
+ * Do OPTIONS command...
+ */
+
+ return (respond_http(client, HTTP_STATUS_OK, NULL, NULL, 0));
+
+ case HTTP_STATE_HEAD :
+ if (!strcmp(client->uri, "/icon.png"))
+ return (respond_http(client, HTTP_STATUS_OK, NULL, "image/png", 0));
+ else if (!strcmp(client->uri, "/") || !strcmp(client->uri, "/media") || !strcmp(client->uri, "/supplies"))
+ return (respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0));
+ else
+ return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
+
+ case HTTP_STATE_GET :
+ if (!strcmp(client->uri, "/icon.png"))
+ {
+ /*
+ * Send PNG icon file.
+ */
+
+ if (client->printer->icon)
+ {
+ int fd; /* Icon file */
+ struct stat fileinfo; /* Icon file information */
+ char buffer[4096]; /* Copy buffer */
+ ssize_t bytes; /* Bytes */
+
+ fprintf(stderr, "Icon file is \"%s\".\n", client->printer->icon);
+
+ if (!stat(client->printer->icon, &fileinfo) && (fd = open(client->printer->icon, O_RDONLY)) >= 0)
+ {
+ if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size))
+ {
+ close(fd);
+ return (0);
+ }
+
+ while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
+ httpWrite2(client->http, buffer, (size_t)bytes);
+
+ httpFlushWrite(client->http);
+
+ close(fd);
+ }
+ else
+ return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
+ }
+ else
+ {
+ fputs("Icon file is internal printer.png.\n", stderr);
+
+ if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", sizeof(printer_png)))
+ return (0);
+
+ httpWrite2(client->http, (const char *)printer_png, sizeof(printer_png));
+ httpFlushWrite(client->http);
+ }
+ }
+ else if (!strcmp(client->uri, "/"))
+ {
+ /*
+ * Show web status page...
+ */
+
+ return (show_status(client));
+ }
+ else if (!strcmp(client->uri, "/media"))
+ {
+ /*
+ * Show web media page...
+ */
+
+ return (show_media(client));
+ }
+ else if (!strcmp(client->uri, "/supplies"))
+ {
+ /*
+ * Show web supplies page...
+ */
+
+ return (show_supplies(client));
+ }
+ else
+ return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
+ break;
+
+ case HTTP_STATE_POST :
+ if (strcmp(httpGetField(client->http, HTTP_FIELD_CONTENT_TYPE),
+ "application/ipp"))
+ {
+ /*
+ * Not an IPP request...
+ */
+
+ return (respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0));
+ }
+
+ /*
+ * Read the IPP request...
+ */
+
+ client->request = ippNew();
+
+ while ((ipp_state = ippRead(client->http,
+ client->request)) != IPP_STATE_DATA)
+ {
+ if (ipp_state == IPP_STATE_ERROR)
+ {
+ fprintf(stderr, "%s IPP read error (%s).\n", client->hostname, cupsLastErrorString());
+ respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+ return (0);
+ }
+ }
+
+ /*
+ * Now that we have the IPP request, process the request...
+ */
+
+ return (process_ipp(client));
+
+ default :
+ break; /* Anti-compiler-warning-code */
+ }
+
+ return (1);
+}
+
+
+/*
+ * 'process_ipp()' - Process an IPP request.
+ */
+
+static int /* O - 1 on success, 0 on error */
+process_ipp(ippeve_client_t *client) /* I - Client */
+{
+ ipp_tag_t group; /* Current group tag */
+ ipp_attribute_t *attr; /* Current attribute */
+ ipp_attribute_t *charset; /* Character set attribute */
+ ipp_attribute_t *language; /* Language attribute */
+ ipp_attribute_t *uri; /* Printer URI attribute */
+ int major, minor; /* Version number */
+ const char *name; /* Name of attribute */
+
+
+ debug_attributes("Request", client->request, 1);
+
+ /*
+ * First build an empty response message for this request...
+ */
+
+ client->operation_id = ippGetOperation(client->request);
+ client->response = ippNewResponse(client->request);
+
+ /*
+ * Then validate the request header and required attributes...
+ */
+
+ major = ippGetVersion(client->request, &minor);
+
+ if (major < 1 || major > 2)
+ {
+ /*
+ * Return an error, since we only support IPP 1.x and 2.x.
+ */
+
+ respond_ipp(client, IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED, "Bad request version number %d.%d.", major, minor);
+ }
+ else if ((major * 10 + minor) > MaxVersion)
+ {
+ if (httpGetState(client->http) != HTTP_STATE_POST_SEND)
+ httpFlush(client->http); /* Flush trailing (junk) data */
+
+ respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+ return (0);
+ }
+ else if (ippGetRequestId(client->request) <= 0)
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad request-id %d.", ippGetRequestId(client->request));
+ }
+ else if (!ippFirstAttribute(client->request))
+ {
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "No attributes in request.");
+ }
+ else
+ {
+ /*
+ * Make sure that the attributes are provided in the correct order and
+ * don't repeat groups...
+ */
+
+ for (attr = ippFirstAttribute(client->request),
+ group = ippGetGroupTag(attr);
+ attr;
+ attr = ippNextAttribute(client->request))
+ {
+ if (ippGetGroupTag(attr) < group && ippGetGroupTag(attr) != IPP_TAG_ZERO)
+ {
+ /*
+ * Out of order; return an error...
+ */
+
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+ "Attribute groups are out of order (%x < %x).",
+ ippGetGroupTag(attr), group);
+ break;
+ }
+ else
+ group = ippGetGroupTag(attr);
+ }
+
+ if (!attr)
+ {
+ /*
+ * Then make sure that the first three attributes are:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri/job-uri
+ */
+
+ attr = ippFirstAttribute(client->request);
+ name = ippGetName(attr);
+ if (attr && name && !strcmp(name, "attributes-charset") &&
+ ippGetValueTag(attr) == IPP_TAG_CHARSET)
+ charset = attr;
+ else
+ charset = NULL;
+
+ attr = ippNextAttribute(client->request);
+ name = ippGetName(attr);
+
+ if (attr && name && !strcmp(name, "attributes-natural-language") &&
+ ippGetValueTag(attr) == IPP_TAG_LANGUAGE)
+ language = attr;
+ else
+ language = NULL;
+
+ if ((attr = ippFindAttribute(client->request, "printer-uri",
+ IPP_TAG_URI)) != NULL)
+ uri = attr;
+ else if ((attr = ippFindAttribute(client->request, "job-uri",
+ IPP_TAG_URI)) != NULL)
+ uri = attr;
+ else
+ uri = NULL;
+
+ if (charset &&
+ strcasecmp(ippGetString(charset, 0, NULL), "us-ascii") &&
+ strcasecmp(ippGetString(charset, 0, NULL), "utf-8"))
+ {
+ /*
+ * Bad character set...
+ */
+
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+ "Unsupported character set \"%s\".",
+ ippGetString(charset, 0, NULL));
+ }
+ else if (!charset || !language || !uri)
+ {
+ /*
+ * Return an error, since attributes-charset,
+ * attributes-natural-language, and printer-uri/job-uri are required
+ * for all operations.
+ */
+
+ respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+ "Missing required attributes.");
+ }
+ else
+ {
+ char scheme[32], /* URI scheme */
+ userpass[32], /* Username/password in URI */
+ host[256], /* Host name in URI */
+ resource[256]; /* Resource path in URI */
+ int port; /* Port number in URI */
+
+ name = ippGetName(uri);
+
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL),
+ scheme, sizeof(scheme),
+ userpass, sizeof(userpass),
+ host, sizeof(host), &port,
+ resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+ respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES,
+ "Bad %s value '%s'.", name, ippGetString(uri, 0, NULL));
+ else if ((!strcmp(name, "job-uri") &&
+ strncmp(resource, "/ipp/print/", 11)) ||
+ (!strcmp(name, "printer-uri") &&
+ strcmp(resource, "/ipp/print")))
+ respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "%s %s not found.",
+ name, ippGetString(uri, 0, NULL));
+ else
+ {
+ /*
+ * Try processing the operation...
+ */
+
+ switch (ippGetOperation(client->request))
+ {
+ case IPP_OP_PRINT_JOB :
+ ipp_print_job(client);
+ break;
+
+ case IPP_OP_PRINT_URI :
+ ipp_print_uri(client);
+ break;
+
+ case IPP_OP_VALIDATE_JOB :
+ ipp_validate_job(client);
+ break;
+
+ case IPP_OP_CREATE_JOB :
+ ipp_create_job(client);
+ break;
+
+ case IPP_OP_SEND_DOCUMENT :
+ ipp_send_document(client);
+ break;
+
+ case IPP_OP_SEND_URI :
+ ipp_send_uri(client);
+ break;
+
+ case IPP_OP_CANCEL_JOB :
+ ipp_cancel_job(client);
+ break;
+
+ case IPP_OP_GET_JOB_ATTRIBUTES :
+ ipp_get_job_attributes(client);
+ break;
+
+ case IPP_OP_GET_JOBS :
+ ipp_get_jobs(client);
+ break;
+
+ case IPP_OP_GET_PRINTER_ATTRIBUTES :
+ ipp_get_printer_attributes(client);
+ break;
+
+ case IPP_OP_CLOSE_JOB :
+ ipp_close_job(client);
+ break;
+
+ case IPP_OP_IDENTIFY_PRINTER :
+ ipp_identify_printer(client);
+ break;
+
+ default :
+ respond_ipp(client, IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED,
+ "Operation not supported.");
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Send the HTTP header and return...
+ */
+
+ if (httpGetState(client->http) != HTTP_STATE_POST_SEND)
+ httpFlush(client->http); /* Flush trailing (junk) data */
+
+ return (respond_http(client, HTTP_STATUS_OK, NULL, "application/ipp",
+ ippLength(client->response)));
+}
+
+
+/*
+ * 'process_job()' - Process a print job.
+ */
+
+static void * /* O - Thread exit status */
+process_job(ippeve_job_t *job) /* I - Job */
+{
+ job->state = IPP_JSTATE_PROCESSING;
+ job->printer->state = IPP_PSTATE_PROCESSING;
+ job->processing = time(NULL);
+
+ while (job->printer->state_reasons & IPPEVE_PREASON_MEDIA_EMPTY)
+ {
+ job->printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED;
+
+ sleep(1);
+ }
+
+ job->printer->state_reasons &= (ippeve_preason_t)~IPPEVE_PREASON_MEDIA_NEEDED;
+
+ if (job->printer->command)
+ {
+ /*
+ * Execute a command with the job spool file and wait for it to complete...
+ */
+
+ int pid, /* Process ID */
+ status; /* Exit status */
+ struct timeval start, /* Start time */
+ end; /* End time */
+ char *myargv[3], /* Command-line arguments */
+ *myenvp[400]; /* Environment variables */
+ int myenvc; /* Number of environment variables */
+ ipp_attribute_t *attr; /* Job attribute */
+ char val[1280], /* IPP_NAME=value */
+ *valptr; /* Pointer into string */
+#ifndef _WIN32
+ int mystdout = -1; /* File for stdout */
+ int mypipe[2]; /* Pipe for stderr */
+ char line[2048], /* Line from stderr */
+ *ptr, /* Pointer into line */
+ *endptr; /* End of line */
+ ssize_t bytes; /* Bytes read */
+#endif /* !_WIN32 */
+
+ fprintf(stderr, "[Job %d] Running command \"%s %s\".\n", job->id, job->printer->command, job->filename);
+ gettimeofday(&start, NULL);
+
+ /*
+ * Setup the command-line arguments...
+ */
+
+ myargv[0] = job->printer->command;
+ myargv[1] = job->filename;
+ myargv[2] = NULL;
+
+ /*
+ * Copy the current environment, then add environment variables for every
+ * Job attribute and Printer -default attributes...
+ */
+
+ for (myenvc = 0; environ[myenvc] && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); myenvc ++)
+ myenvp[myenvc] = strdup(environ[myenvc]);
+
+ if (myenvc > (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 32))
+ {
+ fprintf(stderr, "[Job %d] Too many environment variables to process job.\n", job->id);
+ job->state = IPP_JSTATE_ABORTED;
+ goto error;
+ }
+
+ if (asprintf(myenvp + myenvc, "CONTENT_TYPE=%s", job->format) > 0)
+ myenvc ++;
+
+ if (job->printer->device_uri && asprintf(myenvp + myenvc, "DEVICE_URI=%s", job->printer->device_uri) > 0)
+ myenvc ++;
+
+#if !CUPS_LITE
+ if (job->printer->ppdfile && asprintf(myenvp + myenvc, "PPD=%s", job->printer->ppdfile) > 0)
+ myenvc ++;
+#endif /* !CUPS_LITE */
+
+ for (attr = ippFirstAttribute(job->printer->attrs); attr && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); attr = ippNextAttribute(job->printer->attrs))
+ {
+ /*
+ * Convert "attribute-name-default" to "IPP_ATTRIBUTE_NAME_DEFAULT=" and
+ * then add the value(s) from the attribute.
+ */
+
+ const char *name = ippGetName(attr),
+ /* Attribute name */
+ *suffix = strstr(name, "-default");
+ /* Suffix on attribute name */
+
+ if (!suffix || suffix[8])
+ continue;
+
+ valptr = val;
+ *valptr++ = 'I';
+ *valptr++ = 'P';
+ *valptr++ = 'P';
+ *valptr++ = '_';
+ while (*name && valptr < (val + sizeof(val) - 2))
+ {
+ if (*name == '-')
+ *valptr++ = '_';
+ else
+ *valptr++ = (char)toupper(*name & 255);
+
+ name ++;
+ }
+ *valptr++ = '=';
+ ippAttributeString(attr, valptr, sizeof(val) - (size_t)(valptr - val));
+
+ myenvp[myenvc++] = strdup(val);
+ }
+
+ for (attr = ippFirstAttribute(job->attrs); attr && myenvc < (int)(sizeof(myenvp) / sizeof(myenvp[0]) - 1); attr = ippNextAttribute(job->attrs))
+ {
+ /*
+ * Convert "attribute-name" to "IPP_ATTRIBUTE_NAME=" and then add the
+ * value(s) from the attribute.
+ */
+
+ const char *name = ippGetName(attr);
+ /* Attribute name */
+
+ if (!name)
+ continue;
+
+ valptr = val;
+ *valptr++ = 'I';
+ *valptr++ = 'P';
+ *valptr++ = 'P';
+ *valptr++ = '_';
+ while (*name && valptr < (val + sizeof(val) - 2))
+ {
+ if (*name == '-')
+ *valptr++ = '_';
+ else
+ *valptr++ = (char)toupper(*name & 255);
+
+ name ++;
+ }
+ *valptr++ = '=';
+ ippAttributeString(attr, valptr, sizeof(val) - (size_t)(valptr - val));
+
+ myenvp[myenvc++] = strdup(val);
+ }
+
+ if (attr)
+ {
+ fprintf(stderr, "[Job %d] Too many environment variables to process job.\n", job->id);
+ job->state = IPP_JSTATE_ABORTED;
+ goto error;
+ }
+
+ myenvp[myenvc] = NULL;
+
+ /*
+ * Now run the program...
+ */
+
+#ifdef _WIN32
+ status = _spawnvpe(_P_WAIT, job->printer->command, myargv, myenvp);
+
+#else
+ if (job->printer->device_uri)
+ {
+ char scheme[32], /* URI scheme */
+ userpass[256], /* username:password (unused) */
+ host[256], /* Hostname or IP address */
+ resource[256]; /* Resource path */
+ int port; /* Port number */
+
+
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, job->printer->device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+ {
+ fprintf(stderr, "[Job %d] Bad device URI \"%s\".\n", job->id, job->printer->device_uri);
+ }
+ else if (!strcmp(scheme, "file"))
+ {
+ struct stat fileinfo; /* See if this is a file or directory... */
+
+ if (stat(resource, &fileinfo))
+ {
+ if (errno == ENOENT)
+ {
+ if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC, 0666)) >= 0)
+ fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource);
+ else
+ fprintf(stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, resource, strerror(errno));
+ }
+ else
+ fprintf(stderr, "[Job %d] Unable to access \"%s\": %s\n", job->id, resource, strerror(errno));
+ }
+ else if (S_ISDIR(fileinfo.st_mode))
+ {
+ if ((mystdout = create_job_file(job, line, sizeof(line), resource, "prn")) >= 0)
+ fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, line);
+ else
+ fprintf(stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, line, strerror(errno));
+ }
+ else if (!S_ISREG(fileinfo.st_mode))
+ {
+ if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC, 0666)) >= 0)
+ fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource);
+ else
+ fprintf(stderr, "[Job %d] Unable to create \"%s\": %s\n", job->id, resource, strerror(errno));
+ }
+ else if ((mystdout = open(resource, O_WRONLY)) >= 0)
+ fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, resource);
+ else
+ fprintf(stderr, "[Job %d] Unable to open \"%s\": %s\n", job->id, resource, strerror(errno));
+ }
+ else if (!strcmp(scheme, "socket"))
+ {
+ http_addrlist_t *addrlist; /* List of addresses */
+ char service[32]; /* Service number */
+
+ snprintf(service, sizeof(service), "%d", port);
+
+ if ((addrlist = httpAddrGetList(host, AF_UNSPEC, service)) == NULL)
+ fprintf(stderr, "[Job %d] Unable to find \"%s\": %s\n", job->id, host, cupsLastErrorString());
+ else if (!httpAddrConnect2(addrlist, &mystdout, 30000, &(job->cancel)))
+ fprintf(stderr, "[Job %d] Unable to connect to \"%s\": %s\n", job->id, host, cupsLastErrorString());
+
+ httpAddrFreeList(addrlist);
+ }
+ else
+ {
+ fprintf(stderr, "[Job %d] Unsupported device URI scheme \"%s\".\n", job->id, scheme);
+ }
+ }
+ else if ((mystdout = create_job_file(job, line, sizeof(line), job->printer->directory, "prn")) >= 0)
+ {
+ fprintf(stderr, "[Job %d] Saving print command output to \"%s\".\n", job->id, line);
+ }
+
+ if (mystdout < 0)
+ mystdout = open("/dev/null", O_WRONLY);
+
+ if (pipe(mypipe))
+ {
+ fprintf(stderr, "[Job %d] Unable to create pipe for stderr: %s\n", job->id, strerror(errno));
+ mypipe[0] = mypipe[1] = -1;
+ }
+
+ if ((pid = fork()) == 0)
+ {
+ /*
+ * Child comes here...
+ */
+
+ close(1);
+ dup2(mystdout, 1);
+ close(mystdout);
+
+ close(2);
+ dup2(mypipe[1], 2);
+ close(mypipe[0]);
+ close(mypipe[1]);
+
+ execve(job->printer->command, myargv, myenvp);
+ exit(errno);
+ }
+ else if (pid < 0)
+ {
+ /*
+ * Unable to fork process...
+ */
+
+ fprintf(stderr, "[Job %d] Unable to start job processing command: %s\n", job->id, strerror(errno));
+ status = -1;
+
+ close(mystdout);
+ close(mypipe[0]);
+ close(mypipe[1]);
+
+ /*
+ * Free memory used for environment...
+ */
+
+ while (myenvc > 0)
+ free(myenvp[-- myenvc]);
+ }
+ else
+ {
+ /*
+ * Free memory used for environment...
+ */
+
+ while (myenvc > 0)
+ free(myenvp[-- myenvc]);
+
+ /*
+ * Close the output file in the parent process...
+ */
+
+ close(mystdout);
+
+ /*
+ * If the pipe exists, read from it until EOF...
+ */
+
+ if (mypipe[0] >= 0)
+ {
+ close(mypipe[1]);
+
+ endptr = line;
+ while ((bytes = read(mypipe[0], endptr, sizeof(line) - (size_t)(endptr - line) - 1)) > 0)
+ {
+ endptr += bytes;
+ *endptr = '\0';
+
+ while ((ptr = strchr(line, '\n')) != NULL)
+ {
+ int level = 3; /* Message log level */
+
+ *ptr++ = '\0';
+
+ if (!strncmp(line, "ATTR:", 5))
+ {
+ /*
+ * Process job/printer attribute updates.
+ */
+
+ process_attr_message(job, line);
+ }
+ else if (!strncmp(line, "DEBUG:", 6))
+ {
+ /*
+ * Debug message...
+ */
+
+ level = 2;
+ }
+ else if (!strncmp(line, "ERROR:", 6))
+ {
+ /*
+ * Error message...
+ */
+
+ level = 0;
+ job->message = strdup(line + 6);
+ job->msglevel = 0;
+ }
+ else if (!strncmp(line, "INFO:", 5))
+ {
+ /*
+ * Informational/progress message...
+ */
+
+ level = 1;
+ if (job->msglevel)
+ {
+ job->message = strdup(line + 5);
+ job->msglevel = 1;
+ }
+ }
+ else if (!strncmp(line, "STATE:", 6))
+ {
+ /*
+ * Process printer-state-reasons keywords.
+ */
+
+ process_state_message(job, line);
+ }
+
+ if (Verbosity >= level)
+ fprintf(stderr, "[Job %d] Command - %s\n", job->id, line);
+
+ bytes = ptr - line;
+ if (ptr < endptr)
+ memmove(line, ptr, (size_t)(endptr - ptr));
+ endptr -= bytes;
+ *endptr = '\0';
+ }
+ }
+
+ close(mypipe[0]);
+ }
+
+ /*
+ * Wait for child to complete...
+ */
+
+# ifdef HAVE_WAITPID
+ while (waitpid(pid, &status, 0) < 0);
+# else
+ while (wait(&status) < 0);
+# endif /* HAVE_WAITPID */
+ }
+#endif /* _WIN32 */
+
+ if (status)
+ {
+#ifndef _WIN32
+ if (WIFEXITED(status))
+#endif /* !_WIN32 */
+ fprintf(stderr, "[Job %d] Command \"%s\" exited with status %d.\n", job->id, job->printer->command, WEXITSTATUS(status));
+#ifndef _WIN32
+ else
+ fprintf(stderr, "[Job %d] Command \"%s\" terminated with signal %d.\n", job->id, job->printer->command, WTERMSIG(status));
+#endif /* !_WIN32 */
+ job->state = IPP_JSTATE_ABORTED;
+ }
+ else if (status < 0)
+ job->state = IPP_JSTATE_ABORTED;
+ else
+ fprintf(stderr, "[Job %d] Command \"%s\" completed successfully.\n", job->id, job->printer->command);
+
+ /*
+ * Report the total processing time...
+ */
+
+ gettimeofday(&end, NULL);
+
+ fprintf(stderr, "[Job %d] Processing time was %.3f seconds.\n", job->id, end.tv_sec - start.tv_sec + 0.000001 * (end.tv_usec - start.tv_usec));
+ }
+ else
+ {
+ /*
+ * Sleep for a random amount of time to simulate job processing.
+ */
+
+ sleep((unsigned)(5 + (rand() % 11)));
+ }
+
+ if (job->cancel)
+ job->state = IPP_JSTATE_CANCELED;
+ else if (job->state == IPP_JSTATE_PROCESSING)
+ job->state = IPP_JSTATE_COMPLETED;
+
+ error:
+
+ job->completed = time(NULL);
+ job->printer->state = IPP_PSTATE_IDLE;
+ job->printer->active_job = NULL;
+
+ return (NULL);
+}
+
+
+/*
+ * 'process_state_message()' - Process a STATE: message from a command.
+ */
+
+static void
+process_state_message(
+ ippeve_job_t *job, /* I - Job */
+ char *message) /* I - Message */
+{
+ int i; /* Looping var */
+ ippeve_preason_t state_reasons, /* printer-state-reasons values */
+ bit; /* Current reason bit */
+ char *ptr, /* Pointer into message */
+ *next; /* Next keyword in message */
+ int remove; /* Non-zero if we are removing keywords */
+
+
+ /*
+ * Skip leading "STATE:" and any whitespace...
+ */
+
+ for (message += 6; *message; message ++)
+ if (*message != ' ' && *message != '\t')
+ break;
+
+ /*
+ * Support the following forms of message:
+ *
+ * "keyword[,keyword,...]" to set the printer-state-reasons value(s).
+ *
+ * "-keyword[,keyword,...]" to remove keywords.
+ *
+ * "+keyword[,keyword,...]" to add keywords.
+ *
+ * Keywords may or may not have a suffix (-report, -warning, -error) per
+ * RFC 8011.
+ */
+
+ if (*message == '-')
+ {
+ remove = 1;
+ state_reasons = job->printer->state_reasons;
+ message ++;
+ }
+ else if (*message == '+')
+ {
+ remove = 0;
+ state_reasons = job->printer->state_reasons;
+ message ++;
+ }
+ else
+ {
+ remove = 0;
+ state_reasons = IPPEVE_PREASON_NONE;
+ }
+
+ while (*message)
+ {
+ if ((next = strchr(message, ',')) != NULL)
+ *next++ = '\0';
+
+ if ((ptr = strstr(message, "-error")) != NULL)
+ *ptr = '\0';
+ else if ((ptr = strstr(message, "-report")) != NULL)
+ *ptr = '\0';
+ else if ((ptr = strstr(message, "-warning")) != NULL)
+ *ptr = '\0';
+
+ for (i = 0, bit = 1; i < (int)(sizeof(ippeve_preason_strings) / sizeof(ippeve_preason_strings[0])); i ++, bit *= 2)
+ {
+ if (!strcmp(message, ippeve_preason_strings[i]))
+ {
+ if (remove)
+ state_reasons &= ~bit;
+ else
+ state_reasons |= bit;
+ }
+ }
+
+ if (next)
+ message = next;
+ else
+ break;
+ }
+
+ job->printer->state_reasons = state_reasons;
+}
+
+
+/*
+ * 'register_printer()' - Register a printer object via Bonjour.
+ */
+
+static int /* O - 1 on success, 0 on error */
+register_printer(
+ ippeve_printer_t *printer, /* I - Printer */
+ const char *subtypes) /* I - Service subtype(s) */
+{
+#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
+ ippeve_txt_t ipp_txt; /* Bonjour IPP TXT record */
+ int i, /* Looping var */
+ count; /* Number of values */
+ ipp_attribute_t *color_supported,
+ *document_format_supported,
+ *printer_location,
+ *printer_make_and_model,
+ *printer_more_info,
+ *printer_uuid,
+ *sides_supported,
+ *urf_supported; /* Printer attributes */
+ const char *value; /* Value string */
+ char formats[252], /* List of supported formats */
+ urf[252], /* List of supported URF values */
+ *ptr; /* Pointer into string */
+
+ color_supported = ippFindAttribute(printer->attrs, "color-supported", IPP_TAG_BOOLEAN);
+ document_format_supported = ippFindAttribute(printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE);
+ printer_location = ippFindAttribute(printer->attrs, "printer-location", IPP_TAG_TEXT);
+ printer_make_and_model = ippFindAttribute(printer->attrs, "printer-make-and-model", IPP_TAG_TEXT);
+ printer_more_info = ippFindAttribute(printer->attrs, "printer-more-info", IPP_TAG_URI);
+ printer_uuid = ippFindAttribute(printer->attrs, "printer-uuid", IPP_TAG_URI);
+ sides_supported = ippFindAttribute(printer->attrs, "sides-supported", IPP_TAG_KEYWORD);
+ urf_supported = ippFindAttribute(printer->attrs, "urf-supported", IPP_TAG_KEYWORD);
+
+ for (i = 0, count = ippGetCount(document_format_supported), ptr = formats; i < count; i ++)
+ {
+ value = ippGetString(document_format_supported, i, NULL);
+
+ if (!strcasecmp(value, "application/octet-stream"))
+ continue;
+
+ if (ptr > formats && ptr < (formats + sizeof(formats) - 1))
+ *ptr++ = ',';
+
+ strlcpy(ptr, value, sizeof(formats) - (size_t)(ptr - formats));
+ ptr += strlen(ptr);
+
+ if (ptr >= (formats + sizeof(formats) - 1))
+ break;
+ }
+
+ urf[0] = '\0';
+ for (i = 0, count = ippGetCount(urf_supported), ptr = urf; i < count; i ++)
+ {
+ value = ippGetString(urf_supported, i, NULL);
+
+ if (ptr > urf && ptr < (urf + sizeof(urf) - 1))
+ *ptr++ = ',';
+
+ strlcpy(ptr, value, sizeof(urf) - (size_t)(ptr - urf));
+ ptr += strlen(ptr);
+
+ if (ptr >= (urf + sizeof(urf) - 1))
+ break;
+ }
+
+#endif /* HAVE_DNSSD || HAVE_AVAHI */
+#ifdef HAVE_DNSSD
+ DNSServiceErrorType error; /* Error from Bonjour */
+ char regtype[256]; /* Bonjour service type */
+
+
+ /*
+ * Build the TXT record for IPP...
+ */
+
+ TXTRecordCreate(&ipp_txt, 1024, NULL);
+ TXTRecordSetValue(&ipp_txt, "rp", 9, "ipp/print");
+ if ((value = ippGetString(printer_make_and_model, 0, NULL)) != NULL)
+ TXTRecordSetValue(&ipp_txt, "ty", (uint8_t)strlen(value), value);
+ if ((value = ippGetString(printer_more_info, 0, NULL)) != NULL)
+ TXTRecordSetValue(&ipp_txt, "adminurl", (uint8_t)strlen(value), value);
+ if ((value = ippGetString(printer_location, 0, NULL)) != NULL)
+ TXTRecordSetValue(&ipp_txt, "note", (uint8_t)strlen(value), value);
+ TXTRecordSetValue(&ipp_txt, "pdl", (uint8_t)strlen(formats), formats);
+ TXTRecordSetValue(&ipp_txt, "Color", 1, ippGetBoolean(color_supported, 0) ? "T" : "F");
+ TXTRecordSetValue(&ipp_txt, "Duplex", 1, ippGetCount(sides_supported) > 1 ? "T" : "F");
+ if ((value = ippGetString(printer_uuid, 0, NULL)) != NULL)
+ TXTRecordSetValue(&ipp_txt, "UUID", (uint8_t)strlen(value) - 9, value + 9);
+# ifdef HAVE_SSL
+ TXTRecordSetValue(&ipp_txt, "TLS", 3, "1.2");
+# endif /* HAVE_SSL */
+ if (urf[0])
+ TXTRecordSetValue(&ipp_txt, "URF", (uint8_t)strlen(urf), urf);
+ TXTRecordSetValue(&ipp_txt, "txtvers", 1, "1");
+ TXTRecordSetValue(&ipp_txt, "qtotal", 1, "1");
+
+ /*
+ * Register the _printer._tcp (LPD) service type with a port number of 0 to
+ * defend our service name but not actually support LPD...
+ */
+
+ printer->printer_ref = DNSSDMaster;
+
+ if ((error = DNSServiceRegister(&(printer->printer_ref), kDNSServiceFlagsShareConnection, 0 /* interfaceIndex */, printer->dnssd_name, "_printer._tcp", NULL /* domain */, NULL /* host */, 0 /* port */, 0 /* txtLen */, NULL /* txtRecord */, (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError)
+ {
+ _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, "_printer._tcp", error);
+ return (0);
+ }
+
+ /*
+ * Then register the _ipp._tcp (IPP) service type with the real port number to
+ * advertise our IPP printer...
+ */
+
+ printer->ipp_ref = DNSSDMaster;
+
+ if (subtypes && *subtypes)
+ snprintf(regtype, sizeof(regtype), "_ipp._tcp,%s", subtypes);
+ else
+ strlcpy(regtype, "_ipp._tcp", sizeof(regtype));
+
+ if ((error = DNSServiceRegister(&(printer->ipp_ref), kDNSServiceFlagsShareConnection, 0 /* interfaceIndex */, printer->dnssd_name, regtype, NULL /* domain */, NULL /* host */, htons(printer->port), TXTRecordGetLength(&ipp_txt), TXTRecordGetBytesPtr(&ipp_txt), (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError)
+ {
+ _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, regtype, error);
+ return (0);
+ }
+
+# ifdef HAVE_SSL
+ /*
+ * Then register the _ipps._tcp (IPP) service type with the real port number to
+ * advertise our IPPS printer...
+ */
+
+ printer->ipps_ref = DNSSDMaster;
+
+ if (subtypes && *subtypes)
+ snprintf(regtype, sizeof(regtype), "_ipps._tcp,%s", subtypes);
+ else
+ strlcpy(regtype, "_ipps._tcp", sizeof(regtype));
+
+ if ((error = DNSServiceRegister(&(printer->ipps_ref), kDNSServiceFlagsShareConnection, 0 /* interfaceIndex */, printer->dnssd_name, regtype, NULL /* domain */, NULL /* host */, htons(printer->port), TXTRecordGetLength(&ipp_txt), TXTRecordGetBytesPtr(&ipp_txt), (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError)
+ {
+ _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, regtype, error);
+ return (0);
+ }
+# endif /* HAVE_SSL */
+
+ /*
+ * Similarly, register the _http._tcp,_printer (HTTP) service type with the
+ * real port number to advertise our IPP printer...
+ */
+
+ printer->http_ref = DNSSDMaster;
+
+ if ((error = DNSServiceRegister(&(printer->http_ref), kDNSServiceFlagsShareConnection, 0 /* interfaceIndex */, printer->dnssd_name, "_http._tcp,_printer", NULL /* domain */, NULL /* host */, htons(printer->port), 0 /* txtLen */, NULL /* txtRecord */, (DNSServiceRegisterReply)dnssd_callback, printer)) != kDNSServiceErr_NoError)
+ {
+ _cupsLangPrintf(stderr, _("Unable to register \"%s.%s\": %d"), printer->dnssd_name, "_http._tcp,_printer", error);
+ return (0);
+ }
+
+ TXTRecordDeallocate(&ipp_txt);
+
+#elif defined(HAVE_AVAHI)
+ char temp[256]; /* Subtype service string */
+
+ /*
+ * Create the TXT record...
+ */
+
+ ipp_txt = NULL;
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "rp=ipp/print");
+ if ((value = ippGetString(printer_make_and_model, 0, NULL)) != NULL)
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "ty=%s", value);
+ if ((value = ippGetString(printer_more_info, 0, NULL)) != NULL)
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "adminurl=%s", value);
+ if ((value = ippGetString(printer_location, 0, NULL)) != NULL)
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "note=%s", value);
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "pdl=%s", formats);
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "Color=%s", ippGetBoolean(color_supported, 0) ? "T" : "F");
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "Duplex=%s", ippGetCount(sides_supported) > 1 ? "T" : "F");
+ if ((value = ippGetString(printer_uuid, 0, NULL)) != NULL)
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "UUID=%s", value + 9);
+# ifdef HAVE_SSL
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "TLS=1.2");
+# endif /* HAVE_SSL */
+ if (urf[0])
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "URF=%s", urf);
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "txtvers=1");
+ ipp_txt = avahi_string_list_add_printf(ipp_txt, "qtotal=1");
+
+ /*
+ * Register _printer._tcp (LPD) with port 0 to reserve the service name...
+ */
+
+ avahi_threaded_poll_lock(DNSSDMaster);
+
+ printer->ipp_ref = avahi_entry_group_new(DNSSDClient, dnssd_callback, NULL);
+
+ avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_printer._tcp", NULL, NULL, 0, NULL);
+
+ /*
+ * Then register the _ipp._tcp (IPP)...
+ */
+
+ avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipp._tcp", NULL, NULL, printer->port, ipp_txt);
+ if (subtypes && *subtypes)
+ {
+ char *temptypes = strdup(subtypes), *start, *end;
+
+ for (start = temptypes; *start; start = end)
+ {
+ if ((end = strchr(start, ',')) != NULL)
+ *end++ = '\0';
+ else
+ end = start + strlen(start);
+
+ snprintf(temp, sizeof(temp), "%s._sub._ipp._tcp", start);
+ avahi_entry_group_add_service_subtype(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipp._tcp", NULL, temp);
+ }
+
+ free(temptypes);
+ }
+
+#ifdef HAVE_SSL
+ /*
+ * _ipps._tcp (IPPS) for secure printing...
+ */
+
+ avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipps._tcp", NULL, NULL, printer->port, ipp_txt);
+ if (subtypes && *subtypes)
+ {
+ char *temptypes = strdup(subtypes), *start, *end;
+
+ for (start = temptypes; *start; start = end)
+ {
+ if ((end = strchr(start, ',')) != NULL)
+ *end++ = '\0';
+ else
+ end = start + strlen(start);
+
+ snprintf(temp, sizeof(temp), "%s._sub._ipps._tcp", start);
+ avahi_entry_group_add_service_subtype(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_ipps._tcp", NULL, temp);
+ }
+
+ free(temptypes);
+ }
+#endif /* HAVE_SSL */
+
+ /*
+ * Finally _http.tcp (HTTP) for the web interface...
+ */
+
+ avahi_entry_group_add_service_strlst(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_http._tcp", NULL, NULL, printer->port, NULL);
+ avahi_entry_group_add_service_subtype(printer->ipp_ref, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, printer->dnssd_name, "_http._tcp", NULL, "_printer._sub._http._tcp");
+
+ /*
+ * Commit it...
+ */
+
+ avahi_entry_group_commit(printer->ipp_ref);
+ avahi_threaded_poll_unlock(DNSSDMaster);
+
+ avahi_string_list_free(ipp_txt);
+#endif /* HAVE_DNSSD */
+
+ return (1);
+}
+
+
+/*
+ * 'respond_http()' - Send a HTTP response.
+ */
+
+int /* O - 1 on success, 0 on failure */
+respond_http(
+ ippeve_client_t *client, /* I - Client */
+ http_status_t code, /* I - HTTP status of response */
+ const char *content_encoding, /* I - Content-Encoding of response */
+ const char *type, /* I - MIME media type of response */
+ size_t length) /* I - Length of response */
+{
+ char message[1024]; /* Text message */
+
+
+ fprintf(stderr, "%s %s\n", client->hostname, httpStatus(code));
+
+ if (code == HTTP_STATUS_CONTINUE)
+ {
+ /*
+ * 100-continue doesn't send any headers...
+ */
+
+ return (httpWriteResponse(client->http, HTTP_STATUS_CONTINUE) == 0);
+ }
+
+ /*
+ * Format an error message...
+ */
+
+ if (!type && !length && code != HTTP_STATUS_OK && code != HTTP_STATUS_SWITCHING_PROTOCOLS)
+ {
+ snprintf(message, sizeof(message), "%d - %s\n", code, httpStatus(code));
+
+ type = "text/plain";
+ length = strlen(message);
+ }
+ else
+ message[0] = '\0';
+
+ /*
+ * Send the HTTP response header...
+ */
+
+ httpClearFields(client->http);
+
+ if (code == HTTP_STATUS_METHOD_NOT_ALLOWED ||
+ client->operation == HTTP_STATE_OPTIONS)
+ httpSetField(client->http, HTTP_FIELD_ALLOW, "GET, HEAD, OPTIONS, POST");
+
+ if (type)
+ {
+ if (!strcmp(type, "text/html"))
+ httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE,
+ "text/html; charset=utf-8");
+ else
+ httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE, type);
+
+ if (content_encoding)
+ httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, content_encoding);
+ }
+
+ httpSetLength(client->http, length);
+
+ if (httpWriteResponse(client->http, code) < 0)
+ return (0);
+
+ /*
+ * Send the response data...
+ */
+
+ if (message[0])
+ {
+ /*
+ * Send a plain text message.
+ */
+
+ if (httpPrintf(client->http, "%s", message) < 0)
+ return (0);
+
+ if (httpWrite2(client->http, "", 0) < 0)
+ return (0);
+ }
+ else if (client->response)
+ {
+ /*
+ * Send an IPP response...
+ */
+
+ debug_attributes("Response", client->response, 2);
+
+ ippSetState(client->response, IPP_STATE_IDLE);
+
+ if (ippWrite(client->http, client->response) != IPP_STATE_DATA)
+ return (0);
+ }
+
+ return (1);
+}
+
+
+/*
+ * 'respond_ipp()' - Send an IPP response.
+ */
+
+static void
+respond_ipp(ippeve_client_t *client, /* I - Client */
+ ipp_status_t status, /* I - status-code */
+ const char *message, /* I - printf-style status-message */
+ ...) /* I - Additional args as needed */
+{
+ const char *formatted = NULL; /* Formatted message */
+
+
+ ippSetStatusCode(client->response, status);
+
+ if (message)
+ {
+ va_list ap; /* Pointer to additional args */
+ ipp_attribute_t *attr; /* New status-message attribute */
+
+ va_start(ap, message);
+ if ((attr = ippFindAttribute(client->response, "status-message", IPP_TAG_TEXT)) != NULL)
+ ippSetStringfv(client->response, &attr, 0, message, ap);
+ else
+ attr = ippAddStringfv(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, "status-message", NULL, message, ap);
+ va_end(ap);
+
+ formatted = ippGetString(attr, 0, NULL);
+ }
+
+ if (formatted)
+ fprintf(stderr, "%s %s %s (%s)\n", client->hostname, ippOpString(client->operation_id), ippErrorString(status), formatted);
+ else
+ fprintf(stderr, "%s %s %s\n", client->hostname, ippOpString(client->operation_id), ippErrorString(status));
+}
+
+
+/*
+ * 'respond_unsupported()' - Respond with an unsupported attribute.
+ */
+
+static void
+respond_unsupported(
+ ippeve_client_t *client, /* I - Client */
+ ipp_attribute_t *attr) /* I - Atribute */
+{
+ ipp_attribute_t *temp; /* Copy of attribute */
+
+
+ respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES, "Unsupported %s %s%s value.", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
+
+ temp = ippCopyAttribute(client->response, attr, 0);
+ ippSetGroupTag(client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP);
+}
+
+
+/*
+ * 'run_printer()' - Run the printer service.
+ */
+
+static void
+run_printer(ippeve_printer_t *printer) /* I - Printer */
+{
+ int num_fds; /* Number of file descriptors */
+ struct pollfd polldata[3]; /* poll() data */
+ int timeout; /* Timeout for poll() */
+ ippeve_client_t *client; /* New client */
+
+
+ /*
+ * Setup poll() data for the Bonjour service socket and IPv4/6 listeners...
+ */
+
+ polldata[0].fd = printer->ipv4;
+ polldata[0].events = POLLIN;
+
+ polldata[1].fd = printer->ipv6;
+ polldata[1].events = POLLIN;
+
+ num_fds = 2;
+
+#ifdef HAVE_DNSSD
+ polldata[num_fds ].fd = DNSServiceRefSockFD(DNSSDMaster);
+ polldata[num_fds ++].events = POLLIN;
+#endif /* HAVE_DNSSD */
+
+ /*
+ * Loop until we are killed or have a hard error...
+ */
+
+ for (;;)
+ {
+ if (cupsArrayCount(printer->jobs))
+ timeout = 10;
+ else
+ timeout = -1;
+
+ if (poll(polldata, (nfds_t)num_fds, timeout) < 0 && errno != EINTR)
+ {
+ perror("poll() failed");
+ break;
+ }
+
+ if (polldata[0].revents & POLLIN)
+ {
+ if ((client = create_client(printer, printer->ipv4)) != NULL)
+ {
+ _cups_thread_t t = _cupsThreadCreate((_cups_thread_func_t)process_client, client);
+
+ if (t)
+ {
+ _cupsThreadDetach(t);
+ }
+ else
+ {
+ perror("Unable to create client thread");
+ delete_client(client);
+ }
+ }
+ }
+
+ if (polldata[1].revents & POLLIN)
+ {
+ if ((client = create_client(printer, printer->ipv6)) != NULL)
+ {
+ _cups_thread_t t = _cupsThreadCreate((_cups_thread_func_t)process_client, client);
+
+ if (t)
+ {
+ _cupsThreadDetach(t);
+ }
+ else
+ {
+ perror("Unable to create client thread");
+ delete_client(client);
+ }
+ }
+ }
+
+#ifdef HAVE_DNSSD
+ if (polldata[2].revents & POLLIN)
+ DNSServiceProcessResult(DNSSDMaster);
+#endif /* HAVE_DNSSD */
+
+ /*
+ * Clean out old jobs...
+ */
+
+ clean_jobs(printer);
+ }
+}
+
+
+/*
+ * 'show_media()' - Show media load state.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+show_media(ippeve_client_t *client) /* I - Client connection */
+{
+ ippeve_printer_t *printer = client->printer;
+ /* Printer */
+ int i, j, /* Looping vars */
+ num_ready, /* Number of ready media */
+ num_sizes, /* Number of media sizes */
+ num_sources, /* Number of media sources */
+ num_types; /* Number of media types */
+ ipp_attribute_t *media_col_ready,/* media-col-ready attribute */
+ *media_ready, /* media-ready attribute */
+ *media_sizes, /* media-supported attribute */
+ *media_sources, /* media-source-supported attribute */
+ *media_types, /* media-type-supported attribute */
+ *input_tray; /* printer-input-tray attribute */
+ ipp_t *media_col; /* media-col value */
+ const char *media_size, /* media value */
+ *media_source, /* media-source value */
+ *media_type, /* media-type value */
+ *ready_size, /* media-col-ready media-size[-name] value */
+ *ready_source, /* media-col-ready media-source value */
+ *ready_tray, /* printer-input-tray value */
+ *ready_type; /* media-col-ready media-type value */
+ char tray_str[1024], /* printer-input-tray string value */
+ *tray_ptr; /* Pointer into value */
+ int tray_len; /* Length of printer-input-tray value */
+ int ready_sheets; /* printer-input-tray sheets value */
+ int num_options; /* Number of form options */
+ cups_option_t *options; /* Form options */
+ static const int sheets[] = /* Number of sheets */
+ {
+ 250,
+ 125,
+ 50,
+ 25,
+ 5,
+ 0,
+ -2
+ };
+
+
+ if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0))
+ return (0);
+
+ html_header(client, printer->name, 0);
+
+ if ((media_col_ready = ippFindAttribute(printer->attrs, "media-col-ready", IPP_TAG_BEGIN_COLLECTION)) == NULL)
+ {
+ html_printf(client, "<p>Error: No media-col-ready defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ media_ready = ippFindAttribute(printer->attrs, "media-ready", IPP_TAG_ZERO);
+
+ if ((media_sizes = ippFindAttribute(printer->attrs, "media-supported", IPP_TAG_ZERO)) == NULL)
+ {
+ html_printf(client, "<p>Error: No media-supported defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ if ((media_sources = ippFindAttribute(printer->attrs, "media-source-supported", IPP_TAG_ZERO)) == NULL)
+ {
+ html_printf(client, "<p>Error: No media-source-supported defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ if ((media_types = ippFindAttribute(printer->attrs, "media-type-supported", IPP_TAG_ZERO)) == NULL)
+ {
+ html_printf(client, "<p>Error: No media-type-supported defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ if ((input_tray = ippFindAttribute(printer->attrs, "printer-input-tray", IPP_TAG_STRING)) == NULL)
+ {
+ html_printf(client, "<p>Error: No printer-input-tray defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ num_ready = ippGetCount(media_col_ready);
+ num_sizes = ippGetCount(media_sizes);
+ num_sources = ippGetCount(media_sources);
+ num_types = ippGetCount(media_types);
+
+ if (num_sources != ippGetCount(input_tray))
+ {
+ html_printf(client, "<p>Error: Different number of trays in media-source-supported and printer-input-tray defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ /*
+ * Process form data if present...
+ */
+
+ if (printer->web_forms)
+ num_options = parse_options(client, &options);
+ else
+ num_options = 0;
+
+ if (num_options > 0)
+ {
+ /*
+ * WARNING: A real printer/server implementation MUST NOT implement
+ * media updates via a GET request - GET requests are supposed to be
+ * idempotent (without side-effects) and we obviously are not
+ * authenticating access here. This form is provided solely to
+ * enable testing and development!
+ */
+
+ char name[255]; /* Form name */
+ const char *val; /* Form value */
+ pwg_media_t *media; /* Media info */
+
+ _cupsRWLockWrite(&printer->rwlock);
+
+ ippDeleteAttribute(printer->attrs, media_col_ready);
+ media_col_ready = NULL;
+
+ if (media_ready)
+ {
+ ippDeleteAttribute(printer->attrs, media_ready);
+ media_ready = NULL;
+ }
+
+ printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MEDIA_LOW | IPPEVE_PREASON_MEDIA_EMPTY | IPPEVE_PREASON_MEDIA_NEEDED);
+
+ for (i = 0; i < num_sources; i ++)
+ {
+ media_source = ippGetString(media_sources, i, NULL);
+
+ if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual") || strstr(media_source, "-man") != NULL)
+ continue;
+
+ snprintf(name, sizeof(name), "size%d", i);
+ if ((media_size = cupsGetOption(name, num_options, options)) != NULL && (media = pwgMediaForPWG(media_size)) != NULL)
+ {
+ snprintf(name, sizeof(name), "type%d", i);
+ if ((media_type = cupsGetOption(name, num_options, options)) != NULL && !*media_type)
+ media_type = NULL;
+
+ if (media_ready)
+ ippSetString(printer->attrs, &media_ready, ippGetCount(media_ready), media_size);
+ else
+ media_ready = ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, media_size);
+
+ media_col = create_media_col(media_size, media_source, media_type, media->width, media->length, -1, -1, -1, -1);
+
+ if (media_col_ready)
+ ippSetCollection(printer->attrs, &media_col_ready, ippGetCount(media_col_ready), media_col);
+ else
+ media_col_ready = ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-ready", media_col);
+ ippDelete(media_col);
+ }
+ else
+ media = NULL;
+
+ snprintf(name, sizeof(name), "level%d", i);
+ if ((val = cupsGetOption(name, num_options, options)) != NULL)
+ ready_sheets = atoi(val);
+ else
+ ready_sheets = 0;
+
+ snprintf(tray_str, sizeof(tray_str), "type=sheetFeedAuto%sRemovableTray;mediafeed=%d;mediaxfeed=%d;maxcapacity=%d;level=%d;status=0;name=%s;", !strcmp(media_source, "by-pass-tray") ? "Non" : "", media ? media->length : 0, media ? media->width : 0, strcmp(media_source, "by-pass-tray") ? 250 : 25, ready_sheets, media_source);
+
+ ippSetOctetString(printer->attrs, &input_tray, i, tray_str, (int)strlen(tray_str));
+
+ if (ready_sheets == 0)
+ {
+ printer->state_reasons |= IPPEVE_PREASON_MEDIA_EMPTY;
+ if (printer->active_job)
+ printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED;
+ }
+ else if (ready_sheets < 25 && ready_sheets > 0)
+ printer->state_reasons |= IPPEVE_PREASON_MEDIA_LOW;
+ }
+
+ if (!media_col_ready)
+ media_col_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-col-ready");
+
+ if (!media_ready)
+ media_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-ready");
+
+ _cupsRWUnlock(&printer->rwlock);
+ }
+
+ if (printer->web_forms)
+ html_printf(client, "<form method=\"GET\" action=\"/media\">\n");
+
+ html_printf(client, "<table class=\"form\" summary=\"Media\">\n");
+ for (i = 0; i < num_sources; i ++)
+ {
+ media_source = ippGetString(media_sources, i, NULL);
+
+ if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual") || strstr(media_source, "-man") != NULL)
+ continue;
+
+ for (j = 0, ready_size = NULL, ready_type = NULL; j < num_ready; j ++)
+ {
+ media_col = ippGetCollection(media_col_ready, j);
+ ready_size = ippGetString(ippFindAttribute(media_col, "media-size-name", IPP_TAG_ZERO), 0, NULL);
+ ready_source = ippGetString(ippFindAttribute(media_col, "media-source", IPP_TAG_ZERO), 0, NULL);
+ ready_type = ippGetString(ippFindAttribute(media_col, "media-type", IPP_TAG_ZERO), 0, NULL);
+
+ if (ready_source && !strcmp(ready_source, media_source))
+ break;
+
+ ready_source = NULL;
+ ready_size = NULL;
+ ready_type = NULL;
+ }
+
+ html_printf(client, "<tr><th>%s:</th>", media_source);
+
+ /*
+ * Media size...
+ */
+
+ if (printer->web_forms)
+ {
+ html_printf(client, "<td><select name=\"size%d\"><option value=\"\">None</option>", i);
+ for (j = 0; j < num_sizes; j ++)
+ {
+ media_size = ippGetString(media_sizes, j, NULL);
+
+ html_printf(client, "<option%s>%s</option>", (ready_size && !strcmp(ready_size, media_size)) ? " selected" : "", media_size);
+ }
+ html_printf(client, "</select>");
+ }
+ else
+ html_printf(client, "<td>%s", ready_size);
+
+ /*
+ * Media type...
+ */
+
+ if (printer->web_forms)
+ {
+ html_printf(client, " <select name=\"type%d\"><option value=\"\">None</option>", i);
+ for (j = 0; j < num_types; j ++)
+ {
+ media_type = ippGetString(media_types, j, NULL);
+
+ html_printf(client, "<option%s>%s</option>", (ready_type && !strcmp(ready_type, media_type)) ? " selected" : "", media_type);
+ }
+ html_printf(client, "</select>");
+ }
+ else
+ html_printf(client, ", %s", ready_type);
+
+ /*
+ * Level/sheets loaded...
+ */
+
+ if ((ready_tray = ippGetOctetString(input_tray, i, &tray_len)) != NULL)
+ {
+ if (tray_len > (int)(sizeof(tray_str) - 1))
+ tray_len = (int)sizeof(tray_str) - 1;
+ memcpy(tray_str, ready_tray, (size_t)tray_len);
+ tray_str[tray_len] = '\0';
+
+ if ((tray_ptr = strstr(tray_str, "level=")) != NULL)
+ ready_sheets = atoi(tray_ptr + 6);
+ else
+ ready_sheets = 0;
+ }
+ else
+ ready_sheets = 0;
+
+ if (printer->web_forms)
+ {
+ html_printf(client, " <select name=\"level%d\">", i);
+ for (j = 0; j < (int)(sizeof(sheets) / sizeof(sheets[0])); j ++)
+ {
+ if (!strcmp(media_source, "by-pass-tray") && sheets[j] > 25)
+ continue;
+
+ if (sheets[j] < 0)
+ html_printf(client, "<option value=\"%d\"%s>Unknown</option>", sheets[j], sheets[j] == ready_sheets ? " selected" : "");
+ else
+ html_printf(client, "<option value=\"%d\"%s>%d sheets</option>", sheets[j], sheets[j] == ready_sheets ? " selected" : "", sheets[j]);
+ }
+ html_printf(client, "</select></td></tr>\n");
+ }
+ else if (ready_sheets > 0)
+ html_printf(client, ", %d sheets</td></tr>\n", ready_sheets);
+ else
+ html_printf(client, "</td></tr>\n");
+ }
+
+ if (printer->web_forms)
+ {
+ html_printf(client, "<tr><td></td><td><input type=\"submit\" value=\"Update Media\">");
+ if (num_options > 0)
+ html_printf(client, " <span class=\"badge\" id=\"status\">Media updated.</span>\n");
+ html_printf(client, "</td></tr></table></form>\n");
+
+ if (num_options > 0)
+ html_printf(client, "<script>\n"
+ "setTimeout(hide_status, 3000);\n"
+ "function hide_status() {\n"
+ " var status = document.getElementById('status');\n"
+ " status.style.display = 'none';\n"
+ "}\n"
+ "</script>\n");
+ }
+ else
+ html_printf(client, "</table>\n");
+
+ html_footer(client);
+
+ return (1);
+}
+
+
+/*
+ * 'show_status()' - Show printer/system state.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+show_status(ippeve_client_t *client) /* I - Client connection */
+{
+ ippeve_printer_t *printer = client->printer;
+ /* Printer */
+ ippeve_job_t *job; /* Current job */
+ int i; /* Looping var */
+ ippeve_preason_t reason; /* Current reason */
+ static const char * const reasons[] = /* Reason strings */
+ {
+ "Other",
+ "Cover Open",
+ "Input Tray Missing",
+ "Marker Supply Empty",
+ "Marker Supply Low",
+ "Marker Waste Almost Full",
+ "Marker Waste Full",
+ "Media Empty",
+ "Media Jam",
+ "Media Low",
+ "Media Needed",
+ "Moving to Paused",
+ "Paused",
+ "Spool Area Full",
+ "Toner Empty",
+ "Toner Low"
+ };
+ static const char * const state_colors[] =
+ { /* State colors */
+ "#0C0", /* Idle */
+ "#EE0", /* Processing */
+ "#C00" /* Stopped */
+ };
+
+
+ if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0))
+ return (0);
+
+ html_header(client, printer->name, printer->state == IPP_PSTATE_PROCESSING ? 5 : 15);
+ html_printf(client, "<h1><img style=\"background: %s; border-radius: 10px; float: left; margin-right: 10px; padding: 10px;\" src=\"/icon.png\" width=\"64\" height=\"64\">%s Jobs</h1>\n", state_colors[printer->state - IPP_PSTATE_IDLE], printer->name);
+ html_printf(client, "<p>%s, %d job(s).", printer->state == IPP_PSTATE_IDLE ? "Idle" : printer->state == IPP_PSTATE_PROCESSING ? "Printing" : "Stopped", cupsArrayCount(printer->jobs));
+ for (i = 0, reason = 1; i < (int)(sizeof(reasons) / sizeof(reasons[0])); i ++, reason <<= 1)
+ if (printer->state_reasons & reason)
+ html_printf(client, "\n<br>&nbsp;&nbsp;&nbsp;&nbsp;%s", reasons[i]);
+ html_printf(client, "</p>\n");
+
+ if (cupsArrayCount(printer->jobs) > 0)
+ {
+ _cupsRWLockRead(&(printer->rwlock));
+
+ html_printf(client, "<table class=\"striped\" summary=\"Jobs\"><thead><tr><th>Job #</th><th>Name</th><th>Owner</th><th>Status</th></tr></thead><tbody>\n");
+ for (job = (ippeve_job_t *)cupsArrayFirst(printer->jobs); job; job = (ippeve_job_t *)cupsArrayNext(printer->jobs))
+ {
+ char when[256], /* When job queued/started/finished */
+ hhmmss[64]; /* Time HH:MM:SS */
+
+ switch (job->state)
+ {
+ case IPP_JSTATE_PENDING :
+ case IPP_JSTATE_HELD :
+ snprintf(when, sizeof(when), "Queued at %s", time_string(job->created, hhmmss, sizeof(hhmmss)));
+ break;
+ case IPP_JSTATE_PROCESSING :
+ case IPP_JSTATE_STOPPED :
+ snprintf(when, sizeof(when), "Started at %s", time_string(job->processing, hhmmss, sizeof(hhmmss)));
+ break;
+ case IPP_JSTATE_ABORTED :
+ snprintf(when, sizeof(when), "Aborted at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
+ break;
+ case IPP_JSTATE_CANCELED :
+ snprintf(when, sizeof(when), "Canceled at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
+ break;
+ case IPP_JSTATE_COMPLETED :
+ snprintf(when, sizeof(when), "Completed at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
+ break;
+ }
+
+ html_printf(client, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", job->id, job->name, job->username, when);
+ }
+ html_printf(client, "</tbody></table>\n");
+
+ _cupsRWUnlock(&(printer->rwlock));
+ }
+
+ html_footer(client);
+
+ return (1);
+}
+
+
+/*
+ * 'show_supplies()' - Show printer supplies.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+show_supplies(
+ ippeve_client_t *client) /* I - Client connection */
+{
+ ippeve_printer_t *printer = client->printer;
+ /* Printer */
+ int i, /* Looping var */
+ num_supply; /* Number of supplies */
+ ipp_attribute_t *supply, /* printer-supply attribute */
+ *supply_desc; /* printer-supply-description attribute */
+ int num_options; /* Number of form options */
+ cups_option_t *options; /* Form options */
+ int supply_len, /* Length of supply value */
+ level; /* Supply level */
+ const char *supply_value; /* Supply value */
+ char supply_text[1024], /* Supply string */
+ *supply_ptr; /* Pointer into supply string */
+ static const char * const printer_supply[] =
+ { /* printer-supply values */
+ "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
+ "maxcapacity=100;level=%d;colorantname=unknown;",
+ "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
+ "maxcapacity=100;level=%d;colorantname=black;",
+ "index=3;class=supplyThatIsConsumed;type=toner;unit=percent;"
+ "maxcapacity=100;level=%d;colorantname=cyan;",
+ "index=4;class=supplyThatIsConsumed;type=toner;unit=percent;"
+ "maxcapacity=100;level=%d;colorantname=magenta;",
+ "index=5;class=supplyThatIsConsumed;type=toner;unit=percent;"
+ "maxcapacity=100;level=%d;colorantname=yellow;"
+ };
+ static const char * const backgrounds[] =
+ { /* Background colors for the supply-level bars */
+ "#777 linear-gradient(#333,#777)",
+ "#000 linear-gradient(#666,#000)",
+ "#0FF linear-gradient(#6FF,#0FF)",
+ "#F0F linear-gradient(#F6F,#F0F)",
+ "#CC0 linear-gradient(#EE6,#EE0)"
+ };
+ static const char * const colors[] = /* Text colors for the supply-level bars */
+ {
+ "#fff",
+ "#fff",
+ "#000",
+ "#000",
+ "#000"
+ };
+
+
+ if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0))
+ return (0);
+
+ html_header(client, printer->name, 0);
+
+ if ((supply = ippFindAttribute(printer->attrs, "printer-supply", IPP_TAG_STRING)) == NULL)
+ {
+ html_printf(client, "<p>Error: No printer-supply defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ num_supply = ippGetCount(supply);
+
+ if ((supply_desc = ippFindAttribute(printer->attrs, "printer-supply-description", IPP_TAG_TEXT)) == NULL)
+ {
+ html_printf(client, "<p>Error: No printer-supply-description defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ if (num_supply != ippGetCount(supply_desc))
+ {
+ html_printf(client, "<p>Error: Different number of values for printer-supply and printer-supply-description defined for printer.</p>\n");
+ html_footer(client);
+ return (1);
+ }
+
+ if (printer->web_forms)
+ num_options = parse_options(client, &options);
+ else
+ num_options = 0;
+
+ if (num_options > 0)
+ {
+ /*
+ * WARNING: A real printer/server implementation MUST NOT implement
+ * supply updates via a GET request - GET requests are supposed to be
+ * idempotent (without side-effects) and we obviously are not
+ * authenticating access here. This form is provided solely to
+ * enable testing and development!
+ */
+
+ char name[64]; /* Form field */
+ const char *val; /* Form value */
+
+ _cupsRWLockWrite(&printer->rwlock);
+
+ ippDeleteAttribute(printer->attrs, supply);
+ supply = NULL;
+
+ printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MARKER_SUPPLY_EMPTY | IPPEVE_PREASON_MARKER_SUPPLY_LOW | IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL | IPPEVE_PREASON_MARKER_WASTE_FULL | IPPEVE_PREASON_TONER_EMPTY | IPPEVE_PREASON_TONER_LOW);
+
+ for (i = 0; i < num_supply; i ++)
+ {
+ snprintf(name, sizeof(name), "supply%d", i);
+ if ((val = cupsGetOption(name, num_options, options)) != NULL)
+ {
+ level = atoi(val); /* New level */
+
+ snprintf(supply_text, sizeof(supply_text), printer_supply[i], level);
+ if (supply)
+ ippSetOctetString(printer->attrs, &supply, ippGetCount(supply), supply_text, (int)strlen(supply_text));
+ else
+ supply = ippAddOctetString(printer->attrs, IPP_TAG_PRINTER, "printer-supply", supply_text, (int)strlen(supply_text));
+
+ if (i == 0)
+ {
+ if (level == 100)
+ printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_FULL;
+ else if (level > 90)
+ printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL;
+ }
+ else
+ {
+ if (level == 0)
+ printer->state_reasons |= IPPEVE_PREASON_TONER_EMPTY;
+ else if (level < 10)
+ printer->state_reasons |= IPPEVE_PREASON_TONER_LOW;
+ }
+ }
+ }
+
+ _cupsRWUnlock(&printer->rwlock);
+ }
+
+ if (printer->web_forms)
+ html_printf(client, "<form method=\"GET\" action=\"/supplies\">\n");
+
+ html_printf(client, "<table class=\"form\" summary=\"Supplies\">\n");
+ for (i = 0; i < num_supply; i ++)
+ {
+ supply_value = ippGetOctetString(supply, i, &supply_len);
+ if (supply_len > (int)(sizeof(supply_text) - 1))
+ supply_len = (int)sizeof(supply_text) - 1;
+
+ memcpy(supply_text, supply_value, (size_t)supply_len);
+ supply_text[supply_len] = '\0';
+
+ if ((supply_ptr = strstr(supply_text, "level=")) != NULL)
+ level = atoi(supply_ptr + 6);
+ else
+ level = 50;
+
+ if (printer->web_forms)
+ html_printf(client, "<tr><th>%s:</th><td><input name=\"supply%d\" size=\"3\" value=\"%d\"></td>", ippGetString(supply_desc, i, NULL), i, level);
+ else
+ html_printf(client, "<tr><th>%s:</th>", ippGetString(supply_desc, i, NULL));
+
+ if (level < 10)
+ html_printf(client, "<td class=\"meter\"><span class=\"bar\" style=\"background: %s; padding: 5px %dpx;\"></span>&nbsp;%d%%</td></tr>\n", backgrounds[i], level * 2, level);
+ else
+ html_printf(client, "<td class=\"meter\"><span class=\"bar\" style=\"background: %s; color: %s; padding: 5px %dpx;\">%d%%</span></td></tr>\n", backgrounds[i], colors[i], level * 2, level);
+ }
+
+ if (printer->web_forms)
+ {
+ html_printf(client, "<tr><td></td><td colspan=\"2\"><input type=\"submit\" value=\"Update Supplies\">");
+ if (num_options > 0)
+ html_printf(client, " <span class=\"badge\" id=\"status\">Supplies updated.</span>\n");
+ html_printf(client, "</td></tr>\n</table>\n</form>\n");
+
+ if (num_options > 0)
+ html_printf(client, "<script>\n"
+ "setTimeout(hide_status, 3000);\n"
+ "function hide_status() {\n"
+ " var status = document.getElementById('status');\n"
+ " status.style.display = 'none';\n"
+ "}\n"
+ "</script>\n");
+ }
+ else
+ html_printf(client, "</table>\n");
+
+ html_footer(client);
+
+ return (1);
+}
+
+
+/*
+ * 'time_string()' - Return the local time in hours, minutes, and seconds.
+ */
+
+static char *
+time_string(time_t tv, /* I - Time value */
+ char *buffer, /* I - Buffer */
+ size_t bufsize) /* I - Size of buffer */
+{
+ struct tm *curtime = localtime(&tv);
+ /* Local time */
+
+ strftime(buffer, bufsize, "%X", curtime);
+ return (buffer);
+}
+
+
+/*
+ * 'usage()' - Show program usage.
+ */
+
+static void
+usage(int status) /* O - Exit status */
+{
+ _cupsLangPuts(stdout, _("Usage: ippeveprinter [options] \"name\""));
+ _cupsLangPuts(stdout, _("Options:"));
+ _cupsLangPuts(stderr, _("--help Show program help"));
+ _cupsLangPuts(stderr, _("--no-web-forms Disable web forms for media and supplies"));
+ _cupsLangPuts(stderr, _("--version Show program version"));
+ _cupsLangPuts(stdout, _("-2 Set 2-sided printing support (default=1-sided)"));
+ _cupsLangPuts(stdout, _("-D device-uri Set the device URI for the printer"));
+#ifdef HAVE_SSL
+ _cupsLangPuts(stdout, _("-K keypath Set location of server X.509 certificates and keys."));
+#endif /* HAVE_SSL */
+ _cupsLangPuts(stdout, _("-M manufacturer Set manufacturer name (default=Test)"));
+ _cupsLangPuts(stdout, _("-P filename.ppd Load printer attributes from PPD file"));
+ _cupsLangPuts(stdout, _("-V version Set default IPP version"));
+ _cupsLangPuts(stdout, _("-a filename.conf Load printer attributes from conf file"));
+ _cupsLangPuts(stdout, _("-c command Set print command"));
+ _cupsLangPuts(stdout, _("-d spool-directory Set spool directory"));
+ _cupsLangPuts(stdout, _("-f type/subtype[,...] Set supported file types"));
+ _cupsLangPuts(stdout, _("-i iconfile.png Set icon file"));
+ _cupsLangPuts(stdout, _("-k Keep job spool files"));
+ _cupsLangPuts(stdout, _("-l location Set location of printer"));
+ _cupsLangPuts(stdout, _("-m model Set model name (default=Printer)"));
+ _cupsLangPuts(stdout, _("-n hostname Set hostname for printer"));
+ _cupsLangPuts(stdout, _("-p port Set port number for printer"));
+ _cupsLangPuts(stdout, _("-r subtype,[subtype] Set DNS-SD service subtype"));
+ _cupsLangPuts(stdout, _("-s speed[,color-speed] Set speed in pages per minute"));
+ _cupsLangPuts(stderr, _("-v Be verbose"));
+
+ exit(status);
+}
+
+
+/*
+ * 'valid_doc_attributes()' - Determine whether the document attributes are
+ * valid.
+ *
+ * When one or more document attributes are invalid, this function adds a
+ * suitable response and attributes to the unsupported group.
+ */
+
+static int /* O - 1 if valid, 0 if not */
+valid_doc_attributes(
+ ippeve_client_t *client) /* I - Client */
+{
+ int valid = 1; /* Valid attributes? */
+ ipp_op_t op = ippGetOperation(client->request);
+ /* IPP operation */
+ const char *op_name = ippOpString(op);
+ /* IPP operation name */
+ ipp_attribute_t *attr, /* Current attribute */
+ *supported; /* xxx-supported attribute */
+ const char *compression = NULL,
+ /* compression value */
+ *format = NULL; /* document-format value */
+
+
+ /*
+ * Check operation attributes...
+ */
+
+ if ((attr = ippFindAttribute(client->request, "compression", IPP_TAG_ZERO)) != NULL)
+ {
+ /*
+ * If compression is specified, only accept a supported value in a Print-Job
+ * or Send-Document request...
+ */
+
+ compression = ippGetString(attr, 0, NULL);
+ supported = ippFindAttribute(client->printer->attrs,
+ "compression-supported", IPP_TAG_KEYWORD);
+
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD ||
+ ippGetGroupTag(attr) != IPP_TAG_OPERATION ||
+ (op != IPP_OP_PRINT_JOB && op != IPP_OP_SEND_DOCUMENT &&
+ op != IPP_OP_VALIDATE_JOB) ||
+ !ippContainsString(supported, compression))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else
+ {
+ fprintf(stderr, "%s %s compression=\"%s\"\n", client->hostname, op_name, compression);
+
+ ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_KEYWORD, "compression-supplied", NULL, compression);
+
+ if (strcmp(compression, "none"))
+ {
+ if (Verbosity)
+ fprintf(stderr, "Receiving job file with \"%s\" compression.\n", compression);
+ httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, compression);
+ }
+ }
+ }
+
+ /*
+ * Is it a format we support?
+ */
+
+ if ((attr = ippFindAttribute(client->request, "document-format", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_MIMETYPE ||
+ ippGetGroupTag(attr) != IPP_TAG_OPERATION)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else
+ {
+ format = ippGetString(attr, 0, NULL);
+
+ fprintf(stderr, "%s %s document-format=\"%s\"\n", client->hostname, op_name, format);
+
+ ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-supplied", NULL, format);
+ }
+ }
+ else
+ {
+ format = ippGetString(ippFindAttribute(client->printer->attrs, "document-format-default", IPP_TAG_MIMETYPE), 0, NULL);
+ if (!format)
+ format = "application/octet-stream"; /* Should never happen */
+
+ attr = ippAddString(client->request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, format);
+ }
+
+ if (format && !strcmp(format, "application/octet-stream") && (ippGetOperation(client->request) == IPP_OP_PRINT_JOB || ippGetOperation(client->request) == IPP_OP_SEND_DOCUMENT))
+ {
+ /*
+ * Auto-type the file using the first 8 bytes of the file...
+ */
+
+ unsigned char header[8]; /* First 8 bytes of file */
+
+ memset(header, 0, sizeof(header));
+ httpPeek(client->http, (char *)header, sizeof(header));
+
+ if (!memcmp(header, "%PDF", 4))
+ format = "application/pdf";
+ else if (!memcmp(header, "%!", 2))
+ format = "application/postscript";
+ else if (!memcmp(header, "\377\330\377", 3) && header[3] >= 0xe0 && header[3] <= 0xef)
+ format = "image/jpeg";
+ else if (!memcmp(header, "\211PNG", 4))
+ format = "image/png";
+ else if (!memcmp(header, "RAS2", 4))
+ format = "image/pwg-raster";
+ else if (!memcmp(header, "UNIRAST", 8))
+ format = "image/urf";
+ else
+ format = NULL;
+
+ if (format)
+ {
+ fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n", client->hostname, op_name, format);
+
+ ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format-detected", NULL, format);
+ }
+ }
+
+ if (op != IPP_OP_CREATE_JOB && (supported = ippFindAttribute(client->printer->attrs, "document-format-supported", IPP_TAG_MIMETYPE)) != NULL && !ippContainsString(supported, format))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+
+ /*
+ * document-name
+ */
+
+ if ((attr = ippFindAttribute(client->request, "document-name", IPP_TAG_NAME)) != NULL)
+ ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_NAME, "document-name-supplied", NULL, ippGetString(attr, 0, NULL));
+
+ return (valid);
+}
+
+
+/*
+ * 'valid_job_attributes()' - Determine whether the job attributes are valid.
+ *
+ * When one or more job attributes are invalid, this function adds a suitable
+ * response and attributes to the unsupported group.
+ */
+
+static int /* O - 1 if valid, 0 if not */
+valid_job_attributes(
+ ippeve_client_t *client) /* I - Client */
+{
+ int i, /* Looping var */
+ count, /* Number of values */
+ valid = 1; /* Valid attributes? */
+ ipp_attribute_t *attr, /* Current attribute */
+ *supported; /* xxx-supported attribute */
+
+
+ /*
+ * Check operation attributes...
+ */
+
+ valid = valid_doc_attributes(client);
+
+ /*
+ * Check the various job template attributes...
+ */
+
+ if ((attr = ippFindAttribute(client->request, "copies", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER ||
+ ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 999)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_BOOLEAN)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "job-hold-until", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 ||
+ (ippGetValueTag(attr) != IPP_TAG_NAME &&
+ ippGetValueTag(attr) != IPP_TAG_NAMELANG &&
+ ippGetValueTag(attr) != IPP_TAG_KEYWORD) ||
+ strcmp(ippGetString(attr, 0, NULL), "no-hold"))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "job-impressions", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER || ippGetInteger(attr, 0) < 0)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "job-name", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 ||
+ (ippGetValueTag(attr) != IPP_TAG_NAME &&
+ ippGetValueTag(attr) != IPP_TAG_NAMELANG))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+
+ ippSetGroupTag(client->request, &attr, IPP_TAG_JOB);
+ }
+ else
+ ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, "Untitled");
+
+ if ((attr = ippFindAttribute(client->request, "job-priority", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER ||
+ ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 100)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "job-sheets", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 ||
+ (ippGetValueTag(attr) != IPP_TAG_NAME &&
+ ippGetValueTag(attr) != IPP_TAG_NAMELANG &&
+ ippGetValueTag(attr) != IPP_TAG_KEYWORD) ||
+ strcmp(ippGetString(attr, 0, NULL), "none"))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "media", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 ||
+ (ippGetValueTag(attr) != IPP_TAG_NAME &&
+ ippGetValueTag(attr) != IPP_TAG_NAMELANG &&
+ ippGetValueTag(attr) != IPP_TAG_KEYWORD))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else
+ {
+ supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD);
+
+ if (!ippContainsString(supported, ippGetString(attr, 0, NULL)))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "media-col", IPP_TAG_ZERO)) != NULL)
+ {
+ ipp_t *col, /* media-col collection */
+ *size; /* media-size collection */
+ ipp_attribute_t *member, /* Member attribute */
+ *x_dim, /* x-dimension */
+ *y_dim; /* y-dimension */
+ int x_value, /* y-dimension value */
+ y_value; /* x-dimension value */
+
+ if (ippGetCount(attr) != 1 ||
+ ippGetValueTag(attr) != IPP_TAG_BEGIN_COLLECTION)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+
+ col = ippGetCollection(attr, 0);
+
+ if ((member = ippFindAttribute(col, "media-size-name", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(member) != 1 ||
+ (ippGetValueTag(member) != IPP_TAG_NAME &&
+ ippGetValueTag(member) != IPP_TAG_NAMELANG &&
+ ippGetValueTag(member) != IPP_TAG_KEYWORD))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else
+ {
+ supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD);
+
+ if (!ippContainsString(supported, ippGetString(member, 0, NULL)))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+ }
+ else if ((member = ippFindAttribute(col, "media-size", IPP_TAG_BEGIN_COLLECTION)) != NULL)
+ {
+ if (ippGetCount(member) != 1)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else
+ {
+ size = ippGetCollection(member, 0);
+
+ if ((x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(x_dim) != 1 ||
+ (y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(y_dim) != 1)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else
+ {
+ x_value = ippGetInteger(x_dim, 0);
+ y_value = ippGetInteger(y_dim, 0);
+ supported = ippFindAttribute(client->printer->attrs, "media-size-supported", IPP_TAG_BEGIN_COLLECTION);
+ count = ippGetCount(supported);
+
+ for (i = 0; i < count ; i ++)
+ {
+ size = ippGetCollection(supported, i);
+ x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_ZERO);
+ y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_ZERO);
+
+ if (ippContainsInteger(x_dim, x_value) && ippContainsInteger(y_dim, y_value))
+ break;
+ }
+
+ if (i >= count)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+ }
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "multiple-document-handling", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD ||
+ (strcmp(ippGetString(attr, 0, NULL),
+ "separate-documents-uncollated-copies") &&
+ strcmp(ippGetString(attr, 0, NULL),
+ "separate-documents-collated-copies")))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "orientation-requested", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM ||
+ ippGetInteger(attr, 0) < IPP_ORIENT_PORTRAIT ||
+ ippGetInteger(attr, 0) > IPP_ORIENT_REVERSE_PORTRAIT)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "page-ranges", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetValueTag(attr) != IPP_TAG_RANGE)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "print-quality", IPP_TAG_ZERO)) != NULL)
+ {
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM ||
+ ippGetInteger(attr, 0) < IPP_QUALITY_DRAFT ||
+ ippGetInteger(attr, 0) > IPP_QUALITY_HIGH)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "printer-resolution", IPP_TAG_ZERO)) != NULL)
+ {
+ supported = ippFindAttribute(client->printer->attrs, "printer-resolution-supported", IPP_TAG_RESOLUTION);
+
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_RESOLUTION ||
+ !supported)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else
+ {
+ int xdpi, /* Horizontal resolution for job template attribute */
+ ydpi, /* Vertical resolution for job template attribute */
+ sydpi; /* Vertical resolution for supported value */
+ ipp_res_t units, /* Units for job template attribute */
+ sunits; /* Units for supported value */
+
+ xdpi = ippGetResolution(attr, 0, &ydpi, &units);
+ count = ippGetCount(supported);
+
+ for (i = 0; i < count; i ++)
+ {
+ if (xdpi == ippGetResolution(supported, i, &sydpi, &sunits) && ydpi == sydpi && units == sunits)
+ break;
+ }
+
+ if (i >= count)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+ }
+
+ if ((attr = ippFindAttribute(client->request, "sides", IPP_TAG_ZERO)) != NULL)
+ {
+ const char *sides = ippGetString(attr, 0, NULL);
+ /* "sides" value... */
+
+ if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD)
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ else if ((supported = ippFindAttribute(client->printer->attrs, "sides-supported", IPP_TAG_KEYWORD)) != NULL)
+ {
+ if (!ippContainsString(supported, sides))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+ else if (strcmp(sides, "one-sided"))
+ {
+ respond_unsupported(client, attr);
+ valid = 0;
+ }
+ }
+
+ return (valid);
+}
diff --git a/tools/ippeveps.c b/tools/ippeveps.c
new file mode 100644
index 000000000..2bfc14eb4
--- /dev/null
+++ b/tools/ippeveps.c
@@ -0,0 +1,1138 @@
+/*
+ * Generic Adobe PostScript printer command for ippeveprinter/CUPS.
+ *
+ * Copyright © 2019 by Apple Inc.
+ *
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more
+ * information.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include "ippevecommon.h"
+#if !CUPS_LITE
+# include <cups/ppd-private.h>
+#endif /* !CUPS_LITE */
+#include <limits.h>
+
+#ifdef __APPLE__
+# define PDFTOPS CUPS_SERVERBIN "/filter/cgpdftops"
+#else
+# define PDFTOPS CUPS_SERVERBIN "/filter/pdftops"
+#endif /* __APPLE__ */
+
+
+/*
+ * Local globals...
+ */
+
+#if !CUPS_LITE
+static ppd_file_t *ppd = NULL; /* PPD file data */
+static _ppd_cache_t *ppd_cache = NULL;
+ /* IPP to PPD cache data */
+#endif /* !CUPS_LITE */
+
+
+/*
+ * Local functions...
+ */
+
+static void ascii85(const unsigned char *data, int length, int eod);
+static void dsc_header(int num_pages);
+static void dsc_page(int page);
+static void dsc_trailer(int num_pages);
+static int get_options(cups_option_t **options);
+static int jpeg_to_ps(const char *filename, int copies);
+static int pdf_to_ps(const char *filename, int copies, int num_options, cups_option_t *options);
+static int ps_to_ps(const char *filename, int copies);
+static int raster_to_ps(const char *filename);
+
+
+/*
+ * 'main()' - Main entry for PostScript printer command.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line arguments */
+ char *argv[]) /* I - Command-line arguments */
+{
+ const char *content_type, /* Content type to print */
+ *ipp_copies; /* IPP_COPIES value */
+ int copies; /* Number of copies */
+ int num_options; /* Number of options */
+ cups_option_t *options; /* Options */
+
+
+ /*
+ * Get print options...
+ */
+
+ num_options = get_options(&options);
+ if ((ipp_copies = getenv("IPP_COPIES")) != NULL)
+ copies = atoi(ipp_copies);
+ else
+ copies = 1;
+
+ /*
+ * Print it...
+ */
+
+ if (argc > 2)
+ {
+ fputs("ERROR: Too many arguments supplied, aborting.\n", stderr);
+ return (1);
+ }
+ else if ((content_type = getenv("CONTENT_TYPE")) == NULL)
+ {
+ fputs("ERROR: CONTENT_TYPE environment variable not set, aborting.\n", stderr);
+ return (1);
+ }
+ else if (!strcasecmp(content_type, "application/pdf"))
+ {
+ return (pdf_to_ps(argv[1], copies, num_options, options));
+ }
+ else if (!strcasecmp(content_type, "application/postscript"))
+ {
+ return (ps_to_ps(argv[1], copies));
+ }
+ else if (!strcasecmp(content_type, "image/jpeg"))
+ {
+ return (jpeg_to_ps(argv[1], copies));
+ }
+ else if (!strcasecmp(content_type, "image/pwg-raster") || !strcasecmp(content_type, "image/urf"))
+ {
+ return (raster_to_ps(argv[1]));
+ }
+ else
+ {
+ fprintf(stderr, "ERROR: CONTENT_TYPE %s not supported.\n", content_type);
+ return (1);
+ }
+}
+
+
+/*
+ * 'ascii85()' - Write binary data using a Base85 encoding...
+ */
+
+static void
+ascii85(const unsigned char *data, /* I - Data to write */
+ int length, /* I - Number of bytes to write */
+ int eod) /* I - 1 if this is the end, 0 otherwise */
+{
+ unsigned b = 0; /* Current 32-bit word */
+ unsigned char c[5]; /* Base-85 encoded characters */
+ static int col = 0; /* Column */
+ static unsigned char leftdata[4]; /* Leftover data at the end */
+ static int leftcount = 0; /* Size of leftover data */
+
+
+ length += leftcount;
+
+ while (length > 3)
+ {
+ switch (leftcount)
+ {
+ case 0 :
+ b = (unsigned)((((((data[0] << 8) | data[1]) << 8) | data[2]) << 8) | data[3]);
+ break;
+ case 1 :
+ b = (unsigned)((((((leftdata[0] << 8) | data[0]) << 8) | data[1]) << 8) | data[2]);
+ break;
+ case 2 :
+ b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | data[0]) << 8) | data[1]);
+ break;
+ case 3 :
+ b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | leftdata[2]) << 8) | data[0]);
+ break;
+ }
+
+ if (col >= 76)
+ {
+ col = 0;
+ putchar('\n');
+ }
+
+ if (b == 0)
+ {
+ putchar('z');
+ col ++;
+ }
+ else
+ {
+ c[4] = (b % 85) + '!';
+ b /= 85;
+ c[3] = (b % 85) + '!';
+ b /= 85;
+ c[2] = (b % 85) + '!';
+ b /= 85;
+ c[1] = (b % 85) + '!';
+ b /= 85;
+ c[0] = (unsigned char)(b + '!');
+
+ fwrite(c, 1, 5, stdout);
+ col += 5;
+ }
+
+ data += 4 - leftcount;
+ length -= 4 - leftcount;
+ leftcount = 0;
+ }
+
+ if (length > 0)
+ {
+ // Copy any remainder into the leftdata array...
+ if ((length - leftcount) > 0)
+ memcpy(leftdata + leftcount, data, (size_t)(length - leftcount));
+
+ memset(leftdata + length, 0, (size_t)(4 - length));
+
+ leftcount = length;
+ }
+
+ if (eod)
+ {
+ // Do the end-of-data dance...
+ if (col >= 76)
+ {
+ col = 0;
+ putchar('\n');
+ }
+
+ if (leftcount > 0)
+ {
+ // Write the remaining bytes as needed...
+ b = (unsigned)((((((leftdata[0] << 8) | leftdata[1]) << 8) | leftdata[2]) << 8) | leftdata[3]);
+
+ c[4] = (b % 85) + '!';
+ b /= 85;
+ c[3] = (b % 85) + '!';
+ b /= 85;
+ c[2] = (b % 85) + '!';
+ b /= 85;
+ c[1] = (b % 85) + '!';
+ b /= 85;
+ c[0] = (unsigned char)(b + '!');
+
+ fwrite(c, (size_t)(leftcount + 1), 1, stdout);
+
+ leftcount = 0;
+ }
+
+ puts("~>");
+ col = 0;
+ }
+}
+
+
+/*
+ * 'dsc_header()' - Write out a standard Document Structuring Conventions
+ * PostScript header.
+ */
+
+static void
+dsc_header(int num_pages) /* I - Number of pages or 0 if not known */
+{
+ const char *job_name = getenv("IPP_JOB_NAME");
+ /* job-name value */
+
+
+#if !CUPS_LITE
+ const char *job_id = getenv("IPP_JOB_ID");
+ /* job-id value */
+
+ ppdEmitJCL(ppd, stdout, job_id ? atoi(job_id) : 0, cupsUser(), job_name ? job_name : "Unknown");
+#endif /* !CUPS_LITE */
+
+ puts("%!PS-Adobe-3.0");
+ puts("%%LanguageLevel: 2");
+ printf("%%%%Creator: ippeveps/%d.%d.%d\n", CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR, CUPS_VERSION_PATCH);
+ if (job_name)
+ {
+ fputs("%%Title: ", stdout);
+ while (*job_name)
+ {
+ if (*job_name >= 0x20 && *job_name < 0x7f)
+ putchar(*job_name);
+ else
+ putchar('?');
+
+ job_name ++;
+ }
+ putchar('\n');
+ }
+ if (num_pages > 0)
+ printf("%%%%Pages: %d\n", num_pages);
+ else
+ puts("%%Pages: (atend)");
+ puts("%%EndComments");
+
+#if !CUPS_LITE
+ if (ppd)
+ {
+ puts("%%BeginProlog");
+ if (ppd->patches)
+ {
+ puts("%%BeginFeature: *JobPatchFile 1");
+ puts(ppd->patches);
+ puts("%%EndFeature");
+ }
+ ppdEmit(ppd, stdout, PPD_ORDER_PROLOG);
+ puts("%%EndProlog");
+
+ puts("%%BeginSetup");
+ ppdEmit(ppd, stdout, PPD_ORDER_DOCUMENT);
+ ppdEmit(ppd, stdout, PPD_ORDER_ANY);
+ puts("%%EndSetup");
+ }
+#endif /* !CUPS_LITE */
+}
+
+
+/*
+ * 'dsc_page()' - Mark the start of a page.
+ */
+
+static void
+dsc_page(int page) /* I - Page numebr (1-based) */
+{
+ printf("%%%%Page: (%d) %d\n", page, page);
+
+ fprintf(stderr, "ATTR: job-impressions-completed=%d\n", page);
+
+#if !CUPS_LITE
+ if (ppd)
+ {
+ puts("%%BeginPageSetup");
+ ppdEmit(ppd, stdout, PPD_ORDER_PAGE);
+ puts("%%EndPageSetup");
+ }
+#endif /* !CUPS_LITE */
+}
+
+
+/*
+ * 'dsc_trailer()' - Mark the end of the document.
+ */
+
+static void
+dsc_trailer(int num_pages) /* I - Number of pages */
+{
+ if (num_pages > 0)
+ {
+ puts("%%Trailer");
+ printf("%%%%Pages: %d\n", num_pages);
+ puts("%%EOF");
+ }
+
+#if !CUPS_LITE
+ if (ppd && ppd->jcl_end)
+ ppdEmitJCLEnd(ppd, stdout);
+ else
+#endif /* !CUPS_LITE */
+ putchar(0x04);
+}
+
+
+/*
+ * 'get_options()' - Get the PPD options corresponding to the IPP Job Template
+ * attributes.
+ */
+
+static int /* O - Number of options */
+get_options(cups_option_t **options) /* O - Options */
+{
+ int num_options = 0; /* Number of options */
+ const char *value; /* Option value */
+ pwg_media_t *media = NULL; /* Media mapping */
+ int num_media_col = 0; /* Number of media-col values */
+ cups_option_t *media_col = NULL; /* media-col values */
+#if !CUPS_LITE
+ const char *choice; /* PPD choice */
+#endif /* !CUPS_LITE */
+
+
+ /*
+ * No options to start...
+ */
+
+ *options = NULL;
+
+ /*
+ * Media...
+ */
+
+ if ((value = getenv("IPP_MEDIA")) == NULL)
+ if ((value = getenv("IPP_MEDIA_COL")) == NULL)
+ if ((value = getenv("IPP_MEDIA_DEFAULT")) == NULL)
+ value = getenv("IPP_MEDIA_COL_DEFAULT");
+
+ if (value)
+ {
+ if (*value == '{')
+ {
+ /*
+ * media-col value...
+ */
+
+ num_media_col = cupsParseOptions(value, 0, &media_col);
+ }
+ else
+ {
+ /*
+ * media value - map to media-col.media-size-name...
+ */
+
+ num_media_col = cupsAddOption("media-size-name", value, 0, &media_col);
+ }
+ }
+
+ if ((value = cupsGetOption("media-size-name", num_media_col, media_col)) != NULL)
+ {
+ media = pwgMediaForPWG(value);
+ }
+ else if ((value = cupsGetOption("media-size", num_media_col, media_col)) != NULL)
+ {
+ int num_media_size; /* Number of media-size values */
+ cups_option_t *media_size; /* media-size values */
+ const char *x_dimension, /* x-dimension value */
+ *y_dimension; /* y-dimension value */
+
+ num_media_size = cupsParseOptions(value, 0, &media_size);
+
+ if ((x_dimension = cupsGetOption("x-dimension", num_media_size, media_size)) != NULL && (y_dimension = cupsGetOption("y-dimension", num_media_size, media_size)) != NULL)
+ media = pwgMediaForSize(atoi(x_dimension), atoi(y_dimension));
+
+ cupsFreeOptions(num_media_size, media_size);
+ }
+
+ if (media)
+ num_options = cupsAddOption("PageSize", media->ppd, num_options, options);
+
+#if !CUPS_LITE
+ /*
+ * Load PPD file and the corresponding IPP <-> PPD cache data...
+ */
+
+ if ((ppd = ppdOpenFile(getenv("PPD"))) != NULL)
+ {
+ ppd_cache = _ppdCacheCreateWithPPD(ppd);
+
+ /* TODO: Fix me - values are names, not numbers... Also need to support finishings-col */
+ if ((value = getenv("IPP_FINISHINGS")) == NULL)
+ value = getenv("IPP_FINISHINGS_DEFAULT");
+
+ if (value)
+ {
+ char *ptr; /* Pointer into value */
+ int fin; /* Current value */
+
+ for (fin = strtol(value, &ptr, 10); fin > 0; fin = strtol(ptr + 1, &ptr, 10))
+ {
+ num_options = _ppdCacheGetFinishingOptions(ppd_cache, NULL, (ipp_finishings_t)fin, num_options, options);
+
+ if (*ptr != ',')
+ break;
+ }
+ }
+
+ if ((value = cupsGetOption("media-source", num_media_col, media_col)) != NULL)
+ {
+ if ((choice = _ppdCacheGetInputSlot(ppd_cache, NULL, value)) != NULL)
+ num_options = cupsAddOption("InputSlot", choice, num_options, options);
+ }
+
+ if ((value = cupsGetOption("media-type", num_media_col, media_col)) != NULL)
+ {
+ if ((choice = _ppdCacheGetMediaType(ppd_cache, NULL, value)) != NULL)
+ num_options = cupsAddOption("MediaType", choice, num_options, options);
+ }
+
+ if ((value = getenv("IPP_OUTPUT_BIN")) == NULL)
+ value = getenv("IPP_OUTPUT_BIN_DEFAULT");
+
+ if (value)
+ {
+ if ((choice = _ppdCacheGetOutputBin(ppd_cache, value)) != NULL)
+ num_options = cupsAddOption("OutputBin", choice, num_options, options);
+ }
+
+ if ((value = getenv("IPP_SIDES")) == NULL)
+ value = getenv("IPP_SIDES_DEFAULT");
+
+ if (value && ppd_cache->sides_option)
+ {
+ if (!strcmp(value, "one-sided") && ppd_cache->sides_1sided)
+ num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_1sided, num_options, options);
+ else if (!strcmp(value, "two-sided-long-edge") && ppd_cache->sides_2sided_long)
+ num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_2sided_long, num_options, options);
+ else if (!strcmp(value, "two-sided-short-edge") && ppd_cache->sides_2sided_short)
+ num_options = cupsAddOption(ppd_cache->sides_option, ppd_cache->sides_2sided_short, num_options, options);
+ }
+
+ if ((value = getenv("IPP_PRINT_QUALITY")) == NULL)
+ value = getenv("IPP_PRINT_QUALITY_DEFAULT");
+
+ if (value)
+ {
+ int i; /* Looping var */
+ int pq; /* Print quality (0-2) */
+ int pcm = 1; /* Print color model (0 = mono, 1 = color) */
+ int num_presets; /* Number of presets */
+ cups_option_t *presets; /* Presets */
+
+ if (!strcmp(value, "draft"))
+ pq = 0;
+ else if (!strcmp(value, "high"))
+ pq = 2;
+ else
+ pq = 1;
+
+ if ((value = getenv("IPP_PRINT_COLOR_MODE")) == NULL)
+ value = getenv("IPP_PRINT_COLOR_MODE_DEFAULT");
+
+ if (value && !strcmp(value, "monochrome"))
+ pcm = 0;
+
+ num_presets = ppd_cache->num_presets[pcm][pq];
+ presets = ppd_cache->presets[pcm][pq];
+
+ for (i = 0; i < num_presets; i ++)
+ num_options = cupsAddOption(presets[i].name, presets[i].value, num_options, options);
+ }
+
+ /*
+ * Mark the PPD with the options...
+ */
+
+ ppdMarkDefaults(ppd);
+ cupsMarkOptions(ppd, num_options, *options);
+ }
+#endif /* !CUPS_LITE */
+
+ cupsFreeOptions(num_media_col, media_col);
+
+ return (num_options);
+}
+
+
+/*
+ * 'jpeg_to_ps()' - Convert a JPEG file to PostScript.
+ */
+
+static int /* O - Exit status */
+jpeg_to_ps(const char *filename, /* I - Filename */
+ int copies) /* I - Number of copies */
+{
+ int fd; /* JPEG file descriptor */
+ int copy; /* Current copy */
+ int width = 0, /* Width */
+ height = 0, /* Height */
+ depth = 0, /* Number of colors */
+ length; /* Length of marker */
+ unsigned char buffer[65536], /* Copy buffer */
+ *bufptr, /* Pointer info buffer */
+ *bufend; /* End of buffer */
+ ssize_t bytes; /* Bytes in buffer */
+ const char *decode; /* Decode array */
+ float page_left, /* Left margin */
+ page_top, /* Top margin */
+ page_width, /* Page width in points */
+ page_height, /* Page heigth in points */
+ x_factor, /* X image scaling factor */
+ y_factor, /* Y image scaling factor */
+ page_scaling; /* Image scaling factor */
+#if !CUPS_LITE
+ ppd_size_t *page_size; /* Current page size */
+#endif /* !CUPS_LITE */
+
+
+ /*
+ * Open the input file...
+ */
+
+ if (filename)
+ {
+ if ((fd = open(filename, O_RDONLY)) < 0)
+ {
+ fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
+ return (1);
+ }
+ }
+ else
+ {
+ fd = 0;
+ copies = 1;
+ }
+
+ /*
+ * Read the JPEG dimensions...
+ */
+
+ bytes = read(fd, buffer, sizeof(buffer));
+
+ if (bytes < 3 || memcmp(buffer, "\377\330\377", 3))
+ {
+ fputs("ERROR: Not a JPEG image.\n", stderr);
+
+ if (fd > 0)
+ close(fd);
+
+ return (1);
+ }
+
+ for (bufptr = buffer + 2, bufend = buffer + bytes; bufptr < bufend;)
+ {
+ /*
+ * Scan the file for a SOFn marker, then we can get the dimensions...
+ */
+
+ if (*bufptr == 0xff)
+ {
+ bufptr ++;
+
+ if (bufptr >= bufend)
+ {
+ /*
+ * If we are at the end of the current buffer, re-fill and continue...
+ */
+
+ if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0)
+ break;
+
+ bufptr = buffer;
+ bufend = buffer + bytes;
+ }
+
+ if (*bufptr == 0xff)
+ continue;
+
+ if ((bufptr + 16) >= bufend)
+ {
+ /*
+ * Read more of the marker...
+ */
+
+ bytes = bufend - bufptr;
+
+ memmove(buffer, bufptr, (size_t)bytes);
+ bufptr = buffer;
+ bufend = buffer + bytes;
+
+ if ((bytes = read(fd, bufend, sizeof(buffer) - (size_t)bytes)) <= 0)
+ break;
+
+ bufend += bytes;
+ }
+
+ length = (size_t)((bufptr[1] << 8) | bufptr[2]);
+
+ if ((*bufptr >= 0xc0 && *bufptr <= 0xc3) || (*bufptr >= 0xc5 && *bufptr <= 0xc7) || (*bufptr >= 0xc9 && *bufptr <= 0xcb) || (*bufptr >= 0xcd && *bufptr <= 0xcf))
+ {
+ /*
+ * SOFn marker, look for dimensions...
+ */
+
+ width = (bufptr[6] << 8) | bufptr[7];
+ height = (bufptr[4] << 8) | bufptr[5];
+ depth = bufptr[8];
+ break;
+ }
+
+ /*
+ * Skip past this marker...
+ */
+
+ bufptr ++;
+ bytes = bufend - bufptr;
+
+ while (length >= bytes)
+ {
+ length -= bytes;
+
+ if ((bytes = read(fd, buffer, sizeof(buffer))) <= 0)
+ break;
+
+ bufptr = buffer;
+ bufend = buffer + bytes;
+ }
+
+ if (length > bytes)
+ break;
+
+ bufptr += length;
+ }
+ }
+
+ fprintf(stderr, "DEBUG: JPEG dimensions are %dx%dx%d\n", width, height, depth);
+
+ if (width <= 0 || height <= 0 || depth <= 0)
+ {
+ fputs("ERROR: No valid image data in JPEG file.\n", stderr);
+
+ if (fd > 0)
+ close(fd);
+
+ return (1);
+ }
+
+ fputs("ATTR: job-impressions=1\n", stderr);
+
+ /*
+ * Figure out the dimensions/scaling of the final image...
+ */
+
+#if CUPS_LITE
+ page_left = 18.0f;
+ page_top = 756.0f;
+ page_width = 576.0f;
+ page_height = 720.0f;
+
+#else
+ if ((page_size = ppdPageSize(ppd, NULL)) != NULL)
+ {
+ page_left = page_size->left;
+ page_top = page_size->top;
+ page_width = page_size->right - page_left;
+ page_height = page_top - page_size->bottom;
+ }
+ else
+ {
+ page_left = 18.0f;
+ page_top = 756.0f;
+ page_width = 576.0f;
+ page_height = 720.0f;
+ }
+#endif /* CUPS_LITE */
+
+ fprintf(stderr, "DEBUG: page_left=%.2f, page_top=%.2f, page_width=%.2f, page_height=%.2f\n", page_left, page_top, page_width, page_height);
+
+ /* TODO: Support orientation/rotation, different print-scaling modes */
+ x_factor = page_width / width;
+ y_factor = page_height / height;
+
+ if (x_factor > y_factor && (height * x_factor) <= page_height)
+ page_scaling = x_factor;
+ else
+ page_scaling = y_factor;
+
+ fprintf(stderr, "DEBUG: Scaled dimensions are %.2fx%.2f\n", width * page_scaling, height * page_scaling);
+
+ /*
+ * Write pages...
+ */
+
+ dsc_header(copies);
+
+ for (copy = 1; copy <= copies; copy ++)
+ {
+ dsc_page(copy);
+
+ if (depth == 1)
+ {
+ puts("/DeviceGray setcolorspace");
+ decode = "0 1";
+ }
+ else if (depth == 3)
+ {
+ puts("/DeviceRGB setcolorspace");
+ decode = "0 1 0 1 0 1";
+ }
+ else
+ {
+ puts("/DeviceCMYK setcolorspace");
+ decode = "0 1 0 1 0 1 0 1";
+ }
+
+ printf("gsave %.3f %.3f translate %.3f %.3f scale\n", page_left + 0.5f * (page_width - width * page_scaling), page_top - 0.5f * (page_height - height * page_scaling), page_scaling, page_scaling);
+ printf("<</ImageType 1/Width %d/Height %d/BitsPerComponent 8/ImageMatrix[1 0 0 -1 0 1]/Decode[%s]/DataSource currentfile/ASCII85Decode filter/DCTDecode filter/Interpolate true>>image\n", width, height, decode);
+
+ if (fd > 0)
+ lseek(fd, 0, SEEK_SET);
+
+ while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
+ ascii85(buffer, (int)bytes, 0);
+
+ ascii85(buffer, 0, 1);
+
+ puts("grestore showpage");
+ }
+
+ dsc_trailer(0);
+
+ return (0);
+}
+
+
+/*
+ * 'pdf_to_ps()' - Convert a PDF file to PostScript.
+ */
+
+static int /* O - Exit status */
+pdf_to_ps(const char *filename, /* I - Filename */
+ int copies, /* I - Number of copies */
+ int num_options, /* I - Number of options */
+ cups_option_t *options) /* I - options */
+{
+ int status; /* Exit status */
+ char tempfile[1024]; /* Temporary file */
+ int tempfd; /* Temporary file descriptor */
+ int pid; /* Process ID */
+ const char *pdf_argv[8]; /* Command-line arguments */
+ char pdf_options[1024]; /* Options */
+ const char *value; /* Option value */
+ const char *job_id, /* job-id value */
+ *job_name; /* job-name value */
+
+
+ /*
+ * Create a temporary file for the PostScript version...
+ */
+
+ if ((tempfd = cupsTempFd(tempfile, sizeof(tempfile))) < 0)
+ {
+ fprintf(stderr, "ERROR: Unable to create temporary file: %s\n", strerror(errno));
+ return (1);
+ }
+
+ /*
+ * Run cgpdftops or pdftops in the filter directory...
+ */
+
+ if ((value = cupsGetOption("PageSize", num_options, options)) != NULL)
+ snprintf(pdf_options, sizeof(pdf_options), "PageSize=%s", value);
+ else
+ pdf_options[0] = '\0';
+
+ if ((job_id = getenv("IPP_JOB_ID")) == NULL)
+ job_id = "42";
+ if ((job_name = getenv("IPP_JOB_NAME")) == NULL)
+ job_name = "untitled";
+
+ pdf_argv[0] = "printer";
+ pdf_argv[1] = job_id;
+ pdf_argv[2] = cupsUser();
+ pdf_argv[3] = job_name;
+ pdf_argv[4] = "1";
+ pdf_argv[5] = pdf_options;
+ pdf_argv[6] = filename;
+ pdf_argv[7] = NULL;
+
+ if ((pid = fork()) == 0)
+ {
+ /*
+ * Child comes here...
+ */
+
+ close(1);
+ dup2(tempfd, 1);
+ close(tempfd);
+
+ execv(PDFTOPS, (char * const *)pdf_argv);
+ exit(errno);
+ }
+ else if (pid < 0)
+ {
+ /*
+ * Unable to fork process...
+ */
+
+ perror("ERROR: Unable to start PDF filter");
+
+ close(tempfd);
+ unlink(tempfile);
+
+ return (1);
+ }
+ else
+ {
+ /*
+ * Wait for the filter to complete...
+ */
+
+ close(tempfd);
+
+# ifdef HAVE_WAITPID
+ while (waitpid(pid, &status, 0) < 0);
+# else
+ while (wait(&status) < 0);
+# endif /* HAVE_WAITPID */
+
+ if (status)
+ {
+ if (WIFEXITED(status))
+ fprintf(stderr, "ERROR: " PDFTOPS " exited with status %d.\n", WEXITSTATUS(status));
+ else
+ fprintf(stderr, "ERROR: " PDFTOPS " terminated with signal %d.\n", WTERMSIG(status));
+
+ unlink(tempfile);
+ return (1);
+ }
+ }
+
+ /*
+ * Copy the PostScript output from the command...
+ */
+
+ status = ps_to_ps(tempfile, copies);
+
+ unlink(tempfile);
+
+ return (status);
+}
+
+
+/*
+ * 'ps_to_ps()' - Copy PostScript to the standard output.
+ */
+
+static int /* O - Exit status */
+ps_to_ps(const char *filename, /* I - Filename */
+ int copies) /* I - Number of copies */
+{
+ FILE *fp; /* File to read from */
+ int copy, /* Current copy */
+ page, /* Current page number */
+ num_pages = 0, /* Total number of pages */
+ first_page, /* First page */
+ last_page; /* Last page */
+ const char *page_ranges; /* page-ranges option */
+ long first_pos = -1; /* Offset for first page */
+ char line[1024]; /* Line from file */
+
+
+ /*
+ * Open the print file...
+ */
+
+ if (filename)
+ {
+ if ((fp = fopen(filename, "rb")) == NULL)
+ {
+ fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
+ return (1);
+ }
+ }
+ else
+ {
+ copies = 1;
+ fp = stdin;
+ }
+
+ /*
+ * Check page ranges...
+ */
+
+ if ((page_ranges = getenv("IPP_PAGE_RANGES")) != NULL)
+ {
+ if (sscanf(page_ranges, "%d-%d", &first_page, &last_page) != 2)
+ {
+ first_page = 1;
+ last_page = INT_MAX;
+ }
+ }
+ else
+ {
+ first_page = 1;
+ last_page = INT_MAX;
+ }
+
+ /*
+ * Write the PostScript header for the document...
+ */
+
+ dsc_header(0);
+
+ first_pos = 0;
+
+ while (fgets(line, sizeof(line), fp))
+ {
+ if (!strncmp(line, "%%Page:", 7))
+ break;
+
+ first_pos = ftell(fp);
+
+ if (line[0] != '%')
+ fputs(line, stdout);
+ }
+
+ if (!strncmp(line, "%%Page:", 7))
+ {
+ for (copy = 0; copy < copies; copy ++)
+ {
+ int copy_page = 0; /* Do we copy the page data? */
+
+ if (fp != stdin)
+ fseek(fp, first_pos, SEEK_SET);
+
+ page = 0;
+ while (fgets(line, sizeof(line), fp))
+ {
+ if (!strncmp(line, "%%Page:", 7))
+ {
+ page ++;
+ copy_page = page >= first_page && page <= last_page;
+
+ if (copy_page)
+ {
+ num_pages ++;
+ dsc_page(num_pages);
+ }
+ }
+ else if (copy_page)
+ fputs(line, stdout);
+ }
+ }
+ }
+
+ dsc_trailer(num_pages);
+
+ fprintf(stderr, "ATTR: job-impressions=%d\n", num_pages / copies);
+
+ if (fp != stdin)
+ fclose(fp);
+
+ return (0);
+}
+
+
+/*
+ * 'raster_to_ps()' - Convert PWG Raster/Apple Raster to PostScript.
+ *
+ * The current implementation locally-decodes the raster data and then writes
+ * whole, non-blank lines as 1-line high images with base-85 encoding, resulting
+ * in between 10 and 20 times larger output. A alternate implementation (if it
+ * is deemed necessary) would be to implement a PostScript decode procedure that
+ * handles the modified packbits decompression so that we just have the base-85
+ * encoding overhead (25%). Furthermore, Level 3 PostScript printers also
+ * support Flate compression.
+ *
+ * That said, the most efficient path with the highest quality is for Clients
+ * to supply PDF files and us to use the existing PDF to PostScript conversion
+ * filters.
+ */
+
+static int /* O - Exit status */
+raster_to_ps(const char *filename) /* I - Filename */
+{
+ int fd; /* Input file */
+ cups_raster_t *ras; /* Raster stream */
+ cups_page_header2_t header; /* Page header */
+ int page = 0; /* Current page */
+ unsigned y; /* Current line */
+ unsigned char *line; /* Line buffer */
+ unsigned char white; /* White color */
+ const char *decode; /* Image decode array */
+
+
+ /*
+ * Open the input file...
+ */
+
+ if (filename)
+ {
+ if ((fd = open(filename, O_RDONLY)) < 0)
+ {
+ fprintf(stderr, "ERROR: Unable to open \"%s\": %s\n", filename, strerror(errno));
+ return (1);
+ }
+ }
+ else
+ {
+ fd = 0;
+ }
+
+ /*
+ * Open the raster stream and send pages...
+ */
+
+ if ((ras = cupsRasterOpen(fd, CUPS_RASTER_READ)) == NULL)
+ {
+ fputs("ERROR: Unable to read raster data, aborting.\n", stderr);
+ return (1);
+ }
+
+ dsc_header(0);
+
+ while (cupsRasterReadHeader2(ras, &header))
+ {
+ page ++;
+
+ fprintf(stderr, "DEBUG: Page %d: %ux%ux%u\n", page, header.cupsWidth, header.cupsHeight, header.cupsBitsPerPixel);
+
+ if (header.cupsColorSpace != CUPS_CSPACE_W && header.cupsColorSpace != CUPS_CSPACE_SW && header.cupsColorSpace != CUPS_CSPACE_K && header.cupsColorSpace != CUPS_CSPACE_RGB && header.cupsColorSpace != CUPS_CSPACE_SRGB)
+ {
+ fputs("ERROR: Unsupported color space, aborting.\n", stderr);
+ break;
+ }
+ else if (header.cupsBitsPerColor != 1 && header.cupsBitsPerColor != 8)
+ {
+ fputs("ERROR: Unsupported bit depth, aborting.\n", stderr);
+ break;
+ }
+
+ line = malloc(header.cupsBytesPerLine);
+
+ dsc_page(page);
+
+ puts("gsave");
+ printf("%.6f %.6f scale\n", 72.0f / header.HWResolution[0], 72.0f / header.HWResolution[1]);
+
+ switch (header.cupsColorSpace)
+ {
+ case CUPS_CSPACE_W :
+ case CUPS_CSPACE_SW :
+ decode = "0 1";
+ puts("/DeviceGray setcolorspace");
+ white = 255;
+ break;
+
+ case CUPS_CSPACE_K :
+ decode = "0 1";
+ puts("/DeviceGray setcolorspace");
+ white = 0;
+ break;
+
+ default :
+ decode = "0 1 0 1 0 1";
+ puts("/DeviceRGB setcolorspace");
+ white = 255;
+ break;
+ }
+
+ printf("gsave /L{grestore gsave 0 exch translate <</ImageType 1/Width %u/Height 1/BitsPerComponent %u/ImageMatrix[1 0 0 -1 0 1]/DataSource currentfile/ASCII85Decode filter/Decode[%s]>>image}bind def\n", header.cupsWidth, header.cupsBitsPerColor, decode);
+
+ for (y = header.cupsHeight; y > 0; y --)
+ {
+ if (cupsRasterReadPixels(ras, line, header.cupsBytesPerLine))
+ {
+ if (line[0] != white || memcmp(line, line + 1, header.cupsBytesPerLine - 1))
+ {
+ printf("%d L\n", y - 1);
+ ascii85(line, (int)header.cupsBytesPerLine, 1);
+ }
+ }
+ else
+ break;
+ }
+
+ fprintf(stderr, "DEBUG: y=%d at end...\n", y);
+
+ puts("grestore grestore");
+ puts("showpage");
+
+ free(line);
+ }
+
+ cupsRasterClose(ras);
+
+ dsc_trailer(page);
+
+ fprintf(stderr, "ATTR: job-impressions=%d\n", page);
+
+ return (0);
+}
+
+
diff --git a/tools/ippfind.c b/tools/ippfind.c
new file mode 100644
index 000000000..246ab4dba
--- /dev/null
+++ b/tools/ippfind.c
@@ -0,0 +1,2847 @@
+/*
+ * Utility to find IPP printers via Bonjour/DNS-SD and optionally run
+ * commands such as IPP and Bonjour conformance tests. This tool is
+ * inspired by the UNIX "find" command, thus its name.
+ *
+ * Copyright © 2008-2018 by Apple Inc.
+ *
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more
+ * information.
+ */
+
+/*
+ * Include necessary headers.
+ */
+
+#define _CUPS_NO_DEPRECATED
+#include <cups/cups-private.h>
+#ifdef _WIN32
+# include <process.h>
+# include <sys/timeb.h>
+#else
+# include <sys/wait.h>
+#endif /* _WIN32 */
+#include <regex.h>
+#ifdef HAVE_DNSSD
+# include <dns_sd.h>
+#elif defined(HAVE_AVAHI)
+# include <avahi-client/client.h>
+# include <avahi-client/lookup.h>
+# include <avahi-common/simple-watch.h>
+# include <avahi-common/domain.h>
+# include <avahi-common/error.h>
+# include <avahi-common/malloc.h>
+# define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
+#endif /* HAVE_DNSSD */
+
+#ifndef _WIN32
+extern char **environ; /* Process environment variables */
+#endif /* !_WIN32 */
+
+
+/*
+ * Structures...
+ */
+
+typedef enum ippfind_exit_e /* Exit codes */
+{
+ IPPFIND_EXIT_TRUE = 0, /* OK and result is true */
+ IPPFIND_EXIT_FALSE, /* OK but result is false*/
+ IPPFIND_EXIT_BONJOUR, /* Browse/resolve failure */
+ IPPFIND_EXIT_SYNTAX, /* Bad option or syntax error */
+ IPPFIND_EXIT_MEMORY /* Out of memory */
+} ippfind_exit_t;
+
+typedef enum ippfind_op_e /* Operations for expressions */
+{
+ /* "Evaluation" operations */
+ IPPFIND_OP_NONE, /* No operation */
+ IPPFIND_OP_AND, /* Logical AND of all children */
+ IPPFIND_OP_OR, /* Logical OR of all children */
+ IPPFIND_OP_TRUE, /* Always true */
+ IPPFIND_OP_FALSE, /* Always false */
+ IPPFIND_OP_IS_LOCAL, /* Is a local service */
+ IPPFIND_OP_IS_REMOTE, /* Is a remote service */
+ IPPFIND_OP_DOMAIN_REGEX, /* Domain matches regular expression */
+ IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */
+ IPPFIND_OP_NAME_LITERAL, /* Name matches literal string */
+ IPPFIND_OP_HOST_REGEX, /* Hostname matches regular expression */
+ IPPFIND_OP_PORT_RANGE, /* Port matches range */
+ IPPFIND_OP_PATH_REGEX, /* Path matches regular expression */
+ IPPFIND_OP_TXT_EXISTS, /* TXT record key exists */
+ IPPFIND_OP_TXT_REGEX, /* TXT record key matches regular expression */
+ IPPFIND_OP_URI_REGEX, /* URI matches regular expression */
+
+ /* "Output" operations */
+ IPPFIND_OP_EXEC, /* Execute when true */
+ IPPFIND_OP_LIST, /* List when true */
+ IPPFIND_OP_PRINT_NAME, /* Print URI when true */
+ IPPFIND_OP_PRINT_URI, /* Print name when true */
+ IPPFIND_OP_QUIET /* No output when true */
+} ippfind_op_t;
+
+typedef struct ippfind_expr_s /* Expression */
+{
+ struct ippfind_expr_s
+ *prev, /* Previous expression */
+ *next, /* Next expression */
+ *parent, /* Parent expressions */
+ *child; /* Child expressions */
+ ippfind_op_t op; /* Operation code (see above) */
+ int invert; /* Invert the result */
+ char *name; /* TXT record key or literal name */
+ regex_t re; /* Regular expression for matching */
+ int range[2]; /* Port number range */
+ int num_args; /* Number of arguments for exec */
+ char **args; /* Arguments for exec */
+} ippfind_expr_t;
+
+typedef struct ippfind_srv_s /* Service information */
+{
+#ifdef HAVE_DNSSD
+ DNSServiceRef ref; /* Service reference for query */
+#elif defined(HAVE_AVAHI)
+ AvahiServiceResolver *ref; /* Resolver */
+#endif /* HAVE_DNSSD */
+ char *name, /* Service name */
+ *domain, /* Domain name */
+ *regtype, /* Registration type */
+ *fullName, /* Full name */
+ *host, /* Hostname */
+ *resource, /* Resource path */
+ *uri; /* URI */
+ int num_txt; /* Number of TXT record keys */
+ cups_option_t *txt; /* TXT record keys */
+ int port, /* Port number */
+ is_local, /* Is a local service? */
+ is_processed, /* Did we process the service? */
+ is_resolved; /* Got the resolve data? */
+} ippfind_srv_t;
+
+
+/*
+ * Local globals...
+ */
+
+#ifdef HAVE_DNSSD
+static DNSServiceRef dnssd_ref; /* Master service reference */
+#elif defined(HAVE_AVAHI)
+static AvahiClient *avahi_client = NULL;/* Client information */
+static int avahi_got_data = 0; /* Got data from poll? */
+static AvahiSimplePoll *avahi_poll = NULL;
+ /* Poll information */
+#endif /* HAVE_DNSSD */
+
+static int address_family = AF_UNSPEC;
+ /* Address family for LIST */
+static int bonjour_error = 0; /* Error browsing/resolving? */
+static double bonjour_timeout = 1.0; /* Timeout in seconds */
+static int ipp_version = 20; /* IPP version for LIST */
+
+
+/*
+ * Local functions...
+ */
+
+#ifdef HAVE_DNSSD
+static void DNSSD_API browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) _CUPS_NONNULL(1,5,6,7,8);
+static void DNSSD_API browse_local_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) _CUPS_NONNULL(1,5,6,7,8);
+#elif defined(HAVE_AVAHI)
+static void browse_callback(AvahiServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *serviceName,
+ const char *regtype,
+ const char *replyDomain,
+ AvahiLookupResultFlags flags,
+ void *context);
+static void client_callback(AvahiClient *client,
+ AvahiClientState state,
+ void *context);
+#endif /* HAVE_AVAHI */
+
+static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b);
+static const char *dnssd_error_string(int error);
+static int eval_expr(ippfind_srv_t *service,
+ ippfind_expr_t *expressions);
+static int exec_program(ippfind_srv_t *service, int num_args,
+ char **args);
+static ippfind_srv_t *get_service(cups_array_t *services, const char *serviceName, const char *regtype, const char *replyDomain) _CUPS_NONNULL(1,2,3,4);
+static double get_time(void);
+static int list_service(ippfind_srv_t *service);
+static ippfind_expr_t *new_expr(ippfind_op_t op, int invert,
+ const char *value, const char *regex,
+ char **args);
+#ifdef HAVE_DNSSD
+static void DNSSD_API resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullName, const char *hostTarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, void *context) _CUPS_NONNULL(1,5,6,9, 10);
+#elif defined(HAVE_AVAHI)
+static int poll_callback(struct pollfd *pollfds,
+ unsigned int num_pollfds, int timeout,
+ void *context);
+static void resolve_callback(AvahiServiceResolver *res,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *serviceName,
+ const char *regtype,
+ const char *replyDomain,
+ const char *host_name,
+ const AvahiAddress *address,
+ uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ void *context);
+#endif /* HAVE_DNSSD */
+static void set_service_uri(ippfind_srv_t *service);
+static void show_usage(void) _CUPS_NORETURN;
+static void show_version(void) _CUPS_NORETURN;
+
+
+/*
+ * 'main()' - Browse for printers.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line args */
+ char *argv[]) /* I - Command-line arguments */
+{
+ int i, /* Looping var */
+ have_output = 0,/* Have output expression */
+ status = IPPFIND_EXIT_FALSE;
+ /* Exit status */
+ const char *opt, /* Option character */
+ *search; /* Current browse/resolve string */
+ cups_array_t *searches; /* Things to browse/resolve */
+ cups_array_t *services; /* Service array */
+ ippfind_srv_t *service; /* Current service */
+ ippfind_expr_t *expressions = NULL,
+ /* Expression tree */
+ *temp = NULL, /* New expression */
+ *parent = NULL, /* Parent expression */
+ *current = NULL,/* Current expression */
+ *parens[100]; /* Markers for parenthesis */
+ int num_parens = 0; /* Number of parenthesis */
+ ippfind_op_t logic = IPPFIND_OP_AND;
+ /* Logic for next expression */
+ int invert = 0; /* Invert expression? */
+ int err; /* DNS-SD error */
+#ifdef HAVE_DNSSD
+ fd_set sinput; /* Input set for select() */
+ struct timeval stimeout; /* Timeout for select() */
+#endif /* HAVE_DNSSD */
+ double endtime; /* End time */
+ static const char * const ops[] = /* Node operation names */
+ {
+ "NONE",
+ "AND",
+ "OR",
+ "TRUE",
+ "FALSE",
+ "IS_LOCAL",
+ "IS_REMOTE",
+ "DOMAIN_REGEX",
+ "NAME_REGEX",
+ "NAME_LITERAL",
+ "HOST_REGEX",
+ "PORT_RANGE",
+ "PATH_REGEX",
+ "TXT_EXISTS",
+ "TXT_REGEX",
+ "URI_REGEX",
+ "EXEC",
+ "LIST",
+ "PRINT_NAME",
+ "PRINT_URI",
+ "QUIET"
+ };
+
+
+ /*
+ * Initialize the locale...
+ */
+
+ _cupsSetLocale(argv);
+
+ /*
+ * Create arrays to track services and things we want to browse/resolve...
+ */
+
+ searches = cupsArrayNew(NULL, NULL);
+ services = cupsArrayNew((cups_array_func_t)compare_services, NULL);
+
+ /*
+ * Parse command-line...
+ */
+
+ if (getenv("IPPFIND_DEBUG"))
+ for (i = 1; i < argc; i ++)
+ fprintf(stderr, "argv[%d]=\"%s\"\n", i, argv[i]);
+
+ for (i = 1; i < argc; i ++)
+ {
+ if (argv[i][0] == '-')
+ {
+ if (argv[i][1] == '-')
+ {
+ /*
+ * Parse --option options...
+ */
+
+ if (!strcmp(argv[i], "--and"))
+ {
+ if (logic == IPPFIND_OP_OR)
+ {
+ _cupsLangPuts(stderr, _("ippfind: Cannot use --and after --or."));
+ show_usage();
+ }
+
+ if (!current)
+ {
+ _cupsLangPuts(stderr,
+ _("ippfind: Missing expression before \"--and\"."));
+ show_usage();
+ }
+
+ temp = NULL;
+ }
+ else if (!strcmp(argv[i], "--domain"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ "--domain");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--exec"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Expected program after %s."),
+ "--exec");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
+ argv + i)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ while (i < argc)
+ if (!strcmp(argv[i], ";"))
+ break;
+ else
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Expected semi-colon after %s."),
+ "--exec");
+ show_usage();
+ }
+
+ have_output = 1;
+ }
+ else if (!strcmp(argv[i], "--false"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_FALSE, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--help"))
+ {
+ show_usage();
+ }
+ else if (!strcmp(argv[i], "--host"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ "--host");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--ls"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ }
+ else if (!strcmp(argv[i], "--local"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_IS_LOCAL, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--literal-name"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Missing name after %s."), "--literal-name");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_NAME_LITERAL, invert, argv[i], NULL, NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--name"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ "--name");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--not"))
+ {
+ invert = 1;
+ }
+ else if (!strcmp(argv[i], "--or"))
+ {
+ if (!current)
+ {
+ _cupsLangPuts(stderr,
+ _("ippfind: Missing expression before \"--or\"."));
+ show_usage();
+ }
+
+ logic = IPPFIND_OP_OR;
+
+ if (parent && parent->op == IPPFIND_OP_OR)
+ {
+ /*
+ * Already setup to do "foo --or bar --or baz"...
+ */
+
+ temp = NULL;
+ }
+ else if (!current->prev && parent)
+ {
+ /*
+ * Change parent node into an OR node...
+ */
+
+ parent->op = IPPFIND_OP_OR;
+ temp = NULL;
+ }
+ else if (!current->prev)
+ {
+ /*
+ * Need to group "current" in a new OR node...
+ */
+
+ if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ temp->parent = parent;
+ temp->child = current;
+ current->parent = temp;
+
+ if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
+
+ parent = temp;
+ temp = NULL;
+ }
+ else
+ {
+ /*
+ * Need to group previous expressions in an AND node, and then
+ * put that in an OR node...
+ */
+
+ if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ while (current->prev)
+ {
+ current->parent = temp;
+ current = current->prev;
+ }
+
+ current->parent = temp;
+ temp->child = current;
+ current = temp;
+
+ if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ temp->parent = parent;
+ current->parent = temp;
+
+ if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
+
+ parent = temp;
+ temp = NULL;
+ }
+ }
+ else if (!strcmp(argv[i], "--path"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ "--path");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--port"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Expected port range after %s."),
+ "--port");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--print"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ }
+ else if (!strcmp(argv[i], "--print-name"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ }
+ else if (!strcmp(argv[i], "--quiet"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ }
+ else if (!strcmp(argv[i], "--remote"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--true"))
+ {
+ if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--txt"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Expected key name after %s."),
+ "--txt");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strncmp(argv[i], "--txt-", 6))
+ {
+ const char *key = argv[i] + 6;/* TXT key */
+
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ argv[i - 1]);
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_TXT_REGEX, invert, key, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--uri"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ "--uri");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--version"))
+ {
+ show_version();
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."),
+ "ippfind", argv[i]);
+ show_usage();
+ }
+
+ if (temp)
+ {
+ /*
+ * Add new expression...
+ */
+
+ if (logic == IPPFIND_OP_AND &&
+ current && current->prev &&
+ parent && parent->op != IPPFIND_OP_AND)
+ {
+ /*
+ * Need to re-group "current" in a new AND node...
+ */
+
+ ippfind_expr_t *tempand; /* Temporary AND node */
+
+ if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ /*
+ * Replace "current" with new AND node at the end of this list...
+ */
+
+ current->prev->next = tempand;
+ tempand->prev = current->prev;
+ tempand->parent = parent;
+
+ /*
+ * Add "current to the new AND node...
+ */
+
+ tempand->child = current;
+ current->parent = tempand;
+ current->prev = NULL;
+ parent = tempand;
+ }
+
+ /*
+ * Add the new node at current level...
+ */
+
+ temp->parent = parent;
+ temp->prev = current;
+
+ if (current)
+ current->next = temp;
+ else if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
+
+ current = temp;
+ invert = 0;
+ logic = IPPFIND_OP_AND;
+ temp = NULL;
+ }
+ }
+ else
+ {
+ /*
+ * Parse -o options
+ */
+
+ for (opt = argv[i] + 1; *opt; opt ++)
+ {
+ switch (*opt)
+ {
+ case '4' :
+ address_family = AF_INET;
+ break;
+
+ case '6' :
+ address_family = AF_INET6;
+ break;
+
+ case 'N' : /* Literal name */
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Missing name after %s."), "-N");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_NAME_LITERAL, invert, argv[i], NULL, NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 'P' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Expected port range after %s."),
+ "-P");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i],
+ NULL, NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 'T' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("%s: Missing timeout for \"-T\"."),
+ "ippfind");
+ show_usage();
+ }
+
+ bonjour_timeout = atof(argv[i]);
+ break;
+
+ case 'V' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("%s: Missing version for \"-V\"."),
+ "ippfind");
+ show_usage();
+ }
+
+ if (!strcmp(argv[i], "1.1"))
+ ipp_version = 11;
+ else if (!strcmp(argv[i], "2.0"))
+ ipp_version = 20;
+ else if (!strcmp(argv[i], "2.1"))
+ ipp_version = 21;
+ else if (!strcmp(argv[i], "2.2"))
+ ipp_version = 22;
+ else
+ {
+ _cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."),
+ "ippfind", argv[i]);
+ show_usage();
+ }
+ break;
+
+ case 'd' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after "
+ "%s."), "-d");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL,
+ argv[i], NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 'h' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after "
+ "%s."), "-h");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL,
+ argv[i], NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 'l' :
+ if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ break;
+
+ case 'n' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after "
+ "%s."), "-n");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL,
+ argv[i], NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 'p' :
+ if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ break;
+
+ case 'q' :
+ if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ break;
+
+ case 'r' :
+ if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 's' :
+ if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ have_output = 1;
+ break;
+
+ case 't' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing key name after %s."),
+ "-t");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i],
+ NULL, NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 'u' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after "
+ "%s."), "-u");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL,
+ argv[i], NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ break;
+
+ case 'x' :
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing program after %s."),
+ "-x");
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
+ argv + i)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ while (i < argc)
+ if (!strcmp(argv[i], ";"))
+ break;
+ else
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing semi-colon after %s."),
+ "-x");
+ show_usage();
+ }
+
+ have_output = 1;
+ break;
+
+ default :
+ _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."),
+ "ippfind", *opt);
+ show_usage();
+ }
+
+ if (temp)
+ {
+ /*
+ * Add new expression...
+ */
+
+ if (logic == IPPFIND_OP_AND &&
+ current && current->prev &&
+ parent && parent->op != IPPFIND_OP_AND)
+ {
+ /*
+ * Need to re-group "current" in a new AND node...
+ */
+
+ ippfind_expr_t *tempand; /* Temporary AND node */
+
+ if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ /*
+ * Replace "current" with new AND node at the end of this list...
+ */
+
+ current->prev->next = tempand;
+ tempand->prev = current->prev;
+ tempand->parent = parent;
+
+ /*
+ * Add "current to the new AND node...
+ */
+
+ tempand->child = current;
+ current->parent = tempand;
+ current->prev = NULL;
+ parent = tempand;
+ }
+
+ /*
+ * Add the new node at current level...
+ */
+
+ temp->parent = parent;
+ temp->prev = current;
+
+ if (current)
+ current->next = temp;
+ else if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
+
+ current = temp;
+ invert = 0;
+ logic = IPPFIND_OP_AND;
+ temp = NULL;
+ }
+ }
+ }
+ }
+ else if (!strcmp(argv[i], "("))
+ {
+ if (num_parens >= 100)
+ {
+ _cupsLangPuts(stderr, _("ippfind: Too many parenthesis."));
+ show_usage();
+ }
+
+ if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ parens[num_parens++] = temp;
+
+ if (current)
+ {
+ temp->parent = current->parent;
+ current->next = temp;
+ temp->prev = current;
+ }
+ else
+ expressions = temp;
+
+ parent = temp;
+ current = NULL;
+ invert = 0;
+ logic = IPPFIND_OP_AND;
+ }
+ else if (!strcmp(argv[i], ")"))
+ {
+ if (num_parens <= 0)
+ {
+ _cupsLangPuts(stderr, _("ippfind: Missing open parenthesis."));
+ show_usage();
+ }
+
+ current = parens[--num_parens];
+ parent = current->parent;
+ invert = 0;
+ logic = IPPFIND_OP_AND;
+ }
+ else if (!strcmp(argv[i], "!"))
+ {
+ invert = 1;
+ }
+ else
+ {
+ /*
+ * _regtype._tcp[,subtype][.domain]
+ *
+ * OR
+ *
+ * service-name[._regtype._tcp[.domain]]
+ */
+
+ cupsArrayAdd(searches, argv[i]);
+ }
+ }
+
+ if (num_parens > 0)
+ {
+ _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis."));
+ show_usage();
+ }
+
+ if (!have_output)
+ {
+ /*
+ * Add an implicit --print-uri to the end...
+ */
+
+ if ((temp = new_expr(IPPFIND_OP_PRINT_URI, 0, NULL, NULL, NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+
+ if (current)
+ {
+ while (current->parent)
+ current = current->parent;
+
+ current->next = temp;
+ temp->prev = current;
+ }
+ else
+ expressions = temp;
+ }
+
+ if (cupsArrayCount(searches) == 0)
+ {
+ /*
+ * Add an implicit browse for IPP printers ("_ipp._tcp")...
+ */
+
+ cupsArrayAdd(searches, "_ipp._tcp");
+ }
+
+ if (getenv("IPPFIND_DEBUG"))
+ {
+ int indent = 4; /* Indentation */
+
+ puts("Expression tree:");
+ current = expressions;
+ while (current)
+ {
+ /*
+ * Print the current node...
+ */
+
+ printf("%*s%s%s\n", indent, "", current->invert ? "!" : "",
+ ops[current->op]);
+
+ /*
+ * Advance to the next node...
+ */
+
+ if (current->child)
+ {
+ current = current->child;
+ indent += 4;
+ }
+ else if (current->next)
+ current = current->next;
+ else if (current->parent)
+ {
+ while (current->parent)
+ {
+ indent -= 4;
+ current = current->parent;
+ if (current->next)
+ break;
+ }
+
+ current = current->next;
+ }
+ else
+ current = NULL;
+ }
+
+ puts("\nSearch items:");
+ for (search = (const char *)cupsArrayFirst(searches);
+ search;
+ search = (const char *)cupsArrayNext(searches))
+ printf(" %s\n", search);
+ }
+
+ /*
+ * Start up browsing/resolving...
+ */
+
+#ifdef HAVE_DNSSD
+ if ((err = DNSServiceCreateConnection(&dnssd_ref)) != kDNSServiceErr_NoError)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
+ dnssd_error_string(err));
+ return (IPPFIND_EXIT_BONJOUR);
+ }
+
+#elif defined(HAVE_AVAHI)
+ if ((avahi_poll = avahi_simple_poll_new()) == NULL)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
+ strerror(errno));
+ return (IPPFIND_EXIT_BONJOUR);
+ }
+
+ avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL);
+
+ avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll),
+ 0, client_callback, avahi_poll, &err);
+ if (!avahi_client)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
+ dnssd_error_string(err));
+ return (IPPFIND_EXIT_BONJOUR);
+ }
+#endif /* HAVE_DNSSD */
+
+ for (search = (const char *)cupsArrayFirst(searches);
+ search;
+ search = (const char *)cupsArrayNext(searches))
+ {
+ char buf[1024], /* Full name string */
+ *name = NULL, /* Service instance name */
+ *regtype, /* Registration type */
+ *domain; /* Domain, if any */
+
+ strlcpy(buf, search, sizeof(buf));
+
+ if (!strncmp(buf, "_http._", 7) || !strncmp(buf, "_https._", 8) || !strncmp(buf, "_ipp._", 6) || !strncmp(buf, "_ipps._", 7))
+ {
+ regtype = buf;
+ }
+ else if ((regtype = strstr(buf, "._")) != NULL)
+ {
+ if (strcmp(regtype, "._tcp"))
+ {
+ /*
+ * "something._protocol._tcp" -> search for something with the given
+ * protocol...
+ */
+
+ name = buf;
+ *regtype++ = '\0';
+ }
+ else
+ {
+ /*
+ * "_protocol._tcp" -> search for everything with the given protocol...
+ */
+
+ /* name = NULL; */
+ regtype = buf;
+ }
+ }
+ else
+ {
+ /*
+ * "something" -> search for something with IPP protocol...
+ */
+
+ name = buf;
+ regtype = "_ipp._tcp";
+ }
+
+ for (domain = regtype; *domain; domain ++)
+ {
+ if (*domain == '.' && domain[1] != '_')
+ {
+ *domain++ = '\0';
+ break;
+ }
+ }
+
+ if (!*domain)
+ domain = NULL;
+
+ if (name)
+ {
+ /*
+ * Resolve the given service instance name, regtype, and domain...
+ */
+
+ if (!domain)
+ domain = "local.";
+
+ service = get_service(services, name, regtype, domain);
+
+ if (getenv("IPPFIND_DEBUG"))
+ fprintf(stderr, "Resolving name=\"%s\", regtype=\"%s\", domain=\"%s\"\n", name, regtype, domain);
+
+#ifdef HAVE_DNSSD
+ service->ref = dnssd_ref;
+ err = DNSServiceResolve(&(service->ref),
+ kDNSServiceFlagsShareConnection, 0, name,
+ regtype, domain, resolve_callback,
+ service);
+
+#elif defined(HAVE_AVAHI)
+ service->ref = avahi_service_resolver_new(avahi_client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, name,
+ regtype, domain,
+ AVAHI_PROTO_UNSPEC, 0,
+ resolve_callback, service);
+ if (service->ref)
+ err = 0;
+ else
+ err = avahi_client_errno(avahi_client);
+#endif /* HAVE_DNSSD */
+ }
+ else
+ {
+ /*
+ * Browse for services of the given type...
+ */
+
+ if (getenv("IPPFIND_DEBUG"))
+ fprintf(stderr, "Browsing for regtype=\"%s\", domain=\"%s\"\n", regtype, domain);
+
+#ifdef HAVE_DNSSD
+ DNSServiceRef ref; /* Browse reference */
+
+ ref = dnssd_ref;
+ err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 0, regtype,
+ domain, browse_callback, services);
+
+ if (!err)
+ {
+ ref = dnssd_ref;
+ err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection,
+ kDNSServiceInterfaceIndexLocalOnly, regtype,
+ domain, browse_local_callback, services);
+ }
+
+#elif defined(HAVE_AVAHI)
+ if (avahi_service_browser_new(avahi_client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, regtype, domain, 0,
+ browse_callback, services))
+ err = 0;
+ else
+ err = avahi_client_errno(avahi_client);
+#endif /* HAVE_DNSSD */
+ }
+
+ if (err)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
+ dnssd_error_string(err));
+
+ return (IPPFIND_EXIT_BONJOUR);
+ }
+ }
+
+ /*
+ * Process browse/resolve requests...
+ */
+
+ if (bonjour_timeout > 1.0)
+ endtime = get_time() + bonjour_timeout;
+ else
+ endtime = get_time() + 300.0;
+
+ while (get_time() < endtime)
+ {
+ int process = 0; /* Process services? */
+
+#ifdef HAVE_DNSSD
+ int fd = DNSServiceRefSockFD(dnssd_ref);
+ /* File descriptor for DNS-SD */
+
+ FD_ZERO(&sinput);
+ FD_SET(fd, &sinput);
+
+ stimeout.tv_sec = 0;
+ stimeout.tv_usec = 500000;
+
+ if (select(fd + 1, &sinput, NULL, NULL, &stimeout) < 0)
+ continue;
+
+ if (FD_ISSET(fd, &sinput))
+ {
+ /*
+ * Process responses...
+ */
+
+ DNSServiceProcessResult(dnssd_ref);
+ }
+ else
+ {
+ /*
+ * Time to process services...
+ */
+
+ process = 1;
+ }
+
+#elif defined(HAVE_AVAHI)
+ avahi_got_data = 0;
+
+ if (avahi_simple_poll_iterate(avahi_poll, 500) > 0)
+ {
+ /*
+ * We've been told to exit the loop. Perhaps the connection to
+ * Avahi failed.
+ */
+
+ return (IPPFIND_EXIT_BONJOUR);
+ }
+
+ if (!avahi_got_data)
+ {
+ /*
+ * Time to process services...
+ */
+
+ process = 1;
+ }
+#endif /* HAVE_DNSSD */
+
+ if (process)
+ {
+ /*
+ * Process any services that we have found...
+ */
+
+ int active = 0, /* Number of active resolves */
+ resolved = 0, /* Number of resolved services */
+ processed = 0; /* Number of processed services */
+
+ for (service = (ippfind_srv_t *)cupsArrayFirst(services);
+ service;
+ service = (ippfind_srv_t *)cupsArrayNext(services))
+ {
+ if (service->is_processed)
+ processed ++;
+
+ if (service->is_resolved)
+ resolved ++;
+
+ if (!service->ref && !service->is_resolved)
+ {
+ /*
+ * Found a service, now resolve it (but limit to 50 active resolves...)
+ */
+
+ if (active < 50)
+ {
+#ifdef HAVE_DNSSD
+ service->ref = dnssd_ref;
+ err = DNSServiceResolve(&(service->ref),
+ kDNSServiceFlagsShareConnection, 0,
+ service->name, service->regtype,
+ service->domain, resolve_callback,
+ service);
+
+#elif defined(HAVE_AVAHI)
+ service->ref = avahi_service_resolver_new(avahi_client,
+ AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ service->name,
+ service->regtype,
+ service->domain,
+ AVAHI_PROTO_UNSPEC, 0,
+ resolve_callback,
+ service);
+ if (service->ref)
+ err = 0;
+ else
+ err = avahi_client_errno(avahi_client);
+#endif /* HAVE_DNSSD */
+
+ if (err)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Unable to browse or resolve: %s"),
+ dnssd_error_string(err));
+ return (IPPFIND_EXIT_BONJOUR);
+ }
+
+ active ++;
+ }
+ }
+ else if (service->is_resolved && !service->is_processed)
+ {
+ /*
+ * Resolved, not process this service against the expressions...
+ */
+
+ if (service->ref)
+ {
+#ifdef HAVE_DNSSD
+ DNSServiceRefDeallocate(service->ref);
+#else
+ avahi_service_resolver_free(service->ref);
+#endif /* HAVE_DNSSD */
+
+ service->ref = NULL;
+ }
+
+ if (eval_expr(service, expressions))
+ status = IPPFIND_EXIT_TRUE;
+
+ service->is_processed = 1;
+ }
+ else if (service->ref)
+ active ++;
+ }
+
+ /*
+ * If we have processed all services we have discovered, then we are done.
+ */
+
+ if (processed == cupsArrayCount(services) && bonjour_timeout <= 1.0)
+ break;
+ }
+ }
+
+ if (bonjour_error)
+ return (IPPFIND_EXIT_BONJOUR);
+ else
+ return (status);
+}
+
+
+#ifdef HAVE_DNSSD
+/*
+ * 'browse_callback()' - Browse devices.
+ */
+
+static void DNSSD_API
+browse_callback(
+ DNSServiceRef sdRef, /* I - Service reference */
+ DNSServiceFlags flags, /* I - Option flags */
+ uint32_t interfaceIndex, /* I - Interface number */
+ DNSServiceErrorType errorCode, /* I - Error, if any */
+ const char *serviceName, /* I - Name of service/device */
+ const char *regtype, /* I - Type of service */
+ const char *replyDomain, /* I - Service domain */
+ void *context) /* I - Services array */
+{
+ /*
+ * Only process "add" data...
+ */
+
+ (void)sdRef;
+ (void)interfaceIndex;
+
+ if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
+ return;
+
+ /*
+ * Get the device...
+ */
+
+ get_service((cups_array_t *)context, serviceName, regtype, replyDomain);
+}
+
+
+/*
+ * 'browse_local_callback()' - Browse local devices.
+ */
+
+static void DNSSD_API
+browse_local_callback(
+ DNSServiceRef sdRef, /* I - Service reference */
+ DNSServiceFlags flags, /* I - Option flags */
+ uint32_t interfaceIndex, /* I - Interface number */
+ DNSServiceErrorType errorCode, /* I - Error, if any */
+ const char *serviceName, /* I - Name of service/device */
+ const char *regtype, /* I - Type of service */
+ const char *replyDomain, /* I - Service domain */
+ void *context) /* I - Services array */
+{
+ ippfind_srv_t *service; /* Service */
+
+
+ /*
+ * Only process "add" data...
+ */
+
+ (void)sdRef;
+ (void)interfaceIndex;
+
+ if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
+ return;
+
+ /*
+ * Get the device...
+ */
+
+ service = get_service((cups_array_t *)context, serviceName, regtype,
+ replyDomain);
+ service->is_local = 1;
+}
+#endif /* HAVE_DNSSD */
+
+
+#ifdef HAVE_AVAHI
+/*
+ * 'browse_callback()' - Browse devices.
+ */
+
+static void
+browse_callback(
+ AvahiServiceBrowser *browser, /* I - Browser */
+ AvahiIfIndex interface, /* I - Interface index (unused) */
+ AvahiProtocol protocol, /* I - Network protocol (unused) */
+ AvahiBrowserEvent event, /* I - What happened */
+ const char *name, /* I - Service name */
+ const char *type, /* I - Registration type */
+ const char *domain, /* I - Domain */
+ AvahiLookupResultFlags flags, /* I - Flags */
+ void *context) /* I - Services array */
+{
+ AvahiClient *client = avahi_service_browser_get_client(browser);
+ /* Client information */
+ ippfind_srv_t *service; /* Service information */
+
+
+ (void)interface;
+ (void)protocol;
+ (void)context;
+
+ switch (event)
+ {
+ case AVAHI_BROWSER_FAILURE:
+ fprintf(stderr, "DEBUG: browse_callback: %s\n",
+ avahi_strerror(avahi_client_errno(client)));
+ bonjour_error = 1;
+ avahi_simple_poll_quit(avahi_poll);
+ break;
+
+ case AVAHI_BROWSER_NEW:
+ /*
+ * This object is new on the network. Create a device entry for it if
+ * it doesn't yet exist.
+ */
+
+ service = get_service((cups_array_t *)context, name, type, domain);
+
+ if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
+ service->is_local = 1;
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ break;
+ }
+}
+
+
+/*
+ * 'client_callback()' - Avahi client callback function.
+ */
+
+static void
+client_callback(
+ AvahiClient *client, /* I - Client information (unused) */
+ AvahiClientState state, /* I - Current state */
+ void *context) /* I - User data (unused) */
+{
+ (void)client;
+ (void)context;
+
+ /*
+ * If the connection drops, quit.
+ */
+
+ if (state == AVAHI_CLIENT_FAILURE)
+ {
+ fputs("DEBUG: Avahi connection failed.\n", stderr);
+ bonjour_error = 1;
+ avahi_simple_poll_quit(avahi_poll);
+ }
+}
+#endif /* HAVE_AVAHI */
+
+
+/*
+ * 'compare_services()' - Compare two devices.
+ */
+
+static int /* O - Result of comparison */
+compare_services(ippfind_srv_t *a, /* I - First device */
+ ippfind_srv_t *b) /* I - Second device */
+{
+ return (strcmp(a->name, b->name));
+}
+
+
+/*
+ * 'dnssd_error_string()' - Return an error string for an error code.
+ */
+
+static const char * /* O - Error message */
+dnssd_error_string(int error) /* I - Error number */
+{
+# ifdef HAVE_DNSSD
+ switch (error)
+ {
+ case kDNSServiceErr_NoError :
+ return ("OK.");
+
+ default :
+ case kDNSServiceErr_Unknown :
+ return ("Unknown error.");
+
+ case kDNSServiceErr_NoSuchName :
+ return ("Service not found.");
+
+ case kDNSServiceErr_NoMemory :
+ return ("Out of memory.");
+
+ case kDNSServiceErr_BadParam :
+ return ("Bad parameter.");
+
+ case kDNSServiceErr_BadReference :
+ return ("Bad service reference.");
+
+ case kDNSServiceErr_BadState :
+ return ("Bad state.");
+
+ case kDNSServiceErr_BadFlags :
+ return ("Bad flags.");
+
+ case kDNSServiceErr_Unsupported :
+ return ("Unsupported.");
+
+ case kDNSServiceErr_NotInitialized :
+ return ("Not initialized.");
+
+ case kDNSServiceErr_AlreadyRegistered :
+ return ("Already registered.");
+
+ case kDNSServiceErr_NameConflict :
+ return ("Name conflict.");
+
+ case kDNSServiceErr_Invalid :
+ return ("Invalid name.");
+
+ case kDNSServiceErr_Firewall :
+ return ("Firewall prevents registration.");
+
+ case kDNSServiceErr_Incompatible :
+ return ("Client library incompatible.");
+
+ case kDNSServiceErr_BadInterfaceIndex :
+ return ("Bad interface index.");
+
+ case kDNSServiceErr_Refused :
+ return ("Server prevents registration.");
+
+ case kDNSServiceErr_NoSuchRecord :
+ return ("Record not found.");
+
+ case kDNSServiceErr_NoAuth :
+ return ("Authentication required.");
+
+ case kDNSServiceErr_NoSuchKey :
+ return ("Encryption key not found.");
+
+ case kDNSServiceErr_NATTraversal :
+ return ("Unable to traverse NAT boundary.");
+
+ case kDNSServiceErr_DoubleNAT :
+ return ("Unable to traverse double-NAT boundary.");
+
+ case kDNSServiceErr_BadTime :
+ return ("Bad system time.");
+
+ case kDNSServiceErr_BadSig :
+ return ("Bad signature.");
+
+ case kDNSServiceErr_BadKey :
+ return ("Bad encryption key.");
+
+ case kDNSServiceErr_Transient :
+ return ("Transient error occurred - please try again.");
+
+ case kDNSServiceErr_ServiceNotRunning :
+ return ("Server not running.");
+
+ case kDNSServiceErr_NATPortMappingUnsupported :
+ return ("NAT doesn't support NAT-PMP or UPnP.");
+
+ case kDNSServiceErr_NATPortMappingDisabled :
+ return ("NAT supports NAT-PNP or UPnP but it is disabled.");
+
+ case kDNSServiceErr_NoRouter :
+ return ("No Internet/default router configured.");
+
+ case kDNSServiceErr_PollingMode :
+ return ("Service polling mode error.");
+
+#ifndef _WIN32
+ case kDNSServiceErr_Timeout :
+ return ("Service timeout.");
+#endif /* !_WIN32 */
+ }
+
+# elif defined(HAVE_AVAHI)
+ return (avahi_strerror(error));
+# endif /* HAVE_DNSSD */
+}
+
+
+/*
+ * 'eval_expr()' - Evaluate the expressions against the specified service.
+ *
+ * Returns 1 for true and 0 for false.
+ */
+
+static int /* O - Result of evaluation */
+eval_expr(ippfind_srv_t *service, /* I - Service */
+ ippfind_expr_t *expressions) /* I - Expressions */
+{
+ ippfind_op_t logic; /* Logical operation */
+ int result; /* Result of current expression */
+ ippfind_expr_t *expression; /* Current expression */
+ const char *val; /* TXT value */
+
+ /*
+ * Loop through the expressions...
+ */
+
+ if (expressions && expressions->parent)
+ logic = expressions->parent->op;
+ else
+ logic = IPPFIND_OP_AND;
+
+ for (expression = expressions; expression; expression = expression->next)
+ {
+ switch (expression->op)
+ {
+ default :
+ case IPPFIND_OP_AND :
+ case IPPFIND_OP_OR :
+ if (expression->child)
+ result = eval_expr(service, expression->child);
+ else
+ result = expression->op == IPPFIND_OP_AND;
+ break;
+ case IPPFIND_OP_TRUE :
+ result = 1;
+ break;
+ case IPPFIND_OP_FALSE :
+ result = 0;
+ break;
+ case IPPFIND_OP_IS_LOCAL :
+ result = service->is_local;
+ break;
+ case IPPFIND_OP_IS_REMOTE :
+ result = !service->is_local;
+ break;
+ case IPPFIND_OP_DOMAIN_REGEX :
+ result = !regexec(&(expression->re), service->domain, 0, NULL, 0);
+ break;
+ case IPPFIND_OP_NAME_REGEX :
+ result = !regexec(&(expression->re), service->name, 0, NULL, 0);
+ break;
+ case IPPFIND_OP_NAME_LITERAL :
+ result = !_cups_strcasecmp(expression->name, service->name);
+ break;
+ case IPPFIND_OP_HOST_REGEX :
+ result = !regexec(&(expression->re), service->host, 0, NULL, 0);
+ break;
+ case IPPFIND_OP_PORT_RANGE :
+ result = service->port >= expression->range[0] &&
+ service->port <= expression->range[1];
+ break;
+ case IPPFIND_OP_PATH_REGEX :
+ result = !regexec(&(expression->re), service->resource, 0, NULL, 0);
+ break;
+ case IPPFIND_OP_TXT_EXISTS :
+ result = cupsGetOption(expression->name, service->num_txt,
+ service->txt) != NULL;
+ break;
+ case IPPFIND_OP_TXT_REGEX :
+ val = cupsGetOption(expression->name, service->num_txt,
+ service->txt);
+ if (val)
+ result = !regexec(&(expression->re), val, 0, NULL, 0);
+ else
+ result = 0;
+
+ if (getenv("IPPFIND_DEBUG"))
+ printf("TXT_REGEX of \"%s\": %d\n", val, result);
+ break;
+ case IPPFIND_OP_URI_REGEX :
+ result = !regexec(&(expression->re), service->uri, 0, NULL, 0);
+ break;
+ case IPPFIND_OP_EXEC :
+ result = exec_program(service, expression->num_args,
+ expression->args);
+ break;
+ case IPPFIND_OP_LIST :
+ result = list_service(service);
+ break;
+ case IPPFIND_OP_PRINT_NAME :
+ _cupsLangPuts(stdout, service->name);
+ result = 1;
+ break;
+ case IPPFIND_OP_PRINT_URI :
+ _cupsLangPuts(stdout, service->uri);
+ result = 1;
+ break;
+ case IPPFIND_OP_QUIET :
+ result = 1;
+ break;
+ }
+
+ if (expression->invert)
+ result = !result;
+
+ if (logic == IPPFIND_OP_AND && !result)
+ return (0);
+ else if (logic == IPPFIND_OP_OR && result)
+ return (1);
+ }
+
+ return (logic == IPPFIND_OP_AND);
+}
+
+
+/*
+ * 'exec_program()' - Execute a program for a service.
+ */
+
+static int /* O - 1 if program terminated
+ successfully, 0 otherwise. */
+exec_program(ippfind_srv_t *service, /* I - Service */
+ int num_args, /* I - Number of command-line args */
+ char **args) /* I - Command-line arguments */
+{
+ char **myargv, /* Command-line arguments */
+ **myenvp, /* Environment variables */
+ *ptr, /* Pointer into variable */
+ domain[1024], /* IPPFIND_SERVICE_DOMAIN */
+ hostname[1024], /* IPPFIND_SERVICE_HOSTNAME */
+ name[256], /* IPPFIND_SERVICE_NAME */
+ port[32], /* IPPFIND_SERVICE_PORT */
+ regtype[256], /* IPPFIND_SERVICE_REGTYPE */
+ scheme[128], /* IPPFIND_SERVICE_SCHEME */
+ uri[1024], /* IPPFIND_SERVICE_URI */
+ txt[100][256]; /* IPPFIND_TXT_foo */
+ int i, /* Looping var */
+ myenvc, /* Number of environment variables */
+ status; /* Exit status of program */
+#ifndef _WIN32
+ char program[1024]; /* Program to execute */
+ int pid; /* Process ID */
+#endif /* !_WIN32 */
+
+
+ /*
+ * Environment variables...
+ */
+
+ snprintf(domain, sizeof(domain), "IPPFIND_SERVICE_DOMAIN=%s",
+ service->domain);
+ snprintf(hostname, sizeof(hostname), "IPPFIND_SERVICE_HOSTNAME=%s",
+ service->host);
+ snprintf(name, sizeof(name), "IPPFIND_SERVICE_NAME=%s", service->name);
+ snprintf(port, sizeof(port), "IPPFIND_SERVICE_PORT=%d", service->port);
+ snprintf(regtype, sizeof(regtype), "IPPFIND_SERVICE_REGTYPE=%s",
+ service->regtype);
+ snprintf(scheme, sizeof(scheme), "IPPFIND_SERVICE_SCHEME=%s",
+ !strncmp(service->regtype, "_http._tcp", 10) ? "http" :
+ !strncmp(service->regtype, "_https._tcp", 11) ? "https" :
+ !strncmp(service->regtype, "_ipp._tcp", 9) ? "ipp" :
+ !strncmp(service->regtype, "_ipps._tcp", 10) ? "ipps" : "lpd");
+ snprintf(uri, sizeof(uri), "IPPFIND_SERVICE_URI=%s", service->uri);
+ for (i = 0; i < service->num_txt && i < 100; i ++)
+ {
+ snprintf(txt[i], sizeof(txt[i]), "IPPFIND_TXT_%s=%s", service->txt[i].name,
+ service->txt[i].value);
+ for (ptr = txt[i] + 12; *ptr && *ptr != '='; ptr ++)
+ *ptr = (char)_cups_toupper(*ptr);
+ }
+
+ for (i = 0, myenvc = 7 + service->num_txt; environ[i]; i ++)
+ if (strncmp(environ[i], "IPPFIND_", 8))
+ myenvc ++;
+
+ if ((myenvp = calloc(sizeof(char *), (size_t)(myenvc + 1))) == NULL)
+ {
+ _cupsLangPuts(stderr, _("ippfind: Out of memory."));
+ exit(IPPFIND_EXIT_MEMORY);
+ }
+
+ for (i = 0, myenvc = 0; environ[i]; i ++)
+ if (strncmp(environ[i], "IPPFIND_", 8))
+ myenvp[myenvc++] = environ[i];
+
+ myenvp[myenvc++] = domain;
+ myenvp[myenvc++] = hostname;
+ myenvp[myenvc++] = name;
+ myenvp[myenvc++] = port;
+ myenvp[myenvc++] = regtype;
+ myenvp[myenvc++] = scheme;
+ myenvp[myenvc++] = uri;
+
+ for (i = 0; i < service->num_txt && i < 100; i ++)
+ myenvp[myenvc++] = txt[i];
+
+ /*
+ * Allocate and copy command-line arguments...
+ */
+
+ if ((myargv = calloc(sizeof(char *), (size_t)(num_args + 1))) == NULL)
+ {
+ _cupsLangPuts(stderr, _("ippfind: Out of memory."));
+ exit(IPPFIND_EXIT_MEMORY);
+ }
+
+ for (i = 0; i < num_args; i ++)
+ {
+ if (strchr(args[i], '{'))
+ {
+ char temp[2048], /* Temporary string */
+ *tptr, /* Pointer into temporary string */
+ keyword[256], /* {keyword} */
+ *kptr; /* Pointer into keyword */
+
+ for (ptr = args[i], tptr = temp; *ptr; ptr ++)
+ {
+ if (*ptr == '{')
+ {
+ /*
+ * Do a {var} substitution...
+ */
+
+ for (kptr = keyword, ptr ++; *ptr && *ptr != '}'; ptr ++)
+ if (kptr < (keyword + sizeof(keyword) - 1))
+ *kptr++ = *ptr;
+
+ if (*ptr != '}')
+ {
+ _cupsLangPuts(stderr,
+ _("ippfind: Missing close brace in substitution."));
+ exit(IPPFIND_EXIT_SYNTAX);
+ }
+
+ *kptr = '\0';
+ if (!keyword[0] || !strcmp(keyword, "service_uri"))
+ strlcpy(tptr, service->uri, sizeof(temp) - (size_t)(tptr - temp));
+ else if (!strcmp(keyword, "service_domain"))
+ strlcpy(tptr, service->domain, sizeof(temp) - (size_t)(tptr - temp));
+ else if (!strcmp(keyword, "service_hostname"))
+ strlcpy(tptr, service->host, sizeof(temp) - (size_t)(tptr - temp));
+ else if (!strcmp(keyword, "service_name"))
+ strlcpy(tptr, service->name, sizeof(temp) - (size_t)(tptr - temp));
+ else if (!strcmp(keyword, "service_path"))
+ strlcpy(tptr, service->resource, sizeof(temp) - (size_t)(tptr - temp));
+ else if (!strcmp(keyword, "service_port"))
+ strlcpy(tptr, port + 21, sizeof(temp) - (size_t)(tptr - temp));
+ else if (!strcmp(keyword, "service_scheme"))
+ strlcpy(tptr, scheme + 22, sizeof(temp) - (size_t)(tptr - temp));
+ else if (!strncmp(keyword, "txt_", 4))
+ {
+ const char *val = cupsGetOption(keyword + 4, service->num_txt, service->txt);
+ if (val)
+ strlcpy(tptr, val, sizeof(temp) - (size_t)(tptr - temp));
+ else
+ *tptr = '\0';
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unknown variable \"{%s}\"."),
+ keyword);
+ exit(IPPFIND_EXIT_SYNTAX);
+ }
+
+ tptr += strlen(tptr);
+ }
+ else if (tptr < (temp + sizeof(temp) - 1))
+ *tptr++ = *ptr;
+ }
+
+ *tptr = '\0';
+ myargv[i] = strdup(temp);
+ }
+ else
+ myargv[i] = strdup(args[i]);
+ }
+
+#ifdef _WIN32
+ if (getenv("IPPFIND_DEBUG"))
+ {
+ printf("\nProgram:\n %s\n", args[0]);
+ puts("\nArguments:");
+ for (i = 0; i < num_args; i ++)
+ printf(" %s\n", myargv[i]);
+ puts("\nEnvironment:");
+ for (i = 0; i < myenvc; i ++)
+ printf(" %s\n", myenvp[i]);
+ }
+
+ status = _spawnvpe(_P_WAIT, args[0], myargv, myenvp);
+
+#else
+ /*
+ * Execute the program...
+ */
+
+ if (strchr(args[0], '/') && !access(args[0], X_OK))
+ strlcpy(program, args[0], sizeof(program));
+ else if (!cupsFileFind(args[0], getenv("PATH"), 1, program, sizeof(program)))
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"),
+ args[0], strerror(ENOENT));
+ exit(IPPFIND_EXIT_SYNTAX);
+ }
+
+ if (getenv("IPPFIND_DEBUG"))
+ {
+ printf("\nProgram:\n %s\n", program);
+ puts("\nArguments:");
+ for (i = 0; i < num_args; i ++)
+ printf(" %s\n", myargv[i]);
+ puts("\nEnvironment:");
+ for (i = 0; i < myenvc; i ++)
+ printf(" %s\n", myenvp[i]);
+ }
+
+ if ((pid = fork()) == 0)
+ {
+ /*
+ * Child comes here...
+ */
+
+ execve(program, myargv, myenvp);
+ exit(1);
+ }
+ else if (pid < 0)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"),
+ args[0], strerror(errno));
+ exit(IPPFIND_EXIT_SYNTAX);
+ }
+ else
+ {
+ /*
+ * Wait for it to complete...
+ */
+
+ while (wait(&status) != pid)
+ ;
+ }
+#endif /* _WIN32 */
+
+ /*
+ * Free memory...
+ */
+
+ for (i = 0; i < num_args; i ++)
+ free(myargv[i]);
+
+ free(myargv);
+ free(myenvp);
+
+ /*
+ * Return whether the program succeeded or crashed...
+ */
+
+ if (getenv("IPPFIND_DEBUG"))
+ {
+#ifdef _WIN32
+ printf("Exit Status: %d\n", status);
+#else
+ if (WIFEXITED(status))
+ printf("Exit Status: %d\n", WEXITSTATUS(status));
+ else
+ printf("Terminating Signal: %d\n", WTERMSIG(status));
+#endif /* _WIN32 */
+ }
+
+ return (status == 0);
+}
+
+
+/*
+ * 'get_service()' - Create or update a device.
+ */
+
+static ippfind_srv_t * /* O - Service */
+get_service(cups_array_t *services, /* I - Service array */
+ const char *serviceName, /* I - Name of service/device */
+ const char *regtype, /* I - Type of service */
+ const char *replyDomain) /* I - Service domain */
+{
+ ippfind_srv_t key, /* Search key */
+ *service; /* Service */
+ char fullName[kDNSServiceMaxDomainName];
+ /* Full name for query */
+
+
+ /*
+ * See if this is a new device...
+ */
+
+ key.name = (char *)serviceName;
+ key.regtype = (char *)regtype;
+
+ for (service = cupsArrayFind(services, &key);
+ service;
+ service = cupsArrayNext(services))
+ if (_cups_strcasecmp(service->name, key.name))
+ break;
+ else if (!strcmp(service->regtype, key.regtype))
+ return (service);
+
+ /*
+ * Yes, add the service...
+ */
+
+ service = calloc(sizeof(ippfind_srv_t), 1);
+ service->name = strdup(serviceName);
+ service->domain = strdup(replyDomain);
+ service->regtype = strdup(regtype);
+
+ cupsArrayAdd(services, service);
+
+ /*
+ * Set the "full name" of this service, which is used for queries and
+ * resolves...
+ */
+
+#ifdef HAVE_DNSSD
+ DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
+#else /* HAVE_AVAHI */
+ avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName,
+ regtype, replyDomain);
+#endif /* HAVE_DNSSD */
+
+ service->fullName = strdup(fullName);
+
+ return (service);
+}
+
+
+/*
+ * 'get_time()' - Get the current time-of-day in seconds.
+ */
+
+static double
+get_time(void)
+{
+#ifdef _WIN32
+ struct _timeb curtime; /* Current Windows time */
+
+ _ftime(&curtime);
+
+ return (curtime.time + 0.001 * curtime.millitm);
+
+#else
+ struct timeval curtime; /* Current UNIX time */
+
+ if (gettimeofday(&curtime, NULL))
+ return (0.0);
+ else
+ return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
+#endif /* _WIN32 */
+}
+
+
+/*
+ * 'list_service()' - List the contents of a service.
+ */
+
+static int /* O - 1 if successful, 0 otherwise */
+list_service(ippfind_srv_t *service) /* I - Service */
+{
+ http_addrlist_t *addrlist; /* Address(es) of service */
+ char port[10]; /* Port number of service */
+
+
+ snprintf(port, sizeof(port), "%d", service->port);
+
+ if ((addrlist = httpAddrGetList(service->host, address_family, port)) == NULL)
+ {
+ _cupsLangPrintf(stdout, "%s unreachable", service->uri);
+ return (0);
+ }
+
+ if (!strncmp(service->regtype, "_ipp._tcp", 9) ||
+ !strncmp(service->regtype, "_ipps._tcp", 10))
+ {
+ /*
+ * IPP/IPPS printer
+ */
+
+ http_t *http; /* HTTP connection */
+ ipp_t *request, /* IPP request */
+ *response; /* IPP response */
+ ipp_attribute_t *attr; /* IPP attribute */
+ int i, /* Looping var */
+ count, /* Number of values */
+ version, /* IPP version */
+ paccepting; /* printer-is-accepting-jobs value */
+ ipp_pstate_t pstate; /* printer-state value */
+ char preasons[1024], /* Comma-delimited printer-state-reasons */
+ *ptr, /* Pointer into reasons */
+ *end; /* End of reasons buffer */
+ static const char * const rattrs[] =/* Requested attributes */
+ {
+ "printer-is-accepting-jobs",
+ "printer-state",
+ "printer-state-reasons"
+ };
+
+ /*
+ * Connect to the printer...
+ */
+
+ http = httpConnect2(service->host, service->port, addrlist, address_family,
+ !strncmp(service->regtype, "_ipps._tcp", 10) ?
+ HTTP_ENCRYPTION_ALWAYS :
+ HTTP_ENCRYPTION_IF_REQUESTED,
+ 1, 30000, NULL);
+
+ httpAddrFreeList(addrlist);
+
+ if (!http)
+ {
+ _cupsLangPrintf(stdout, "%s unavailable", service->uri);
+ return (0);
+ }
+
+ /*
+ * Get the current printer state...
+ */
+
+ response = NULL;
+ version = ipp_version;
+
+ do
+ {
+ request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
+ ippSetVersion(request, version / 10, version % 10);
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
+ service->uri);
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requesting-user-name", NULL, cupsUser());
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (int)(sizeof(rattrs) / sizeof(rattrs[0])), NULL, rattrs);
+
+ response = cupsDoRequest(http, request, service->resource);
+
+ if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST && version > 11)
+ version = 11;
+ }
+ while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && version > 11);
+
+ /*
+ * Show results...
+ */
+
+ if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
+ {
+ _cupsLangPrintf(stdout, "%s: unavailable", service->uri);
+ return (0);
+ }
+
+ if ((attr = ippFindAttribute(response, "printer-state",
+ IPP_TAG_ENUM)) != NULL)
+ pstate = (ipp_pstate_t)ippGetInteger(attr, 0);
+ else
+ pstate = IPP_PSTATE_STOPPED;
+
+ if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs",
+ IPP_TAG_BOOLEAN)) != NULL)
+ paccepting = ippGetBoolean(attr, 0);
+ else
+ paccepting = 0;
+
+ if ((attr = ippFindAttribute(response, "printer-state-reasons",
+ IPP_TAG_KEYWORD)) != NULL)
+ {
+ strlcpy(preasons, ippGetString(attr, 0, NULL), sizeof(preasons));
+
+ for (i = 1, count = ippGetCount(attr), ptr = preasons + strlen(preasons),
+ end = preasons + sizeof(preasons) - 1;
+ i < count && ptr < end;
+ i ++, ptr += strlen(ptr))
+ {
+ *ptr++ = ',';
+ strlcpy(ptr, ippGetString(attr, i, NULL), (size_t)(end - ptr + 1));
+ }
+ }
+ else
+ strlcpy(preasons, "none", sizeof(preasons));
+
+ ippDelete(response);
+ httpClose(http);
+
+ _cupsLangPrintf(stdout, "%s %s %s %s", service->uri, ippEnumString("printer-state", (int)pstate), paccepting ? "accepting-jobs" : "not-accepting-jobs", preasons);
+ }
+ else if (!strncmp(service->regtype, "_http._tcp", 10) ||
+ !strncmp(service->regtype, "_https._tcp", 11))
+ {
+ /*
+ * HTTP/HTTPS web page
+ */
+
+ http_t *http; /* HTTP connection */
+ http_status_t status; /* HEAD status */
+
+
+ /*
+ * Connect to the web server...
+ */
+
+ http = httpConnect2(service->host, service->port, addrlist, address_family,
+ !strncmp(service->regtype, "_ipps._tcp", 10) ?
+ HTTP_ENCRYPTION_ALWAYS :
+ HTTP_ENCRYPTION_IF_REQUESTED,
+ 1, 30000, NULL);
+
+ httpAddrFreeList(addrlist);
+
+ if (!http)
+ {
+ _cupsLangPrintf(stdout, "%s unavailable", service->uri);
+ return (0);
+ }
+
+ if (httpGet(http, service->resource))
+ {
+ _cupsLangPrintf(stdout, "%s unavailable", service->uri);
+ return (0);
+ }
+
+ do
+ {
+ status = httpUpdate(http);
+ }
+ while (status == HTTP_STATUS_CONTINUE);
+
+ httpFlush(http);
+ httpClose(http);
+
+ if (status >= HTTP_STATUS_BAD_REQUEST)
+ {
+ _cupsLangPrintf(stdout, "%s unavailable", service->uri);
+ return (0);
+ }
+
+ _cupsLangPrintf(stdout, "%s available", service->uri);
+ }
+ else if (!strncmp(service->regtype, "_printer._tcp", 13))
+ {
+ /*
+ * LPD printer
+ */
+
+ int sock; /* Socket */
+
+
+ if (!httpAddrConnect(addrlist, &sock))
+ {
+ _cupsLangPrintf(stdout, "%s unavailable", service->uri);
+ httpAddrFreeList(addrlist);
+ return (0);
+ }
+
+ _cupsLangPrintf(stdout, "%s available", service->uri);
+ httpAddrFreeList(addrlist);
+
+ httpAddrClose(NULL, sock);
+ }
+ else
+ {
+ _cupsLangPrintf(stdout, "%s unsupported", service->uri);
+ httpAddrFreeList(addrlist);
+ return (0);
+ }
+
+ return (1);
+}
+
+
+/*
+ * 'new_expr()' - Create a new expression.
+ */
+
+static ippfind_expr_t * /* O - New expression */
+new_expr(ippfind_op_t op, /* I - Operation */
+ int invert, /* I - Invert result? */
+ const char *value, /* I - TXT key or port range */
+ const char *regex, /* I - Regular expression */
+ char **args) /* I - Pointer to argument strings */
+{
+ ippfind_expr_t *temp; /* New expression */
+
+
+ if ((temp = calloc(1, sizeof(ippfind_expr_t))) == NULL)
+ return (NULL);
+
+ temp->op = op;
+ temp->invert = invert;
+
+ if (op == IPPFIND_OP_TXT_EXISTS || op == IPPFIND_OP_TXT_REGEX || op == IPPFIND_OP_NAME_LITERAL)
+ temp->name = (char *)value;
+ else if (op == IPPFIND_OP_PORT_RANGE)
+ {
+ /*
+ * Pull port number range of the form "number", "-number" (0-number),
+ * "number-" (number-65535), and "number-number".
+ */
+
+ if (*value == '-')
+ {
+ temp->range[1] = atoi(value + 1);
+ }
+ else if (strchr(value, '-'))
+ {
+ if (sscanf(value, "%d-%d", temp->range, temp->range + 1) == 1)
+ temp->range[1] = 65535;
+ }
+ else
+ {
+ temp->range[0] = temp->range[1] = atoi(value);
+ }
+ }
+
+ if (regex)
+ {
+ int err = regcomp(&(temp->re), regex, REG_NOSUB | REG_ICASE | REG_EXTENDED);
+
+ if (err)
+ {
+ char message[256]; /* Error message */
+
+ regerror(err, &(temp->re), message, sizeof(message));
+ _cupsLangPrintf(stderr, _("ippfind: Bad regular expression: %s"),
+ message);
+ exit(IPPFIND_EXIT_SYNTAX);
+ }
+ }
+
+ if (args)
+ {
+ int num_args; /* Number of arguments */
+
+ for (num_args = 1; args[num_args]; num_args ++)
+ if (!strcmp(args[num_args], ";"))
+ break;
+
+ temp->num_args = num_args;
+ temp->args = malloc((size_t)num_args * sizeof(char *));
+ memcpy(temp->args, args, (size_t)num_args * sizeof(char *));
+ }
+
+ return (temp);
+}
+
+
+#ifdef HAVE_AVAHI
+/*
+ * 'poll_callback()' - Wait for input on the specified file descriptors.
+ *
+ * Note: This function is needed because avahi_simple_poll_iterate is broken
+ * and always uses a timeout of 0 (!) milliseconds.
+ * (Avahi Ticket #364)
+ */
+
+static int /* O - Number of file descriptors matching */
+poll_callback(
+ struct pollfd *pollfds, /* I - File descriptors */
+ unsigned int num_pollfds, /* I - Number of file descriptors */
+ int timeout, /* I - Timeout in milliseconds (unused) */
+ void *context) /* I - User data (unused) */
+{
+ int val; /* Return value */
+
+
+ (void)timeout;
+ (void)context;
+
+ val = poll(pollfds, num_pollfds, 500);
+
+ if (val > 0)
+ avahi_got_data = 1;
+
+ return (val);
+}
+#endif /* HAVE_AVAHI */
+
+
+/*
+ * 'resolve_callback()' - Process resolve data.
+ */
+
+#ifdef HAVE_DNSSD
+static void DNSSD_API
+resolve_callback(
+ DNSServiceRef sdRef, /* I - Service reference */
+ DNSServiceFlags flags, /* I - Data flags */
+ uint32_t interfaceIndex, /* I - Interface */
+ DNSServiceErrorType errorCode, /* I - Error, if any */
+ const char *fullName, /* I - Full service name */
+ const char *hostTarget, /* I - Hostname */
+ uint16_t port, /* I - Port number (network byte order) */
+ uint16_t txtLen, /* I - Length of TXT record data */
+ const unsigned char *txtRecord, /* I - TXT record data */
+ void *context) /* I - Service */
+{
+ char key[256], /* TXT key value */
+ *value; /* Value from TXT record */
+ const unsigned char *txtEnd; /* End of TXT record */
+ uint8_t valueLen; /* Length of value */
+ ippfind_srv_t *service = (ippfind_srv_t *)context;
+ /* Service */
+
+
+ /*
+ * Only process "add" data...
+ */
+
+ (void)sdRef;
+ (void)flags;
+ (void)interfaceIndex;
+ (void)fullName;
+
+ if (errorCode != kDNSServiceErr_NoError)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
+ dnssd_error_string(errorCode));
+ bonjour_error = 1;
+ return;
+ }
+
+ service->is_resolved = 1;
+ service->host = strdup(hostTarget);
+ service->port = ntohs(port);
+
+ value = service->host + strlen(service->host) - 1;
+ if (value >= service->host && *value == '.')
+ *value = '\0';
+
+ /*
+ * Loop through the TXT key/value pairs and add them to an array...
+ */
+
+ for (txtEnd = txtRecord + txtLen; txtRecord < txtEnd; txtRecord += valueLen)
+ {
+ /*
+ * Ignore bogus strings...
+ */
+
+ valueLen = *txtRecord++;
+
+ memcpy(key, txtRecord, valueLen);
+ key[valueLen] = '\0';
+
+ if ((value = strchr(key, '=')) == NULL)
+ continue;
+
+ *value++ = '\0';
+
+ /*
+ * Add to array of TXT values...
+ */
+
+ service->num_txt = cupsAddOption(key, value, service->num_txt,
+ &(service->txt));
+ }
+
+ set_service_uri(service);
+}
+
+
+#elif defined(HAVE_AVAHI)
+static void
+resolve_callback(
+ AvahiServiceResolver *resolver, /* I - Resolver */
+ AvahiIfIndex interface, /* I - Interface */
+ AvahiProtocol protocol, /* I - Address protocol */
+ AvahiResolverEvent event, /* I - Event */
+ const char *serviceName,/* I - Service name */
+ const char *regtype, /* I - Registration type */
+ const char *replyDomain,/* I - Domain name */
+ const char *hostTarget, /* I - FQDN */
+ const AvahiAddress *address, /* I - Address */
+ uint16_t port, /* I - Port number */
+ AvahiStringList *txt, /* I - TXT records */
+ AvahiLookupResultFlags flags, /* I - Lookup flags */
+ void *context) /* I - Service */
+{
+ char key[256], /* TXT key */
+ *value; /* TXT value */
+ ippfind_srv_t *service = (ippfind_srv_t *)context;
+ /* Service */
+ AvahiStringList *current; /* Current TXT key/value pair */
+
+
+ (void)address;
+
+ if (event != AVAHI_RESOLVER_FOUND)
+ {
+ bonjour_error = 1;
+
+ avahi_service_resolver_free(resolver);
+ avahi_simple_poll_quit(avahi_poll);
+ return;
+ }
+
+ service->is_resolved = 1;
+ service->host = strdup(hostTarget);
+ service->port = port;
+
+ value = service->host + strlen(service->host) - 1;
+ if (value >= service->host && *value == '.')
+ *value = '\0';
+
+ /*
+ * Loop through the TXT key/value pairs and add them to an array...
+ */
+
+ for (current = txt; current; current = current->next)
+ {
+ /*
+ * Ignore bogus strings...
+ */
+
+ if (current->size > (sizeof(key) - 1))
+ continue;
+
+ memcpy(key, current->text, current->size);
+ key[current->size] = '\0';
+
+ if ((value = strchr(key, '=')) == NULL)
+ continue;
+
+ *value++ = '\0';
+
+ /*
+ * Add to array of TXT values...
+ */
+
+ service->num_txt = cupsAddOption(key, value, service->num_txt,
+ &(service->txt));
+ }
+
+ set_service_uri(service);
+}
+#endif /* HAVE_DNSSD */
+
+
+/*
+ * 'set_service_uri()' - Set the URI of the service.
+ */
+
+static void
+set_service_uri(ippfind_srv_t *service) /* I - Service */
+{
+ char uri[1024]; /* URI */
+ const char *path, /* Resource path */
+ *scheme; /* URI scheme */
+
+
+ if (!strncmp(service->regtype, "_http.", 6))
+ {
+ scheme = "http";
+ path = cupsGetOption("path", service->num_txt, service->txt);
+ }
+ else if (!strncmp(service->regtype, "_https.", 7))
+ {
+ scheme = "https";
+ path = cupsGetOption("path", service->num_txt, service->txt);
+ }
+ else if (!strncmp(service->regtype, "_ipp.", 5))
+ {
+ scheme = "ipp";
+ path = cupsGetOption("rp", service->num_txt, service->txt);
+ }
+ else if (!strncmp(service->regtype, "_ipps.", 6))
+ {
+ scheme = "ipps";
+ path = cupsGetOption("rp", service->num_txt, service->txt);
+ }
+ else if (!strncmp(service->regtype, "_printer.", 9))
+ {
+ scheme = "lpd";
+ path = cupsGetOption("rp", service->num_txt, service->txt);
+ }
+ else
+ return;
+
+ if (!path || !*path)
+ path = "/";
+
+ if (*path == '/')
+ {
+ service->resource = strdup(path);
+ }
+ else
+ {
+ snprintf(uri, sizeof(uri), "/%s", path);
+ service->resource = strdup(uri);
+ }
+
+ httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL,
+ service->host, service->port, service->resource);
+ service->uri = strdup(uri);
+}
+
+
+/*
+ * 'show_usage()' - Show program usage.
+ */
+
+static void
+show_usage(void)
+{
+ _cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]"
+ "[.domain.] ... [expression]\n"
+ " ippfind [options] name[.regtype[.domain.]] "
+ "... [expression]\n"
+ " ippfind --help\n"
+ " ippfind --version"));
+ _cupsLangPuts(stderr, _("Options:"));
+ _cupsLangPuts(stderr, _("-4 Connect using IPv4"));
+ _cupsLangPuts(stderr, _("-6 Connect using IPv6"));
+ _cupsLangPuts(stderr, _("-T seconds Set the browse timeout in seconds"));
+ _cupsLangPuts(stderr, _("-V version Set default IPP version"));
+ _cupsLangPuts(stderr, _("--version Show program version"));
+ _cupsLangPuts(stderr, _("Expressions:"));
+ _cupsLangPuts(stderr, _("-P number[-number] Match port to number or range"));
+ _cupsLangPuts(stderr, _("-d regex Match domain to regular expression"));
+ _cupsLangPuts(stderr, _("-h regex Match hostname to regular expression"));
+ _cupsLangPuts(stderr, _("-l List attributes"));
+ _cupsLangPuts(stderr, _("-n regex Match service name to regular expression"));
+ _cupsLangPuts(stderr, _("-p Print URI if true"));
+ _cupsLangPuts(stderr, _("-q Quietly report match via exit code"));
+ _cupsLangPuts(stderr, _("-r True if service is remote"));
+ _cupsLangPuts(stderr, _("-s Print service name if true"));
+ _cupsLangPuts(stderr, _("-t key True if the TXT record contains the key"));
+ _cupsLangPuts(stderr, _("-u regex Match URI to regular expression"));
+ _cupsLangPuts(stderr, _("-x utility [argument ...] ;\n"
+ " Execute program if true"));
+ _cupsLangPuts(stderr, _("--domain regex Match domain to regular expression"));
+ _cupsLangPuts(stderr, _("--exec utility [argument ...] ;\n"
+ " Execute program if true"));
+ _cupsLangPuts(stderr, _("--host regex Match hostname to regular expression"));
+ _cupsLangPuts(stderr, _("--ls List attributes"));
+ _cupsLangPuts(stderr, _("--local True if service is local"));
+ _cupsLangPuts(stderr, _("--name regex Match service name to regular expression"));
+ _cupsLangPuts(stderr, _("--path regex Match resource path to regular expression"));
+ _cupsLangPuts(stderr, _("--port number[-number] Match port to number or range"));
+ _cupsLangPuts(stderr, _("--print Print URI if true"));
+ _cupsLangPuts(stderr, _("--print-name Print service name if true"));
+ _cupsLangPuts(stderr, _("--quiet Quietly report match via exit code"));
+ _cupsLangPuts(stderr, _("--remote True if service is remote"));
+ _cupsLangPuts(stderr, _("--txt key True if the TXT record contains the key"));
+ _cupsLangPuts(stderr, _("--txt-* regex Match TXT record key to regular expression"));
+ _cupsLangPuts(stderr, _("--uri regex Match URI to regular expression"));
+ _cupsLangPuts(stderr, _("Modifiers:"));
+ _cupsLangPuts(stderr, _("( expressions ) Group expressions"));
+ _cupsLangPuts(stderr, _("! expression Unary NOT of expression"));
+ _cupsLangPuts(stderr, _("--not expression Unary NOT of expression"));
+ _cupsLangPuts(stderr, _("--false Always false"));
+ _cupsLangPuts(stderr, _("--true Always true"));
+ _cupsLangPuts(stderr, _("expression expression Logical AND"));
+ _cupsLangPuts(stderr, _("expression --and expression\n"
+ " Logical AND"));
+ _cupsLangPuts(stderr, _("expression --or expression\n"
+ " Logical OR"));
+ _cupsLangPuts(stderr, _("Substitutions:"));
+ _cupsLangPuts(stderr, _("{} URI"));
+ _cupsLangPuts(stderr, _("{service_domain} Domain name"));
+ _cupsLangPuts(stderr, _("{service_hostname} Fully-qualified domain name"));
+ _cupsLangPuts(stderr, _("{service_name} Service instance name"));
+ _cupsLangPuts(stderr, _("{service_port} Port number"));
+ _cupsLangPuts(stderr, _("{service_regtype} DNS-SD registration type"));
+ _cupsLangPuts(stderr, _("{service_scheme} URI scheme"));
+ _cupsLangPuts(stderr, _("{service_uri} URI"));
+ _cupsLangPuts(stderr, _("{txt_*} Value of TXT record key"));
+ _cupsLangPuts(stderr, _("Environment Variables:"));
+ _cupsLangPuts(stderr, _("IPPFIND_SERVICE_DOMAIN Domain name"));
+ _cupsLangPuts(stderr, _("IPPFIND_SERVICE_HOSTNAME\n"
+ " Fully-qualified domain name"));
+ _cupsLangPuts(stderr, _("IPPFIND_SERVICE_NAME Service instance name"));
+ _cupsLangPuts(stderr, _("IPPFIND_SERVICE_PORT Port number"));
+ _cupsLangPuts(stderr, _("IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
+ _cupsLangPuts(stderr, _("IPPFIND_SERVICE_SCHEME URI scheme"));
+ _cupsLangPuts(stderr, _("IPPFIND_SERVICE_URI URI"));
+ _cupsLangPuts(stderr, _("IPPFIND_TXT_* Value of TXT record key"));
+
+ exit(IPPFIND_EXIT_TRUE);
+}
+
+
+/*
+ * 'show_version()' - Show program version.
+ */
+
+static void
+show_version(void)
+{
+ _cupsLangPuts(stderr, CUPS_SVERSION);
+
+ exit(IPPFIND_EXIT_TRUE);
+}
diff --git a/tools/ipptool.c b/tools/ipptool.c
new file mode 100644
index 000000000..847dfb586
--- /dev/null
+++ b/tools/ipptool.c
@@ -0,0 +1,5122 @@
+/*
+ * ipptool command for CUPS.
+ *
+ * Copyright © 2007-2019 by Apple Inc.
+ * Copyright © 1997-2007 by Easy Software Products.
+ *
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more
+ * information.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups-private.h>
+#include <regex.h>
+#include <sys/stat.h>
+#ifdef _WIN32
+# include <windows.h>
+# ifndef R_OK
+# define R_OK 0
+# endif /* !R_OK */
+#else
+# include <signal.h>
+# include <termios.h>
+#endif /* _WIN32 */
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif /* !O_BINARY */
+
+
+/*
+ * Types...
+ */
+
+typedef enum _cups_transfer_e /**** How to send request data ****/
+{
+ _CUPS_TRANSFER_AUTO, /* Chunk for files, length for static */
+ _CUPS_TRANSFER_CHUNKED, /* Chunk always */
+ _CUPS_TRANSFER_LENGTH /* Length always */
+} _cups_transfer_t;
+
+typedef enum _cups_output_e /**** Output mode ****/
+{
+ _CUPS_OUTPUT_QUIET, /* No output */
+ _CUPS_OUTPUT_TEST, /* Traditional CUPS test output */
+ _CUPS_OUTPUT_PLIST, /* XML plist test output */
+ _CUPS_OUTPUT_IPPSERVER, /* ippserver attribute file output */
+ _CUPS_OUTPUT_LIST, /* Tabular list output */
+ _CUPS_OUTPUT_CSV /* Comma-separated values output */
+} _cups_output_t;
+
+typedef enum _cups_with_e /**** WITH flags ****/
+{
+ _CUPS_WITH_LITERAL = 0, /* Match string is a literal value */
+ _CUPS_WITH_ALL = 1, /* Must match all values */
+ _CUPS_WITH_REGEX = 2, /* Match string is a regular expression */
+ _CUPS_WITH_HOSTNAME = 4, /* Match string is a URI hostname */
+ _CUPS_WITH_RESOURCE = 8, /* Match string is a URI resource */
+ _CUPS_WITH_SCHEME = 16 /* Match string is a URI scheme */
+} _cups_with_t;
+
+typedef struct _cups_expect_s /**** Expected attribute info ****/
+{
+ int optional, /* Optional attribute? */
+ not_expect, /* Don't expect attribute? */
+ expect_all; /* Expect all attributes to match/not match */
+ char *name, /* Attribute name */
+ *of_type, /* Type name */
+ *same_count_as, /* Parallel attribute name */
+ *if_defined, /* Only required if variable defined */
+ *if_not_defined, /* Only required if variable is not defined */
+ *with_value, /* Attribute must include this value */
+ *with_value_from, /* Attribute must have one of the values in this attribute */
+ *define_match, /* Variable to define on match */
+ *define_no_match, /* Variable to define on no-match */
+ *define_value; /* Variable to define with value */
+ int repeat_limit, /* Maximum number of times to repeat */
+ repeat_match, /* Repeat test on match */
+ repeat_no_match, /* Repeat test on no match */
+ with_flags, /* WITH flags */
+ count; /* Expected count if > 0 */
+ ipp_tag_t in_group; /* IN-GROUP value */
+} _cups_expect_t;
+
+typedef struct _cups_status_s /**** Status info ****/
+{
+ ipp_status_t status; /* Expected status code */
+ char *if_defined, /* Only if variable is defined */
+ *if_not_defined, /* Only if variable is not defined */
+ *define_match, /* Variable to define on match */
+ *define_no_match, /* Variable to define on no-match */
+ *define_value; /* Variable to define with value */
+ int repeat_limit, /* Maximum number of times to repeat */
+ repeat_match, /* Repeat the test when it does not match */
+ repeat_no_match; /* Repeat the test when it matches */
+} _cups_status_t;
+
+typedef struct _cups_testdata_s /**** Test Data ****/
+{
+ /* Global Options */
+ http_encryption_t encryption; /* Encryption for connection */
+ int family; /* Address family */
+ _cups_output_t output; /* Output mode */
+ int stop_after_include_error;
+ /* Stop after include errors? */
+ double timeout; /* Timeout for connection */
+ int validate_headers, /* Validate HTTP headers in response? */
+ verbosity; /* Show all attributes? */
+
+ /* Test Defaults */
+ int def_ignore_errors; /* Default IGNORE-ERRORS value */
+ _cups_transfer_t def_transfer; /* Default TRANSFER value */
+ int def_version; /* Default IPP version */
+
+ /* Global State */
+ http_t *http; /* HTTP connection to printer/server */
+ cups_file_t *outfile; /* Output file */
+ int show_header, /* Show the test header? */
+ xml_header; /* 1 if XML plist header was written */
+ int pass, /* Have we passed all tests? */
+ test_count, /* Number of tests (total) */
+ pass_count, /* Number of tests that passed */
+ fail_count, /* Number of tests that failed */
+ skip_count; /* Number of tests that were skipped */
+
+ /* Per-Test State */
+ cups_array_t *errors; /* Errors array */
+ int prev_pass, /* Result of previous test */
+ skip_previous; /* Skip on previous test failure? */
+ char compression[16]; /* COMPRESSION value */
+ useconds_t delay; /* Initial delay */
+ int num_displayed; /* Number of displayed attributes */
+ char *displayed[200]; /* Displayed attributes */
+ int num_expects; /* Number of expected attributes */
+ _cups_expect_t expects[200], /* Expected attributes */
+ *expect, /* Current expected attribute */
+ *last_expect; /* Last EXPECT (for predicates) */
+ char file[1024], /* Data filename */
+ file_id[1024]; /* File identifier */
+ int ignore_errors; /* Ignore test failures? */
+ char name[1024]; /* Test name */
+ useconds_t repeat_interval; /* Repeat interval (delay) */
+ int request_id; /* Current request ID */
+ char resource[512]; /* Resource for request */
+ int skip_test, /* Skip this test? */
+ num_statuses; /* Number of valid status codes */
+ _cups_status_t statuses[100], /* Valid status codes */
+ *last_status; /* Last STATUS (for predicates) */
+ char test_id[1024]; /* Test identifier */
+ _cups_transfer_t transfer; /* To chunk or not to chunk */
+ int version; /* IPP version number to use */
+} _cups_testdata_t;
+
+
+/*
+ * Globals...
+ */
+
+static int Cancel = 0; /* Cancel test? */
+
+
+/*
+ * Local functions...
+ */
+
+static void add_stringf(cups_array_t *a, const char *s, ...) _CUPS_FORMAT(2, 3);
+static int compare_uris(const char *a, const char *b);
+static void copy_hex_string(char *buffer, unsigned char *data, int datalen, size_t bufsize);
+static int do_test(_ipp_file_t *f, _ipp_vars_t *vars, _cups_testdata_t *data);
+static int do_tests(const char *testfile, _ipp_vars_t *vars, _cups_testdata_t *data);
+static int error_cb(_ipp_file_t *f, _cups_testdata_t *data, const char *error);
+static int expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag);
+static char *get_filename(const char *testfile, char *dst, const char *src, size_t dstsize);
+static const char *get_string(ipp_attribute_t *attr, int element, int flags, char *buffer, size_t bufsize);
+static void init_data(_cups_testdata_t *data);
+static char *iso_date(const ipp_uchar_t *date);
+static void pause_message(const char *message);
+static void print_attr(cups_file_t *outfile, _cups_output_t output, ipp_attribute_t *attr, ipp_tag_t *group);
+static void print_csv(_cups_testdata_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
+static void print_fatal_error(_cups_testdata_t *data, const char *s, ...) _CUPS_FORMAT(2, 3);
+static void print_ippserver_attr(_cups_testdata_t *data, ipp_attribute_t *attr, int indent);
+static void print_ippserver_string(_cups_testdata_t *data, const char *s, size_t len);
+static void print_line(_cups_testdata_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
+static void print_xml_header(_cups_testdata_t *data);
+static void print_xml_string(cups_file_t *outfile, const char *element, const char *s);
+static void print_xml_trailer(_cups_testdata_t *data, int success, const char *message);
+#ifndef _WIN32
+static void sigterm_handler(int sig);
+#endif /* _WIN32 */
+static int timeout_cb(http_t *http, void *user_data);
+static int token_cb(_ipp_file_t *f, _ipp_vars_t *vars, _cups_testdata_t *data, const char *token);
+static void usage(void) _CUPS_NORETURN;
+static const char *with_flags_string(int flags);
+static int with_value(_cups_testdata_t *data, cups_array_t *errors, char *value, int flags, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
+static int with_value_from(cups_array_t *errors, ipp_attribute_t *fromattr, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
+
+
+/*
+ * 'main()' - Parse options and do tests.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line args */
+ char *argv[]) /* I - Command-line arguments */
+{
+ int i; /* Looping var */
+ int status; /* Status of tests... */
+ char *opt, /* Current option */
+ name[1024], /* Name/value buffer */
+ *value, /* Pointer to value */
+ filename[1024], /* Real filename */
+ testname[1024]; /* Real test filename */
+ const char *ext, /* Extension on filename */
+ *testfile; /* Test file to use */
+ int interval, /* Test interval in microseconds */
+ repeat; /* Repeat count */
+ _cups_testdata_t data; /* Test data */
+ _ipp_vars_t vars; /* Variables */
+ _cups_globals_t *cg = _cupsGlobals();
+ /* Global data */
+
+
+#ifndef _WIN32
+ /*
+ * Catch SIGINT and SIGTERM...
+ */
+
+ signal(SIGINT, sigterm_handler);
+ signal(SIGTERM, sigterm_handler);
+#endif /* !_WIN32 */
+
+ /*
+ * Initialize the locale and variables...
+ */
+
+ _cupsSetLocale(argv);
+
+ init_data(&data);
+
+ _ippVarsInit(&vars, NULL, (_ipp_ferror_cb_t)error_cb, (_ipp_ftoken_cb_t)token_cb);
+
+ /*
+ * We need at least:
+ *
+ * ipptool URI testfile
+ */
+
+ interval = 0;
+ repeat = 0;
+ status = 0;
+ testfile = NULL;
+
+ for (i = 1; i < argc; i ++)
+ {
+ if (!strcmp(argv[i], "--help"))
+ {
+ usage();
+ }
+ else if (!strcmp(argv[i], "--ippserver"))
+ {
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("ipptool: Missing filename for \"--ippserver\"."));
+ usage();
+ }
+
+ if (data.outfile != cupsFileStdout())
+ usage();
+
+ if ((data.outfile = cupsFileOpen(argv[i], "w")) == NULL)
+ {
+ _cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
+ exit(1);
+ }
+
+ data.output = _CUPS_OUTPUT_IPPSERVER;
+ }
+ else if (!strcmp(argv[i], "--stop-after-include-error"))
+ {
+ data.stop_after_include_error = 1;
+ }
+ else if (!strcmp(argv[i], "--version"))
+ {
+ puts(CUPS_SVERSION);
+ return (0);
+ }
+ else if (argv[i][0] == '-')
+ {
+ for (opt = argv[i] + 1; *opt; opt ++)
+ {
+ switch (*opt)
+ {
+ case '4' : /* Connect using IPv4 only */
+ data.family = AF_INET;
+ break;
+
+#ifdef AF_INET6
+ case '6' : /* Connect using IPv6 only */
+ data.family = AF_INET6;
+ break;
+#endif /* AF_INET6 */
+
+ case 'C' : /* Enable HTTP chunking */
+ data.def_transfer = _CUPS_TRANSFER_CHUNKED;
+ break;
+
+ case 'E' : /* Encrypt with TLS */
+#ifdef HAVE_SSL
+ data.encryption = HTTP_ENCRYPT_REQUIRED;
+#else
+ _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
+ argv[0]);
+#endif /* HAVE_SSL */
+ break;
+
+ case 'I' : /* Ignore errors */
+ data.def_ignore_errors = 1;
+ break;
+
+ case 'L' : /* Disable HTTP chunking */
+ data.def_transfer = _CUPS_TRANSFER_LENGTH;
+ break;
+
+ case 'P' : /* Output to plist file */
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr, _("%s: Missing filename for \"-P\"."), "ipptool");
+ usage();
+ }
+
+ if (data.outfile != cupsFileStdout())
+ usage();
+
+ if ((data.outfile = cupsFileOpen(argv[i], "w")) == NULL)
+ {
+ _cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
+ exit(1);
+ }
+
+ data.output = _CUPS_OUTPUT_PLIST;
+
+ if (interval || repeat)
+ {
+ _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
+ usage();
+ }
+ break;
+
+ case 'S' : /* Encrypt with SSL */
+#ifdef HAVE_SSL
+ data.encryption = HTTP_ENCRYPT_ALWAYS;
+#else
+ _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
+ argv[0]);
+#endif /* HAVE_SSL */
+ break;
+
+ case 'T' : /* Set timeout */
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("%s: Missing timeout for \"-T\"."),
+ "ipptool");
+ usage();
+ }
+
+ data.timeout = _cupsStrScand(argv[i], NULL, localeconv());
+ break;
+
+ case 'V' : /* Set IPP version */
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("%s: Missing version for \"-V\"."),
+ "ipptool");
+ usage();
+ }
+
+ if (!strcmp(argv[i], "1.0"))
+ {
+ data.def_version = 10;
+ }
+ else if (!strcmp(argv[i], "1.1"))
+ {
+ data.def_version = 11;
+ }
+ else if (!strcmp(argv[i], "2.0"))
+ {
+ data.def_version = 20;
+ }
+ else if (!strcmp(argv[i], "2.1"))
+ {
+ data.def_version = 21;
+ }
+ else if (!strcmp(argv[i], "2.2"))
+ {
+ data.def_version = 22;
+ }
+ else
+ {
+ _cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."), "ipptool", argv[i]);
+ usage();
+ }
+ break;
+
+ case 'X' : /* Produce XML output */
+ data.output = _CUPS_OUTPUT_PLIST;
+
+ if (interval || repeat)
+ {
+ _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
+ usage();
+ }
+ break;
+
+ case 'c' : /* CSV output */
+ data.output = _CUPS_OUTPUT_CSV;
+ break;
+
+ case 'd' : /* Define a variable */
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr,
+ _("ipptool: Missing name=value for \"-d\"."));
+ usage();
+ }
+
+ strlcpy(name, argv[i], sizeof(name));
+ if ((value = strchr(name, '=')) != NULL)
+ *value++ = '\0';
+ else
+ value = name + strlen(name);
+
+ _ippVarsSet(&vars, name, value);
+ break;
+
+ case 'f' : /* Set the default test filename */
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr,
+ _("ipptool: Missing filename for \"-f\"."));
+ usage();
+ }
+
+ if (access(argv[i], 0))
+ {
+ /*
+ * Try filename.gz...
+ */
+
+ snprintf(filename, sizeof(filename), "%s.gz", argv[i]);
+ if (access(filename, 0) && filename[0] != '/'
+#ifdef _WIN32
+ && (!isalpha(filename[0] & 255) || filename[1] != ':')
+#endif /* _WIN32 */
+ )
+ {
+ snprintf(filename, sizeof(filename), "%s/ipptool/%s", cg->cups_datadir, argv[i]);
+ if (access(filename, 0))
+ {
+ snprintf(filename, sizeof(filename), "%s/ipptool/%s.gz", cg->cups_datadir, argv[i]);
+ if (access(filename, 0))
+ strlcpy(filename, argv[i], sizeof(filename));
+ }
+ }
+ }
+ else
+ strlcpy(filename, argv[i], sizeof(filename));
+
+ _ippVarsSet(&vars, "filename", filename);
+
+ if ((ext = strrchr(filename, '.')) != NULL)
+ {
+ /*
+ * Guess the MIME media type based on the extension...
+ */
+
+ if (!_cups_strcasecmp(ext, ".gif"))
+ _ippVarsSet(&vars, "filetype", "image/gif");
+ else if (!_cups_strcasecmp(ext, ".htm") ||
+ !_cups_strcasecmp(ext, ".htm.gz") ||
+ !_cups_strcasecmp(ext, ".html") ||
+ !_cups_strcasecmp(ext, ".html.gz"))
+ _ippVarsSet(&vars, "filetype", "text/html");
+ else if (!_cups_strcasecmp(ext, ".jpg") ||
+ !_cups_strcasecmp(ext, ".jpeg"))
+ _ippVarsSet(&vars, "filetype", "image/jpeg");
+ else if (!_cups_strcasecmp(ext, ".pcl") ||
+ !_cups_strcasecmp(ext, ".pcl.gz"))
+ _ippVarsSet(&vars, "filetype", "application/vnd.hp-PCL");
+ else if (!_cups_strcasecmp(ext, ".pdf"))
+ _ippVarsSet(&vars, "filetype", "application/pdf");
+ else if (!_cups_strcasecmp(ext, ".png"))
+ _ippVarsSet(&vars, "filetype", "image/png");
+ else if (!_cups_strcasecmp(ext, ".ps") ||
+ !_cups_strcasecmp(ext, ".ps.gz"))
+ _ippVarsSet(&vars, "filetype", "application/postscript");
+ else if (!_cups_strcasecmp(ext, ".pwg") ||
+ !_cups_strcasecmp(ext, ".pwg.gz") ||
+ !_cups_strcasecmp(ext, ".ras") ||
+ !_cups_strcasecmp(ext, ".ras.gz"))
+ _ippVarsSet(&vars, "filetype", "image/pwg-raster");
+ else if (!_cups_strcasecmp(ext, ".tif") ||
+ !_cups_strcasecmp(ext, ".tiff"))
+ _ippVarsSet(&vars, "filetype", "image/tiff");
+ else if (!_cups_strcasecmp(ext, ".txt") ||
+ !_cups_strcasecmp(ext, ".txt.gz"))
+ _ippVarsSet(&vars, "filetype", "text/plain");
+ else if (!_cups_strcasecmp(ext, ".urf") ||
+ !_cups_strcasecmp(ext, ".urf.gz"))
+ _ippVarsSet(&vars, "filetype", "image/urf");
+ else if (!_cups_strcasecmp(ext, ".xps"))
+ _ippVarsSet(&vars, "filetype", "application/openxps");
+ else
+ _ippVarsSet(&vars, "filetype", "application/octet-stream");
+ }
+ else
+ {
+ /*
+ * Use the "auto-type" MIME media type...
+ */
+
+ _ippVarsSet(&vars, "filetype", "application/octet-stream");
+ }
+ break;
+
+ case 'h' : /* Validate response headers */
+ data.validate_headers = 1;
+ break;
+
+ case 'i' : /* Test every N seconds */
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("ipptool: Missing seconds for \"-i\"."));
+ usage();
+ }
+ else
+ {
+ interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) * 1000000.0);
+ if (interval <= 0)
+ {
+ _cupsLangPuts(stderr, _("ipptool: Invalid seconds for \"-i\"."));
+ usage();
+ }
+ }
+
+ if ((data.output == _CUPS_OUTPUT_PLIST || data.output == _CUPS_OUTPUT_IPPSERVER) && interval)
+ {
+ _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
+ usage();
+ }
+ break;
+
+ case 'l' : /* List as a table */
+ data.output = _CUPS_OUTPUT_LIST;
+ break;
+
+ case 'n' : /* Repeat count */
+ i ++;
+
+ if (i >= argc)
+ {
+ _cupsLangPuts(stderr, _("ipptool: Missing count for \"-n\"."));
+ usage();
+ }
+ else
+ repeat = atoi(argv[i]);
+
+ if ((data.output == _CUPS_OUTPUT_PLIST || data.output == _CUPS_OUTPUT_IPPSERVER) && repeat)
+ {
+ _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
+ usage();
+ }
+ break;
+
+ case 'q' : /* Be quiet */
+ data.output = _CUPS_OUTPUT_QUIET;
+ break;
+
+ case 't' : /* CUPS test output */
+ data.output = _CUPS_OUTPUT_TEST;
+ break;
+
+ case 'v' : /* Be verbose */
+ data.verbosity ++;
+ break;
+
+ default :
+ _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), "ipptool", *opt);
+ usage();
+ }
+ }
+ }
+ else if (!strncmp(argv[i], "ipp://", 6) || !strncmp(argv[i], "http://", 7)
+#ifdef HAVE_SSL
+ || !strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8)
+#endif /* HAVE_SSL */
+ )
+ {
+ /*
+ * Set URI...
+ */
+
+ if (vars.uri)
+ {
+ _cupsLangPuts(stderr, _("ipptool: May only specify a single URI."));
+ usage();
+ }
+
+#ifdef HAVE_SSL
+ if (!strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8))
+ data.encryption = HTTP_ENCRYPT_ALWAYS;
+#endif /* HAVE_SSL */
+
+ if (!_ippVarsSet(&vars, "uri", argv[i]))
+ {
+ _cupsLangPrintf(stderr, _("ipptool: Bad URI \"%s\"."), argv[i]);
+ return (1);
+ }
+
+ if (vars.username[0] && vars.password)
+ cupsSetPasswordCB2(_ippVarsPasswordCB, &vars);
+ }
+ else
+ {
+ /*
+ * Run test...
+ */
+
+ if (!vars.uri)
+ {
+ _cupsLangPuts(stderr, _("ipptool: URI required before test file."));
+ _cupsLangPuts(stderr, argv[i]);
+ usage();
+ }
+
+ if (access(argv[i], 0) && argv[i][0] != '/'
+#ifdef _WIN32
+ && (!isalpha(argv[i][0] & 255) || argv[i][1] != ':')
+#endif /* _WIN32 */
+ )
+ {
+ snprintf(testname, sizeof(testname), "%s/ipptool/%s", cg->cups_datadir, argv[i]);
+ if (access(testname, 0))
+ testfile = argv[i];
+ else
+ testfile = testname;
+ }
+ else
+ testfile = argv[i];
+
+ if (!do_tests(testfile, &vars, &data))
+ status = 1;
+ }
+ }
+
+ if (!vars.uri || !testfile)
+ usage();
+
+ /*
+ * Loop if the interval is set...
+ */
+
+ if (data.output == _CUPS_OUTPUT_PLIST)
+ print_xml_trailer(&data, !status, NULL);
+ else if (interval > 0 && repeat > 0)
+ {
+ while (repeat > 1)
+ {
+ usleep((useconds_t)interval);
+ do_tests(testfile, &vars, &data);
+ repeat --;
+ }
+ }
+ else if (interval > 0)
+ {
+ for (;;)
+ {
+ usleep((useconds_t)interval);
+ do_tests(testfile, &vars, &data);
+ }
+ }
+
+ if ((data.output == _CUPS_OUTPUT_TEST || (data.output == _CUPS_OUTPUT_PLIST && data.outfile)) && data.test_count > 1)
+ {
+ /*
+ * Show a summary report if there were multiple tests...
+ */
+
+ cupsFilePrintf(cupsFileStdout(), "\nSummary: %d tests, %d passed, %d failed, %d skipped\nScore: %d%%\n", data.test_count, data.pass_count, data.fail_count, data.skip_count, 100 * (data.pass_count + data.skip_count) / data.test_count);
+ }
+
+ cupsFileClose(data.outfile);
+
+/*
+ * Exit...
+ */
+
+ return (status);
+}
+
+
+/*
+ * 'add_stringf()' - Add a formatted string to an array.
+ */
+
+static void
+add_stringf(cups_array_t *a, /* I - Array */
+ const char *s, /* I - Printf-style format string */
+ ...) /* I - Additional args as needed */
+{
+ char buffer[10240]; /* Format buffer */
+ va_list ap; /* Argument pointer */
+
+
+ /*
+ * Don't bother is the array is NULL...
+ */
+
+ if (!a)
+ return;
+
+ /*
+ * Format the message...
+ */
+
+ va_start(ap, s);
+ vsnprintf(buffer, sizeof(buffer), s, ap);
+ va_end(ap);
+
+ /*
+ * Add it to the array...
+ */
+
+ cupsArrayAdd(a, buffer);
+}
+
+
+/*
+ * 'compare_uris()' - Compare two URIs...
+ */
+
+static int /* O - Result of comparison */
+compare_uris(const char *a, /* I - First URI */
+ const char *b) /* I - Second URI */
+{
+ char ascheme[32], /* Components of first URI */
+ auserpass[256],
+ ahost[256],
+ aresource[256];
+ int aport;
+ char bscheme[32], /* Components of second URI */
+ buserpass[256],
+ bhost[256],
+ bresource[256];
+ int bport;
+ char *ptr; /* Pointer into string */
+ int result; /* Result of comparison */
+
+
+ /*
+ * Separate the URIs into their components...
+ */
+
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, a, ascheme, sizeof(ascheme), auserpass, sizeof(auserpass), ahost, sizeof(ahost), &aport, aresource, sizeof(aresource)) < HTTP_URI_STATUS_OK)
+ return (-1);
+
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, b, bscheme, sizeof(bscheme), buserpass, sizeof(buserpass), bhost, sizeof(bhost), &bport, bresource, sizeof(bresource)) < HTTP_URI_STATUS_OK)
+ return (-1);
+
+ /*
+ * Strip trailing dots from the host components, if present...
+ */
+
+ if ((ptr = ahost + strlen(ahost) - 1) > ahost && *ptr == '.')
+ *ptr = '\0';
+
+ if ((ptr = bhost + strlen(bhost) - 1) > bhost && *ptr == '.')
+ *ptr = '\0';
+
+ /*
+ * Compare each component...
+ */
+
+ if ((result = _cups_strcasecmp(ascheme, bscheme)) != 0)
+ return (result);
+
+ if ((result = strcmp(auserpass, buserpass)) != 0)
+ return (result);
+
+ if ((result = _cups_strcasecmp(ahost, bhost)) != 0)
+ return (result);
+
+ if (aport != bport)
+ return (aport - bport);
+
+ if (!_cups_strcasecmp(ascheme, "mailto") || !_cups_strcasecmp(ascheme, "urn"))
+ return (_cups_strcasecmp(aresource, bresource));
+ else
+ return (strcmp(aresource, bresource));
+}
+
+
+/*
+ * 'copy_hex_string()' - Copy an octetString to a C string and encode as hex if
+ * needed.
+ */
+
+static void
+copy_hex_string(char *buffer, /* I - String buffer */
+ unsigned char *data, /* I - octetString data */
+ int datalen, /* I - octetString length */
+ size_t bufsize) /* I - Size of string buffer */
+{
+ char *bufptr, /* Pointer into string buffer */
+ *bufend = buffer + bufsize - 2;
+ /* End of string buffer */
+ unsigned char *dataptr, /* Pointer into octetString data */
+ *dataend = data + datalen;
+ /* End of octetString data */
+ static const char *hexdigits = "0123456789ABCDEF";
+ /* Hex digits */
+
+
+ /*
+ * First see if there are any non-ASCII bytes in the octetString...
+ */
+
+ for (dataptr = data; dataptr < dataend; dataptr ++)
+ if (*dataptr < 0x20 || *dataptr >= 0x7f)
+ break;
+
+ if (dataptr < dataend)
+ {
+ /*
+ * Yes, encode as hex...
+ */
+
+ *buffer = '<';
+
+ for (bufptr = buffer + 1, dataptr = data; bufptr < bufend && dataptr < dataend; dataptr ++)
+ {
+ *bufptr++ = hexdigits[*dataptr >> 4];
+ *bufptr++ = hexdigits[*dataptr & 15];
+ }
+
+ if (bufptr < bufend)
+ *bufptr++ = '>';
+
+ *bufptr = '\0';
+ }
+ else
+ {
+ /*
+ * No, copy as a string...
+ */
+
+ if ((size_t)datalen > bufsize)
+ datalen = (int)bufsize - 1;
+
+ memcpy(buffer, data, (size_t)datalen);
+ buffer[datalen] = '\0';
+ }
+}
+
+
+/*
+ * 'do_test()' - Do a single test from the test file.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+do_test(_ipp_file_t *f, /* I - IPP data file */
+ _ipp_vars_t *vars, /* I - IPP variables */
+ _cups_testdata_t *data) /* I - Test data */
+
+{
+ int i, /* Looping var */
+ status_ok, /* Did we get a matching status? */
+ repeat_count = 0, /* Repeat count */
+ repeat_test; /* Repeat the test? */
+ _cups_expect_t *expect; /* Current expected attribute */
+ ipp_t *request, /* IPP request */
+ *response; /* IPP response */
+ size_t length; /* Length of IPP request */
+ http_status_t status; /* HTTP status */
+ cups_array_t *a; /* Duplicate attribute array */
+ ipp_tag_t group; /* Current group */
+ ipp_attribute_t *attrptr, /* Attribute pointer */
+ *found; /* Found attribute */
+ char temp[1024]; /* Temporary string */
+ cups_file_t *reqfile; /* File to send */
+ ssize_t bytes; /* Bytes read/written */
+ char buffer[131072]; /* Copy buffer */
+ size_t widths[200]; /* Width of columns */
+ const char *error; /* Current error */
+
+
+ if (Cancel)
+ return (0);
+
+ /*
+ * Take over control of the attributes in the request...
+ */
+
+ request = f->attrs;
+ f->attrs = NULL;
+
+ /*
+ * Submit the IPP request...
+ */
+
+ data->test_count ++;
+
+ ippSetVersion(request, data->version / 10, data->version % 10);
+ ippSetRequestId(request, data->request_id);
+
+ if (data->output == _CUPS_OUTPUT_PLIST)
+ {
+ cupsFilePuts(data->outfile, "<dict>\n");
+ cupsFilePuts(data->outfile, "<key>Name</key>\n");
+ print_xml_string(data->outfile, "string", data->name);
+ if (data->file_id[0])
+ {
+ cupsFilePuts(data->outfile, "<key>FileId</key>\n");
+ print_xml_string(data->outfile, "string", data->file_id);
+ }
+ if (data->test_id[0])
+ {
+ cupsFilePuts(data->outfile, "<key>TestId</key>\n");
+ print_xml_string(data->outfile, "string", data->test_id);
+ }
+ cupsFilePuts(data->outfile, "<key>Version</key>\n");
+ cupsFilePrintf(data->outfile, "<string>%d.%d</string>\n", data->version / 10, data->version % 10);
+ cupsFilePuts(data->outfile, "<key>Operation</key>\n");
+ print_xml_string(data->outfile, "string", ippOpString(ippGetOperation(request)));
+ cupsFilePuts(data->outfile, "<key>RequestId</key>\n");
+ cupsFilePrintf(data->outfile, "<integer>%d</integer>\n", data->request_id);
+ cupsFilePuts(data->outfile, "<key>RequestAttributes</key>\n");
+ cupsFilePuts(data->outfile, "<array>\n");
+ if (ippFirstAttribute(request))
+ {
+ cupsFilePuts(data->outfile, "<dict>\n");
+ for (attrptr = ippFirstAttribute(request), group = ippGetGroupTag(attrptr); attrptr; attrptr = ippNextAttribute(request))
+ print_attr(data->outfile, data->output, attrptr, &group);
+ cupsFilePuts(data->outfile, "</dict>\n");
+ }
+ cupsFilePuts(data->outfile, "</array>\n");
+ }
+
+ if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+ {
+ if (data->verbosity)
+ {
+ cupsFilePrintf(cupsFileStdout(), " %s:\n", ippOpString(ippGetOperation(request)));
+
+ for (attrptr = ippFirstAttribute(request); attrptr; attrptr = ippNextAttribute(request))
+ print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
+ }
+
+ cupsFilePrintf(cupsFileStdout(), " %-68.68s [", data->name);
+ }
+
+ if ((data->skip_previous && !data->prev_pass) || data->skip_test)
+ {
+ data->skip_count ++;
+
+ ippDelete(request);
+ request = NULL;
+ response = NULL;
+
+ if (data->output == _CUPS_OUTPUT_PLIST)
+ {
+ cupsFilePuts(data->outfile, "<key>Successful</key>\n");
+ cupsFilePuts(data->outfile, "<true />\n");
+ cupsFilePuts(data->outfile, "<key>Skipped</key>\n");
+ cupsFilePuts(data->outfile, "<true />\n");
+ cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
+ print_xml_string(data->outfile, "string", "skip");
+ cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
+ cupsFilePuts(data->outfile, "<dict />\n");
+ }
+
+ if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+ cupsFilePuts(cupsFileStdout(), "SKIP]\n");
+
+ goto skip_error;
+ }
+
+ vars->password_tries = 0;
+
+ do
+ {
+ if (data->delay > 0)
+ usleep(data->delay);
+
+ data->delay = data->repeat_interval;
+ repeat_count ++;
+
+ status = HTTP_STATUS_OK;
+
+ if (data->transfer == _CUPS_TRANSFER_CHUNKED || (data->transfer == _CUPS_TRANSFER_AUTO && data->file[0]))
+ {
+ /*
+ * Send request using chunking - a 0 length means "chunk".
+ */
+
+ length = 0;
+ }
+ else
+ {
+ /*
+ * Send request using content length...
+ */
+
+ length = ippLength(request);
+
+ if (data->file[0] && (reqfile = cupsFileOpen(data->file, "r")) != NULL)
+ {
+ /*
+ * Read the file to get the uncompressed file size...
+ */
+
+ while ((bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
+ length += (size_t)bytes;
+
+ cupsFileClose(reqfile);
+ }
+ }
+
+ /*
+ * Send the request...
+ */
+
+ data->prev_pass = 1;
+ repeat_test = 0;
+ response = NULL;
+
+ if (status != HTTP_STATUS_ERROR)
+ {
+ while (!response && !Cancel && data->prev_pass)
+ {
+ status = cupsSendRequest(data->http, request, data->resource, length);
+
+#ifdef HAVE_LIBZ
+ if (data->compression[0])
+ httpSetField(data->http, HTTP_FIELD_CONTENT_ENCODING, data->compression);
+#endif /* HAVE_LIBZ */
+
+ if (!Cancel && status == HTTP_STATUS_CONTINUE && ippGetState(request) == IPP_DATA && data->file[0])
+ {
+ if ((reqfile = cupsFileOpen(data->file, "r")) != NULL)
+ {
+ while (!Cancel && (bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
+ {
+ if ((status = cupsWriteRequestData(data->http, buffer, (size_t)bytes)) != HTTP_STATUS_CONTINUE)
+ break;
+ }
+
+ cupsFileClose(reqfile);
+ }
+ else
+ {
+ snprintf(buffer, sizeof(buffer), "%s: %s", data->file, strerror(errno));
+ _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
+
+ status = HTTP_STATUS_ERROR;
+ }
+ }
+
+ /*
+ * Get the server's response...
+ */
+
+ if (!Cancel && status != HTTP_STATUS_ERROR)
+ {
+ response = cupsGetResponse(data->http, data->resource);
+ status = httpGetStatus(data->http);
+ }
+
+ if (!Cancel && status == HTTP_STATUS_ERROR && httpError(data->http) != EINVAL &&
+#ifdef _WIN32
+ httpError(data->http) != WSAETIMEDOUT)
+#else
+ httpError(data->http) != ETIMEDOUT)
+#endif /* _WIN32 */
+ {
+ if (httpReconnect2(data->http, 30000, NULL))
+ data->prev_pass = 0;
+ }
+ else if (status == HTTP_STATUS_ERROR || status == HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED)
+ {
+ data->prev_pass = 0;
+ break;
+ }
+ else if (status != HTTP_STATUS_OK)
+ {
+ httpFlush(data->http);
+
+ if (status == HTTP_STATUS_UNAUTHORIZED)
+ continue;
+
+ break;
+ }
+ }
+ }
+
+ if (!Cancel && status == HTTP_STATUS_ERROR && httpError(data->http) != EINVAL &&
+#ifdef _WIN32
+ httpError(data->http) != WSAETIMEDOUT)
+#else
+ httpError(data->http) != ETIMEDOUT)
+#endif /* _WIN32 */
+ {
+ if (httpReconnect2(data->http, 30000, NULL))
+ data->prev_pass = 0;
+ }
+ else if (status == HTTP_STATUS_ERROR)
+ {
+ if (!Cancel)
+ httpReconnect2(data->http, 30000, NULL);
+
+ data->prev_pass = 0;
+ }
+ else if (status != HTTP_STATUS_OK)
+ {
+ httpFlush(data->http);
+ data->prev_pass = 0;
+ }
+
+ /*
+ * Check results of request...
+ */
+
+ cupsArrayClear(data->errors);
+
+ if (httpGetVersion(data->http) != HTTP_1_1)
+ {
+ int version = (int)httpGetVersion(data->http);
+
+ add_stringf(data->errors, "Bad HTTP version (%d.%d)", version / 100, version % 100);
+ }
+
+ if (data->validate_headers)
+ {
+ const char *header; /* HTTP header value */
+
+ if ((header = httpGetField(data->http, HTTP_FIELD_CONTENT_TYPE)) == NULL || _cups_strcasecmp(header, "application/ipp"))
+ add_stringf(data->errors, "Bad HTTP Content-Type in response (%s)", header && *header ? header : "<missing>");
+
+ if ((header = httpGetField(data->http, HTTP_FIELD_DATE)) != NULL && *header && httpGetDateTime(header) == 0)
+ add_stringf(data->errors, "Bad HTTP Date in response (%s)", header);
+ }
+
+ if (!response)
+ {
+ /*
+ * No response, log error...
+ */
+
+ add_stringf(data->errors, "IPP request failed with status %s (%s)", ippErrorString(cupsLastError()), cupsLastErrorString());
+ }
+ else
+ {
+ /*
+ * Collect common attribute values...
+ */
+
+ if ((attrptr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
+ {
+ snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
+ _ippVarsSet(vars, "job-id", temp);
+ }
+
+ if ((attrptr = ippFindAttribute(response, "job-uri", IPP_TAG_URI)) != NULL)
+ _ippVarsSet(vars, "job-uri", ippGetString(attrptr, 0, NULL));
+
+ if ((attrptr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) != NULL)
+ {
+ snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
+ _ippVarsSet(vars, "notify-subscription-id", temp);
+ }
+
+ /*
+ * Check response, validating groups and attributes and logging errors
+ * as needed...
+ */
+
+ if (ippGetState(response) != IPP_DATA)
+ add_stringf(data->errors, "Missing end-of-attributes-tag in response (RFC 2910 section 3.5.1)");
+
+ if (data->version)
+ {
+ int major, minor; /* IPP version */
+
+ major = ippGetVersion(response, &minor);
+
+ if (major != (data->version / 10) || minor != (data->version % 10))
+ add_stringf(data->errors, "Bad version %d.%d in response - expected %d.%d (RFC 2911 section 3.1.8).", major, minor, data->version / 10, data->version % 10);
+ }
+
+ if (ippGetRequestId(response) != data->request_id)
+ add_stringf(data->errors, "Bad request ID %d in response - expected %d (RFC 2911 section 3.1.1)", ippGetRequestId(response), data->request_id);
+
+ attrptr = ippFirstAttribute(response);
+ if (!attrptr)
+ {
+ add_stringf(data->errors, "Missing first attribute \"attributes-charset (charset)\" in group operation-attributes-tag (RFC 2911 section 3.1.4).");
+ }
+ else
+ {
+ if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_CHARSET || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 ||strcmp(ippGetName(attrptr), "attributes-charset"))
+ add_stringf(data->errors, "Bad first attribute \"%s (%s%s)\" in group %s, expected \"attributes-charset (charset)\" in group operation-attributes-tag (RFC 2911 section 3.1.4).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
+
+ attrptr = ippNextAttribute(response);
+ if (!attrptr)
+ add_stringf(data->errors, "Missing second attribute \"attributes-natural-language (naturalLanguage)\" in group operation-attributes-tag (RFC 2911 section 3.1.4).");
+ else if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_LANGUAGE || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 || strcmp(ippGetName(attrptr), "attributes-natural-language"))
+ add_stringf(data->errors, "Bad first attribute \"%s (%s%s)\" in group %s, expected \"attributes-natural-language (naturalLanguage)\" in group operation-attributes-tag (RFC 2911 section 3.1.4).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
+ }
+
+ if ((attrptr = ippFindAttribute(response, "status-message", IPP_TAG_ZERO)) != NULL)
+ {
+ const char *status_message = ippGetString(attrptr, 0, NULL);
+ /* String value */
+
+ if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
+ add_stringf(data->errors, "status-message (text(255)) has wrong value tag %s (RFC 2911 section 3.1.6.2).", ippTagString(ippGetValueTag(attrptr)));
+ if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
+ add_stringf(data->errors, "status-message (text(255)) has wrong group tag %s (RFC 2911 section 3.1.6.2).", ippTagString(ippGetGroupTag(attrptr)));
+ if (ippGetCount(attrptr) != 1)
+ add_stringf(data->errors, "status-message (text(255)) has %d values (RFC 2911 section 3.1.6.2).", ippGetCount(attrptr));
+ if (status_message && strlen(status_message) > 255)
+ add_stringf(data->errors, "status-message (text(255)) has bad length %d (RFC 2911 section 3.1.6.2).", (int)strlen(status_message));
+ }
+
+ if ((attrptr = ippFindAttribute(response, "detailed-status-message",
+ IPP_TAG_ZERO)) != NULL)
+ {
+ const char *detailed_status_message = ippGetString(attrptr, 0, NULL);
+ /* String value */
+
+ if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
+ add_stringf(data->errors,
+ "detailed-status-message (text(MAX)) has wrong "
+ "value tag %s (RFC 2911 section 3.1.6.3).",
+ ippTagString(ippGetValueTag(attrptr)));
+ if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
+ add_stringf(data->errors,
+ "detailed-status-message (text(MAX)) has wrong "
+ "group tag %s (RFC 2911 section 3.1.6.3).",
+ ippTagString(ippGetGroupTag(attrptr)));
+ if (ippGetCount(attrptr) != 1)
+ add_stringf(data->errors,
+ "detailed-status-message (text(MAX)) has %d values"
+ " (RFC 2911 section 3.1.6.3).",
+ ippGetCount(attrptr));
+ if (detailed_status_message && strlen(detailed_status_message) > 1023)
+ add_stringf(data->errors,
+ "detailed-status-message (text(MAX)) has bad "
+ "length %d (RFC 2911 section 3.1.6.3).",
+ (int)strlen(detailed_status_message));
+ }
+
+ a = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+
+ for (attrptr = ippFirstAttribute(response), group = ippGetGroupTag(attrptr);
+ attrptr;
+ attrptr = ippNextAttribute(response))
+ {
+ if (ippGetGroupTag(attrptr) != group)
+ {
+ int out_of_order = 0; /* Are attribute groups out-of-order? */
+ cupsArrayClear(a);
+
+ switch (ippGetGroupTag(attrptr))
+ {
+ case IPP_TAG_ZERO :
+ break;
+
+ case IPP_TAG_OPERATION :
+ out_of_order = 1;
+ break;
+
+ case IPP_TAG_UNSUPPORTED_GROUP :
+ if (group != IPP_TAG_OPERATION)
+ out_of_order = 1;
+ break;
+
+ case IPP_TAG_JOB :
+ case IPP_TAG_PRINTER :
+ if (group != IPP_TAG_OPERATION && group != IPP_TAG_UNSUPPORTED_GROUP)
+ out_of_order = 1;
+ break;
+
+ case IPP_TAG_SUBSCRIPTION :
+ if (group > ippGetGroupTag(attrptr) && group != IPP_TAG_DOCUMENT)
+ out_of_order = 1;
+ break;
+
+ default :
+ if (group > ippGetGroupTag(attrptr))
+ out_of_order = 1;
+ break;
+ }
+
+ if (out_of_order)
+ add_stringf(data->errors, "Attribute groups out of order (%s < %s)",
+ ippTagString(ippGetGroupTag(attrptr)),
+ ippTagString(group));
+
+ if (ippGetGroupTag(attrptr) != IPP_TAG_ZERO)
+ group = ippGetGroupTag(attrptr);
+ }
+
+ if (!ippValidateAttribute(attrptr))
+ cupsArrayAdd(data->errors, (void *)cupsLastErrorString());
+
+ if (ippGetName(attrptr))
+ {
+ if (cupsArrayFind(a, (void *)ippGetName(attrptr)) && data->output < _CUPS_OUTPUT_LIST)
+ add_stringf(data->errors, "Duplicate \"%s\" attribute in %s group",
+ ippGetName(attrptr), ippTagString(group));
+
+ cupsArrayAdd(a, (void *)ippGetName(attrptr));
+ }
+ }
+
+ cupsArrayDelete(a);
+
+ /*
+ * Now check the test-defined expected status-code and attribute
+ * values...
+ */
+
+ for (i = 0, status_ok = 0; i < data->num_statuses; i ++)
+ {
+ if (data->statuses[i].if_defined &&
+ !_ippVarsGet(vars, data->statuses[i].if_defined))
+ continue;
+
+ if (data->statuses[i].if_not_defined &&
+ _ippVarsGet(vars, data->statuses[i].if_not_defined))
+ continue;
+
+ if (ippGetStatusCode(response) == data->statuses[i].status)
+ {
+ status_ok = 1;
+
+ if (data->statuses[i].repeat_match && repeat_count < data->statuses[i].repeat_limit)
+ repeat_test = 1;
+
+ if (data->statuses[i].define_match)
+ _ippVarsSet(vars, data->statuses[i].define_match, "1");
+ }
+ else
+ {
+ if (data->statuses[i].repeat_no_match && repeat_count < data->statuses[i].repeat_limit)
+ repeat_test = 1;
+
+ if (data->statuses[i].define_no_match)
+ {
+ _ippVarsSet(vars, data->statuses[i].define_no_match, "1");
+ status_ok = 1;
+ }
+ }
+ }
+
+ if (!status_ok && data->num_statuses > 0)
+ {
+ for (i = 0; i < data->num_statuses; i ++)
+ {
+ if (data->statuses[i].if_defined &&
+ !_ippVarsGet(vars, data->statuses[i].if_defined))
+ continue;
+
+ if (data->statuses[i].if_not_defined &&
+ _ippVarsGet(vars, data->statuses[i].if_not_defined))
+ continue;
+
+ if (!data->statuses[i].repeat_match || repeat_count >= data->statuses[i].repeat_limit)
+ add_stringf(data->errors, "EXPECTED: STATUS %s (got %s)",
+ ippErrorString(data->statuses[i].status),
+ ippErrorString(cupsLastError()));
+ }
+
+ if ((attrptr = ippFindAttribute(response, "status-message",
+ IPP_TAG_TEXT)) != NULL)
+ add_stringf(data->errors, "status-message=\"%s\"", ippGetString(attrptr, 0, NULL));
+ }
+
+ for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
+ {
+ ipp_attribute_t *group_found; /* Found parent attribute for group tests */
+
+ if (expect->if_defined && !_ippVarsGet(vars, expect->if_defined))
+ continue;
+
+ if (expect->if_not_defined &&
+ _ippVarsGet(vars, expect->if_not_defined))
+ continue;
+
+ if ((found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL && expect->in_group && expect->in_group != ippGetGroupTag(found))
+ {
+ while ((found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL)
+ if (expect->in_group == ippGetGroupTag(found))
+ break;
+ }
+
+ do
+ {
+ group_found = found;
+
+ if (expect->in_group && strchr(expect->name, '/'))
+ {
+ char group_name[256],/* Parent attribute name */
+ *group_ptr; /* Pointer into parent attribute name */
+
+ strlcpy(group_name, expect->name, sizeof(group_name));
+ if ((group_ptr = strchr(group_name, '/')) != NULL)
+ *group_ptr = '\0';
+
+ group_found = ippFindAttribute(response, group_name, IPP_TAG_ZERO);
+ }
+
+ if ((found && expect->not_expect) ||
+ (!found && !(expect->not_expect || expect->optional)) ||
+ (found && !expect_matches(expect, ippGetValueTag(found))) ||
+ (group_found && expect->in_group && ippGetGroupTag(group_found) != expect->in_group))
+ {
+ if (expect->define_no_match)
+ _ippVarsSet(vars, expect->define_no_match, "1");
+ else if (!expect->define_match && !expect->define_value)
+ {
+ if (found && expect->not_expect)
+ add_stringf(data->errors, "NOT EXPECTED: %s", expect->name);
+ else if (!found && !(expect->not_expect || expect->optional))
+ add_stringf(data->errors, "EXPECTED: %s", expect->name);
+ else if (found)
+ {
+ if (!expect_matches(expect, ippGetValueTag(found)))
+ add_stringf(data->errors, "EXPECTED: %s OF-TYPE %s (got %s)",
+ expect->name, expect->of_type,
+ ippTagString(ippGetValueTag(found)));
+
+ if (expect->in_group && ippGetGroupTag(group_found) != expect->in_group)
+ add_stringf(data->errors, "EXPECTED: %s IN-GROUP %s (got %s).",
+ expect->name, ippTagString(expect->in_group),
+ ippTagString(ippGetGroupTag(group_found)));
+ }
+ }
+
+ if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
+ repeat_test = 1;
+ break;
+ }
+
+ if (found)
+ ippAttributeString(found, buffer, sizeof(buffer));
+
+ if (found && expect->with_value_from && !with_value_from(NULL, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, buffer, sizeof(buffer)))
+ {
+ if (expect->define_no_match)
+ _ippVarsSet(vars, expect->define_no_match, "1");
+ else if (!expect->define_match && !expect->define_value && ((!expect->repeat_match && !expect->repeat_no_match) || repeat_count >= expect->repeat_limit))
+ {
+ add_stringf(data->errors, "EXPECTED: %s WITH-VALUES-FROM %s", expect->name, expect->with_value_from);
+
+ with_value_from(data->errors, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, buffer, sizeof(buffer));
+ }
+
+ if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
+ repeat_test = 1;
+
+ break;
+ }
+ else if (found && !with_value(data, NULL, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer)))
+ {
+ if (expect->define_no_match)
+ _ippVarsSet(vars, expect->define_no_match, "1");
+ else if (!expect->define_match && !expect->define_value &&
+ !expect->repeat_match && (!expect->repeat_no_match || repeat_count >= expect->repeat_limit))
+ {
+ if (expect->with_flags & _CUPS_WITH_REGEX)
+ add_stringf(data->errors, "EXPECTED: %s %s /%s/", expect->name, with_flags_string(expect->with_flags), expect->with_value);
+ else
+ add_stringf(data->errors, "EXPECTED: %s %s \"%s\"", expect->name, with_flags_string(expect->with_flags), expect->with_value);
+
+ with_value(data, data->errors, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer));
+ }
+
+ if (expect->repeat_no_match &&
+ repeat_count < expect->repeat_limit)
+ repeat_test = 1;
+
+ break;
+ }
+
+ if (found && expect->count > 0 && ippGetCount(found) != expect->count)
+ {
+ if (expect->define_no_match)
+ _ippVarsSet(vars, expect->define_no_match, "1");
+ else if (!expect->define_match && !expect->define_value)
+ {
+ add_stringf(data->errors, "EXPECTED: %s COUNT %d (got %d)", expect->name,
+ expect->count, ippGetCount(found));
+ }
+
+ if (expect->repeat_no_match &&
+ repeat_count < expect->repeat_limit)
+ repeat_test = 1;
+
+ break;
+ }
+
+ if (found && expect->same_count_as)
+ {
+ attrptr = ippFindAttribute(response, expect->same_count_as,
+ IPP_TAG_ZERO);
+
+ if (!attrptr || ippGetCount(attrptr) != ippGetCount(found))
+ {
+ if (expect->define_no_match)
+ _ippVarsSet(vars, expect->define_no_match, "1");
+ else if (!expect->define_match && !expect->define_value)
+ {
+ if (!attrptr)
+ add_stringf(data->errors,
+ "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
+ "(not returned)", expect->name,
+ ippGetCount(found), expect->same_count_as);
+ else if (ippGetCount(attrptr) != ippGetCount(found))
+ add_stringf(data->errors,
+ "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
+ "(%d values)", expect->name, ippGetCount(found),
+ expect->same_count_as, ippGetCount(attrptr));
+ }
+
+ if (expect->repeat_no_match &&
+ repeat_count < expect->repeat_limit)
+ repeat_test = 1;
+
+ break;
+ }
+ }
+
+ if (found && expect->define_match)
+ _ippVarsSet(vars, expect->define_match, "1");
+
+ if (found && expect->define_value)
+ {
+ if (!expect->with_value)
+ {
+ int last = ippGetCount(found) - 1;
+ /* Last element in attribute */
+
+ switch (ippGetValueTag(found))
+ {
+ case IPP_TAG_ENUM :
+ case IPP_TAG_INTEGER :
+ snprintf(buffer, sizeof(buffer), "%d", ippGetInteger(found, last));
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ if (ippGetBoolean(found, last))
+ strlcpy(buffer, "true", sizeof(buffer));
+ else
+ strlcpy(buffer, "false", sizeof(buffer));
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ {
+ int xres, /* Horizontal resolution */
+ yres; /* Vertical resolution */
+ ipp_res_t units; /* Resolution units */
+
+ xres = ippGetResolution(found, last, &yres, &units);
+
+ if (xres == yres)
+ snprintf(buffer, sizeof(buffer), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ else
+ snprintf(buffer, sizeof(buffer), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ }
+ break;
+
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ case IPP_TAG_NAME :
+ case IPP_TAG_NAMELANG :
+ case IPP_TAG_TEXT :
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_URI :
+ case IPP_TAG_URISCHEME :
+ strlcpy(buffer, ippGetString(found, last, NULL), sizeof(buffer));
+ break;
+
+ default :
+ ippAttributeString(found, buffer, sizeof(buffer));
+ break;
+ }
+ }
+
+ _ippVarsSet(vars, expect->define_value, buffer);
+ }
+
+ if (found && expect->repeat_match &&
+ repeat_count < expect->repeat_limit)
+ repeat_test = 1;
+ }
+ while (expect->expect_all && (found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL);
+ }
+ }
+
+ /*
+ * If we are going to repeat this test, display intermediate results...
+ */
+
+ if (repeat_test)
+ {
+ if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+ {
+ cupsFilePrintf(cupsFileStdout(), "%04d]\n", repeat_count);
+\
+ if (data->num_displayed > 0)
+ {
+ for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+ {
+ const char *attrname = ippGetName(attrptr);
+ if (attrname)
+ {
+ for (i = 0; i < data->num_displayed; i ++)
+ {
+ if (!strcmp(data->displayed[i], attrname))
+ {
+ print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+ {
+ cupsFilePrintf(cupsFileStdout(), " %-68.68s [", data->name);
+ }
+
+ ippDelete(response);
+ response = NULL;
+ }
+ }
+ while (repeat_test);
+
+ ippDelete(request);
+
+ request = NULL;
+
+ if (cupsArrayCount(data->errors) > 0)
+ data->prev_pass = data->pass = 0;
+
+ if (data->prev_pass)
+ data->pass_count ++;
+ else
+ data->fail_count ++;
+
+ if (data->output == _CUPS_OUTPUT_PLIST)
+ {
+ cupsFilePuts(data->outfile, "<key>Successful</key>\n");
+ cupsFilePuts(data->outfile, data->prev_pass ? "<true />\n" : "<false />\n");
+ cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
+ print_xml_string(data->outfile, "string", ippErrorString(cupsLastError()));
+ cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
+ cupsFilePuts(data->outfile, "<array>\n");
+ cupsFilePuts(data->outfile, "<dict>\n");
+ for (attrptr = ippFirstAttribute(response), group = ippGetGroupTag(attrptr);
+ attrptr;
+ attrptr = ippNextAttribute(response))
+ print_attr(data->outfile, data->output, attrptr, &group);
+ cupsFilePuts(data->outfile, "</dict>\n");
+ cupsFilePuts(data->outfile, "</array>\n");
+ }
+ else if (data->output == _CUPS_OUTPUT_IPPSERVER && response)
+ {
+ for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+ {
+ if (!ippGetName(attrptr) || ippGetGroupTag(attrptr) != IPP_TAG_PRINTER)
+ continue;
+
+ print_ippserver_attr(data, attrptr, 0);
+ }
+ }
+
+ if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+ {
+ cupsFilePuts(cupsFileStdout(), data->prev_pass ? "PASS]\n" : "FAIL]\n");
+
+ if (!data->prev_pass || (data->verbosity && response))
+ {
+ cupsFilePrintf(cupsFileStdout(), " RECEIVED: %lu bytes in response\n", (unsigned long)ippLength(response));
+ cupsFilePrintf(cupsFileStdout(), " status-code = %s (%s)\n", ippErrorString(cupsLastError()), cupsLastErrorString());
+
+ if (data->verbosity && response)
+ {
+ for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+ print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
+ }
+ }
+ }
+ else if (!data->prev_pass && data->output != _CUPS_OUTPUT_QUIET)
+ fprintf(stderr, "%s\n", cupsLastErrorString());
+
+ if (data->prev_pass && data->output >= _CUPS_OUTPUT_LIST && !data->verbosity && data->num_displayed > 0)
+ {
+ size_t width; /* Length of value */
+
+ for (i = 0; i < data->num_displayed; i ++)
+ {
+ widths[i] = strlen(data->displayed[i]);
+
+ for (attrptr = ippFindAttribute(response, data->displayed[i], IPP_TAG_ZERO);
+ attrptr;
+ attrptr = ippFindNextAttribute(response, data->displayed[i], IPP_TAG_ZERO))
+ {
+ width = ippAttributeString(attrptr, NULL, 0);
+ if (width > widths[i])
+ widths[i] = width;
+ }
+ }
+
+ if (data->output == _CUPS_OUTPUT_CSV)
+ print_csv(data, NULL, NULL, data->num_displayed, data->displayed, widths);
+ else
+ print_line(data, NULL, NULL, data->num_displayed, data->displayed, widths);
+
+ attrptr = ippFirstAttribute(response);
+
+ while (attrptr)
+ {
+ while (attrptr && ippGetGroupTag(attrptr) <= IPP_TAG_OPERATION)
+ attrptr = ippNextAttribute(response);
+
+ if (attrptr)
+ {
+ if (data->output == _CUPS_OUTPUT_CSV)
+ print_csv(data, response, attrptr, data->num_displayed, data->displayed, widths);
+ else
+ print_line(data, response, attrptr, data->num_displayed, data->displayed, widths);
+
+ while (attrptr && ippGetGroupTag(attrptr) > IPP_TAG_OPERATION)
+ attrptr = ippNextAttribute(response);
+ }
+ }
+ }
+ else if (!data->prev_pass)
+ {
+ if (data->output == _CUPS_OUTPUT_PLIST)
+ {
+ cupsFilePuts(data->outfile, "<key>Errors</key>\n");
+ cupsFilePuts(data->outfile, "<array>\n");
+
+ for (error = (char *)cupsArrayFirst(data->errors);
+ error;
+ error = (char *)cupsArrayNext(data->errors))
+ print_xml_string(data->outfile, "string", error);
+
+ cupsFilePuts(data->outfile, "</array>\n");
+ }
+
+ if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+ {
+ for (error = (char *)cupsArrayFirst(data->errors);
+ error;
+ error = (char *)cupsArrayNext(data->errors))
+ cupsFilePrintf(cupsFileStdout(), " %s\n", error);
+ }
+ }
+
+ if (data->num_displayed > 0 && !data->verbosity && response && (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
+ {
+ for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+ {
+ if (ippGetName(attrptr))
+ {
+ for (i = 0; i < data->num_displayed; i ++)
+ {
+ if (!strcmp(data->displayed[i], ippGetName(attrptr)))
+ {
+ print_attr(data->outfile, data->output, attrptr, NULL);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ skip_error:
+
+ if (data->output == _CUPS_OUTPUT_PLIST)
+ cupsFilePuts(data->outfile, "</dict>\n");
+
+ ippDelete(response);
+ response = NULL;
+
+ for (i = 0; i < data->num_statuses; i ++)
+ {
+ if (data->statuses[i].if_defined)
+ free(data->statuses[i].if_defined);
+ if (data->statuses[i].if_not_defined)
+ free(data->statuses[i].if_not_defined);
+ if (data->statuses[i].define_match)
+ free(data->statuses[i].define_match);
+ if (data->statuses[i].define_no_match)
+ free(data->statuses[i].define_no_match);
+ }
+ data->num_statuses = 0;
+
+ for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
+ {
+ free(expect->name);
+ if (expect->of_type)
+ free(expect->of_type);
+ if (expect->same_count_as)
+ free(expect->same_count_as);
+ if (expect->if_defined)
+ free(expect->if_defined);
+ if (expect->if_not_defined)
+ free(expect->if_not_defined);
+ if (expect->with_value)
+ free(expect->with_value);
+ if (expect->define_match)
+ free(expect->define_match);
+ if (expect->define_no_match)
+ free(expect->define_no_match);
+ if (expect->define_value)
+ free(expect->define_value);
+ }
+ data->num_expects = 0;
+
+ for (i = 0; i < data->num_displayed; i ++)
+ free(data->displayed[i]);
+ data->num_displayed = 0;
+
+ return (data->ignore_errors || data->prev_pass);
+}
+
+
+/*
+ * 'do_tests()' - Do tests as specified in the test file.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+do_tests(const char *testfile, /* I - Test file to use */
+ _ipp_vars_t *vars, /* I - Variables */
+ _cups_testdata_t *data) /* I - Test data */
+{
+ http_encryption_t encryption; /* Encryption mode */
+
+
+ /*
+ * Connect to the printer/server...
+ */
+
+ if (!_cups_strcasecmp(vars->scheme, "https") || !_cups_strcasecmp(vars->scheme, "ipps"))
+ encryption = HTTP_ENCRYPTION_ALWAYS;
+ else
+ encryption = data->encryption;
+
+ if ((data->http = httpConnect2(vars->host, vars->port, NULL, data->family, encryption, 1, 30000, NULL)) == NULL)
+ {
+ print_fatal_error(data, "Unable to connect to \"%s\" on port %d - %s", vars->host, vars->port, cupsLastErrorString());
+ return (0);
+ }
+
+#ifdef HAVE_LIBZ
+ httpSetDefaultField(data->http, HTTP_FIELD_ACCEPT_ENCODING, "deflate, gzip, identity");
+#else
+ httpSetDefaultField(data->http, HTTP_FIELD_ACCEPT_ENCODING, "identity");
+#endif /* HAVE_LIBZ */
+
+ if (data->timeout > 0.0)
+ httpSetTimeout(data->http, data->timeout, timeout_cb, NULL);
+
+ /*
+ * Run tests...
+ */
+
+ _ippFileParse(vars, testfile, (void *)data);
+
+ /*
+ * Close connection and return...
+ */
+
+ httpClose(data->http);
+ data->http = NULL;
+
+ return (data->pass);
+}
+
+
+/*
+ * 'error_cb()' - Print/add an error message.
+ */
+
+static int /* O - 1 to continue, 0 to stop */
+error_cb(_ipp_file_t *f, /* I - IPP file data */
+ _cups_testdata_t *data, /* I - Test data */
+ const char *error) /* I - Error message */
+{
+ (void)f;
+
+ print_fatal_error(data, "%s", error);
+
+ return (1);
+}
+
+
+/*
+ * 'expect_matches()' - Return true if the tag matches the specification.
+ */
+
+static int /* O - 1 if matches, 0 otherwise */
+expect_matches(
+ _cups_expect_t *expect, /* I - Expected attribute */
+ ipp_tag_t value_tag) /* I - Value tag for attribute */
+{
+ int match; /* Match? */
+ char *of_type, /* Type name to match */
+ *next, /* Next name to match */
+ sep; /* Separator character */
+
+
+ /*
+ * If we don't expect a particular type, return immediately...
+ */
+
+ if (!expect->of_type)
+ return (1);
+
+ /*
+ * Parse the "of_type" value since the string can contain multiple attribute
+ * types separated by "," or "|"...
+ */
+
+ for (of_type = expect->of_type, match = 0; !match && *of_type; of_type = next)
+ {
+ /*
+ * Find the next separator, and set it (temporarily) to nul if present.
+ */
+
+ for (next = of_type; *next && *next != '|' && *next != ','; next ++);
+
+ if ((sep = *next) != '\0')
+ *next = '\0';
+
+ /*
+ * Support some meta-types to make it easier to write the test file.
+ */
+
+ if (!strcmp(of_type, "text"))
+ match = value_tag == IPP_TAG_TEXTLANG || value_tag == IPP_TAG_TEXT;
+ else if (!strcmp(of_type, "name"))
+ match = value_tag == IPP_TAG_NAMELANG || value_tag == IPP_TAG_NAME;
+ else if (!strcmp(of_type, "collection"))
+ match = value_tag == IPP_TAG_BEGIN_COLLECTION;
+ else
+ match = value_tag == ippTagValue(of_type);
+
+ /*
+ * Restore the separator if we have one...
+ */
+
+ if (sep)
+ *next++ = sep;
+ }
+
+ return (match);
+}
+
+
+/*
+ * 'get_filename()' - Get a filename based on the current test file.
+ */
+
+static char * /* O - Filename */
+get_filename(const char *testfile, /* I - Current test file */
+ char *dst, /* I - Destination filename */
+ const char *src, /* I - Source filename */
+ size_t dstsize) /* I - Size of destination buffer */
+{
+ char *dstptr; /* Pointer into destination */
+ _cups_globals_t *cg = _cupsGlobals();
+ /* Global data */
+
+
+ if (*src == '<' && src[strlen(src) - 1] == '>')
+ {
+ /*
+ * Map <filename> to CUPS_DATADIR/ipptool/filename...
+ */
+
+ snprintf(dst, dstsize, "%s/ipptool/%s", cg->cups_datadir, src + 1);
+ dstptr = dst + strlen(dst) - 1;
+ if (*dstptr == '>')
+ *dstptr = '\0';
+ }
+ else if (!access(src, R_OK) || *src == '/'
+#ifdef _WIN32
+ || (isalpha(*src & 255) && src[1] == ':')
+#endif /* _WIN32 */
+ )
+ {
+ /*
+ * Use the path as-is...
+ */
+
+ strlcpy(dst, src, dstsize);
+ }
+ else
+ {
+ /*
+ * Make path relative to testfile...
+ */
+
+ strlcpy(dst, testfile, dstsize);
+ if ((dstptr = strrchr(dst, '/')) != NULL)
+ dstptr ++;
+ else
+ dstptr = dst; /* Should never happen */
+
+ strlcpy(dstptr, src, dstsize - (size_t)(dstptr - dst));
+ }
+
+ return (dst);
+}
+
+
+/*
+ * 'get_string()' - Get a pointer to a string value or the portion of interest.
+ */
+
+static const char * /* O - Pointer to string */
+get_string(ipp_attribute_t *attr, /* I - IPP attribute */
+ int element, /* I - Element to fetch */
+ int flags, /* I - Value ("with") flags */
+ char *buffer, /* I - Temporary buffer */
+ size_t bufsize) /* I - Size of temporary buffer */
+{
+ const char *value; /* Value */
+ char *ptr, /* Pointer into value */
+ scheme[256], /* URI scheme */
+ userpass[256], /* Username/password */
+ hostname[256], /* Hostname */
+ resource[1024]; /* Resource */
+ int port; /* Port number */
+
+
+ value = ippGetString(attr, element, NULL);
+
+ if (flags & _CUPS_WITH_HOSTNAME)
+ {
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), buffer, (int)bufsize, &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+ buffer[0] = '\0';
+
+ ptr = buffer + strlen(buffer) - 1;
+ if (ptr >= buffer && *ptr == '.')
+ *ptr = '\0'; /* Drop trailing "." */
+
+ return (buffer);
+ }
+ else if (flags & _CUPS_WITH_RESOURCE)
+ {
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, buffer, (int)bufsize) < HTTP_URI_STATUS_OK)
+ buffer[0] = '\0';
+
+ return (buffer);
+ }
+ else if (flags & _CUPS_WITH_SCHEME)
+ {
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, value, buffer, (int)bufsize, userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+ buffer[0] = '\0';
+
+ return (buffer);
+ }
+ else if (ippGetValueTag(attr) == IPP_TAG_URI && (!strncmp(value, "ipp://", 6) || !strncmp(value, "http://", 7) || !strncmp(value, "ipps://", 7) || !strncmp(value, "https://", 8)))
+ {
+ http_uri_status_t status = httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource));
+
+ if (status < HTTP_URI_STATUS_OK)
+ {
+ /*
+ * Bad URI...
+ */
+
+ buffer[0] = '\0';
+ }
+ else
+ {
+ /*
+ * Normalize URI with no trailing dot...
+ */
+
+ if ((ptr = hostname + strlen(hostname) - 1) >= hostname && *ptr == '.')
+ *ptr = '\0';
+
+ httpAssembleURI(HTTP_URI_CODING_ALL, buffer, (int)bufsize, scheme, userpass, hostname, port, resource);
+ }
+
+ return (buffer);
+ }
+ else
+ return (value);
+}
+
+
+/*
+ * 'init_data()' - Initialize test data.
+ */
+
+static void
+init_data(_cups_testdata_t *data) /* I - Data */
+{
+ memset(data, 0, sizeof(_cups_testdata_t));
+
+ data->output = _CUPS_OUTPUT_LIST;
+ data->outfile = cupsFileStdout();
+ data->family = AF_UNSPEC;
+ data->def_transfer = _CUPS_TRANSFER_AUTO;
+ data->def_version = 11;
+ data->errors = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
+ data->pass = 1;
+ data->prev_pass = 1;
+ data->request_id = (CUPS_RAND() % 1000) * 137 + 1;
+ data->show_header = 1;
+}
+
+
+/*
+ * 'iso_date()' - Return an ISO 8601 date/time string for the given IPP dateTime
+ * value.
+ */
+
+static char * /* O - ISO 8601 date/time string */
+iso_date(const ipp_uchar_t *date) /* I - IPP (RFC 1903) date/time value */
+{
+ time_t utctime; /* UTC time since 1970 */
+ struct tm *utcdate; /* UTC date/time */
+ static char buffer[255]; /* String buffer */
+
+
+ utctime = ippDateToTime(date);
+ utcdate = gmtime(&utctime);
+
+ snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02dZ",
+ utcdate->tm_year + 1900, utcdate->tm_mon + 1, utcdate->tm_mday,
+ utcdate->tm_hour, utcdate->tm_min, utcdate->tm_sec);
+
+ return (buffer);
+}
+
+
+/*
+ * 'pause_message()' - Display the message and pause until the user presses a key.
+ */
+
+static void
+pause_message(const char *message) /* I - Message */
+{
+#ifdef _WIN32
+ HANDLE tty; /* Console handle */
+ DWORD mode; /* Console mode */
+ char key; /* Key press */
+ DWORD bytes; /* Bytes read for key press */
+
+
+ /*
+ * Disable input echo and set raw input...
+ */
+
+ if ((tty = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
+ return;
+
+ if (!GetConsoleMode(tty, &mode))
+ return;
+
+ if (!SetConsoleMode(tty, 0))
+ return;
+
+#else
+ int tty; /* /dev/tty - never read from stdin */
+ struct termios original, /* Original input mode */
+ noecho; /* No echo input mode */
+ char key; /* Current key press */
+
+
+ /*
+ * Disable input echo and set raw input...
+ */
+
+ if ((tty = open("/dev/tty", O_RDONLY)) < 0)
+ return;
+
+ if (tcgetattr(tty, &original))
+ {
+ close(tty);
+ return;
+ }
+
+ noecho = original;
+ noecho.c_lflag &= (tcflag_t)~(ICANON | ECHO | ECHOE | ISIG);
+
+ if (tcsetattr(tty, TCSAFLUSH, &noecho))
+ {
+ close(tty);
+ return;
+ }
+#endif /* _WIN32 */
+
+ /*
+ * Display the prompt...
+ */
+
+ cupsFilePrintf(cupsFileStdout(), "%s\n---- PRESS ANY KEY ----", message);
+
+#ifdef _WIN32
+ /*
+ * Read a key...
+ */
+
+ ReadFile(tty, &key, 1, &bytes, NULL);
+
+ /*
+ * Cleanup...
+ */
+
+ SetConsoleMode(tty, mode);
+
+#else
+ /*
+ * Read a key...
+ */
+
+ read(tty, &key, 1);
+
+ /*
+ * Cleanup...
+ */
+
+ tcsetattr(tty, TCSAFLUSH, &original);
+ close(tty);
+#endif /* _WIN32 */
+
+ /*
+ * Erase the "press any key" prompt...
+ */
+
+ cupsFilePuts(cupsFileStdout(), "\r \r");
+}
+
+
+/*
+ * 'print_attr()' - Print an attribute on the screen.
+ */
+
+static void
+print_attr(cups_file_t *outfile, /* I - Output file */
+ _cups_output_t output, /* I - Output format */
+ ipp_attribute_t *attr, /* I - Attribute to print */
+ ipp_tag_t *group) /* IO - Current group */
+{
+ int i, /* Looping var */
+ count; /* Number of values */
+ ipp_attribute_t *colattr; /* Collection attribute */
+
+
+ if (output == _CUPS_OUTPUT_PLIST)
+ {
+ if (!ippGetName(attr) || (group && *group != ippGetGroupTag(attr)))
+ {
+ if (ippGetGroupTag(attr) != IPP_TAG_ZERO)
+ {
+ cupsFilePuts(outfile, "</dict>\n");
+ cupsFilePuts(outfile, "<dict>\n");
+ }
+
+ if (group)
+ *group = ippGetGroupTag(attr);
+ }
+
+ if (!ippGetName(attr))
+ return;
+
+ print_xml_string(outfile, "key", ippGetName(attr));
+ if ((count = ippGetCount(attr)) > 1)
+ cupsFilePuts(outfile, "<array>\n");
+
+ switch (ippGetValueTag(attr))
+ {
+ case IPP_TAG_INTEGER :
+ case IPP_TAG_ENUM :
+ for (i = 0; i < count; i ++)
+ cupsFilePrintf(outfile, "<integer>%d</integer>\n", ippGetInteger(attr, i));
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ for (i = 0; i < count; i ++)
+ cupsFilePuts(outfile, ippGetBoolean(attr, i) ? "<true />\n" : "<false />\n");
+ break;
+
+ case IPP_TAG_RANGE :
+ for (i = 0; i < count; i ++)
+ {
+ int lower, upper; /* Lower and upper ranges */
+
+ lower = ippGetRange(attr, i, &upper);
+ cupsFilePrintf(outfile, "<dict><key>lower</key><integer>%d</integer><key>upper</key><integer>%d</integer></dict>\n", lower, upper);
+ }
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ for (i = 0; i < count; i ++)
+ {
+ int xres, yres; /* Resolution values */
+ ipp_res_t units; /* Resolution units */
+
+ xres = ippGetResolution(attr, i, &yres, &units);
+ cupsFilePrintf(outfile, "<dict><key>xres</key><integer>%d</integer><key>yres</key><integer>%d</integer><key>units</key><string>%s</string></dict>\n", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ }
+ break;
+
+ case IPP_TAG_DATE :
+ for (i = 0; i < count; i ++)
+ cupsFilePrintf(outfile, "<date>%s</date>\n", iso_date(ippGetDate(attr, i)));
+ break;
+
+ case IPP_TAG_STRING :
+ for (i = 0; i < count; i ++)
+ {
+ int datalen; /* Length of data */
+ void *data = ippGetOctetString(attr, i, &datalen);
+ /* Data */
+ char buffer[IPP_MAX_LENGTH * 5 / 4 + 1];
+ /* Base64 output buffer */
+
+ cupsFilePrintf(outfile, "<data>%s</data>\n", httpEncode64_2(buffer, sizeof(buffer), data, datalen));
+ }
+ break;
+
+ case IPP_TAG_TEXT :
+ case IPP_TAG_NAME :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_URI :
+ case IPP_TAG_URISCHEME :
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ for (i = 0; i < count; i ++)
+ print_xml_string(outfile, "string", ippGetString(attr, i, NULL));
+ break;
+
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_NAMELANG :
+ for (i = 0; i < count; i ++)
+ {
+ const char *s, /* String */
+ *lang; /* Language */
+
+ s = ippGetString(attr, i, &lang);
+ cupsFilePuts(outfile, "<dict><key>language</key><string>");
+ print_xml_string(outfile, NULL, lang);
+ cupsFilePuts(outfile, "</string><key>string</key><string>");
+ print_xml_string(outfile, NULL, s);
+ cupsFilePuts(outfile, "</string></dict>\n");
+ }
+ break;
+
+ case IPP_TAG_BEGIN_COLLECTION :
+ for (i = 0; i < count; i ++)
+ {
+ ipp_t *col = ippGetCollection(attr, i);
+ /* Collection value */
+
+ cupsFilePuts(outfile, "<dict>\n");
+ for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
+ print_attr(outfile, output, colattr, NULL);
+ cupsFilePuts(outfile, "</dict>\n");
+ }
+ break;
+
+ default :
+ cupsFilePrintf(outfile, "<string>&lt;&lt;%s&gt;&gt;</string>\n", ippTagString(ippGetValueTag(attr)));
+ break;
+ }
+
+ if (count > 1)
+ cupsFilePuts(outfile, "</array>\n");
+ }
+ else
+ {
+ char buffer[131072]; /* Value buffer */
+
+ if (output == _CUPS_OUTPUT_TEST)
+ {
+ if (!ippGetName(attr))
+ {
+ cupsFilePuts(outfile, " -- separator --\n");
+ return;
+ }
+
+ cupsFilePrintf(outfile, " %s (%s%s) = ", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
+ }
+
+ ippAttributeString(attr, buffer, sizeof(buffer));
+ cupsFilePrintf(outfile, "%s\n", buffer);
+ }
+}
+
+
+/*
+ * 'print_csv()' - Print a line of CSV text.
+ */
+
+static void
+print_csv(
+ _cups_testdata_t *data, /* I - Test data */
+ ipp_t *ipp, /* I - Response message */
+ ipp_attribute_t *attr, /* I - First attribute for line */
+ int num_displayed, /* I - Number of attributes to display */
+ char **displayed, /* I - Attributes to display */
+ size_t *widths) /* I - Column widths */
+{
+ int i; /* Looping var */
+ size_t maxlength; /* Max length of all columns */
+ char *buffer, /* String buffer */
+ *bufptr; /* Pointer into buffer */
+ ipp_attribute_t *current; /* Current attribute */
+
+
+ /*
+ * Get the maximum string length we have to show and allocate...
+ */
+
+ for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
+ if (widths[i] > maxlength)
+ maxlength = widths[i];
+
+ maxlength += 2;
+
+ if ((buffer = malloc(maxlength)) == NULL)
+ return;
+
+ /*
+ * Loop through the attributes to display...
+ */
+
+ if (attr)
+ {
+ for (i = 0; i < num_displayed; i ++)
+ {
+ if (i)
+ cupsFilePutChar(data->outfile, ',');
+
+ buffer[0] = '\0';
+
+ for (current = attr; current; current = ippNextAttribute(ipp))
+ {
+ if (!ippGetName(current))
+ break;
+ else if (!strcmp(ippGetName(current), displayed[i]))
+ {
+ ippAttributeString(current, buffer, maxlength);
+ break;
+ }
+ }
+
+ if (strchr(buffer, ',') != NULL || strchr(buffer, '\"') != NULL ||
+ strchr(buffer, '\\') != NULL)
+ {
+ cupsFilePutChar(cupsFileStdout(), '\"');
+ for (bufptr = buffer; *bufptr; bufptr ++)
+ {
+ if (*bufptr == '\\' || *bufptr == '\"')
+ cupsFilePutChar(cupsFileStdout(), '\\');
+ cupsFilePutChar(cupsFileStdout(), *bufptr);
+ }
+ cupsFilePutChar(cupsFileStdout(), '\"');
+ }
+ else
+ cupsFilePuts(data->outfile, buffer);
+ }
+ cupsFilePutChar(cupsFileStdout(), '\n');
+ }
+ else
+ {
+ for (i = 0; i < num_displayed; i ++)
+ {
+ if (i)
+ cupsFilePutChar(cupsFileStdout(), ',');
+
+ cupsFilePuts(data->outfile, displayed[i]);
+ }
+ cupsFilePutChar(cupsFileStdout(), '\n');
+ }
+
+ free(buffer);
+}
+
+
+/*
+ * 'print_fatal_error()' - Print a fatal error message.
+ */
+
+static void
+print_fatal_error(
+ _cups_testdata_t *data, /* I - Test data */
+ const char *s, /* I - Printf-style format string */
+ ...) /* I - Additional arguments as needed */
+{
+ char buffer[10240]; /* Format buffer */
+ va_list ap; /* Pointer to arguments */
+
+
+ /*
+ * Format the error message...
+ */
+
+ va_start(ap, s);
+ vsnprintf(buffer, sizeof(buffer), s, ap);
+ va_end(ap);
+
+ /*
+ * Then output it...
+ */
+
+ if (data->output == _CUPS_OUTPUT_PLIST)
+ {
+ print_xml_header(data);
+ print_xml_trailer(data, 0, buffer);
+ }
+
+ _cupsLangPrintf(stderr, "ipptool: %s", buffer);
+}
+
+
+/*
+ * 'print_ippserver_attr()' - Print a attribute suitable for use by ippserver.
+ */
+
+static void
+print_ippserver_attr(
+ _cups_testdata_t *data, /* I - Test data */
+ ipp_attribute_t *attr, /* I - Attribute to print */
+ int indent) /* I - Indentation level */
+{
+ int i, /* Looping var */
+ count = ippGetCount(attr);
+ /* Number of values */
+ ipp_attribute_t *colattr; /* Collection attribute */
+
+
+ if (indent == 0)
+ cupsFilePrintf(data->outfile, "ATTR %s %s", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
+ else
+ cupsFilePrintf(data->outfile, "%*sMEMBER %s %s", indent, "", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
+
+ switch (ippGetValueTag(attr))
+ {
+ case IPP_TAG_INTEGER :
+ case IPP_TAG_ENUM :
+ for (i = 0; i < count; i ++)
+ cupsFilePrintf(data->outfile, "%s%d", i ? "," : " ", ippGetInteger(attr, i));
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ cupsFilePuts(data->outfile, ippGetBoolean(attr, 0) ? " true" : " false");
+
+ for (i = 1; i < count; i ++)
+ cupsFilePuts(data->outfile, ippGetBoolean(attr, 1) ? ",true" : ",false");
+ break;
+
+ case IPP_TAG_RANGE :
+ for (i = 0; i < count; i ++)
+ {
+ int upper, lower = ippGetRange(attr, i, &upper);
+
+ cupsFilePrintf(data->outfile, "%s%d-%d", i ? "," : " ", lower, upper);
+ }
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ for (i = 0; i < count; i ++)
+ {
+ ipp_res_t units;
+ int yres, xres = ippGetResolution(attr, i, &yres, &units);
+
+ cupsFilePrintf(data->outfile, "%s%dx%d%s", i ? "," : " ", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ }
+ break;
+
+ case IPP_TAG_DATE :
+ for (i = 0; i < count; i ++)
+ cupsFilePrintf(data->outfile, "%s%s", i ? "," : " ", iso_date(ippGetDate(attr, i)));
+ break;
+
+ case IPP_TAG_STRING :
+ for (i = 0; i < count; i ++)
+ {
+ int len;
+ const char *s = (const char *)ippGetOctetString(attr, i, &len);
+
+ cupsFilePuts(data->outfile, i ? "," : " ");
+ print_ippserver_string(data, s, (size_t)len);
+ }
+ break;
+
+ case IPP_TAG_TEXT :
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_NAME :
+ case IPP_TAG_NAMELANG :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_URI :
+ case IPP_TAG_URISCHEME :
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ for (i = 0; i < count; i ++)
+ {
+ const char *s = ippGetString(attr, i, NULL);
+
+ cupsFilePuts(data->outfile, i ? "," : " ");
+ print_ippserver_string(data, s, strlen(s));
+ }
+ break;
+
+ case IPP_TAG_BEGIN_COLLECTION :
+ for (i = 0; i < count; i ++)
+ {
+ ipp_t *col = ippGetCollection(attr, i);
+
+ cupsFilePuts(data->outfile, i ? ",{\n" : " {\n");
+ for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
+ print_ippserver_attr(data, colattr, indent + 4);
+ cupsFilePrintf(data->outfile, "%*s}", indent, "");
+ }
+ break;
+
+ default :
+ /* Out-of-band value */
+ break;
+ }
+
+ cupsFilePuts(data->outfile, "\n");
+}
+
+
+/*
+ * 'print_ippserver_string()' - Print a string suitable for use by ippserver.
+ */
+
+static void
+print_ippserver_string(
+ _cups_testdata_t *data, /* I - Test data */
+ const char *s, /* I - String to print */
+ size_t len) /* I - Length of string */
+{
+ cupsFilePutChar(data->outfile, '\"');
+ while (len > 0)
+ {
+ if (*s == '\"')
+ cupsFilePutChar(data->outfile, '\\');
+ cupsFilePutChar(data->outfile, *s);
+
+ s ++;
+ len --;
+ }
+ cupsFilePutChar(data->outfile, '\"');
+}
+
+
+/*
+ * 'print_line()' - Print a line of formatted or CSV text.
+ */
+
+static void
+print_line(
+ _cups_testdata_t *data, /* I - Test data */
+ ipp_t *ipp, /* I - Response message */
+ ipp_attribute_t *attr, /* I - First attribute for line */
+ int num_displayed, /* I - Number of attributes to display */
+ char **displayed, /* I - Attributes to display */
+ size_t *widths) /* I - Column widths */
+{
+ int i; /* Looping var */
+ size_t maxlength; /* Max length of all columns */
+ char *buffer; /* String buffer */
+ ipp_attribute_t *current; /* Current attribute */
+
+
+ /*
+ * Get the maximum string length we have to show and allocate...
+ */
+
+ for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
+ if (widths[i] > maxlength)
+ maxlength = widths[i];
+
+ maxlength += 2;
+
+ if ((buffer = malloc(maxlength)) == NULL)
+ return;
+
+ /*
+ * Loop through the attributes to display...
+ */
+
+ if (attr)
+ {
+ for (i = 0; i < num_displayed; i ++)
+ {
+ if (i)
+ cupsFilePutChar(cupsFileStdout(), ' ');
+
+ buffer[0] = '\0';
+
+ for (current = attr; current; current = ippNextAttribute(ipp))
+ {
+ if (!ippGetName(current))
+ break;
+ else if (!strcmp(ippGetName(current), displayed[i]))
+ {
+ ippAttributeString(current, buffer, maxlength);
+ break;
+ }
+ }
+
+ cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], buffer);
+ }
+ cupsFilePutChar(cupsFileStdout(), '\n');
+ }
+ else
+ {
+ for (i = 0; i < num_displayed; i ++)
+ {
+ if (i)
+ cupsFilePutChar(cupsFileStdout(), ' ');
+
+ cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], displayed[i]);
+ }
+ cupsFilePutChar(cupsFileStdout(), '\n');
+
+ for (i = 0; i < num_displayed; i ++)
+ {
+ if (i)
+ cupsFilePutChar(cupsFileStdout(), ' ');
+
+ memset(buffer, '-', widths[i]);
+ buffer[widths[i]] = '\0';
+ cupsFilePuts(data->outfile, buffer);
+ }
+ cupsFilePutChar(cupsFileStdout(), '\n');
+ }
+
+ free(buffer);
+}
+
+
+/*
+ * 'print_xml_header()' - Print a standard XML plist header.
+ */
+
+static void
+print_xml_header(_cups_testdata_t *data)/* I - Test data */
+{
+ if (!data->xml_header)
+ {
+ cupsFilePuts(data->outfile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ cupsFilePuts(data->outfile, "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n");
+ cupsFilePuts(data->outfile, "<plist version=\"1.0\">\n");
+ cupsFilePuts(data->outfile, "<dict>\n");
+ cupsFilePuts(data->outfile, "<key>ipptoolVersion</key>\n");
+ cupsFilePuts(data->outfile, "<string>" CUPS_SVERSION "</string>\n");
+ cupsFilePuts(data->outfile, "<key>Transfer</key>\n");
+ cupsFilePrintf(data->outfile, "<string>%s</string>\n", data->transfer == _CUPS_TRANSFER_AUTO ? "auto" : data->transfer == _CUPS_TRANSFER_CHUNKED ? "chunked" : "length");
+ cupsFilePuts(data->outfile, "<key>Tests</key>\n");
+ cupsFilePuts(data->outfile, "<array>\n");
+
+ data->xml_header = 1;
+ }
+}
+
+
+/*
+ * 'print_xml_string()' - Print an XML string with escaping.
+ */
+
+static void
+print_xml_string(cups_file_t *outfile, /* I - Test data */
+ const char *element, /* I - Element name or NULL */
+ const char *s) /* I - String to print */
+{
+ if (element)
+ cupsFilePrintf(outfile, "<%s>", element);
+
+ while (*s)
+ {
+ if (*s == '&')
+ cupsFilePuts(outfile, "&amp;");
+ else if (*s == '<')
+ cupsFilePuts(outfile, "&lt;");
+ else if (*s == '>')
+ cupsFilePuts(outfile, "&gt;");
+ else if ((*s & 0xe0) == 0xc0)
+ {
+ /*
+ * Validate UTF-8 two-byte sequence...
+ */
+
+ if ((s[1] & 0xc0) != 0x80)
+ {
+ cupsFilePutChar(outfile, '?');
+ s ++;
+ }
+ else
+ {
+ cupsFilePutChar(outfile, *s++);
+ cupsFilePutChar(outfile, *s);
+ }
+ }
+ else if ((*s & 0xf0) == 0xe0)
+ {
+ /*
+ * Validate UTF-8 three-byte sequence...
+ */
+
+ if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
+ {
+ cupsFilePutChar(outfile, '?');
+ s += 2;
+ }
+ else
+ {
+ cupsFilePutChar(outfile, *s++);
+ cupsFilePutChar(outfile, *s++);
+ cupsFilePutChar(outfile, *s);
+ }
+ }
+ else if ((*s & 0xf8) == 0xf0)
+ {
+ /*
+ * Validate UTF-8 four-byte sequence...
+ */
+
+ if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
+ (s[3] & 0xc0) != 0x80)
+ {
+ cupsFilePutChar(outfile, '?');
+ s += 3;
+ }
+ else
+ {
+ cupsFilePutChar(outfile, *s++);
+ cupsFilePutChar(outfile, *s++);
+ cupsFilePutChar(outfile, *s++);
+ cupsFilePutChar(outfile, *s);
+ }
+ }
+ else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255)))
+ {
+ /*
+ * Invalid control character...
+ */
+
+ cupsFilePutChar(outfile, '?');
+ }
+ else
+ cupsFilePutChar(outfile, *s);
+
+ s ++;
+ }
+
+ if (element)
+ cupsFilePrintf(outfile, "</%s>\n", element);
+}
+
+
+/*
+ * 'print_xml_trailer()' - Print the XML trailer with success/fail value.
+ */
+
+static void
+print_xml_trailer(
+ _cups_testdata_t *data, /* I - Test data */
+ int success, /* I - 1 on success, 0 on failure */
+ const char *message) /* I - Error message or NULL */
+{
+ if (data->xml_header)
+ {
+ cupsFilePuts(data->outfile, "</array>\n");
+ cupsFilePuts(data->outfile, "<key>Successful</key>\n");
+ cupsFilePuts(data->outfile, success ? "<true />\n" : "<false />\n");
+ if (message)
+ {
+ cupsFilePuts(data->outfile, "<key>ErrorMessage</key>\n");
+ print_xml_string(data->outfile, "string", message);
+ }
+ cupsFilePuts(data->outfile, "</dict>\n");
+ cupsFilePuts(data->outfile, "</plist>\n");
+
+ data->xml_header = 0;
+ }
+}
+
+
+#ifndef _WIN32
+/*
+ * 'sigterm_handler()' - Handle SIGINT and SIGTERM.
+ */
+
+static void
+sigterm_handler(int sig) /* I - Signal number (unused) */
+{
+ (void)sig;
+
+ Cancel = 1;
+
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+}
+#endif /* !_WIN32 */
+
+
+/*
+ * 'timeout_cb()' - Handle HTTP timeouts.
+ */
+
+static int /* O - 1 to continue, 0 to cancel */
+timeout_cb(http_t *http, /* I - Connection to server */
+ void *user_data) /* I - User data (unused) */
+{
+ int buffered = 0; /* Bytes buffered but not yet sent */
+
+
+ (void)user_data;
+
+ /*
+ * If the socket still have data waiting to be sent to the printer (as can
+ * happen if the printer runs out of paper), continue to wait until the output
+ * buffer is empty...
+ */
+
+#ifdef SO_NWRITE /* macOS and some versions of Linux */
+ socklen_t len = sizeof(buffered); /* Size of return value */
+
+ if (getsockopt(httpGetFd(http), SOL_SOCKET, SO_NWRITE, &buffered, &len))
+ buffered = 0;
+
+#elif defined(SIOCOUTQ) /* Others except Windows */
+ if (ioctl(httpGetFd(http), SIOCOUTQ, &buffered))
+ buffered = 0;
+
+#else /* Windows (not possible) */
+ (void)http;
+#endif /* SO_NWRITE */
+
+ return (buffered > 0);
+}
+
+
+/*
+ * 'token_cb()' - Parse test file-specific tokens and run tests.
+ */
+
+static int /* O - 1 to continue, 0 to stop */
+token_cb(_ipp_file_t *f, /* I - IPP file data */
+ _ipp_vars_t *vars, /* I - IPP variables */
+ _cups_testdata_t *data, /* I - Test data */
+ const char *token) /* I - Current token */
+{
+ char name[1024], /* Name string */
+ temp[1024], /* Temporary string */
+ value[1024], /* Value string */
+ *ptr; /* Pointer into value */
+
+
+ if (!token)
+ {
+ /*
+ * Initialize state as needed (nothing for now...)
+ */
+
+ return (1);
+ }
+ else if (f->attrs)
+ {
+ /*
+ * Parse until we see a close brace...
+ */
+
+ if (_cups_strcasecmp(token, "COUNT") &&
+ _cups_strcasecmp(token, "DEFINE-MATCH") &&
+ _cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
+ _cups_strcasecmp(token, "DEFINE-VALUE") &&
+ _cups_strcasecmp(token, "IF-DEFINED") &&
+ _cups_strcasecmp(token, "IF-NOT-DEFINED") &&
+ _cups_strcasecmp(token, "IN-GROUP") &&
+ _cups_strcasecmp(token, "OF-TYPE") &&
+ _cups_strcasecmp(token, "REPEAT-LIMIT") &&
+ _cups_strcasecmp(token, "REPEAT-MATCH") &&
+ _cups_strcasecmp(token, "REPEAT-NO-MATCH") &&
+ _cups_strcasecmp(token, "SAME-COUNT-AS") &&
+ _cups_strcasecmp(token, "WITH-ALL-VALUES") &&
+ _cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") &&
+ _cups_strcasecmp(token, "WITH-ALL-RESOURCES") &&
+ _cups_strcasecmp(token, "WITH-ALL-SCHEMES") &&
+ _cups_strcasecmp(token, "WITH-HOSTNAME") &&
+ _cups_strcasecmp(token, "WITH-RESOURCE") &&
+ _cups_strcasecmp(token, "WITH-SCHEME") &&
+ _cups_strcasecmp(token, "WITH-VALUE") &&
+ _cups_strcasecmp(token, "WITH-VALUE-FROM"))
+ data->last_expect = NULL;
+
+ if (_cups_strcasecmp(token, "DEFINE-MATCH") &&
+ _cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
+ _cups_strcasecmp(token, "IF-DEFINED") &&
+ _cups_strcasecmp(token, "IF-NOT-DEFINED") &&
+ _cups_strcasecmp(token, "REPEAT-LIMIT") &&
+ _cups_strcasecmp(token, "REPEAT-MATCH") &&
+ _cups_strcasecmp(token, "REPEAT-NO-MATCH"))
+ data->last_status = NULL;
+
+ if (!strcmp(token, "}"))
+ {
+ return (do_test(f, vars, data));
+ }
+ else if (!strcmp(token, "COMPRESSION"))
+ {
+ /*
+ * COMPRESSION none
+ * COMPRESSION deflate
+ * COMPRESSION gzip
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ _ippVarsExpand(vars, data->compression, temp, sizeof(data->compression));
+#ifdef HAVE_LIBZ
+ if (strcmp(data->compression, "none") && strcmp(data->compression, "deflate") &&
+ strcmp(data->compression, "gzip"))
+#else
+ if (strcmp(data->compression, "none"))
+#endif /* HAVE_LIBZ */
+ {
+ print_fatal_error(data, "Unsupported COMPRESSION value \"%s\" on line %d of \"%s\".", data->compression, f->linenum, f->filename);
+ return (0);
+ }
+
+ if (!strcmp(data->compression, "none"))
+ data->compression[0] = '\0';
+ }
+ else
+ {
+ print_fatal_error(data, "Missing COMPRESSION value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "DEFINE"))
+ {
+ /*
+ * DEFINE name value
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+ _ippVarsSet(vars, name, value);
+ }
+ else
+ {
+ print_fatal_error(data, "Missing DEFINE name and/or value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "IGNORE-ERRORS"))
+ {
+ /*
+ * IGNORE-ERRORS yes
+ * IGNORE-ERRORS no
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+ {
+ data->ignore_errors = !_cups_strcasecmp(temp, "yes");
+ }
+ else
+ {
+ print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "NAME"))
+ {
+ /*
+ * Name of test...
+ */
+
+ _ippFileReadToken(f, temp, sizeof(temp));
+ _ippVarsExpand(vars, data->name, temp, sizeof(data->name));
+ }
+ else if (!_cups_strcasecmp(token, "PAUSE"))
+ {
+ /*
+ * Pause with a message...
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ pause_message(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "Missing PAUSE message on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "REQUEST-ID"))
+ {
+ /*
+ * REQUEST-ID #
+ * REQUEST-ID random
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ if (isdigit(temp[0] & 255))
+ {
+ data->request_id = atoi(temp);
+ }
+ else if (!_cups_strcasecmp(temp, "random"))
+ {
+ data->request_id = (CUPS_RAND() % 1000) * 137 + 1;
+ }
+ else
+ {
+ print_fatal_error(data, "Bad REQUEST-ID value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing REQUEST-ID value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "SKIP-IF-DEFINED"))
+ {
+ /*
+ * SKIP-IF-DEFINED variable
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)))
+ {
+ if (_ippVarsGet(vars, name))
+ data->skip_test = 1;
+ }
+ else
+ {
+ print_fatal_error(data, "Missing SKIP-IF-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "SKIP-IF-MISSING"))
+ {
+ /*
+ * SKIP-IF-MISSING filename
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ char filename[1024]; /* Filename */
+
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+ get_filename(f->filename, filename, temp, sizeof(filename));
+
+ if (access(filename, R_OK))
+ data->skip_test = 1;
+ }
+ else
+ {
+ print_fatal_error(data, "Missing SKIP-IF-MISSING filename on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
+ {
+ /*
+ * SKIP-IF-NOT-DEFINED variable
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)))
+ {
+ if (!_ippVarsGet(vars, name))
+ data->skip_test = 1;
+ }
+ else
+ {
+ print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "SKIP-PREVIOUS-ERROR"))
+ {
+ /*
+ * SKIP-PREVIOUS-ERROR yes
+ * SKIP-PREVIOUS-ERROR no
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+ {
+ data->skip_previous = !_cups_strcasecmp(temp, "yes");
+ }
+ else
+ {
+ print_fatal_error(data, "Missing SKIP-PREVIOUS-ERROR value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "TEST-ID"))
+ {
+ /*
+ * TEST-ID "string"
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ _ippVarsExpand(vars, data->test_id, temp, sizeof(data->test_id));
+ }
+ else
+ {
+ print_fatal_error(data, "Missing TEST-ID value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "TRANSFER"))
+ {
+ /*
+ * TRANSFER auto
+ * TRANSFER chunked
+ * TRANSFER length
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ if (!strcmp(temp, "auto"))
+ {
+ data->transfer = _CUPS_TRANSFER_AUTO;
+ }
+ else if (!strcmp(temp, "chunked"))
+ {
+ data->transfer = _CUPS_TRANSFER_CHUNKED;
+ }
+ else if (!strcmp(temp, "length"))
+ {
+ data->transfer = _CUPS_TRANSFER_LENGTH;
+ }
+ else
+ {
+ print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing TRANSFER value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "VERSION"))
+ {
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ if (!strcmp(temp, "0.0"))
+ {
+ data->version = 0;
+ }
+ else if (!strcmp(temp, "1.0"))
+ {
+ data->version = 10;
+ }
+ else if (!strcmp(temp, "1.1"))
+ {
+ data->version = 11;
+ }
+ else if (!strcmp(temp, "2.0"))
+ {
+ data->version = 20;
+ }
+ else if (!strcmp(temp, "2.1"))
+ {
+ data->version = 21;
+ }
+ else if (!strcmp(temp, "2.2"))
+ {
+ data->version = 22;
+ }
+ else
+ {
+ print_fatal_error(data, "Bad VERSION \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing VERSION number on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "RESOURCE"))
+ {
+ /*
+ * Resource name...
+ */
+
+ if (!_ippFileReadToken(f, data->resource, sizeof(data->resource)))
+ {
+ print_fatal_error(data, "Missing RESOURCE path on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "OPERATION"))
+ {
+ /*
+ * Operation...
+ */
+
+ ipp_op_t op; /* Operation code */
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing OPERATION code on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+
+ if ((op = ippOpValue(value)) == (ipp_op_t)-1 && (op = (ipp_op_t)strtol(value, NULL, 0)) == 0)
+ {
+ print_fatal_error(data, "Bad OPERATION code \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+
+ ippSetOperation(f->attrs, op);
+ }
+ else if (!_cups_strcasecmp(token, "GROUP"))
+ {
+ /*
+ * Attribute group...
+ */
+
+ ipp_tag_t group_tag; /* Group tag */
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing GROUP tag on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if ((group_tag = ippTagValue(temp)) == IPP_TAG_ZERO || group_tag >= IPP_TAG_UNSUPPORTED_VALUE)
+ {
+ print_fatal_error(data, "Bad GROUP tag \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+
+ if (group_tag == f->group_tag)
+ ippAddSeparator(f->attrs);
+
+ f->group_tag = group_tag;
+ }
+ else if (!_cups_strcasecmp(token, "DELAY"))
+ {
+ /*
+ * Delay before operation...
+ */
+
+ double dval; /* Delay value */
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing DELAY value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+
+ if ((dval = _cupsStrScand(value, &ptr, localeconv())) < 0.0 || (*ptr && *ptr != ','))
+ {
+ print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
+ return (0);
+ }
+
+ data->delay = (useconds_t)(1000000.0 * dval);
+
+ if (*ptr == ',')
+ {
+ if ((dval = _cupsStrScand(ptr + 1, &ptr, localeconv())) <= 0.0 || *ptr)
+ {
+ print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
+ return (0);
+ }
+
+ data->repeat_interval = (useconds_t)(1000000.0 * dval);
+ }
+ else
+ data->repeat_interval = data->delay;
+ }
+ else if (!_cups_strcasecmp(token, "FILE"))
+ {
+ /*
+ * File...
+ */
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing FILE filename on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+ get_filename(f->filename, data->file, value, sizeof(data->file));
+
+ if (access(data->file, R_OK))
+ {
+ print_fatal_error(data, "Filename \"%s\" (mapped to \"%s\") on line %d of \"%s\" cannot be read.", value, data->file, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "STATUS"))
+ {
+ /*
+ * Status...
+ */
+
+ if (data->num_statuses >= (int)(sizeof(data->statuses) / sizeof(data->statuses[0])))
+ {
+ print_fatal_error(data, "Too many STATUS's on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing STATUS code on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if ((data->statuses[data->num_statuses].status = ippErrorValue(temp)) == (ipp_status_t)-1 && (data->statuses[data->num_statuses].status = (ipp_status_t)strtol(temp, NULL, 0)) == 0)
+ {
+ print_fatal_error(data, "Bad STATUS code \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+
+ data->last_status = data->statuses + data->num_statuses;
+ data->num_statuses ++;
+
+ data->last_status->define_match = NULL;
+ data->last_status->define_no_match = NULL;
+ data->last_status->if_defined = NULL;
+ data->last_status->if_not_defined = NULL;
+ data->last_status->repeat_limit = 1000;
+ data->last_status->repeat_match = 0;
+ data->last_status->repeat_no_match = 0;
+ }
+ else if (!_cups_strcasecmp(token, "EXPECT") || !_cups_strcasecmp(token, "EXPECT-ALL"))
+ {
+ /*
+ * Expected attributes...
+ */
+
+ int expect_all = !_cups_strcasecmp(token, "EXPECT-ALL");
+
+ if (data->num_expects >= (int)(sizeof(data->expects) / sizeof(data->expects[0])))
+ {
+ print_fatal_error(data, "Too many EXPECT's on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (!_ippFileReadToken(f, name, sizeof(name)))
+ {
+ print_fatal_error(data, "Missing EXPECT name on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ data->last_expect = data->expects + data->num_expects;
+ data->num_expects ++;
+
+ memset(data->last_expect, 0, sizeof(_cups_expect_t));
+ data->last_expect->repeat_limit = 1000;
+ data->last_expect->expect_all = expect_all;
+
+ if (name[0] == '!')
+ {
+ data->last_expect->not_expect = 1;
+ data->last_expect->name = strdup(name + 1);
+ }
+ else if (name[0] == '?')
+ {
+ data->last_expect->optional = 1;
+ data->last_expect->name = strdup(name + 1);
+ }
+ else
+ data->last_expect->name = strdup(name);
+ }
+ else if (!_cups_strcasecmp(token, "COUNT"))
+ {
+ int count; /* Count value */
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing COUNT number on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if ((count = atoi(temp)) <= 0)
+ {
+ print_fatal_error(data, "Bad COUNT \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->count = count;
+ }
+ else
+ {
+ print_fatal_error(data, "COUNT without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing DEFINE-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->define_match = strdup(temp);
+ }
+ else if (data->last_status)
+ {
+ data->last_status->define_match = strdup(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "DEFINE-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing DEFINE-NO-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->define_no_match = strdup(temp);
+ }
+ else if (data->last_status)
+ {
+ data->last_status->define_no_match = strdup(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "DEFINE-NO-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing DEFINE-VALUE variable on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->define_value = strdup(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "DEFINE-VALUE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "OF-TYPE"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing OF-TYPE value tag(s) on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->of_type = strdup(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "OF-TYPE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "IN-GROUP"))
+ {
+ ipp_tag_t in_group; /* IN-GROUP value */
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing IN-GROUP group tag on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if ((in_group = ippTagValue(temp)) == IPP_TAG_ZERO || in_group >= IPP_TAG_UNSUPPORTED_VALUE)
+ {
+ print_fatal_error(data, "Bad IN-GROUP group tag \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+ else if (data->last_expect)
+ {
+ data->last_expect->in_group = in_group;
+ }
+ else
+ {
+ print_fatal_error(data, "IN-GROUP without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "REPEAT-LIMIT"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing REPEAT-LIMIT value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ else if (atoi(temp) <= 0)
+ {
+ print_fatal_error(data, "Bad REPEAT-LIMIT value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_status)
+ {
+ data->last_status->repeat_limit = atoi(temp);
+ }
+ else if (data->last_expect)
+ {
+ data->last_expect->repeat_limit = atoi(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "REPEAT-LIMIT without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "REPEAT-MATCH"))
+ {
+ if (data->last_status)
+ {
+ data->last_status->repeat_match = 1;
+ }
+ else if (data->last_expect)
+ {
+ data->last_expect->repeat_match = 1;
+ }
+ else
+ {
+ print_fatal_error(data, "REPEAT-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "REPEAT-NO-MATCH"))
+ {
+ if (data->last_status)
+ {
+ data->last_status->repeat_no_match = 1;
+ }
+ else if (data->last_expect)
+ {
+ data->last_expect->repeat_no_match = 1;
+ }
+ else
+ {
+ print_fatal_error(data, "REPEAT-NO-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing SAME-COUNT-AS name on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->same_count_as = strdup(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "SAME-COUNT-AS without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "IF-DEFINED"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing IF-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->if_defined = strdup(temp);
+ }
+ else if (data->last_status)
+ {
+ data->last_status->if_defined = strdup(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "IF-DEFINED without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing IF-NOT-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ data->last_expect->if_not_defined = strdup(temp);
+ }
+ else if (data->last_status)
+ {
+ data->last_status->if_not_defined = strdup(temp);
+ }
+ else
+ {
+ print_fatal_error(data, "IF-NOT-DEFINED without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "WITH-ALL-VALUES") ||
+ !_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") ||
+ !_cups_strcasecmp(token, "WITH-ALL-RESOURCES") ||
+ !_cups_strcasecmp(token, "WITH-ALL-SCHEMES") ||
+ !_cups_strcasecmp(token, "WITH-HOSTNAME") ||
+ !_cups_strcasecmp(token, "WITH-RESOURCE") ||
+ !_cups_strcasecmp(token, "WITH-SCHEME") ||
+ !_cups_strcasecmp(token, "WITH-VALUE"))
+ {
+ off_t lastpos; /* Last file position */
+ int lastline; /* Last line number */
+
+ if (data->last_expect)
+ {
+ if (!_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") || !_cups_strcasecmp(token, "WITH-HOSTNAME"))
+ data->last_expect->with_flags = _CUPS_WITH_HOSTNAME;
+ else if (!_cups_strcasecmp(token, "WITH-ALL-RESOURCES") || !_cups_strcasecmp(token, "WITH-RESOURCE"))
+ data->last_expect->with_flags = _CUPS_WITH_RESOURCE;
+ else if (!_cups_strcasecmp(token, "WITH-ALL-SCHEMES") || !_cups_strcasecmp(token, "WITH-SCHEME"))
+ data->last_expect->with_flags = _CUPS_WITH_SCHEME;
+
+ if (!_cups_strncasecmp(token, "WITH-ALL-", 9))
+ data->last_expect->with_flags |= _CUPS_WITH_ALL;
+ }
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing %s value on line %d of \"%s\".", token, f->linenum, f->filename);
+ return (0);
+ }
+
+ /*
+ * Read additional comma-delimited values - needed since legacy test files
+ * will have unquoted WITH-VALUE values with commas...
+ */
+
+ ptr = temp + strlen(temp);
+
+ for (;;)
+ {
+ lastpos = cupsFileTell(f->fp);
+ lastline = f->linenum;
+ ptr += strlen(ptr);
+
+ if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
+ break;
+
+ if (!strcmp(ptr, ","))
+ {
+ /*
+ * Append a value...
+ */
+
+ ptr += strlen(ptr);
+
+ if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
+ break;
+ }
+ else
+ {
+ /*
+ * Not another value, stop here...
+ */
+
+ cupsFileSeek(f->fp, lastpos);
+ f->linenum = lastline;
+ *ptr = '\0';
+ break;
+ }
+ }
+
+ if (data->last_expect)
+ {
+ /*
+ * Expand any variables in the value and then save it.
+ */
+
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+
+ ptr = value + strlen(value) - 1;
+
+ if (value[0] == '/' && ptr > value && *ptr == '/')
+ {
+ /*
+ * WITH-VALUE is a POSIX extended regular expression.
+ */
+
+ data->last_expect->with_value = calloc(1, (size_t)(ptr - value));
+ data->last_expect->with_flags |= _CUPS_WITH_REGEX;
+
+ if (data->last_expect->with_value)
+ memcpy(data->last_expect->with_value, value + 1, (size_t)(ptr - value - 1));
+ }
+ else
+ {
+ /*
+ * WITH-VALUE is a literal value...
+ */
+
+ for (ptr = value; *ptr; ptr ++)
+ {
+ if (*ptr == '\\' && ptr[1])
+ {
+ /*
+ * Remove \ from \foo...
+ */
+
+ _cups_strcpy(ptr, ptr + 1);
+ }
+ }
+
+ data->last_expect->with_value = strdup(value);
+ data->last_expect->with_flags |= _CUPS_WITH_LITERAL;
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "WITH-VALUE-FROM"))
+ {
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing %s value on line %d of \"%s\".", token, f->linenum, f->filename);
+ return (0);
+ }
+
+ if (data->last_expect)
+ {
+ /*
+ * Expand any variables in the value and then save it.
+ */
+
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+
+ data->last_expect->with_value_from = strdup(value);
+ data->last_expect->with_flags = _CUPS_WITH_LITERAL;
+ }
+ else
+ {
+ print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!_cups_strcasecmp(token, "DISPLAY"))
+ {
+ /*
+ * Display attributes...
+ */
+
+ if (data->num_displayed >= (int)(sizeof(data->displayed) / sizeof(data->displayed[0])))
+ {
+ print_fatal_error(data, "Too many DISPLAY's on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ if (!_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ print_fatal_error(data, "Missing DISPLAY name on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ data->displayed[data->num_displayed] = strdup(temp);
+ data->num_displayed ++;
+ }
+ else
+ {
+ print_fatal_error(data, "Unexpected token %s seen on line %d of \"%s\".", token, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else
+ {
+ /*
+ * Scan for the start of a test (open brace)...
+ */
+
+ if (!strcmp(token, "{"))
+ {
+ /*
+ * Start new test...
+ */
+
+ if (data->show_header)
+ {
+ if (data->output == _CUPS_OUTPUT_PLIST)
+ print_xml_header(data);
+
+ if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+ cupsFilePrintf(cupsFileStdout(), "\"%s\":\n", f->filename);
+
+ data->show_header = 0;
+ }
+
+ data->compression[0] = '\0';
+ data->delay = 0;
+ data->num_expects = 0;
+ data->last_expect = NULL;
+ data->file[0] = '\0';
+ data->ignore_errors = data->def_ignore_errors;
+ strlcpy(data->name, f->filename, sizeof(data->name));
+ if ((ptr = strrchr(data->name, '.')) != NULL)
+ *ptr = '\0';
+ data->repeat_interval = 5000000;
+ data->request_id ++;
+ strlcpy(data->resource, vars->resource, sizeof(data->resource));
+ data->skip_previous = 0;
+ data->skip_test = 0;
+ data->num_statuses = 0;
+ data->last_status = NULL;
+ data->test_id[0] = '\0';
+ data->transfer = data->def_transfer;
+ data->version = data->def_version;
+
+ f->attrs = ippNew();
+ f->group_tag = IPP_TAG_ZERO;
+ }
+ else if (!strcmp(token, "DEFINE"))
+ {
+ /*
+ * DEFINE name value
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+ _ippVarsSet(vars, name, value);
+ }
+ else
+ {
+ print_fatal_error(data, "Missing DEFINE name and/or value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "DEFINE-DEFAULT"))
+ {
+ /*
+ * DEFINE-DEFAULT name value
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ if (!_ippVarsGet(vars, name))
+ {
+ _ippVarsExpand(vars, value, temp, sizeof(value));
+ _ippVarsSet(vars, name, value);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing DEFINE-DEFAULT name and/or value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "FILE-ID"))
+ {
+ /*
+ * FILE-ID "string"
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ _ippVarsExpand(vars, data->file_id, temp, sizeof(data->file_id));
+ }
+ else
+ {
+ print_fatal_error(data, "Missing FILE-ID value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "IGNORE-ERRORS"))
+ {
+ /*
+ * IGNORE-ERRORS yes
+ * IGNORE-ERRORS no
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+ {
+ data->def_ignore_errors = !_cups_strcasecmp(temp, "yes");
+ }
+ else
+ {
+ print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "INCLUDE"))
+ {
+ /*
+ * INCLUDE "filename"
+ * INCLUDE <filename>
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ /*
+ * Map the filename to and then run the tests...
+ */
+
+ _cups_testdata_t inc_data; /* Data for included file */
+ char filename[1024]; /* Mapped filename */
+
+ memcpy(&inc_data, data, sizeof(inc_data));
+ inc_data.http = NULL;
+ inc_data.pass = 1;
+ inc_data.prev_pass = 1;
+ inc_data.show_header = 1;
+
+ if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), vars, &inc_data) && data->stop_after_include_error)
+ {
+ data->pass = data->prev_pass = 0;
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing INCLUDE filename on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ data->show_header = 1;
+ }
+ else if (!strcmp(token, "INCLUDE-IF-DEFINED"))
+ {
+ /*
+ * INCLUDE-IF-DEFINED name "filename"
+ * INCLUDE-IF-DEFINED name <filename>
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ /*
+ * Map the filename to and then run the tests...
+ */
+
+ _cups_testdata_t inc_data; /* Data for included file */
+ char filename[1024]; /* Mapped filename */
+
+ memcpy(&inc_data, data, sizeof(inc_data));
+ inc_data.http = NULL;
+ inc_data.pass = 1;
+ inc_data.prev_pass = 1;
+ inc_data.show_header = 1;
+
+ if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), vars, &inc_data) && data->stop_after_include_error)
+ {
+ data->pass = data->prev_pass = 0;
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing INCLUDE-IF-DEFINED name or filename on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ data->show_header = 1;
+ }
+ else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED"))
+ {
+ /*
+ * INCLUDE-IF-NOT-DEFINED name "filename"
+ * INCLUDE-IF-NOT-DEFINED name <filename>
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ /*
+ * Map the filename to and then run the tests...
+ */
+
+ _cups_testdata_t inc_data; /* Data for included file */
+ char filename[1024]; /* Mapped filename */
+
+ memcpy(&inc_data, data, sizeof(inc_data));
+ inc_data.http = NULL;
+ inc_data.pass = 1;
+ inc_data.prev_pass = 1;
+ inc_data.show_header = 1;
+
+ if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), vars, &inc_data) && data->stop_after_include_error)
+ {
+ data->pass = data->prev_pass = 0;
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing INCLUDE-IF-NOT-DEFINED name or filename on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+
+ data->show_header = 1;
+ }
+ else if (!strcmp(token, "SKIP-IF-DEFINED"))
+ {
+ /*
+ * SKIP-IF-DEFINED variable
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)))
+ {
+ if (_ippVarsGet(vars, name))
+ data->skip_test = 1;
+ }
+ else
+ {
+ print_fatal_error(data, "Missing SKIP-IF-DEFINED variable on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
+ {
+ /*
+ * SKIP-IF-NOT-DEFINED variable
+ */
+
+ if (_ippFileReadToken(f, name, sizeof(name)))
+ {
+ if (!_ippVarsGet(vars, name))
+ data->skip_test = 1;
+ }
+ else
+ {
+ print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED variable on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "STOP-AFTER-INCLUDE-ERROR"))
+ {
+ /*
+ * STOP-AFTER-INCLUDE-ERROR yes
+ * STOP-AFTER-INCLUDE-ERROR no
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+ {
+ data->stop_after_include_error = !_cups_strcasecmp(temp, "yes");
+ }
+ else
+ {
+ print_fatal_error(data, "Missing STOP-AFTER-INCLUDE-ERROR value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "TRANSFER"))
+ {
+ /*
+ * TRANSFER auto
+ * TRANSFER chunked
+ * TRANSFER length
+ */
+
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ if (!strcmp(temp, "auto"))
+ data->def_transfer = _CUPS_TRANSFER_AUTO;
+ else if (!strcmp(temp, "chunked"))
+ data->def_transfer = _CUPS_TRANSFER_CHUNKED;
+ else if (!strcmp(temp, "length"))
+ data->def_transfer = _CUPS_TRANSFER_LENGTH;
+ else
+ {
+ print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing TRANSFER value on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else if (!strcmp(token, "VERSION"))
+ {
+ if (_ippFileReadToken(f, temp, sizeof(temp)))
+ {
+ if (!strcmp(temp, "1.0"))
+ data->def_version = 10;
+ else if (!strcmp(temp, "1.1"))
+ data->def_version = 11;
+ else if (!strcmp(temp, "2.0"))
+ data->def_version = 20;
+ else if (!strcmp(temp, "2.1"))
+ data->def_version = 21;
+ else if (!strcmp(temp, "2.2"))
+ data->def_version = 22;
+ else
+ {
+ print_fatal_error(data, "Bad VERSION \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Missing VERSION number on line %d of \"%s\".", f->linenum, f->filename);
+ return (0);
+ }
+ }
+ else
+ {
+ print_fatal_error(data, "Unexpected token %s seen on line %d of \"%s\".", token, f->linenum, f->filename);
+ return (0);
+ }
+ }
+
+ return (1);
+}
+
+
+/*
+ * 'usage()' - Show program usage.
+ */
+
+static void
+usage(void)
+{
+ _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... filenameN ]"));
+ _cupsLangPuts(stderr, _("Options:"));
+ _cupsLangPuts(stderr, _("--ippserver filename Produce ippserver attribute file"));
+ _cupsLangPuts(stderr, _("--stop-after-include-error\n"
+ " Stop tests after a failed INCLUDE"));
+ _cupsLangPuts(stderr, _("--version Show version"));
+ _cupsLangPuts(stderr, _("-4 Connect using IPv4"));
+ _cupsLangPuts(stderr, _("-6 Connect using IPv6"));
+ _cupsLangPuts(stderr, _("-C Send requests using chunking (default)"));
+ _cupsLangPuts(stderr, _("-E Test with encryption using HTTP Upgrade to TLS"));
+ _cupsLangPuts(stderr, _("-I Ignore errors"));
+ _cupsLangPuts(stderr, _("-L Send requests using content-length"));
+ _cupsLangPuts(stderr, _("-P filename.plist Produce XML plist to a file and test report to standard output"));
+ _cupsLangPuts(stderr, _("-S Test with encryption using HTTPS"));
+ _cupsLangPuts(stderr, _("-T seconds Set the receive/send timeout in seconds"));
+ _cupsLangPuts(stderr, _("-V version Set default IPP version"));
+ _cupsLangPuts(stderr, _("-X Produce XML plist instead of plain text"));
+ _cupsLangPuts(stderr, _("-c Produce CSV output"));
+ _cupsLangPuts(stderr, _("-d name=value Set named variable to value"));
+ _cupsLangPuts(stderr, _("-f filename Set default request filename"));
+ _cupsLangPuts(stderr, _("-h Validate HTTP response headers"));
+ _cupsLangPuts(stderr, _("-i seconds Repeat the last file with the given time interval"));
+ _cupsLangPuts(stderr, _("-l Produce plain text output"));
+ _cupsLangPuts(stderr, _("-n count Repeat the last file the given number of times"));
+ _cupsLangPuts(stderr, _("-q Run silently"));
+ _cupsLangPuts(stderr, _("-t Produce a test report"));
+ _cupsLangPuts(stderr, _("-v Be verbose"));
+
+ exit(1);
+}
+
+
+/*
+ * 'with_flags_string()' - Return the "WITH-xxx" predicate that corresponds to
+ the flags.
+ */
+
+static const char * /* O - WITH-xxx string */
+with_flags_string(int flags) /* I - WITH flags */
+{
+ if (flags & _CUPS_WITH_ALL)
+ {
+ if (flags & _CUPS_WITH_HOSTNAME)
+ return ("WITH-ALL-HOSTNAMES");
+ else if (flags & _CUPS_WITH_RESOURCE)
+ return ("WITH-ALL-RESOURCES");
+ else if (flags & _CUPS_WITH_SCHEME)
+ return ("WITH-ALL-SCHEMES");
+ else
+ return ("WITH-ALL-VALUES");
+ }
+ else if (flags & _CUPS_WITH_HOSTNAME)
+ return ("WITH-HOSTNAME");
+ else if (flags & _CUPS_WITH_RESOURCE)
+ return ("WITH-RESOURCE");
+ else if (flags & _CUPS_WITH_SCHEME)
+ return ("WITH-SCHEME");
+ else
+ return ("WITH-VALUE");
+}
+
+
+/*
+ * 'with_value()' - Test a WITH-VALUE predicate.
+ */
+
+static int /* O - 1 on match, 0 on non-match */
+with_value(_cups_testdata_t *data, /* I - Test data */
+ cups_array_t *errors, /* I - Errors array */
+ char *value, /* I - Value string */
+ int flags, /* I - Flags for match */
+ ipp_attribute_t *attr, /* I - Attribute to compare */
+ char *matchbuf, /* I - Buffer to hold matching value */
+ size_t matchlen) /* I - Length of match buffer */
+{
+ int i, /* Looping var */
+ count, /* Number of values */
+ match; /* Match? */
+ char temp[1024], /* Temporary value string */
+ *valptr; /* Pointer into value */
+ const char *name; /* Attribute name */
+
+
+ *matchbuf = '\0';
+ match = (flags & _CUPS_WITH_ALL) ? 1 : 0;
+
+ /*
+ * NULL matches everything.
+ */
+
+ if (!value || !*value)
+ return (1);
+
+ /*
+ * Compare the value string to the attribute value.
+ */
+
+ name = ippGetName(attr);
+ count = ippGetCount(attr);
+
+ switch (ippGetValueTag(attr))
+ {
+ case IPP_TAG_INTEGER :
+ case IPP_TAG_ENUM :
+ for (i = 0; i < count; i ++)
+ {
+ char op, /* Comparison operator */
+ *nextptr; /* Next pointer */
+ int intvalue, /* Integer value */
+ attrvalue = ippGetInteger(attr, i),
+ /* Attribute value */
+ valmatch = 0; /* Does the current value match? */
+
+ valptr = value;
+
+ while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
+ *valptr == '-' || *valptr == ',' || *valptr == '<' ||
+ *valptr == '=' || *valptr == '>')
+ {
+ op = '=';
+ while (*valptr && !isdigit(*valptr & 255) && *valptr != '-')
+ {
+ if (*valptr == '<' || *valptr == '>' || *valptr == '=')
+ op = *valptr;
+ valptr ++;
+ }
+
+ if (!*valptr)
+ break;
+
+ intvalue = (int)strtol(valptr, &nextptr, 0);
+ if (nextptr == valptr)
+ break;
+ valptr = nextptr;
+
+ if ((op == '=' && attrvalue == intvalue) ||
+ (op == '<' && attrvalue < intvalue) ||
+ (op == '>' && attrvalue > intvalue))
+ {
+ if (!matchbuf[0])
+ snprintf(matchbuf, matchlen, "%d", attrvalue);
+
+ valmatch = 1;
+ break;
+ }
+ }
+
+ if (flags & _CUPS_WITH_ALL)
+ {
+ if (!valmatch)
+ {
+ match = 0;
+ break;
+ }
+ }
+ else if (valmatch)
+ {
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match && errors)
+ {
+ for (i = 0; i < count; i ++)
+ add_stringf(data->errors, "GOT: %s=%d", name, ippGetInteger(attr, i));
+ }
+ break;
+
+ case IPP_TAG_RANGE :
+ for (i = 0; i < count; i ++)
+ {
+ char op, /* Comparison operator */
+ *nextptr; /* Next pointer */
+ int intvalue, /* Integer value */
+ lower, /* Lower range */
+ upper, /* Upper range */
+ valmatch = 0; /* Does the current value match? */
+
+ lower = ippGetRange(attr, i, &upper);
+ valptr = value;
+
+ while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
+ *valptr == '-' || *valptr == ',' || *valptr == '<' ||
+ *valptr == '=' || *valptr == '>')
+ {
+ op = '=';
+ while (*valptr && !isdigit(*valptr & 255) && *valptr != '-')
+ {
+ if (*valptr == '<' || *valptr == '>' || *valptr == '=')
+ op = *valptr;
+ valptr ++;
+ }
+
+ if (!*valptr)
+ break;
+
+ intvalue = (int)strtol(valptr, &nextptr, 0);
+ if (nextptr == valptr)
+ break;
+ valptr = nextptr;
+
+ if ((op == '=' && (lower == intvalue || upper == intvalue)) ||
+ (op == '<' && upper < intvalue) ||
+ (op == '>' && upper > intvalue))
+ {
+ if (!matchbuf[0])
+ snprintf(matchbuf, matchlen, "%d-%d", lower, upper);
+
+ valmatch = 1;
+ break;
+ }
+ }
+
+ if (flags & _CUPS_WITH_ALL)
+ {
+ if (!valmatch)
+ {
+ match = 0;
+ break;
+ }
+ }
+ else if (valmatch)
+ {
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match && errors)
+ {
+ for (i = 0; i < count; i ++)
+ {
+ int lower, upper; /* Range values */
+
+ lower = ippGetRange(attr, i, &upper);
+ add_stringf(data->errors, "GOT: %s=%d-%d", name, lower, upper);
+ }
+ }
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ for (i = 0; i < count; i ++)
+ {
+ if ((!strcmp(value, "true") || !strcmp(value, "1")) == ippGetBoolean(attr, i))
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, value, matchlen);
+
+ if (!(flags & _CUPS_WITH_ALL))
+ {
+ match = 1;
+ break;
+ }
+ }
+ else if (flags & _CUPS_WITH_ALL)
+ {
+ match = 0;
+ break;
+ }
+ }
+
+ if (!match && errors)
+ {
+ for (i = 0; i < count; i ++)
+ add_stringf(data->errors, "GOT: %s=%s", name, ippGetBoolean(attr, i) ? "true" : "false");
+ }
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ for (i = 0; i < count; i ++)
+ {
+ int xres, yres; /* Resolution values */
+ ipp_res_t units; /* Resolution units */
+
+ xres = ippGetResolution(attr, i, &yres, &units);
+ if (xres == yres)
+ snprintf(temp, sizeof(temp), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ else
+ snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+
+ if (!strcmp(value, temp))
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, value, matchlen);
+
+ if (!(flags & _CUPS_WITH_ALL))
+ {
+ match = 1;
+ break;
+ }
+ }
+ else if (flags & _CUPS_WITH_ALL)
+ {
+ match = 0;
+ break;
+ }
+ }
+
+ if (!match && errors)
+ {
+ for (i = 0; i < count; i ++)
+ {
+ int xres, yres; /* Resolution values */
+ ipp_res_t units; /* Resolution units */
+
+ xres = ippGetResolution(attr, i, &yres, &units);
+ if (xres == yres)
+ snprintf(temp, sizeof(temp), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ else
+ snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+
+ if (strcmp(value, temp))
+ add_stringf(data->errors, "GOT: %s=%s", name, temp);
+ }
+ }
+ break;
+
+ case IPP_TAG_NOVALUE :
+ case IPP_TAG_UNKNOWN :
+ return (1);
+
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ case IPP_TAG_NAME :
+ case IPP_TAG_NAMELANG :
+ case IPP_TAG_TEXT :
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_URI :
+ case IPP_TAG_URISCHEME :
+ if (flags & _CUPS_WITH_REGEX)
+ {
+ /*
+ * Value is an extended, case-sensitive POSIX regular expression...
+ */
+
+ regex_t re; /* Regular expression */
+
+ if ((i = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0)
+ {
+ regerror(i, &re, temp, sizeof(temp));
+
+ print_fatal_error(data, "Unable to compile WITH-VALUE regular expression \"%s\" - %s", value, temp);
+ return (0);
+ }
+
+ /*
+ * See if ALL of the values match the given regular expression.
+ */
+
+ for (i = 0; i < count; i ++)
+ {
+ if (!regexec(&re, get_string(attr, i, flags, temp, sizeof(temp)),
+ 0, NULL, 0))
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
+
+ if (!(flags & _CUPS_WITH_ALL))
+ {
+ match = 1;
+ break;
+ }
+ }
+ else if (flags & _CUPS_WITH_ALL)
+ {
+ match = 0;
+ break;
+ }
+ }
+
+ regfree(&re);
+ }
+ else if (ippGetValueTag(attr) == IPP_TAG_URI && !(flags & (_CUPS_WITH_SCHEME | _CUPS_WITH_HOSTNAME | _CUPS_WITH_RESOURCE)))
+ {
+ /*
+ * Value is a literal URI string, see if the value(s) match...
+ */
+
+ for (i = 0; i < count; i ++)
+ {
+ if (!compare_uris(value, get_string(attr, i, flags, temp, sizeof(temp))))
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
+
+ if (!(flags & _CUPS_WITH_ALL))
+ {
+ match = 1;
+ break;
+ }
+ }
+ else if (flags & _CUPS_WITH_ALL)
+ {
+ match = 0;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Value is a literal string, see if the value(s) match...
+ */
+
+ for (i = 0; i < count; i ++)
+ {
+ int result;
+
+ switch (ippGetValueTag(attr))
+ {
+ case IPP_TAG_URI :
+ /*
+ * Some URI components are case-sensitive, some not...
+ */
+
+ if (flags & (_CUPS_WITH_SCHEME | _CUPS_WITH_HOSTNAME))
+ result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+ else
+ result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+ break;
+
+ case IPP_TAG_MIMETYPE :
+ case IPP_TAG_NAME :
+ case IPP_TAG_NAMELANG :
+ case IPP_TAG_TEXT :
+ case IPP_TAG_TEXTLANG :
+ /*
+ * mimeMediaType, nameWithoutLanguage, nameWithLanguage,
+ * textWithoutLanguage, and textWithLanguage are defined to
+ * be case-insensitive strings...
+ */
+
+ result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+ break;
+
+ default :
+ /*
+ * Other string syntaxes are defined as lowercased so we use
+ * case-sensitive comparisons to catch problems...
+ */
+
+ result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+ break;
+ }
+
+ if (!result)
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
+
+ if (!(flags & _CUPS_WITH_ALL))
+ {
+ match = 1;
+ break;
+ }
+ }
+ else if (flags & _CUPS_WITH_ALL)
+ {
+ match = 0;
+ break;
+ }
+ }
+ }
+
+ if (!match && errors)
+ {
+ for (i = 0; i < count; i ++)
+ add_stringf(data->errors, "GOT: %s=\"%s\"", name, ippGetString(attr, i, NULL));
+ }
+ break;
+
+ case IPP_TAG_STRING :
+ if (flags & _CUPS_WITH_REGEX)
+ {
+ /*
+ * Value is an extended, case-sensitive POSIX regular expression...
+ */
+
+ void *adata; /* Pointer to octetString data */
+ int adatalen; /* Length of octetString */
+ regex_t re; /* Regular expression */
+
+ if ((i = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0)
+ {
+ regerror(i, &re, temp, sizeof(temp));
+
+ print_fatal_error(data, "Unable to compile WITH-VALUE regular expression \"%s\" - %s", value, temp);
+ return (0);
+ }
+
+ /*
+ * See if ALL of the values match the given regular expression.
+ */
+
+ for (i = 0; i < count; i ++)
+ {
+ if ((adata = ippGetOctetString(attr, i, &adatalen)) == NULL || adatalen >= (int)sizeof(temp))
+ {
+ match = 0;
+ break;
+ }
+ memcpy(temp, adata, (size_t)adatalen);
+ temp[adatalen] = '\0';
+
+ if (!regexec(&re, temp, 0, NULL, 0))
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, temp, matchlen);
+
+ if (!(flags & _CUPS_WITH_ALL))
+ {
+ match = 1;
+ break;
+ }
+ }
+ else if (flags & _CUPS_WITH_ALL)
+ {
+ match = 0;
+ break;
+ }
+ }
+
+ regfree(&re);
+
+ if (!match && errors)
+ {
+ for (i = 0; i < count; i ++)
+ {
+ adata = ippGetOctetString(attr, i, &adatalen);
+ copy_hex_string(temp, adata, adatalen, sizeof(temp));
+ add_stringf(data->errors, "GOT: %s=\"%s\"", name, temp);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Value is a literal or hex-encoded string...
+ */
+
+ unsigned char withdata[1023], /* WITH-VALUE data */
+ *adata; /* Pointer to octetString data */
+ int withlen, /* Length of WITH-VALUE data */
+ adatalen; /* Length of octetString */
+
+ if (*value == '<')
+ {
+ /*
+ * Grab hex-encoded value...
+ */
+
+ if ((withlen = (int)strlen(value)) & 1 || withlen > (int)(2 * (sizeof(withdata) + 1)))
+ {
+ print_fatal_error(data, "Bad WITH-VALUE hex value.");
+ return (0);
+ }
+
+ withlen = withlen / 2 - 1;
+
+ for (valptr = value + 1, adata = withdata; *valptr; valptr += 2)
+ {
+ int ch; /* Current character/byte */
+
+ if (isdigit(valptr[0]))
+ ch = (valptr[0] - '0') << 4;
+ else if (isalpha(valptr[0]))
+ ch = (tolower(valptr[0]) - 'a' + 10) << 4;
+ else
+ break;
+
+ if (isdigit(valptr[1]))
+ ch |= valptr[1] - '0';
+ else if (isalpha(valptr[1]))
+ ch |= tolower(valptr[1]) - 'a' + 10;
+ else
+ break;
+
+ *adata++ = (unsigned char)ch;
+ }
+
+ if (*valptr)
+ {
+ print_fatal_error(data, "Bad WITH-VALUE hex value.");
+ return (0);
+ }
+ }
+ else
+ {
+ /*
+ * Copy literal string value...
+ */
+
+ withlen = (int)strlen(value);
+
+ memcpy(withdata, value, (size_t)withlen);
+ }
+
+ for (i = 0; i < count; i ++)
+ {
+ adata = ippGetOctetString(attr, i, &adatalen);
+
+ if (withlen == adatalen && !memcmp(withdata, adata, (size_t)withlen))
+ {
+ if (!matchbuf[0])
+ copy_hex_string(matchbuf, adata, adatalen, matchlen);
+
+ if (!(flags & _CUPS_WITH_ALL))
+ {
+ match = 1;
+ break;
+ }
+ }
+ else if (flags & _CUPS_WITH_ALL)
+ {
+ match = 0;
+ break;
+ }
+ }
+
+ if (!match && errors)
+ {
+ for (i = 0; i < count; i ++)
+ {
+ adata = ippGetOctetString(attr, i, &adatalen);
+ copy_hex_string(temp, adata, adatalen, sizeof(temp));
+ add_stringf(data->errors, "GOT: %s=\"%s\"", name, temp);
+ }
+ }
+ }
+ break;
+
+ default :
+ break;
+ }
+
+ return (match);
+}
+
+
+/*
+ * 'with_value_from()' - Test a WITH-VALUE-FROM predicate.
+ */
+
+static int /* O - 1 on match, 0 on non-match */
+with_value_from(
+ cups_array_t *errors, /* I - Errors array */
+ ipp_attribute_t *fromattr, /* I - "From" attribute */
+ ipp_attribute_t *attr, /* I - Attribute to compare */
+ char *matchbuf, /* I - Buffer to hold matching value */
+ size_t matchlen) /* I - Length of match buffer */
+{
+ int i, j, /* Looping vars */
+ count = ippGetCount(attr), /* Number of attribute values */
+ match = 1; /* Match? */
+
+
+ *matchbuf = '\0';
+
+ /*
+ * Compare the from value(s) to the attribute value(s)...
+ */
+
+ switch (ippGetValueTag(attr))
+ {
+ case IPP_TAG_INTEGER :
+ if (ippGetValueTag(fromattr) != IPP_TAG_INTEGER && ippGetValueTag(fromattr) != IPP_TAG_RANGE)
+ goto wrong_value_tag;
+
+ for (i = 0; i < count; i ++)
+ {
+ int value = ippGetInteger(attr, i);
+ /* Current integer value */
+
+ if (ippContainsInteger(fromattr, value))
+ {
+ if (!matchbuf[0])
+ snprintf(matchbuf, matchlen, "%d", value);
+ }
+ else
+ {
+ add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
+ match = 0;
+ }
+ }
+ break;
+
+ case IPP_TAG_ENUM :
+ if (ippGetValueTag(fromattr) != IPP_TAG_ENUM)
+ goto wrong_value_tag;
+
+ for (i = 0; i < count; i ++)
+ {
+ int value = ippGetInteger(attr, i);
+ /* Current integer value */
+
+ if (ippContainsInteger(fromattr, value))
+ {
+ if (!matchbuf[0])
+ snprintf(matchbuf, matchlen, "%d", value);
+ }
+ else
+ {
+ add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
+ match = 0;
+ }
+ }
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ if (ippGetValueTag(fromattr) != IPP_TAG_RESOLUTION)
+ goto wrong_value_tag;
+
+ for (i = 0; i < count; i ++)
+ {
+ int xres, yres;
+ ipp_res_t units;
+ int fromcount = ippGetCount(fromattr);
+ int fromxres, fromyres;
+ ipp_res_t fromunits;
+
+ xres = ippGetResolution(attr, i, &yres, &units);
+
+ for (j = 0; j < fromcount; j ++)
+ {
+ fromxres = ippGetResolution(fromattr, j, &fromyres, &fromunits);
+ if (fromxres == xres && fromyres == yres && fromunits == units)
+ break;
+ }
+
+ if (j < fromcount)
+ {
+ if (!matchbuf[0])
+ {
+ if (xres == yres)
+ snprintf(matchbuf, matchlen, "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ else
+ snprintf(matchbuf, matchlen, "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ }
+ }
+ else
+ {
+ if (xres == yres)
+ add_stringf(errors, "GOT: %s=%d%s", ippGetName(attr), xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+ else
+ add_stringf(errors, "GOT: %s=%dx%d%s", ippGetName(attr), xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+
+ match = 0;
+ }
+ }
+ break;
+
+ case IPP_TAG_NOVALUE :
+ case IPP_TAG_UNKNOWN :
+ return (1);
+
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ case IPP_TAG_NAME :
+ case IPP_TAG_NAMELANG :
+ case IPP_TAG_TEXT :
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_URISCHEME :
+ for (i = 0; i < count; i ++)
+ {
+ const char *value = ippGetString(attr, i, NULL);
+ /* Current string value */
+
+ if (ippContainsString(fromattr, value))
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, value, matchlen);
+ }
+ else
+ {
+ add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
+ match = 0;
+ }
+ }
+ break;
+
+ case IPP_TAG_URI :
+ for (i = 0; i < count; i ++)
+ {
+ const char *value = ippGetString(attr, i, NULL);
+ /* Current string value */
+ int fromcount = ippGetCount(fromattr);
+
+ for (j = 0; j < fromcount; j ++)
+ {
+ if (!compare_uris(value, ippGetString(fromattr, j, NULL)))
+ {
+ if (!matchbuf[0])
+ strlcpy(matchbuf, value, matchlen);
+ break;
+ }
+ }
+
+ if (j >= fromcount)
+ {
+ add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
+ match = 0;
+ }
+ }
+ break;
+
+ default :
+ match = 0;
+ break;
+ }
+
+ return (match);
+
+ /* value tag mismatch between fromattr and attr */
+ wrong_value_tag :
+
+ add_stringf(errors, "GOT: %s OF-TYPE %s", ippGetName(attr), ippTagString(ippGetValueTag(attr)));
+
+ return (0);
+}
diff --git a/tools/printer-png.h b/tools/printer-png.h
new file mode 100644
index 000000000..9a3bfef0b
--- /dev/null
+++ b/tools/printer-png.h
@@ -0,0 +1,447 @@
+static const unsigned char printer_png[] =
+{
+ 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
+ 0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x08,0x06,0x00,0x00,0x00,0xc3,0x3e,0x61,
+ 0xcb,0x00,0x00,0x04,0x19,0x69,0x43,0x43,0x50,0x6b,0x43,0x47,0x43,0x6f,0x6c,0x6f,
+ 0x72,0x53,0x70,0x61,0x63,0x65,0x47,0x65,0x6e,0x65,0x72,0x69,0x63,0x52,0x47,0x42,
+ 0x00,0x00,0x38,0x8d,0x8d,0x55,0x5d,0x68,0x1c,0x55,0x14,0x3e,0xbb,0x73,0x67,0x23,
+ 0x24,0xce,0x53,0x6c,0x34,0x85,0x74,0xa8,0x3f,0x0d,0x25,0x0d,0x93,0x56,0x34,0xa1,
+ 0xb4,0xba,0x7f,0xdd,0xdd,0x36,0x6e,0x96,0x49,0x36,0xda,0x22,0xe8,0x64,0xf6,0xee,
+ 0xce,0x98,0xc9,0xce,0x38,0x33,0xbb,0xfd,0xa1,0x4f,0x45,0x50,0x7c,0x31,0xea,0x9b,
+ 0x14,0xc4,0xbf,0xb7,0x80,0x20,0x28,0xf5,0x0f,0xdb,0x3e,0xb4,0x2f,0x95,0x0a,0x25,
+ 0xda,0xd4,0x20,0x28,0x3e,0xb4,0xf8,0x83,0x50,0xe8,0x8b,0xa6,0xeb,0x99,0x3b,0x33,
+ 0x99,0x69,0xba,0xb1,0xde,0x65,0xee,0x7c,0xf3,0x9d,0xef,0x9e,0x7b,0xee,0xb9,0x67,
+ 0xef,0x05,0xe8,0xb9,0xaa,0x58,0x96,0x91,0x14,0x01,0x16,0x9a,0xae,0x2d,0x17,0x32,
+ 0xe2,0x73,0x87,0x8f,0x88,0x3d,0x2b,0x90,0x84,0x87,0xa0,0x17,0x06,0xa1,0x57,0x51,
+ 0x1d,0x2b,0x5d,0xa9,0x4c,0x02,0x36,0x4f,0x0b,0x77,0xb5,0x5b,0xdf,0x43,0xc2,0x7b,
+ 0x5f,0xd9,0xd5,0xdd,0xfe,0x9f,0xad,0xb7,0x46,0x1d,0x15,0x20,0x71,0x1f,0x62,0xb3,
+ 0xe6,0xa8,0x0b,0x88,0x8f,0x01,0xf0,0xa7,0x55,0xcb,0x76,0x01,0x7a,0xfa,0x91,0x1f,
+ 0x3f,0xea,0x5a,0x1e,0xf6,0x62,0xe8,0xb7,0x31,0x40,0xc4,0x2f,0x7a,0xb8,0xe1,0x63,
+ 0xd7,0xc3,0x73,0x3e,0x7e,0x8d,0x69,0x66,0xe4,0x2c,0xe2,0xd3,0x88,0x05,0x55,0x53,
+ 0x6a,0x88,0x97,0x10,0x8f,0xcc,0xc5,0xf8,0x46,0x0c,0xfb,0x31,0xb0,0xd6,0x5f,0xa0,
+ 0x4d,0x6a,0xeb,0xaa,0xe8,0xe5,0xa2,0x62,0x9b,0x75,0xdd,0xa0,0xb1,0x70,0xef,0x61,
+ 0xfe,0x9f,0x6d,0xc1,0x68,0x85,0xf3,0x6d,0xc3,0xa7,0xcf,0x99,0x9f,0x3e,0x84,0xef,
+ 0x61,0x5c,0xfb,0x2b,0x35,0x25,0xe7,0xe1,0x51,0xc4,0x4b,0xaa,0x92,0x9f,0x46,0xfc,
+ 0x08,0xe2,0x6b,0x6d,0x7d,0xb6,0x1c,0xe0,0xdb,0x96,0x9b,0x91,0x11,0x3f,0x06,0x90,
+ 0xdc,0xde,0x9a,0xaf,0xa6,0x11,0xef,0x44,0x5c,0xac,0xdb,0x07,0xaa,0xbe,0x9f,0xa4,
+ 0xad,0xb5,0x8a,0x21,0x7e,0xe7,0x84,0x36,0xf3,0x2c,0xe2,0x2d,0x88,0xcf,0x37,0xe7,
+ 0xca,0x53,0xc1,0xd8,0xab,0xaa,0x93,0xc5,0x9c,0xc1,0x76,0xc4,0xb7,0x35,0x5a,0xf2,
+ 0xf2,0x3b,0x04,0xc0,0x89,0xba,0x5b,0x9a,0xf1,0xc7,0x72,0xfb,0x6d,0x53,0x9e,0xf2,
+ 0xe7,0xe5,0xea,0x35,0x9a,0xcb,0x7b,0x79,0x44,0xfc,0xfa,0xbc,0x79,0x48,0xf6,0x7d,
+ 0x72,0x9f,0x39,0xed,0xe9,0x7c,0xe8,0xf3,0x84,0x96,0x2d,0x07,0xfc,0xa5,0x97,0x94,
+ 0x83,0x15,0xc4,0x83,0x88,0x7f,0xa1,0x46,0x41,0xf6,0xe7,0xe2,0xfe,0xb1,0xdc,0x4a,
+ 0x10,0x03,0x19,0x6a,0x1a,0xe5,0x49,0x7f,0x2e,0x92,0xa3,0x0e,0x5b,0x2f,0xe3,0x5d,
+ 0x6d,0xa6,0xe8,0xcf,0x4b,0x0c,0x17,0x37,0xd4,0x1f,0x4b,0x16,0xeb,0xfa,0x81,0x52,
+ 0xa0,0xff,0x44,0xb3,0x8b,0x72,0x80,0xaf,0x59,0x06,0xab,0x51,0x8c,0x8d,0x4f,0xda,
+ 0x2d,0xb9,0xea,0xeb,0xf9,0x51,0xc5,0xce,0x17,0x7c,0x9f,0x7c,0x85,0x36,0xab,0x81,
+ 0x7f,0xbe,0x0d,0xb3,0x09,0x05,0x28,0x98,0x30,0x87,0xbd,0x0a,0x4d,0x58,0x03,0x11,
+ 0x64,0x28,0x40,0x06,0xdf,0x16,0xd8,0x68,0xa9,0x83,0x0e,0x06,0x32,0x14,0xad,0x14,
+ 0x19,0x8a,0x5f,0xa1,0x66,0x17,0x1b,0xe7,0xc0,0x3c,0xf2,0x3a,0xb4,0x99,0xcd,0xc1,
+ 0xbe,0xc2,0x94,0xfe,0xc8,0xc8,0x5f,0x83,0xf9,0xb8,0xce,0xb4,0x2a,0x64,0x87,0x3e,
+ 0x82,0x16,0xb2,0x1a,0xfc,0x8e,0xac,0x16,0xd3,0x65,0xf1,0xab,0x85,0x5c,0x63,0x13,
+ 0x3f,0x7e,0x2c,0x37,0x02,0x3f,0x26,0x19,0x20,0x12,0xd9,0x83,0xcf,0x5e,0x32,0x49,
+ 0xf6,0x91,0x71,0x32,0x01,0x22,0x79,0x8a,0x3c,0x4d,0xf6,0x93,0x1c,0xb2,0x13,0x64,
+ 0xef,0xfa,0xd8,0x4a,0x6c,0x45,0x5e,0x3c,0x37,0xd6,0xfd,0xbc,0x8c,0x33,0x52,0xa6,
+ 0x9b,0x45,0xdd,0x39,0xb4,0xbb,0xa0,0x60,0xff,0x33,0x2a,0x4c,0x5c,0x53,0xd7,0xac,
+ 0x2c,0x0e,0xb6,0x86,0x23,0xcb,0x29,0xfb,0x05,0x5d,0xbd,0xfc,0xc6,0x5f,0xb1,0x5c,
+ 0xe9,0x2c,0x37,0x51,0xb6,0xe2,0x19,0x9d,0xba,0x57,0xce,0xf9,0x5f,0xf9,0xeb,0xfc,
+ 0x32,0xf6,0x2b,0xfc,0x6a,0xa4,0xe0,0x7f,0xe4,0x57,0xf1,0xb7,0x72,0xc7,0x5a,0xcc,
+ 0xbb,0xb2,0x4c,0xc3,0xec,0x6c,0x58,0x73,0x77,0x55,0x1a,0x6d,0x06,0xe3,0x16,0xf0,
+ 0xd1,0x99,0xc5,0x89,0xc5,0x1d,0xf3,0x71,0xf1,0xe4,0x57,0x0f,0x46,0x7e,0x96,0xc9,
+ 0x99,0xe7,0xaf,0xf4,0x5d,0x3c,0x59,0x6f,0x2e,0x0e,0x46,0xac,0x97,0x05,0xfa,0x6a,
+ 0xf9,0x56,0x19,0x4e,0x8d,0x44,0xac,0xf4,0x83,0xf4,0x87,0xb4,0x2c,0xbd,0x27,0x7d,
+ 0x28,0xfd,0xc6,0xbd,0xcd,0x7d,0xca,0x7d,0xcd,0x7d,0xce,0x7d,0xc1,0x5d,0x02,0x91,
+ 0x3b,0xcb,0x9d,0xe3,0xbe,0xe1,0x2e,0x70,0x1f,0x73,0x5f,0xc6,0xf6,0x6a,0xf3,0x1a,
+ 0x5a,0xdf,0x7b,0x16,0x79,0x18,0xb7,0x67,0xe9,0x96,0x6b,0xac,0x4a,0x21,0x23,0x6c,
+ 0x15,0x1e,0x16,0x72,0xc2,0x36,0xe1,0x51,0x61,0x32,0xf2,0x27,0x0c,0x08,0x63,0x42,
+ 0x51,0xd8,0x81,0x96,0xad,0xeb,0xfb,0x16,0x9f,0x2f,0x9e,0x3d,0x1d,0x0e,0x63,0x1f,
+ 0xe6,0xa7,0xfb,0x5c,0xbe,0x2e,0x56,0x01,0x89,0xfb,0xb1,0x02,0xf4,0x4d,0xfe,0x55,
+ 0x55,0x54,0xe9,0x70,0x94,0x29,0x1d,0x56,0x6f,0x4d,0x38,0xbe,0x41,0x13,0x8c,0x24,
+ 0x43,0x64,0x8c,0x94,0x36,0x54,0xf7,0xb8,0x57,0xf3,0xa1,0x22,0x95,0x4f,0xe5,0x52,
+ 0x69,0x10,0x53,0x3b,0x53,0x13,0xa9,0xb1,0xd4,0x41,0x0f,0x87,0xb3,0xa6,0x76,0xa0,
+ 0x6d,0x02,0xfb,0xfc,0x1d,0xd5,0xa9,0x6e,0xb2,0x52,0xea,0xd2,0x63,0xde,0x7d,0x02,
+ 0x59,0xd3,0x3a,0x6e,0xeb,0x0d,0xcd,0x15,0x77,0x4b,0xd2,0x93,0x62,0x1a,0xaf,0x36,
+ 0x2a,0x96,0x9a,0xea,0xe8,0x88,0xa8,0x18,0x86,0xc8,0x4c,0x8e,0x68,0x53,0x87,0xda,
+ 0x6d,0x5a,0x1b,0x05,0xef,0xde,0xf4,0x8f,0xf4,0x9b,0x32,0xbb,0x0f,0x13,0x5b,0x2e,
+ 0x47,0x9c,0xfb,0x0c,0xc0,0xbe,0x3f,0xf1,0xec,0xfb,0x2e,0xe2,0x8e,0xb4,0x00,0x96,
+ 0x1c,0x80,0x81,0xc7,0x23,0x6e,0x18,0xcf,0xca,0x07,0xde,0x05,0x38,0xf3,0x84,0xda,
+ 0xb2,0xdb,0xc1,0x1d,0x91,0x48,0x7c,0x0b,0xe0,0xd4,0xf7,0xec,0xf6,0xbf,0xfa,0x32,
+ 0x78,0x7e,0xfd,0xd4,0xe9,0xdc,0xc4,0x73,0xac,0xe7,0x2d,0x80,0xb5,0x37,0x3b,0x9d,
+ 0xbf,0xdf,0xef,0x74,0xd6,0x3e,0x40,0xff,0xab,0x00,0x67,0x8d,0x7f,0x01,0xa0,0x9f,
+ 0x7c,0x55,0x03,0x5c,0x0b,0xef,0x00,0x00,0x03,0x62,0x69,0x54,0x58,0x74,0x58,0x4d,
+ 0x4c,0x3a,0x63,0x6f,0x6d,0x2e,0x61,0x64,0x6f,0x62,0x65,0x2e,0x78,0x6d,0x70,0x00,
+ 0x00,0x00,0x00,0x00,0x3c,0x78,0x3a,0x78,0x6d,0x70,0x6d,0x65,0x74,0x61,0x20,0x78,
+ 0x6d,0x6c,0x6e,0x73,0x3a,0x78,0x3d,0x22,0x61,0x64,0x6f,0x62,0x65,0x3a,0x6e,0x73,
+ 0x3a,0x6d,0x65,0x74,0x61,0x2f,0x22,0x20,0x78,0x3a,0x78,0x6d,0x70,0x74,0x6b,0x3d,
+ 0x22,0x58,0x4d,0x50,0x20,0x43,0x6f,0x72,0x65,0x20,0x35,0x2e,0x34,0x2e,0x30,0x22,
+ 0x3e,0x0a,0x20,0x20,0x20,0x3c,0x72,0x64,0x66,0x3a,0x52,0x44,0x46,0x20,0x78,0x6d,
+ 0x6c,0x6e,0x73,0x3a,0x72,0x64,0x66,0x3d,0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,
+ 0x77,0x77,0x77,0x2e,0x77,0x33,0x2e,0x6f,0x72,0x67,0x2f,0x31,0x39,0x39,0x39,0x2f,
+ 0x30,0x32,0x2f,0x32,0x32,0x2d,0x72,0x64,0x66,0x2d,0x73,0x79,0x6e,0x74,0x61,0x78,
+ 0x2d,0x6e,0x73,0x23,0x22,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x72,0x64,
+ 0x66,0x3a,0x44,0x65,0x73,0x63,0x72,0x69,0x70,0x74,0x69,0x6f,0x6e,0x20,0x72,0x64,
+ 0x66,0x3a,0x61,0x62,0x6f,0x75,0x74,0x3d,0x22,0x22,0x0a,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x78,0x6d,0x6c,0x6e,0x73,0x3a,0x64,0x63,0x3d,
+ 0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x70,0x75,0x72,0x6c,0x2e,0x6f,0x72,0x67,
+ 0x2f,0x64,0x63,0x2f,0x65,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x73,0x2f,0x31,0x2e,0x31,
+ 0x2f,0x22,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x78,
+ 0x6d,0x6c,0x6e,0x73,0x3a,0x70,0x68,0x6f,0x74,0x6f,0x73,0x68,0x6f,0x70,0x3d,0x22,
+ 0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x6e,0x73,0x2e,0x61,0x64,0x6f,0x62,0x65,0x2e,
+ 0x63,0x6f,0x6d,0x2f,0x70,0x68,0x6f,0x74,0x6f,0x73,0x68,0x6f,0x70,0x2f,0x31,0x2e,
+ 0x30,0x2f,0x22,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x78,0x6d,0x6c,0x6e,0x73,0x3a,0x49,0x70,0x74,0x63,0x34,0x78,0x6d,0x70,0x45,0x78,
+ 0x74,0x3d,0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x69,0x70,0x74,0x63,0x2e,0x6f,
+ 0x72,0x67,0x2f,0x73,0x74,0x64,0x2f,0x49,0x70,0x74,0x63,0x34,0x78,0x6d,0x70,0x45,
+ 0x78,0x74,0x2f,0x32,0x30,0x30,0x38,0x2d,0x30,0x32,0x2d,0x32,0x39,0x2f,0x22,0x3e,
+ 0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x64,0x63,0x3a,0x63,0x72,
+ 0x65,0x61,0x74,0x6f,0x72,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x3c,0x72,0x64,0x66,0x3a,0x53,0x65,0x71,0x3e,0x0a,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x72,0x64,0x66,
+ 0x3a,0x6c,0x69,0x3e,0x4d,0x69,0x63,0x68,0x61,0x65,0x6c,0x20,0x53,0x77,0x65,0x65,
+ 0x74,0x3c,0x2f,0x72,0x64,0x66,0x3a,0x6c,0x69,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x72,0x64,0x66,0x3a,0x53,0x65,0x71,
+ 0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x64,0x63,0x3a,
+ 0x63,0x72,0x65,0x61,0x74,0x6f,0x72,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x3c,0x64,0x63,0x3a,0x72,0x69,0x67,0x68,0x74,0x73,0x3e,0x0a,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x72,0x64,0x66,0x3a,0x41,
+ 0x6c,0x74,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x3c,0x72,0x64,0x66,0x3a,0x6c,0x69,0x20,0x78,0x6d,0x6c,0x3a,0x6c,
+ 0x61,0x6e,0x67,0x3d,0x22,0x78,0x2d,0x64,0x65,0x66,0x61,0x75,0x6c,0x74,0x22,0x3e,
+ 0x43,0x6f,0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x32,0x30,0x31,0x30,0x20,0x41,
+ 0x70,0x70,0x6c,0x65,0x20,0x49,0x6e,0x63,0x2e,0x3c,0x2f,0x72,0x64,0x66,0x3a,0x6c,
+ 0x69,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,
+ 0x2f,0x72,0x64,0x66,0x3a,0x41,0x6c,0x74,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x3c,0x2f,0x64,0x63,0x3a,0x72,0x69,0x67,0x68,0x74,0x73,0x3e,0x0a,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x70,0x68,0x6f,0x74,0x6f,0x73,
+ 0x68,0x6f,0x70,0x3a,0x48,0x65,0x61,0x64,0x6c,0x69,0x6e,0x65,0x3e,0x4e,0x65,0x77,
+ 0x20,0x49,0x6d,0x61,0x67,0x65,0x3c,0x2f,0x70,0x68,0x6f,0x74,0x6f,0x73,0x68,0x6f,
+ 0x70,0x3a,0x48,0x65,0x61,0x64,0x6c,0x69,0x6e,0x65,0x3e,0x0a,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x3c,0x49,0x70,0x74,0x63,0x34,0x78,0x6d,0x70,0x45,0x78,
+ 0x74,0x3a,0x48,0x65,0x61,0x64,0x6c,0x69,0x6e,0x65,0x3e,0x4e,0x65,0x77,0x20,0x49,
+ 0x6d,0x61,0x67,0x65,0x3c,0x2f,0x49,0x70,0x74,0x63,0x34,0x78,0x6d,0x70,0x45,0x78,
+ 0x74,0x3a,0x48,0x65,0x61,0x64,0x6c,0x69,0x6e,0x65,0x3e,0x0a,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x3c,0x2f,0x72,0x64,0x66,0x3a,0x44,0x65,0x73,0x63,0x72,0x69,0x70,0x74,
+ 0x69,0x6f,0x6e,0x3e,0x0a,0x20,0x20,0x20,0x3c,0x2f,0x72,0x64,0x66,0x3a,0x52,0x44,
+ 0x46,0x3e,0x0a,0x3c,0x2f,0x78,0x3a,0x78,0x6d,0x70,0x6d,0x65,0x74,0x61,0x3e,0x0a,
+ 0x5d,0xae,0x72,0x2e,0x00,0x00,0x13,0xdb,0x49,0x44,0x41,0x54,0x78,0xda,0xed,0x9d,
+ 0x07,0x90,0x55,0x35,0x17,0xc7,0xd7,0x5e,0xc7,0xde,0xd7,0x55,0xec,0xbd,0x0b,0x0a,
+ 0x28,0x88,0x88,0xae,0x05,0x7b,0xd7,0xc1,0xbe,0x2a,0x28,0x22,0xba,0xe8,0x88,0x8d,
+ 0xcf,0x82,0x34,0xa5,0x0a,0xa2,0x28,0xae,0xa8,0x58,0x41,0x71,0x64,0x11,0x07,0xc5,
+ 0x2e,0xa2,0xa0,0xa8,0xe8,0x88,0xd8,0x16,0x15,0xb0,0x20,0x56,0x14,0xcd,0xb7,0xbf,
+ 0xb8,0xe7,0x9a,0xcd,0xe6,0xbe,0x77,0xef,0x6b,0xbb,0xdc,0x97,0xcc,0xfc,0x27,0xf7,
+ 0xdd,0x92,0x77,0x6f,0xce,0x3f,0x27,0x27,0xc9,0x49,0x52,0x52,0x92,0x22,0x28,0xa5,
+ 0xca,0x6a,0x51,0x59,0x8b,0xea,0x5a,0xd4,0xd4,0x62,0x89,0xf2,0xa1,0xa9,0x87,0x25,
+ 0x75,0xb2,0xaa,0xae,0x93,0x5d,0x59,0x49,0xdc,0x50,0xfb,0x50,0x69,0x2d,0xaa,0x6a,
+ 0xb1,0xd4,0xe7,0xe7,0x32,0x1f,0x96,0xd6,0xc9,0xb2,0x34,0xaa,0xf0,0xcb,0x6b,0xb1,
+ 0xc8,0xe7,0x5b,0xe2,0x02,0x32,0x2d,0x4f,0x27,0xfc,0x0a,0x5f,0xea,0x13,0xaf,0x0d,
+ 0x2a,0x52,0x95,0x7c,0x2f,0xfc,0xe2,0x20,0x41,0xb9,0xab,0xce,0xf7,0x6a,0xbf,0xb8,
+ 0xaa,0x83,0x52,0x93,0x00,0x55,0xf6,0x1d,0x7b,0xee,0xb9,0xa7,0xda,0x7c,0xf3,0xcd,
+ 0xd5,0x4d,0x37,0xdd,0xe4,0xb3,0x2b,0x99,0xa1,0xca,0x6c,0xea,0x35,0x50,0xfd,0x1b,
+ 0x6c,0xb0,0x81,0xe2,0xf2,0xe5,0x97,0x5f,0xee,0xb3,0x2a,0xb9,0x55,0x41,0x59,0x49,
+ 0x5d,0x5b,0xb1,0x41,0xe8,0xd1,0xa3,0x87,0xba,0xe8,0xa2,0x8b,0xd4,0x13,0x4f,0x3c,
+ 0xe1,0xb3,0x2a,0xb9,0xa1,0xb2,0xa4,0xae,0xc3,0xc0,0x87,0xe2,0x0c,0xd5,0x10,0xa0,
+ 0xc6,0x75,0x65,0xe8,0xd0,0xa1,0xaa,0x4f,0x9f,0x3e,0xea,0xc5,0x17,0x5f,0xac,0x77,
+ 0x7e,0xe6,0xcc,0x99,0xfa,0x7c,0xdf,0xbe,0x7d,0xd5,0x9f,0x7f,0xfe,0xa9,0xe6,0xcf,
+ 0x9f,0xaf,0xaa,0xaa,0xaa,0x54,0xa7,0x4e,0x9d,0xd4,0x29,0xa7,0x9c,0xa2,0xfa,0xf5,
+ 0xeb,0xa7,0xe6,0xcd,0x9b,0xa7,0xd2,0xa5,0xf9,0xcf,0x3f,0xff,0xa8,0x57,0x5f,0x7d,
+ 0x55,0xdd,0x78,0xe3,0x8d,0xea,0xa8,0xa3,0x8e,0xd2,0x55,0xcd,0xd3,0x4f,0x3f,0x9d,
+ 0xf2,0x6d,0xbf,0xfc,0xf2,0x4b,0x75,0xfb,0xed,0xb7,0xab,0xd3,0x4e,0x3b,0x4d,0x1d,
+ 0x77,0xdc,0x71,0xda,0x3e,0xf9,0xe4,0x93,0x4f,0xea,0xdd,0x33,0x78,0xf0,0x60,0xfd,
+ 0x1f,0xcf,0x3d,0xf7,0x5c,0x68,0x3a,0xcf,0x3f,0xff,0xbc,0xbe,0x67,0xf4,0xe8,0xd1,
+ 0x0d,0xae,0x8d,0x1f,0x3f,0x5e,0x5d,0x79,0xe5,0x95,0xaa,0xbc,0xbc,0x5c,0x5d,0x7c,
+ 0xf1,0xc5,0xea,0xc1,0x07,0x1f,0xd4,0xef,0x9a,0xd0,0x50,0x03,0x01,0x9c,0xdd,0xbb,
+ 0x61,0x36,0x00,0x42,0xe4,0x3c,0x80,0x0c,0x9b,0x6c,0xb2,0x49,0xf0,0x5b,0xc0,0xb9,
+ 0xd7,0x5e,0x7b,0x2d,0x65,0x9a,0x57,0x5d,0x75,0x55,0x83,0xe7,0xc0,0xf9,0xe7,0x9f,
+ 0xaf,0x89,0xd5,0x80,0xaa,0xd5,0xd5,0x6a,0xbd,0xf5,0xd6,0x6b,0x70,0xff,0xda,0x6b,
+ 0xaf,0xad,0xaf,0x49,0x40,0x70,0x9c,0xdf,0x76,0xdb,0x6d,0x43,0xbf,0x7a,0xfb,0xed,
+ 0xb7,0xd7,0xf7,0x74,0xee,0xdc,0x39,0x38,0xf7,0xf3,0xcf,0x3f,0x6b,0x02,0xbb,0xde,
+ 0x09,0xb2,0xfd,0xf6,0xdb,0x6f,0x49,0x24,0xc0,0x92,0x92,0xb0,0x2b,0x51,0x08,0xb0,
+ 0xe9,0xa6,0x9b,0xaa,0xe5,0x97,0x5f,0x5e,0xb5,0x68,0xd1,0x42,0x67,0x66,0xbb,0x76,
+ 0xed,0x82,0x6b,0x65,0x65,0x65,0x6a,0xc9,0x92,0x25,0xce,0x34,0xd7,0x59,0x67,0x1d,
+ 0x1d,0xf3,0x9b,0x4c,0x3f,0xef,0xbc,0xf3,0x82,0x6b,0xe0,0xce,0x3b,0xef,0xac,0xf7,
+ 0xdc,0x87,0x1f,0x7e,0xa8,0x56,0x58,0x61,0x05,0x7d,0xed,0xd0,0x43,0x0f,0x55,0x13,
+ 0x26,0x4c,0x50,0x4f,0x3e,0xf9,0xa4,0xda,0x7d,0xf7,0xdd,0xf5,0xb9,0x0d,0x37,0xdc,
+ 0x30,0x10,0xd0,0xc3,0x0f,0x3f,0x1c,0xa4,0x83,0x86,0xb1,0xc3,0x4b,0x2f,0xbd,0x14,
+ 0x5c,0x9f,0x31,0x63,0x46,0x70,0xfe,0xf4,0xd3,0x4f,0xd7,0xe7,0xd6,0x5c,0x73,0x4d,
+ 0xad,0x65,0xd0,0x12,0xd7,0x5e,0x7b,0x6d,0xf0,0xbf,0x68,0xb6,0x24,0x86,0xac,0x08,
+ 0x40,0xe9,0x9b,0x34,0x69,0x52,0xbd,0xeb,0x10,0x41,0xae,0xdf,0x75,0xd7,0x5d,0xce,
+ 0x34,0xc1,0x09,0x27,0x9c,0xa0,0x7e,0xfd,0xf5,0xd7,0xe0,0x1a,0xaa,0x7c,0xad,0xb5,
+ 0xd6,0x0a,0xc8,0xf3,0xd7,0x5f,0x7f,0x05,0xd7,0x8e,0x3c,0xf2,0x48,0x7d,0x7e,0xe7,
+ 0x9d,0x77,0x56,0x4b,0x97,0xfe,0xd7,0x60,0xf9,0xfa,0xeb,0xaf,0xd5,0xca,0x2b,0xaf,
+ 0xac,0xaf,0x0d,0x1b,0x36,0x4c,0x9f,0xfb,0xfd,0xf7,0xdf,0xf5,0x7b,0x71,0xee,0xc2,
+ 0x0b,0x2f,0x6c,0xf0,0x5d,0x67,0x9d,0x75,0x96,0xbe,0xd6,0xbc,0x79,0xf3,0xe0,0xdc,
+ 0xb4,0x69,0xd3,0xd4,0x72,0xcb,0x2d,0xa7,0xcf,0x43,0x20,0x33,0x5c,0x72,0xc9,0x25,
+ 0x01,0xd9,0xcd,0x77,0xf2,0x04,0xa8,0xc5,0x3b,0xef,0xbc,0xd3,0xe0,0x39,0xea,0x7f,
+ 0x29,0x35,0x67,0x9e,0x79,0xa6,0x33,0xcd,0x63,0x8f,0x3d,0xd6,0x59,0xaf,0xd2,0xea,
+ 0x90,0xb4,0xe7,0xcc,0x99,0xa3,0xcf,0x2d,0x5c,0xb8,0x30,0x38,0xd7,0xbf,0x7f,0xff,
+ 0x06,0xcf,0xb4,0x6e,0xdd,0x5a,0x5f,0xc3,0x06,0x91,0x50,0x51,0x51,0x11,0x68,0x9a,
+ 0x3f,0xfe,0xf8,0x23,0x38,0xff,0xd3,0x4f,0x3f,0xa9,0xd5,0x57,0x5f,0x5d,0x5f,0x1b,
+ 0x39,0x72,0x64,0x03,0x21,0x43,0x40,0xfb,0xbd,0x26,0x4f,0x9e,0x1c,0xfc,0xff,0xdc,
+ 0xb9,0x73,0x3d,0x01,0x4c,0x02,0x90,0xa1,0xae,0xb0,0xc5,0x16,0x5b,0xe8,0xeb,0x08,
+ 0x27,0x4e,0xdf,0xc2,0xbd,0xf7,0xde,0x1b,0xa4,0x4d,0xc6,0x13,0x50,0xe3,0x72,0x6e,
+ 0x8f,0x3d,0xf6,0x50,0xc7,0x1f,0x7f,0x7c,0x3d,0xac,0xbf,0xfe,0xfa,0xfa,0xda,0x41,
+ 0x07,0x1d,0x14,0xa4,0x63,0x3e,0xf3,0xe8,0xa3,0x8f,0x06,0xe7,0x47,0x8c,0x18,0x11,
+ 0xa8,0x79,0xea,0x7c,0x09,0x1d,0x3a,0x74,0xd0,0xe7,0x57,0x5a,0x69,0xa5,0x06,0xe9,
+ 0x1f,0x70,0xc0,0x01,0x41,0x5a,0xa6,0x41,0x3c,0x76,0xec,0x58,0xfd,0x9f,0xe9,0x70,
+ 0xd9,0x65,0x97,0x15,0x1f,0x01,0xf6,0xde,0x7b,0x6f,0x7d,0x7d,0x87,0x1d,0x76,0x88,
+ 0x45,0x00,0x5a,0x01,0x92,0xb6,0xa8,0xe2,0x07,0x1e,0x78,0xc0,0x69,0x98,0xd9,0xa0,
+ 0x7a,0x30,0xc3,0x76,0xdb,0x6d,0xa7,0xcf,0x53,0x7d,0x48,0xd8,0x77,0xdf,0x7d,0x03,
+ 0x43,0xd3,0x0c,0xdb,0x6c,0xb3,0x4d,0xa4,0xff,0x30,0xc9,0xd4,0xbb,0x77,0xef,0x48,
+ 0xcf,0xd8,0x85,0xa0,0x28,0x08,0x80,0x05,0xce,0xf5,0xf6,0xed,0xdb,0xc7,0x22,0xc0,
+ 0x98,0x31,0x63,0x82,0xb4,0x5f,0x7e,0xf9,0xe5,0xc0,0xfa,0x97,0x73,0xbd,0x7a,0xf5,
+ 0x52,0x2f,0xbc,0xf0,0x82,0x13,0xd4,0xe3,0x66,0xa0,0x89,0xc8,0x33,0x2b,0xae,0xb8,
+ 0xa2,0xfa,0xf6,0xdb,0x6f,0x75,0x8b,0x45,0xd2,0x79,0xf3,0xcd,0x37,0xeb,0xdd,0xbb,
+ 0xff,0xfe,0xfb,0xeb,0xf3,0x5b,0x6d,0xb5,0x55,0x68,0xfa,0x60,0xc1,0x82,0x05,0xc1,
+ 0x33,0xdf,0x7d,0xf7,0x9d,0x7a,0xf7,0xdd,0x77,0xd3,0xa2,0xa9,0x57,0x1b,0x39,0x27,
+ 0x00,0x86,0xd2,0x1a,0x6b,0xac,0xe1,0x2c,0x69,0xe9,0x08,0x70,0xeb,0xad,0xb7,0x06,
+ 0x69,0xd7,0xd4,0xd4,0x04,0x6d,0x7f,0x39,0x47,0x9f,0x41,0xd4,0xf0,0xf9,0xe7,0x9f,
+ 0x07,0x86,0x1d,0x56,0xfd,0xa5,0x97,0x5e,0xaa,0x8f,0x69,0x39,0xd8,0xe1,0xdc,0x73,
+ 0xcf,0xd5,0xd7,0x56,0x5d,0x75,0xd5,0x7a,0x36,0x43,0x31,0x84,0xac,0x08,0x80,0x81,
+ 0xe6,0xea,0x48,0x91,0xeb,0xf7,0xdd,0x77,0x9f,0x33,0xcd,0x2e,0x5d,0xba,0x38,0xff,
+ 0x93,0x01,0x28,0x29,0x89,0xa6,0x31,0xd6,0xac,0x59,0x33,0x7d,0x7e,0xe3,0x8d,0x37,
+ 0x56,0x3f,0xfe,0xf8,0x63,0xe4,0x8f,0x3b,0xf8,0xe0,0x83,0xf5,0x73,0x3b,0xee,0xb8,
+ 0xa3,0x5a,0x77,0xdd,0x75,0xf5,0xf1,0x90,0x21,0x43,0x1a,0xdc,0xc7,0x7b,0xca,0x3b,
+ 0x43,0x42,0x4f,0x80,0x88,0x04,0xd8,0x69,0xa7,0x9d,0xd4,0x5b,0x6f,0xbd,0x55,0xaf,
+ 0xa7,0x8e,0x7a,0x9f,0x6b,0x08,0xcd,0x6e,0x36,0x49,0x9a,0xb4,0x12,0xae,0xbe,0xfa,
+ 0xea,0x7a,0xfd,0x04,0xf4,0xcc,0x49,0xba,0x77,0xdf,0x7d,0x77,0xbd,0xe7,0x68,0xf7,
+ 0xcb,0x35,0x0c,0x2b,0x4a,0xb7,0x04,0x9a,0x85,0xb4,0xe7,0xed,0xe6,0x28,0xe1,0xfe,
+ 0xfb,0xef,0xaf,0x57,0x1f,0xaf,0xb6,0xda,0x6a,0x4e,0x02,0xfd,0xfd,0xf7,0xdf,0xba,
+ 0x59,0x28,0x5a,0x80,0xe6,0xab,0x49,0x40,0x54,0x3f,0xef,0x40,0xb3,0xd3,0x13,0xc0,
+ 0x20,0x80,0x00,0xa1,0x1f,0x78,0xe0,0x81,0x81,0xea,0x5f,0x65,0x95,0x55,0x9c,0xdd,
+ 0xba,0x66,0x3f,0x80,0x58,0xe3,0x6d,0xdb,0xb6,0x0d,0x6c,0x06,0xd0,0xa6,0x4d,0x1b,
+ 0x67,0x4f,0xa0,0xa8,0x70,0x40,0xdb,0x9f,0x16,0x01,0x1d,0x50,0xf2,0x9f,0x1c,0xdb,
+ 0xe1,0x97,0x5f,0x7e,0xd1,0xff,0x21,0xcf,0x99,0x4d,0x45,0x3b,0xbc,0xf7,0xde,0x7b,
+ 0xaa,0xb4,0xb4,0xb4,0x5e,0x27,0x17,0x06,0x9c,0x69,0x20,0x3e,0xfb,0xec,0xb3,0xc5,
+ 0x43,0x80,0x8d,0x36,0xda,0x48,0x7f,0xf4,0x15,0x57,0x5c,0x11,0x4a,0x80,0x29,0x53,
+ 0xa6,0x68,0x2d,0x60,0x0a,0x95,0x0c,0x7b,0xe5,0x95,0x57,0x52,0x92,0x8a,0x76,0x37,
+ 0xfd,0xed,0x94,0x36,0x79,0x8e,0xe3,0x0b,0x2e,0xb8,0xa0,0x41,0xef,0xa1,0x19,0x9e,
+ 0x7a,0xea,0xa9,0x7a,0x64,0x11,0x23,0x8f,0x16,0x00,0x5d,0xcb,0xae,0x20,0x1d,0x3f,
+ 0xa6,0x61,0x19,0x16,0x7e,0xf8,0xe1,0x07,0xdd,0x77,0x21,0x7d,0x05,0x02,0x9a,0x9a,
+ 0x68,0x1e,0x8c,0xba,0xa2,0x21,0x40,0x58,0x70,0x19,0x81,0x58,0xd9,0x08,0xfd,0x9b,
+ 0x6f,0xbe,0x49,0xf9,0xac,0xad,0x55,0x28,0xe9,0xa8,0x6f,0xe0,0x2a,0xf5,0x61,0x61,
+ 0xd1,0xa2,0x45,0xea,0x8d,0x37,0xde,0xd0,0xcf,0xe5,0xc3,0x68,0xa3,0x4a,0xf8,0xf8,
+ 0xe3,0x8f,0x35,0x61,0x92,0xa8,0xf6,0x73,0x4e,0x80,0xa8,0xc1,0x3b,0x99,0x78,0x02,
+ 0x78,0x02,0x78,0x02,0x78,0x02,0x2c,0xd3,0x04,0x98,0x35,0x6b,0x96,0x1a,0x38,0x70,
+ 0xa0,0x1a,0x34,0x68,0x50,0xec,0xd1,0x31,0x9a,0x77,0x3c,0x9b,0xce,0x18,0xf3,0xa1,
+ 0x09,0x13,0xc0,0x07,0x4f,0x00,0x1f,0x3c,0x01,0x7c,0xf0,0x04,0xf0,0xc1,0x13,0xc0,
+ 0x07,0x4f,0x00,0x1f,0x3c,0x01,0x8a,0x23,0x24,0x75,0xae,0xa4,0x27,0x40,0xc4,0x90,
+ 0xcf,0x4e,0x2c,0xc6,0x1e,0x3c,0x01,0x9a,0x78,0xc8,0xc7,0x5c,0x49,0x26,0x9c,0xe0,
+ 0xb9,0x6c,0x7b,0x4f,0x7b,0x02,0x14,0x49,0xd8,0x65,0x97,0x5d,0xb4,0x56,0x61,0x52,
+ 0x8a,0x27,0x40,0x96,0x21,0xd3,0x79,0x87,0xf6,0x5c,0xc7,0xaf,0xbe,0xfa,0x4a,0xcf,
+ 0x4c,0xc2,0x25,0x7c,0xf8,0xf0,0xe1,0xce,0xf4,0x53,0x3d,0xcf,0x0c,0x25,0xbc,0x87,
+ 0x70,0x60,0x39,0xfa,0xe8,0xa3,0x55,0xcf,0x9e,0x3d,0xb5,0x03,0xa9,0x19,0x66,0xcf,
+ 0x9e,0xad,0x9f,0x91,0x6a,0x05,0x3f,0x45,0x7e,0x03,0xba,0xd8,0xed,0x10,0x65,0x4e,
+ 0x64,0xdc,0xef,0x49,0x1c,0x01,0x32,0x9d,0x77,0x68,0x0e,0x6e,0xe1,0x63,0x20,0xd3,
+ 0xd6,0xec,0xb9,0x83,0x51,0x3c,0xa4,0x3e,0xfb,0xec,0xb3,0xc0,0xf5,0xdc,0x04,0xde,
+ 0x45,0xa6,0xff,0xa4,0xe9,0xfd,0x6c,0x03,0x0f,0x26,0x33,0x44,0x9d,0x13,0x19,0xf7,
+ 0x7b,0x12,0x4b,0x80,0xb8,0xf3,0x0e,0xcd,0x0c,0x63,0x8e,0xa1,0x4c,0x40,0x21,0xb3,
+ 0x10,0x54,0x1c,0x02,0x88,0xb7,0x12,0x02,0x87,0x6c,0xbb,0xee,0xba,0x6b,0x70,0x0d,
+ 0x0f,0x28,0x53,0x03,0xdc,0x76,0xdb,0x6d,0x41,0x9a,0xbb,0xed,0xb6,0x9b,0xfe,0x0d,
+ 0x18,0x2c,0x93,0x10,0x67,0x4e,0x64,0xdc,0xef,0x49,0x2c,0x01,0xe2,0xce,0x3b,0x34,
+ 0x33,0x8c,0x89,0xae,0xe3,0xc6,0x8d,0x8b,0xd5,0x0a,0xb0,0x7d,0x24,0xbb,0x77,0xef,
+ 0x1e,0x58,0xf5,0xc4,0xe2,0x93,0xc8,0xc4,0xd9,0xb8,0x36,0x40,0x9c,0x39,0x91,0x71,
+ 0xbf,0x27,0xb1,0x04,0x88,0x33,0xef,0xd0,0xce,0xb0,0x01,0x03,0x06,0xc4,0x6e,0x06,
+ 0x9a,0xcf,0x33,0xbf,0xc0,0x0e,0x38,0xb9,0x86,0xcd,0x47,0x48,0x45,0x80,0x4c,0xe6,
+ 0x44,0xc6,0xf9,0x9e,0x44,0xdb,0x00,0xae,0xe0,0x9a,0x77,0x68,0x67,0x58,0x2a,0x07,
+ 0x97,0x28,0x04,0x70,0xb9,0x9c,0x9f,0x7c,0xf2,0xc9,0x19,0x11,0x20,0x93,0x39,0x91,
+ 0x71,0xbe,0xa7,0xe8,0x08,0xe0,0x9a,0x77,0x98,0x6b,0x02,0xb8,0x9e,0xcf,0x94,0x00,
+ 0x99,0xce,0x89,0xf4,0x04,0x88,0x31,0xef,0xb0,0x29,0x13,0x20,0xd3,0x39,0x91,0x9e,
+ 0x00,0x31,0xe6,0x1d,0x36,0x15,0x02,0xd0,0xbe,0x77,0xb5,0xfd,0x33,0x99,0x13,0x59,
+ 0xf4,0x04,0x88,0x3b,0xef,0xb0,0x31,0x09,0x20,0xcd,0x39,0xd7,0xac,0x26,0x42,0x26,
+ 0x73,0x22,0x8b,0x9e,0x00,0x71,0xe7,0x1d,0x36,0x26,0x01,0x30,0xe6,0xe4,0x9d,0x59,
+ 0x6d,0xcd,0x0e,0x99,0xcc,0x89,0x6c,0x92,0x04,0xa0,0x3d,0xcc,0x8a,0x1a,0x0c,0x7a,
+ 0x1c,0x7e,0xf8,0xe1,0x01,0x0e,0x3b,0xec,0x30,0xbd,0x3a,0x07,0xb3,0x78,0x01,0xeb,
+ 0x09,0x00,0xce,0xd1,0x85,0x7b,0xe2,0x89,0x27,0xea,0x66,0x4e,0xd7,0xae,0x5d,0xd5,
+ 0x0d,0x37,0xdc,0xa0,0x67,0xf6,0xd2,0x11,0x32,0x7d,0xfa,0x74,0x3d,0x8d,0x2b,0xac,
+ 0x1f,0x20,0xce,0xbc,0xc3,0xc6,0x24,0x80,0x39,0x2b,0x99,0x4e,0x1b,0xbe,0x99,0x2e,
+ 0x64,0x33,0xc4,0x9d,0x13,0xd9,0x64,0x08,0x80,0x9a,0x9d,0x3a,0x75,0xaa,0xee,0x94,
+ 0x81,0xbd,0x22,0x74,0x96,0x71,0x93,0xd8,0x05,0xf3,0x9e,0xb0,0xfb,0x5b,0xb5,0x6a,
+ 0xa5,0x3b,0x56,0x20,0x96,0xa8,0xf3,0x4c,0xe7,0x1d,0xd2,0x33,0x28,0xf7,0x2d,0x5e,
+ 0xbc,0x38,0xf4,0x7b,0xc2,0xe6,0x4a,0xa6,0x7b,0x9e,0xfa,0x9d,0x6b,0x54,0x41,0xae,
+ 0x70,0xdd,0x75,0xd7,0xd5,0x9b,0x94,0xba,0xdf,0x7e,0xfb,0x35,0xb8,0x27,0xce,0x9c,
+ 0xc8,0xa8,0xdf,0x93,0x37,0x02,0x20,0x10,0xea,0x2b,0x98,0x4f,0x69,0xa6,0x37,0xcb,
+ 0x04,0x2c,0x97,0xd8,0x3c,0x0e,0xbb,0x6e,0x82,0xf5,0x7a,0x10,0x3e,0xa4,0x02,0xac,
+ 0x08,0x86,0x2a,0xe4,0x3f,0x73,0x35,0xef,0xb0,0x31,0x02,0xef,0xcf,0xec,0x64,0x96,
+ 0xb0,0xb3,0x35,0x9b,0x19,0xf2,0x31,0x27,0x32,0xa7,0x04,0x40,0xe5,0xd0,0x7e,0x3d,
+ 0xe6,0x98,0x63,0xf4,0x48,0x58,0xae,0x80,0xe0,0x99,0xbf,0x4f,0x2f,0x9f,0xd9,0x19,
+ 0xc2,0x28,0x1b,0x19,0x81,0x26,0xf0,0xb3,0x8e,0x1a,0x91,0x00,0x2c,0xa0,0x30,0x6a,
+ 0xd4,0xa8,0x60,0xb8,0x32,0x57,0x60,0xcd,0x81,0xbd,0xf6,0xda,0x4b,0x75,0xec,0xd8,
+ 0xb1,0x41,0x4f,0x18,0xc0,0x28,0x62,0xb5,0x2f,0xb4,0x80,0x27,0x40,0x81,0x09,0x40,
+ 0xa6,0xd3,0xa1,0x42,0x1d,0x84,0xba,0x17,0xb5,0x8c,0xe1,0x26,0xb1,0x0b,0xe6,0x3d,
+ 0xae,0xfb,0xc5,0x66,0xa0,0x8d,0x4c,0x7d,0x4f,0xa9,0x37,0x49,0x41,0x55,0x00,0x31,
+ 0x38,0x66,0x54,0x8d,0xea,0x86,0xc1,0x1d,0x4f,0x80,0x02,0x10,0x80,0xba,0xea,0xed,
+ 0xb7,0xdf,0x56,0xdd,0xba,0x75,0xd3,0x82,0x61,0xb8,0xd5,0x06,0x64,0x90,0xd8,0x05,
+ 0xf3,0x1e,0xfb,0x7e,0x84,0xcb,0x3a,0xbe,0xb4,0x08,0x10,0xb0,0x08,0x9f,0x98,0xaa,
+ 0x00,0x62,0xb0,0x04,0x1d,0x96,0x30,0xe7,0xa8,0x37,0xbf,0xff,0xfe,0x7b,0x4d,0x00,
+ 0x3f,0xef,0x30,0xcf,0x04,0xc0,0xe0,0xc2,0xbb,0x05,0x6b,0x36,0x9f,0x40,0xb0,0x26,
+ 0xb0,0x27,0x30,0x26,0x71,0xb4,0x40,0xf0,0x8c,0xb1,0xb3,0x2a,0x09,0xe7,0x59,0xb1,
+ 0x03,0x3b,0x20,0x89,0x4b,0xb8,0x36,0x19,0x02,0x50,0xea,0x11,0x3e,0xcd,0xa8,0x7c,
+ 0x0b,0x1f,0x20,0x58,0x01,0x4e,0x10,0x18,0x7f,0x22,0x78,0x56,0xfb,0x42,0x43,0x6c,
+ 0xbd,0xf5,0xd6,0x8d,0x4e,0x00,0xaa,0x40,0x56,0x44,0xf9,0xe8,0xa3,0x8f,0x92,0x4b,
+ 0x00,0x84,0x4f,0xe6,0xe2,0xa9,0x52,0x08,0xe1,0x03,0x54,0x3d,0x75,0x7f,0xcb,0x96,
+ 0x2d,0x03,0x75,0x2f,0x82,0x67,0xfd,0xa1,0x2d,0xb7,0xdc,0x52,0xfb,0xe7,0xe7,0x8b,
+ 0x00,0xd8,0x14,0xd8,0x16,0x0c,0xb4,0x30,0x6a,0x88,0x2f,0x1e,0xfd,0x0a,0x27,0x9d,
+ 0x74,0x92,0x7e,0x27,0xc8,0x87,0xd7,0x11,0x1d,0x4d,0x54,0x85,0x38,0x67,0x2c,0xcb,
+ 0xfb,0x09,0x94,0xa4,0x63,0x39,0x6e,0x49,0x58,0xe2,0x85,0x22,0x00,0x16,0x3f,0x82,
+ 0xe7,0x58,0xd4,0xbd,0x29,0x78,0xf6,0x22,0xa0,0x53,0x86,0xfb,0xa2,0x12,0x00,0x0d,
+ 0x86,0xbf,0xde,0xeb,0xaf,0xbf,0xae,0x7b,0x12,0xe9,0x2c,0xb9,0xfe,0xfa,0xeb,0xf5,
+ 0x02,0x52,0xd8,0x1b,0xfc,0x07,0x3d,0x71,0xac,0x15,0xcc,0x0a,0x67,0x08,0x18,0xa3,
+ 0x12,0xb0,0xbe,0x20,0xc2,0x06,0xf4,0xc4,0x71,0x0f,0xab,0x90,0x7f,0xfa,0xe9,0xa7,
+ 0xda,0x1d,0x8b,0xce,0x25,0xe9,0x8b,0x48,0x14,0x01,0x68,0x5b,0xd3,0xc6,0xbe,0xe5,
+ 0x96,0x5b,0x0a,0x26,0x7c,0x21,0x80,0x08,0x5e,0xd4,0x3d,0xc2,0xc7,0xcf,0x0e,0x21,
+ 0xe1,0x08,0x81,0x43,0x24,0x06,0x23,0x3d,0x8d,0x74,0x9e,0x3c,0xf3,0xcc,0x33,0xda,
+ 0xe1,0x83,0x11,0x3f,0x76,0xf9,0xa0,0x23,0x09,0xd2,0xd2,0xc3,0xc6,0xda,0x80,0xf4,
+ 0x9a,0xe1,0x12,0x86,0x40,0x21,0x0f,0xe9,0x90,0x06,0xc2,0xe5,0x1c,0x83,0x2d,0x9b,
+ 0x6d,0xb6,0x99,0x76,0x17,0x63,0xa1,0x6b,0x13,0xa4,0xc1,0x7f,0xb2,0x72,0x18,0xfe,
+ 0x85,0xac,0x8b,0x48,0xf3,0x93,0x92,0x8f,0x01,0x8a,0xeb,0x19,0x1d,0x4d,0x89,0x23,
+ 0x00,0xa5,0x8a,0x8e,0x1d,0xac,0xf0,0x42,0x13,0x80,0x85,0x9e,0x45,0xf0,0x08,0x00,
+ 0xe1,0x8b,0xe0,0x11,0x1e,0x02,0xc3,0xdf,0x8d,0xfe,0x70,0xce,0xa3,0x15,0x38,0x27,
+ 0xa5,0x56,0x04,0x8a,0x00,0x19,0xfd,0xa3,0x1b,0x35,0x2a,0xf8,0x6f,0x62,0x9e,0x83,
+ 0x2c,0x08,0xfe,0xec,0xb3,0xcf,0xd6,0x24,0x63,0x25,0x34,0xd6,0x19,0xa6,0xf5,0x81,
+ 0x06,0x60,0x75,0x34,0xfa,0x21,0x12,0x47,0x00,0x3e,0x86,0xd2,0xcf,0x07,0x32,0xd8,
+ 0x50,0x48,0x02,0x50,0xb2,0x45,0xdd,0x23,0x78,0x84,0x80,0x5b,0x34,0x82,0x46,0x35,
+ 0xa3,0x82,0x39,0xc7,0xe2,0x94,0xf4,0x15,0xd0,0x1f,0x2e,0xb1,0x79,0x4c,0x6c,0x83,
+ 0xf3,0x3c,0x27,0x55,0x0a,0xe4,0x22,0x2d,0xfe,0x07,0xc2,0x40,0x24,0x08,0x84,0xba,
+ 0xa7,0x2a,0x60,0xf0,0x8a,0xd5,0x46,0x99,0x0d,0x84,0x47,0x11,0xfb,0x10,0xe1,0xaa,
+ 0x05,0x01,0xd0,0x02,0x54,0x3f,0x68,0x00,0x0a,0x4b,0xa2,0x08,0x80,0xfa,0x67,0x95,
+ 0x4d,0x3e,0xb2,0x90,0xc2,0x07,0x47,0x1c,0x71,0x44,0x50,0xcf,0x23,0x78,0x31,0xb8,
+ 0x50,0xe5,0x94,0x68,0x8c,0x42,0xd4,0xbb,0x40,0x6c,0x05,0x0c,0x45,0x11,0x2c,0xc2,
+ 0xe4,0x79,0xd1,0x1a,0x94,0x62,0x88,0xc3,0xe2,0xd1,0xc4,0xa4,0x47,0xda,0xdc,0x87,
+ 0xa6,0xa1,0xba,0x21,0x1d,0x06,0x62,0x18,0x31,0x94,0xd1,0x48,0xb4,0x51,0x18,0x30,
+ 0x56,0x25,0x36,0x8f,0xa3,0xde,0x6f,0x23,0xdd,0xf3,0x71,0xc0,0x40,0x19,0xdd,0xe7,
+ 0x51,0x48,0x59,0x12,0x66,0xfc,0x31,0xf0,0xc0,0x62,0x89,0x8d,0x41,0x00,0x11,0x3c,
+ 0x82,0x43,0xd5,0x53,0x2a,0x11,0xbe,0x59,0x15,0xd8,0x42,0xa5,0x3a,0xa0,0x4e,0x47,
+ 0xfd,0x73,0x2f,0x6a,0x9c,0x12,0x0f,0x49,0x10,0x2c,0xbd,0x87,0xf4,0x27,0x1c,0x72,
+ 0xc8,0x21,0x91,0x00,0x09,0x24,0x36,0x8f,0x73,0x75,0x7f,0x5c,0xd8,0xe9,0xdb,0xe0,
+ 0x1a,0xe4,0x45,0x63,0xcb,0x48,0x2a,0x5a,0xdc,0x74,0x27,0x8f,0x4c,0x00,0x54,0x1a,
+ 0xa3,0x52,0xef,0xbf,0xff,0x7e,0x41,0x85,0x4f,0x29,0xc6,0x10,0xa3,0xb4,0x63,0x71,
+ 0x53,0x52,0xa5,0x8e,0xa7,0x44,0x73,0x8d,0x3a,0x1a,0xc1,0x72,0x3f,0x7d,0x04,0x8c,
+ 0x0e,0xe2,0x22,0x0d,0xe3,0x25,0x36,0x8f,0x89,0x5d,0xd7,0x6d,0x14,0xfa,0x7e,0x1b,
+ 0xe9,0xde,0x3f,0x15,0xd8,0xef,0x80,0x96,0x13,0xfe,0x15,0xe6,0x50,0x3a,0x32,0x84,
+ 0x04,0xa9,0x34,0x41,0x28,0x01,0xb0,0x70,0xa9,0xeb,0x0a,0x25,0x7c,0x1c,0x26,0xa8,
+ 0x9f,0x69,0x96,0xc9,0x47,0xf0,0x41,0x30,0x1a,0xf0,0x91,0xb9,0x04,0x5a,0x41,0x62,
+ 0xf3,0x38,0x57,0xf7,0x17,0x02,0xfc,0x3f,0xf9,0x46,0xe9,0x37,0x1d,0x6d,0xc4,0x6f,
+ 0x02,0xbf,0x42,0x19,0x2c,0x8b,0x4d,0x00,0xd9,0x11,0xa3,0xd0,0x55,0x80,0x7c,0x00,
+ 0xec,0x47,0xa5,0x09,0xf0,0xee,0x91,0xd8,0x3c,0x0e,0xbb,0x9e,0x0e,0xd9,0x3e,0x1f,
+ 0x37,0x7d,0x1b,0xe9,0xbe,0x27,0xdd,0xfd,0x54,0x6d,0xd4,0xf5,0x61,0x0e,0x36,0x14,
+ 0x1e,0x7a,0x29,0x65,0xb0,0x6c,0x99,0x20,0x00,0xfd,0xfd,0x7c,0x14,0x5d,0xc0,0xd4,
+ 0x6b,0x12,0xbb,0x60,0xde,0xe3,0xba,0x3f,0xdb,0xeb,0xe9,0x10,0x37,0x7d,0x1b,0x99,
+ 0xbe,0x0f,0x25,0x1f,0x7b,0x26,0xcc,0xa3,0xca,0x84,0x39,0x58,0xb6,0xcc,0x68,0x00,
+ 0x8f,0xd4,0x48,0x25,0x70,0x4a,0xbd,0xd8,0x01,0xc4,0x51,0x7a,0x4a,0x3d,0x01,0x96,
+ 0x61,0x02,0x98,0xc2,0xb6,0x81,0xf6,0xf0,0x04,0x48,0x28,0x01,0x5c,0x02,0x17,0xa1,
+ 0x4b,0x9c,0x77,0x02,0x60,0x99,0x47,0xad,0x2f,0x3d,0xe2,0x03,0x43,0xcf,0x95,0xef,
+ 0xb6,0xc0,0x6d,0xa1,0x9b,0xc8,0x1b,0x01,0xb0,0x40,0x79,0x49,0x3a,0x6d,0x4c,0x30,
+ 0x08,0x23,0xb1,0x79,0x4c,0x8c,0x71,0x67,0x36,0xa3,0xc2,0x80,0xd7,0x8f,0x9d,0xee,
+ 0xb2,0x82,0x54,0xdf,0x1f,0x17,0x61,0x5d,0xf0,0x22,0x70,0x57,0x89,0xb7,0x0d,0xc9,
+ 0xbc,0x11,0x80,0xce,0x07,0xba,0x1c,0xa5,0xbb,0xd4,0xe5,0xc2,0x6d,0xba,0x7d,0xef,
+ 0xdb,0xbc,0x8d,0xda,0xaf,0xd5,0x99,0xaa,0xfd,0x61,0xd7,0xa8,0x83,0x0f,0xfd,0x17,
+ 0x72,0x4c,0xac,0x51,0x7b,0xdc,0xb6,0x7d,0x77,0xb5,0xf3,0x2e,0xad,0xf5,0x47,0xb8,
+ 0xba,0x38,0xed,0xff,0x33,0xcf,0xe5,0xe3,0x7e,0x1b,0xf6,0xf3,0x51,0xbe,0x3f,0xd5,
+ 0xf3,0x61,0xe0,0xfb,0xc3,0x34,0x6f,0x2a,0xa1,0xdb,0xad,0x88,0xbc,0x11,0x80,0x76,
+ 0xa7,0xe9,0xb9,0x93,0x0a,0xad,0x5a,0xb7,0x53,0x47,0x9f,0x78,0xb3,0xba,0xa8,0xdb,
+ 0x04,0x75,0x61,0xb7,0xa7,0x75,0xac,0x71,0xb9,0x11,0x1b,0x68,0x79,0xc0,0xa9,0xfa,
+ 0xe5,0xa3,0xa6,0x9f,0x4b,0x88,0x0b,0x9a,0xe9,0x8e,0x26,0x71,0x21,0x81,0x26,0x4c,
+ 0x45,0x00,0x57,0x93,0xd2,0x6e,0x4e,0xe6,0x8d,0x00,0xa8,0xf1,0x30,0x8f,0x5f,0x1b,
+ 0x7c,0x4c,0xcb,0x36,0x27,0xa8,0x2b,0x6f,0x7c,0x41,0x55,0xfe,0x6f,0x6a,0x5a,0x5c,
+ 0xd6,0x73,0xa2,0x6a,0xd1,0xaa,0x3c,0xa5,0x57,0xb1,0x09,0xbc,0x74,0x24,0x36,0x8f,
+ 0xa3,0xbe,0x5f,0x63,0xc3,0xf5,0xfe,0x94,0x6e,0x06,0xb7,0xc2,0x08,0x60,0x97,0x72,
+ 0x7b,0xec,0xc1,0x3c,0xce,0x0b,0x01,0x48,0x38,0xcc,0x03,0xd8,0xf6,0xf8,0x6d,0x7f,
+ 0x48,0x07,0x75,0x41,0xf7,0x7b,0x54,0xdf,0x51,0xb3,0x23,0x81,0x7b,0x79,0x26,0x2c,
+ 0xbd,0x5c,0x7a,0x20,0x67,0x92,0x7e,0x21,0xd2,0xc3,0xbe,0x92,0xb1,0x8e,0x30,0x02,
+ 0x84,0x0d,0x06,0xd9,0x83,0x4f,0x79,0x21,0x00,0x2a,0x91,0x71,0x72,0x17,0xce,0x38,
+ 0xe3,0x8c,0x20,0x06,0x6d,0xdb,0x77,0x54,0xb7,0x8c,0x9c,0xa6,0x86,0x3f,0x39,0xaf,
+ 0x21,0xc6,0x7d,0xfd,0x5f,0x5c,0x8b,0x01,0x0f,0x7c,0xa8,0x3a,0x1c,0x79,0xba,0x9e,
+ 0x5c,0x12,0x96,0x9e,0x79,0x2e,0x1f,0xc8,0xf6,0xff,0xb2,0x7d,0x9e,0xe1,0x60,0xbc,
+ 0xa0,0x18,0x14,0x0b,0x23,0x80,0x5d,0xd2,0xc3,0xc0,0xc8,0x67,0x5e,0x08,0x20,0x33,
+ 0x76,0xa3,0xa0,0x6d,0xbb,0x0e,0xea,0xec,0xae,0xfd,0x54,0xcf,0x81,0x13,0xd5,0x35,
+ 0x77,0x3c,0x1b,0xc4,0xfa,0xd8,0x88,0xbb,0xf5,0xaa,0x52,0x87,0x1f,0xd7,0x29,0x6d,
+ 0xda,0xf8,0xf0,0x49,0x6c,0x1e,0x47,0x7d,0x9f,0x7c,0xa7,0x97,0xcd,0xff,0x41,0x00,
+ 0x06,0x76,0x20,0x00,0xc0,0xef,0x21,0x4c,0x03,0xb8,0x4a,0xbb,0x3d,0xd4,0x9d,0x37,
+ 0x02,0x60,0x00,0x9e,0x73,0xce,0x39,0x91,0xc0,0x47,0xd1,0xa4,0x09,0xeb,0xb9,0x92,
+ 0xf3,0x30,0x9f,0x8c,0xb0,0x9f,0xc7,0x15,0x4b,0x62,0xf3,0x38,0xea,0xff,0xa7,0x43,
+ 0xbe,0xd3,0x8f,0xfa,0x7f,0x10,0x00,0x8f,0x25,0x11,0x3e,0x80,0x0c,0x61,0x1a,0xc0,
+ 0x55,0xda,0x5d,0xc8,0x0b,0x01,0x18,0x8f,0x47,0x60,0x2c,0x89,0xc6,0x5c,0x81,0x8a,
+ 0x8a,0x0a,0x1d,0xbb,0x20,0xd7,0x88,0x5d,0xb0,0x9f,0xb7,0xef,0x8f,0x7b,0x3d,0xdd,
+ 0xff,0xa7,0x4b,0x2f,0xdb,0xf7,0x8b,0xfb,0xfd,0x90,0x9e,0x82,0x80,0x17,0x13,0xde,
+ 0x4c,0x26,0x01,0xc2,0x0c,0x41,0x5b,0xe8,0x36,0x01,0xcc,0xb5,0x16,0xf2,0x46,0x00,
+ 0x9c,0x33,0x80,0xb8,0x4d,0xe3,0xc1,0x93,0x09,0xe4,0x59,0x62,0xf3,0x38,0xea,0xf5,
+ 0xb8,0xe9,0x15,0xfa,0xfe,0x28,0xdf,0x8f,0x93,0xab,0x2d,0x7c,0x10,0x66,0x08,0xda,
+ 0xa5,0x5c,0x84,0x6d,0x2e,0xb0,0x21,0x24,0xc8,0x0b,0x01,0xa8,0x9b,0xf0,0xa3,0xc3,
+ 0x3d,0x0b,0xf7,0x2b,0x3c,0x76,0xc4,0x69,0x33,0x15,0xc4,0x63,0xd7,0x06,0x19,0x90,
+ 0x6b,0x08,0x41,0xc5,0x05,0xdc,0x3c,0xce,0x16,0x61,0xdf,0x21,0x48,0x97,0x0f,0x22,
+ 0x7c,0xd2,0x12,0x0f,0x27,0x5b,0xf8,0x80,0x2a,0x21,0x2e,0x01,0x4c,0xd0,0xf3,0x5a,
+ 0x90,0xc1,0x20,0xf1,0xe4,0x71,0x7d,0x84,0x47,0xe6,0x08,0xb3,0x01,0x52,0x09,0xdc,
+ 0x3e,0x2e,0xd8,0x68,0x20,0x5a,0x41,0x5c,0xae,0xbd,0xf0,0xb2,0x03,0x4d,0x40,0x16,
+ 0x8f,0x0e,0xcb,0x6b,0x5b,0xf0,0xa6,0xd0,0x6d,0xf8,0xe1,0xe0,0x04,0xc2,0x55,0xea,
+ 0x6d,0xc1,0x33,0xa0,0x06,0x3c,0x01,0x12,0x4a,0x00,0x57,0x69,0x17,0xc1,0x7b,0x02,
+ 0x24,0x1c,0xae,0x92,0x6e,0x1e,0x03,0x7c,0x09,0x3c,0x01,0x12,0x0a,0x5b,0xe0,0x2e,
+ 0xe1,0x7b,0x02,0x14,0x01,0x01,0x44,0xc8,0x66,0x89,0x37,0x63,0xe0,0x09,0x90,0x50,
+ 0x02,0x98,0xc2,0x36,0x05,0xee,0x09,0x50,0x04,0x08,0x13,0xbc,0x6b,0x92,0x89,0x27,
+ 0x40,0x42,0x09,0x10,0x75,0x26,0x92,0x27,0x40,0x02,0x61,0x97,0xf4,0x30,0x30,0x7b,
+ 0xc8,0x13,0x20,0xa1,0x1a,0x20,0x4c,0xe0,0x9e,0x00,0x45,0xa2,0x01,0x5c,0xc2,0x77,
+ 0xc1,0x13,0x20,0xc1,0x04,0x10,0x21,0xdb,0x04,0x90,0xf5,0x05,0x3c,0x01,0x12,0x0a,
+ 0xbb,0x94,0x8b,0xb0,0x25,0x36,0x49,0xe0,0x09,0x50,0x24,0x04,0x08,0x5b,0x91,0x24,
+ 0x27,0x04,0xc0,0x35,0xc9,0xa3,0xe9,0xc0,0x2c,0xf1,0xae,0x25,0x68,0xcc,0x63,0xaf,
+ 0x01,0x12,0x88,0x54,0x25,0xde,0x86,0x27,0x40,0xc2,0x09,0x10,0xb6,0x90,0x14,0x0b,
+ 0x67,0x01,0x4f,0x80,0x84,0x12,0x20,0x6c,0xb5,0x30,0x59,0x31,0xcd,0x13,0x20,0xc1,
+ 0x70,0x95,0x74,0xf3,0x18,0xb0,0xaa,0xb9,0x27,0x40,0x42,0x61,0x0b,0xdc,0x16,0xbe,
+ 0xb9,0x8c,0x9d,0x27,0x40,0x82,0x09,0x20,0xa5,0x5c,0x84,0x2e,0x8b,0x6f,0xb0,0xd2,
+ 0x1a,0x0b,0x68,0x12,0x7b,0x02,0x24,0x94,0x00,0xa6,0xd0,0x81,0x29,0x74,0x56,0x0c,
+ 0x95,0x75,0x94,0x3d,0x01,0x12,0x08,0x11,0xbc,0x08,0x1c,0x98,0x8b,0x67,0x33,0x9f,
+ 0x00,0xb7,0x72,0x62,0x4f,0x80,0x04,0x42,0x04,0x6f,0x0b,0x5d,0x20,0xcb,0xe2,0xb3,
+ 0xcd,0x8d,0x27,0x40,0x02,0xe1,0x2a,0xe9,0xe6,0x3e,0x09,0x4c,0xce,0x61,0x06,0x37,
+ 0xfb,0x0d,0xcf,0x9a,0x35,0x2b,0xfb,0x95,0x42,0xa9,0x6f,0x7c,0xc6,0x37,0x0d,0x88,
+ 0xc0,0xcd,0x0d,0x30,0x88,0x99,0x4d,0x84,0xe0,0x01,0xc6,0x21,0x7b,0x28,0x56,0x57,
+ 0x57,0xeb,0xb5,0x82,0x59,0xf6,0x3f,0xab,0xc5,0xa2,0x0b,0xbd,0x65,0x8c,0x47,0x38,
+ 0xd8,0xd8,0x02,0x81,0x8b,0xb0,0x45,0xf0,0xb2,0xdd,0x0d,0x2b,0xb2,0x0d,0x19,0x32,
+ 0x44,0x8d,0x1d,0x3b,0x56,0xef,0x6c,0xf2,0xc5,0x17,0x5f,0xe8,0x8d,0x3f,0xd8,0x00,
+ 0x24,0xe3,0xe5,0xe2,0xd9,0x5d,0xcb,0x67,0x7e,0xe3,0x83,0x81,0x20,0x11,0xba,0xec,
+ 0x6b,0x24,0x7b,0x1c,0xb1,0xbf,0x51,0x97,0x2e,0x5d,0xd4,0xa0,0x41,0x83,0xf4,0xe6,
+ 0xdd,0x93,0x27,0x4f,0xd6,0xbb,0xbd,0x51,0x88,0xd9,0xd5,0x2c,0xa3,0xfd,0x02,0xd8,
+ 0x6c,0xe0,0x83,0x0f,0x3e,0xd0,0x6c,0xda,0x67,0x9f,0x7d,0xbc,0x10,0x1a,0x59,0xf8,
+ 0xe6,0xc6,0x56,0x08,0x1c,0x30,0x29,0xb7,0x73,0xe7,0xce,0x7a,0xb7,0xb4,0xc1,0x83,
+ 0x07,0x6b,0xe1,0x4f,0x9a,0x34,0x49,0x17,0x5c,0x36,0xb5,0x64,0x3f,0xa3,0x8c,0x76,
+ 0x0c,0xe1,0x21,0x76,0x0c,0x63,0xcb,0x18,0x12,0x2c,0xc4,0x96,0xb1,0x1e,0x6e,0x50,
+ 0xcf,0x8b,0xd0,0x59,0x97,0x01,0x50,0x2d,0xa3,0x99,0xd9,0xd0,0x73,0xc0,0x80,0x01,
+ 0x6a,0xf8,0xf0,0xe1,0xea,0xa1,0x87,0x1e,0xd2,0x25,0x9f,0x6a,0x9b,0x2d,0xed,0x50,
+ 0xfd,0x51,0x36,0xb3,0x82,0x00,0x4b,0x52,0x6d,0x1a,0xc5,0xbe,0x7c,0x30,0xab,0x50,
+ 0x5b,0xc7,0x7a,0xd4,0xdf,0x42,0x47,0x4a,0x3b,0x6d,0x7f,0x76,0x2a,0xed,0xdd,0xbb,
+ 0xb7,0xea,0xdb,0xb7,0xaf,0xba,0xe3,0x8e,0x3b,0xb4,0xe0,0xd9,0xd5,0x6c,0xdc,0xb8,
+ 0x71,0x7a,0x0f,0x45,0x34,0x36,0x25,0x3f,0xaa,0xf0,0x91,0x3d,0x04,0xa8,0x09,0xdb,
+ 0x36,0x6e,0xfe,0xfc,0xf9,0x6a,0xc6,0x8c,0x19,0xba,0x49,0x31,0x62,0xc4,0x08,0xd5,
+ 0xa3,0x47,0x0f,0x5f,0x1d,0x14,0x50,0xf8,0xcd,0x9a,0x35,0xd3,0x6b,0x00,0xf5,0xea,
+ 0xd5,0x2b,0x28,0xed,0x18,0x79,0xf7,0xdc,0x73,0x8f,0x2e,0xf1,0xc8,0x65,0xca,0x94,
+ 0x29,0x7a,0x47,0xf7,0x39,0x73,0xe6,0xa8,0x05,0x0b,0x16,0xc4,0xdd,0xc6,0xae,0x06,
+ 0x02,0x54,0xa7,0xda,0x38,0x72,0xee,0xdc,0xb9,0x7a,0x4b,0xf6,0x47,0x1e,0x79,0x44,
+ 0x0d,0x1b,0x36,0x4c,0xf5,0xeb,0xd7,0x4f,0x55,0x56,0x56,0xfe,0xbb,0x0c,0xac,0x6f,
+ 0x22,0xe6,0x65,0xb8,0x97,0x25,0xe0,0x4e,0x3d,0xf5,0x54,0x5d,0xb7,0x53,0xda,0x07,
+ 0x0e,0x1c,0xa8,0x0b,0x20,0xa5,0xfd,0xf1,0xc7,0x1f,0xd7,0x4d,0x3c,0x36,0xb1,0x9c,
+ 0x39,0x73,0xa6,0x16,0x3c,0xa5,0x9e,0xe6,0x9e,0xec,0x12,0x16,0x63,0x0f,0xc3,0x6a,
+ 0x08,0x50,0x99,0x6a,0xf3,0xc8,0x85,0x0b,0x17,0x06,0x9b,0x29,0x43,0x02,0x5e,0x84,
+ 0x0d,0x95,0xfb,0xf4,0xe9,0xa3,0x5f,0x90,0xad,0x65,0xc1,0xcd,0x37,0xdf,0xec,0x91,
+ 0x05,0x24,0x1f,0xc9,0x53,0x4a,0x7b,0xff,0xfe,0xfd,0xb5,0x61,0x47,0x9b,0x9e,0xd2,
+ 0x3e,0x61,0xc2,0x04,0x2d,0x83,0xe9,0xd3,0xa7,0x6b,0x79,0xd0,0xc4,0xa3,0xc4,0x53,
+ 0x48,0xd9,0x1b,0x99,0x02,0x9b,0xaa,0xb9,0x17,0x12,0x2a,0x21,0x40,0x19,0x76,0x5f,
+ 0x98,0x31,0xc8,0x06,0xc9,0x54,0x05,0xfc,0x29,0x9a,0x00,0xb5,0x33,0x66,0xcc,0x18,
+ 0xfd,0x62,0x6c,0xc2,0x3c,0x74,0xe8,0x50,0xad,0x96,0x3c,0xb2,0x07,0x79,0x49,0x9e,
+ 0x8e,0x1c,0x39,0x52,0x8d,0x1e,0x3d,0x5a,0x3d,0xf6,0xd8,0x63,0x6a,0xe2,0xc4,0x89,
+ 0xc1,0x6e,0xa5,0x6c,0x57,0x4b,0x69,0xa7,0x85,0x46,0x3d,0x9f,0xe5,0xc6,0xd5,0xc8,
+ 0xbc,0xac,0x84,0x50,0x7b,0x50,0x15,0xa6,0x05,0x84,0x04,0x68,0x02,0xaa,0x03,0x6c,
+ 0x02,0x0c,0x43,0x5a,0x07,0xb0,0x72,0xfc,0xf8,0xf1,0xda,0x08,0x61,0x57,0x6e,0x8f,
+ 0xcc,0x41,0x1e,0x92,0x97,0x6c,0x51,0x8b,0xd0,0x29,0xed,0xd3,0xa6,0x4d,0xd3,0xed,
+ 0x79,0x4a,0x3b,0xf9,0xbf,0x78,0xf1,0xe2,0x7a,0xa5,0x3d,0xcb,0xed,0x6a,0xab,0x4a,
+ 0x24,0xd4,0xfe,0x28,0xad,0xc5,0xa2,0x54,0x24,0xa0,0x3a,0x40,0xdd,0xb0,0x9f,0x30,
+ 0xad,0x03,0x9a,0x88,0x6c,0x2c,0x09,0x33,0x69,0x7a,0x78,0x64,0x0f,0xf2,0x92,0x3c,
+ 0x45,0xdb,0x52,0xd8,0xe6,0xcd,0x9b,0xa7,0x3b,0xe4,0x72,0x50,0xda,0xed,0x80,0xac,
+ 0x4b,0x4b,0xcc,0x50,0x7b,0xa2,0x3c,0xac,0x2a,0x10,0x22,0xc0,0x3c,0x88,0xc0,0x0b,
+ 0x41,0x06,0x54,0x11,0x2f,0x08,0xe8,0x75,0xf2,0xc8,0x1c,0x92,0x8f,0xe4,0x29,0x06,
+ 0x1d,0x1b,0x3e,0x52,0xda,0xd9,0x99,0x3c,0x07,0xa5,0xdd,0x56,0xfd,0xe5,0x25,0xae,
+ 0x50,0x7b,0xa1,0x22,0x15,0x09,0x84,0x08,0xbc,0x10,0x6c,0x84,0x10,0x1e,0xb9,0x07,
+ 0x79,0x9b,0x63,0xa1,0x9b,0xc2,0xaf,0x28,0x49,0x15,0xea,0x34,0xc1,0x22,0xe5,0x43,
+ 0xd2,0xc2,0xa2,0xd0,0x92,0xef,0x20,0x41,0x69,0x9d,0x61,0xb8,0xd4,0xe7,0xdb,0x32,
+ 0x1f,0x96,0xd6,0xc9,0xb2,0xb4,0x24,0x6e,0xa8,0x6b,0x22,0x56,0xd6,0x75,0x16,0xd5,
+ 0xb8,0xba,0x8d,0x7d,0x68,0x72,0x61,0x49,0x9d,0xac,0xaa,0xeb,0x64,0x57,0x96,0x4a,
+ 0xc6,0xff,0x07,0x09,0xd2,0xdd,0xcf,0xb2,0x57,0xe1,0x2d,0x00,0x00,0x00,0x00,0x49,
+ 0x45,0x4e,0x44,0xae,0x42,0x60,0x82,
+
+};
diff --git a/tools/printer.opacity b/tools/printer.opacity
new file mode 100644
index 000000000..6551295e7
--- /dev/null
+++ b/tools/printer.opacity
Binary files differ
diff --git a/tools/printer.png b/tools/printer.png
new file mode 100644
index 000000000..bb269318d
--- /dev/null
+++ b/tools/printer.png
Binary files differ