diff options
author | Didier Raboud <odyx@debian.org> | 2012-10-25 20:57:13 +0200 |
---|---|---|
committer | Didier Raboud <odyx@debian.org> | 2012-10-25 20:57:13 +0200 |
commit | 49a2853988b074d087e82c51aec4f9fc052a057d (patch) | |
tree | c38ece96005bc33bd4e133fd0037f3428fdc039d /test | |
parent | a312f7e1ac68fb22275719f6208b670d9edd45b5 (diff) | |
download | cups-49a2853988b074d087e82c51aec4f9fc052a057d.tar.gz |
Imported Upstream version 1.5.0upstream/1.5.0
Diffstat (limited to 'test')
-rw-r--r-- | test/4.1-requests.test | 4 | ||||
-rw-r--r-- | test/4.2-cups-printer-ops.test | 4 | ||||
-rw-r--r-- | test/4.3-job-ops.test | 4 | ||||
-rw-r--r-- | test/4.4-subscription-ops.test | 9 | ||||
-rw-r--r-- | test/Dependencies | 20 | ||||
-rw-r--r-- | test/Makefile | 84 | ||||
-rw-r--r-- | test/create-printer-subscription.test | 35 | ||||
-rw-r--r-- | test/get-completed-jobs.test | 51 | ||||
-rw-r--r-- | test/get-jobs.test | 46 | ||||
-rw-r--r-- | test/get-printer-attributes.test | 9 | ||||
-rw-r--r-- | test/ipp-1.1.test | 752 | ||||
-rw-r--r-- | test/ipp-2.0.test | 80 | ||||
-rw-r--r-- | test/ipp-2.1.test | 95 | ||||
-rw-r--r-- | test/ippserver.c | 4380 | ||||
-rw-r--r-- | test/ipptest.c | 1278 | ||||
-rw-r--r-- | test/ipptool.c | 4921 | ||||
-rw-r--r-- | test/print-job-hold.test | 34 | ||||
-rw-r--r-- | test/print-job-media-col.test | 7 | ||||
-rw-r--r-- | test/print-job.test | 14 | ||||
-rw-r--r-- | test/print-uri.test | 27 | ||||
-rw-r--r-- | test/printer.opacity | bin | 0 -> 18493 bytes | |||
-rw-r--r-- | test/printer.png | bin | 0 -> 5133 bytes | |||
-rwxr-xr-x | test/run-stp-tests.sh | 39 | ||||
-rw-r--r-- | test/str-header.html | 9 | ||||
-rw-r--r-- | test/testfile.ps | 6 | ||||
-rw-r--r-- | test/testhp.ppd | 15 | ||||
-rw-r--r-- | test/testps.ppd | 12 |
27 files changed, 10550 insertions, 1385 deletions
diff --git a/test/4.1-requests.test b/test/4.1-requests.test index 860d43c0..e2eb79ca 100644 --- a/test/4.1-requests.test +++ b/test/4.1-requests.test @@ -1,5 +1,5 @@ # -# "$Id: 4.1-requests.test 8235 2009-01-13 00:55:16Z mike $" +# "$Id: 4.1-requests.test 9084 2010-04-07 06:54:31Z mike $" # # Verify that the server requires the following attributes: # @@ -155,5 +155,5 @@ STATUS server-error-version-not-supported } # -# End of "$Id: 4.1-requests.test 8235 2009-01-13 00:55:16Z mike $" +# End of "$Id: 4.1-requests.test 9084 2010-04-07 06:54:31Z mike $" # diff --git a/test/4.2-cups-printer-ops.test b/test/4.2-cups-printer-ops.test index c858a8db..5fa828eb 100644 --- a/test/4.2-cups-printer-ops.test +++ b/test/4.2-cups-printer-ops.test @@ -1,5 +1,5 @@ # -# "$Id: 4.2-cups-printer-ops.test 8536 2009-04-21 23:02:10Z mike $" +# "$Id: 4.2-cups-printer-ops.test 9084 2010-04-07 06:54:31Z mike $" # # Verify that the CUPS printer operations work. # @@ -323,5 +323,5 @@ } # -# End of "$Id: 4.2-cups-printer-ops.test 8536 2009-04-21 23:02:10Z mike $" +# End of "$Id: 4.2-cups-printer-ops.test 9084 2010-04-07 06:54:31Z mike $" # diff --git a/test/4.3-job-ops.test b/test/4.3-job-ops.test index b3d8b75a..e82de18d 100644 --- a/test/4.3-job-ops.test +++ b/test/4.3-job-ops.test @@ -1,5 +1,5 @@ # -# "$Id: 4.3-job-ops.test 8929 2009-12-15 22:40:37Z mike $" +# "$Id: 4.3-job-ops.test 9084 2010-04-07 06:54:31Z mike $" # # Verify that the IPP job operations work. # @@ -326,5 +326,5 @@ } # -# End of "$Id: 4.3-job-ops.test 8929 2009-12-15 22:40:37Z mike $" +# End of "$Id: 4.3-job-ops.test 9084 2010-04-07 06:54:31Z mike $" # diff --git a/test/4.4-subscription-ops.test b/test/4.4-subscription-ops.test index 7ea3367e..720add80 100644 --- a/test/4.4-subscription-ops.test +++ b/test/4.4-subscription-ops.test @@ -1,5 +1,5 @@ # -# "$Id: 4.4-subscription-ops.test 8145 2008-11-19 19:45:40Z mike $" +# "$Id: 4.4-subscription-ops.test 9352 2010-11-06 04:55:26Z mike $" # # Verify that the CUPS subscription operations work. # @@ -16,6 +16,7 @@ ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $scheme://$hostname:$port/printers/Test1 + ATTR name requesting-user-name $user GROUP subscription ATTR uri notify-recipient-uri testnotify:// @@ -48,6 +49,7 @@ ATTR language attributes-natural-language en ATTR uri printer-uri $scheme://$hostname:$port/printers/Test1 ATTR integer notify-subscription-id $notify-subscription-id + ATTR name requesting-user-name $user # What statuses are OK? STATUS client-error-not-found @@ -69,6 +71,7 @@ ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $scheme://$hostname:$port/printers/Test1 + ATTR name requesting-user-name $user GROUP subscription ATTR uri notify-recipient-uri testnotify:// @@ -102,6 +105,7 @@ ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $scheme://$hostname:$port/printers/Test1 + ATTR name requesting-user-name $user # What statuses are OK? STATUS successful-ok @@ -129,6 +133,7 @@ ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $scheme://$hostname:$port/printers/Test1 + ATTR name requesting-user-name $user GROUP subscription ATTR uri notify-recipient-uri testnotify:// @@ -144,5 +149,5 @@ } # -# End of "$Id: 4.4-subscription-ops.test 8145 2008-11-19 19:45:40Z mike $" +# End of "$Id: 4.4-subscription-ops.test 9352 2010-11-06 04:55:26Z mike $" # diff --git a/test/Dependencies b/test/Dependencies index 43ea4007..1c296636 100644 --- a/test/Dependencies +++ b/test/Dependencies @@ -1,5 +1,19 @@ # DO NOT DELETE THIS LINE -- make depend depends on it. -ipptest.o: ../cups/string.h ../config.h ../cups/cups.h ../cups/ipp.h -ipptest.o: ../cups/http.h ../cups/versioning.h ../cups/ppd.h ../cups/array.h -ipptest.o: ../cups/file.h ../cups/language.h ../cups/language.h +ippserver.o: ../cups/cups-private.h ../cups/cups.h ../cups/file.h +ippserver.o: ../cups/versioning.h ../cups/ipp.h ../cups/http.h +ippserver.o: ../cups/array.h ../cups/language.h ../cups/string-private.h +ippserver.o: ../config.h ../cups/debug-private.h ../cups/ppd-private.h +ippserver.o: ../cups/ppd.h ../cups/cups.h ../cups/pwg-private.h +ippserver.o: ../cups/http-private.h ../cups/http.h ../cups/md5-private.h +ippserver.o: ../cups/ipp-private.h ../cups/ipp.h ../cups/language-private.h +ippserver.o: ../cups/transcode.h ../cups/thread-private.h +ipptool.o: ../cups/cups-private.h ../cups/cups.h ../cups/file.h +ipptool.o: ../cups/versioning.h ../cups/ipp.h ../cups/http.h ../cups/array.h +ipptool.o: ../cups/language.h ../cups/string-private.h ../config.h +ipptool.o: ../cups/debug-private.h ../cups/ppd-private.h ../cups/ppd.h +ipptool.o: ../cups/cups.h ../cups/pwg-private.h ../cups/http-private.h +ipptool.o: ../cups/http.h ../cups/md5-private.h ../cups/ipp-private.h +ipptool.o: ../cups/ipp.h ../cups/language-private.h ../cups/transcode.h +ipptool.o: ../cups/thread-private.h ../cups/file-private.h +ipptool.o: ../cups/cups-private.h diff --git a/test/Makefile b/test/Makefile index b218f1c7..8f25af6a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,9 +1,9 @@ # -# "$Id: Makefile 8224 2009-01-09 21:28:38Z mike $" +# "$Id: Makefile 9517 2011-02-10 23:19:21Z mike $" # -# IPP test makefile for the Common UNIX Printing System (CUPS). +# IPP test makefile for CUPS. # -# Copyright 2007-2009 by Apple Inc. +# Copyright 2007-2010 by Apple Inc. # Copyright 1997-2006 by Easy Software Products, all rights reserved. # # These coded instructions, statements, and computer programs are the @@ -17,10 +17,34 @@ include ../Makedefs # +# Sample test files. +# + +TESTFILES = \ + create-printer-subscription.test \ + get-completed-jobs.test \ + get-jobs.test \ + ipp-1.1.test \ + ipp-2.0.test \ + ipp-2.1.test \ + testfile.jpg \ + testfile.pdf \ + testfile.ps \ + testfile.txt +OBJS = \ + ippserver.o \ + ipptool.o +TARGETS = \ + ippserver \ + ipptool \ + ipptool-static + + +# # Make all targets... # -all: ipptest +all: $(TARGETS) # @@ -42,7 +66,7 @@ unittests: # clean: - $(RM) ipptest ipptest.o + $(RM) $(TARGETS) $(OBJS) # @@ -50,7 +74,7 @@ clean: # depend: - makedepend -Y -I.. -fDependencies ipptest.c >/dev/null 2>&1 + makedepend -Y -I.. -fDependencies $(OBJS:.o=.c) >/dev/null 2>&1 # @@ -65,6 +89,11 @@ install: all install-data install-headers install-libs install-exec # install-data: + echo Installing sample ipptool files in $(DATADIR)/ipptool... + $(INSTALL_DIR) -m 755 $(DATADIR)/ipptool + for file in $(TESTFILES); do \ + $(INSTALL_DATA) $$file $(DATADIR)/ipptool; \ + done # @@ -72,6 +101,13 @@ install-data: # install-exec: + echo Installing ipptool in $(BINDIR)... + $(INSTALL_DIR) -m 755 $(BINDIR) + $(INSTALL_BIN) ipptool $(BINDIR) + if test "x$(SYMROOT)" != "x"; then \ + $(INSTALL_DIR) $(SYMROOT); \ + cp ipptool $(SYMROOT); \ + fi # @@ -96,12 +132,40 @@ uninstall: # -# ipptest +# ippserver +# + +ippserver: ippserver.o ../cups/$(LIBCUPSSTATIC) + echo Linking $@... + $(CC) $(LDFLAGS) -o $@ ippserver.o ../cups/$(LIBCUPSSTATIC) \ + $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ) + + +# +# ippserver-shared +# + +ippserver-shared: ippserver.o ../cups/$(LIBCUPS) + echo Linking $@... + $(CC) $(LDFLAGS) -o $@ ippserver.o $(LIBS) + + +# +# ipptool +# + +ipptool: ipptool.o ../cups/$(LIBCUPS) + echo Linking $@... + $(CC) $(LDFLAGS) -o $@ ipptool.o $(LIBS) + + +# +# ipptool-static # -ipptest: ipptest.o ../cups/libcups.a +ipptool-static: ipptool.o ../cups/$(LIBCUPSSTATIC) echo Linking $@... - $(CC) $(LDFLAGS) -o ipptest ipptest.o ../cups/libcups.a \ + $(CC) $(LDFLAGS) -o $@ ipptool.o ../cups/$(LIBCUPSSTATIC) \ $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ) @@ -113,5 +177,5 @@ include Dependencies # -# End of "$Id: Makefile 8224 2009-01-09 21:28:38Z mike $". +# End of "$Id: Makefile 9517 2011-02-10 23:19:21Z mike $". # diff --git a/test/create-printer-subscription.test b/test/create-printer-subscription.test index c6427ef7..19f5c85f 100644 --- a/test/create-printer-subscription.test +++ b/test/create-printer-subscription.test @@ -1,36 +1,49 @@ # -# "$Id: 4.4-subscription-ops.test 4840 2005-11-14 21:53:30Z mike $" +# "$Id: create-printer-subscription.test 9084 2010-04-07 06:54:31Z mike $" # -# Verify that the CUPS subscription operations work. +# Create a printer subscription. # +# Copyright 2007-2010 by Apple Inc. +# Copyright 2001-2006 by Easy Software Products. All rights reserved. +# +# These coded instructions, statements, and computer programs are the +# property of Apple Inc. and are protected by Federal copyright +# law. Distribution and use rights are outlined in the file "LICENSE.txt" +# which should have been included with this file. If this file is +# file is missing or damaged, see the license at "http://www.cups.org/". +# +# Usage: +# +# ./ipptool -d recipient=uri printer-uri create-printer-subscription.test +# + + { # The name of the test... - NAME "Add Printer Subscription w/Lease" + NAME "Create a printer subscription" # The operation to use OPERATION Create-Printer-Subscription - RESOURCE / # The attributes to send - GROUP operation + GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri - GROUP subscription - ATTR uri notify-recipient testnotify://nowait + GROUP subscription-attributes-tag + ATTR uri notify-recipient $recipient ATTR keyword notify-events printer-state-changed # What statuses are OK? STATUS successful-ok # What attributes do we expect? - EXPECT attributes-charset - EXPECT attributes-natural-language - EXPECT notify-subscription-id + EXPECT notify-subscription-id OF-TYPE integer WITH-VALUE >0 DISPLAY notify-subscription-id } + # -# End of "$Id: 4.4-subscription-ops.test 4840 2005-11-14 21:53:30Z mike $" +# End of "$Id: create-printer-subscription.test 9084 2010-04-07 06:54:31Z mike $" # diff --git a/test/get-completed-jobs.test b/test/get-completed-jobs.test new file mode 100644 index 00000000..7b5696c4 --- /dev/null +++ b/test/get-completed-jobs.test @@ -0,0 +1,51 @@ +# +# "$Id: get-completed-jobs.test 9086 2010-04-07 18:46:04Z mike $" +# +# Get list of completed jobs. +# +# Copyright 2007-2010 by Apple Inc. +# Copyright 2001-2006 by Easy Software Products. All rights reserved. +# +# These coded instructions, statements, and computer programs are the +# property of Apple Inc. and are protected by Federal copyright +# law. Distribution and use rights are outlined in the file "LICENSE.txt" +# which should have been included with this file. If this file is +# file is missing or damaged, see the license at "http://www.cups.org/". +# +# Usage: +# +# ./ipptool printer-uri get-completed-jobs.test +# + + +{ + # The name of the test... + NAME "Get completed jobs" + + # The operation to use + OPERATION Get-Jobs + + # Attributes, starting in the operation group... + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR language attributes-natural-language en + ATTR uri printer-uri $uri + ATTR keyword which-jobs completed + ATTR keyword requested-attributes + job-id,job-state,job-name,job-originating-user-name,job-media-sheets-completed + + # What statuses are OK? + STATUS successful-ok + + # What attributes to display + DISPLAY job-id + DISPLAY job-state + DISPLAY job-name + DISPLAY job-originating-user-name + DISPLAY job-media-sheets-completed +} + + +# +# End of "$Id: get-completed-jobs.test 9086 2010-04-07 18:46:04Z mike $". +# diff --git a/test/get-jobs.test b/test/get-jobs.test index 1b4ed201..4da5f6a7 100644 --- a/test/get-jobs.test +++ b/test/get-jobs.test @@ -1,21 +1,53 @@ -# Get list of jobs +# +# "$Id: get-jobs.test 9702 2011-04-20 21:16:08Z mike $" +# +# Get list of not-completed jobs. +# +# Copyright 2007-2010 by Apple Inc. +# Copyright 2001-2006 by Easy Software Products. All rights reserved. +# +# These coded instructions, statements, and computer programs are the +# property of Apple Inc. and are protected by Federal copyright +# law. Distribution and use rights are outlined in the file "LICENSE.txt" +# which should have been included with this file. If this file is +# file is missing or damaged, see the license at "http://www.cups.org/". +# +# Usage: +# +# ./ipptool printer-uri get-jobs.test +# + + { # The name of the test... - NAME "Get-Jobs" - - # The resource to use for the POST - # RESOURCE /admin + NAME "Get pending jobs" # The operation to use OPERATION Get-Jobs # Attributes, starting in the operation group... - GROUP operation + GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri -# ATTR keyword which-jobs completed + ATTR keyword requested-attributes + job-id,job-state,job-name,job-originating-user-name,job-media-sheets,job-media-sheets-completed,job-impressions,job-impressions-completed # What statuses are OK? STATUS successful-ok + + # What attributes to display + DISPLAY job-id + DISPLAY job-state + DISPLAY job-name + DISPLAY job-originating-user-name + DISPLAY job-impressions + DISPLAY job-impressions-completed + DISPLAY job-media-sheets + DISPLAY job-media-sheets-completed } + + +# +# End of "$Id: get-jobs.test 9702 2011-04-20 21:16:08Z mike $". +# diff --git a/test/get-printer-attributes.test b/test/get-printer-attributes.test index 54ce778b..5940017a 100644 --- a/test/get-printer-attributes.test +++ b/test/get-printer-attributes.test @@ -1,16 +1,13 @@ # Get printer attributes using get-printer-attributes { # The name of the test... - NAME "Get printer attributes using get-printer-attributes" - - # The resource to use for the POST - # RESOURCE /admin + NAME "Get printer attributes using Get-Printer-Attributes" # The operation to use - OPERATION get-printer-attributes + OPERATION Get-Printer-Attributes # Attributes, starting in the operation group... - GROUP operation + GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri diff --git a/test/ipp-1.1.test b/test/ipp-1.1.test new file mode 100644 index 00000000..3eeab4d3 --- /dev/null +++ b/test/ipp-1.1.test @@ -0,0 +1,752 @@ +# +# "$Id: ipp-1.1.test 9830 2011-06-14 21:11:17Z mike $" +# +# IPP/1.1 test suite. +# +# Copyright 2007-2011 by Apple Inc. +# Copyright 2001-2006 by Easy Software Products. All rights reserved. +# +# These coded instructions, statements, and computer programs are the +# property of Apple Inc. and are protected by Federal copyright +# law. Distribution and use rights are outlined in the file "LICENSE.txt" +# which should have been included with this file. If this file is +# file is missing or damaged, see the license at "http://www.cups.org/". +# +# Usage: +# +# ./ipptool -f filename -t printer-uri ipp-1.1.test +# + +# Regular expression for IPP URI schemes +# Matches strings beginning with ipp:// or ipps:// +DEFINE IPP_URI_SCHEME "/^ipps?://.+$$/" + +# Test that a request-id value of 0 is not accepted. +# +# Required by: RFC 2911 section 3.1.1 +{ + NAME "3.1.1: Bad request-id value 0" + REQUEST-ID 0 + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + + STATUS client-error-bad-request + EXPECT !printer-uri-supported +} + + +# Test that the first two attributes must be attributes-charset and +# attributes-natural-language. +# +# Required by: RFC 2911 section 3.1.4 +{ + NAME "3.1.4: No Operation Attributes" + REQUEST-ID random + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + + STATUS client-error-bad-request + EXPECT !printer-uri-supported +} +{ + NAME "3.1.4: attributes-charset" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR uri printer-uri $uri + + STATUS client-error-bad-request + EXPECT !printer-uri-supported +} +{ + NAME "3.1.4: attributes-natural-language" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + + STATUS client-error-bad-request + EXPECT !printer-uri-supported +} +{ + NAME "3.1.4: attributes-natural-language + attributes-charset" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR naturalLanguage attributes-natural-language en + ATTR charset attributes-charset utf-8 + ATTR uri printer-uri $uri + + STATUS client-error-bad-request + EXPECT !printer-uri-supported +} +{ + NAME "3.1.4: attributes-charset + attributes-natural-language" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + + STATUS successful-ok + EXPECT printer-uri-supported OF-TYPE uri WITH-VALUE "$IPP_URI_SCHEME" +} + + +# Test that bad IPP versions are not supported. +# +# Required by: RFC 2911 section 3.1.8 +{ + # The name of the test... + NAME "3.1.8: Unsupported IPP version 0.0" + VERSION 0.0 + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + + STATUS server-error-version-not-supported + EXPECT !printer-uri-supported +} + + +# Test that printer operations require the printer-uri operation attribute. +# +# Required by: RFC 2911 section 3.2 +{ + NAME "3.2: No printer-uri operation attribute" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + + STATUS client-error-bad-request + EXPECT !printer-uri-supported +} + + +# Test Print-Job operation +# +# Required by: RFC 2911 section 3.2.1 +{ + NAME "3.2.1: Print-Job Operation" + OPERATION Print-Job + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR name job-name $filename + ATTR boolean ipp-attribute-fidelity false + ATTR name document-name $filename + ATTR keyword compression none + ATTR mimeMediaType document-format application/octet-stream + FILE $filename + + STATUS successful-ok + STATUS client-error-document-format-not-supported + EXPECT job-uri OF-TYPE uri COUNT 1 IN-GROUP job-attributes-tag WITH-VALUE "$IPP_URI_SCHEME" + EXPECT job-id OF-TYPE integer COUNT 1 IN-GROUP job-attributes-tag + WITH-VALUE >0 + EXPECT job-state OF-TYPE enum COUNT 1 IN-GROUP job-attributes-tag + WITH-VALUE 3,4,5,6,7,8,9 + EXPECT job-state-reasons OF-TYPE keyword IN-GROUP job-attributes-tag + EXPECT ?job-state-message OF-TYPE text IN-GROUP job-attributes-tag + EXPECT ?number-of-intervening-jobs OF-TYPE integer + IN-GROUP job-attributes-tag WITH-VALUE >-1 +} + +# Test Get-Printer-Attributes operation +# +# Required by: RFC 2911 section 3.2.5 +{ + NAME "3.2.5: Get-Printer-Attributes Operation (default)" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR mimeMediaType document-format application/octet-stream + + STATUS successful-ok + + # Job template attributes + EXPECT ?copies-default OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT ?copies-supported OF-TYPE rangeOfInteger IN-GROUP printer-attributes-tag + EXPECT ?finishings-default OF-TYPE enum IN-GROUP printer-attributes-tag + EXPECT ?finishings-supported OF-TYPE enum IN-GROUP printer-attributes-tag WITH-VALUE 3 + EXPECT ?job-hold-until-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?job-hold-until-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag WITH-VALUE no-hold + EXPECT ?job-priority-default OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0,<101 + EXPECT ?job-priority-supported OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0,<101 + EXPECT ?job-sheets-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag + EXPECT ?job-sheets-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag WITH-VALUE none + EXPECT ?media-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?media-ready OF-TYPE keyword|name IN-GROUP printer-attributes-tag + EXPECT ?media-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag + EXPECT ?multiple-document-handling-default OF-TYPE keyword IN-GROUP printer-attributes-tag WITH-VALUE "/^(single-document|separate-documents-uncollated-copies|separate-documents-collated-copies|single-document-new-sheet)$$/" + EXPECT ?multiple-document-handling-supported OF-TYPE keyword IN-GROUP printer-attributes-tag WITH-VALUE "/^(single-document|separate-documents-uncollated-copies|separate-documents-collated-copies|single-document-new-sheet)$$/" + EXPECT ?number-up-default OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT ?number-up-supported OF-TYPE integer|rangeOfInteger IN-GROUP printer-attributes-tag WITH-VALUE >0 + EXPECT ?number-up-supported WITH-VALUE 1 + EXPECT ?orientation-requested-default OF-TYPE enum,no-value IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE no-value,3,4,5,6 + EXPECT ?orientation-requested-supported OF-TYPE enum IN-GROUP printer-attributes-tag WITH-VALUE 3,4,5,6 + EXPECT ?pages-ranges-supported OF-TYPE boolean IN-GROUP printer-attributes-tag + EXPECT ?print-quality-default OF-TYPE enum IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE 3,4,5 + EXPECT ?print-quality-supported OF-TYPE enum IN-GROUP printer-attributes-tag WITH-VALUE 3,4,5 + EXPECT ?printer-resolution-default OF-TYPE resolution IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?printer-resolution-supported OF-TYPE resolution IN-GROUP printer-attributes-tag + EXPECT ?sides-default OF-TYPE keyword IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^(one-sided|two-sided-long-edge|two-sided-short-edge)$$/" + EXPECT ?sides-supported OF-TYPE keyword IN-GROUP printer-attributes-tag WITH-VALUE "/^(one-sided|two-sided-long-edge|two-sided-short-edge)$$/" + + # Printer description attributes + EXPECT ?color-supported OF-TYPE boolean IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?job-impressions-supported OF-TYPE rangeOfInteger IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?job-k-octets-supported OF-TYPE rangeOfInteger IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?job-media-sheets-supported OF-TYPE rangeOfInteger IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?multiple-document-jobs-supported OF-TYPE boolean IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?multiple-operation-time-out OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT ?pages-per-minute OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?pages-per-minute-color OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?printer-driver-installer OF-TYPE uri IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?printer-info OF-TYPE text IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{0,127}$$/" + EXPECT ?printer-location OF-TYPE text IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{0,127}$$/" + EXPECT ?printer-make-and-model OF-TYPE text IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{0,127}$$/" + EXPECT ?printer-message-from-operator OF-TYPE text IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{0,127}$$/" + EXPECT ?printer-more-info OF-TYPE uri IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?printer-more-info-manufacturer OF-TYPE uri IN-GROUP printer-attributes-tag COUNT 1 + EXPECT ?printer-state-message OF-TYPE text IN-GROUP printer-attributes-tag + EXPECT ?reference-uri-schemes-supported OF-TYPE uriScheme IN-GROUP printer-attributes-tag + EXPECT charset-configured OF-TYPE charset IN-GROUP printer-attributes-tag COUNT 1 + EXPECT charset-supported OF-TYPE charset IN-GROUP printer-attributes-tag WITH-VALUE utf-8 + EXPECT compression-supported OF-TYPE keyword IN-GROUP printer-attributes-tag WITH-VALUE none + EXPECT document-format-default OF-TYPE mimeMediaType IN-GROUP printer-attributes-tag COUNT 1 + EXPECT document-format-supported OF-TYPE mimeMediaType IN-GROUP printer-attributes-tag + EXPECT generated-natural-language-supported OF-TYPE naturalLanguage IN-GROUP printer-attributes-tag + EXPECT ipp-versions-supported OF-TYPE keyword IN-GROUP printer-attributes-tag WITH-VALUE 1.1 + EXPECT natural-language-configured OF-TYPE naturalLanguage IN-GROUP printer-attributes-tag COUNT 1 + EXPECT operations-supported OF-TYPE enum IN-GROUP printer-attributes-tag WITH-VALUE 0x0002 # Print-Job + EXPECT operations-supported WITH-VALUE 0x0004 # Validate-Job + EXPECT operations-supported WITH-VALUE 0x0008 # Cancel-Job + EXPECT operations-supported WITH-VALUE 0x0009 # Get-Job-Attributes + EXPECT operations-supported WITH-VALUE 0x000a # Get-Jobs + EXPECT operations-supported WITH-VALUE 0x000b # Get-Printer-Attributes + EXPECT pdl-override-supported OF-TYPE keyword IN-GROUP printer-attributes-tag COUNT 1 + EXPECT printer-is-accepting-jobs OF-TYPE boolean IN-GROUP printer-attributes-tag COUNT 1 + EXPECT printer-name OF-TYPE name IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{1,127}$$/" + EXPECT printer-state OF-TYPE enum IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE 3,4,5 + EXPECT printer-state-reasons OF-TYPE keyword IN-GROUP printer-attributes-tag + EXPECT printer-up-time OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT printer-uri-supported OF-TYPE uri IN-GROUP printer-attributes-tag SAME-COUNT-AS uri-security-supported WITH-VALUE "$IPP_URI_SCHEME" + EXPECT queued-job-count OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 + EXPECT uri-authentication-supported OF-TYPE keyword IN-GROUP printer-attributes-tag + EXPECT uri-security-supported OF-TYPE keyword IN-GROUP printer-attributes-tag SAME-COUNT-AS uri-authentication-supported +} + + +# Test Get-Printer-Attributes operation with requested-attributes +# +# Required by: RFC 2911 section 3.2.5 +{ + NAME "3.2.5: Get-Printer-Attributes Operation (requested-attributes)" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR mimeMediaType document-format application/octet-stream + ATTR keyword requested-attributes printer-uri-supported + + STATUS successful-ok + + EXPECT printer-uri-supported OF-TYPE uri IN-GROUP printer-attributes-tag WITH-VALUE "$IPP_URI_SCHEME" + EXPECT !printer-name +} + + +# Test Get-Jobs operation +# +# Required by: RFC 2911 section 3.2.6 +{ + NAME "3.2.6: Get-Jobs Operation (default)" + OPERATION Get-Jobs + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + + STATUS successful-ok + EXPECT job-id OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT job-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "$IPP_URI_SCHEME" + EXPECT !job-printer-uri + EXPECT !job-more-info + EXPECT !job-name + EXPECT !job-originating-user-name + EXPECT !job-state + EXPECT !job-state-reasons + EXPECT !job-state-message + EXPECT !job-detailed-status-messages + EXPECT !number-of-documents + EXPECT !output-device-assigned + EXPECT !time-at-creation + EXPECT !time-at-processing + EXPECT !time-at-completed + EXPECT !job-printer-up-time + EXPECT !date-time-at-creation + EXPECT !date-time-at-processing + EXPECT !date-time-at-completed + EXPECT !number-of-intervening-jobs + EXPECT !job-message-from-operator + EXPECT !job-k-octets + EXPECT !job-impressions + EXPECT !job-media-sheets + EXPECT !job-k-octets-processed + EXPECT !job-impressions-completed + EXPECT !job-media-sheets-completed + + EXPECT !copies + EXPECT !finishings + EXPECT !job-hold-until + EXPECT !job-priority + EXPECT !job-sheets + EXPECT !media + EXPECT !multiple-document-handling + EXPECT !number-up + EXPECT !orientation-requested + EXPECT !pages-ranges + EXPECT !print-quality + EXPECT !printer-resolution + EXPECT !sides +} + + +# Test Get-Jobs operation +# +# Required by: RFC 2911 section 3.2.6 +{ + NAME "3.2.6: Get-Jobs Operation (requested-attributes)" + OPERATION Get-Jobs + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR keyword requested-attributes all + + STATUS successful-ok + EXPECT job-id OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT job-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "$IPP_URI_SCHEME" + EXPECT job-printer-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-more-info OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 + EXPECT job-name OF-TYPE name IN-GROUP job-attributes-tag COUNT 1 + EXPECT job-originating-user-name OF-TYPE name IN-GROUP job-attributes-tag COUNT 1 + EXPECT job-state OF-TYPE enum IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >2,<10 + EXPECT job-state-reasons OF-TYPE keyword IN-GROUP job-attributes-tag + EXPECT ?job-state-message OF-TYPE text IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-detailed-status-messages OF-TYPE text IN-GROUP job-attributes-tag + EXPECT ?number-of-documents OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?output-device-assigned OF-TYPE name IN-GROUP job-attributes-tag COUNT 1 + EXPECT time-at-creation OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE no-value,>-1 + EXPECT time-at-processing OF-TYPE no-value,integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE no-value,>-1 + EXPECT time-at-completed OF-TYPE no-value,integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE no-value,>-1 + EXPECT job-printer-up-time OF-TYPE no-value,integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?date-time-at-creation OF-TYPE no-value,dateTime IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?date-time-at-processing OF-TYPE no-value,dateTime IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?date-time-at-completed OF-TYPE no-value,dateTime IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?number-of-intervening-jobs OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-message-from-operator OF-TYPE text IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-k-octets OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-impressions OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-media-sheets OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-k-octets-processed OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-impressions-completed OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-media-sheets-completed OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + + EXPECT ?copies OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT ?finishings OF-TYPE enum IN-GROUP job-attributes-tag + EXPECT ?job-hold-until OF-TYPE keyword|name IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-priority OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0,<101 + EXPECT ?job-sheets OF-TYPE keyword|name IN-GROUP job-attributes-tag + EXPECT ?media OF-TYPE keyword|name IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?multiple-document-handling OF-TYPE keyword IN-GROUP job-attributes-tag WITH-VALUE "/^(single-document|separate-documents-uncollated-copies|separate-documents-collated-copies|single-document-new-sheet)$$/" + EXPECT ?number-up OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT ?orientation-requested OF-TYPE enum IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE 3,4,5,6 + EXPECT ?pages-ranges OF-TYPE rangeOfInteger IN-GROUP job-attributes-tag + EXPECT ?print-quality OF-TYPE enum IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE 3,4,5 + EXPECT ?printer-resolution OF-TYPE resolution IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?sides OF-TYPE keyword IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "/^(one-sided|two-sided-long-edge|two-sided-short-edge)$$/" +} + + +# Test Get-Jobs operation +# +# Required by: RFC 2911 section 3.2.6 +{ + NAME "3.2.6: Get-Jobs Operation (my-jobs)" + OPERATION Get-Jobs + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR boolean my-jobs true + + STATUS successful-ok + EXPECT job-id OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT job-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "$IPP_URI_SCHEME" + EXPECT !job-printer-uri + EXPECT !job-more-info + EXPECT !job-name + EXPECT !job-originating-user-name + EXPECT !job-state + EXPECT !job-state-reasons + EXPECT !job-state-message + EXPECT !job-detailed-status-messages + EXPECT !number-of-documents + EXPECT !output-device-assigned + EXPECT !time-at-creation + EXPECT !time-at-processing + EXPECT !time-at-completed + EXPECT !job-printer-up-time + EXPECT !date-time-at-creation + EXPECT !date-time-at-processing + EXPECT !date-time-at-completed + EXPECT !number-of-intervening-jobs + EXPECT !job-message-from-operator + EXPECT !job-k-octets + EXPECT !job-impressions + EXPECT !job-media-sheets + EXPECT !job-k-octets-processed + EXPECT !job-impressions-completed + EXPECT !job-media-sheets-completed + + EXPECT !copies + EXPECT !finishings + EXPECT !job-hold-until + EXPECT !job-priority + EXPECT !job-sheets + EXPECT !media + EXPECT !multiple-document-handling + EXPECT !number-up + EXPECT !orientation-requested + EXPECT !pages-ranges + EXPECT !print-quality + EXPECT !printer-resolution + EXPECT !sides +} + + +# Test Get-Jobs operation +# +# Required by: RFC 2911 section 3.2.6 +{ + # Skip this test when doing authenticated printing since we'll always + # use the authenticated username over the requesting-user-name value. + SKIP-IF-DEFINED uriuser + + NAME "3.2.6: Get-Jobs Operation (my-jobs different user)" + OPERATION Get-Jobs + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name not-$user + ATTR boolean my-jobs true + + STATUS successful-ok + EXPECT !job-id + EXPECT !job-uri + EXPECT !job-printer-uri + EXPECT !job-more-info + EXPECT !job-name + EXPECT !job-originating-user-name + EXPECT !job-state + EXPECT !job-state-reasons + EXPECT !job-state-message + EXPECT !job-detailed-status-messages + EXPECT !number-of-documents + EXPECT !output-device-assigned + EXPECT !time-at-creation + EXPECT !time-at-processing + EXPECT !time-at-completed + EXPECT !job-printer-up-time + EXPECT !date-time-at-creation + EXPECT !date-time-at-processing + EXPECT !date-time-at-completed + EXPECT !number-of-intervening-jobs + EXPECT !job-message-from-operator + EXPECT !job-k-octets + EXPECT !job-impressions + EXPECT !job-media-sheets + EXPECT !job-k-octets-processed + EXPECT !job-impressions-completed + EXPECT !job-media-sheets-completed + + EXPECT !copies + EXPECT !finishings + EXPECT !job-hold-until + EXPECT !job-priority + EXPECT !job-sheets + EXPECT !media + EXPECT !multiple-document-handling + EXPECT !number-up + EXPECT !orientation-requested + EXPECT !pages-ranges + EXPECT !print-quality + EXPECT !printer-resolution + EXPECT !sides +} + + +# Test Get-Jobs operation +# +# Required by: RFC 2911 section 3.2.6 +{ + NAME "3.2.6: Get-Jobs Operation (which-jobs=not-completed)" + OPERATION Get-Jobs + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR keyword which-jobs not-completed + + STATUS successful-ok + EXPECT job-id OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT job-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "$IPP_URI_SCHEME" + EXPECT !job-printer-uri + EXPECT !job-more-info + EXPECT !job-name + EXPECT !job-originating-user-name + EXPECT !job-state + EXPECT !job-state-reasons + EXPECT !job-state-message + EXPECT !job-detailed-status-messages + EXPECT !number-of-documents + EXPECT !output-device-assigned + EXPECT !time-at-creation + EXPECT !time-at-processing + EXPECT !time-at-completed + EXPECT !job-printer-up-time + EXPECT !date-time-at-creation + EXPECT !date-time-at-processing + EXPECT !date-time-at-completed + EXPECT !number-of-intervening-jobs + EXPECT !job-message-from-operator + EXPECT !job-k-octets + EXPECT !job-impressions + EXPECT !job-media-sheets + EXPECT !job-k-octets-processed + EXPECT !job-impressions-completed + EXPECT !job-media-sheets-completed + + EXPECT !copies + EXPECT !finishings + EXPECT !job-hold-until + EXPECT !job-priority + EXPECT !job-sheets + EXPECT !media + EXPECT !multiple-document-handling + EXPECT !number-up + EXPECT !orientation-requested + EXPECT !pages-ranges + EXPECT !print-quality + EXPECT !printer-resolution + EXPECT !sides +} + + + +# Test Get-Jobs operation +# +# Required by: RFC 2911 section 3.2.6 +{ + NAME "3.2.6: Get-Jobs Operation (which-jobs=completed)" + OPERATION Get-Jobs + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR keyword which-jobs completed + DELAY 20 + + STATUS successful-ok + EXPECT job-id OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT job-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "$IPP_URI_SCHEME" + EXPECT !job-printer-uri + EXPECT !job-more-info + EXPECT !job-name + EXPECT !job-originating-user-name + EXPECT !job-state + EXPECT !job-state-reasons + EXPECT !job-state-message + EXPECT !job-detailed-status-messages + EXPECT !number-of-documents + EXPECT !output-device-assigned + EXPECT !time-at-creation + EXPECT !time-at-processing + EXPECT !time-at-completed + EXPECT !job-printer-up-time + EXPECT !date-time-at-creation + EXPECT !date-time-at-processing + EXPECT !date-time-at-completed + EXPECT !number-of-intervening-jobs + EXPECT !job-message-from-operator + EXPECT !job-k-octets + EXPECT !job-impressions + EXPECT !job-media-sheets + EXPECT !job-k-octets-processed + EXPECT !job-impressions-completed + EXPECT !job-media-sheets-completed + + EXPECT !copies + EXPECT !finishings + EXPECT !job-hold-until + EXPECT !job-priority + EXPECT !job-sheets + EXPECT !media + EXPECT !multiple-document-handling + EXPECT !number-up + EXPECT !orientation-requested + EXPECT !pages-ranges + EXPECT !print-quality + EXPECT !printer-resolution + EXPECT !sides +} + + +# Test Cancel-Job operation +# +# Required by: RFC 2911 section 3.3.3 +{ + NAME "3.3.3: Cancel-Job Operation (completed job)" + OPERATION Cancel-Job + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR integer job-id $job-id + ATTR name requesting-user-name $user + + STATUS client-error-not-possible +} + + +# Test Print-Job operation +# +# Required by: RFC 2911 section 3.2.1 +{ + NAME "3.2.1: Print-Job Operation" + OPERATION Print-Job + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR name job-name $filename + ATTR boolean ipp-attribute-fidelity false + ATTR name document-name $filename + ATTR keyword compression none + ATTR mimeMediaType document-format application/octet-stream + FILE $filename + + STATUS successful-ok + STATUS client-error-document-format-not-supported + EXPECT job-uri OF-TYPE uri COUNT 1 IN-GROUP job-attributes-tag WITH-VALUE "$IPP_URI_SCHEME" + EXPECT job-id OF-TYPE integer COUNT 1 IN-GROUP job-attributes-tag + WITH-VALUE >0 + EXPECT job-state OF-TYPE enum COUNT 1 IN-GROUP job-attributes-tag + WITH-VALUE 3,4,5,6,7,8,9 + EXPECT job-state-reasons OF-TYPE keyword IN-GROUP job-attributes-tag + EXPECT ?job-state-message OF-TYPE text IN-GROUP job-attributes-tag + EXPECT ?number-of-intervening-jobs OF-TYPE integer + IN-GROUP job-attributes-tag WITH-VALUE >-1 +} + + +# Test Cancel-Job operation +# +# Required by: RFC 2911 section 3.3.3 +{ + NAME "3.3.3: Cancel-Job Operation (pending/processing job)" + OPERATION Cancel-Job + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR integer job-id $job-id + ATTR name requesting-user-name $user + + STATUS successful-ok + STATUS client-error-not-possible +} + + +# Test Get-Job-Attributes operation +# +# Required by: RFC 2911 section 3.3.4 +{ + NAME "3.3.4: Get-Job-Attributes Operation" + OPERATION Get-Job-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR integer job-id $job-id + ATTR name requesting-user-name $user + + STATUS successful-ok + EXPECT job-id OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT job-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "$IPP_URI_SCHEME" + EXPECT job-printer-uri OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-more-info OF-TYPE uri IN-GROUP job-attributes-tag COUNT 1 + EXPECT job-name OF-TYPE name IN-GROUP job-attributes-tag COUNT 1 + EXPECT job-originating-user-name OF-TYPE name IN-GROUP job-attributes-tag COUNT 1 + EXPECT job-state OF-TYPE enum IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >2,<10 + EXPECT job-state-reasons OF-TYPE keyword IN-GROUP job-attributes-tag + EXPECT ?job-state-message OF-TYPE text IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-detailed-status-messages OF-TYPE text IN-GROUP job-attributes-tag + EXPECT ?number-of-documents OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?output-device-assigned OF-TYPE name IN-GROUP job-attributes-tag COUNT 1 + EXPECT time-at-creation OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE no-value,>-1 + EXPECT time-at-processing OF-TYPE no-value,integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE no-value,>-1 + EXPECT time-at-completed OF-TYPE no-value,integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE no-value,>-1 + EXPECT job-printer-up-time OF-TYPE no-value,integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?date-time-at-creation OF-TYPE no-value,dateTime IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?date-time-at-processing OF-TYPE no-value,dateTime IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?date-time-at-completed OF-TYPE no-value,dateTime IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?number-of-intervening-jobs OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-message-from-operator OF-TYPE text IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-k-octets OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-impressions OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-media-sheets OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-k-octets-processed OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-impressions-completed OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-media-sheets-completed OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 + + EXPECT ?copies OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT ?finishings OF-TYPE enum IN-GROUP job-attributes-tag + EXPECT ?job-hold-until OF-TYPE keyword|name IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?job-priority OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0,<101 + EXPECT ?job-sheets OF-TYPE keyword|name IN-GROUP job-attributes-tag + EXPECT ?media OF-TYPE keyword|name IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?multiple-document-handling OF-TYPE keyword IN-GROUP job-attributes-tag WITH-VALUE "/^(single-document|separate-documents-uncollated-copies|separate-documents-collated-copies|single-document-new-sheet)$$/" + EXPECT ?number-up OF-TYPE integer IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT ?orientation-requested OF-TYPE enum IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE 3,4,5,6 + EXPECT ?pages-ranges OF-TYPE rangeOfInteger IN-GROUP job-attributes-tag + EXPECT ?print-quality OF-TYPE enum IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE 3,4,5 + EXPECT ?printer-resolution OF-TYPE resolution IN-GROUP job-attributes-tag COUNT 1 + EXPECT ?sides OF-TYPE keyword IN-GROUP job-attributes-tag COUNT 1 WITH-VALUE "/^(one-sided|two-sided-long-edge|two-sided-short-edge)$$/" +} + + +# +# End of "$Id: ipp-1.1.test 9830 2011-06-14 21:11:17Z mike $". +# diff --git a/test/ipp-2.0.test b/test/ipp-2.0.test new file mode 100644 index 00000000..9b4e6875 --- /dev/null +++ b/test/ipp-2.0.test @@ -0,0 +1,80 @@ +# +# "$Id: ipp-2.0.test 9238 2010-08-11 21:21:46Z mike $" +# +# IPP/2.0 test suite. +# +# Copyright 2007-2010 by Apple Inc. +# Copyright 2001-2006 by Easy Software Products. All rights reserved. +# +# These coded instructions, statements, and computer programs are the +# property of Apple Inc. and are protected by Federal copyright +# law. Distribution and use rights are outlined in the file "LICENSE.txt" +# which should have been included with this file. If this file is +# file is missing or damaged, see the license at "http://www.cups.org/". +# +# Usage: +# +# ./ipptool -V 2.0 -f filename -t printer-uri ipp-2.0.test +# + +# Do all of the IPP/1.1 tests as an IPP/2.0 client +# +# Required by: PWG 5100.10 section 4.3 +INCLUDE "ipp-1.1.test" + + +# Regular expression for PWG media size names (eek!) +DEFINE MEDIA_REGEX "/^((custom|na|asme|roc|oe)_[a-z0-9][-a-z0-9]*_([1-9][0-9]*(\.[0-9]*[1-9])?|0\.[0-9]*[1-9])x([1-9][0-9]*(\.[0-9]*[1-9])?|0\.[0-9]*[1-9])in|(custom|iso|jis|jpn|prc|om)_[a-z0-9][-a-z0-9]*_([1-9][0-9]*(\.[0-9]*[1-9])?|0\.[0-9]*[1-9])x([1-9][0-9]*(\.[0-9]*[1-9])?|0\.[0-9]*[1-9])mm)$$/" + + +# Test required printer description attribute support. +# +# Required by: PWG 5100.10 section 6.2 +{ + NAME "PWG 5100.10 section 6.2 - Required Printer Description Attributes" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR mimeMediaType document-format application/octet-stream + + STATUS successful-ok + + # Figure out capabilities + EXPECT color-supported OF-TYPE boolean IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE true DEFINE-MATCH PRINTER_IS_COLOR + + # Job template attributes + EXPECT copies-default OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0 + EXPECT copies-supported OF-TYPE rangeOfInteger IN-GROUP printer-attributes-tag + EXPECT finishings-default OF-TYPE enum IN-GROUP printer-attributes-tag + EXPECT finishings-supported OF-TYPE enum IN-GROUP printer-attributes-tag WITH-VALUE 3 + EXPECT media-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "$MEDIA_REGEX" + EXPECT media-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag WITH-VALUE "$MEDIA_REGEX" + EXPECT orientation-requested-default OF-TYPE enum,no-value IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE no-value,3,4,5,6 + EXPECT orientation-requested-supported OF-TYPE enum IN-GROUP printer-attributes-tag WITH-VALUE 3,4,5,6 + EXPECT output-bin-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag COUNT 1 + EXPECT output-bin-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag + EXPECT print-quality-default OF-TYPE enum IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE 3,4,5 + EXPECT print-quality-supported OF-TYPE enum IN-GROUP printer-attributes-tag WITH-VALUE 3,4,5 + EXPECT printer-resolution-default OF-TYPE resolution IN-GROUP printer-attributes-tag COUNT 1 + EXPECT printer-resolution-supported OF-TYPE resolution IN-GROUP printer-attributes-tag + EXPECT sides-default OF-TYPE keyword IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^(one-sided|two-sided-long-edge|two-sided-short-edge)$$/" + EXPECT sides-supported OF-TYPE keyword IN-GROUP printer-attributes-tag WITH-VALUE "/^(one-sided|two-sided-long-edge|two-sided-short-edge)$$/" + + # Printer description attributes + EXPECT color-supported OF-TYPE boolean IN-GROUP printer-attributes-tag COUNT 1 + EXPECT pages-per-minute OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 + EXPECT pages-per-minute-color OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 IF-DEFINED PRINTER_IS_COLOR + EXPECT !pages-per-minute-color IF-NOT-DEFINED PRINTER_IS_COLOR + EXPECT printer-info OF-TYPE text IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{0,127}$$/" + EXPECT printer-location OF-TYPE text IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{0,127}$$/" + EXPECT printer-make-and-model OF-TYPE text IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE "/^.{0,127}$$/" + EXPECT printer-more-info OF-TYPE uri IN-GROUP printer-attributes-tag COUNT 1 +} + + +# +# End of "$Id: ipp-2.0.test 9238 2010-08-11 21:21:46Z mike $". +# diff --git a/test/ipp-2.1.test b/test/ipp-2.1.test new file mode 100644 index 00000000..7cb8c49f --- /dev/null +++ b/test/ipp-2.1.test @@ -0,0 +1,95 @@ +# +# "$Id: ipp-2.1.test 9230 2010-08-10 00:02:02Z mike $" +# +# IPP/2.1 test suite. +# +# Copyright 2007-2010 by Apple Inc. +# Copyright 2001-2006 by Easy Software Products. All rights reserved. +# +# These coded instructions, statements, and computer programs are the +# property of Apple Inc. and are protected by Federal copyright +# law. Distribution and use rights are outlined in the file "LICENSE.txt" +# which should have been included with this file. If this file is +# file is missing or damaged, see the license at "http://www.cups.org/". +# +# Usage: +# +# ./ipptool -V 2.1 {-d PRINTER_IS_COLOR=1} -f filename -t printer-uri ipp-2.1.test +# + +# Do all of the IPP/1.1 and IPP/2.0 tests as an IPP/2.1 client +INCLUDE "ipp-2.0.test" + + +# Test required printer description attribute support. +# +# Required by: PWG 5100.10 section 6.3 +{ + NAME "PWG 5100.10 section 6.3 - Required Printer Description Attributes" + OPERATION Get-Printer-Attributes + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR naturalLanguage attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR mimeMediaType document-format application/octet-stream + + STATUS successful-ok + + # Job template attributes + EXPECT job-hold-until-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag COUNT 1 + EXPECT job-hold-until-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag WITH-VALUE no-hold + EXPECT job-priority-default OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0,<101 + EXPECT job-priority-supported OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0,<101 + EXPECT job-settable-attributes-supported OF-TYPE keyword IN-GROUP printer-attributes-tag + EXPECT job-sheets-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag + EXPECT job-sheets-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag WITH-VALUE none + EXPECT media-col-default OF-TYPE collection IN-GROUP printer-attributes-tag COUNT 1 + EXPECT media-col-supported OF-TYPE keyword IN-GROUP printer-attributes-tag + EXPECT media-col-supported WITH-VALUE media-size + EXPECT media-default OF-TYPE keyword|name IN-GROUP printer-attributes-tag COUNT 1 + EXPECT media-supported OF-TYPE keyword|name IN-GROUP printer-attributes-tag + + # Subscription attributes + EXPECT notify-events-default OF-TYPE keyword IN-GROUP printer-attributes-tag + EXPECT notify-events-supported OF-TYPE keyword IN-GROUP printer-attributes-tag + EXPECT notify-lease-duration-default OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 + EXPECT notify-lease-duration-supported OF-TYPE integer|rangeOfInteger IN-GROUP printer-attributes-tag + EXPECT notify-max-events-supported OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >1 + EXPECT notify-pull-method-supported OF-TYPE keyword IN-GROUP printer-attributes-tag WITH-VALUE ippget + + # Printer description attributes + EXPECT ippget-event-life OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 + EXPECT multiple-operation-time-out OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 WITH-VALUE >0 + + EXPECT operations-supported WITH-VALUE 0x0005 # Create-Job + EXPECT operations-supported WITH-VALUE 0x0006 # Send-Document + EXPECT operations-supported WITH-VALUE 0x000C # Hold-Job + EXPECT operations-supported WITH-VALUE 0x000D # Release-Job + EXPECT operations-supported WITH-VALUE 0x000E # Restart-Job + EXPECT operations-supported WITH-VALUE 0x0010 # Pause-Printer + EXPECT operations-supported WITH-VALUE 0x0011 # Resume-Printer + EXPECT operations-supported WITH-VALUE 0x0012 # Purge-Jobs + EXPECT operations-supported WITH-VALUE 0x0013 # Set-Printer-Attributes + EXPECT operations-supported WITH-VALUE 0x0014 # Set-Job-Attributes + EXPECT operations-supported WITH-VALUE 0x0015 # Get-Printer-Supported-Values + EXPECT operations-supported WITH-VALUE 0x0016 # Create-Printer-Subscriptions + EXPECT operations-supported WITH-VALUE 0x0018 # Get-Subscription-Attributes + EXPECT operations-supported WITH-VALUE 0x0019 # Get-Subscriptions + EXPECT operations-supported WITH-VALUE 0x001A # Renew-Subscription + EXPECT operations-supported WITH-VALUE 0x001B # Cancel-Subscription + EXPECT operations-supported WITH-VALUE 0x001C # Get-Notifications + EXPECT operations-supported WITH-VALUE 0x0022 # Enable-Printer + EXPECT operations-supported WITH-VALUE 0x0023 # Disable-Printer + + EXPECT ?printer-alert OF-TYPE octetString IN-GROUP printer-attributes-tag + EXPECT ?printer-alert-description OF-TYPE text IN-GROUP printer-attributes-tag SAME-COUNT-AS printer-alert + EXPECT printer-settable-attributes-supported OF-TYPE keyword IN-GROUP printer-attributes-tag + EXPECT printer-state-change-time OF-TYPE integer IN-GROUP printer-attributes-tag COUNT 1 + EXPECT printer-state-reasons OF-TYPE keyword IN-GROUP printer-attributes-tag +} + + +# +# End of "$Id: ipp-2.1.test 9230 2010-08-10 00:02:02Z mike $". +# diff --git a/test/ippserver.c b/test/ippserver.c new file mode 100644 index 00000000..fc10d52b --- /dev/null +++ b/test/ippserver.c @@ -0,0 +1,4380 @@ +/* + * "$Id: ippserver.c 9793 2011-05-20 03:49:49Z mike $" + * + * Sample IPP/2.0 server for CUPS. + * + * Copyright 2010-2011 by Apple Inc. + * + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". + * + * This file is subject to the Apple OS-Developed Software exception. + * + * Contents: + * + * main() - Main entry to the sample server. + * clean_jobs() - Clean out old (completed) jobs. + * compare_jobs() - Compare two jobs. + * copy_attribute() - Copy a single attribute. + * copy_attributes() - Copy attributes from one request to another. + * copy_job_attrs() - Copy job attributes to the response. + * create_client() - Accept a new network connection and create a + * client object. + * create_job() - Create a new job object from a Print-Job or + * Create-Job request. + * create_listener() - Create a listener socket. + * create_media_col() - Create a media-col value. + * create_printer() - Create, register, and listen for connections + * to a printer object. + * create_requested_array() - Create an array for requested-attributes. + * debug_attributes() - Print attributes in a request or response. + * delete_client() - Close the socket and free all memory used by + * a client object. + * delete_job() - Remove from the printer and free all memory + * used by a job object. + * delete_printer() - Unregister, close listen sockets, and free + * all memory used by a printer object. + * dnssd_callback() - Handle Bonjour registration events. + * find_job() - Find a job specified in a request. + * html_escape() - Write a HTML-safe string. + * html_printf() - Send formatted text to the client, quoting + * as needed. + * ipp_cancel_job() - Cancel a job. + * ipp_create_job() - Create a job object. + * ipp_get_job_attributes() - Get the attributes for a job object. + * ipp_get_jobs() - Get a list of job objects. + * ipp_get_printer_attributes() - Get the attributes for a printer object. + * ipp_print_job() - Create a job object with an attached + * document. + * ipp_send_document() - Add an attached document to a job object + * created with Create-Job. + * ipp_validate_job() - Validate job creation attributes. + * process_client() - Process client requests on a thread. + * process_http() - Process a HTTP request. + * process_ipp() - Process an IPP request. + * process_job() - Process a print job. + * register_printer() - Register a printer object via Bonjour. + * respond_http() - Send a HTTP response. + * respond_ipp() - Send an IPP response. + * run_printer() - Run the printer service. + * usage() - Show program usage. + * valid_job_attributes() - Determine whether the job attributes are + * valid. + */ + +/* + * Include necessary headers... + */ + +#include <cups/cups-private.h> +#ifdef HAVE_DNSSD +# include <dns_sd.h> +#endif /* HAVE_DNSSD */ +#include <sys/stat.h> +#include <poll.h> +#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 */ + + +/* + * Constants... + */ + +enum _ipp_preasons_e /* printer-state-reasons bit values */ +{ + _IPP_PRINTER_NONE = 0x0000, /* none */ + _IPP_PRINTER_OTHER = 0x0001, /* other */ + _IPP_PRINTER_COVER_OPEN = 0x0002, /* cover-open */ + _IPP_PRINTER_INPUT_TRAY_MISSING = 0x0004, + /* input-tray-missing */ + _IPP_PRINTER_MARKER_SUPPLY_EMPTY = 0x0008, + /* marker-supply-empty */ + _IPP_PRINTER_MARKER_SUPPLY_LOW = 0x0010, + /* marker-suply-low */ + _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL = 0x0020, + /* marker-waste-almost-full */ + _IPP_PRINTER_MARKER_WASTE_FULL = 0x0040, + /* marker-waste-full */ + _IPP_PRINTER_MEDIA_EMPTY = 0x0080, /* media-empty */ + _IPP_PRINTER_MEDIA_JAM = 0x0100, /* media-jam */ + _IPP_PRINTER_MEDIA_LOW = 0x0200, /* media-low */ + _IPP_PRINTER_MEDIA_NEEDED = 0x0400, /* media-needed */ + _IPP_PRINTER_MOVING_TO_PAUSED = 0x0800, + /* moving-to-paused */ + _IPP_PRINTER_PAUSED = 0x1000, /* paused */ + _IPP_PRINTER_SPOOL_AREA_FULL = 0x2000,/* spool-area-full */ + _IPP_PRINTER_TONER_EMPTY = 0x4000, /* toner-empty */ + _IPP_PRINTER_TONER_LOW = 0x8000 /* toner-low */ +}; +typedef unsigned int _ipp_preasons_t; /* Bitfield for printer-state-reasons */ + +typedef enum _ipp_media_class_e +{ + _IPP_GENERAL, /* General-purpose size */ + _IPP_PHOTO_ONLY, /* Photo-only size */ + _IPP_ENV_ONLY /* Envelope-only size */ +} _ipp_media_class_t; + +static const char * const media_supported[] = +{ /* media-supported values */ + "iso_a4_210x297mm", /* A4 */ + "iso_a5_148x210mm", /* A5 */ + "iso_a6_105x148mm", /* A6 */ + "iso_dl_110x220mm", /* DL */ + "na_legal_8.5x14in", /* Legal */ + "na_letter_8.5x11in", /* Letter */ + "na_number-10_4.125x9.5in", /* #10 */ + "na_index-3x5_3x5in", /* 3x5 */ + "oe_photo-l_3.5x5in", /* L */ + "na_index-4x6_4x6in", /* 4x6 */ + "na_5x7_5x7in" /* 5x7 */ +}; +static const int media_col_sizes[][3] = +{ /* media-col-database sizes */ + { 21000, 29700, _IPP_GENERAL }, /* A4 */ + { 14800, 21000, _IPP_PHOTO_ONLY }, /* A5 */ + { 10500, 14800, _IPP_PHOTO_ONLY }, /* A6 */ + { 11000, 22000, _IPP_ENV_ONLY }, /* DL */ + { 21590, 35560, _IPP_GENERAL }, /* Legal */ + { 21590, 27940, _IPP_GENERAL }, /* Letter */ + { 10477, 24130, _IPP_ENV_ONLY }, /* #10 */ + { 7630, 12700, _IPP_PHOTO_ONLY }, /* 3x5 */ + { 8890, 12700, _IPP_PHOTO_ONLY }, /* L */ + { 10160, 15240, _IPP_PHOTO_ONLY }, /* 4x6 */ + { 12700, 17780, _IPP_PHOTO_ONLY } /* 5x7 */ +}; +static const char * const media_type_supported[] = + /* media-type-supported values */ +{ + "auto", + "cardstock", + "envelope", + "labels", + "other", + "photographic-glossy", + "photographic-high-gloss", + "photographic-matte", + "photographic-satin", + "photographic-semi-gloss", + "stationery", + "stationery-letterhead", + "transparency" +}; + + +/* + * Structures... + */ + +typedef struct _ipp_job_s _ipp_job_t; + +typedef struct _ipp_printer_s /**** Printer data ****/ +{ + int ipv4, /* IPv4 listener */ + ipv6; /* IPv6 listener */ +#ifdef HAVE_DNSSD + DNSServiceRef common_ref, /* Shared service connection */ + ipp_ref, /* Bonjour IPP service */ + http_ref, /* Bonjour HTTP service */ + printer_ref; /* Bonjour LPD service */ + TXTRecordRef ipp_txt; /* Bonjour IPP TXT record */ + char *dnssd_name; /* printer-dnssd-name */ +#endif /* HAVE_DNSSD */ + char *name, /* printer-name */ + *icon, /* Icon filename */ + *directory, /* Spool directory */ + *hostname, /* Hostname */ + *uri; /* printer-uri-supported */ + int port; /* Port */ + size_t urilen; /* Length of printer URI */ + ipp_t *attrs; /* Static attributes */ + ipp_pstate_t state; /* printer-state value */ + _ipp_preasons_t state_reasons; /* printer-state-reasons values */ + cups_array_t *jobs; /* Jobs */ + _ipp_job_t *active_job; /* Current active/pending job */ + int next_job_id; /* Next job-id value */ + _cups_rwlock_t rwlock; /* Printer lock */ +} _ipp_printer_t; + +struct _ipp_job_s /**** Job data ****/ +{ + int id; /* Job ID */ + char *name, /* job-name */ + *username, /* job-originating-user-name */ + *format; /* document-format */ + ipp_jstate_t state; /* job-state value */ + time_t processing, /* time-at-processing value */ + completed; /* time-at-completed value */ + ipp_t *attrs; /* Static attributes */ + int cancel; /* Non-zero when job canceled */ + char *filename; /* Print file name */ + int fd; /* Print file descriptor */ + _ipp_printer_t *printer; /* Printer */ +}; + +typedef struct _ipp_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 */ + http_addr_t addr; /* Client address */ + _ipp_printer_t *printer; /* Printer */ + _ipp_job_t *job; /* Current job, if any */ +} _ipp_client_t; + + +/* + * Local functions... + */ + +static void clean_jobs(_ipp_printer_t *printer); +static int compare_jobs(_ipp_job_t *a, _ipp_job_t *b); +static ipp_attribute_t *copy_attribute(ipp_t *to, ipp_attribute_t *attr, + ipp_tag_t group_tag, int quickcopy); +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(_ipp_client_t *client, + _ipp_job_t *job, cups_array_t *ra); +static _ipp_client_t *create_client(_ipp_printer_t *printer, int sock); +static _ipp_job_t *create_job(_ipp_client_t *client); +static int create_listener(int family, int *port); +static ipp_t *create_media_col(const char *media, const char *type, + int width, int length, int margins); +static _ipp_printer_t *create_printer(const char *servername, + const char *name, const char *location, + const char *make, const char *model, + const char *icon, + const char *docformats, int ppm, + int ppm_color, int duplex, int port, +#ifdef HAVE_DNSSD + const char *regtype, +#endif /* HAVE_DNSSD */ + const char *directory); +static cups_array_t *create_requested_array(_ipp_client_t *client); +static void debug_attributes(const char *title, ipp_t *ipp); +static void delete_client(_ipp_client_t *client); +static void delete_job(_ipp_job_t *job); +static void delete_printer(_ipp_printer_t *printer); +#ifdef HAVE_DNSSD +static void dnssd_callback(DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + _ipp_printer_t *printer); +#endif /* HAVE_DNSSD */ +static _ipp_job_t *find_job(_ipp_client_t *client); +static void html_escape(_ipp_client_t *client, const char *s, + size_t slen); +static void html_printf(_ipp_client_t *client, const char *format, + ...) +# ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 2, 3))) +# endif /* __GNUC__ */ +; +static void ipp_cancel_job(_ipp_client_t *client); +#if 0 +static void ipp_create_job(_ipp_client_t *client); +#endif /* 0 */ +static void ipp_get_job_attributes(_ipp_client_t *client); +static void ipp_get_jobs(_ipp_client_t *client); +static void ipp_get_printer_attributes(_ipp_client_t *client); +static void ipp_print_job(_ipp_client_t *client); +#if 0 +static void ipp_send_document(_ipp_client_t *client); +#endif /* 0 */ +static void ipp_validate_job(_ipp_client_t *client); +static void *process_client(_ipp_client_t *client); +static int process_http(_ipp_client_t *client); +static int process_ipp(_ipp_client_t *client); +static void *process_job(_ipp_job_t *job); +#ifdef HAVE_DNSSD +static int register_printer(_ipp_printer_t *printer, + const char *location, const char *make, + const char *model, const char *formats, + const char *adminurl, int color, + int duplex, const char *regtype); +#endif /* HAVE_DNSSD */ +static int respond_http(_ipp_client_t *client, http_status_t code, + const char *type, size_t length); +static void respond_ipp(_ipp_client_t *client, ipp_status_t status, + const char *message, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 3, 4))) +#endif /* __GNUC__ */ +; +static void run_printer(_ipp_printer_t *printer); +static void usage(int status); +static int valid_job_attributes(_ipp_client_t *client); + + +/* + * Globals... + */ + +static int KeepFiles = 0, + Verbosity = 0; + + +/* + * '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 */ + *servername = NULL, /* Server host name */ + *name = NULL, /* Printer name */ + *location = "", /* Location of printer */ + *make = "Test", /* Manufacturer */ + *model = "Printer", /* Model */ + *icon = "printer.png", /* Icon file */ + *formats = "application/pdf,image/jpeg"; + /* Supported formats */ +#ifdef HAVE_DNSSD + const char *regtype = "_ipp._tcp"; /* Bonjour service type */ +#endif /* HAVE_DNSSD */ + int port = 8631, /* Port number (0 = auto) TODO: FIX */ + duplex = 0, /* Duplex mode */ + ppm = 10, /* Pages per minute for mono */ + ppm_color = 0; /* Pages per minute for color */ + char directory[1024] = ""; /* Spool directory */ + _ipp_printer_t *printer; /* Printer object */ + + + /* + * Parse command-line arguments... + */ + + for (i = 1; i < argc; i ++) + if (argv[i][0] == '-') + { + for (opt = argv[i] + 1; *opt; opt ++) + switch (*opt) + { + case '2' : /* -2 (enable 2-sided printing) */ + duplex = 1; + break; + + case 'M' : /* -M manufacturer */ + i ++; + if (i >= argc) + usage(1); + make = 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); + formats = argv[i]; + break; + + case 'h' : /* -h (show help) */ + usage(0); + 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]; + 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); + port = atoi(argv[i]); + break; + +#ifdef HAVE_DNSSD + case 'r' : /* -r regtype */ + i ++; + if (i >= argc) + usage(1); + regtype = argv[i]; + break; +#endif /* HAVE_DNSSD */ + + case 's' : /* -s speed[,color-speed] */ + i ++; + if (i >= argc) + usage(1); + if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1) + usage(1); + break; + + case 'v' : /* -v (be verbose) */ + Verbosity ++; + break; + + default : /* Unknown */ + fprintf(stderr, "Unknown option \"-%c\".\n", *opt); + usage(1); + break; + } + } + else if (!name) + { + name = argv[i]; + } + else + { + fprintf(stderr, "Unexpected command-line argument \"%s\"\n", argv[i]); + usage(1); + } + + if (!name) + usage(1); + + /* + * Apply defaults as needed... + */ + + if (!directory[0]) + { + snprintf(directory, sizeof(directory), "/tmp/ippserver.%d", (int)getpid()); + + if (mkdir(directory, 0777) && errno != EEXIST) + { + fprintf(stderr, "Unable to create spool directory \"%s\": %s\n", + directory, strerror(errno)); + usage(1); + } + + if (Verbosity) + fprintf(stderr, "Using spool directory \"%s\".\n", directory); + } + + /* + * Create the printer... + */ + + if ((printer = create_printer(servername, name, location, make, model, icon, + formats, ppm, ppm_color, duplex, port, +#ifdef HAVE_DNSSD + regtype, +#endif /* HAVE_DNSSD */ + directory)) == NULL) + return (1); + + /* + * 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(_ipp_printer_t *printer) /* I - Printer */ +{ + _ipp_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 = (_ipp_job_t *)cupsArrayFirst(printer->jobs); + job; + job = (_ipp_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(_ipp_job_t *a, /* I - First job */ + _ipp_job_t *b) /* I - Second job */ +{ + return (b->id - a->id); +} + + +/* + * 'copy_attribute()' - Copy a single attribute. + */ + +static ipp_attribute_t * /* O - New attribute */ +copy_attribute( + ipp_t *to, /* O - Destination request/response */ + ipp_attribute_t *attr, /* I - Attribute to copy */ + ipp_tag_t group_tag, /* I - Group to put the copy in */ + int quickcopy) /* I - Do a quick copy? */ +{ + int i; /* Looping var */ + ipp_attribute_t *toattr; /* Destination attribute */ + + + if (Verbosity && attr->name) + { + char buffer[2048]; /* Attribute value */ + + _ippAttrString(attr, buffer, sizeof(buffer)); + + fprintf(stderr, "Copying %s (%s%s) %s\n", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag & ~IPP_TAG_COPY), buffer); + } + + switch (attr->value_tag & ~IPP_TAG_COPY) + { + case IPP_TAG_ZERO : + toattr = ippAddSeparator(to); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + toattr = ippAddIntegers(to, group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].integer = attr->values[i].integer; + break; + + case IPP_TAG_BOOLEAN : + toattr = ippAddBooleans(to, group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].boolean = attr->values[i].boolean; + 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 : + toattr = ippAddStrings(to, group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = attr->values[i].string.text; + } + else + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + break; + + case IPP_TAG_DATE : + toattr = ippAddDate(to, group_tag, attr->name, + attr->values[0].date); + break; + + case IPP_TAG_RESOLUTION : + toattr = ippAddResolutions(to, group_tag, attr->name, + attr->num_values, IPP_RES_PER_INCH, + NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].resolution.xres = attr->values[i].resolution.xres; + toattr->values[i].resolution.yres = attr->values[i].resolution.yres; + toattr->values[i].resolution.units = attr->values[i].resolution.units; + } + break; + + case IPP_TAG_RANGE : + toattr = ippAddRanges(to, group_tag, attr->name, + attr->num_values, NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].range.lower = attr->values[i].range.lower; + toattr->values[i].range.upper = attr->values[i].range.upper; + } + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + toattr = ippAddStrings(to, group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].string.charset = attr->values[i].string.charset; + toattr->values[i].string.text = attr->values[i].string.text; + } + } + else + { + for (i = 0; i < attr->num_values; i ++) + { + if (!i) + toattr->values[i].string.charset = + _cupsStrAlloc(attr->values[i].string.charset); + else + toattr->values[i].string.charset = + toattr->values[0].string.charset; + + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + toattr = ippAddCollections(to, group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].collection = attr->values[i].collection; + attr->values[i].collection->use ++; + } + break; + + case IPP_TAG_STRING : + if (quickcopy) + { + toattr = ippAddOctetString(to, group_tag, attr->name, NULL, 0); + toattr->value_tag |= quickcopy; + toattr->values[0].unknown.data = attr->values[0].unknown.data; + toattr->values[0].unknown.length = attr->values[0].unknown.length; + } + else + toattr = ippAddOctetString(to, attr->group_tag, attr->name, + attr->values[0].unknown.data, + attr->values[0].unknown.length); + break; + + default : + toattr = ippAddIntegers(to, group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].unknown.length = attr->values[i].unknown.length; + + if (toattr->values[i].unknown.length > 0) + { + if ((toattr->values[i].unknown.data = + malloc(toattr->values[i].unknown.length)) == NULL) + toattr->values[i].unknown.length = 0; + else + memcpy(toattr->values[i].unknown.data, + attr->values[i].unknown.data, + toattr->values[i].unknown.length); + } + } + break; /* anti-compiler-warning-code */ + } + + return (toattr); +} + + +/* + * '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? */ +{ + ipp_attribute_t *fromattr; /* Source attribute */ + + + if (!to || !from) + return; + + for (fromattr = from->attrs; fromattr; fromattr = fromattr->next) + { + /* + * Filter attributes as needed... + */ + + if ((group_tag != IPP_TAG_ZERO && fromattr->group_tag != group_tag && + fromattr->group_tag != IPP_TAG_ZERO) || !fromattr->name) + continue; + + if (!ra || cupsArrayFind(ra, fromattr->name)) + copy_attribute(to, fromattr, fromattr->group_tag, quickcopy); + } +} + + +/* + * 'copy_job_attrs()' - Copy job attributes to the response. + */ + +static void +copy_job_attributes( + _ipp_client_t *client, /* I - Client */ + _ipp_job_t *job, /* I - Job */ + cups_array_t *ra) /* I - requested-attributes */ +{ + copy_attributes(client->response, job->attrs, ra, IPP_TAG_ZERO, 0); + + if (!ra || cupsArrayFind(ra, "job-printer-up-time")) + ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER, + "job-printer-up-time", (int)time(NULL)); + + if (!ra || cupsArrayFind(ra, "job-state")) + ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_ENUM, + "job-state", job->state); + + if (!ra || cupsArrayFind(ra, "job-state-reasons")) + { + switch (job->state) + { + case IPP_JOB_PENDING : + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "none"); + break; + + case IPP_JOB_HELD : + if (job->fd >= 0) + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "job-incoming"); + else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO)) + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "job-hold-until-specified"); + break; + + case IPP_JOB_PROCESSING : + if (job->cancel) + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "processing-to-stop-point"); + else + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "job-printing"); + break; + + case IPP_JOB_STOPPED : + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "job-stopped"); + break; + + case IPP_JOB_CANCELED : + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "job-canceled-by-user"); + break; + + case IPP_JOB_ABORTED : + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons", + NULL, "aborted-by-system"); + break; + + case IPP_JOB_COMPLETED : + ippAddString(client->response, IPP_TAG_JOB, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "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", job->completed); + + if (!ra || cupsArrayFind(ra, "time-at-processing")) + ippAddInteger(client->response, IPP_TAG_JOB, + job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE, + "time-at-processing", job->processing); +} + + +/* + * 'create_client()' - Accept a new network connection and create a client + * object. + */ + +static _ipp_client_t * /* O - Client */ +create_client(_ipp_printer_t *printer, /* I - Printer */ + int sock) /* I - Listen socket */ +{ + _ipp_client_t *client; /* Client */ + int val; /* Parameter value */ + socklen_t addrlen; /* Length of address */ + + + if ((client = calloc(1, sizeof(_ipp_client_t))) == NULL) + { + perror("Unable to allocate memory for client"); + return (NULL); + } + + client->printer = printer; + client->http.activity = time(NULL); + client->http.hostaddr = &(client->addr); + client->http.blocking = 1; + + /* + * Accept the client and get the remote address... + */ + + addrlen = sizeof(http_addr_t); + + if ((client->http.fd = accept(sock, (struct sockaddr *)&(client->addr), + &addrlen)) < 0) + { + perror("Unable to accept client connection"); + + free(client); + + return (NULL); + } + + httpAddrString(&(client->addr), client->http.hostname, + sizeof(client->http.hostname)); + + if (Verbosity) + fprintf(stderr, "Accepted connection from %s (%s)\n", client->http.hostname, + client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6"); + + /* + * Using TCP_NODELAY improves responsiveness, especially on systems + * with a slow loopback interface. Since we write large buffers + * when sending print files and requests, there shouldn't be any + * performance penalty for this... + */ + + val = 1; + setsockopt(client->http.fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, + sizeof(val)); + + return (client); +} + + +/* + * 'create_job()' - Create a new job object from a Print-Job or Create-Job + * request. + */ + +static _ipp_job_t * /* O - Job */ +create_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job */ + ipp_attribute_t *attr; /* Job attribute */ + char uri[1024]; /* job-uri value */ + + + _cupsRWLockWrite(&(client->printer->rwlock)); + if (client->printer->active_job && + client->printer->active_job->state < IPP_JOB_CANCELED) + { + /* + * Only accept a single job at a time... + */ + + _cupsRWLockWrite(&(client->printer->rwlock)); + return (NULL); + } + + /* + * Allocate and initialize the job object... + */ + + if ((job = calloc(1, sizeof(_ipp_job_t))) == NULL) + { + perror("Unable to allocate memory for job"); + return (NULL); + } + + job->printer = client->printer; + job->attrs = client->request; + job->state = IPP_JOB_HELD; + job->fd = -1; + client->request = NULL; + + /* + * Set all but the first two attributes to the job attributes group... + */ + + for (attr = job->attrs->attrs->next->next; attr; attr = attr->next) + attr->group_tag = IPP_TAG_JOB; + + /* + * Get the requesting-user-name, document format, and priority... + */ + + if ((attr = ippFindAttribute(job->attrs, "requesting-user-name", + IPP_TAG_NAME)) != NULL) + { + _cupsStrFree(attr->name); + attr->name = _cupsStrAlloc("job-originating-user-name"); + } + else + attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME | IPP_TAG_COPY, + "job-originating-user-name", NULL, "anonymous"); + + if (attr) + job->username = attr->values[0].string.text; + else + job->username = "anonymous"; + + if ((attr = ippFindAttribute(job->attrs, "document-format", + IPP_TAG_MIMETYPE)) != NULL) + job->format = attr->values[0].string.text; + else + job->format = "application/octet-stream"; + + /* + * 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); + + 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-printer-uri", NULL, + client->printer->uri); + ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", + (int)time(NULL)); + + cupsArrayAdd(client->printer->jobs, job); + client->printer->active_job = job; + + _cupsRWUnlock(&(client->printer->rwlock)); + + return (job); +} + + +/* + * 'create_listener()' - Create a listener socket. + */ + +static int /* O - Listener socket or -1 on error */ +create_listener(int family, /* I - Address family */ + int *port) /* IO - Port number */ +{ + int sock, /* Listener socket */ + val; /* Socket value */ + http_addr_t address; /* Listen address */ + socklen_t addrlen; /* Length of listen address */ + + + if ((sock = socket(family, SOCK_STREAM, 0)) < 0) + return (-1); + + val = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + +#ifdef IPV6_V6ONLY + if (family == AF_INET6) + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); +#endif /* IPV6_V6ONLY */ + + if (!*port) + { + /* + * Get the auto-assigned port number for the IPv4 socket... + */ + + /* TODO: This code does not appear to work - port is always 0... */ + addrlen = sizeof(address); + if (getsockname(sock, (struct sockaddr *)&address, &addrlen)) + { + perror("getsockname() failed"); + *port = 8631; + } + else + *port = _httpAddrPort(&address); + + fprintf(stderr, "Listening on port %d.\n", *port); + } + + memset(&address, 0, sizeof(address)); + address.addr.sa_family = family; + _httpAddrSetPort(&address, *port); + + if (bind(sock, (struct sockaddr *)&address, httpAddrLength(&address))) + { + close(sock); + return (-1); + } + + if (listen(sock, 5)) + { + close(sock); + return (-1); + } + + 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 *type, /* I - Nedua type */ + int width, /* I - x-dimension in 2540ths */ + int length, /* I - y-dimension in 2540ths */ + int margins) /* I - Value for margins */ +{ + ipp_t *media_col = ippNew(), /* media-col value */ + *media_size = ippNew(); /* media-size value */ + char media_key[256]; /* media-key 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); + + snprintf(media_key, sizeof(media_key), "%s_%s%s", media, type, + margins == 0 ? "_borderless" : ""); + + ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL, + media_key); + ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-bottom-margin", margins); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-left-margin", margins); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-right-margin", margins); + ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-top-margin", margins); + ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type", + NULL, type); + + ippDelete(media_size); + + return (media_col); +} + + +/* + * 'create_printer()' - Create, register, and listen for connections to a + * printer object. + */ + +static _ipp_printer_t * /* O - Printer */ +create_printer(const char *servername, /* I - Server hostname (NULL for default) */ + const char *name, /* I - printer-name */ + const char *location, /* I - printer-location */ + const char *make, /* I - printer-make-and-model */ + const char *model, /* I - printer-make-and-model */ + const char *icon, /* I - printer-icons */ + const char *docformats, /* I - document-format-supported */ + int ppm, /* I - Pages per minute in grayscale */ + int ppm_color, /* I - Pages per minute in color (0 for gray) */ + int duplex, /* I - 1 = duplex, 0 = simplex */ + int port, /* I - Port for listeners or 0 for auto */ +#ifdef HAVE_DNSSD + const char *regtype, /* I - Bonjour service type */ +#endif /* HAVE_DNSSD */ + const char *directory) /* I - Spool directory */ +{ + int i, j; /* Looping vars */ + _ipp_printer_t *printer; /* Printer */ + char hostname[256], /* Hostname */ + uri[1024], /* Printer URI */ + icons[1024], /* printer-icons URI */ + adminurl[1024], /* printer-more-info URI */ + device_id[1024],/* printer-device-id */ + make_model[128];/* printer-make-and-model */ + int num_formats; /* Number of document-format-supported values */ + char *defformat, /* document-format-default value */ + *formats[100], /* document-format-supported values */ + *ptr; /* Pointer into string */ + const char *prefix; /* Prefix string */ + int num_database; /* Number of database values */ + ipp_attribute_t *media_col_database; + /* media-col-database value */ + ipp_t *media_col_default; + /* media-col-default value */ + ipp_value_t *media_col_value; + /* Current media-col-database value */ + int k_supported; /* Maximum file size supported */ +#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 int orients[4] = /* orientation-requested-supported values */ + { + IPP_PORTRAIT, + IPP_LANDSCAPE, + IPP_REVERSE_LANDSCAPE, + IPP_REVERSE_PORTRAIT + }; + static const char * const versions[] =/* ipp-versions-supported values */ + { + "1.0", + "1.1", + "2.0" + }; + static const int ops[] = /* operations-supported values */ + { + IPP_PRINT_JOB, + IPP_VALIDATE_JOB, + IPP_CREATE_JOB, + IPP_SEND_DOCUMENT, + IPP_CANCEL_JOB, + IPP_GET_JOB_ATTRIBUTES, + IPP_GET_JOBS, + IPP_GET_PRINTER_ATTRIBUTES + }; + static const char * const charsets[] =/* charset-supported values */ + { + "us-ascii", + "utf-8" + }; + static const char * const job_creation[] = + { /* job-creation-attributes-supported values */ + "copies", + "ipp-attribute-fidelity", + "job-name", + "job-priority", + "media", + "media-col", + "multiple-document-handling", + "orientation-requested", + "print-quality", + "sides" + }; + static const char * const media_col_supported[] = + { /* media-col-supported values */ + "media-bottom-margin", + "media-left-margin", + "media-right-margin", + "media-size", + "media-top-margin", + "media-type" + }; + static const int media_xxx_margin_supported[] = + { /* media-xxx-margin-supported values */ + 0, + 635 + }; + static const char * const multiple_document_handling[] = + { /* multiple-document-handling-supported values */ + "separate-documents-uncollated-copies", + "separate-documents-collated-copies" + }; + static const int print_quality_supported[] = + { /* print-quality-supported values */ + IPP_QUALITY_DRAFT, + IPP_QUALITY_NORMAL, + IPP_QUALITY_HIGH + }; + static const char * const sides_supported[] = + { /* sides-supported values */ + "one-sided", + "two-sided-long-edge", + "two-sided-short-edge" + }; + static const char * const which_jobs[] = + { /* which-jobs-supported values */ + "completed", + "not-completed", + "aborted", + "all", + "canceled", + "pending", + "pending-held", + "processing", + "processing-stopped" + }; + + + /* + * Allocate memory for the printer... + */ + + if ((printer = calloc(1, sizeof(_ipp_printer_t))) == NULL) + { + perror("Unable to allocate memory for printer"); + return (NULL); + } + + printer->ipv4 = -1; + printer->ipv6 = -1; + printer->name = _cupsStrAlloc(name); +#ifdef HAVE_DNSSD + printer->dnssd_name = _cupsStrRetain(printer->name); +#endif /* HAVE_DNSSD */ + printer->directory = _cupsStrAlloc(directory); + printer->hostname = _cupsStrAlloc(servername ? servername : + httpGetHostname(NULL, hostname, + sizeof(hostname))); + printer->port = port; + printer->state = IPP_PRINTER_IDLE; + printer->state_reasons = _IPP_PRINTER_NONE; + printer->jobs = cupsArrayNew((cups_array_func_t)compare_jobs, NULL); + printer->next_job_id = 1; + + httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, + printer->hostname, printer->port, "/ipp"); + printer->uri = _cupsStrAlloc(uri); + printer->urilen = strlen(uri); + + _cupsRWInit(&(printer->rwlock)); + + /* + * Create the listener sockets... + */ + + if ((printer->ipv4 = create_listener(AF_INET, &(printer->port))) < 0) + { + perror("Unable to create IPv4 listener"); + goto bad_printer; + } + + if ((printer->ipv6 = create_listener(AF_INET6, &(printer->port))) < 0) + { + perror("Unable to create IPv6 listener"); + goto bad_printer; + } + + /* + * Prepare values for the printer attributes... + */ + + httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), "http", NULL, + printer->hostname, printer->port, "/icon.png"); + httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), "http", NULL, + printer->hostname, printer->port, "/"); + + if (Verbosity) + { + fprintf(stderr, "printer-more-info=\"%s\"\n", adminurl); + fprintf(stderr, "printer-uri=\"%s\"\n", uri); + } + + snprintf(make_model, sizeof(make_model), "%s %s", make, model); + + num_formats = 1; + formats[0] = strdup(docformats); + defformat = formats[0]; + for (ptr = strchr(formats[0], ','); ptr; ptr = strchr(ptr, ',')) + { + *ptr++ = '\0'; + formats[num_formats++] = ptr; + + if (!_cups_strcasecmp(ptr, "application/octet-stream")) + defformat = ptr; + } + + snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;", make, model); + ptr = device_id + strlen(device_id); + prefix = "CMD:"; + for (i = 0; i < num_formats; i ++) + { + if (!_cups_strcasecmp(formats[i], "application/pdf")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPDF", prefix); + else if (!_cups_strcasecmp(formats[i], "application/postscript")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPS", prefix); + else if (!_cups_strcasecmp(formats[i], "application/vnd.hp-PCL")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPCL", prefix); + else if (!_cups_strcasecmp(formats[i], "image/jpeg")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sJPEG", prefix); + else if (!_cups_strcasecmp(formats[i], "image/png")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPNG", prefix); + else if (_cups_strcasecmp(formats[i], "application/octet-stream")) + snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%s%s", prefix, + formats[i]); + + ptr += strlen(ptr); + prefix = ","; + } + strlcat(device_id, ";", sizeof(device_id)); + + /* + * 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 */ + + /* + * Create the printer attributes. This list of attributes is sorted to improve + * performance when the client provides a requested-attributes attribute... + */ + + printer->attrs = ippNew(); + + /* charset-configured */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY, + "charset-configured", NULL, "utf-8"); + + /* charset-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY, + "charset-supported", sizeof(charsets) / sizeof(charsets[0]), + NULL, charsets); + + /* color-supported */ + ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "color-supported", + ppm_color > 0); + + /* compression-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "compression-supported", NULL, "none"); + + /* copies-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "copies-default", 1); + + /* copies-supported */ + ippAddRange(printer->attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999); + + /* document-format-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, + "document-format-default", NULL, defformat); + + /* document-format-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE, + "document-format-supported", num_formats, NULL, + (const char * const *)formats); + + /* finishings-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "finishings-default", IPP_FINISHINGS_NONE); + + /* finishings-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "finishings-supported", IPP_FINISHINGS_NONE); + + /* generated-natural-language-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY, + "generated-natural-language-supported", NULL, "en"); + + /* ipp-versions-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "ipp-versions-supported", + sizeof(versions) / sizeof(versions[0]), NULL, versions); + + /* job-creation-attributes-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "job-creation-attributes-supported", + sizeof(job_creation) / sizeof(job_creation[0]), + NULL, job_creation); + + /* 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", 100); + + /* job-sheets-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "job-sheets-default", NULL, "none"); + + /* job-sheets-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "job-sheets-supported", NULL, "none"); + + /* media-bottom-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-bottom-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* media-col-database */ + for (num_database = 0, i = 0; + i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0])); + i ++) + { + if (media_col_sizes[i][2] == _IPP_ENV_ONLY) + num_database += 2; /* auto + envelope */ + else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY) + num_database += 12; /* auto + photographic-* + borderless */ + else + num_database += (int)(sizeof(media_type_supported) / + sizeof(media_type_supported[0])) + 6; + /* All types + borderless */ + } + + media_col_database = ippAddCollections(printer->attrs, IPP_TAG_PRINTER, + "media-col-database", num_database, + NULL); + for (media_col_value = media_col_database->values, i = 0; + i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0])); + i ++) + { + for (j = 0; + j < (int)(sizeof(media_type_supported) / + sizeof(media_type_supported[0])); + j ++) + { + if (media_col_sizes[i][2] == _IPP_ENV_ONLY && + strcmp(media_type_supported[j], "auto") && + strcmp(media_type_supported[j], "envelope")) + continue; + else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY && + strcmp(media_type_supported[j], "auto") && + strncmp(media_type_supported[j], "photographic-", 13)) + continue; + + media_col_value->collection = + create_media_col(media_supported[i], media_type_supported[j], + media_col_sizes[i][0], media_col_sizes[i][1], + media_xxx_margin_supported[1]); + media_col_value ++; + + if (media_col_sizes[i][2] != _IPP_ENV_ONLY && + (!strcmp(media_type_supported[j], "auto") || + !strncmp(media_type_supported[j], "photographic-", 13))) + { + /* + * Add borderless version for this combination... + */ + + media_col_value->collection = + create_media_col(media_supported[i], media_type_supported[j], + media_col_sizes[i][0], media_col_sizes[i][1], + media_xxx_margin_supported[0]); + media_col_value ++; + } + } + } + + /* media-col-default */ + media_col_default = create_media_col(media_supported[0], + media_type_supported[0], + media_col_sizes[0][0], + media_col_sizes[0][1], + media_xxx_margin_supported[1]); + + ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-default", + media_col_default); + ippDelete(media_col_default); + + /* media-col-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "media-col-supported", + (int)(sizeof(media_col_supported) / + sizeof(media_col_supported[0])), NULL, + media_col_supported); + + /* media-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "media-default", NULL, media_supported[0]); + + /* media-left-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-left-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* media-right-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-right-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* media-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "media-supported", + (int)(sizeof(media_supported) / sizeof(media_supported[0])), + NULL, media_supported); + + /* media-top-margin-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "media-top-margin-supported", + (int)(sizeof(media_xxx_margin_supported) / + sizeof(media_xxx_margin_supported[0])), + media_xxx_margin_supported); + + /* multiple-document-handling-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "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); + + /* natural-language-configured */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY, + "natural-language-configured", NULL, "en"); + + /* number-up-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "number-up-default", 1); + + /* number-up-supported */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "number-up-supported", 1); + + /* operations-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "operations-supported", sizeof(ops) / sizeof(ops[0]), ops); + + /* orientation-requested-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, + "orientation-requested-default", 0); + + /* orientation-requested-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "orientation-requested-supported", 4, orients); + + /* output-bin-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "output-bin-default", NULL, "face-down"); + + /* output-bin-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "output-bin-supported", NULL, "face-down"); + + /* pages-per-minute */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "pages-per-minute", ppm); + + /* pages-per-minute-color */ + if (ppm_color > 0) + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "pages-per-minute-color", ppm_color); + + /* pdl-override-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "pdl-override-supported", NULL, "attempted"); + + /* print-quality-default */ + ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "print-quality-default", IPP_QUALITY_NORMAL); + + /* print-quality-supported */ + ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "print-quality-supported", + (int)(sizeof(print_quality_supported) / + sizeof(print_quality_supported[0])), + print_quality_supported); + + /* printer-device-id */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-device-id", NULL, device_id); + + /* 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-make-and-model */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-make-and-model", NULL, make_model); + + /* 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-resolution-default */ + ippAddResolution(printer->attrs, IPP_TAG_PRINTER, + "printer-resolution-default", IPP_RES_PER_INCH, 600, 600); + + /* printer-resolution-supported */ + ippAddResolution(printer->attrs, IPP_TAG_PRINTER, + "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600); + + /* printer-uri-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-uri-supported", NULL, uri); + + /* sides-default */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "sides-default", NULL, "one-sided"); + + /* sides-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "sides-supported", duplex ? 3 : 1, NULL, sides_supported); + + /* uri-authentication-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "uri-authentication-supported", NULL, "none"); + + /* uri-security-supported */ + ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "uri-security-supported", NULL, "none"); + + /* which-jobs-supported */ + ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY, + "which-jobs-supported", + sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs); + + free(formats[0]); + + debug_attributes("Printer", printer->attrs); + +#ifdef HAVE_DNSSD + /* + * Register the printer with Bonjour... + */ + + if (!register_printer(printer, location, make, model, docformats, adminurl, + ppm_color > 0, duplex, regtype)) + goto bad_printer; +#endif /* HAVE_DNSSD */ + + /* + * Return it! + */ + + return (printer); + + + /* + * If we get here we were unable to create the printer... + */ + + bad_printer: + + delete_printer(printer); + return (NULL); +} + + +/* + * 'create_requested_array()' - Create an array for requested-attributes. + */ + +static cups_array_t * /* O - requested-attributes array */ +create_requested_array( + _ipp_client_t *client) /* I - Client */ +{ + int i; /* Looping var */ + ipp_attribute_t *requested; /* requested-attributes attribute */ + cups_array_t *ra; /* Requested attributes array */ + char *value; /* Current value */ + + + /* + * Get the requested-attributes attribute, and return NULL if we don't + * have one... + */ + + if ((requested = ippFindAttribute(client->request, "requested-attributes", + IPP_TAG_KEYWORD)) == NULL) + return (NULL); + + /* + * If the attribute contains a single "all" keyword, return NULL... + */ + + if (requested->num_values == 1 && + !strcmp(requested->values[0].string.text, "all")) + return (NULL); + + /* + * Create an array using "strcmp" as the comparison function... + */ + + ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); + + for (i = 0; i < requested->num_values; i ++) + { + value = requested->values[i].string.text; + + if (!strcmp(value, "job-template")) + { + cupsArrayAdd(ra, "copies"); + cupsArrayAdd(ra, "copies-default"); + cupsArrayAdd(ra, "copies-supported"); + cupsArrayAdd(ra, "finishings"); + cupsArrayAdd(ra, "finishings-default"); + cupsArrayAdd(ra, "finishings-supported"); + cupsArrayAdd(ra, "job-hold-until"); + cupsArrayAdd(ra, "job-hold-until-default"); + cupsArrayAdd(ra, "job-hold-until-supported"); + cupsArrayAdd(ra, "job-priority"); + cupsArrayAdd(ra, "job-priority-default"); + cupsArrayAdd(ra, "job-priority-supported"); + cupsArrayAdd(ra, "job-sheets"); + cupsArrayAdd(ra, "job-sheets-default"); + cupsArrayAdd(ra, "job-sheets-supported"); + cupsArrayAdd(ra, "media"); + cupsArrayAdd(ra, "media-col"); + cupsArrayAdd(ra, "media-col-default"); + cupsArrayAdd(ra, "media-col-supported"); + cupsArrayAdd(ra, "media-default"); + cupsArrayAdd(ra, "media-source-supported"); + cupsArrayAdd(ra, "media-supported"); + cupsArrayAdd(ra, "media-type-supported"); + cupsArrayAdd(ra, "multiple-document-handling"); + cupsArrayAdd(ra, "multiple-document-handling-default"); + cupsArrayAdd(ra, "multiple-document-handling-supported"); + cupsArrayAdd(ra, "number-up"); + cupsArrayAdd(ra, "number-up-default"); + cupsArrayAdd(ra, "number-up-supported"); + cupsArrayAdd(ra, "orientation-requested"); + cupsArrayAdd(ra, "orientation-requested-default"); + cupsArrayAdd(ra, "orientation-requested-supported"); + cupsArrayAdd(ra, "page-ranges"); + cupsArrayAdd(ra, "page-ranges-supported"); + cupsArrayAdd(ra, "printer-resolution"); + cupsArrayAdd(ra, "printer-resolution-default"); + cupsArrayAdd(ra, "printer-resolution-supported"); + cupsArrayAdd(ra, "print-quality"); + cupsArrayAdd(ra, "print-quality-default"); + cupsArrayAdd(ra, "print-quality-supported"); + cupsArrayAdd(ra, "sides"); + cupsArrayAdd(ra, "sides-default"); + cupsArrayAdd(ra, "sides-supported"); + } + else if (!strcmp(value, "job-description")) + { + cupsArrayAdd(ra, "date-time-at-completed"); + cupsArrayAdd(ra, "date-time-at-creation"); + cupsArrayAdd(ra, "date-time-at-processing"); + cupsArrayAdd(ra, "job-detailed-status-message"); + cupsArrayAdd(ra, "job-document-access-errors"); + cupsArrayAdd(ra, "job-id"); + cupsArrayAdd(ra, "job-impressions"); + cupsArrayAdd(ra, "job-impressions-completed"); + cupsArrayAdd(ra, "job-k-octets"); + cupsArrayAdd(ra, "job-k-octets-processed"); + cupsArrayAdd(ra, "job-media-sheets"); + cupsArrayAdd(ra, "job-media-sheets-completed"); + cupsArrayAdd(ra, "job-message-from-operator"); + cupsArrayAdd(ra, "job-more-info"); + cupsArrayAdd(ra, "job-name"); + cupsArrayAdd(ra, "job-originating-user-name"); + cupsArrayAdd(ra, "job-printer-up-time"); + cupsArrayAdd(ra, "job-printer-uri"); + cupsArrayAdd(ra, "job-state"); + cupsArrayAdd(ra, "job-state-message"); + cupsArrayAdd(ra, "job-state-reasons"); + cupsArrayAdd(ra, "job-uri"); + cupsArrayAdd(ra, "number-of-documents"); + cupsArrayAdd(ra, "number-of-intervening-jobs"); + cupsArrayAdd(ra, "output-device-assigned"); + cupsArrayAdd(ra, "time-at-completed"); + cupsArrayAdd(ra, "time-at-creation"); + cupsArrayAdd(ra, "time-at-processing"); + } + else if (!strcmp(value, "printer-description")) + { + cupsArrayAdd(ra, "charset-configured"); + cupsArrayAdd(ra, "charset-supported"); + cupsArrayAdd(ra, "color-supported"); + cupsArrayAdd(ra, "compression-supported"); + cupsArrayAdd(ra, "document-format-default"); + cupsArrayAdd(ra, "document-format-supported"); + cupsArrayAdd(ra, "generated-natural-language-supported"); + cupsArrayAdd(ra, "ipp-versions-supported"); + cupsArrayAdd(ra, "job-impressions-supported"); + cupsArrayAdd(ra, "job-k-octets-supported"); + cupsArrayAdd(ra, "job-media-sheets-supported"); + cupsArrayAdd(ra, "multiple-document-jobs-supported"); + cupsArrayAdd(ra, "multiple-operation-time-out"); + cupsArrayAdd(ra, "natural-language-configured"); + cupsArrayAdd(ra, "notify-attributes-supported"); + cupsArrayAdd(ra, "notify-lease-duration-default"); + cupsArrayAdd(ra, "notify-lease-duration-supported"); + cupsArrayAdd(ra, "notify-max-events-supported"); + cupsArrayAdd(ra, "notify-events-default"); + cupsArrayAdd(ra, "notify-events-supported"); + cupsArrayAdd(ra, "notify-pull-method-supported"); + cupsArrayAdd(ra, "notify-schemes-supported"); + cupsArrayAdd(ra, "operations-supported"); + cupsArrayAdd(ra, "pages-per-minute"); + cupsArrayAdd(ra, "pages-per-minute-color"); + cupsArrayAdd(ra, "pdl-override-supported"); + cupsArrayAdd(ra, "printer-alert"); + cupsArrayAdd(ra, "printer-alert-description"); + cupsArrayAdd(ra, "printer-current-time"); + cupsArrayAdd(ra, "printer-driver-installer"); + cupsArrayAdd(ra, "printer-info"); + cupsArrayAdd(ra, "printer-is-accepting-jobs"); + cupsArrayAdd(ra, "printer-location"); + cupsArrayAdd(ra, "printer-make-and-model"); + cupsArrayAdd(ra, "printer-message-from-operator"); + cupsArrayAdd(ra, "printer-more-info"); + cupsArrayAdd(ra, "printer-more-info-manufacturer"); + cupsArrayAdd(ra, "printer-name"); + cupsArrayAdd(ra, "printer-state"); + cupsArrayAdd(ra, "printer-state-message"); + cupsArrayAdd(ra, "printer-state-reasons"); + cupsArrayAdd(ra, "printer-up-time"); + cupsArrayAdd(ra, "printer-uri-supported"); + cupsArrayAdd(ra, "queued-job-count"); + cupsArrayAdd(ra, "reference-uri-schemes-supported"); + cupsArrayAdd(ra, "uri-authentication-supported"); + cupsArrayAdd(ra, "uri-security-supported"); + } + else if (!strcmp(value, "printer-defaults")) + { + cupsArrayAdd(ra, "copies-default"); + cupsArrayAdd(ra, "document-format-default"); + cupsArrayAdd(ra, "finishings-default"); + cupsArrayAdd(ra, "job-hold-until-default"); + cupsArrayAdd(ra, "job-priority-default"); + cupsArrayAdd(ra, "job-sheets-default"); + cupsArrayAdd(ra, "media-default"); + cupsArrayAdd(ra, "media-col-default"); + cupsArrayAdd(ra, "number-up-default"); + cupsArrayAdd(ra, "orientation-requested-default"); + cupsArrayAdd(ra, "sides-default"); + } + else if (!strcmp(value, "subscription-template")) + { + cupsArrayAdd(ra, "notify-attributes"); + cupsArrayAdd(ra, "notify-charset"); + cupsArrayAdd(ra, "notify-events"); + cupsArrayAdd(ra, "notify-lease-duration"); + cupsArrayAdd(ra, "notify-natural-language"); + cupsArrayAdd(ra, "notify-pull-method"); + cupsArrayAdd(ra, "notify-recipient-uri"); + cupsArrayAdd(ra, "notify-time-interval"); + cupsArrayAdd(ra, "notify-user-data"); + } + else + cupsArrayAdd(ra, value); + } + + return (ra); +} + + +/* + * 'debug_attributes()' - Print attributes in a request or response. + */ + +static void +debug_attributes(const char *title, /* I - Title */ + ipp_t *ipp) /* I - Request/response */ +{ + ipp_tag_t group_tag; /* Current group */ + ipp_attribute_t *attr; /* Current attribute */ + char buffer[2048]; /* String buffer for value */ + + + if (Verbosity <= 1) + return; + + fprintf(stderr, "%s:\n", title); + for (attr = ipp->attrs, group_tag = IPP_TAG_ZERO; attr; attr = attr->next) + { + if (attr->group_tag != group_tag) + { + group_tag = attr->group_tag; + fprintf(stderr, " %s\n", ippTagString(group_tag)); + } + + if (attr->name) + { + _ippAttrString(attr, buffer, sizeof(buffer)); + fprintf(stderr, " %s (%s%s) %s\n", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag), buffer); + } + } +} + + +/* + * 'delete_client()' - Close the socket and free all memory used by a client + * object. + */ + +static void +delete_client(_ipp_client_t *client) /* I - Client */ +{ + if (Verbosity) + fprintf(stderr, "Closing connection from %s (%s)\n", client->http.hostname, + client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6"); + + /* + * Flush pending writes before closing... + */ + + httpFlushWrite(&(client->http)); + + if (client->http.fd >= 0) + close(client->http.fd); + + /* + * Free memory... + */ + + httpClearCookie(&(client->http)); + httpClearFields(&(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(_ipp_job_t *job) /* I - Job */ +{ + if (Verbosity) + fprintf(stderr, "Removing job #%d from history.\n", job->id); + + ippDelete(job->attrs); + + 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(_ipp_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->http_ref) + DNSServiceRefDeallocate(printer->http_ref); + + if (printer->common_ref) + DNSServiceRefDeallocate(printer->common_ref); + + TXTRecordDeallocate(&(printer->ipp_txt)); + + if (printer->dnssd_name) + _cupsStrFree(printer->dnssd_name); +#endif /* HAVE_DNSSD */ + + if (printer->name) + _cupsStrFree(printer->name); + if (printer->icon) + _cupsStrFree(printer->icon); + if (printer->directory) + _cupsStrFree(printer->directory); + if (printer->hostname) + _cupsStrFree(printer->hostname); + if (printer->uri) + _cupsStrFree(printer->uri); + + ippDelete(printer->attrs); + cupsArrayDelete(printer->jobs); + + free(printer); +} + + +#ifdef HAVE_DNSSD +/* + * 'dnssd_callback()' - Handle Bonjour registration events. + */ + +static void +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 */ + _ipp_printer_t *printer) /* I - Printer */ +{ + if (errorCode) + { + fprintf(stderr, "DNSServiceRegister for %s failed with error %d.\n", + regtype, (int)errorCode); + return; + } + else if (_cups_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 */ + _cupsStrFree(printer->dnssd_name); + printer->dnssd_name = _cupsStrAlloc(name); + } +} +#endif /* HAVE_DNSSD */ + + +/* + * 'find_job()' - Find a job specified in a request. + */ + +static _ipp_job_t * /* O - Job or NULL */ +find_job(_ipp_client_t *client) /* I - Client */ +{ + ipp_attribute_t *attr; /* job-id or job-uri attribute */ + _ipp_job_t key, /* Job search key */ + *job; /* Matching job, if any */ + + + key.id = 0; + + if ((attr = ippFindAttribute(client->request, "job-uri", + IPP_TAG_URI)) != NULL) + { + if (!strncmp(attr->values[0].string.text, client->printer->uri, + client->printer->urilen) && + attr->values[0].string.text[client->printer->urilen] == '/') + key.id = atoi(attr->values[0].string.text + client->printer->urilen + 1); + } + else if ((attr = ippFindAttribute(client->request, "job-id", + IPP_TAG_INTEGER)) != NULL) + key.id = attr->values[0].integer; + + _cupsRWLockRead(&(client->printer->rwlock)); + job = (_ipp_job_t *)cupsArrayFind(client->printer->jobs, &key); + _cupsRWUnlock(&(client->printer->rwlock)); + + return (job); +} + + +/* + * 'html_escape()' - Write a HTML-safe string. + */ + +static void +html_escape(_ipp_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, s - start); + + if (*s == '&') + httpWrite2(&(client->http), "&", 5); + else + httpWrite2(&(client->http), "<", 4); + + start = s + 1; + } + + s ++; + } + + if (s > start) + httpWrite2(&(client->http), start, s - start); +} + + +/* + * 'html_printf()' - Send formatted text to the client, quoting as needed. + */ + +static void +html_printf(_ipp_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, format - start); + + tptr = tformat; + *tptr++ = *format++; + + if (*format == '%') + { + httpWrite2(&(client->http), "%", 1); + format ++; + continue; + } + else if (strchr(" -+#\'", *format)) + *tptr++ = *format++; + + if (*format == '*') + { + /* + * Get width from argument... + */ + + format ++; + width = va_arg(ap, int); + + snprintf(tptr, sizeof(tformat) - (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) - (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 ((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 ((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 ((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] = 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, format - start); + + va_end(ap); +} + + +/* + * 'ipp_cancel_job()' - Cancel a job. + */ + +static void +ipp_cancel_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job information */ + + + /* + * Get the job... + */ + + if ((job = find_job(client)) == NULL) + return; + + /* + * See if the job is already completed, canceled, or aborted; if so, + * we can't cancel... + */ + + switch (job->state) + { + case IPP_JOB_CANCELED : + respond_ipp(client, IPP_NOT_POSSIBLE, + "Job #%d is already canceled - can\'t cancel.", job->id); + break; + + case IPP_JOB_ABORTED : + respond_ipp(client, IPP_NOT_POSSIBLE, + "Job #%d is already aborted - can\'t cancel.", job->id); + break; + + case IPP_JOB_COMPLETED : + respond_ipp(client, IPP_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_JOB_PROCESSING || + (job->state == IPP_JOB_HELD && job->fd >= 0)) + job->cancel = 1; + else + { + job->state = IPP_JOB_CANCELED; + job->completed = time(NULL); + } + + _cupsRWUnlock(&(client->printer->rwlock)); + + respond_ipp(client, IPP_OK, NULL); + break; + } +} + + +#if 0 +/* + * 'ipp_create_job()' - Create a job object. + */ + +static void +ipp_create_job(_ipp_client_t *client) /* I - Client */ +{ +} +#endif /* 0 */ + + +/* + * 'ipp_get_job_attributes()' - Get the attributes for a job object. + */ + +static void +ipp_get_job_attributes( + _ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* Job */ + cups_array_t *ra; /* requested-attributes */ + + + if ((job = find_job(client)) == NULL) + { + respond_ipp(client, IPP_NOT_FOUND, "Job not found."); + return; + } + + respond_ipp(client, IPP_OK, NULL); + + ra = create_requested_array(client); + copy_job_attributes(client, job, ra); + cupsArrayDelete(ra); +} + + +/* + * 'ipp_get_jobs()' - Get a list of job objects. + */ + +static void +ipp_get_jobs(_ipp_client_t *client) /* I - Client */ +{ + ipp_attribute_t *attr; /* Current attribute */ + 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 */ + _ipp_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) + fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->http.hostname, + attr->values[0].string.text); + + if (!attr || !strcmp(attr->values[0].string.text, "not-completed")) + { + job_comparison = -1; + job_state = IPP_JOB_STOPPED; + } + else if (!strcmp(attr->values[0].string.text, "completed")) + { + job_comparison = 1; + job_state = IPP_JOB_CANCELED; + } + else if (!strcmp(attr->values[0].string.text, "aborted")) + { + job_comparison = 0; + job_state = IPP_JOB_ABORTED; + } + else if (!strcmp(attr->values[0].string.text, "all")) + { + job_comparison = 1; + job_state = IPP_JOB_PENDING; + } + else if (!strcmp(attr->values[0].string.text, "canceled")) + { + job_comparison = 0; + job_state = IPP_JOB_CANCELED; + } + else if (!strcmp(attr->values[0].string.text, "pending")) + { + job_comparison = 0; + job_state = IPP_JOB_PENDING; + } + else if (!strcmp(attr->values[0].string.text, "pending-held")) + { + job_comparison = 0; + job_state = IPP_JOB_HELD; + } + else if (!strcmp(attr->values[0].string.text, "processing")) + { + job_comparison = 0; + job_state = IPP_JOB_PROCESSING; + } + else if (!strcmp(attr->values[0].string.text, "processing-stopped")) + { + job_comparison = 0; + job_state = IPP_JOB_STOPPED; + } + else + { + respond_ipp(client, IPP_ATTRIBUTES, + "The which-jobs value \"%s\" is not supported.", + attr->values[0].string.text); + ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD, + "which-jobs", NULL, attr->values[0].string.text); + return; + } + + /* + * See if they want to limit the number of jobs reported... + */ + + if ((attr = ippFindAttribute(client->request, "limit", + IPP_TAG_INTEGER)) != NULL) + { + limit = attr->values[0].integer; + + fprintf(stderr, "%s Get-Jobs limit=%d", client->http.hostname, limit); + } + else + limit = 0; + + if ((attr = ippFindAttribute(client->request, "first-job-id", + IPP_TAG_INTEGER)) != NULL) + { + first_job_id = attr->values[0].integer; + + fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->http.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) + { + fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->http.hostname, + attr->values[0].boolean ? "true" : "false"); + + if (attr->values[0].boolean) + { + if ((attr = ippFindAttribute(client->request, "requesting-user-name", + IPP_TAG_NAME)) == NULL) + { + respond_ipp(client, IPP_BAD_REQUEST, + "Need requesting-user-name with my-jobs."); + return; + } + + username = attr->values[0].string.text; + + fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n", + client->http.hostname, username); + } + } + + /* + * OK, build a list of jobs for this printer... + */ + + if ((ra = create_requested_array(client)) == NULL && + !ippFindAttribute(client->request, "requested-attributes", + IPP_TAG_KEYWORD)) + { + /* + * IPP conformance - Get-Jobs has a default requested-attributes value of + * "job-id" and "job-uri". + */ + + ra = cupsArrayNew((cups_array_func_t)strcmp, NULL); + cupsArrayAdd(ra, "job-id"); + cupsArrayAdd(ra, "job-uri"); + } + + respond_ipp(client, IPP_OK, NULL); + + _cupsRWLockRead(&(client->printer->rwlock)); + + for (count = 0, job = (_ipp_job_t *)cupsArrayFirst(client->printer->jobs); + (limit <= 0 || count < limit) && job; + job = (_ipp_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 && _cups_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( + _ipp_client_t *client) /* I - Client */ +{ + cups_array_t *ra; /* Requested attributes array */ + _ipp_printer_t *printer; /* Printer */ + + + /* + * Send the attributes... + */ + + ra = create_requested_array(client); + printer = client->printer; + + respond_ipp(client, IPP_OK, NULL); + + _cupsRWLockRead(&(printer->rwlock)); + + copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO, + IPP_TAG_COPY); + + if (!ra || cupsArrayFind(ra, "printer-state")) + ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, + "printer-state", printer->state); + + if (!ra || cupsArrayFind(ra, "printer-state-reasons")) + { + if (printer->state_reasons == _IPP_PRINTER_NONE) + ippAddString(client->response, IPP_TAG_PRINTER, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "printer-state-reasons", + NULL, "none"); + else + { + int num_reasons = 0;/* Number of reasons */ + const char *reasons[32]; /* Reason strings */ + + if (printer->state_reasons & _IPP_PRINTER_OTHER) + reasons[num_reasons ++] = "other"; + if (printer->state_reasons & _IPP_PRINTER_COVER_OPEN) + reasons[num_reasons ++] = "cover-open"; + if (printer->state_reasons & _IPP_PRINTER_INPUT_TRAY_MISSING) + reasons[num_reasons ++] = "input-tray-missing"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_EMPTY) + reasons[num_reasons ++] = "marker-supply-empty-warning"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_LOW) + reasons[num_reasons ++] = "marker-supply-low-report"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL) + reasons[num_reasons ++] = "marker-waste-almost-full-report"; + if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_FULL) + reasons[num_reasons ++] = "marker-waste-full-warning"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_EMPTY) + reasons[num_reasons ++] = "media-empty-warning"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_JAM) + reasons[num_reasons ++] = "media-jam-warning"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_LOW) + reasons[num_reasons ++] = "media-low-report"; + if (printer->state_reasons & _IPP_PRINTER_MEDIA_NEEDED) + reasons[num_reasons ++] = "media-needed-report"; + if (printer->state_reasons & _IPP_PRINTER_MOVING_TO_PAUSED) + reasons[num_reasons ++] = "moving-to-paused"; + if (printer->state_reasons & _IPP_PRINTER_PAUSED) + reasons[num_reasons ++] = "paused"; + if (printer->state_reasons & _IPP_PRINTER_SPOOL_AREA_FULL) + reasons[num_reasons ++] = "spool-area-full"; + if (printer->state_reasons & _IPP_PRINTER_TONER_EMPTY) + reasons[num_reasons ++] = "toner-empty-warning"; + if (printer->state_reasons & _IPP_PRINTER_TONER_LOW) + reasons[num_reasons ++] = "toner-low-report"; + + ippAddStrings(client->response, IPP_TAG_PRINTER, + IPP_TAG_KEYWORD | IPP_TAG_COPY, "printer-state-reasons", + num_reasons, NULL, reasons); + } + } + + if (!ra || cupsArrayFind(ra, "printer-up-time")) + ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "printer-up-time", (int)time(NULL)); + + 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_JOB_CANCELED); + + _cupsRWUnlock(&(printer->rwlock)); + + cupsArrayDelete(ra); +} + + +/* + * 'ipp_print_job()' - Create a job object with an attached document. + */ + +static void +ipp_print_job(_ipp_client_t *client) /* I - Client */ +{ + _ipp_job_t *job; /* New job */ + char filename[1024], /* Filename buffer */ + buffer[4096]; /* Copy buffer */ + ssize_t bytes; /* Bytes read */ + 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 (client->http.state == HTTP_POST_SEND) + { + respond_ipp(client, IPP_BAD_REQUEST, "No file in request."); + return; + } + + /* + * Print the job... + */ + + if ((job = create_job(client)) == NULL) + { + respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job."); + return; + } + + /* + * Create a file for the request data... + */ + + if (!_cups_strcasecmp(job->format, "image/jpeg")) + snprintf(filename, sizeof(filename), "%s/%d.jpg", + client->printer->directory, job->id); + else if (!_cups_strcasecmp(job->format, "image/png")) + snprintf(filename, sizeof(filename), "%s/%d.png", + client->printer->directory, job->id); + else if (!_cups_strcasecmp(job->format, "application/pdf")) + snprintf(filename, sizeof(filename), "%s/%d.pdf", + client->printer->directory, job->id); + else if (!_cups_strcasecmp(job->format, "application/postscript")) + snprintf(filename, sizeof(filename), "%s/%d.ps", + client->printer->directory, job->id); + else + snprintf(filename, sizeof(filename), "%s/%d.prn", + client->printer->directory, job->id); + + if ((job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) + { + job->state = IPP_JOB_ABORTED; + + respond_ipp(client, IPP_INTERNAL_ERROR, + "Unable to create print file: %s", strerror(errno)); + return; + } + + while ((bytes = httpRead2(&(client->http), buffer, sizeof(buffer))) > 0) + { + if (write(job->fd, buffer, bytes) < bytes) + { + int error = errno; /* Write error */ + + job->state = IPP_JOB_ABORTED; + + close(job->fd); + job->fd = -1; + + unlink(filename); + + respond_ipp(client, IPP_INTERNAL_ERROR, + "Unable to write print file: %s", strerror(error)); + return; + } + } + + if (bytes < 0) + { + /* + * Got an error while reading the print data, so abort this job. + */ + + job->state = IPP_JOB_ABORTED; + + close(job->fd); + job->fd = -1; + + unlink(filename); + + respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to read print file."); + return; + } + + if (close(job->fd)) + { + int error = errno; /* Write error */ + + job->state = IPP_JOB_ABORTED; + job->fd = -1; + + unlink(filename); + + respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s", + strerror(error)); + return; + } + + job->fd = -1; + job->filename = strdup(filename); + job->state = IPP_JOB_PENDING; + + /* + * Process the job... + */ + + if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job)) + { + job->state = IPP_JOB_ABORTED; + respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job."); + return; + } + + /* + * Return the job info... + */ + + respond_ipp(client, IPP_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); +} + + +#if 0 +/* + * 'ipp_send_document()' - Add an attached document to a job object created with + * Create-Job. + */ + +static void +ipp_send_document(_ipp_client_t *client)/* I - Client */ +{ +} +#endif /* 0 */ + + +/* + * 'ipp_validate_job()' - Validate job creation attributes. + */ + +static void +ipp_validate_job(_ipp_client_t *client) /* I - Client */ +{ +} + + +/* + * 'process_client()' - Process client requests on a thread. + */ + +static void * /* O - Exit status */ +process_client(_ipp_client_t *client) /* I - Client */ +{ + /* + * Loop until we are out of requests or timeout (30 seconds)... + */ + + while (httpWait(&(client->http), 30000)) + 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(_ipp_client_t *client) /* I - Client connection */ +{ + char line[4096], /* Line from client... */ + operation[64], /* Operation code from socket */ + uri[1024], /* URI */ + version[64], /* HTTP version number string */ + *ptr; /* Pointer into strings */ + int major, minor; /* HTTP version numbers */ + http_status_t status; /* Transfer status */ + ipp_state_t state; /* State of IPP transfer */ + + + /* + * Abort if we have an error on the connection... + */ + + if (client->http.error) + return (0); + + /* + * Clear state variables... + */ + + httpClearFields(&(client->http)); + ippDelete(client->request); + ippDelete(client->response); + + client->http.activity = time(NULL); + client->http.version = HTTP_1_1; + client->http.keep_alive = HTTP_KEEPALIVE_OFF; + client->http.data_encoding = HTTP_ENCODE_LENGTH; + client->http.data_remaining = 0; + client->request = NULL; + client->response = NULL; + client->operation = HTTP_WAITING; + + /* + * Read a request from the connection... + */ + + while ((ptr = httpGets(line, sizeof(line) - 1, &(client->http))) != NULL) + if (*ptr) + break; + + if (!ptr) + return (0); + + /* + * Parse the request line... + */ + + fprintf(stderr, "%s %s\n", client->http.hostname, line); + + switch (sscanf(line, "%63s%1023s%63s", operation, uri, version)) + { + case 1 : + fprintf(stderr, "%s Bad request line.\n", client->http.hostname); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + + case 2 : + client->http.version = HTTP_0_9; + break; + + case 3 : + if (sscanf(version, "HTTP/%d.%d", &major, &minor) != 2) + { + fprintf(stderr, "%s Bad HTTP version.\n", client->http.hostname); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + if (major < 2) + { + client->http.version = (http_version_t)(major * 100 + minor); + if (client->http.version == HTTP_1_1) + client->http.keep_alive = HTTP_KEEPALIVE_ON; + else + client->http.keep_alive = HTTP_KEEPALIVE_OFF; + } + else + { + respond_http(client, HTTP_NOT_SUPPORTED, NULL, 0); + return (0); + } + break; + } + + /* + * Handle full URLs in the request line... + */ + + if (!strncmp(client->uri, "http:", 5) || !strncmp(client->uri, "ipp:", 4)) + { + char scheme[32], /* Method/scheme */ + userpass[128], /* Username:password */ + hostname[HTTP_MAX_HOST];/* Hostname */ + int port; /* Port number */ + + /* + * 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_OK) + { + fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + } + else + { + /* + * Decode URI + */ + + if (!_httpDecodeURI(client->uri, uri, sizeof(client->uri))) + { + fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + } + + /* + * Process the request... + */ + + if (!strcmp(operation, "GET")) + client->http.state = HTTP_GET; + else if (!strcmp(operation, "POST")) + client->http.state = HTTP_POST; + else if (!strcmp(operation, "OPTIONS")) + client->http.state = HTTP_OPTIONS; + else if (!strcmp(operation, "HEAD")) + client->http.state = HTTP_HEAD; + else + { + fprintf(stderr, "%s Bad operation \"%s\".\n", client->http.hostname, + operation); + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + client->start = time(NULL); + client->operation = client->http.state; + client->http.status = HTTP_OK; + + /* + * Parse incoming parameters until the status changes... + */ + + while ((status = httpUpdate(&(client->http))) == HTTP_CONTINUE); + + if (status != HTTP_OK) + { + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + if (!client->http.fields[HTTP_FIELD_HOST][0] && + client->http.version >= HTTP_1_1) + { + /* + * HTTP/1.1 and higher require the "Host:" field... + */ + + respond_http(client, HTTP_BAD_REQUEST, NULL, 0); + return (0); + } + + /* + * Handle HTTP Upgrade... + */ + + if (!_cups_strcasecmp(client->http.fields[HTTP_FIELD_CONNECTION], "Upgrade")) + { + if (!respond_http(client, HTTP_NOT_IMPLEMENTED, NULL, 0)) + return (0); + } + + /* + * Handle HTTP Expect... + */ + + if (client->http.expect && + (client->operation == HTTP_POST || client->operation == HTTP_PUT)) + { + if (client->http.expect == HTTP_CONTINUE) + { + /* + * Send 100-continue header... + */ + + if (!respond_http(client, HTTP_CONTINUE, NULL, 0)) + return (0); + } + else + { + /* + * Send 417-expectation-failed header... + */ + + if (!respond_http(client, HTTP_EXPECTATION_FAILED, NULL, 0)) + return (0); + + httpPrintf(&(client->http), "Content-Length: 0\r\n"); + httpPrintf(&(client->http), "\r\n"); + httpFlushWrite(&(client->http)); + client->http.data_encoding = HTTP_ENCODE_LENGTH; + } + } + + /* + * Handle new transfers... + */ + + switch (client->operation) + { + case HTTP_OPTIONS : + /* + * Do HEAD/OPTIONS command... + */ + + return (respond_http(client, HTTP_OK, NULL, 0)); + + case HTTP_HEAD : + if (!strcmp(client->uri, "/icon.png")) + return (respond_http(client, HTTP_OK, "image/png", 0)); + else if (!strcmp(client->uri, "/")) + return (respond_http(client, HTTP_OK, "text/html", 0)); + else + return (respond_http(client, HTTP_NOT_FOUND, NULL, 0)); + break; + + case HTTP_GET : + if (!strcmp(client->uri, "/icon.png")) + { + /* + * Send PNG icon file. + */ + + int fd; /* Icon file */ + struct stat fileinfo; /* Icon file information */ + char buffer[4096]; /* Copy buffer */ + ssize_t bytes; /* Bytes */ + + if (!stat(client->printer->icon, &fileinfo) && + (fd = open(client->printer->icon, O_RDONLY)) >= 0) + { + if (!respond_http(client, HTTP_OK, "image/png", fileinfo.st_size)) + { + close(fd); + return (0); + } + + while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) + httpWrite2(&(client->http), buffer, bytes); + + httpFlushWrite(&(client->http)); + + close(fd); + } + else + return (respond_http(client, HTTP_NOT_FOUND, NULL, 0)); + } + else if (!strcmp(client->uri, "/")) + { + /* + * Show web status page... + */ + + if (!respond_http(client, HTTP_OK, "text/html", 0)) + return (0); + + html_printf(client, + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " + "\"http://www.w3.org/TR/html4/strict.dtd\">\n" + "<html>\n" + "<head>\n" + "<title>%s</title>\n" + "<link rel=\"SHORTCUT ICON\" href=\"/icon.png\" " + "type=\"image/png\">\n" + "</head>\n" + "<body>\n" + "</body>\n" + "<h1>%s</h1>\n" + "<p>%s, %d job(s).</p>\n" + "</body>\n" + "</html>\n", + client->printer->name, client->printer->name, + client->printer->state == IPP_PRINTER_IDLE ? "Idle" : + client->printer->state == IPP_PRINTER_PROCESSING ? + "Printing" : "Stopped", + cupsArrayCount(client->printer->jobs)); + httpWrite2(&(client->http), "", 0); + + return (1); + } + else + return (respond_http(client, HTTP_NOT_FOUND, NULL, 0)); + break; + + case HTTP_POST : + if (client->http.data_remaining < 0 || + (!client->http.fields[HTTP_FIELD_CONTENT_LENGTH][0] && + client->http.data_encoding == HTTP_ENCODE_LENGTH)) + { + /* + * Negative content lengths are invalid... + */ + + return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0)); + } + + if (strcmp(client->http.fields[HTTP_FIELD_CONTENT_TYPE], + "application/ipp")) + { + /* + * Not an IPP request... + */ + + return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0)); + } + + /* + * Read the IPP request... + */ + + client->request = ippNew(); + + while ((state = ippRead(&(client->http), client->request)) != IPP_DATA) + if (state == IPP_ERROR) + { + fprintf(stderr, "%s IPP read error (%s).\n", client->http.hostname, + ippOpString(client->request->request.op.operation_id)); + respond_http(client, HTTP_BAD_REQUEST, 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(_ipp_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 */ + + + debug_attributes("Request", client->request); + + /* + * First build an empty response message for this request... + */ + + client->operation_id = client->request->request.op.operation_id; + client->response = ippNew(); + + client->response->request.status.version[0] = + client->request->request.op.version[0]; + client->response->request.status.version[1] = + client->request->request.op.version[1]; + client->response->request.status.request_id = + client->request->request.op.request_id; + + /* + * Then validate the request header and required attributes... + */ + + if (client->request->request.any.version[0] < 1 || + client->request->request.any.version[0] > 2) + { + /* + * Return an error, since we only support IPP 1.x and 2.x. + */ + + respond_ipp(client, IPP_VERSION_NOT_SUPPORTED, + "Bad request version number %d.%d.", + client->request->request.any.version[0], + client->request->request.any.version[1]); + } + else if (client->request->request.any.request_id <= 0) + respond_ipp(client, IPP_BAD_REQUEST, "Bad request-id %d.", + client->request->request.any.request_id); + else if (!client->request->attrs) + respond_ipp(client, IPP_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 = client->request->attrs, group = attr->group_tag; + attr; + attr = attr->next) + if (attr->group_tag < group && attr->group_tag != IPP_TAG_ZERO) + { + /* + * Out of order; return an error... + */ + + respond_ipp(client, IPP_BAD_REQUEST, + "Attribute groups are out of order (%x < %x).", + attr->group_tag, group); + break; + } + else + group = attr->group_tag; + + if (!attr) + { + /* + * Then make sure that the first three attributes are: + * + * attributes-charset + * attributes-natural-language + * printer-uri/job-uri + */ + + attr = client->request->attrs; + if (attr && attr->name && + !strcmp(attr->name, "attributes-charset") && + (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_CHARSET) + charset = attr; + else + charset = NULL; + + if (attr) + attr = attr->next; + + if (attr && attr->name && + !strcmp(attr->name, "attributes-natural-language") && + (attr->value_tag & IPP_TAG_MASK) == 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; + + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, + charset ? charset->values[0].string.text : "utf-8"); + + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, + language ? language->values[0].string.text : "en"); + + if (charset && + _cups_strcasecmp(charset->values[0].string.text, "us-ascii") && + _cups_strcasecmp(charset->values[0].string.text, "utf-8")) + { + /* + * Bad character set... + */ + + respond_ipp(client, IPP_BAD_REQUEST, + "Unsupported character set \"%s\".", + charset->values[0].string.text); + } + 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_BAD_REQUEST, "Missing required attributes."); + } + else if (strcmp(uri->values[0].string.text, client->printer->uri) && + strncmp(uri->values[0].string.text, client->printer->uri, + client->printer->urilen)) + { + respond_ipp(client, IPP_NOT_FOUND, "%s %s not found.", uri->name, + uri->values[0].string.text); + } + else + { + /* + * Try processing the operation... + */ + + if (client->http.expect == HTTP_CONTINUE) + { + /* + * Send 100-continue header... + */ + + if (!respond_http(client, HTTP_CONTINUE, NULL, 0)) + return (0); + } + + switch (client->request->request.op.operation_id) + { + case IPP_PRINT_JOB : + ipp_print_job(client); + break; + + case IPP_VALIDATE_JOB : + ipp_validate_job(client); + break; + + case IPP_CANCEL_JOB : + ipp_cancel_job(client); + break; + + case IPP_GET_JOB_ATTRIBUTES : + ipp_get_job_attributes(client); + break; + + case IPP_GET_JOBS : + ipp_get_jobs(client); + break; + + case IPP_GET_PRINTER_ATTRIBUTES : + ipp_get_printer_attributes(client); + break; + + default : + respond_ipp(client, IPP_OPERATION_NOT_SUPPORTED, + "Operation not supported."); + break; + } + } + } + } + + /* + * Send the HTTP header and return... + */ + + if (client->http.state != HTTP_POST_SEND) + httpFlush(&(client->http)); /* Flush trailing (junk) data */ + + return (respond_http(client, HTTP_OK, "application/ipp", + ippLength(client->response))); +} + + +/* + * 'process_job()' - Process a print job. + */ + +static void * /* O - Thread exit status */ +process_job(_ipp_job_t *job) /* I - Job */ +{ + job->state = IPP_JOB_PROCESSING; + job->printer->state = IPP_PRINTER_PROCESSING; + + sleep(5); + + if (job->cancel) + job->state = IPP_JOB_CANCELED; + else + job->state = IPP_JOB_COMPLETED; + + job->completed = time(NULL); + job->printer->state = IPP_PRINTER_IDLE; + job->printer->active_job = NULL; + + return (NULL); +} + + +#ifdef HAVE_DNSSD +/* + * 'register_printer()' - Register a printer object via Bonjour. + */ + +static int /* O - 1 on success, 0 on error */ +register_printer( + _ipp_printer_t *printer, /* I - Printer */ + const char *location, /* I - Location */ + const char *make, /* I - Manufacturer */ + const char *model, /* I - Model name */ + const char *formats, /* I - Supported formats */ + const char *adminurl, /* I - Web interface URL */ + int color, /* I - 1 = color, 0 = monochrome */ + int duplex, /* I - 1 = duplex, 0 = simplex */ + const char *regtype) /* I - Service type */ +{ + DNSServiceErrorType error; /* Error from Bonjour */ + char make_model[256],/* Make and model together */ + product[256]; /* Product string */ + + + /* + * Build the TXT record for IPP... + */ + + snprintf(make_model, sizeof(make_model), "%s %s", make, model); + snprintf(product, sizeof(product), "(%s)", model); + + TXTRecordCreate(&(printer->ipp_txt), 1024, NULL); + TXTRecordSetValue(&(printer->ipp_txt), "txtvers", 1, "1"); + TXTRecordSetValue(&(printer->ipp_txt), "qtotal", 1, "1"); + TXTRecordSetValue(&(printer->ipp_txt), "rp", 3, "ipp"); + TXTRecordSetValue(&(printer->ipp_txt), "ty", (uint8_t)strlen(make_model), + make_model); + TXTRecordSetValue(&(printer->ipp_txt), "adminurl", (uint8_t)strlen(adminurl), + adminurl); + TXTRecordSetValue(&(printer->ipp_txt), "note", (uint8_t)strlen(location), + location); + TXTRecordSetValue(&(printer->ipp_txt), "priority", 1, "0"); + TXTRecordSetValue(&(printer->ipp_txt), "product", (uint8_t)strlen(product), + product); + TXTRecordSetValue(&(printer->ipp_txt), "pdl", (uint8_t)strlen(formats), + formats); + TXTRecordSetValue(&(printer->ipp_txt), "Color", 1, color ? "T" : "F"); + TXTRecordSetValue(&(printer->ipp_txt), "Duplex", 1, duplex ? "T" : "F"); + TXTRecordSetValue(&(printer->ipp_txt), "usb_MFG", (uint8_t)strlen(make), + make); + TXTRecordSetValue(&(printer->ipp_txt), "usb_MDL", (uint8_t)strlen(model), + model); + TXTRecordSetValue(&(printer->ipp_txt), "air", 4, "none"); + + /* + * Create a shared service reference for Bonjour... + */ + + if ((error = DNSServiceCreateConnection(&(printer->common_ref))) + != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to create mDNSResponder connection: %d\n", error); + return (0); + } + + /* + * 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 = printer->common_ref; + + 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) + { + fprintf(stderr, "Unable to register \"%s._printer._tcp\": %d\n", + printer->dnssd_name, error); + return (0); + } + + /* + * Then register the _ipp._tcp (IPP) service type with the real port number to + * advertise our IPP printer... + */ + + printer->ipp_ref = printer->common_ref; + + if ((error = DNSServiceRegister(&(printer->ipp_ref), + kDNSServiceFlagsShareConnection, + 0 /* interfaceIndex */, printer->dnssd_name, + regtype, NULL /* domain */, + NULL /* host */, htons(printer->port), + TXTRecordGetLength(&(printer->ipp_txt)), + TXTRecordGetBytesPtr(&(printer->ipp_txt)), + (DNSServiceRegisterReply)dnssd_callback, + printer)) != kDNSServiceErr_NoError) + { + fprintf(stderr, "Unable to register \"%s.%s\": %d\n", + printer->dnssd_name, regtype, error); + return (0); + } + + /* + * Similarly, register the _http._tcp,_printer (HTTP) service type with the + * real port number to advertise our IPP printer... + */ + + printer->http_ref = printer->common_ref; + + 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) + { + fprintf(stderr, "Unable to register \"%s.%s\": %d\n", + printer->dnssd_name, regtype, error); + return (0); + } + + return (1); +} +#endif /* HAVE_DNSSD */ + + +/* + * 'respond_http()' - Send a HTTP response. + */ + +int /* O - 1 on success, 0 on failure */ +respond_http(_ipp_client_t *client, /* I - Client */ + http_status_t code, /* I - HTTP status of response */ + const char *type, /* I - MIME type of response */ + size_t length) /* I - Length of response */ +{ + char message[1024]; /* Text message */ + + + fprintf(stderr, "%s %s\n", client->http.hostname, httpStatus(code)); + + if (code == HTTP_CONTINUE) + { + /* + * 100-continue doesn't send any headers... + */ + + return (httpPrintf(&(client->http), "HTTP/%d.%d 100 Continue\r\n\r\n", + client->http.version / 100, + client->http.version % 100) > 0); + } + + /* + * Format an error message... + */ + + if (!type && !length && code != HTTP_OK) + { + snprintf(message, sizeof(message), "%d - %s\n", code, httpStatus(code)); + + type = "text/plain"; + length = strlen(message); + } + else + message[0] = '\0'; + + /* + * Send the HTTP status header... + */ + + httpFlushWrite(&(client->http)); + + client->http.data_encoding = HTTP_ENCODE_FIELDS; + + if (httpPrintf(&(client->http), "HTTP/%d.%d %d %s\r\n", client->http.version / 100, + client->http.version % 100, code, httpStatus(code)) < 0) + return (0); + + /* + * Follow the header with the response fields... + */ + + if (httpPrintf(&(client->http), "Date: %s\r\n", httpGetDateString(time(NULL))) < 0) + return (0); + + if (client->http.keep_alive && client->http.version >= HTTP_1_0) + { + if (httpPrintf(&(client->http), + "Connection: Keep-Alive\r\n" + "Keep-Alive: timeout=10\r\n") < 0) + return (0); + } + + if (code == HTTP_METHOD_NOT_ALLOWED || client->operation == HTTP_OPTIONS) + { + if (httpPrintf(&(client->http), "Allow: GET, HEAD, OPTIONS, POST\r\n") < 0) + return (0); + } + + if (type) + { + if (!strcmp(type, "text/html")) + { + if (httpPrintf(&(client->http), + "Content-Type: text/html; charset=utf-8\r\n") < 0) + return (0); + } + else if (httpPrintf(&(client->http), "Content-Type: %s\r\n", type) < 0) + return (0); + } + + if (length == 0 && !message[0]) + { + if (httpPrintf(&(client->http), "Transfer-Encoding: chunked\r\n\r\n") < 0) + return (0); + } + else if (httpPrintf(&(client->http), "Content-Length: " CUPS_LLFMT "\r\n\r\n", + CUPS_LLCAST length) < 0) + return (0); + + if (httpFlushWrite(&(client->http)) < 0) + return (0); + + /* + * Send the response data... + */ + + if (message[0]) + { + /* + * Send a plain text message. + */ + + if (httpPrintf(&(client->http), "%s", message) < 0) + return (0); + } + else if (client->response) + { + /* + * Send an IPP response... + */ + + debug_attributes("Response", client->response); + + client->http.data_encoding = HTTP_ENCODE_LENGTH; + client->http.data_remaining = (off_t)ippLength(client->response); + client->response->state = IPP_IDLE; + + if (ippWrite(&(client->http), client->response) != IPP_DATA) + return (0); + } + else + client->http.data_encoding = HTTP_ENCODE_CHUNKED; + + /* + * Flush the data and return... + */ + + return (httpFlushWrite(&(client->http)) >= 0); +} + + +/* + * 'respond_ipp()' - Send an IPP response. + */ + +static void +respond_ipp(_ipp_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 */ +{ + va_list ap; /* Pointer to additional args */ + char formatted[1024]; /* Formatted errror message */ + + + client->response->request.status.status_code = status; + + if (!client->response->attrs) + { + ippAddString(client->response, IPP_TAG_OPERATION, + IPP_TAG_CHARSET | IPP_TAG_COPY, "attributes-charset", NULL, + "utf-8"); + ippAddString(client->response, IPP_TAG_OPERATION, + IPP_TAG_LANGUAGE | IPP_TAG_COPY, "attributes-natural-language", + NULL, "en-us"); + } + + if (message) + { + va_start(ap, message); + vsnprintf(formatted, sizeof(formatted), message, ap); + va_end(ap); + + ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, + "status-message", NULL, formatted); + } + else + formatted[0] = '\0'; + + fprintf(stderr, "%s %s %s (%s)\n", client->http.hostname, + ippOpString(client->operation_id), ippErrorString(status), formatted); +} + + +/* + * 'run_printer()' - Run the printer service. + */ + +static void +run_printer(_ipp_printer_t *printer) /* I - Printer */ +{ + int num_fds; /* Number of file descriptors */ + struct pollfd polldata[3]; /* poll() data */ + int timeout; /* Timeout for poll() */ + _ipp_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(printer->common_ref); + 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, num_fds, timeout) < 0 && errno != EINTR) + { + perror("poll() failed"); + break; + } + + if (polldata[0].revents & POLLIN) + { + if ((client = create_client(printer, printer->ipv4)) != NULL) + { + if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client)) + { + perror("Unable to create client thread"); + delete_client(client); + } + } + } + + if (polldata[1].revents & POLLIN) + { + if ((client = create_client(printer, printer->ipv6)) != NULL) + { + if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client)) + { + perror("Unable to create client thread"); + delete_client(client); + } + } + } + +#ifdef HAVE_DNSSD + if (polldata[2].revents & POLLIN) + DNSServiceProcessResult(printer->common_ref); +#endif /* HAVE_DNSSD */ + + /* + * Clean out old jobs... + */ + + clean_jobs(printer); + } +} + + +/* + * 'usage()' - Show program usage. + */ + +static void +usage(int status) /* O - Exit status */ +{ + if (!status) + { + puts(CUPS_SVERSION " - Copyright 2010 by Apple Inc. All rights reserved."); + puts(""); + } + + puts("Usage: ippserver [options] \"name\""); + puts(""); + puts("Options:"); + puts("-2 Supports 2-sided printing (default=1-sided)"); + puts("-M manufacturer Manufacturer name (default=Test)"); + printf("-d spool-directory Spool directory " + "(default=/tmp/ippserver.%d)\n", (int)getpid()); + puts("-f type/subtype[,...] List of supported types " + "(default=application/pdf,image/jpeg)"); + puts("-h Show program help"); + puts("-i iconfile.png PNG icon file (default=printer.png)"); + puts("-l location Location of printer (default=empty string)"); + puts("-m model Model name (default=Printer)"); + puts("-n hostname Hostname for printer"); + puts("-p port Port number (default=auto)"); + puts("-r regtype Bonjour service type (default=_ipp._tcp)"); + puts("-s speed[,color-speed] Speed in pages per minute (default=10,0)"); + puts("-v[vvv] Be (very) verbose"); + + exit(status); +} + + +/* + * '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( + _ipp_client_t *client) /* I - Client */ +{ + int i; /* Looping var */ + ipp_attribute_t *attr, /* Current attribute */ + *supported; /* document-format-supported */ + const char *format = NULL; /* document-format value */ + int valid = 1; /* Valid attributes? */ + + + /* + * Check operation attributes... + */ + +#define respond_unsupported(client, attr) \ + if (valid) \ + { \ + respond_ipp(client, IPP_ATTRIBUTES, "Unsupported %s %s%s value.", \ + attr->name, attr->num_values > 1 ? "1setOf " : "", \ + ippTagString(attr->value_tag)); \ + valid = 0; \ + } \ + copy_attribute(client->response, attr, IPP_TAG_UNSUPPORTED_GROUP, 0) + + if ((attr = ippFindAttribute(client->request, "compression", + IPP_TAG_ZERO)) != NULL) + { + /* + * If compression is specified, only accept "none"... + */ + + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD || + strcmp(attr->values[0].string.text, "none")) + { + respond_unsupported(client, attr); + } + else + fprintf(stderr, "%s Print-Job compression=\"%s\"\n", + client->http.hostname, attr->values[0].string.text); + } + + /* + * Is it a format we support? + */ + + if ((attr = ippFindAttribute(client->request, "document-format", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_MIMETYPE) + { + respond_unsupported(client, attr); + } + else + { + format = attr->values[0].string.text; + + fprintf(stderr, "%s %s document-format=\"%s\"\n", + client->http.hostname, + ippOpString(client->request->request.op.operation_id), format); + } + } + else + format = "application/octet-stream"; + + if (!strcmp(format, "application/octet-stream") && + client->request->request.op.operation_id != IPP_VALIDATE_JOB) + { + /* + * Auto-type the file using the first 4 bytes of the file... + */ + + unsigned char header[4]; /* First 4 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"; + + if (format) + fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n", + client->http.hostname, + ippOpString(client->request->request.op.operation_id), format); + + if (!attr) + ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, + "document-format", NULL, format); + else + { + _cupsStrFree(attr->values[0].string.text); + attr->values[0].string.text = _cupsStrAlloc(format); + } + } + + if ((supported = ippFindAttribute(client->printer->attrs, + "document-format-supported", + IPP_TAG_MIMETYPE)) != NULL) + { + for (i = 0; i < supported->num_values; i ++) + if (!_cups_strcasecmp(format, supported->values[i].string.text)) + break; + + if (i >= supported->num_values) + { + respond_unsupported(client, attr); + } + } + + /* + * Check the various job template attributes... + */ + + if ((attr = ippFindAttribute(client->request, "copies", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER || + attr->values[0].integer < 1 || attr->values[0].integer > 999) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BOOLEAN) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-hold-until", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG && + attr->value_tag != IPP_TAG_KEYWORD) || + strcmp(attr->values[0].string.text, "no-hold")) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-name", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG)) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-priority", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER || + attr->values[0].integer < 1 || attr->values[0].integer > 100) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "job-sheets", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG && + attr->value_tag != IPP_TAG_KEYWORD) || + strcmp(attr->values[0].string.text, "none")) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "media", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || + (attr->value_tag != IPP_TAG_NAME && + attr->value_tag != IPP_TAG_NAMELANG && + attr->value_tag != IPP_TAG_KEYWORD)) + { + respond_unsupported(client, attr); + } + else + { + for (i = 0; + i < (int)(sizeof(media_supported) / sizeof(media_supported[0])); + i ++) + if (!strcmp(attr->values[0].string.text, media_supported[i])) + break; + + if (i >= (int)(sizeof(media_supported) / sizeof(media_supported[0]))) + { + respond_unsupported(client, attr); + } + } + } + + if ((attr = ippFindAttribute(client->request, "media-col", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BEGIN_COLLECTION) + { + respond_unsupported(client, attr); + } + /* TODO: check for valid media-col */ + } + + if ((attr = ippFindAttribute(client->request, "multiple-document-handling", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD || + (strcmp(attr->values[0].string.text, + "separate-documents-uncollated-copies") && + strcmp(attr->values[0].string.text, + "separate-documents-collated-copies"))) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "orientation-requested", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM || + attr->values[0].integer < IPP_PORTRAIT || + attr->values[0].integer > IPP_REVERSE_PORTRAIT) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "page-ranges", + IPP_TAG_ZERO)) != NULL) + { + respond_unsupported(client, attr); + } + + if ((attr = ippFindAttribute(client->request, "print-quality", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM || + attr->values[0].integer < IPP_QUALITY_DRAFT || + attr->values[0].integer > IPP_QUALITY_HIGH) + { + respond_unsupported(client, attr); + } + } + + if ((attr = ippFindAttribute(client->request, "printer-resolution", + IPP_TAG_ZERO)) != NULL) + { + respond_unsupported(client, attr); + } + + if ((attr = ippFindAttribute(client->request, "sides", + IPP_TAG_ZERO)) != NULL) + { + if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD) + { + respond_unsupported(client, attr); + } + + if ((supported = ippFindAttribute(client->printer->attrs, "sides", + IPP_TAG_KEYWORD)) != NULL) + { + for (i = 0; i < supported->num_values; i ++) + if (!strcmp(attr->values[0].string.text, + supported->values[i].string.text)) + break; + + if (i >= supported->num_values) + { + respond_unsupported(client, attr); + } + } + else + { + respond_unsupported(client, attr); + } + } + + return (valid); +} + + +/* + * End of "$Id: ippserver.c 9793 2011-05-20 03:49:49Z mike $". + */ diff --git a/test/ipptest.c b/test/ipptest.c deleted file mode 100644 index e95bc656..00000000 --- a/test/ipptest.c +++ /dev/null @@ -1,1278 +0,0 @@ -/* - * "$Id: ipptest.c 8929 2009-12-15 22:40:37Z mike $" - * - * IPP test command for the Common UNIX Printing System (CUPS). - * - * Copyright 2007-2009 by Apple Inc. - * Copyright 1997-2007 by Easy Software Products. - * - * These coded instructions, statements, and computer programs are the - * property of Apple Inc. and are protected by Federal copyright - * law. Distribution and use rights are outlined in the file "LICENSE.txt" - * which should have been included with this file. If this file is - * file is missing or damaged, see the license at "http://www.cups.org/". - * - * Contents: - * - * main() - Parse options and do tests. - * do_tests() - Do tests as specified in the test file. - * expect_matches() - Return true if the tag matches the specification. - * get_token() - Get a token from a file. - * print_attr() - Print an attribute on the screen. - * print_col() - Print a collection attribute on the screen. - * usage() - Show program usage. - */ - -/* - * Include necessary headers... - */ - -#include <stdio.h> -#include <stdlib.h> -#include <cups/string.h> -#include <errno.h> -#include <ctype.h> - -#include <cups/cups.h> -#include <cups/language.h> -#include <cups/http-private.h> -#ifndef O_BINARY -# define O_BINARY 0 -#endif /* !O_BINARY */ - - -/* - * Types... - */ - -typedef struct _cups_expect_s /**** Expected attribute info ****/ -{ - int not_expect; /* Don't expect attribute? */ - char *name, /* Attribute name */ - *of_type, /* Type name */ - *same_count_as, /* Parallel attribute name */ - *if_defined; /* Only required if variable defined */ -} _cups_expect_t; - - -/* - * Globals... - */ - -int Chunking = 0; /* Use chunked requests */ -int Verbosity = 0; /* Show all attributes? */ - - -/* - * Local functions... - */ - -static int do_tests(const char *uri, const char *testfile); -static int expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag); -static char *get_token(FILE *fp, char *buf, int buflen, - int *linenum); -static void print_attr(ipp_attribute_t *attr); -static void print_col(ipp_t *col); -static void usage(void); - - -/* - * '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 */ - const char *uri, /* URI to use */ - *testfile; /* Test file to use */ - int interval; /* Test interval */ - - - /* - * We need at least: - * - * testipp URL testfile - */ - - uri = NULL; - testfile = NULL; - status = 0; - interval = 0; - - for (i = 1; i < argc; i ++) - { - if (argv[i][0] == '-') - { - for (opt = argv[i] + 1; *opt; opt ++) - { - switch (*opt) - { - case 'c' : /* Enable HTTP chunking */ - Chunking = 1; - break; - - case 'd' : /* Define a variable */ - i ++; - - if (i >= argc) - { - fputs("ipptest: Missing name=value for \"-d\"!\n", stderr); - usage(); - } - else - putenv(argv[i]); - break; - - case 'i' : /* Test every N seconds */ - i++; - - if (i >= argc) - { - fputs("ipptest: Missing seconds for \"-i\"!\n", stderr); - usage(); - } - else - interval = atoi(argv[i]); - break; - - case 'v' : /* Be verbose */ - Verbosity ++; - break; - - default : - fprintf(stderr, "ipptest: Unknown option \"-%c\"!\n", *opt); - usage(); - break; - } - } - } - else if (!strncmp(argv[i], "ipp://", 6) || - !strncmp(argv[i], "http://", 7) || - !strncmp(argv[i], "https://", 8)) - { - /* - * Set URI... - */ - - if (!testfile && uri) - { - fputs("ipptest: May only specify a single URI before a test!\n", - stderr); - usage(); - } - - uri = argv[i]; - testfile = NULL; - } - else - { - /* - * Run test... - */ - - testfile = argv[i]; - - if (!do_tests(uri, testfile)) - status ++; - } - } - - if (!uri || !testfile) - usage(); - - /* - * Loop if the interval is set... - */ - - if (interval) - { - for (;;) - { - sleep(interval); - do_tests(uri, testfile); - } - } - - /* - * Exit... - */ - - return (status); -} - - -/* - * 'do_tests()' - Do tests as specified in the test file. - */ - -static int /* 1 = success, 0 = failure */ -do_tests(const char *uri, /* I - URI to connect on */ - const char *testfile) /* I - Test file to use */ -{ - int i; /* Looping var */ - int linenum; /* Current line number */ - int version; /* IPP version number to use */ - http_t *http; /* HTTP connection to server */ - char scheme[HTTP_MAX_URI], /* URI scheme */ - userpass[HTTP_MAX_URI], /* username:password */ - server[HTTP_MAX_URI], /* Server */ - resource[HTTP_MAX_URI]; /* Resource path */ - int port; /* Port number */ - FILE *fp; /* Test file */ - char token[1024], /* Token from file */ - *tokenptr, /* Pointer into token */ - temp[1024], /* Temporary string */ - *tempptr; /* Pointer into temp string */ - ipp_t *request; /* IPP request */ - ipp_t *response; /* IPP response */ - ipp_op_t op; /* Operation */ - ipp_tag_t group; /* Current group */ - ipp_tag_t value; /* Current value type */ - ipp_attribute_t *attrptr, /* Attribute pointer */ - *found; /* Found attribute */ - char attr[128]; /* Attribute name */ - int num_statuses; /* Number of valid status codes */ - ipp_status_t statuses[100]; /* Valid status codes */ - int num_expects; /* Number of expected attributes */ - _cups_expect_t expects[100], /* Expected attributes */ - *expect, /* Current expected attribute */ - *last_expect; /* Last EXPECT (for predicates) */ - int num_displayed; /* Number of displayed attributes */ - char *displayed[100]; /* Displayed attributes */ - char name[1024]; /* Name of test */ - char filename[1024]; /* Filename */ - int pass; /* Did we pass the test? */ - int job_id; /* Job ID from last operation */ - int subscription_id; /* Subscription ID from last operation */ - - - /* - * Open the test file... - */ - - if ((fp = fopen(testfile, "r")) == NULL) - { - printf("Unable to open test file %s - %s\n", testfile, strerror(errno)); - return (0); - } - - /* - * Connect to the server... - */ - - httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, - sizeof(userpass), server, sizeof(server), &port, resource, - sizeof(resource)); - if ((http = httpConnect(server, port)) == NULL) - { - printf("Unable to connect to %s on port %d - %s\n", server, port, - strerror(errno)); - fclose(fp); - return (0); - } - - /* - * Loop on tests... - */ - - printf("\"%s\":\n", testfile); - pass = 1; - job_id = 0; - subscription_id = 0; - version = 11; - linenum = 1; - - while (get_token(fp, token, sizeof(token), &linenum) != NULL) - { - /* - * Expect an open brace... - */ - - if (strcmp(token, "{")) - { - printf("Unexpected token %s seen on line %d - aborting test!\n", token, - linenum); - httpClose(http); - return (0); - } - - /* - * Initialize things... - */ - - httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, - sizeof(userpass), server, sizeof(server), &port, resource, - sizeof(resource)); - - request = ippNew(); - op = (ipp_op_t)0; - group = IPP_TAG_ZERO; - num_statuses = 0; - num_expects = 0; - num_displayed = 0; - last_expect = NULL; - filename[0] = '\0'; - - strcpy(name, testfile); - if (strrchr(name, '.') != NULL) - *strrchr(name, '.') = '\0'; - - /* - * Parse until we see a close brace... - */ - - while (get_token(fp, token, sizeof(token), &linenum) != NULL) - { - if (strcasecmp(token, "EXPECT") && - strcasecmp(token, "IF-DEFINED") && - strcasecmp(token, "OF-TYPE") && - strcasecmp(token, "SAME-COUNT-AS")) - last_expect = NULL; - - if (!strcmp(token, "}")) - break; - else if (!strcasecmp(token, "NAME")) - { - /* - * Name of test... - */ - - get_token(fp, name, sizeof(name), &linenum); - } - else if (!strcasecmp(token, "VERSION")) - { - /* - * IPP version number for test... - */ - - int major, minor; /* Major/minor IPP version */ - - - get_token(fp, temp, sizeof(temp), &linenum); - if (sscanf(temp, "%d.%d", &major, &minor) == 2 && - major >= 0 && minor >= 0 && minor < 10) - version = major * 10 + minor; - else - { - printf("Bad version %s seen on line %d - aborting test!\n", token, - linenum); - httpClose(http); - ippDelete(request); - return (0); - } - } - else if (!strcasecmp(token, "RESOURCE")) - { - /* - * Resource name... - */ - - get_token(fp, resource, sizeof(resource), &linenum); - } - else if (!strcasecmp(token, "OPERATION")) - { - /* - * Operation... - */ - - get_token(fp, token, sizeof(token), &linenum); - op = ippOpValue(token); - } - else if (!strcasecmp(token, "GROUP")) - { - /* - * Attribute group... - */ - - get_token(fp, token, sizeof(token), &linenum); - value = ippTagValue(token); - - if (value == group) - ippAddSeparator(request); - - group = value; - } - else if (!strcasecmp(token, "DELAY")) - { - /* - * Delay before operation... - */ - - int delay; - - get_token(fp, token, sizeof(token), &linenum); - if ((delay = atoi(token)) > 0) - sleep(delay); - } - else if (!strcasecmp(token, "ATTR")) - { - /* - * Attribute... - */ - - get_token(fp, token, sizeof(token), &linenum); - value = ippTagValue(token); - get_token(fp, attr, sizeof(attr), &linenum); - get_token(fp, temp, sizeof(temp), &linenum); - - token[sizeof(token) - 1] = '\0'; - - for (tempptr = temp, tokenptr = token; - *tempptr && tokenptr < (token + sizeof(token) - 1);) - if (*tempptr == '$') - { - /* - * Substitute a string/number... - */ - - if (!strncasecmp(tempptr + 1, "uri", 3)) - { - strlcpy(tokenptr, uri, sizeof(token) - (tokenptr - token)); - tempptr += 4; - } - else if (!strncasecmp(tempptr + 1, "scheme", 6) || - !strncasecmp(tempptr + 1, "method", 6)) - { - strlcpy(tokenptr, scheme, sizeof(token) - (tokenptr - token)); - tempptr += 7; - } - else if (!strncasecmp(tempptr + 1, "username", 8)) - { - strlcpy(tokenptr, userpass, sizeof(token) - (tokenptr - token)); - tempptr += 9; - } - else if (!strncasecmp(tempptr + 1, "hostname", 8)) - { - strlcpy(tokenptr, server, sizeof(token) - (tokenptr - token)); - tempptr += 9; - } - else if (!strncasecmp(tempptr + 1, "port", 4)) - { - snprintf(tokenptr, sizeof(token) - (tokenptr - token), - "%d", port); - tempptr += 5; - } - else if (!strncasecmp(tempptr + 1, "resource", 8)) - { - strlcpy(tokenptr, resource, sizeof(token) - (tokenptr - token)); - tempptr += 9; - } - else if (!strncasecmp(tempptr + 1, "job-id", 6)) - { - snprintf(tokenptr, sizeof(token) - (tokenptr - token), - "%d", job_id); - tempptr += 7; - } - else if (!strncasecmp(tempptr + 1, "notify-subscription-id", 22)) - { - snprintf(tokenptr, sizeof(token) - (tokenptr - token), - "%d", subscription_id); - tempptr += 23; - } - else if (!strncasecmp(tempptr + 1, "user", 4)) - { - strlcpy(tokenptr, cupsUser(), sizeof(token) - (tokenptr - token)); - tempptr += 5; - } - else if (!strncasecmp(tempptr + 1, "ENV[", 4)) - { - char *end; /* End of $ENV[name] */ - - - if ((end = strchr(tempptr + 5, ']')) != NULL) - { - *end++ = '\0'; - strlcpy(tokenptr, - getenv(tempptr + 5) ? getenv(tempptr + 5) : tempptr + 5, - sizeof(token) - (tokenptr - token)); - tempptr = end; - } - else - { - *tokenptr++ = *tempptr++; - *tokenptr = '\0'; - } - } - else - { - *tokenptr++ = *tempptr++; - *tokenptr = '\0'; - } - - tokenptr += strlen(tokenptr); - } - else - { - *tokenptr++ = *tempptr++; - *tokenptr = '\0'; - } - - switch (value) - { - case IPP_TAG_BOOLEAN : - if (!strcasecmp(token, "true")) - ippAddBoolean(request, group, attr, 1); - else - ippAddBoolean(request, group, attr, atoi(token)); - break; - - case IPP_TAG_INTEGER : - case IPP_TAG_ENUM : - ippAddInteger(request, group, value, attr, atoi(token)); - break; - - case IPP_TAG_RESOLUTION : - puts(" ERROR: resolution tag not yet supported!"); - break; - - case IPP_TAG_RANGE : - puts(" ERROR: range tag not yet supported!"); - break; - - default : - if (!strchr(token, ',')) - ippAddString(request, group, value, attr, NULL, token); - else - { - /* - * Multiple string values... - */ - - int num_values; /* Number of values */ - char *values[100], /* Values */ - *ptr; /* Pointer to next value */ - - - values[0] = token; - num_values = 1; - - for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ',')) - { - *ptr++ = '\0'; - values[num_values] = ptr; - num_values ++; - } - - ippAddStrings(request, group, value, attr, num_values, - NULL, (const char **)values); - } - break; - } - } - else if (!strcasecmp(token, "FILE")) - { - /* - * File... - */ - - get_token(fp, filename, sizeof(filename), &linenum); - } - else if (!strcasecmp(token, "STATUS") && - num_statuses < (int)(sizeof(statuses) / sizeof(statuses[0]))) - { - /* - * Status... - */ - - get_token(fp, token, sizeof(token), &linenum); - statuses[num_statuses] = ippErrorValue(token); - num_statuses ++; - } - else if (!strcasecmp(token, "EXPECT")) - { - /* - * Expected attributes... - */ - - if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0]))) - { - fprintf(stderr, "ipptest: Too many EXPECT's on line %d\n", linenum); - httpClose(http); - ippDelete(request); - return (0); - } - - get_token(fp, token, sizeof(token), &linenum); - - last_expect = expects + num_expects; - num_expects ++; - - if (token[0] == '!') - { - last_expect->not_expect = 1; - last_expect->name = strdup(token + 1); - } - else - { - last_expect->not_expect = 0; - last_expect->name = strdup(token); - } - - last_expect->of_type = NULL; - last_expect->same_count_as = NULL; - last_expect->if_defined = NULL; - } - else if (!strcasecmp(token, "OF-TYPE")) - { - get_token(fp, token, sizeof(token), &linenum); - - if (last_expect) - last_expect->of_type = strdup(token); - else - { - fprintf(stderr, - "ipptest: OF-TYPE without a preceding EXPECT on line %d\n", - linenum); - httpClose(http); - ippDelete(request); - return (0); - } - } - else if (!strcasecmp(token, "SAME-COUNT-AS")) - { - get_token(fp, token, sizeof(token), &linenum); - - if (last_expect) - last_expect->same_count_as = strdup(token); - else - { - fprintf(stderr, - "ipptest: SAME-COUNT-AS without a preceding EXPECT on line " - "%d\n", linenum); - httpClose(http); - ippDelete(request); - return (0); - } - } - else if (!strcasecmp(token, "IF-DEFINED")) - { - get_token(fp, token, sizeof(token), &linenum); - - if (last_expect) - last_expect->if_defined = strdup(token); - else - { - fprintf(stderr, - "ipptest: IF-DEFINED without a preceding EXPECT on line %d\n", - linenum); - httpClose(http); - ippDelete(request); - return (0); - } - } - else if (!strcasecmp(token, "DISPLAY") && - num_displayed < (int)(sizeof(displayed) / sizeof(displayed[0]))) - { - /* - * Display attributes... - */ - - get_token(fp, token, sizeof(token), &linenum); - displayed[num_displayed] = strdup(token); - num_displayed ++; - } - else - { - fprintf(stderr, - "ipptest: Unexpected token %s seen on line %d - aborting " - "test!\n", token, linenum); - httpClose(http); - ippDelete(request); - return (0); - } - } - - /* - * Submit the IPP request... - */ - - request->request.op.version[0] = version / 10; - request->request.op.version[1] = version % 10; - request->request.op.operation_id = op; - request->request.op.request_id = 1; - - if (Verbosity) - { - printf(" %s:\n", ippOpString(op)); - - for (attrptr = request->attrs; attrptr; attrptr = attrptr->next) - print_attr(attrptr); - } - - printf(" %-60.60s [", name); - fflush(stdout); - - if (Chunking) - { - http_status_t status = cupsSendRequest(http, request, resource, 0); - - if (status == HTTP_CONTINUE && filename[0]) - { - int fd; /* File to send */ - char buffer[8192]; /* Copy buffer */ - ssize_t bytes; /* Bytes read/written */ - - - if ((fd = open(filename, O_RDONLY | O_BINARY)) >= 0) - { - while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) - if ((status = cupsWriteRequestData(http, buffer, - bytes)) != HTTP_CONTINUE) - break; - } - else - status = HTTP_ERROR; - } - - ippDelete(request); - - if (status == HTTP_CONTINUE) - response = cupsGetResponse(http, resource); - else - response = NULL; - } - else if (filename[0]) - response = cupsDoFileRequest(http, request, resource, filename); - else - response = cupsDoIORequest(http, request, resource, -1, - Verbosity ? 1 : -1); - - if (response == NULL) - { - time_t curtime; - - curtime = time(NULL); - - puts("FAIL]"); - printf(" ERROR %04x (%s) @ %s\n", cupsLastError(), - cupsLastErrorString(), ctime(&curtime)); - pass = 0; - } - else - { - if (http->version != HTTP_1_1) - pass = 0; - - if ((attrptr = ippFindAttribute(response, "job-id", - IPP_TAG_INTEGER)) != NULL) - job_id = attrptr->values[0].integer; - - if ((attrptr = ippFindAttribute(response, "notify-subscription-id", - IPP_TAG_INTEGER)) != NULL) - subscription_id = attrptr->values[0].integer; - - for (i = 0; i < num_statuses; i ++) - if (response->request.status.status_code == statuses[i]) - break; - - if (i == num_statuses && num_statuses > 0) - pass = 0; - else - { - for (i = num_expects, expect = expects; i > 0; i --, expect ++) - { - if (expect->if_defined && !getenv(expect->if_defined)) - continue; - - found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); - - if ((found == NULL) != expect->not_expect || - (found && !expect_matches(expect, found->value_tag))) - { - pass = 0; - break; - } - - if (found && expect->same_count_as) - { - attrptr = ippFindAttribute(response, expect->same_count_as, - IPP_TAG_ZERO); - - if (!attrptr || attrptr->num_values != found->num_values) - { - pass = 0; - break; - } - } - } - } - - if (pass) - { - puts("PASS]"); - printf(" RECEIVED: %lu bytes in response\n", - (unsigned long)ippLength(response)); - - if (Verbosity) - { - for (attrptr = response->attrs; - attrptr != NULL; - attrptr = attrptr->next) - { - print_attr(attrptr); - } - } - else if (num_displayed > 0) - { - for (attrptr = response->attrs; - attrptr != NULL; - attrptr = attrptr->next) - { - if (attrptr->name) - { - for (i = 0; i < num_displayed; i ++) - { - if (!strcmp(displayed[i], attrptr->name)) - { - print_attr(attrptr); - break; - } - } - } - } - } - } - else - { - puts("FAIL]"); - printf(" RECEIVED: %lu bytes in response\n", - (unsigned long)ippLength(response)); - - if (http->version != HTTP_1_1) - printf(" BAD HTTP VERSION (%d.%d)\n", http->version / 100, - http->version % 100); - - for (i = 0; i < num_statuses; i ++) - if (response->request.status.status_code == statuses[i]) - break; - - if (i == num_statuses && num_statuses > 0) - puts(" BAD STATUS"); - - printf(" status-code = %04x (%s)\n", - cupsLastError(), ippErrorString(cupsLastError())); - - for (i = num_expects, expect = expects; i > 0; i --, expect ++) - { - if (expect->if_defined && !getenv(expect->if_defined)) - continue; - - found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); - - if ((found == NULL) != expect->not_expect) - { - if (expect->not_expect) - printf(" NOT EXPECTED: %s\n", expect->name); - else - printf(" EXPECTED: %s\n", expect->name); - } - else if (found) - { - if (!expect_matches(expect, found->value_tag)) - printf(" EXPECTED: %s of type %s but got %s\n", - expect->name, expect->of_type, - ippTagString(found->value_tag)); - else if (expect->same_count_as) - { - attrptr = ippFindAttribute(response, expect->same_count_as, - IPP_TAG_ZERO); - - if (!attrptr) - printf(" EXPECTED: %s (%d values) same count as %s " - "(not returned)\n", - expect->name, found->num_values, expect->same_count_as); - else if (attrptr->num_values != found->num_values) - printf(" EXPECTED: %s (%d values) same count as %s " - "(%d values)\n", - expect->name, found->num_values, expect->same_count_as, - attrptr->num_values); - } - } - } - - for (attrptr = response->attrs; attrptr != NULL; attrptr = attrptr->next) - print_attr(attrptr); - } - - ippDelete(response); - } - - for (i = num_expects, expect = 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 (!pass) - break; - } - - fclose(fp); - httpClose(http); - - return (pass); -} - - -/* - * '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 */ - - - /* - * 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 "|"... - */ - - 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. - */ - - if ((next = strchr(of_type, '|')) != NULL) - *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 (next) - *next++ = '|'; - } - - return (match); -} - - -/* - * 'get_token()' - Get a token from a file. - */ - -static char * /* O - Token from file or NULL on EOF */ -get_token(FILE *fp, /* I - File to read from */ - char *buf, /* I - Buffer to read into */ - int buflen, /* I - Length of buffer */ - int *linenum) /* IO - Current line number */ -{ - int ch, /* Character from file */ - quote; /* Quoting character */ - char *bufptr, /* Pointer into buffer */ - *bufend; /* End of buffer */ - - - for (;;) - { - /* - * Skip whitespace... - */ - - while (isspace(ch = getc(fp))) - { - if (ch == '\n') - (*linenum) ++; - } - - /* - * Read a token... - */ - - if (ch == EOF) - return (NULL); - else if (ch == '\'' || ch == '\"') - { - /* - * Quoted text... - */ - - quote = ch; - bufptr = buf; - bufend = buf + buflen - 1; - - while ((ch = getc(fp)) != EOF) - if (ch == quote) - break; - else if (bufptr < bufend) - *bufptr++ = ch; - - *bufptr = '\0'; - return (buf); - } - else if (ch == '#') - { - /* - * Comment... - */ - - while ((ch = getc(fp)) != EOF) - if (ch == '\n') - break; - - (*linenum) ++; - } - else - { - /* - * Whitespace delimited text... - */ - - ungetc(ch, fp); - - bufptr = buf; - bufend = buf + buflen - 1; - - while ((ch = getc(fp)) != EOF) - if (isspace(ch) || ch == '#') - break; - else if (bufptr < bufend) - *bufptr++ = ch; - - if (ch == '#') - ungetc(ch, fp); - - *bufptr = '\0'; - return (buf); - } - } -} - - -/* - * 'print_attr()' - Print an attribute on the screen. - */ - -static void -print_attr(ipp_attribute_t *attr) /* I - Attribute to print */ -{ - int i; /* Looping var */ - - - if (attr->name == NULL) - { - puts(" -- separator --"); - return; - } - - printf(" %s (%s%s) = ", attr->name, - attr->num_values > 1 ? "1setOf " : "", - ippTagString(attr->value_tag)); - - switch (attr->value_tag) - { - case IPP_TAG_INTEGER : - case IPP_TAG_ENUM : - for (i = 0; i < attr->num_values; i ++) - printf("%d ", attr->values[i].integer); - break; - - case IPP_TAG_BOOLEAN : - for (i = 0; i < attr->num_values; i ++) - if (attr->values[i].boolean) - printf("true "); - else - printf("false "); - break; - - case IPP_TAG_NOVALUE : - printf("novalue"); - break; - - case IPP_TAG_RANGE : - for (i = 0; i < attr->num_values; i ++) - printf("%d-%d ", attr->values[i].range.lower, - attr->values[i].range.upper); - break; - - case IPP_TAG_RESOLUTION : - for (i = 0; i < attr->num_values; i ++) - printf("%dx%d%s ", attr->values[i].resolution.xres, - attr->values[i].resolution.yres, - attr->values[i].resolution.units == IPP_RES_PER_INCH ? - "dpi" : "dpc"); - break; - - case IPP_TAG_STRING : - case IPP_TAG_TEXT : - case IPP_TAG_NAME : - case IPP_TAG_KEYWORD : - case IPP_TAG_CHARSET : - case IPP_TAG_URI : - case IPP_TAG_MIMETYPE : - case IPP_TAG_LANGUAGE : - for (i = 0; i < attr->num_values; i ++) - printf("\"%s\" ", attr->values[i].string.text); - break; - - case IPP_TAG_TEXTLANG : - case IPP_TAG_NAMELANG : - for (i = 0; i < attr->num_values; i ++) - printf("\"%s\",%s ", attr->values[i].string.text, - attr->values[i].string.charset); - break; - - case IPP_TAG_BEGIN_COLLECTION : - for (i = 0; i < attr->num_values; i ++) - { - if (i) - putchar(' '); - - print_col(attr->values[i].collection); - } - break; - - default : - break; /* anti-compiler-warning-code */ - } - - putchar('\n'); -} - - -/* - * 'print_col()' - Print a collection attribute on the screen. - */ - -static void -print_col(ipp_t *col) /* I - Collection attribute to print */ -{ - int i; /* Looping var */ - ipp_attribute_t *attr; /* Current attribute in collection */ - - - putchar('{'); - for (attr = col->attrs; attr; attr = attr->next) - { - printf("%s(%s%s)=", attr->name, attr->num_values > 1 ? "1setOf " : "", - ippTagString(attr->value_tag)); - - switch (attr->value_tag) - { - case IPP_TAG_INTEGER : - case IPP_TAG_ENUM : - for (i = 0; i < attr->num_values; i ++) - printf("%d ", attr->values[i].integer); - break; - - case IPP_TAG_BOOLEAN : - for (i = 0; i < attr->num_values; i ++) - if (attr->values[i].boolean) - printf("true "); - else - printf("false "); - break; - - case IPP_TAG_NOVALUE : - printf("novalue"); - break; - - case IPP_TAG_RANGE : - for (i = 0; i < attr->num_values; i ++) - printf("%d-%d ", attr->values[i].range.lower, - attr->values[i].range.upper); - break; - - case IPP_TAG_RESOLUTION : - for (i = 0; i < attr->num_values; i ++) - printf("%dx%d%s ", attr->values[i].resolution.xres, - attr->values[i].resolution.yres, - attr->values[i].resolution.units == IPP_RES_PER_INCH ? - "dpi" : "dpc"); - break; - - case IPP_TAG_STRING : - case IPP_TAG_TEXT : - case IPP_TAG_NAME : - case IPP_TAG_KEYWORD : - case IPP_TAG_CHARSET : - case IPP_TAG_URI : - case IPP_TAG_MIMETYPE : - case IPP_TAG_LANGUAGE : - for (i = 0; i < attr->num_values; i ++) - printf("\"%s\" ", attr->values[i].string.text); - break; - - case IPP_TAG_TEXTLANG : - case IPP_TAG_NAMELANG : - for (i = 0; i < attr->num_values; i ++) - printf("\"%s\",%s ", attr->values[i].string.text, - attr->values[i].string.charset); - break; - - case IPP_TAG_BEGIN_COLLECTION : - for (i = 0; i < attr->num_values; i ++) - { - print_col(attr->values[i].collection); - putchar(' '); - } - break; - - default : - break; /* anti-compiler-warning-code */ - } - } - - putchar('}'); -} - - -/* - * 'usage()' - Show program usage. - */ - -static void -usage(void) -{ - fputs("Usage: ipptest [options] URL testfile [ ... testfileN ]\n", stderr); - fputs("Options:\n", stderr); - fputs("\n", stderr); - fputs("-c Send requests using chunking.\n", stderr); - fputs("-d name=value Define variable.\n", stderr); - fputs("-i seconds Repeat the last test file with the given interval.\n", - stderr); - fputs("-v Show all attributes in response, even on success.\n", - stderr); - - exit(1); -} - - -/* - * End of "$Id: ipptest.c 8929 2009-12-15 22:40:37Z mike $". - */ diff --git a/test/ipptool.c b/test/ipptool.c new file mode 100644 index 00000000..5397336f --- /dev/null +++ b/test/ipptool.c @@ -0,0 +1,4921 @@ +/* + * "$Id: ipptool.c 9829 2011-06-14 21:01:39Z mike $" + * + * ipptool command for CUPS. + * + * Copyright 2007-2011 by Apple Inc. + * Copyright 1997-2007 by Easy Software Products. + * + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". + * + * This file is subject to the Apple OS-Developed Software exception. + * + * Contents: + * + * main() - Parse options and do tests. + * compare_vars() - Compare two variables. + * do_tests() - Do tests as specified in the test file. + * expand_variables() - Expand variables in a string. + * expect_matches() - Return true if the tag matches the specification. + * get_collection() - Get a collection value from the current test file. + * get_filename() - Get a filename based on the current test file. + * get_token() - Get a token from a file. + * get_variable() - Get the value of a variable. + * iso_date() - Return an ISO 8601 date/time string for the given IPP + * dateTime value. + * password_cb() - Password callback for authenticated tests. + * print_attr() - Print an attribute on the screen. + * print_col() - Print a collection attribute on the screen. + * print_csv() - Print a line of CSV text. + * print_fatal_error() - Print a fatal error message. + * print_line() - Print a line of formatted or CSV text. + * print_test_error() - Print a test error message. + * print_xml_header() - Print a standard XML plist header. + * print_xml_string() - Print an XML string with escaping. + * print_xml_trailer() - Print the XML trailer with success/fail value. + * set_variable() - Set a variable value. + * timeout_cb() - Handle HTTP timeouts. + * usage() - Show program usage. + * validate_attr() - Determine whether an attribute is valid. + * with_value() - Test a WITH-VALUE predicate. + */ + +/* + * Include necessary headers... + */ + +#include <cups/cups-private.h> +#include <cups/file-private.h> +#include <regex.h> + +#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_LIST, /* Tabular list output */ + _CUPS_OUTPUT_CSV /* Comma-separated values output */ +} _cups_output_t; + +typedef struct _cups_expect_s /**** Expected attribute info ****/ +{ + int optional, /* Optional attribute? */ + not_expect; /* Don't expect attribute? */ + 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 */ + *define_match, /* Variable to define on match */ + *define_no_match, /* Variable to define on no-match */ + *define_value; /* Variable to define with value */ + int with_regex, /* WITH-VALUE is a regular expression */ + 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 */ +} _cups_status_t; + +typedef struct _cups_var_s /**** Variable ****/ +{ + char *name, /* Name of variable */ + *value; /* Value of variable */ +} _cups_var_t; + +typedef struct _cups_vars_s /**** Set of variables ****/ +{ + const char *uri, /* URI for printer */ + *filename; /* Filename */ + char scheme[64], /* Scheme from URI */ + userpass[256], /* Username/password from URI */ + hostname[256], /* Hostname from URI */ + resource[1024]; /* Resource path from URI */ + int port; /* Port number from URI */ + http_encryption_t encryption; /* Encryption for connection? */ + double timeout; /* Timeout for connection */ + int family; /* Address family */ + cups_array_t *vars; /* Array of variables */ +} _cups_vars_t; + + +/* + * Globals... + */ + +_cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO; + /* How to transfer requests */ +_cups_output_t Output = _CUPS_OUTPUT_LIST; + /* Output mode */ +int IgnoreErrors = 0, /* Ignore errors? */ + Verbosity = 0, /* Show all attributes? */ + Version = 11, /* Default IPP version */ + XMLHeader = 0; /* 1 if header is written */ +char *Password = NULL; /* Password from URI */ +const char * const URIStatusStrings[] = /* URI status strings */ +{ + "URI too large", + "Bad arguments to function", + "Bad resource in URI", + "Bad port number in URI", + "Bad hostname/address in URI", + "Bad username in URI", + "Bad scheme in URI", + "Bad/empty URI", + "OK", + "Missing scheme in URI", + "Unknown scheme in URI", + "Missing resource in URI" +}; + + +/* + * Local functions... + */ + +static int compare_vars(_cups_var_t *a, _cups_var_t *b); +static int do_tests(_cups_vars_t *vars, const char *testfile); +static void expand_variables(_cups_vars_t *vars, char *dst, const char *src, + size_t dstsize) +#ifdef __GNUC__ +__attribute((nonnull(1,2,3))) +#endif /* __GNUC__ */ +; +static int expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag); +static ipp_t *get_collection(_cups_vars_t *vars, FILE *fp, int *linenum); +static char *get_filename(const char *testfile, char *dst, const char *src, + size_t dstsize); +static char *get_token(FILE *fp, char *buf, int buflen, + int *linenum); +static char *get_variable(_cups_vars_t *vars, const char *name); +static char *iso_date(ipp_uchar_t *date); +static const char *password_cb(const char *prompt); +static void print_attr(ipp_attribute_t *attr, ipp_tag_t *group); +static void print_col(ipp_t *col); +static void print_csv(ipp_attribute_t *attr, int num_displayed, + char **displayed, size_t *widths); +static void print_fatal_error(const char *s, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 1, 2))) +#endif /* __GNUC__ */ +; +static void print_line(ipp_attribute_t *attr, int num_displayed, + char **displayed, size_t *widths); +static void print_test_error(const char *s, ...) +#ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 1, 2))) +#endif /* __GNUC__ */ +; +static void print_xml_header(void); +static void print_xml_string(const char *element, const char *s); +static void print_xml_trailer(int success, const char *message); +static void set_variable(_cups_vars_t *vars, const char *name, + const char *value); +static int timeout_cb(http_t *http, void *user_data); +static void usage(void); +static int validate_attr(ipp_attribute_t *attr, int print); +static int with_value(char *value, int regex, ipp_attribute_t *attr, + int report); + + +/* + * '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 *testfile; /* Test file to use */ + int interval, /* Test interval in microseconds */ + repeat; /* Repeat count */ + _cups_vars_t vars; /* Variables */ + http_uri_status_t uri_status; /* URI separation status */ + _cups_globals_t *cg = _cupsGlobals(); + /* Global data */ + + + + /* + * Initialize the locale and variables... + */ + + _cupsSetLocale(argv); + + memset(&vars, 0, sizeof(vars)); + vars.family = AF_UNSPEC; + vars.vars = cupsArrayNew((cups_array_func_t)compare_vars, NULL); + + /* + * We need at least: + * + * ipptool URI testfile + */ + + interval = 0; + repeat = 0; + status = 0; + testfile = NULL; + + for (i = 1; i < argc; i ++) + { + if (argv[i][0] == '-') + { + for (opt = argv[i] + 1; *opt; opt ++) + { + switch (*opt) + { + case '4' : /* Connect using IPv4 only */ + vars.family = AF_INET; + break; + +#ifdef AF_INET6 + case '6' : /* Connect using IPv6 only */ + vars.family = AF_INET6; + break; +#endif /* AF_INET6 */ + + case 'C' : /* Enable HTTP chunking */ + Transfer = _CUPS_TRANSFER_CHUNKED; + break; + + case 'E' : /* Encrypt with TLS */ +#ifdef HAVE_SSL + vars.encryption = HTTP_ENCRYPT_REQUIRED; +#else + _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."), + argv[0]); +#endif /* HAVE_SSL */ + break; + + case 'I' : /* Ignore errors */ + IgnoreErrors = 1; + break; + + case 'L' : /* Disable HTTP chunking */ + Transfer = _CUPS_TRANSFER_LENGTH; + break; + + case 'S' : /* Encrypt with SSL */ +#ifdef HAVE_SSL + vars.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) + { + _cupsLangPuts(stderr, + _("ipptool: Missing timeout for \"-T\".")); + usage(); + } + + vars.timeout = _cupsStrScand(argv[i], NULL, localeconv()); + break; + + case 'V' : /* Set IPP version */ + i ++; + + if (i >= argc) + { + _cupsLangPuts(stderr, + _("ipptool: Missing version for \"-V\".")); + usage(); + } + + if (!strcmp(argv[i], "1.0")) + Version = 10; + else if (!strcmp(argv[i], "1.1")) + Version = 11; + else if (!strcmp(argv[i], "2.0")) + Version = 20; + else if (!strcmp(argv[i], "2.1")) + Version = 21; + else if (!strcmp(argv[i], "2.2")) + Version = 22; + else + { + _cupsLangPrintf(stderr, + _("ipptool: Bad version %s for \"-V\"."), + argv[i]); + usage(); + } + break; + + case 'X' : /* Produce XML output */ + Output = _CUPS_OUTPUT_PLIST; + + if (interval || repeat) + { + _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are " + "incompatible with -X\".")); + usage(); + } + break; + + case 'c' : /* CSV output */ + 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); + + set_variable(&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) && argv[i][0] != '/') + { + snprintf(filename, sizeof(filename), "%s/ipptool/%s", + cg->cups_datadir, argv[i]); + if (access(argv[i], 0)) + vars.filename = argv[i]; + else + vars.filename = filename; + } + else + vars.filename = argv[i]; + 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 (Output == _CUPS_OUTPUT_PLIST && interval) + { + _cupsLangPuts(stderr, _("ipptool: \"-i\" is incompatible with " + "\"-X\".")); + usage(); + } + break; + + case 'l' : /* List as a table */ + 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 (Output == _CUPS_OUTPUT_PLIST && repeat) + { + _cupsLangPuts(stderr, _("ipptool: \"-n\" is incompatible with " + "\"-X\".")); + usage(); + } + break; + + case 'q' : /* Be quiet */ + Output = _CUPS_OUTPUT_QUIET; + break; + + case 't' : /* CUPS test output */ + Output = _CUPS_OUTPUT_TEST; + break; + + case 'v' : /* Be verbose */ + Verbosity ++; + break; + + default : + _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\"."), + *opt); + usage(); + break; + } + } + } + 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)) + vars.encryption = HTTP_ENCRYPT_ALWAYS; +#endif /* HAVE_SSL */ + + vars.uri = argv[i]; + uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, vars.uri, + vars.scheme, sizeof(vars.scheme), + vars.userpass, sizeof(vars.userpass), + vars.hostname, sizeof(vars.hostname), + &(vars.port), + vars.resource, sizeof(vars.resource)); + + if (uri_status != HTTP_URI_OK) + { + _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s."), + URIStatusStrings[uri_status - HTTP_URI_OVERFLOW]); + return (1); + } + + if (vars.userpass[0]) + { + if ((Password = strchr(vars.userpass, ':')) != NULL) + *Password++ = '\0'; + + cupsSetUser(vars.userpass); + cupsSetPasswordCB(password_cb); + set_variable(&vars, "uriuser", vars.userpass); + } + } + else + { + /* + * Run test... + */ + + if (!vars.uri) + { + _cupsLangPuts(stderr, _("ipptool: URI required before test file.")); + usage(); + } + + if (access(argv[i], 0) && argv[i][0] != '/') + { + 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(&vars, testfile)) + status = 1; + } + } + + if (!vars.uri || !testfile) + usage(); + + /* + * Loop if the interval is set... + */ + + if (Output == _CUPS_OUTPUT_PLIST) + print_xml_trailer(!status, NULL); + else if (interval > 0 && repeat > 0) + { + while (repeat > 1) + { + usleep(interval); + do_tests(&vars, testfile); + repeat --; + } + } + else if (interval > 0) + { + for (;;) + { + usleep(interval); + do_tests(&vars, testfile); + } + } + + /* + * Exit... + */ + + return (status); +} + + +/* + * 'compare_vars()' - Compare two variables. + */ + +static int /* O - Result of comparison */ +compare_vars(_cups_var_t *a, /* I - First variable */ + _cups_var_t *b) /* I - Second variable */ +{ + return (_cups_strcasecmp(a->name, b->name)); +} + + +/* + * 'do_tests()' - Do tests as specified in the test file. + */ + +static int /* 1 = success, 0 = failure */ +do_tests(_cups_vars_t *vars, /* I - Variables */ + const char *testfile) /* I - Test file to use */ +{ + int i, /* Looping var */ + linenum, /* Current line number */ + pass, /* Did we pass the test? */ + prev_pass = 1, /* Did we pass the previous test? */ + request_id, /* Current request ID */ + show_header = 1, /* Show the test header? */ + ignore_errors, /* Ignore test failures? */ + skip_previous = 0; /* Skip on previous test failure? */ + http_t *http = NULL; /* HTTP connection to server */ + FILE *fp = NULL; /* Test file */ + char resource[512], /* Resource for request */ + token[1024], /* Token from file */ + *tokenptr, /* Pointer into token */ + temp[1024]; /* Temporary string */ + ipp_t *request = NULL; /* IPP request */ + ipp_t *response = NULL; /* IPP response */ + char attr[128]; /* Attribute name */ + ipp_op_t op; /* Operation */ + ipp_tag_t group; /* Current group */ + ipp_tag_t value; /* Current value type */ + ipp_attribute_t *attrptr, /* Attribute pointer */ + *found, /* Found attribute */ + *lastcol = NULL; /* Last collection attribute */ + char name[1024]; /* Name of test */ + char filename[1024]; /* Filename */ + _cups_transfer_t transfer; /* To chunk or not to chunk */ + int version, /* IPP version number to use */ + skip_test; /* Skip this test? */ + int num_statuses = 0; /* Number of valid status codes */ + _cups_status_t statuses[100], /* Valid status codes */ + *last_status; /* Last STATUS (for predicates) */ + int num_expects = 0; /* Number of expected attributes */ + _cups_expect_t expects[200], /* Expected attributes */ + *expect, /* Current expected attribute */ + *last_expect; /* Last EXPECT (for predicates) */ + int num_displayed = 0; /* Number of displayed attributes */ + char *displayed[200]; /* Displayed attributes */ + size_t widths[200]; /* Width of columns */ + + + /* + * Open the test file... + */ + + if ((fp = fopen(testfile, "r")) == NULL) + { + print_fatal_error("Unable to open test file %s - %s", testfile, + strerror(errno)); + pass = 0; + goto test_exit; + } + + /* + * Connect to the server... + */ + + if ((http = _httpCreate(vars->hostname, vars->port, NULL, vars->encryption, + vars->family)) == NULL) + { + print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname, + vars->port, strerror(errno)); + pass = 0; + goto test_exit; + } + + if (httpReconnect(http)) + { + print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname, + vars->port, strerror(errno)); + pass = 0; + goto test_exit; + } + + if (vars->timeout > 0.0) + httpSetTimeout(http, vars->timeout, timeout_cb, NULL); + + /* + * Loop on tests... + */ + + CUPS_SRAND(time(NULL)); + + pass = 1; + linenum = 1; + request_id = (CUPS_RAND() % 1000) * 137 + 1; + + while (get_token(fp, token, sizeof(token), &linenum) != NULL) + { + /* + * Expect an open brace... + */ + + if (!strcmp(token, "DEFINE")) + { + /* + * DEFINE name value + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + expand_variables(vars, token, temp, sizeof(token)); + set_variable(vars, attr, token); + } + else + { + print_fatal_error("Missing DEFINE name and/or value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "DEFINE-DEFAULT")) + { + /* + * DEFINE-DEFAULT name value + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + expand_variables(vars, token, temp, sizeof(token)); + if (!get_variable(vars, attr)) + set_variable(vars, attr, token); + } + else + { + print_fatal_error("Missing DEFINE-DEFAULT name and/or value on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "IGNORE-ERRORS")) + { + /* + * IGNORE-ERRORS yes + * IGNORE-ERRORS no + */ + + if (get_token(fp, temp, sizeof(temp), &linenum) && + (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no"))) + { + IgnoreErrors = !_cups_strcasecmp(temp, "yes"); + } + else + { + print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "INCLUDE")) + { + /* + * INCLUDE "filename" + * INCLUDE <filename> + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + /* + * Map the filename to and then run the tests... + */ + + if (!do_tests(vars, get_filename(testfile, filename, temp, + sizeof(filename)))) + { + pass = 0; + + if (!IgnoreErrors) + goto test_exit; + } + } + else + { + print_fatal_error("Missing INCLUDE filename on line %d.", linenum); + pass = 0; + goto test_exit; + } + + show_header = 1; + continue; + } + else if (!strcmp(token, "INCLUDE-IF-DEFINED")) + { + /* + * INCLUDE-IF-DEFINED name "filename" + * INCLUDE-IF-DEFINED name <filename> + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + /* + * Map the filename to and then run the tests... + */ + + if (get_variable(vars, attr) && + !do_tests(vars, get_filename(testfile, filename, temp, + sizeof(filename)))) + { + pass = 0; + + if (!IgnoreErrors) + goto test_exit; + } + } + else + { + print_fatal_error("Missing INCLUDE-IF-DEFINED name or filename on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + + show_header = 1; + continue; + } + else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED")) + { + /* + * INCLUDE-IF-NOT-DEFINED name "filename" + * INCLUDE-IF-NOT-DEFINED name <filename> + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + /* + * Map the filename to and then run the tests... + */ + + if (!get_variable(vars, attr) && + !do_tests(vars, get_filename(testfile, filename, temp, + sizeof(filename)))) + { + pass = 0; + + if (!IgnoreErrors) + goto test_exit; + } + } + else + { + print_fatal_error("Missing INCLUDE-IF-NOT-DEFINED name or filename on " + "line %d.", linenum); + pass = 0; + goto test_exit; + } + + show_header = 1; + continue; + } + else if (!strcmp(token, "SKIP-IF-DEFINED")) + { + /* + * SKIP-IF-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (get_variable(vars, temp)) + goto test_exit; + } + else + { + print_fatal_error("Missing SKIP-IF-DEFINED variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-IF-NOT-DEFINED")) + { + /* + * SKIP-IF-NOT-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!get_variable(vars, temp)) + goto test_exit; + } + else + { + print_fatal_error("Missing SKIP-IF-NOT-DEFINED variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "TRANSFER")) + { + /* + * TRANSFER auto + * TRANSFER chunked + * TRANSFER length + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "auto")) + Transfer = _CUPS_TRANSFER_AUTO; + else if (!strcmp(temp, "chunked")) + Transfer = _CUPS_TRANSFER_CHUNKED; + else if (!strcmp(temp, "length")) + Transfer = _CUPS_TRANSFER_LENGTH; + else + { + print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp, + linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing TRANSFER value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "VERSION")) + { + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "1.0")) + Version = 10; + else if (!strcmp(temp, "1.1")) + Version = 11; + else if (!strcmp(temp, "2.0")) + Version = 20; + else if (!strcmp(temp, "2.1")) + Version = 21; + else if (!strcmp(temp, "2.2")) + Version = 22; + else + { + print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing VERSION number on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (strcmp(token, "{")) + { + print_fatal_error("Unexpected token %s seen on line %d.", token, linenum); + pass = 0; + goto test_exit; + } + + /* + * Initialize things... + */ + + if (show_header) + { + if (Output == _CUPS_OUTPUT_PLIST) + print_xml_header(); + else if (Output == _CUPS_OUTPUT_TEST) + printf("\"%s\":\n", testfile); + + show_header = 0; + } + + strlcpy(resource, vars->resource, sizeof(resource)); + + request_id ++; + request = ippNew(); + op = (ipp_op_t)0; + group = IPP_TAG_ZERO; + ignore_errors = IgnoreErrors; + last_expect = NULL; + last_status = NULL; + filename[0] = '\0'; + skip_test = 0; + version = Version; + transfer = Transfer; + + strlcpy(name, testfile, sizeof(name)); + if (strrchr(name, '.') != NULL) + *strrchr(name, '.') = '\0'; + + /* + * Parse until we see a close brace... + */ + + while (get_token(fp, token, sizeof(token), &linenum) != NULL) + { + 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, "SAME-COUNT-AS") && + _cups_strcasecmp(token, "WITH-VALUE")) + last_expect = NULL; + + if (_cups_strcasecmp(token, "IF-DEFINED") && + _cups_strcasecmp(token, "IF-NOT-DEFINED")) + last_status = NULL; + + if (!strcmp(token, "}")) + break; + else if (!strcmp(token, "{") && lastcol) + { + /* + * Another collection value + */ + + ipp_t *col = get_collection(vars, fp, &linenum); + /* Collection value */ + + if (col) + { + ipp_attribute_t *tempcol; /* Pointer to new buffer */ + + + /* + * Reallocate memory... + */ + + if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) + + (lastcol->num_values + 1) * + sizeof(ipp_value_t))) == NULL) + { + print_fatal_error("Unable to allocate memory on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (tempcol != lastcol) + { + /* + * Reset pointers in the list... + */ + + if (request->prev) + request->prev->next = tempcol; + else + request->attrs = tempcol; + + lastcol = request->current = request->last = tempcol; + } + + lastcol->values[lastcol->num_values].collection = col; + lastcol->num_values ++; + } + else + { + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "DEFINE")) + { + /* + * DEFINE name value + */ + + if (get_token(fp, attr, sizeof(attr), &linenum) && + get_token(fp, temp, sizeof(temp), &linenum)) + { + expand_variables(vars, token, temp, sizeof(token)); + set_variable(vars, attr, token); + } + else + { + print_fatal_error("Missing DEFINE name and/or value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "IGNORE-ERRORS")) + { + /* + * IGNORE-ERRORS yes + * IGNORE-ERRORS no + */ + + if (get_token(fp, temp, sizeof(temp), &linenum) && + (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no"))) + { + ignore_errors = !_cups_strcasecmp(temp, "yes"); + } + else + { + print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!_cups_strcasecmp(token, "NAME")) + { + /* + * Name of test... + */ + + get_token(fp, name, sizeof(name), &linenum); + } + else if (!strcmp(token, "REQUEST-ID")) + { + /* + * REQUEST-ID # + * REQUEST-ID random + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (isdigit(temp[0] & 255)) + request_id = atoi(temp); + else if (!_cups_strcasecmp(temp, "random")) + request_id = (CUPS_RAND() % 1000) * 137 + 1; + else + { + print_fatal_error("Bad REQUEST-ID value \"%s\" on line %d.", temp, + linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing REQUEST-ID value on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-IF-DEFINED")) + { + /* + * SKIP-IF-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (get_variable(vars, temp)) + skip_test = 1; + } + else + { + print_fatal_error("Missing SKIP-IF-DEFINED value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-IF-NOT-DEFINED")) + { + /* + * SKIP-IF-NOT-DEFINED variable + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!get_variable(vars, temp)) + skip_test = 1; + } + else + { + print_fatal_error("Missing SKIP-IF-NOT-DEFINED value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!strcmp(token, "SKIP-PREVIOUS-ERROR")) + { + /* + * SKIP-PREVIOUS-ERROR yes + * SKIP-PREVIOUS-ERROR no + */ + + if (get_token(fp, temp, sizeof(temp), &linenum) && + (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no"))) + { + skip_previous = !_cups_strcasecmp(temp, "yes"); + } + else + { + print_fatal_error("Missing SKIP-PREVIOUS-ERROR value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + continue; + } + else if (!strcmp(token, "TRANSFER")) + { + /* + * TRANSFER auto + * TRANSFER chunked + * TRANSFER length + */ + + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "auto")) + transfer = _CUPS_TRANSFER_AUTO; + else if (!strcmp(temp, "chunked")) + transfer = _CUPS_TRANSFER_CHUNKED; + else if (!strcmp(temp, "length")) + transfer = _CUPS_TRANSFER_LENGTH; + else + { + print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp, + linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing TRANSFER value on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "VERSION")) + { + if (get_token(fp, temp, sizeof(temp), &linenum)) + { + if (!strcmp(temp, "0.0")) + version = 0; + else if (!strcmp(temp, "1.0")) + version = 10; + else if (!strcmp(temp, "1.1")) + version = 11; + else if (!strcmp(temp, "2.0")) + version = 20; + else if (!strcmp(temp, "2.1")) + version = 21; + else if (!strcmp(temp, "2.2")) + version = 22; + else + { + print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum); + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Missing VERSION number on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "RESOURCE")) + { + /* + * Resource name... + */ + + if (!get_token(fp, resource, sizeof(resource), &linenum)) + { + print_fatal_error("Missing RESOURCE path on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "OPERATION")) + { + /* + * Operation... + */ + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing OPERATION code on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((op = ippOpValue(token)) == (ipp_op_t)-1 && + (op = strtol(token, NULL, 0)) == 0) + { + print_fatal_error("Bad OPERATION code \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "GROUP")) + { + /* + * Attribute group... + */ + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing GROUP tag on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((value = ippTagValue(token)) < 0) + { + print_fatal_error("Bad GROUP tag \"%s\" on line %d.", token, linenum); + pass = 0; + goto test_exit; + } + + if (value == group) + ippAddSeparator(request); + + group = value; + } + else if (!_cups_strcasecmp(token, "DELAY")) + { + /* + * Delay before operation... + */ + + double delay; + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DELAY value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((delay = _cupsStrScand(token, NULL, localeconv())) <= 0.0) + { + print_fatal_error("Bad DELAY value \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + else + { + if (Output == _CUPS_OUTPUT_TEST) + printf(" [%g second delay]\n", delay); + + usleep((int)(1000000.0 * delay)); + } + } + else if (!_cups_strcasecmp(token, "ATTR")) + { + /* + * Attribute... + */ + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing ATTR value tag on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((value = ippTagValue(token)) == IPP_TAG_ZERO) + { + print_fatal_error("Bad ATTR value tag \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, attr, sizeof(attr), &linenum)) + { + print_fatal_error("Missing ATTR name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing ATTR value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + expand_variables(vars, token, temp, sizeof(token)); + + switch (value) + { + case IPP_TAG_BOOLEAN : + if (!_cups_strcasecmp(token, "true")) + ippAddBoolean(request, group, attr, 1); + else + ippAddBoolean(request, group, attr, atoi(token)); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + if (!strchr(token, ',')) + ippAddInteger(request, group, value, attr, + strtol(token, &tokenptr, 0)); + else + { + int values[100], /* Values */ + num_values = 1; /* Number of values */ + + values[0] = strtol(token, &tokenptr, 10); + while (tokenptr && *tokenptr && + num_values < (int)(sizeof(values) / sizeof(values[0]))) + { + if (*tokenptr == ',') + tokenptr ++; + else if (!isdigit(*tokenptr & 255) && *tokenptr != '-') + break; + + values[num_values] = strtol(tokenptr, &tokenptr, 0); + num_values ++; + } + + ippAddIntegers(request, group, value, attr, num_values, values); + } + + if (!tokenptr || *tokenptr) + { + print_fatal_error("Bad %s value \"%s\" on line %d.", + ippTagString(value), token, linenum); + pass = 0; + goto test_exit; + } + break; + + case IPP_TAG_RESOLUTION : + { + int xres, /* X resolution */ + yres; /* Y resolution */ + char *ptr; /* Pointer into value */ + + xres = yres = strtol(token, (char **)&ptr, 10); + if (ptr > token && xres > 0) + { + if (*ptr == 'x') + yres = strtol(ptr + 1, (char **)&ptr, 10); + } + + if (ptr <= token || xres <= 0 || yres <= 0 || !ptr || + (_cups_strcasecmp(ptr, "dpi") && _cups_strcasecmp(ptr, "dpc") && + _cups_strcasecmp(ptr, "other"))) + { + print_fatal_error("Bad resolution value \"%s\" on line %d.", + token, linenum); + pass = 0; + goto test_exit; + } + + if (!_cups_strcasecmp(ptr, "dpi")) + ippAddResolution(request, group, attr, IPP_RES_PER_INCH, + xres, yres); + else if (!_cups_strcasecmp(ptr, "dpc")) + ippAddResolution(request, group, attr, IPP_RES_PER_CM, + xres, yres); + else + ippAddResolution(request, group, attr, (ipp_res_t)0, + xres, yres); + } + break; + + case IPP_TAG_RANGE : + { + int lowers[4], /* Lower value */ + uppers[4], /* Upper values */ + num_vals; /* Number of values */ + + + num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d", + lowers + 0, uppers + 0, + lowers + 1, uppers + 1, + lowers + 2, uppers + 2, + lowers + 3, uppers + 3); + + if ((num_vals & 1) || num_vals == 0) + { + print_fatal_error("Bad rangeOfInteger value \"%s\" on line " + "%d.", token, linenum); + pass = 0; + goto test_exit; + } + + ippAddRanges(request, group, attr, num_vals / 2, lowers, + uppers); + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + if (!strcmp(token, "{")) + { + ipp_t *col = get_collection(vars, fp, &linenum); + /* Collection value */ + + if (col) + { + lastcol = ippAddCollection(request, group, attr, col); + ippDelete(col); + } + else + { + pass = 0; + goto test_exit; + } + } + else + { + print_fatal_error("Bad ATTR collection value on line %d.", + linenum); + pass = 0; + goto test_exit; + } + break; + + default : + print_fatal_error("Unsupported ATTR value tag %s on line %d.", + ippTagString(value), linenum); + pass = 0; + goto test_exit; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + 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 : + if (!strchr(token, ',')) + ippAddString(request, group, value, attr, NULL, token); + else + { + /* + * Multiple string values... + */ + + int num_values; /* Number of values */ + char *values[100], /* Values */ + *ptr; /* Pointer to next value */ + + + values[0] = token; + num_values = 1; + + for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ',')) + { + *ptr++ = '\0'; + values[num_values] = ptr; + num_values ++; + } + + ippAddStrings(request, group, value, attr, num_values, + NULL, (const char **)values); + } + break; + } + } + else if (!_cups_strcasecmp(token, "FILE")) + { + /* + * File... + */ + + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing FILE filename on line %d.", linenum); + pass = 0; + goto test_exit; + } + + expand_variables(vars, token, temp, sizeof(token)); + get_filename(testfile, filename, token, sizeof(filename)); + } + else if (!_cups_strcasecmp(token, "STATUS")) + { + /* + * Status... + */ + + if (num_statuses >= (int)(sizeof(statuses) / sizeof(statuses[0]))) + { + print_fatal_error("Too many STATUS's on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing STATUS code on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((statuses[num_statuses].status = ippErrorValue(token)) + == (ipp_status_t)-1 && + (statuses[num_statuses].status = strtol(token, NULL, 0)) == 0) + { + print_fatal_error("Bad STATUS code \"%s\" on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + + last_status = statuses + num_statuses; + num_statuses ++; + + last_status->if_defined = NULL; + last_status->if_not_defined = NULL; + } + else if (!_cups_strcasecmp(token, "EXPECT")) + { + /* + * Expected attributes... + */ + + if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0]))) + { + print_fatal_error("Too many EXPECT's on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing EXPECT name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + last_expect = expects + num_expects; + num_expects ++; + + memset(last_expect, 0, sizeof(_cups_expect_t)); + + if (token[0] == '!') + { + last_expect->not_expect = 1; + last_expect->name = strdup(token + 1); + } + else if (token[0] == '?') + { + last_expect->optional = 1; + last_expect->name = strdup(token + 1); + } + else + last_expect->name = strdup(token); + } + else if (!_cups_strcasecmp(token, "COUNT")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing COUNT number on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((i = atoi(token)) <= 0) + { + print_fatal_error("Bad COUNT \"%s\" on line %d.", token, linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->count = i; + else + { + print_fatal_error("COUNT without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DEFINE-MATCH")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DEFINE-MATCH variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->define_match = strdup(token); + else + { + print_fatal_error("DEFINE-MATCH without a preceding EXPECT on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DEFINE-NO-MATCH variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->define_no_match = strdup(token); + else + { + print_fatal_error("DEFINE-NO-MATCH without a preceding EXPECT on " + "line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DEFINE-VALUE")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DEFINE-VALUE variable on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->define_value = strdup(token); + else + { + print_fatal_error("DEFINE-VALUE without a preceding EXPECT on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "OF-TYPE")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing OF-TYPE value tag(s) on line %d.", + linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->of_type = strdup(token); + else + { + print_fatal_error("OF-TYPE without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "IN-GROUP")) + { + ipp_tag_t in_group; /* IN-GROUP value */ + + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IN-GROUP group tag on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if ((in_group = ippTagValue(token)) == (ipp_tag_t)-1) + { + } + else if (last_expect) + last_expect->in_group = in_group; + else + { + print_fatal_error("IN-GROUP without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "SAME-COUNT-AS")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing SAME-COUNT-AS name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->same_count_as = strdup(token); + else + { + print_fatal_error("SAME-COUNT-AS without a preceding EXPECT on line " + "%d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "IF-DEFINED")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IF-DEFINED name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->if_defined = strdup(token); + else if (last_status) + last_status->if_defined = strdup(token); + else + { + print_fatal_error("IF-DEFINED without a preceding EXPECT or STATUS " + "on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED")) + { + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing IF-NOT-DEFINED name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + last_expect->if_not_defined = strdup(token); + else if (last_status) + last_status->if_not_defined = strdup(token); + else + { + print_fatal_error("IF-NOT-DEFINED without a preceding EXPECT or STATUS " + "on line %d.", linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "WITH-VALUE")) + { + if (!get_token(fp, temp, sizeof(temp), &linenum)) + { + print_fatal_error("Missing WITH-VALUE value on line %d.", linenum); + pass = 0; + goto test_exit; + } + + if (last_expect) + { + /* + * Expand any variables in the value and then save it. + */ + + expand_variables(vars, token, temp, sizeof(token)); + + tokenptr = token + strlen(token) - 1; + + if (token[0] == '/' && tokenptr > token && *tokenptr == '/') + { + /* + * WITH-VALUE is a POSIX extended regular expression. + */ + + last_expect->with_value = calloc(1, tokenptr - token); + last_expect->with_regex = 1; + + if (last_expect->with_value) + memcpy(last_expect->with_value, token + 1, tokenptr - token - 1); + } + else + { + /* + * WITH-VALUE is a literal value... + */ + + last_expect->with_value = strdup(token); + } + } + else + { + print_fatal_error("WITH-VALUE without a preceding EXPECT on line %d.", + linenum); + pass = 0; + goto test_exit; + } + } + else if (!_cups_strcasecmp(token, "DISPLAY")) + { + /* + * Display attributes... + */ + + if (num_displayed >= (int)(sizeof(displayed) / sizeof(displayed[0]))) + { + print_fatal_error("Too many DISPLAY's on line %d", linenum); + pass = 0; + goto test_exit; + } + + if (!get_token(fp, token, sizeof(token), &linenum)) + { + print_fatal_error("Missing DISPLAY name on line %d.", linenum); + pass = 0; + goto test_exit; + } + + displayed[num_displayed] = strdup(token); + num_displayed ++; + } + else + { + print_fatal_error("Unexpected token %s seen on line %d.", token, + linenum); + pass = 0; + goto test_exit; + } + } + + /* + * Submit the IPP request... + */ + + request->request.op.version[0] = version / 10; + request->request.op.version[1] = version % 10; + request->request.op.operation_id = op; + request->request.op.request_id = request_id; + + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<dict>"); + puts("<key>Name</key>"); + print_xml_string("string", name); + puts("<key>Operation</key>"); + print_xml_string("string", ippOpString(op)); + puts("<key>RequestAttributes</key>"); + puts("<array>"); + if (request->attrs) + { + puts("<dict>"); + for (attrptr = request->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + print_attr(attrptr, &group); + puts("</dict>"); + } + puts("</array>"); + } + else if (Output == _CUPS_OUTPUT_TEST) + { + if (Verbosity) + { + printf(" %s:\n", ippOpString(op)); + + for (attrptr = request->attrs; attrptr; attrptr = attrptr->next) + print_attr(attrptr, NULL); + } + + printf(" %-69.69s [", name); + fflush(stdout); + } + + if ((skip_previous && !prev_pass) || skip_test) + { + ippDelete(request); + request = NULL; + + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<key>Successful</key>"); + puts("<true />"); + puts("<key>StatusCode</key>"); + print_xml_string("string", "skip"); + puts("<key>ResponseAttributes</key>"); + puts("<dict>"); + puts("</dict>"); + } + else if (Output == _CUPS_OUTPUT_TEST) + puts("SKIP]"); + + goto skip_error; + } + + if (transfer == _CUPS_TRANSFER_CHUNKED || + (transfer == _CUPS_TRANSFER_AUTO && filename[0])) + { + /* + * Send request using chunking... + */ + + http_status_t status = cupsSendRequest(http, request, resource, 0); + + if (status == HTTP_CONTINUE && filename[0]) + { + int fd; /* File to send */ + char buffer[8192]; /* Copy buffer */ + ssize_t bytes; /* Bytes read/written */ + + if ((fd = open(filename, O_RDONLY | O_BINARY)) >= 0) + { + while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) + if ((status = cupsWriteRequestData(http, buffer, + bytes)) != HTTP_CONTINUE) + break; + } + else + { + snprintf(buffer, sizeof(buffer), "%s: %s", filename, strerror(errno)); + _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0); + + status = HTTP_ERROR; + } + } + + ippDelete(request); + + if (status == HTTP_CONTINUE) + response = cupsGetResponse(http, resource); + else + response = NULL; + } + else if (filename[0]) + response = cupsDoFileRequest(http, request, resource, filename); + else + response = cupsDoRequest(http, request, resource); + + request = NULL; + prev_pass = 1; + + if (!response) + prev_pass = pass = 0; + else + { + if (http->version != HTTP_1_1) + prev_pass = pass = 0; + + if (response->request.status.request_id != request_id) + prev_pass = pass = 0; + + if (version && + (response->request.status.version[0] != (version / 10) || + response->request.status.version[1] != (version % 10))) + prev_pass = pass = 0; + + if ((attrptr = ippFindAttribute(response, "job-id", + IPP_TAG_INTEGER)) != NULL) + { + snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer); + set_variable(vars, "job-id", temp); + } + + if ((attrptr = ippFindAttribute(response, "job-uri", + IPP_TAG_URI)) != NULL) + set_variable(vars, "job-uri", attrptr->values[0].string.text); + + if ((attrptr = ippFindAttribute(response, "notify-subscription-id", + IPP_TAG_INTEGER)) != NULL) + { + snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer); + set_variable(vars, "notify-subscription-id", temp); + } + + attrptr = response->attrs; + if (!attrptr || !attrptr->name || + attrptr->value_tag != IPP_TAG_CHARSET || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-charset")) + prev_pass = pass = 0; + + if (attrptr) + { + attrptr = attrptr->next; + if (!attrptr || !attrptr->name || + attrptr->value_tag != IPP_TAG_LANGUAGE || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-natural-language")) + prev_pass = pass = 0; + } + + if ((attrptr = ippFindAttribute(response, "status-message", + IPP_TAG_ZERO)) != NULL && + (attrptr->value_tag != IPP_TAG_TEXT || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 255))) + prev_pass = pass = 0; + + if ((attrptr = ippFindAttribute(response, "detailed-status-message", + IPP_TAG_ZERO)) != NULL && + (attrptr->value_tag != IPP_TAG_TEXT || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 1023))) + prev_pass = pass = 0; + + for (attrptr = response->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + { + if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO) + { + prev_pass = pass = 0; + break; + } + + if (!validate_attr(attrptr, 0)) + { + prev_pass = pass = 0; + break; + } + } + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_not_defined && + get_variable(vars, statuses[i].if_not_defined)) + continue; + + if (response->request.status.status_code == statuses[i].status) + break; + } + + if (i == num_statuses && num_statuses > 0) + prev_pass = pass = 0; + else + { + for (i = num_expects, expect = expects; i > 0; i --, expect ++) + { + if (expect->if_defined && !get_variable(vars, expect->if_defined)) + continue; + + if (expect->if_not_defined && + get_variable(vars, expect->if_not_defined)) + continue; + + found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); + + if ((found && expect->not_expect) || + (!found && !(expect->not_expect || expect->optional)) || + (found && !expect_matches(expect, found->value_tag)) || + (found && expect->in_group && + found->group_tag != expect->in_group)) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match) + prev_pass = pass = 0; + + continue; + } + + if (found && + !with_value(expect->with_value, expect->with_regex, found, 0)) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match) + prev_pass = pass = 0; + + continue; + } + + if (found && expect->count > 0 && found->num_values != expect->count) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match) + prev_pass = pass = 0; + + continue; + } + + if (found && expect->same_count_as) + { + attrptr = ippFindAttribute(response, expect->same_count_as, + IPP_TAG_ZERO); + + if (!attrptr || attrptr->num_values != found->num_values) + { + if (expect->define_no_match) + set_variable(vars, expect->define_no_match, "1"); + else if (!expect->define_match) + prev_pass = pass = 0; + + continue; + } + } + + if (found && expect->define_match) + set_variable(vars, expect->define_match, "1"); + + if (found && expect->define_value) + { + _ippAttrString(found, token, sizeof(token)); + set_variable(vars, expect->define_value, token); + } + } + } + } + + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<key>Successful</key>"); + puts(prev_pass ? "<true />" : "<false />"); + puts("<key>StatusCode</key>"); + print_xml_string("string", ippErrorString(cupsLastError())); + puts("<key>ResponseAttributes</key>"); + puts("<array>"); + puts("<dict>"); + for (attrptr = response ? response->attrs : NULL, + group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO; + attrptr; + attrptr = attrptr->next) + print_attr(attrptr, &group); + puts("</dict>"); + puts("</array>"); + } + else if (Output == _CUPS_OUTPUT_TEST) + { + puts(prev_pass ? "PASS]" : "FAIL]"); + + if (Verbosity && response) + { + printf(" RECEIVED: %lu bytes in response\n", + (unsigned long)ippLength(response)); + printf(" status-code = %x (%s)\n", cupsLastError(), + ippErrorString(cupsLastError())); + + for (attrptr = response->attrs; + attrptr != NULL; + attrptr = attrptr->next) + { + print_attr(attrptr, NULL); + } + } + } + else if (!prev_pass) + fprintf(stderr, "%s\n", cupsLastErrorString()); + + if (prev_pass && Output != _CUPS_OUTPUT_PLIST && + Output != _CUPS_OUTPUT_QUIET && !Verbosity && num_displayed > 0) + { + if (Output >= _CUPS_OUTPUT_LIST) + { + size_t width; /* Length of value */ + + + for (i = 0; i < num_displayed; i ++) + { + widths[i] = strlen(displayed[i]); + + for (attrptr = ippFindAttribute(response, displayed[i], IPP_TAG_ZERO); + attrptr; + attrptr = ippFindNextAttribute(response, displayed[i], + IPP_TAG_ZERO)) + { + width = _ippAttrString(attrptr, NULL, 0); + if (width > widths[i]) + widths[i] = width; + } + } + + if (Output == _CUPS_OUTPUT_CSV) + print_csv(NULL, num_displayed, displayed, widths); + else + print_line(NULL, num_displayed, displayed, widths); + + attrptr = response->attrs; + + while (attrptr) + { + while (attrptr && attrptr->group_tag <= IPP_TAG_OPERATION) + attrptr = attrptr->next; + + if (attrptr) + { + if (Output == _CUPS_OUTPUT_CSV) + print_csv(attrptr, num_displayed, displayed, widths); + else + print_line(attrptr, num_displayed, displayed, widths); + + while (attrptr && attrptr->group_tag > IPP_TAG_OPERATION) + attrptr = attrptr->next; + } + } + } + else + { + for (attrptr = response->attrs; + attrptr != NULL; + attrptr = attrptr->next) + { + if (attrptr->name) + { + for (i = 0; i < num_displayed; i ++) + { + if (!strcmp(displayed[i], attrptr->name)) + { + print_attr(attrptr, NULL); + break; + } + } + } + } + } + } + else if (!prev_pass) + { + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<key>Errors</key>"); + puts("<array>"); + } + + if (http->version != HTTP_1_1) + print_test_error("Bad HTTP version (%d.%d)", http->version / 100, + http->version % 100); + + if (!response) + print_test_error("IPP request failed with status %s (%s)", + ippErrorString(cupsLastError()), + cupsLastErrorString()); + else + { + if (version && + (response->request.status.version[0] != (version / 10) || + response->request.status.version[1] != (version % 10))) + print_test_error("Bad version %d.%d in response - expected %d.%d " + "(RFC 2911 section 3.1.8).", + response->request.status.version[0], + response->request.status.version[1], + version / 10, version % 10); + + if (response->request.status.request_id != request_id) + print_test_error("Bad request ID %d in response - expected %d " + "(RFC 2911 section 3.1.1)", + response->request.status.request_id, request_id); + + attrptr = response->attrs; + if (!attrptr) + print_test_error("Missing first attribute \"attributes-charset " + "(charset)\" in group operation-attributes-tag " + "(RFC 2911 section 3.1.4)."); + else + { + if (!attrptr->name || + attrptr->value_tag != IPP_TAG_CHARSET || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-charset")) + print_test_error("Bad first attribute \"%s (%s%s)\" in group %s, " + "expected \"attributes-charset (charset)\" in " + "group operation-attributes-tag (RFC 2911 section " + "3.1.4).", + attrptr->name ? attrptr->name : "(null)", + attrptr->num_values > 1 ? "1setOf " : "", + ippTagString(attrptr->value_tag), + ippTagString(attrptr->group_tag)); + + attrptr = attrptr->next; + if (!attrptr) + print_test_error("Missing second attribute \"attributes-natural-" + "language (naturalLanguage)\" in group " + "operation-attributes-tag (RFC 2911 section " + "3.1.4)."); + else if (!attrptr->name || + attrptr->value_tag != IPP_TAG_LANGUAGE || + attrptr->group_tag != IPP_TAG_OPERATION || + attrptr->num_values != 1 || + strcmp(attrptr->name, "attributes-natural-language")) + print_test_error("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).", + attrptr->name ? attrptr->name : "(null)", + attrptr->num_values > 1 ? "1setOf " : "", + ippTagString(attrptr->value_tag), + ippTagString(attrptr->group_tag)); + } + + if ((attrptr = ippFindAttribute(response, "status-message", + IPP_TAG_ZERO)) != NULL) + { + if (attrptr->value_tag != IPP_TAG_TEXT) + print_test_error("status-message (text(255)) has wrong value tag " + "%s (RFC 2911 section 3.1.6.2).", + ippTagString(attrptr->value_tag)); + if (attrptr->group_tag != IPP_TAG_OPERATION) + print_test_error("status-message (text(255)) has wrong group tag " + "%s (RFC 2911 section 3.1.6.2).", + ippTagString(attrptr->group_tag)); + if (attrptr->num_values != 1) + print_test_error("status-message (text(255)) has %d values " + "(RFC 2911 section 3.1.6.2).", + attrptr->num_values); + if (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 255) + print_test_error("status-message (text(255)) has bad length %d" + " (RFC 2911 section 3.1.6.2).", + (int)strlen(attrptr->values[0].string.text)); + } + + if ((attrptr = ippFindAttribute(response, "detailed-status-message", + IPP_TAG_ZERO)) != NULL) + { + if (attrptr->value_tag != IPP_TAG_TEXT) + print_test_error("detailed-status-message (text(MAX)) has wrong " + "value tag %s (RFC 2911 section 3.1.6.3).", + ippTagString(attrptr->value_tag)); + if (attrptr->group_tag != IPP_TAG_OPERATION) + print_test_error("detailed-status-message (text(MAX)) has wrong " + "group tag %s (RFC 2911 section 3.1.6.3).", + ippTagString(attrptr->group_tag)); + if (attrptr->num_values != 1) + print_test_error("detailed-status-message (text(MAX)) has %d values" + " (RFC 2911 section 3.1.6.3).", + attrptr->num_values); + if (attrptr->value_tag == IPP_TAG_TEXT && + strlen(attrptr->values[0].string.text) > 1023) + print_test_error("detailed-status-message (text(MAX)) has bad " + "length %d (RFC 2911 section 3.1.6.3).", + (int)strlen(attrptr->values[0].string.text)); + } + + for (attrptr = response->attrs, group = attrptr->group_tag; + attrptr; + attrptr = attrptr->next) + { + if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO) + print_test_error("Attribute groups out of order (%s < %s)", + ippTagString(attrptr->group_tag), + ippTagString(group)); + + validate_attr(attrptr, 1); + } + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_not_defined && + get_variable(vars, statuses[i].if_not_defined)) + continue; + + if (response->request.status.status_code == statuses[i].status) + break; + } + + if (i == num_statuses && num_statuses > 0) + { + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined && + !get_variable(vars, statuses[i].if_defined)) + continue; + + if (statuses[i].if_not_defined && + get_variable(vars, statuses[i].if_not_defined)) + continue; + + print_test_error("EXPECTED: STATUS %s (got %s)", + ippErrorString(statuses[i].status), + ippErrorString(cupsLastError())); + } + + if ((attrptr = ippFindAttribute(response, "status-message", + IPP_TAG_TEXT)) != NULL) + print_test_error("status-message=\"%s\"", + attrptr->values[0].string.text); + } + + for (i = num_expects, expect = expects; i > 0; i --, expect ++) + { + if (expect->define_match || expect->define_no_match) + continue; + + if (expect->if_defined && !get_variable(vars, expect->if_defined)) + continue; + + if (expect->if_not_defined && + get_variable(vars, expect->if_not_defined)) + continue; + + found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO); + + if (found && expect->not_expect) + print_test_error("NOT EXPECTED: %s", expect->name); + else if (!found && !(expect->not_expect || expect->optional)) + print_test_error("EXPECTED: %s", expect->name); + else if (found) + { + if (!expect_matches(expect, found->value_tag)) + print_test_error("EXPECTED: %s OF-TYPE %s (got %s)", + expect->name, expect->of_type, + ippTagString(found->value_tag)); + + if (expect->in_group && found->group_tag != expect->in_group) + print_test_error("EXPECTED: %s IN-GROUP %s (got %s).", + expect->name, ippTagString(expect->in_group), + ippTagString(found->group_tag)); + + if (!with_value(expect->with_value, expect->with_regex, found, 0)) + { + if (expect->with_regex) + print_test_error("EXPECTED: %s WITH-VALUE /%s/", + expect->name, expect->with_value); + else + print_test_error("EXPECTED: %s WITH-VALUE \"%s\"", + expect->name, expect->with_value); + + with_value(expect->with_value, expect->with_regex, found, 1); + } + + if (expect->count > 0 && found->num_values != expect->count) + { + print_test_error("EXPECTED: %s COUNT %d (got %d)", expect->name, + expect->count, found->num_values); + } + + if (expect->same_count_as) + { + attrptr = ippFindAttribute(response, expect->same_count_as, + IPP_TAG_ZERO); + + if (!attrptr) + print_test_error("EXPECTED: %s (%d values) SAME-COUNT-AS %s " + "(not returned)", expect->name, + found->num_values, expect->same_count_as); + else if (attrptr->num_values != found->num_values) + print_test_error("EXPECTED: %s (%d values) SAME-COUNT-AS %s " + "(%d values)", expect->name, found->num_values, + expect->same_count_as, attrptr->num_values); + } + } + } + } + + if (Output == _CUPS_OUTPUT_PLIST) + puts("</array>"); + } + + skip_error: + + if (Output == _CUPS_OUTPUT_PLIST) + puts("</dict>"); + + ippDelete(response); + response = NULL; + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined) + free(statuses[i].if_defined); + if (statuses[i].if_not_defined) + free(statuses[i].if_not_defined); + } + num_statuses = 0; + + for (i = num_expects, expect = 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); + } + num_expects = 0; + + for (i = 0; i < num_displayed; i ++) + free(displayed[i]); + num_displayed = 0; + + if (!ignore_errors && !prev_pass) + break; + } + + test_exit: + + if (fp) + fclose(fp); + + httpClose(http); + ippDelete(request); + ippDelete(response); + + for (i = 0; i < num_statuses; i ++) + { + if (statuses[i].if_defined) + free(statuses[i].if_defined); + if (statuses[i].if_not_defined) + free(statuses[i].if_not_defined); + } + + for (i = num_expects, expect = 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); + } + + for (i = 0; i < num_displayed; i ++) + free(displayed[i]); + + return (pass); +} + + +/* + * 'expand_variables()' - Expand variables in a string. + */ + +static void +expand_variables(_cups_vars_t *vars, /* I - Variables */ + char *dst, /* I - Destination string buffer */ + const char *src, /* I - Source string */ + size_t dstsize) /* I - Size of destination buffer */ +{ + char *dstptr, /* Pointer into destination */ + *dstend, /* End of destination */ + temp[256], /* Temporary string */ + *tempptr; /* Pointer into temporary string */ + const char *value; /* Value to substitute */ + + + dstptr = dst; + dstend = dst + dstsize - 1; + + while (*src && dstptr < dstend) + { + if (*src == '$') + { + /* + * Substitute a string/number... + */ + + if (!strncmp(src, "$$", 2)) + { + value = "$"; + src += 2; + } + else if (!strncmp(src, "$ENV[", 5)) + { + strlcpy(temp, src + 5, sizeof(temp)); + + for (tempptr = temp; *tempptr; tempptr ++) + if (*tempptr == ']') + break; + + if (*tempptr) + *tempptr++ = '\0'; + + value = getenv(temp); + src += tempptr - temp + 5; + } + else if (vars) + { + strlcpy(temp, src + 1, sizeof(temp)); + + for (tempptr = temp; *tempptr; tempptr ++) + if (!isalnum(*tempptr & 255) && *tempptr != '-' && *tempptr != '_') + break; + + if (*tempptr) + *tempptr = '\0'; + + if (!strcmp(temp, "uri")) + value = vars->uri; + else if (!strcmp(temp, "filename")) + value = vars->filename; + else if (!strcmp(temp, "scheme") || !strcmp(temp, "method")) + value = vars->scheme; + else if (!strcmp(temp, "username")) + value = vars->userpass; + else if (!strcmp(temp, "hostname")) + value = vars->hostname; + else if (!strcmp(temp, "port")) + { + snprintf(temp, sizeof(temp), "%d", vars->port); + value = temp; + } + else if (!strcmp(temp, "resource")) + value = vars->resource; + else if (!strcmp(temp, "user")) + value = cupsUser(); + else + value = get_variable(vars, temp); + + src += tempptr - temp + 1; + } + else + { + value = "$"; + src ++; + } + + if (value) + { + strlcpy(dstptr, value, dstend - dstptr + 1); + dstptr += strlen(dstptr); + } + } + else + *dstptr++ = *src++; + } + + *dstptr = '\0'; +} + + +/* + * '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_collection()' - Get a collection value from the current test file. + */ + +static ipp_t * /* O - Collection value */ +get_collection(_cups_vars_t *vars, /* I - Variables */ + FILE *fp, /* I - File to read from */ + int *linenum) /* IO - Line number */ +{ + char token[1024], /* Token from file */ + temp[1024], /* Temporary string */ + attr[128]; /* Attribute name */ + ipp_tag_t value; /* Current value type */ + ipp_t *col = ippNew(); /* Collection value */ + ipp_attribute_t *lastcol = NULL; /* Last collection attribute */ + + + while (get_token(fp, token, sizeof(token), linenum) != NULL) + { + if (!strcmp(token, "}")) + break; + else if (!strcmp(token, "{") && lastcol) + { + /* + * Another collection value + */ + + ipp_t *subcol = get_collection(vars, fp, linenum); + /* Collection value */ + + if (subcol) + { + ipp_attribute_t *tempcol; /* Pointer to new buffer */ + + + /* + * Reallocate memory... + */ + + if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) + + (lastcol->num_values + 1) * + sizeof(ipp_value_t))) == NULL) + { + print_fatal_error("Unable to allocate memory on line %d.", *linenum); + goto col_error; + } + + if (tempcol != lastcol) + { + /* + * Reset pointers in the list... + */ + + if (col->prev) + col->prev->next = tempcol; + else + col->attrs = tempcol; + + lastcol = col->current = col->last = tempcol; + } + + lastcol->values[lastcol->num_values].collection = subcol; + lastcol->num_values ++; + } + else + goto col_error; + } + else if (!_cups_strcasecmp(token, "MEMBER")) + { + /* + * Attribute... + */ + + lastcol = NULL; + + if (!get_token(fp, token, sizeof(token), linenum)) + { + print_fatal_error("Missing MEMBER value tag on line %d.", *linenum); + goto col_error; + } + + if ((value = ippTagValue(token)) == IPP_TAG_ZERO) + { + print_fatal_error("Bad MEMBER value tag \"%s\" on line %d.", token, + *linenum); + goto col_error; + } + + if (!get_token(fp, attr, sizeof(attr), linenum)) + { + print_fatal_error("Missing MEMBER name on line %d.", *linenum); + goto col_error; + } + + if (!get_token(fp, temp, sizeof(temp), linenum)) + { + print_fatal_error("Missing MEMBER value on line %d.", *linenum); + goto col_error; + } + + expand_variables(vars, token, temp, sizeof(token)); + + switch (value) + { + case IPP_TAG_BOOLEAN : + if (!_cups_strcasecmp(token, "true")) + ippAddBoolean(col, IPP_TAG_ZERO, attr, 1); + else + ippAddBoolean(col, IPP_TAG_ZERO, attr, atoi(token)); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + ippAddInteger(col, IPP_TAG_ZERO, value, attr, atoi(token)); + break; + + case IPP_TAG_RESOLUTION : + { + int xres, /* X resolution */ + yres; /* Y resolution */ + char units[6]; /* Units */ + + if (sscanf(token, "%dx%d%5s", &xres, &yres, units) != 3 || + (_cups_strcasecmp(units, "dpi") && _cups_strcasecmp(units, "dpc") && + _cups_strcasecmp(units, "other"))) + { + print_fatal_error("Bad resolution value \"%s\" on line %d.", + token, *linenum); + goto col_error; + } + + if (!_cups_strcasecmp(units, "dpi")) + ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres, + IPP_RES_PER_INCH); + else if (!_cups_strcasecmp(units, "dpc")) + ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres, + IPP_RES_PER_CM); + else + ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres, + (ipp_res_t)0); + } + break; + + case IPP_TAG_RANGE : + { + int lowers[4], /* Lower value */ + uppers[4], /* Upper values */ + num_vals; /* Number of values */ + + + num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d", + lowers + 0, uppers + 0, + lowers + 1, uppers + 1, + lowers + 2, uppers + 2, + lowers + 3, uppers + 3); + + if ((num_vals & 1) || num_vals == 0) + { + print_fatal_error("Bad rangeOfInteger value \"%s\" on line %d.", + token, *linenum); + goto col_error; + } + + ippAddRanges(col, IPP_TAG_ZERO, attr, num_vals / 2, lowers, + uppers); + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + if (!strcmp(token, "{")) + { + ipp_t *subcol = get_collection(vars, fp, linenum); + /* Collection value */ + + if (subcol) + { + lastcol = ippAddCollection(col, IPP_TAG_ZERO, attr, subcol); + ippDelete(subcol); + } + else + goto col_error; + } + else + { + print_fatal_error("Bad collection value on line %d.", *linenum); + goto col_error; + } + break; + + default : + if (!strchr(token, ',')) + ippAddString(col, IPP_TAG_ZERO, value, attr, NULL, token); + else + { + /* + * Multiple string values... + */ + + int num_values; /* Number of values */ + char *values[100], /* Values */ + *ptr; /* Pointer to next value */ + + + values[0] = token; + num_values = 1; + + for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ',')) + { + *ptr++ = '\0'; + values[num_values] = ptr; + num_values ++; + } + + ippAddStrings(col, IPP_TAG_ZERO, value, attr, num_values, + NULL, (const char **)values); + } + break; + } + } + } + + return (col); + + /* + * If we get here there was a parse error; free memory and return. + */ + + col_error: + + ippDelete(col); + + return (NULL); +} + + +/* + * '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 (*src == '/' || !strchr(testfile, '/')) + { + /* + * 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 - (dstptr - dst)); + } + + return (dst); +} + + +/* + * 'get_token()' - Get a token from a file. + */ + +static char * /* O - Token from file or NULL on EOF */ +get_token(FILE *fp, /* I - File to read from */ + char *buf, /* I - Buffer to read into */ + int buflen, /* I - Length of buffer */ + int *linenum) /* IO - Current line number */ +{ + int ch, /* Character from file */ + quote; /* Quoting character */ + char *bufptr, /* Pointer into buffer */ + *bufend; /* End of buffer */ + + + for (;;) + { + /* + * Skip whitespace... + */ + + while (isspace(ch = getc(fp))) + { + if (ch == '\n') + (*linenum) ++; + } + + /* + * Read a token... + */ + + if (ch == EOF) + return (NULL); + else if (ch == '\'' || ch == '\"') + { + /* + * Quoted text or regular expression... + */ + + quote = ch; + bufptr = buf; + bufend = buf + buflen - 1; + + while ((ch = getc(fp)) != EOF) + { + if (ch == '\\') + { + /* + * Escape next character... + */ + + if (bufptr < bufend) + *bufptr++ = ch; + + if ((ch = getc(fp)) != EOF && bufptr < bufend) + *bufptr++ = ch; + } + else if (ch == quote) + break; + else if (bufptr < bufend) + *bufptr++ = ch; + } + + *bufptr = '\0'; + + return (buf); + } + else if (ch == '#') + { + /* + * Comment... + */ + + while ((ch = getc(fp)) != EOF) + if (ch == '\n') + break; + + (*linenum) ++; + } + else + { + /* + * Whitespace delimited text... + */ + + ungetc(ch, fp); + + bufptr = buf; + bufend = buf + buflen - 1; + + while ((ch = getc(fp)) != EOF) + if (isspace(ch) || ch == '#') + break; + else if (bufptr < bufend) + *bufptr++ = ch; + + if (ch == '#') + ungetc(ch, fp); + else if (ch == '\n') + (*linenum) ++; + + *bufptr = '\0'; + + return (buf); + } + } +} + + +/* + * 'get_variable()' - Get the value of a variable. + */ + +static char * /* O - Value or NULL */ +get_variable(_cups_vars_t *vars, /* I - Variables */ + const char *name) /* I - Variable name */ +{ + _cups_var_t key, /* Search key */ + *match; /* Matching variable, if any */ + + + key.name = (char *)name; + match = cupsArrayFind(vars->vars, &key); + + return (match ? match->value : NULL); +} + + +/* + * '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(ipp_uchar_t *date) /* I - IPP (RFC 1903) date/time value */ +{ + unsigned year = (date[0] << 8) + date[1]; + /* Year */ + static char buffer[255]; /* String buffer */ + + + if (date[9] == 0 && date[10] == 0) + snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02uZ", + year, date[2], date[3], date[4], date[5], date[6]); + else + snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02u%c%02u%02u", + year, date[2], date[3], date[4], date[5], date[6], + date[8], date[9], date[10]); + + return (buffer); +} + + +/* + * 'password_cb()' - Password callback for authenticated tests. + */ + +static const char * /* O - Password */ +password_cb(const char *prompt) /* I - Prompt (unused) */ +{ + (void)prompt; + + return (Password); +} + + +/* + * 'print_attr()' - Print an attribute on the screen. + */ + +static void +print_attr(ipp_attribute_t *attr, /* I - Attribute to print */ + ipp_tag_t *group) /* IO - Current group */ +{ + int i; /* Looping var */ + ipp_attribute_t *colattr; /* Collection attribute */ + + + if (Output == _CUPS_OUTPUT_PLIST) + { + if (!attr->name || (group && *group != attr->group_tag)) + { + puts("</dict>"); + puts("<dict>"); + + if (group) + *group = attr->group_tag; + } + + if (!attr->name) + return; + + print_xml_string("key", attr->name); + if (attr->num_values > 1) + puts("<array>"); + } + else if (Output == _CUPS_OUTPUT_TEST) + { + if (!attr->name) + { + puts(" -- separator --"); + return; + } + + printf(" %s (%s%s) = ", attr->name, + attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag)); + } + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<integer>%d</integer>\n", attr->values[i].integer); + else + printf("%d ", attr->values[i].integer); + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + puts(attr->values[i].boolean ? "<true />" : "<false />"); + else if (attr->values[i].boolean) + fputs("true ", stdout); + else + fputs("false ", stdout); + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<dict><key>lower</key><integer>%d</integer>" + "<key>upper</key><integer>%d</integer></dict>\n", + attr->values[i].range.lower, attr->values[i].range.upper); + else + printf("%d-%d ", attr->values[i].range.lower, + attr->values[i].range.upper); + break; + + case IPP_TAG_RESOLUTION : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<dict><key>xres</key><integer>%d</integer>" + "<key>yres</key><integer>%d</integer>" + "<key>units</key><string>%s</string></dict>\n", + attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + else + printf("%dx%d%s ", attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + break; + + case IPP_TAG_DATE : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + printf("<date>%s</date>\n", iso_date(attr->values[i].date)); + else + printf("%s ", iso_date(attr->values[i].date)); + break; + + case IPP_TAG_STRING : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_CHARSET : + case IPP_TAG_URI : + case IPP_TAG_MIMETYPE : + case IPP_TAG_LANGUAGE : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + print_xml_string("string", attr->values[i].string.text); + else + printf("\"%s\" ", attr->values[i].string.text); + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + for (i = 0; i < attr->num_values; i ++) + if (Output == _CUPS_OUTPUT_PLIST) + { + fputs("<dict><key>language</key><string>", stdout); + print_xml_string(NULL, attr->values[i].string.charset); + fputs("</string><key>string</key><string>", stdout); + print_xml_string(NULL, attr->values[i].string.text); + puts("</string></dict>"); + } + else + printf("\"%s\"(%s) ", attr->values[i].string.text, + attr->values[i].string.charset); + break; + + case IPP_TAG_BEGIN_COLLECTION : + for (i = 0; i < attr->num_values; i ++) + { + if (Output == _CUPS_OUTPUT_PLIST) + { + puts("<dict>"); + for (colattr = attr->values[i].collection->attrs; + colattr; + colattr = colattr->next) + print_attr(colattr, NULL); + puts("</dict>"); + } + else + { + if (i) + putchar(' '); + + print_col(attr->values[i].collection); + } + } + break; + + default : + if (Output == _CUPS_OUTPUT_PLIST) + printf("<string><<%s>></string>\n", + ippTagString(attr->value_tag)); + else + fputs(ippTagString(attr->value_tag), stdout); + break; + } + + if (Output == _CUPS_OUTPUT_PLIST) + { + if (attr->num_values > 1) + puts("</array>"); + } + else + putchar('\n'); +} + + +/* + * 'print_col()' - Print a collection attribute on the screen. + */ + +static void +print_col(ipp_t *col) /* I - Collection attribute to print */ +{ + int i; /* Looping var */ + ipp_attribute_t *attr; /* Current attribute in collection */ + + + fputs("{ ", stdout); + for (attr = col->attrs; attr; attr = attr->next) + { + printf("%s (%s%s) = ", attr->name, attr->num_values > 1 ? "1setOf " : "", + ippTagString(attr->value_tag)); + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + printf("%d ", attr->values[i].integer); + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + if (attr->values[i].boolean) + printf("true "); + else + printf("false "); + break; + + case IPP_TAG_NOVALUE : + printf("novalue"); + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + printf("%d-%d ", attr->values[i].range.lower, + attr->values[i].range.upper); + break; + + case IPP_TAG_RESOLUTION : + for (i = 0; i < attr->num_values; i ++) + printf("%dx%d%s ", attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + break; + + case IPP_TAG_STRING : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_CHARSET : + case IPP_TAG_URI : + case IPP_TAG_MIMETYPE : + case IPP_TAG_LANGUAGE : + for (i = 0; i < attr->num_values; i ++) + printf("\"%s\" ", attr->values[i].string.text); + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + for (i = 0; i < attr->num_values; i ++) + printf("\"%s\",%s ", attr->values[i].string.text, + attr->values[i].string.charset); + break; + + case IPP_TAG_BEGIN_COLLECTION : + for (i = 0; i < attr->num_values; i ++) + { + print_col(attr->values[i].collection); + putchar(' '); + } + break; + + default : + break; /* anti-compiler-warning-code */ + } + } + + putchar('}'); +} + + +/* + * 'print_csv()' - Print a line of CSV text. + */ + +static void +print_csv( + 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) + putchar(','); + + buffer[0] = '\0'; + + for (current = attr; current; current = current->next) + { + if (!current->name) + break; + else if (!strcmp(current->name, displayed[i])) + { + _ippAttrString(current, buffer, maxlength); + break; + } + } + + if (strchr(buffer, ',') != NULL || strchr(buffer, '\"') != NULL || + strchr(buffer, '\\') != NULL) + { + putchar('\"'); + for (bufptr = buffer; *bufptr; bufptr ++) + { + if (*bufptr == '\\' || *bufptr == '\"') + putchar('\\'); + putchar(*bufptr); + } + putchar('\"'); + } + else + fputs(buffer, stdout); + } + putchar('\n'); + } + else + { + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(','); + + fputs(displayed[i], stdout); + } + putchar('\n'); + } + + free(buffer); +} + + +/* + * 'print_fatal_error()' - Print a fatal error message. + */ + +static void +print_fatal_error(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 (Output == _CUPS_OUTPUT_PLIST) + { + print_xml_header(); + print_xml_trailer(0, buffer); + } + else + _cupsLangPrintf(stderr, "ipptool: %s", buffer); +} + + +/* + * 'print_line()' - Print a line of formatted or CSV text. + */ + +static void +print_line( + 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) + putchar(' '); + + buffer[0] = '\0'; + + for (current = attr; current; current = current->next) + { + if (!current->name) + break; + else if (!strcmp(current->name, displayed[i])) + { + _ippAttrString(current, buffer, maxlength); + break; + } + } + + printf("%*s", (int)-widths[i], buffer); + } + putchar('\n'); + } + else + { + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(' '); + + printf("%*s", (int)-widths[i], displayed[i]); + } + putchar('\n'); + + for (i = 0; i < num_displayed; i ++) + { + if (i) + putchar(' '); + + memset(buffer, '-', widths[i]); + buffer[widths[i]] = '\0'; + fputs(buffer, stdout); + } + putchar('\n'); + } + + free(buffer); +} + + +/* + * 'print_test_error()' - Print a test error message. + */ + +static void +print_test_error(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 (Output == _CUPS_OUTPUT_PLIST) + print_xml_string("string", buffer); + else + printf(" %s\n", buffer); +} + + +/* + * 'print_xml_header()' - Print a standard XML plist header. + */ + +static void +print_xml_header(void) +{ + if (!XMLHeader) + { + puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + puts("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); + puts("<plist version=\"1.0\">"); + puts("<dict>"); + puts("<key>Transfer</key>"); + printf("<string>%s</string>\n", + Transfer == _CUPS_TRANSFER_AUTO ? "auto" : + Transfer == _CUPS_TRANSFER_CHUNKED ? "chunked" : "length"); + puts("<key>Tests</key>"); + puts("<array>"); + + XMLHeader = 1; + } +} + + +/* + * 'print_xml_string()' - Print an XML string with escaping. + */ + +static void +print_xml_string(const char *element, /* I - Element name or NULL */ + const char *s) /* I - String to print */ +{ + if (element) + printf("<%s>", element); + + while (*s) + { + if (*s == '&') + fputs("&", stdout); + else if (*s == '<') + fputs("<", stdout); + else if (*s == '>') + fputs(">", stdout); + else + putchar(*s); + + s ++; + } + + if (element) + printf("</%s>\n", element); +} + + +/* + * 'print_xml_trailer()' - Print the XML trailer with success/fail value. + */ + +static void +print_xml_trailer(int success, /* I - 1 on success, 0 on failure */ + const char *message) /* I - Error message or NULL */ +{ + if (XMLHeader) + { + puts("</array>"); + puts("<key>Successful</key>"); + puts(success ? "<true />" : "<false />"); + if (message) + { + puts("<key>ErrorMessage</key>"); + print_xml_string("string", message); + } + puts("</dict>"); + puts("</plist>"); + + XMLHeader = 0; + } +} + + +/* + * 'set_variable()' - Set a variable value. + */ + +static void +set_variable(_cups_vars_t *vars, /* I - Variables */ + const char *name, /* I - Variable name */ + const char *value) /* I - Value string */ +{ + _cups_var_t key, /* Search key */ + *var; /* New variable */ + + + key.name = (char *)name; + if ((var = cupsArrayFind(vars->vars, &key)) != NULL) + { + free(var->value); + var->value = strdup(value); + } + else if ((var = malloc(sizeof(_cups_var_t))) == NULL) + { + print_fatal_error("Unable to allocate memory for variable \"%s\".", name); + exit(1); + } + else + { + var->name = strdup(name); + var->value = strdup(value); + + cupsArrayAdd(vars->vars, var); + } +} + + +/* + * 'timeout_cb()' - Handle HTTP timeouts. + */ + +static int /* O - 1 to continue, 0 to cancel */ +timeout_cb(http_t *http, /* I - Connection to server (unused) */ + void *user_data) /* I - User data (unused) */ +{ + (void)http; + (void)user_data; + + return (0); +} + + +/* + * 'usage()' - Show program usage. + */ + +static void +usage(void) +{ + _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... " + "filenameN ]")); + _cupsLangPuts(stderr, _("Options:")); + _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 TLS " + "encryption.")); + _cupsLangPuts(stderr, _(" -I Ignore errors.")); + _cupsLangPuts(stderr, _(" -L Send requests using " + "content-length.")); + _cupsLangPuts(stderr, _(" -S Test with SSL " + "encryption.")); + _cupsLangPuts(stderr, _(" -T 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, _(" -d name=value Set named variable to " + "value.")); + _cupsLangPuts(stderr, _(" -f filename Set default request " + "filename.")); + _cupsLangPuts(stderr, _(" -i seconds Repeat the last file with " + "the given time interval.")); + _cupsLangPuts(stderr, _(" -n count Repeat the last file the " + "given number of times.")); + _cupsLangPuts(stderr, _(" -q Be quiet - no output " + "except errors.")); + _cupsLangPuts(stderr, _(" -t Produce a test report.")); + _cupsLangPuts(stderr, _(" -v Show all attributes sent " + "and received.")); + + exit(1); +} + + +/* + * 'validate_attr()' - Determine whether an attribute is valid. + */ + +static int /* O - 1 if valid, 0 otherwise */ +validate_attr(ipp_attribute_t *attr, /* I - Attribute to validate */ + int print) /* I - 1 = report issues to stdout */ +{ + int i; /* Looping var */ + char scheme[64], /* Scheme from URI */ + userpass[256], /* Username/password from URI */ + hostname[256], /* Hostname from URI */ + resource[1024]; /* Resource from URI */ + int port, /* Port number from URI */ + uri_status, /* URI separation status */ + valid = 1; /* Is the attribute valid? */ + const char *ptr; /* Pointer into string */ + ipp_attribute_t *colattr; /* Collection attribute */ + regex_t re; /* Regular expression */ + ipp_uchar_t *date; /* Current date value */ + + + /* + * Skip separators. + */ + + if (!attr->name) + return (1); + + /* + * Validate the attribute name. + */ + + for (ptr = attr->name; *ptr; ptr ++) + if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' && *ptr != '_') + break; + + if (*ptr || ptr == attr->name) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad attribute name - invalid character (RFC " + "2911 section 4.1.3).", attr->name); + } + + if ((ptr - attr->name) > 255) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad attribute name - bad length (RFC 2911 " + "section 4.1.3).", attr->name); + } + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].boolean != 0 && + attr->values[i].boolean != 1) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad boolen value %d (RFC 2911 section " + "4.1.10).", attr->name, attr->values[i].boolean); + else + break; + } + } + break; + + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].integer < 1) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad enum value %d - out of range " + "(RFC 2911 section 4.1.4).", attr->name, + attr->values[i].integer); + else + break; + } + } + break; + + case IPP_TAG_STRING : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].unknown.length > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad octetString value - bad length %d " + "(RFC 2911 section 4.1.10).", attr->name, + attr->values[i].unknown.length); + else + break; + } + } + break; + + case IPP_TAG_DATE : + for (i = 0; i < attr->num_values; i ++) + { + date = attr->values[i].date; + + if (date[2] < 1 || date[2] > 12) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime month %u (RFC 2911 " + "section 4.1.13).", attr->name, date[2]); + else + break; + } + + if (date[3] < 1 || date[3] > 31) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime day %u (RFC 2911 " + "section 4.1.13).", attr->name, date[3]); + else + break; + } + + if (date[4] > 23) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime hours %u (RFC 2911 " + "section 4.1.13).", attr->name, date[4]); + else + break; + } + + if (date[5] > 59) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime minutes %u (RFC 2911 " + "section 4.1.13).", attr->name, date[5]); + else + break; + } + + if (date[6] > 60) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime seconds %u (RFC 2911 " + "section 4.1.13).", attr->name, date[6]); + else + break; + } + + if (date[7] > 9) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime deciseconds %u (RFC 2911 " + "section 4.1.13).", attr->name, date[7]); + else + break; + } + + if (date[8] != '-' && date[8] != '+') + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime UTC sign '%c' (RFC 2911 " + "section 4.1.13).", attr->name, date[8]); + else + break; + } + + if (date[9] > 11) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime UTC hours %u (RFC 2911 " + "section 4.1.13).", attr->name, date[9]); + else + break; + } + + if (date[10] > 59) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad dateTime UTC minutes %u (RFC 2911 " + "section 4.1.13).", attr->name, date[10]); + else + break; + } + } + break; + + case IPP_TAG_RESOLUTION : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].resolution.xres <= 0) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad resolution value %dx%d%s - cross " + "feed resolution must be positive (RFC 2911 " + "section 4.1.13).", attr->name, + attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == + IPP_RES_PER_INCH ? "dpi" : + attr->values[i].resolution.units == + IPP_RES_PER_CM ? "dpc" : "unknown"); + else + break; + } + + if (attr->values[i].resolution.yres <= 0) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad resolution value %dx%d%s - feed " + "resolution must be positive (RFC 2911 section " + "4.1.13).", attr->name, + attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == + IPP_RES_PER_INCH ? "dpi" : + attr->values[i].resolution.units == + IPP_RES_PER_CM ? "dpc" : "unknown"); + else + break; + } + + if (attr->values[i].resolution.units != IPP_RES_PER_INCH && + attr->values[i].resolution.units != IPP_RES_PER_CM) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad resolution value %dx%d%s - bad " + "units value (RFC 2911 section 4.1.13).", + attr->name, attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == + IPP_RES_PER_INCH ? "dpi" : + attr->values[i].resolution.units == + IPP_RES_PER_CM ? "dpc" : "unknown"); + else + break; + } + } + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + { + if (attr->values[i].range.lower > attr->values[i].range.upper) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad rangeOfInteger value %d-%d - lower " + "greater than upper (RFC 2911 section 4.1.13).", + attr->name, attr->values[i].range.lower, + attr->values[i].range.upper); + else + break; + } + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + for (i = 0; i < attr->num_values; i ++) + { + for (colattr = attr->values[i].collection->attrs; + colattr; + colattr = colattr->next) + { + if (!validate_attr(colattr, 0)) + { + valid = 0; + break; + } + } + + if (colattr && print) + { + print_test_error("\"%s\": Bad collection value.", attr->name); + + while (colattr) + { + validate_attr(colattr, print); + colattr = colattr->next; + } + } + } + break; + + case IPP_TAG_TEXT : + case IPP_TAG_TEXTLANG : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + { + if ((*ptr & 0xe0) == 0xc0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf0) == 0xe0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf8) == 0xf0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if (*ptr & 0x80) + break; + } + + if (*ptr) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad text value \"%s\" - bad UTF-8 " + "sequence (RFC 2911 section 4.1.1).", attr->name, + attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad text value \"%s\" - bad length %d " + "(RFC 2911 section 4.1.1).", attr->name, + attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_NAME : + case IPP_TAG_NAMELANG : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + { + if ((*ptr & 0xe0) == 0xc0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf0) == 0xe0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if ((*ptr & 0xf8) == 0xf0) + { + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + ptr ++; + if ((*ptr & 0xc0) != 0x80) + break; + } + else if (*ptr & 0x80) + break; + } + + if (*ptr) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad name value \"%s\" - bad UTF-8 " + "sequence (RFC 2911 section 4.1.2).", attr->name, + attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad name value \"%s\" - bad length %d " + "(RFC 2911 section 4.1.2).", attr->name, + attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_KEYWORD : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' && + *ptr != '_') + break; + + if (*ptr || ptr == attr->values[i].string.text) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad keyword value \"%s\" - invalid " + "character (RFC 2911 section 4.1.3).", + attr->name, attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 255) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad keyword value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.3).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_URI : + for (i = 0; i < attr->num_values; i ++) + { + uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, + attr->values[i].string.text, + scheme, sizeof(scheme), + userpass, sizeof(userpass), + hostname, sizeof(hostname), + &port, resource, sizeof(resource)); + + if (uri_status < HTTP_URI_OK) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad URI value \"%s\" - %s " + "(RFC 2911 section 4.1.5).", attr->name, + attr->values[i].string.text, + URIStatusStrings[uri_status - + HTTP_URI_OVERFLOW]); + else + break; + } + + if (strlen(attr->values[i].string.text) > 1023) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad URI value \"%s\" - bad length %d " + "(RFC 2911 section 4.1.5).", attr->name, + attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_URISCHEME : + for (i = 0; i < attr->num_values; i ++) + { + ptr = attr->values[i].string.text; + if (islower(*ptr & 255)) + { + for (ptr ++; *ptr; ptr ++) + if (!islower(*ptr & 255) && !isdigit(*ptr & 255) && + *ptr != '+' && *ptr != '-' && *ptr != '.') + break; + } + + if (*ptr || ptr == attr->values[i].string.text) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad uriScheme value \"%s\" - bad " + "characters (RFC 2911 section 4.1.6).", + attr->name, attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 63) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad uriScheme value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.6).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_CHARSET : + for (i = 0; i < attr->num_values; i ++) + { + for (ptr = attr->values[i].string.text; *ptr; ptr ++) + if (!isprint(*ptr & 255) || isupper(*ptr & 255) || + isspace(*ptr & 255)) + break; + + if (*ptr || ptr == attr->values[i].string.text) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad charset value \"%s\" - bad " + "characters (RFC 2911 section 4.1.7).", + attr->name, attr->values[i].string.text); + else + break; + } + + if ((ptr - attr->values[i].string.text) > 40) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad charset value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.7).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + case IPP_TAG_LANGUAGE : + /* + * The following regular expression is derived from the ABNF for + * language tags in RFC 4646. All I can say is that this is the + * easiest way to check the values... + */ + + if ((i = regcomp(&re, + "^(" + "(([a-z]{2,3}(-[a-z][a-z][a-z]){0,3})|[a-z]{4,8})" + /* language */ + "(-[a-z][a-z][a-z][a-z]){0,1}" /* script */ + "(-([a-z][a-z]|[0-9][0-9][0-9])){0,1}" /* region */ + "(-([a-z]{5,8}|[0-9][0-9][0-9]))*" /* variant */ + "(-[a-wy-z](-[a-z0-9]{2,8})+)*" /* extension */ + "(-x(-[a-z0-9]{1,8})+)*" /* privateuse */ + "|" + "x(-[a-z0-9]{1,8})+" /* privateuse */ + "|" + "[a-z]{1,3}(-[a-z][0-9]{2,8}){1,2}" /* grandfathered */ + ")$", + REG_NOSUB | REG_EXTENDED)) != 0) + { + char temp[256]; /* Temporary error string */ + + regerror(i, &re, temp, sizeof(temp)); + print_fatal_error("Unable to compile naturalLanguage regular " + "expression: %s.", temp); + } + + for (i = 0; i < attr->num_values; i ++) + { + if (regexec(&re, attr->values[i].string.text, 0, NULL, 0)) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad naturalLanguage value \"%s\" - bad " + "characters (RFC 2911 section 4.1.8).", + attr->name, attr->values[i].string.text); + else + break; + } + + if (strlen(attr->values[i].string.text) > 63) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad naturalLanguage value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.8).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + + regfree(&re); + break; + + case IPP_TAG_MIMETYPE : + /* + * The following regular expression is derived from the ABNF for + * language tags in RFC 2045 and 4288. All I can say is that this is + * the easiest way to check the values... + */ + + if ((i = regcomp(&re, + "^" + "[-a-zA-Z0-9!#$&.+^_]{1,127}" /* type-name */ + "/" + "[-a-zA-Z0-9!#$&.+^_]{1,127}" /* subtype-name */ + "(;[-a-zA-Z0-9!#$&.+^_]{1,127}=" /* parameter= */ + "([-a-zA-Z0-9!#$&.+^_]{1,127}|\"[^\"]*\"))*" + /* value */ + "$", + REG_NOSUB | REG_EXTENDED)) != 0) + { + char temp[256]; /* Temporary error string */ + + regerror(i, &re, temp, sizeof(temp)); + print_fatal_error("Unable to compile mimeMediaType regular " + "expression: %s.", temp); + } + + for (i = 0; i < attr->num_values; i ++) + { + if (regexec(&re, attr->values[i].string.text, 0, NULL, 0)) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad mimeMediaType value \"%s\" - bad " + "characters (RFC 2911 section 4.1.9).", + attr->name, attr->values[i].string.text); + else + break; + } + + if (strlen(attr->values[i].string.text) > 255) + { + valid = 0; + + if (print) + print_test_error("\"%s\": Bad mimeMediaType value \"%s\" - bad " + "length %d (RFC 2911 section 4.1.9).", + attr->name, attr->values[i].string.text, + (int)strlen(attr->values[i].string.text)); + else + break; + } + } + break; + + default : + break; + } + + return (valid); +} + + +/* + * 'with_value()' - Test a WITH-VALUE predicate. + */ + +static int /* O - 1 on match, 0 on non-match */ +with_value(char *value, /* I - Value string */ + int regex, /* I - Value is a regular expression */ + ipp_attribute_t *attr, /* I - Attribute to compare */ + int report) /* I - 1 = report failures */ +{ + int i; /* Looping var */ + char *valptr; /* Pointer into value */ + + + /* + * NULL matches everything. + */ + + if (!value || !*value) + return (1); + + /* + * Compare the value string to the attribute value. + */ + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + for (i = 0; i < attr->num_values; i ++) + { + char op, /* Comparison operator */ + *nextptr; /* Next pointer */ + int intvalue; /* Integer value */ + + + valptr = value; + if (!strncmp(valptr, "no-value,", 9)) + valptr += 9; + + 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 = strtol(valptr, &nextptr, 0); + if (nextptr == valptr) + break; + valptr = nextptr; + + switch (op) + { + case '=' : + if (attr->values[i].integer == intvalue) + return (1); + break; + case '<' : + if (attr->values[i].integer < intvalue) + return (1); + break; + case '>' : + if (attr->values[i].integer > intvalue) + return (1); + break; + } + } + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=%d", attr->name, attr->values[i].integer); + } + break; + + case IPP_TAG_RANGE : + for (i = 0; i < attr->num_values; i ++) + { + char op, /* Comparison operator */ + *nextptr; /* Next pointer */ + int intvalue; /* Integer value */ + + + valptr = value; + if (!strncmp(valptr, "no-value,", 9)) + valptr += 9; + + 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 = strtol(valptr, &nextptr, 0); + if (nextptr == valptr) + break; + valptr = nextptr; + + switch (op) + { + case '=' : + if (attr->values[i].range.lower == intvalue || + attr->values[i].range.upper == intvalue) + return (1); + break; + case '<' : + if (attr->values[i].range.upper < intvalue) + return (1); + break; + case '>' : + if (attr->values[i].range.upper > intvalue) + return (1); + break; + } + } + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=%d-%d", attr->name, + attr->values[i].range.lower, + attr->values[i].range.upper); + } + break; + + case IPP_TAG_BOOLEAN : + for (i = 0; i < attr->num_values; i ++) + { + if (!strcmp(value, "true") == attr->values[i].boolean) + return (1); + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=%s", attr->name, + attr->values[i].boolean ? "true" : "false"); + } + break; + + case IPP_TAG_NOVALUE : + return (!strcmp(value, "no-value") || !strncmp(value, "no-value,", 9)); + + 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 (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) + { + char temp[256]; /* Temporary string */ + + regerror(i, &re, temp, sizeof(temp)); + + print_fatal_error("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 < attr->num_values; i ++) + { + if (regexec(&re, attr->values[i].string.text, 0, NULL, 0)) + { + if (report) + print_test_error("GOT: %s=\"%s\"", attr->name, + attr->values[i].string.text); + else + break; + } + } + + regfree(&re); + + return (i == attr->num_values); + } + else + { + /* + * Value is a literal string, see if at least one value matches the + * literal string... + */ + + for (i = 0; i < attr->num_values; i ++) + { + if (!strcmp(value, attr->values[i].string.text)) + return (1); + } + + if (report) + { + for (i = 0; i < attr->num_values; i ++) + print_test_error("GOT: %s=\"%s\"", attr->name, + attr->values[i].string.text); + } + } + break; + + default : + break; + } + + return (0); +} + + +/* + * End of "$Id: ipptool.c 9829 2011-06-14 21:01:39Z mike $". + */ diff --git a/test/print-job-hold.test b/test/print-job-hold.test index 6bdaa838..d70e523e 100644 --- a/test/print-job-hold.test +++ b/test/print-job-hold.test @@ -1,33 +1,41 @@ # Test print-job and job-hold-until attribute { # The name of the test... - NAME "Print with print-job + job-hold-until" - - # The resource to use for the POST - # RESOURCE /admin + NAME "Print-Job w/job-hold-until=indefinite" # The operation to use - OPERATION print-job + OPERATION Print-Job # Attributes, starting in the operation group... - GROUP operation + GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri ATTR name requesting-user-name $user - ATTR mimetype document-format application/postscript ATTR keyword job-hold-until indefinite + ATTR name job-name $filename - GROUP job - ATTR integer copies 1 - - FILE ../data/testprint.ps + FILE $filename # What statuses are OK? - STATUS ok - STATUS ok-subst + STATUS successful-ok + STATUS successful-ok-ignored-or-substituted-attributes # What attributes do we expect? EXPECT job-id EXPECT job-uri } +{ + NAME "Release-Job" + OPERATION Release-Job + + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR language attributes-natural-language en + ATTR uri printer-uri $uri + ATTR integer job-id $job-id + ATTR name requesting-user-name $user + + # What statuses are OK? + STATUS successful-ok +} diff --git a/test/print-job-media-col.test b/test/print-job-media-col.test index 373825ab..cafd291f 100644 --- a/test/print-job-media-col.test +++ b/test/print-job-media-col.test @@ -25,7 +25,14 @@ MEMBER integer x-dimension 10160 MEMBER integer y-dimension 15240 } + + # Borderless + MEMBER integer media-left-margin 0 + MEMBER integer media-right-margin 0 + MEMBER integer media-top-margin 0 + MEMBER integer media-bottom-margin 0 } + ATTR enum print-quality 5 FILE $filename diff --git a/test/print-job.test b/test/print-job.test index a0c814e5..a830a124 100644 --- a/test/print-job.test +++ b/test/print-job.test @@ -1,26 +1,22 @@ # Print a test page using print-job { # The name of the test... - NAME "Print test page using print-job" - - # The resource to use for the POST - # RESOURCE /admin + NAME "Print file using Print-Job" # The operation to use - OPERATION print-job + OPERATION Print-Job # Attributes, starting in the operation group... - GROUP operation + GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri ATTR name requesting-user-name $user - ATTR mimetype document-format application/postscript - GROUP job + GROUP job-attributes-tag ATTR integer copies 1 - FILE testfile.ps + FILE $filename # What statuses are OK? STATUS successful-ok diff --git a/test/print-uri.test b/test/print-uri.test new file mode 100644 index 00000000..aaa7d392 --- /dev/null +++ b/test/print-uri.test @@ -0,0 +1,27 @@ +# Print a file using Print-URI +{ + # The name of the test... + NAME "Print file using Print-URI" + + # The operation to use + OPERATION Print-URI + + # Attributes, starting in the operation group... + GROUP operation-attributes-tag + ATTR charset attributes-charset utf-8 + ATTR language attributes-natural-language en + ATTR uri printer-uri $uri + ATTR name requesting-user-name $user + ATTR uri document-uri file://$filename + + GROUP job-attributes-tag + ATTR integer copies 1 + + # What statuses are OK? + STATUS successful-ok + STATUS successful-ok-ignored-or-substituted-attributes + + # What attributes do we expect? + EXPECT job-id + EXPECT job-uri +} diff --git a/test/printer.opacity b/test/printer.opacity Binary files differnew file mode 100644 index 00000000..595854a2 --- /dev/null +++ b/test/printer.opacity diff --git a/test/printer.png b/test/printer.png Binary files differnew file mode 100644 index 00000000..18c05db1 --- /dev/null +++ b/test/printer.png diff --git a/test/run-stp-tests.sh b/test/run-stp-tests.sh index b0667f5e..9c709ea7 100755 --- a/test/run-stp-tests.sh +++ b/test/run-stp-tests.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# "$Id: run-stp-tests.sh 9445 2011-01-08 00:03:51Z mike $" +# "$Id: run-stp-tests.sh 9750 2011-05-06 22:53:53Z mike $" # # Perform the complete set of IPP compliance tests specified in the # CUPS Software Test Plan. @@ -251,6 +251,7 @@ ln -s $root/backend/ipp /tmp/cups-$user/bin/backend ln -s $root/backend/lpd /tmp/cups-$user/bin/backend ln -s $root/backend/mdns /tmp/cups-$user/bin/backend ln -s $root/backend/parallel /tmp/cups-$user/bin/backend +ln -s $root/backend/pseudo /tmp/cups-$user/bin/backend ln -s $root/backend/serial /tmp/cups-$user/bin/backend ln -s $root/backend/snmp /tmp/cups-$user/bin/backend ln -s $root/backend/socket /tmp/cups-$user/bin/backend @@ -266,6 +267,8 @@ ln -s $root/filter/hpgltops /tmp/cups-$user/bin/filter ln -s $root/filter/pstops /tmp/cups-$user/bin/filter ln -s $root/filter/rastertoepson /tmp/cups-$user/bin/filter ln -s $root/filter/rastertohp /tmp/cups-$user/bin/filter +ln -s $root/filter/rastertolabel /tmp/cups-$user/bin/filter +ln -s $root/filter/rastertopwg /tmp/cups-$user/bin/filter ln -s $root/filter/texttops /tmp/cups-$user/bin/filter ln -s $root/data/classified /tmp/cups-$user/share/banners @@ -285,6 +288,14 @@ ln -s $root/data/*.h /tmp/cups-$user/share/ppdc ln -s $root/data/*.defs /tmp/cups-$user/share/ppdc ln -s $root/templates /tmp/cups-$user/share +if test -f $root/filter/imagetops; then + ln -s $root/filter/imagetops /tmp/cups-$user/bin/filter +fi + +if test -f $root/filter/imagetoraster; then + ln -s $root/filter/imagetoraster /tmp/cups-$user/bin/filter +fi + # # Mac OS X filters and configuration files... # @@ -300,6 +311,8 @@ if test `uname` = Darwin; then ln -s /usr/libexec/cups/filter/pstoappleps /tmp/cups-$user/bin/filter ln -s /usr/libexec/cups/filter/pstocupsraster /tmp/cups-$user/bin/filter ln -s /usr/libexec/cups/filter/pstopdffilter /tmp/cups-$user/bin/filter + ln -s /usr/libexec/cups/filter/rastertourf /tmp/cups-$user/bin/filter + ln -s /usr/libexec/cups/filter/xhtmltopdf /tmp/cups-$user/bin/filter if test -f /private/etc/cups/apple.types; then ln -s /private/etc/cups/apple.* /tmp/cups-$user/share/mime @@ -307,8 +320,6 @@ if test `uname` = Darwin; then ln -s /usr/share/cups/mime/apple.* /tmp/cups-$user/share/mime fi else - ln -s $root/filter/imagetops /tmp/cups-$user/bin/filter - ln -s $root/filter/imagetoraster /tmp/cups-$user/bin/filter ln -s $root/filter/pdftops /tmp/cups-$user/bin/filter fi @@ -542,7 +553,7 @@ done # date=`date "+%Y-%m-%d"` -strfile=/tmp/cups-$user/cups-str-1.4-$date-$user.html +strfile=/tmp/cups-$user/cups-str-1.5-$date-$user.html rm -f $strfile cat str-header.html >$strfile @@ -565,7 +576,7 @@ for file in 4*.test; do echo "Performing $file..." echo "" >>$strfile - ./ipptest ipp://localhost:$port/printers $file | tee -a $strfile + ./ipptool -t ipp://localhost:$port/printers $file | tee -a $strfile status=$? if test $status != 0; then @@ -749,10 +760,10 @@ fi # Warning log messages count=`$GREP '^W ' /tmp/cups-$user/log/error_log | wc -l | awk '{print $1}'` -if test $count != 0; then - echo "FAIL: $count warning messages, expected 0." +if test $count != 9; then + echo "FAIL: $count warning messages, expected 9." $GREP '^W ' /tmp/cups-$user/log/error_log - echo "<P>FAIL: $count warning messages, expected 0.</P>" >>$strfile + echo "<P>FAIL: $count warning messages, expected 9.</P>" >>$strfile echo "<PRE>" >>$strfile $GREP '^W ' /tmp/cups-$user/log/error_log | sed -e '1,$s/&/&/g' -e '1,$s/</</g' >>$strfile echo "</PRE>" >>$strfile @@ -810,16 +821,6 @@ else echo "<P>PASS: $count debug2 messages.</P>" >>$strfile fi -# Page log file... -if $GREP -iq 'testfile.pdf na_letter_8.5x11in' /tmp/cups-$user/log/page_log; then - echo "PASS: page_log formatted correctly." - echo "<P>PASS: page_log formatted correctly.</P>" >>$strfile -else - echo "WARN: page_log formatted incorrectly." - echo "<P>WARN: page_log formatted incorrectly.</P>" >>$strfile -# fail=`expr $fail + 1` -fi - # Log files... echo "<H2>access_log</H2>" >>$strfile echo "<PRE>" >>$strfile @@ -865,5 +866,5 @@ if test $fail != 0; then fi # -# End of "$Id: run-stp-tests.sh 9445 2011-01-08 00:03:51Z mike $" +# End of "$Id: run-stp-tests.sh 9750 2011-05-06 22:53:53Z mike $" # diff --git a/test/str-header.html b/test/str-header.html index ff53adb8..84f0d9b7 100644 --- a/test/str-header.html +++ b/test/str-header.html @@ -2,9 +2,9 @@ <HEAD> <META NAME="Description" CONTENT="CUPS Test Report"> <META NAME="COPYRIGHT" CONTENT="Copyright 2007-2010, All Rights Reserved"> - <META NAME="DOCNUMBER" CONTENT="CUPS-STR-1.4"> + <META NAME="DOCNUMBER" CONTENT="CUPS-STR-1.5"> <META NAME="Author" CONTENT="Apple Inc."> - <TITLE>CUPS 1.4 Software Test Report</TITLE> + <TITLE>CUPS 1.5 Software Test Report</TITLE> <STYLE TYPE="text/css"><!-- PRE { font-size: 80%; @@ -14,11 +14,10 @@ </HEAD> <BODY> -<H1>CUPS 1.4 Software Test Report</H1> +<H1>CUPS 1.5 Software Test Report</H1> <P>This software test report provides detailed test results that -are used to evaluate the stability and compliance of the Common -UNIX Printing System ("CUPS") Version 1.4. +are used to evaluate the stability and compliance of CUPS Version 1.5. <H2>Document Overview</H2> diff --git a/test/testfile.ps b/test/testfile.ps index bce9c917..d02503a1 100644 --- a/test/testfile.ps +++ b/test/testfile.ps @@ -12,9 +12,9 @@ %%BeginProlog %%BeginResource procset testprint 1.3 0 % -% PostScript test page for the Common UNIX Printing System ("CUPS"). +% PostScript test page for CUPS. % -% Copyright 2007 Apple Inc. +% Copyright 2007-2011 Apple Inc. % Copyright 1993-2007 Easy Software Products % % These coded instructions, statements, and computer programs are the @@ -593,6 +593,6 @@ gsave grestore showpage % -% End of "$Id: testfile.ps 6649 2007-07-11 21:46:42Z mike $". +% End of "$Id: testfile.ps 9771 2011-05-12 05:21:56Z mike $". % %%EOF diff --git a/test/testhp.ppd b/test/testhp.ppd index 7392bc29..4a82be67 100644 --- a/test/testhp.ppd +++ b/test/testhp.ppd @@ -1,10 +1,10 @@ *PPD-Adobe: "4.3" *% -*% "$Id: testhp.ppd 6649 2007-07-11 21:46:42Z mike $" +*% "$Id: testhp.ppd 9563 2011-02-22 20:21:24Z mike $" *% -*% Test HP PPD file for the Common UNIX Printing System (CUPS). +*% Test HP PPD file for CUPS. *% -*% Copyright 2007 by Apple Inc. +*% Copyright 2007-2011 by Apple Inc. *% Copyright 1997-2005 by Easy Software Products. *% *% These coded instructions, statements, and computer programs are the @@ -15,14 +15,15 @@ *% *FormatVersion: "4.3" *FileVersion: "1.1" -*LanguageVersion: English +*LanguageVersion: English *LanguageEncoding: ISOLatin1 *PCFileName: "TESTHP.PPD" *Manufacturer: "ESP" -*Product: "(CUPS v1.1)" +*Product: "(CUPS v1.5)" *cupsVersion: 1.1 *cupsManualCopies: True *cupsFilter: "application/vnd.cups-raster 50 rastertohp" +*cupsFilter2: "application/vnd.cups-raster application/vnd.hp-pcl 50 rastertohp" *ModelName: "Test HP Printer" *ShortNickName: "Test HP Printer" *NickName: "Test HP Printer CUPS v1.1" @@ -82,7 +83,7 @@ *PageRegion EnvMonarch/Envelope Monarch: "<</PageSize[279 540]/ImagingBBox null>>setpagedevice" *CloseUI: *PageRegion -*DefaultImageableArea: Letter +*DefaultImageableArea: Letter *ImageableArea Letter/US Letter: "18 36 594 756" *ImageableArea Legal/US Legal: "18 36 594 972" *ImageableArea Executive/US Executive: "18 36 504 684" @@ -182,5 +183,5 @@ *Font ZapfChancery-MediumItalic: Standard "(001.007S)" Standard ROM *Font ZapfDingbats: Special "(001.004S)" Standard ROM *% -*% End of "$Id: testhp.ppd 6649 2007-07-11 21:46:42Z mike $". +*% End of "$Id: testhp.ppd 9563 2011-02-22 20:21:24Z mike $". *% diff --git a/test/testps.ppd b/test/testps.ppd index 77c8ca62..6e478003 100644 --- a/test/testps.ppd +++ b/test/testps.ppd @@ -1,10 +1,10 @@ *PPD-Adobe: "4.3" *% -*% "$Id: testps.ppd 6649 2007-07-11 21:46:42Z mike $" +*% "$Id: testps.ppd 9771 2011-05-12 05:21:56Z mike $" *% -*% Test PS PPD file for the Common UNIX Printing System (CUPS). +*% Test PS PPD file for CUPS. *% -*% Copyright 2007 by Apple Inc. +*% Copyright 2007-2011 by Apple Inc. *% Copyright 1997-2005 by Easy Software Products. *% *% These coded instructions, statements, and computer programs are the @@ -15,7 +15,7 @@ *% *FormatVersion: "4.3" *FileVersion: "1.1" -*LanguageVersion: English +*LanguageVersion: English *LanguageEncoding: ISOLatin1 *PCFileName: "TESTPS.PPD" *Manufacturer: "ESP" @@ -79,7 +79,7 @@ *PageRegion EnvMonarch/Envelope Monarch: "<</PageSize[279 540]/ImagingBBox null>>setpagedevice" *CloseUI: *PageRegion -*DefaultImageableArea: Letter +*DefaultImageableArea: Letter *ImageableArea Letter/US Letter: "18 36 594 756" *ImageableArea Legal/US Legal: "18 36 594 972" *ImageableArea Executive/US Executive: "18 36 504 684" @@ -179,5 +179,5 @@ *Font ZapfChancery-MediumItalic: Standard "(001.007S)" Standard ROM *Font ZapfDingbats: Special "(001.004S)" Standard ROM *% -*% End of "$Id: testps.ppd 6649 2007-07-11 21:46:42Z mike $". +*% End of "$Id: testps.ppd 9771 2011-05-12 05:21:56Z mike $". *% |