diff options
author | Michael R Sweet <michael.r.sweet@gmail.com> | 2019-04-26 11:05:27 -0400 |
---|---|---|
committer | Michael R Sweet <michael.r.sweet@gmail.com> | 2019-04-26 11:05:27 -0400 |
commit | f1ac9f5889e6a9a8996041ab46e6ca1e0cb8a238 (patch) | |
tree | b28d47f3580aa4ea386b7a09c6c6340f16cdbd80 /tools | |
parent | a76950482b3327f096e7188234173d739101aef1 (diff) | |
download | cups-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/Dependencies | 29 | ||||
-rw-r--r-- | tools/Makefile | 243 | ||||
-rw-r--r-- | tools/dither.h | 68 | ||||
-rw-r--r-- | tools/ippevecommon.h | 26 | ||||
-rw-r--r-- | tools/ippevepcl.c | 530 | ||||
-rw-r--r-- | tools/ippeveprinter.c | 8080 | ||||
-rw-r--r-- | tools/ippeveps.c | 1138 | ||||
-rw-r--r-- | tools/ippfind.c | 2847 | ||||
-rw-r--r-- | tools/ipptool.c | 5122 | ||||
-rw-r--r-- | tools/printer-png.h | 447 | ||||
-rw-r--r-- | tools/printer.opacity | bin | 0 -> 42248 bytes | |||
-rw-r--r-- | tools/printer.png | bin | 0 -> 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, "&", 5); + else + httpWrite2(client->http, "<", 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> %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> %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><<%s>></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, "&"); + else if (*s == '<') + cupsFilePuts(outfile, "<"); + else if (*s == '>') + cupsFilePuts(outfile, ">"); + 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 Binary files differnew file mode 100644 index 000000000..6551295e7 --- /dev/null +++ b/tools/printer.opacity diff --git a/tools/printer.png b/tools/printer.png Binary files differnew file mode 100644 index 000000000..bb269318d --- /dev/null +++ b/tools/printer.png |