summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorDidier Raboud <odyx@debian.org>2012-10-25 20:57:13 +0200
committerDidier Raboud <odyx@debian.org>2012-10-25 20:57:13 +0200
commit49a2853988b074d087e82c51aec4f9fc052a057d (patch)
treec38ece96005bc33bd4e133fd0037f3428fdc039d /test
parenta312f7e1ac68fb22275719f6208b670d9edd45b5 (diff)
downloadcups-49a2853988b074d087e82c51aec4f9fc052a057d.tar.gz
Imported Upstream version 1.5.0upstream/1.5.0
Diffstat (limited to 'test')
-rw-r--r--test/4.1-requests.test4
-rw-r--r--test/4.2-cups-printer-ops.test4
-rw-r--r--test/4.3-job-ops.test4
-rw-r--r--test/4.4-subscription-ops.test9
-rw-r--r--test/Dependencies20
-rw-r--r--test/Makefile84
-rw-r--r--test/create-printer-subscription.test35
-rw-r--r--test/get-completed-jobs.test51
-rw-r--r--test/get-jobs.test46
-rw-r--r--test/get-printer-attributes.test9
-rw-r--r--test/ipp-1.1.test752
-rw-r--r--test/ipp-2.0.test80
-rw-r--r--test/ipp-2.1.test95
-rw-r--r--test/ippserver.c4380
-rw-r--r--test/ipptest.c1278
-rw-r--r--test/ipptool.c4921
-rw-r--r--test/print-job-hold.test34
-rw-r--r--test/print-job-media-col.test7
-rw-r--r--test/print-job.test14
-rw-r--r--test/print-uri.test27
-rw-r--r--test/printer.opacitybin0 -> 18493 bytes
-rw-r--r--test/printer.pngbin0 -> 5133 bytes
-rwxr-xr-xtest/run-stp-tests.sh39
-rw-r--r--test/str-header.html9
-rw-r--r--test/testfile.ps6
-rw-r--r--test/testhp.ppd15
-rw-r--r--test/testps.ppd12
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), "&amp;", 5);
+ else
+ httpWrite2(&(client->http), "&lt;", 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>&lt;&lt;%s&gt;&gt;</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("&amp;", stdout);
+ else if (*s == '<')
+ fputs("&lt;", stdout);
+ else if (*s == '>')
+ fputs("&gt;", 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
new file mode 100644
index 00000000..595854a2
--- /dev/null
+++ b/test/printer.opacity
Binary files differ
diff --git a/test/printer.png b/test/printer.png
new file mode 100644
index 00000000..18c05db1
--- /dev/null
+++ b/test/printer.png
Binary files differ
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/&/&amp;/g' -e '1,$s/</&lt;/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 $".
*%