diff --git a/cups-cupsenumdests2.patch b/cups-cupsenumdests2.patch index 9e2e9f1..adb9806 100644 --- a/cups-cupsenumdests2.patch +++ b/cups-cupsenumdests2.patch @@ -1,121 +1,4606 @@ -diff -up cups-2.2.4/cups/cups.h.cupsenumdests2 cups-2.2.4/cups/cups.h ---- cups-2.2.4/cups/cups.h.cupsenumdests2 2017-06-30 17:44:38.000000000 +0200 -+++ cups-2.2.4/cups/cups.h 2017-10-04 15:31:45.358029517 +0200 -@@ -527,11 +527,20 @@ extern int cupsEnumDests(unsigned flags - cups_ptype_t type, cups_ptype_t mask, - cups_dest_cb_t cb, void *user_data) - _CUPS_API_1_6; -+extern int cupsEnumDests2(http_t *http, unsigned flags, int msec, int *cancel, -+ cups_ptype_t type, cups_ptype_t mask, -+ cups_dest_cb_t cb, void *user_data) -+ _CUPS_API_1_6; - # ifdef __BLOCKS__ - extern int cupsEnumDestsBlock(unsigned flags, int msec, - int *cancel, cups_ptype_t type, - cups_ptype_t mask, - cups_dest_block_t block) -+ _CUPS_API_1_6; -+extern int cupsEnumDestsBlock2(http_t *http, unsigned flags, int msec, -+ int *cancel, cups_ptype_t type, -+ cups_ptype_t mask, -+ cups_dest_block_t block) - _CUPS_API_1_6; - # endif /* __BLOCKS__ */ - extern ipp_status_t cupsFinishDestDocument(http_t *http, -diff -up cups-2.2.4/cups/dest.c.cupsenumdests2 cups-2.2.4/cups/dest.c ---- cups-2.2.4/cups/dest.c.cupsenumdests2 2017-10-04 15:31:45.356029534 +0200 -+++ cups-2.2.4/cups/dest.c 2017-10-04 15:31:45.359029509 +0200 -@@ -1377,6 +1377,423 @@ cupsEnumDests( - return (1); +From 9554d4e748afc212ff68d56098f5c430c48325d3 Mon Sep 17 00:00:00 2001 +From: Michael Sweet +Date: Wed, 11 Oct 2017 13:27:36 -0400 +Subject: [PATCH] `cupsGetDests2` was not using the supplied HTTP connection + (Issue #5135) + +- Make a local cups_enum_dests function that accepts a http_t *. +- Have both cupsEnumDests and cupsGetDests2 call cups_enum_dests. +--- + cups/dest.c | 4458 ++++++++++++++++++++++++++++++----------------------------- + 1 files changed, 2235 insertions(+), 2223 deletions(-) + +diff --git a/cups/dest.c b/cups/dest.c +index b3f1e8b79..45626884b 100644 +--- a/cups/dest.c ++++ b/cups/dest.c +@@ -230,6 +230,7 @@ static void cups_dnssd_unquote(char *dst, const char *src, + size_t dstsize); + static int cups_elapsed(struct timeval *t); + #endif /* HAVE_DNSSD || HAVE_AVAHI */ ++static int cups_enum_dests(http_t *http, unsigned flags, int msec, int *cancel, cups_ptype_t type, cups_ptype_t mask, cups_dest_cb_t cb, void *user_data); + static int cups_find_dest(const char *name, const char *instance, + int num_dests, cups_dest_t *dests, int prev, + int *rdiff); +@@ -978,2958 +979,2969 @@ cupsEnumDests( + cups_dest_cb_t cb, /* I - Callback function */ + void *user_data) /* I - User data */ + { +- int i, /* Looping var */ +- num_dests; /* Number of destinations */ +- cups_dest_t *dests = NULL, /* Destinations */ +- *dest; /* Current destination */ +- const char *defprinter; /* Default printer */ +- char name[1024], /* Copy of printer name */ +- *instance, /* Pointer to instance name */ +- *user_default; /* User default printer */ +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +- int count, /* Number of queries started */ +- completed, /* Number of completed queries */ +- remaining; /* Remainder of timeout */ +- struct timeval curtime; /* Current time */ +- _cups_dnssd_data_t data; /* Data for callback */ +- _cups_dnssd_device_t *device; /* Current device */ +-# ifdef HAVE_DNSSD +- int nfds, /* Number of files responded */ +- main_fd; /* File descriptor for lookups */ +- DNSServiceRef ipp_ref = NULL, /* IPP browser */ +- local_ipp_ref = NULL; /* Local IPP browser */ +-# ifdef HAVE_SSL +- DNSServiceRef ipps_ref = NULL,/* IPPS browser */ +- local_ipps_ref = NULL; /* Local IPPS browser */ +-# endif /* HAVE_SSL */ +-# ifdef HAVE_POLL +- struct pollfd pfd; /* Polling data */ +-# else +- fd_set input; /* Input set for select() */ +- struct timeval timeout; /* Timeout for select() */ +-# endif /* HAVE_POLL */ +-# else /* HAVE_AVAHI */ +- int error; /* Error value */ +- AvahiServiceBrowser *ipp_ref = NULL;/* IPP browser */ +-# ifdef HAVE_SSL +- AvahiServiceBrowser *ipps_ref = NULL; /* IPPS browser */ +-# endif /* HAVE_SSL */ +-# endif /* HAVE_DNSSD */ +-#endif /* HAVE_DNSSD || HAVE_AVAHI */ ++ return (cups_enum_dests(CUPS_HTTP_DEFAULT, flags, msec, cancel, type, mask, cb, user_data)); ++} + + +- DEBUG_printf(("cupsEnumDests(flags=%x, msec=%d, cancel=%p, type=%x, mask=%x, cb=%p, user_data=%p)", flags, msec, (void *)cancel, type, mask, (void *)cb, (void *)user_data)); ++# ifdef __BLOCKS__ ++/* ++ * 'cupsEnumDestsBlock()' - Enumerate available destinations with a block. ++ * ++ * Destinations are enumerated from one or more sources. The block receives the ++ * @code user_data@ pointer and the destination pointer which can be used as ++ * input to the @link cupsCopyDest@ function. The block must return 1 to ++ * continue enumeration or 0 to stop. ++ * ++ * The @code type@ and @code mask@ arguments allow the caller to filter the ++ * destinations that are enumerated. Passing 0 for both will enumerate all ++ * printers. The constant @code CUPS_PRINTER_DISCOVERED@ is used to filter on ++ * destinations that are available but have not yet been added locally. ++ * ++ * Enumeration happens on the current thread and does not return until all ++ * destinations have been enumerated or the block returns 0. ++ * ++ * Note: The block will likely receive multiple updates for the same ++ * destinations - it is up to the caller to suppress any duplicate destinations. ++ * ++ * @since CUPS 1.6/macOS 10.8@ @exclude all@ ++ */ + +- /* +- * Range check input... +- */ ++int /* O - 1 on success, 0 on failure */ ++cupsEnumDestsBlock( ++ unsigned flags, /* I - Enumeration flags */ ++ int timeout, /* I - Timeout in milliseconds, 0 for indefinite */ ++ int *cancel, /* I - Pointer to "cancel" variable */ ++ cups_ptype_t type, /* I - Printer type bits */ ++ cups_ptype_t mask, /* I - Mask for printer type bits */ ++ cups_dest_block_t block) /* I - Block */ ++{ ++ return (cupsEnumDests(flags, timeout, cancel, type, mask, ++ (cups_dest_cb_t)cups_block_cb, (void *)block)); ++} ++# endif /* __BLOCKS__ */ + +- (void)flags; + +- if (!cb) +- { +- DEBUG_puts("1cupsEnumDests: No callback, returning 0."); +- return (0); +- } ++/* ++ * 'cupsFreeDests()' - Free the memory used by the list of destinations. ++ */ + +- /* +- * Get ready to enumerate... +- */ ++void ++cupsFreeDests(int num_dests, /* I - Number of destinations */ ++ cups_dest_t *dests) /* I - Destinations */ ++{ ++ int i; /* Looping var */ ++ cups_dest_t *dest; /* Current destination */ + +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +- memset(&data, 0, sizeof(data)); + +- data.type = type; +- data.mask = mask; +- data.cb = cb; +- data.user_data = user_data; +- data.devices = cupsArrayNew3((cups_array_func_t)cups_dnssd_compare_devices, NULL, NULL, 0, NULL, (cups_afree_func_t)cups_dnssd_free_device); +-#endif /* HAVE_DNSSD || HAVE_AVAHI */ ++ if (num_dests == 0 || dests == NULL) ++ return; + +- if (!(mask & CUPS_PRINTER_DISCOVERED) || !(type & CUPS_PRINTER_DISCOVERED)) ++ for (i = num_dests, dest = dests; i > 0; i --, dest ++) + { +- /* +- * Get the list of local printers and pass them to the callback function... +- */ ++ _cupsStrFree(dest->name); ++ _cupsStrFree(dest->instance); + +- num_dests = _cupsGetDests(CUPS_HTTP_DEFAULT, IPP_OP_CUPS_GET_PRINTERS, NULL, +- &dests, type, mask); ++ cupsFreeOptions(dest->num_options, dest->options); ++ } + +- if ((user_default = _cupsUserDefault(name, sizeof(name))) != NULL) +- defprinter = name; +- else if ((defprinter = cupsGetDefault2(CUPS_HTTP_DEFAULT)) != NULL) +- { +- strlcpy(name, defprinter, sizeof(name)); +- defprinter = name; +- } ++ free(dests); ++} + +- if (defprinter) +- { +- /* +- * Separate printer and instance name... +- */ + +- if ((instance = strchr(name, '/')) != NULL) +- *instance++ = '\0'; ++/* ++ * 'cupsGetDest()' - Get the named destination from the list. ++ * ++ * Use the @link cupsEnumDests@ or @link cupsGetDests2@ functions to get a ++ * list of supported destinations for the current user. ++ */ + +- /* +- * Lookup the printer and instance and make it the default... +- */ ++cups_dest_t * /* O - Destination pointer or @code NULL@ */ ++cupsGetDest(const char *name, /* I - Destination name or @code NULL@ for the default destination */ ++ const char *instance, /* I - Instance name or @code NULL@ */ ++ int num_dests, /* I - Number of destinations */ ++ cups_dest_t *dests) /* I - Destinations */ ++{ ++ int diff, /* Result of comparison */ ++ match; /* Matching index */ + +- if ((dest = cupsGetDest(name, instance, num_dests, dests)) != NULL) +- dest->is_default = 1; +- } + +- for (i = num_dests, dest = dests; +- i > 0 && (!cancel || !*cancel); +- i --, dest ++) ++ if (num_dests <= 0 || !dests) ++ return (NULL); ++ ++ if (!name) ++ { ++ /* ++ * NULL name for default printer. ++ */ ++ ++ while (num_dests > 0) + { +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +- const char *device_uri; /* Device URI */ +-#endif /* HAVE_DNSSD || HAVE_AVAHI */ ++ if (dests->is_default) ++ return (dests); + +- if (!(*cb)(user_data, i > 1 ? CUPS_DEST_FLAGS_MORE : CUPS_DEST_FLAGS_NONE, +- dest)) +- break; ++ num_dests --; ++ dests ++; ++ } ++ } ++ else ++ { ++ /* ++ * Lookup name and optionally the instance... ++ */ + +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +- if (!dest->instance && (device_uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL && !strncmp(device_uri, "dnssd://", 8)) +- { +- /* +- * Add existing queue using service name, etc. so we don't list it again... +- */ ++ match = cups_find_dest(name, instance, num_dests, dests, -1, &diff); + +- char scheme[32], /* URI scheme */ +- userpass[32], /* Username:password */ +- serviceName[256], /* Service name (host field) */ +- resource[256], /* Resource (options) */ +- *regtype, /* Registration type */ +- *replyDomain; /* Registration domain */ +- int port; /* Port number (not used) */ ++ if (!diff) ++ return (dests + match); ++ } + +- if (httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), serviceName, sizeof(serviceName), &port, resource, sizeof(resource)) >= HTTP_URI_STATUS_OK) +- { +- if ((regtype = strstr(serviceName, "._ipp")) != NULL) +- { +- *regtype++ = '\0'; ++ return (NULL); ++} + +- if ((replyDomain = strstr(regtype, "._tcp.")) != NULL) +- { +- replyDomain[5] = '\0'; +- replyDomain += 6; + +- if ((device = cups_dnssd_get_device(&data, serviceName, regtype, replyDomain)) != NULL) +- device->state = _CUPS_DNSSD_ACTIVE; +- } +- } +- } +- } +-#endif /* HAVE_DNSSD || HAVE_AVAHI */ +- } ++/* ++ * '_cupsGetDestResource()' - Get the resource path and URI for a destination. ++ */ + +- cupsFreeDests(num_dests, dests); ++const char * /* O - Printer URI */ ++_cupsGetDestResource( ++ cups_dest_t *dest, /* I - Destination */ ++ char *resource, /* I - Resource buffer */ ++ size_t resourcesize) /* I - Size of resource buffer */ ++{ ++ const char *uri; /* Printer URI */ ++ char scheme[32], /* URI scheme */ ++ userpass[256], /* Username and password (unused) */ ++ hostname[256]; /* Hostname */ ++ int port; /* Port number */ + +- if (i > 0 || msec == 0) +- goto enum_finished; +- } ++ ++ DEBUG_printf(("_cupsGetDestResource(dest=%p(%s), resource=%p, resourcesize=%d)", (void *)dest, dest->name, (void *)resource, (int)resourcesize)); + + /* +- * Return early if the caller doesn't want to do discovery... ++ * Range check input... + */ + +- if ((mask & CUPS_PRINTER_DISCOVERED) && !(type & CUPS_PRINTER_DISCOVERED)) +- goto enum_finished; ++ if (!dest || !resource || resourcesize < 1) ++ { ++ if (resource) ++ *resource = '\0'; ++ ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); ++ return (NULL); ++ } + +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) + /* +- * Get Bonjour-shared printers... ++ * Grab the printer URI... + */ + +- gettimeofday(&curtime, NULL); +- +-# ifdef HAVE_DNSSD +- if (DNSServiceCreateConnection(&data.main_ref) != kDNSServiceErr_NoError) ++ if ((uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options)) == NULL) + { +- DEBUG_puts("1cupsEnumDests: Unable to create service browser, returning 0."); +- return (0); +- } ++ if ((uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL) ++ { ++#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) ++ if (strstr(uri, "._tcp")) ++ uri = cups_dnssd_resolve(dest, uri, 5000, NULL, NULL, NULL); ++#endif /* HAVE_DNSSD || HAVE_AVAHI */ ++ } + +- main_fd = DNSServiceRefSockFD(data.main_ref); ++ if (uri) ++ { ++ DEBUG_printf(("1_cupsGetDestResource: Resolved printer-uri-supported=\"%s\"", uri)); + +- ipp_ref = data.main_ref; +- if (DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, "_ipp._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data) != kDNSServiceErr_NoError) +- { +- DEBUG_puts("1cupsEnumDests: Unable to create IPP browser, returning 0."); +- DNSServiceRefDeallocate(data.main_ref); +- return (0); +- } ++ uri = _cupsCreateDest(dest->name, cupsGetOption("printer-info", dest->num_options, dest->options), NULL, uri, resource, resourcesize); ++ } + +- local_ipp_ref = data.main_ref; +- if (DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly, "_ipp._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_local_cb, &data) != kDNSServiceErr_NoError) +- { +- DEBUG_puts("1cupsEnumDests: Unable to create local IPP browser, returning 0."); +- DNSServiceRefDeallocate(data.main_ref); +- return (0); +- } ++ if (uri) ++ { ++ DEBUG_printf(("1_cupsGetDestResource: Local printer-uri-supported=\"%s\"", uri)); + +-# ifdef HAVE_SSL +- ipps_ref = data.main_ref; +- if (DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0, "_ipps._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data) != kDNSServiceErr_NoError) +- { +- DEBUG_puts("1cupsEnumDests: Unable to create IPPS browser, returning 0."); +- DNSServiceRefDeallocate(data.main_ref); +- return (0); +- } ++ dest->num_options = cupsAddOption("printer-uri-supported", uri, dest->num_options, &dest->options); + +- local_ipps_ref = data.main_ref; +- if (DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly, "_ipps._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_local_cb, &data) != kDNSServiceErr_NoError) +- { +- DEBUG_puts("1cupsEnumDests: Unable to create local IPPS browser, returning 0."); +- DNSServiceRefDeallocate(data.main_ref); +- return (0); +- } +-# endif /* HAVE_SSL */ ++ uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options); ++ } ++ else ++ { ++ DEBUG_puts("1_cupsGetDestResource: No printer-uri-supported found."); + +-# else /* HAVE_AVAHI */ +- if ((data.simple_poll = avahi_simple_poll_new()) == NULL) +- { +- DEBUG_puts("1cupsEnumDests: Unable to create Avahi poll, returning 0."); +- return (0); +- } ++ if (resource) ++ *resource = '\0'; + +- avahi_simple_poll_set_func(data.simple_poll, cups_dnssd_poll_cb, &data); ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOENT), 0); + +- data.client = avahi_client_new(avahi_simple_poll_get(data.simple_poll), +- 0, cups_dnssd_client_cb, &data, +- &error); +- if (!data.client) +- { +- DEBUG_puts("1cupsEnumDests: Unable to create Avahi client, returning 0."); +- avahi_simple_poll_free(data.simple_poll); +- return (0); ++ return (NULL); ++ } + } +- +- data.browsers = 1; +- if ((ipp_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipp._tcp", NULL, 0, cups_dnssd_browse_cb, &data)) == NULL) ++ else + { +- DEBUG_puts("1cupsEnumDests: Unable to create Avahi IPP browser, returning 0."); +- +- avahi_client_free(data.client); +- avahi_simple_poll_free(data.simple_poll); +- return (0); +- } ++ DEBUG_printf(("1_cupsGetDestResource: printer-uri-supported=\"%s\"", uri)); + +-# ifdef HAVE_SSL +- data.browsers ++; +- if ((ipps_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipps._tcp", NULL, 0, cups_dnssd_browse_cb, &data)) == NULL) +- { +- DEBUG_puts("1cupsEnumDests: Unable to create Avahi IPPS browser, returning 0."); ++ if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), ++ userpass, sizeof(userpass), hostname, sizeof(hostname), ++ &port, resource, (int)resourcesize) < HTTP_URI_STATUS_OK) ++ { ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer-uri."), 1); + +- avahi_service_browser_free(ipp_ref); +- avahi_client_free(data.client); +- avahi_simple_poll_free(data.simple_poll); +- return (0); ++ return (NULL); ++ } + } +-# endif /* HAVE_SSL */ +-# endif /* HAVE_DNSSD */ + +- if (msec < 0) +- remaining = INT_MAX; +- else +- remaining = msec; ++ DEBUG_printf(("1_cupsGetDestResource: resource=\"%s\"", resource)); + +- while (remaining > 0 && (!cancel || !*cancel)) +- { +- /* +- * Check for input... +- */ ++ return (uri); ++} + +- DEBUG_printf(("1cupsEnumDests: remaining=%d", remaining)); + +- cups_elapsed(&curtime); ++/* ++ * 'cupsGetDestWithURI()' - Get a destination associated with a URI. ++ * ++ * "name" is the desired name for the printer. If @code NULL@, a name will be ++ * created using the URI. ++ * ++ * "uri" is the "ipp" or "ipps" URI for the printer. ++ * ++ * @since CUPS 2.0/macOS 10.10@ ++ */ + +-# ifdef HAVE_DNSSD +-# ifdef HAVE_POLL +- pfd.fd = main_fd; +- pfd.events = POLLIN; ++cups_dest_t * /* O - Destination or @code NULL@ */ ++cupsGetDestWithURI(const char *name, /* I - Desired printer name or @code NULL@ */ ++ const char *uri) /* I - URI for the printer */ ++{ ++ cups_dest_t *dest; /* New destination */ ++ char temp[1024], /* Temporary string */ ++ scheme[256], /* Scheme from URI */ ++ userpass[256], /* Username:password from URI */ ++ hostname[256], /* Hostname from URI */ ++ resource[1024], /* Resource path from URI */ ++ *ptr; /* Pointer into string */ ++ const char *info; /* printer-info string */ ++ int port; /* Port number from URI */ + +- nfds = poll(&pfd, 1, remaining > _CUPS_DNSSD_MAXTIME ? _CUPS_DNSSD_MAXTIME : remaining); + +-# else +- FD_ZERO(&input); +- FD_SET(main_fd, &input); ++ /* ++ * Range check input... ++ */ + +- timeout.tv_sec = 0; +- timeout.tv_usec = 1000 * (remaining > _CUPS_DNSSD_MAXTIME ? _CUPS_DNSSD_MAXTIME : remaining); ++ if (!uri) ++ { ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); ++ return (NULL); ++ } + +- nfds = select(main_fd + 1, &input, NULL, NULL, &timeout); +-# endif /* HAVE_POLL */ ++ if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK || ++ (strncmp(uri, "ipp://", 6) && strncmp(uri, "ipps://", 7))) ++ { ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer-uri."), 1); + +- if (nfds > 0) +- DNSServiceProcessResult(data.main_ref); +- else if (nfds < 0 && errno != EINTR && errno != EAGAIN) +- break; ++ return (NULL); ++ } + +-# else /* HAVE_AVAHI */ +- data.got_data = 0; ++ if (name) ++ { ++ info = name; ++ } ++ else ++ { ++ /* ++ * Create the name from the URI... ++ */ + +- if ((error = avahi_simple_poll_iterate(data.simple_poll, _CUPS_DNSSD_MAXTIME)) > 0) ++ if (strstr(hostname, "._tcp")) + { + /* +- * We've been told to exit the loop. Perhaps the connection to +- * Avahi failed. ++ * Use the service instance name... + */ + +- break; +- } +- +- DEBUG_printf(("1cupsEnumDests: got_data=%d", data.got_data)); +-# endif /* HAVE_DNSSD */ +- +- remaining -= cups_elapsed(&curtime); ++ if ((ptr = strstr(hostname, "._")) != NULL) ++ *ptr = '\0'; + +- for (device = (_cups_dnssd_device_t *)cupsArrayFirst(data.devices), +- count = 0, completed = 0; +- device; +- device = (_cups_dnssd_device_t *)cupsArrayNext(data.devices)) ++ cups_queue_name(temp, hostname, sizeof(temp)); ++ name = temp; ++ info = hostname; ++ } ++ else if (!strncmp(resource, "/classes/", 9)) + { +- if (device->ref) +- count ++; +- +- if (device->state == _CUPS_DNSSD_ACTIVE) +- completed ++; +- +- if (!device->ref && device->state == _CUPS_DNSSD_NEW) +- { +- DEBUG_printf(("1cupsEnumDests: Querying '%s'.", device->fullName)); +- +-# ifdef HAVE_DNSSD +- device->ref = data.main_ref; +- +- if (DNSServiceQueryRecord(&(device->ref), +- kDNSServiceFlagsShareConnection, +- 0, device->fullName, +- kDNSServiceType_TXT, +- kDNSServiceClass_IN, +- (DNSServiceQueryRecordReply)cups_dnssd_query_cb, +- &data) == kDNSServiceErr_NoError) +- { +- count ++; +- } +- else +- { +- device->ref = 0; +- device->state = _CUPS_DNSSD_ERROR; +- +- DEBUG_puts("1cupsEnumDests: Query failed."); +- } +- +-# else /* HAVE_AVAHI */ +- if ((device->ref = avahi_record_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, device->fullName, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT, 0, cups_dnssd_query_cb, &data)) != NULL) +- { +- DEBUG_printf(("1cupsEnumDests: Query ref=%p", device->ref)); +- count ++; +- } +- else +- { +- device->state = _CUPS_DNSSD_ERROR; +- +- DEBUG_printf(("1cupsEnumDests: Query failed: %s", avahi_strerror(avahi_client_errno(data.client)))); +- } +-# endif /* HAVE_DNSSD */ +- } +- else if (device->ref && device->state == _CUPS_DNSSD_PENDING) +- { +- completed ++; +- +- DEBUG_printf(("1cupsEnumDests: Query for \"%s\" is complete.", device->fullName)); +- +- if ((device->type & mask) == type) +- { +- DEBUG_printf(("1cupsEnumDests: Add callback for \"%s\".", device->dest.name)); +- if (!(*cb)(user_data, CUPS_DEST_FLAGS_NONE, &device->dest)) +- { +- remaining = -1; +- break; +- } +- } +- +- device->state = _CUPS_DNSSD_ACTIVE; +- } ++ snprintf(temp, sizeof(temp), "%s @ %s", resource + 9, hostname); ++ name = resource + 9; ++ info = temp; ++ } ++ else if (!strncmp(resource, "/printers/", 10)) ++ { ++ snprintf(temp, sizeof(temp), "%s @ %s", resource + 10, hostname); ++ name = resource + 10; ++ info = temp; ++ } ++ else ++ { ++ name = hostname; ++ info = hostname; + } +- +-# ifdef HAVE_AVAHI +- DEBUG_printf(("1cupsEnumDests: remaining=%d, browsers=%d, completed=%d, count=%d, devices count=%d", remaining, data.browsers, completed, count, cupsArrayCount(data.devices))); +- +- if (data.browsers == 0 && completed == cupsArrayCount(data.devices)) +- break; +-# else +- DEBUG_printf(("1cupsEnumDests: remaining=%d, completed=%d, count=%d, devices count=%d", remaining, completed, count, cupsArrayCount(data.devices))); +- +- if (completed == cupsArrayCount(data.devices)) +- break; +-# endif /* HAVE_AVAHI */ + } +-#endif /* HAVE_DNSSD || HAVE_AVAHI */ + + /* +- * Return... ++ * Create the destination... + */ + +- enum_finished: +- +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +- cupsArrayDelete(data.devices); ++ if ((dest = calloc(1, sizeof(cups_dest_t))) == NULL) ++ { ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); ++ return (NULL); ++ } + +-# ifdef HAVE_DNSSD +- if (ipp_ref) +- DNSServiceRefDeallocate(ipp_ref); +- if (local_ipp_ref) +- DNSServiceRefDeallocate(local_ipp_ref); ++ dest->name = _cupsStrAlloc(name); ++ dest->num_options = cupsAddOption("device-uri", uri, dest->num_options, &(dest->options)); ++ dest->num_options = cupsAddOption("printer-info", info, dest->num_options, &(dest->options)); + +-# ifdef HAVE_SSL +- if (ipps_ref) +- DNSServiceRefDeallocate(ipps_ref); +- if (local_ipps_ref) +- DNSServiceRefDeallocate(local_ipps_ref); +-# endif /* HAVE_SSL */ ++ return (dest); ++} + +- if (data.main_ref) +- DNSServiceRefDeallocate(data.main_ref); + +-# else /* HAVE_AVAHI */ +- if (ipp_ref) +- avahi_service_browser_free(ipp_ref); +-# ifdef HAVE_SSL +- if (ipps_ref) +- avahi_service_browser_free(ipps_ref); +-# endif /* HAVE_SSL */ +- +- if (data.client) +- avahi_client_free(data.client); +- if (data.simple_poll) +- avahi_simple_poll_free(data.simple_poll); +-# endif /* HAVE_DNSSD */ +-#endif /* HAVE_DNSSD || HAVE_AVAHI */ +- +- DEBUG_puts("1cupsEnumDests: Returning 1."); +- +- return (1); +-} +- +- +-# ifdef __BLOCKS__ + /* +- * 'cupsEnumDestsBlock()' - Enumerate available destinations with a block. ++ * '_cupsGetDests()' - Get destinations from a server. + * +- * Destinations are enumerated from one or more sources. The block receives the +- * @code user_data@ pointer and the destination pointer which can be used as +- * input to the @link cupsCopyDest@ function. The block must return 1 to +- * continue enumeration or 0 to stop. ++ * "op" is IPP_OP_CUPS_GET_PRINTERS to get a full list, IPP_OP_CUPS_GET_DEFAULT ++ * to get the system-wide default printer, or IPP_OP_GET_PRINTER_ATTRIBUTES for ++ * a known printer. + * +- * The @code type@ and @code mask@ arguments allow the caller to filter the +- * destinations that are enumerated. Passing 0 for both will enumerate all +- * printers. The constant @code CUPS_PRINTER_DISCOVERED@ is used to filter on +- * destinations that are available but have not yet been added locally. ++ * "name" is the name of an existing printer and is only used when "op" is ++ * IPP_OP_GET_PRINTER_ATTRIBUTES. + * +- * Enumeration happens on the current thread and does not return until all +- * destinations have been enumerated or the block returns 0. ++ * "dest" is initialized to point to the array of destinations. + * +- * Note: The block will likely receive multiple updates for the same +- * destinations - it is up to the caller to suppress any duplicate destinations. ++ * 0 is returned if there are no printers, no default printer, or the named ++ * printer does not exist, respectively. + * +- * @since CUPS 1.6/macOS 10.8@ @exclude all@ +- */ +- +-int /* O - 1 on success, 0 on failure */ +-cupsEnumDestsBlock( +- unsigned flags, /* I - Enumeration flags */ +- int timeout, /* I - Timeout in milliseconds, 0 for indefinite */ +- int *cancel, /* I - Pointer to "cancel" variable */ +- cups_ptype_t type, /* I - Printer type bits */ +- cups_ptype_t mask, /* I - Mask for printer type bits */ +- cups_dest_block_t block) /* I - Block */ +-{ +- return (cupsEnumDests(flags, timeout, cancel, type, mask, +- (cups_dest_cb_t)cups_block_cb, (void *)block)); +-} +-# endif /* __BLOCKS__ */ +- +- +-/* +- * 'cupsFreeDests()' - Free the memory used by the list of destinations. ++ * Free the memory used by the destination array using the @link cupsFreeDests@ ++ * function. ++ * ++ * Note: On macOS this function also gets the default paper from the system ++ * preferences (~/L/P/org.cups.PrintingPrefs.plist) and includes it in the ++ * options array for each destination that supports it. + */ + +-void +-cupsFreeDests(int num_dests, /* I - Number of destinations */ +- cups_dest_t *dests) /* I - Destinations */ ++int /* O - Number of destinations */ ++_cupsGetDests(http_t *http, /* I - Connection to server or ++ * @code CUPS_HTTP_DEFAULT@ */ ++ ipp_op_t op, /* I - IPP operation */ ++ const char *name, /* I - Name of destination */ ++ cups_dest_t **dests, /* IO - Destinations */ ++ cups_ptype_t type, /* I - Printer type bits */ ++ cups_ptype_t mask) /* I - Printer type mask */ + { +- int i; /* Looping var */ ++ int num_dests = 0; /* Number of destinations */ + cups_dest_t *dest; /* Current destination */ ++ ipp_t *request, /* IPP Request */ ++ *response; /* IPP Response */ ++ ipp_attribute_t *attr; /* Current attribute */ ++ const char *printer_name; /* printer-name attribute */ ++ char uri[1024]; /* printer-uri value */ ++ int num_options; /* Number of options */ ++ cups_option_t *options; /* Options */ ++#ifdef __APPLE__ ++ char media_default[41]; /* Default paper size */ ++#endif /* __APPLE__ */ ++ char optname[1024], /* Option name */ ++ value[2048], /* Option value */ ++ *ptr; /* Pointer into name/value */ ++ static const char * const pattrs[] = /* Attributes we're interested in */ ++ { ++ "auth-info-required", ++ "device-uri", ++ "job-sheets-default", ++ "marker-change-time", ++ "marker-colors", ++ "marker-high-levels", ++ "marker-levels", ++ "marker-low-levels", ++ "marker-message", ++ "marker-names", ++ "marker-types", ++#ifdef __APPLE__ ++ "media-supported", ++#endif /* __APPLE__ */ ++ "printer-commands", ++ "printer-defaults", ++ "printer-info", ++ "printer-is-accepting-jobs", ++ "printer-is-shared", ++ "printer-is-temporary", ++ "printer-location", ++ "printer-make-and-model", ++ "printer-mandatory-job-attributes", ++ "printer-name", ++ "printer-state", ++ "printer-state-change-time", ++ "printer-state-reasons", ++ "printer-type", ++ "printer-uri-supported" ++ }; + + +- if (num_dests == 0 || dests == NULL) +- return; +- +- for (i = num_dests, dest = dests; i > 0; i --, dest ++) +- { +- _cupsStrFree(dest->name); +- _cupsStrFree(dest->instance); +- +- cupsFreeOptions(dest->num_options, dest->options); +- } ++ DEBUG_printf(("_cupsGetDests(http=%p, op=%x(%s), name=\"%s\", dests=%p, type=%x, mask=%x)", (void *)http, op, ippOpString(op), name, (void *)dests, type, mask)); + +- free(dests); +-} ++#ifdef __APPLE__ ++ /* ++ * Get the default paper size... ++ */ + ++ appleGetPaperSize(media_default, sizeof(media_default)); ++ DEBUG_printf(("1_cupsGetDests: Default media is '%s'.", media_default)); ++#endif /* __APPLE__ */ + +-/* +- * 'cupsGetDest()' - Get the named destination from the list. +- * +- * Use the @link cupsEnumDests@ or @link cupsGetDests2@ functions to get a +- * list of supported destinations for the current user. +- */ ++ /* ++ * Build a IPP_OP_CUPS_GET_PRINTERS or IPP_OP_GET_PRINTER_ATTRIBUTES request, which ++ * require the following attributes: ++ * ++ * attributes-charset ++ * attributes-natural-language ++ * requesting-user-name ++ * printer-uri [for IPP_OP_GET_PRINTER_ATTRIBUTES] ++ */ + +-cups_dest_t * /* O - Destination pointer or @code NULL@ */ +-cupsGetDest(const char *name, /* I - Destination name or @code NULL@ for the default destination */ +- const char *instance, /* I - Instance name or @code NULL@ */ +- int num_dests, /* I - Number of destinations */ +- cups_dest_t *dests) /* I - Destinations */ +-{ +- int diff, /* Result of comparison */ +- match; /* Matching index */ ++ request = ippNewRequest(op); + ++ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, ++ "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]), ++ NULL, pattrs); + +- if (num_dests <= 0 || !dests) +- return (NULL); ++ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, ++ "requesting-user-name", NULL, cupsUser()); + +- if (!name) ++ if (name && op != IPP_OP_CUPS_GET_DEFAULT) + { +- /* +- * NULL name for default printer. +- */ +- +- while (num_dests > 0) +- { +- if (dests->is_default) +- return (dests); +- +- num_dests --; +- dests ++; +- } ++ httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, ++ "localhost", ippPort(), "/printers/%s", name); ++ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ++ uri); + } +- else ++ else if (mask) + { +- /* +- * Lookup name and optionally the instance... +- */ +- +- match = cups_find_dest(name, instance, num_dests, dests, -1, &diff); +- +- if (!diff) +- return (dests + match); ++ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type", (int)type); ++ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask", (int)mask); + } + +- return (NULL); +-} +- +- +-/* +- * '_cupsGetDestResource()' - Get the resource path and URI for a destination. +- */ +- +-const char * /* O - Printer URI */ +-_cupsGetDestResource( +- cups_dest_t *dest, /* I - Destination */ +- char *resource, /* I - Resource buffer */ +- size_t resourcesize) /* I - Size of resource buffer */ +-{ +- const char *uri; /* Printer URI */ +- char scheme[32], /* URI scheme */ +- userpass[256], /* Username and password (unused) */ +- hostname[256]; /* Hostname */ +- int port; /* Port number */ +- +- +- DEBUG_printf(("_cupsGetDestResource(dest=%p(%s), resource=%p, resourcesize=%d)", (void *)dest, dest->name, (void *)resource, (int)resourcesize)); +- + /* +- * Range check input... ++ * Do the request and get back a response... + */ + +- if (!dest || !resource || resourcesize < 1) ++ if ((response = cupsDoRequest(http, request, "/")) != NULL) + { +- if (resource) +- *resource = '\0'; ++ for (attr = response->attrs; attr != NULL; attr = attr->next) ++ { ++ /* ++ * Skip leading attributes until we hit a printer... ++ */ + +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); +- return (NULL); +- } ++ while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) ++ attr = attr->next; + +- /* +- * Grab the printer URI... +- */ ++ if (attr == NULL) ++ break; + +- if ((uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options)) == NULL) +- { +- if ((uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL) +- { +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +- if (strstr(uri, "._tcp")) +- uri = cups_dnssd_resolve(dest, uri, 5000, NULL, NULL, NULL); +-#endif /* HAVE_DNSSD || HAVE_AVAHI */ +- } ++ /* ++ * Pull the needed attributes from this printer... ++ */ + +- if (uri) +- { +- DEBUG_printf(("1_cupsGetDestResource: Resolved printer-uri-supported=\"%s\"", uri)); ++ printer_name = NULL; ++ num_options = 0; ++ options = NULL; + +- uri = _cupsCreateDest(dest->name, cupsGetOption("printer-info", dest->num_options, dest->options), NULL, uri, resource, resourcesize); +- } ++ for (; attr && attr->group_tag == IPP_TAG_PRINTER; attr = attr->next) ++ { ++ if (attr->value_tag != IPP_TAG_INTEGER && ++ attr->value_tag != IPP_TAG_ENUM && ++ attr->value_tag != IPP_TAG_BOOLEAN && ++ attr->value_tag != IPP_TAG_TEXT && ++ attr->value_tag != IPP_TAG_TEXTLANG && ++ attr->value_tag != IPP_TAG_NAME && ++ attr->value_tag != IPP_TAG_NAMELANG && ++ attr->value_tag != IPP_TAG_KEYWORD && ++ attr->value_tag != IPP_TAG_RANGE && ++ attr->value_tag != IPP_TAG_URI) ++ continue; + +- if (uri) +- { +- DEBUG_printf(("1_cupsGetDestResource: Local printer-uri-supported=\"%s\"", uri)); ++ if (!strcmp(attr->name, "auth-info-required") || ++ !strcmp(attr->name, "device-uri") || ++ !strcmp(attr->name, "marker-change-time") || ++ !strcmp(attr->name, "marker-colors") || ++ !strcmp(attr->name, "marker-high-levels") || ++ !strcmp(attr->name, "marker-levels") || ++ !strcmp(attr->name, "marker-low-levels") || ++ !strcmp(attr->name, "marker-message") || ++ !strcmp(attr->name, "marker-names") || ++ !strcmp(attr->name, "marker-types") || ++ !strcmp(attr->name, "printer-commands") || ++ !strcmp(attr->name, "printer-info") || ++ !strcmp(attr->name, "printer-is-shared") || ++ !strcmp(attr->name, "printer-is-temporary") || ++ !strcmp(attr->name, "printer-make-and-model") || ++ !strcmp(attr->name, "printer-mandatory-job-attributes") || ++ !strcmp(attr->name, "printer-state") || ++ !strcmp(attr->name, "printer-state-change-time") || ++ !strcmp(attr->name, "printer-type") || ++ !strcmp(attr->name, "printer-is-accepting-jobs") || ++ !strcmp(attr->name, "printer-location") || ++ !strcmp(attr->name, "printer-state-reasons") || ++ !strcmp(attr->name, "printer-uri-supported")) ++ { ++ /* ++ * Add a printer description attribute... ++ */ + +- dest->num_options = cupsAddOption("printer-uri-supported", uri, dest->num_options, &dest->options); ++ num_options = cupsAddOption(attr->name, ++ cups_make_string(attr, value, ++ sizeof(value)), ++ num_options, &options); ++ } ++#ifdef __APPLE__ ++ else if (!strcmp(attr->name, "media-supported") && media_default[0]) ++ { ++ /* ++ * See if we can set a default media size... ++ */ + +- uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options); +- } +- else +- { +- DEBUG_puts("1_cupsGetDestResource: No printer-uri-supported found."); ++ int i; /* Looping var */ + +- if (resource) +- *resource = '\0'; ++ for (i = 0; i < attr->num_values; i ++) ++ if (!_cups_strcasecmp(media_default, attr->values[i].string.text)) ++ { ++ DEBUG_printf(("1_cupsGetDests: Setting media to '%s'.", media_default)); ++ num_options = cupsAddOption("media", media_default, num_options, &options); ++ break; ++ } ++ } ++#endif /* __APPLE__ */ ++ else if (!strcmp(attr->name, "printer-name") && ++ attr->value_tag == IPP_TAG_NAME) ++ printer_name = attr->values[0].string.text; ++ else if (strncmp(attr->name, "notify-", 7) && ++ strncmp(attr->name, "print-quality-", 14) && ++ (attr->value_tag == IPP_TAG_BOOLEAN || ++ attr->value_tag == IPP_TAG_ENUM || ++ attr->value_tag == IPP_TAG_INTEGER || ++ attr->value_tag == IPP_TAG_KEYWORD || ++ attr->value_tag == IPP_TAG_NAME || ++ attr->value_tag == IPP_TAG_RANGE) && ++ (ptr = strstr(attr->name, "-default")) != NULL) ++ { ++ /* ++ * Add a default option... ++ */ + +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOENT), 0); ++ strlcpy(optname, attr->name, sizeof(optname)); ++ optname[ptr - attr->name] = '\0'; + +- return (NULL); +- } +- } +- else +- { +- DEBUG_printf(("1_cupsGetDestResource: printer-uri-supported=\"%s\"", uri)); ++ if (_cups_strcasecmp(optname, "media") || !cupsGetOption("media", num_options, options)) ++ num_options = cupsAddOption(optname, cups_make_string(attr, value, sizeof(value)), num_options, &options); ++ } ++ } + +- if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), +- userpass, sizeof(userpass), hostname, sizeof(hostname), +- &port, resource, (int)resourcesize) < HTTP_URI_STATUS_OK) +- { +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer-uri."), 1); ++ /* ++ * See if we have everything needed... ++ */ + +- return (NULL); ++ if (!printer_name) ++ { ++ cupsFreeOptions(num_options, options); ++ ++ if (attr == NULL) ++ break; ++ else ++ continue; ++ } ++ ++ if ((dest = cups_add_dest(printer_name, NULL, &num_dests, dests)) != NULL) ++ { ++ dest->num_options = num_options; ++ dest->options = options; ++ } ++ else ++ cupsFreeOptions(num_options, options); ++ ++ if (attr == NULL) ++ break; + } ++ ++ ippDelete(response); + } + +- DEBUG_printf(("1_cupsGetDestResource: resource=\"%s\"", resource)); ++ /* ++ * Return the count... ++ */ + +- return (uri); ++ return (num_dests); + } + + + /* +- * 'cupsGetDestWithURI()' - Get a destination associated with a URI. ++ * 'cupsGetDests()' - Get the list of destinations from the default server. + * +- * "name" is the desired name for the printer. If @code NULL@, a name will be +- * created using the URI. ++ * Starting with CUPS 1.2, the returned list of destinations include the ++ * "printer-info", "printer-is-accepting-jobs", "printer-is-shared", ++ * "printer-make-and-model", "printer-state", "printer-state-change-time", ++ * "printer-state-reasons", "printer-type", and "printer-uri-supported" ++ * attributes as options. + * +- * "uri" is the "ipp" or "ipps" URI for the printer. ++ * CUPS 1.4 adds the "marker-change-time", "marker-colors", ++ * "marker-high-levels", "marker-levels", "marker-low-levels", "marker-message", ++ * "marker-names", "marker-types", and "printer-commands" attributes as options. + * +- * @since CUPS 2.0/macOS 10.10@ ++ * CUPS 2.2 adds accessible IPP printers to the list of destinations that can ++ * be used. The "printer-uri-supported" option will be present for those IPP ++ * printers that have been recently used. ++ * ++ * Use the @link cupsFreeDests@ function to free the destination list and ++ * the @link cupsGetDest@ function to find a particular destination. ++ * ++ * @exclude all@ + */ + +-cups_dest_t * /* O - Destination or @code NULL@ */ +-cupsGetDestWithURI(const char *name, /* I - Desired printer name or @code NULL@ */ +- const char *uri) /* I - URI for the printer */ ++int /* O - Number of destinations */ ++cupsGetDests(cups_dest_t **dests) /* O - Destinations */ + { +- cups_dest_t *dest; /* New destination */ +- char temp[1024], /* Temporary string */ +- scheme[256], /* Scheme from URI */ +- userpass[256], /* Username:password from URI */ +- hostname[256], /* Hostname from URI */ +- resource[1024], /* Resource path from URI */ +- *ptr; /* Pointer into string */ +- const char *info; /* printer-info string */ +- int port; /* Port number from URI */ ++ return (cupsGetDests2(CUPS_HTTP_DEFAULT, dests)); ++} + + +- /* +- * Range check input... ++/* ++ * 'cupsGetDests2()' - Get the list of destinations from the specified server. ++ * ++ * Starting with CUPS 1.2, the returned list of destinations include the ++ * "printer-info", "printer-is-accepting-jobs", "printer-is-shared", ++ * "printer-make-and-model", "printer-state", "printer-state-change-time", ++ * "printer-state-reasons", "printer-type", and "printer-uri-supported" ++ * attributes as options. ++ * ++ * CUPS 1.4 adds the "marker-change-time", "marker-colors", ++ * "marker-high-levels", "marker-levels", "marker-low-levels", "marker-message", ++ * "marker-names", "marker-types", and "printer-commands" attributes as options. ++ * ++ * CUPS 2.2 adds accessible IPP printers to the list of destinations that can ++ * be used. The "printer-uri-supported" option will be present for those IPP ++ * printers that have been recently used. ++ * ++ * Use the @link cupsFreeDests@ function to free the destination list and ++ * the @link cupsGetDest@ function to find a particular destination. ++ * ++ * @since CUPS 1.1.21/macOS 10.4@ ++ */ ++ ++int /* O - Number of destinations */ ++cupsGetDests2(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ ++ cups_dest_t **dests) /* O - Destinations */ ++{ ++ _cups_getdata_t data; /* Enumeration data */ ++ cups_dest_t *dest; /* Current destination */ ++ const char *home; /* HOME environment variable */ ++ char filename[1024]; /* Local ~/.cups/lpoptions file */ ++ const char *defprinter; /* Default printer */ ++ char name[1024], /* Copy of printer name */ ++ *instance, /* Pointer to instance name */ ++ *user_default; /* User default printer */ ++ int num_reals; /* Number of real queues */ ++ cups_dest_t *reals; /* Real queues */ ++ _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ ++ ++ ++ DEBUG_printf(("cupsGetDests2(http=%p, dests=%p)", (void *)http, (void *)dests)); ++ ++/* ++ * Range check the input... + */ + +- if (!uri) ++ if (!dests) + { +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); +- return (NULL); ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad NULL dests pointer"), 1); ++ DEBUG_puts("1cupsGetDests2: NULL dests pointer, returning 0."); ++ return (0); + } + +- if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK || +- (strncmp(uri, "ipp://", 6) && strncmp(uri, "ipps://", 7))) +- { +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer-uri."), 1); ++ /* ++ * Grab the printers and classes... ++ */ + +- return (NULL); +- } ++ data.num_dests = 0; ++ data.dests = NULL; + +- if (name) ++ cups_enum_dests(http, 0, _CUPS_DNSSD_GET_DESTS, NULL, 0, 0, (cups_dest_cb_t)cups_get_cb, &data); ++ ++ /* ++ * Make a copy of the "real" queues for a later sanity check... ++ */ ++ ++ if (data.num_dests > 0) + { +- info = name; ++ num_reals = data.num_dests; ++ reals = calloc((size_t)num_reals, sizeof(cups_dest_t)); ++ ++ if (reals) ++ memcpy(reals, data.dests, (size_t)num_reals * sizeof(cups_dest_t)); ++ else ++ num_reals = 0; + } + else ++ { ++ num_reals = 0; ++ reals = NULL; ++ } ++ ++ /* ++ * Grab the default destination... ++ */ ++ ++ if ((user_default = _cupsUserDefault(name, sizeof(name))) != NULL) ++ defprinter = name; ++ else if ((defprinter = cupsGetDefault2(http)) != NULL) ++ { ++ strlcpy(name, defprinter, sizeof(name)); ++ defprinter = name; ++ } ++ ++ if (defprinter) + { + /* +- * Create the name from the URI... ++ * Separate printer and instance name... + */ + +- if (strstr(hostname, "._tcp")) ++ if ((instance = strchr(name, '/')) != NULL) ++ *instance++ = '\0'; ++ ++ /* ++ * Lookup the printer and instance and make it the default... ++ */ ++ ++ if ((dest = cupsGetDest(name, instance, data.num_dests, data.dests)) != NULL) ++ dest->is_default = 1; ++ } ++ else ++ instance = NULL; ++ ++ /* ++ * Load the /etc/cups/lpoptions and ~/.cups/lpoptions files... ++ */ ++ ++ snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); ++ data.num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL, data.num_dests, &data.dests); ++ ++ if ((home = getenv("HOME")) != NULL) ++ { ++ snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); ++ ++ data.num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL, data.num_dests, &data.dests); ++ } ++ ++ /* ++ * Validate the current default destination - this prevents old ++ * Default lines in /etc/cups/lpoptions and ~/.cups/lpoptions from ++ * pointing to a non-existent printer or class... ++ */ ++ ++ if (num_reals) ++ { ++ /* ++ * See if we have a default printer... ++ */ ++ ++ if ((dest = cupsGetDest(NULL, NULL, data.num_dests, data.dests)) != NULL) + { + /* +- * Use the service instance name... ++ * Have a default; see if it is real... + */ + +- if ((ptr = strstr(hostname, "._")) != NULL) +- *ptr = '\0'; ++ if (!cupsGetDest(dest->name, NULL, num_reals, reals)) ++ { ++ /* ++ * Remove the non-real printer from the list, since we don't want jobs ++ * going to an unexpected printer... () ++ */ + +- cups_queue_name(temp, hostname, sizeof(temp)); +- name = temp; +- info = hostname; +- } +- else if (!strncmp(resource, "/classes/", 9)) +- { +- snprintf(temp, sizeof(temp), "%s @ %s", resource + 9, hostname); +- name = resource + 9; +- info = temp; +- } +- else if (!strncmp(resource, "/printers/", 10)) +- { +- snprintf(temp, sizeof(temp), "%s @ %s", resource + 10, hostname); +- name = resource + 10; +- info = temp; +- } +- else +- { +- name = hostname; +- info = hostname; ++ data.num_dests = cupsRemoveDest(dest->name, dest->instance, data.num_dests, &data.dests); ++ } + } ++ ++ /* ++ * Free memory... ++ */ ++ ++ free(reals); + } + + /* +- * Create the destination... ++ * Return the number of destinations... + */ + +- if ((dest = calloc(1, sizeof(cups_dest_t))) == NULL) +- { +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); +- return (NULL); +- } ++ *dests = data.dests; + +- dest->name = _cupsStrAlloc(name); +- dest->num_options = cupsAddOption("device-uri", uri, dest->num_options, &(dest->options)); +- dest->num_options = cupsAddOption("printer-info", info, dest->num_options, &(dest->options)); ++ if (data.num_dests > 0) ++ _cupsSetError(IPP_STATUS_OK, NULL, 0); + +- return (dest); ++ DEBUG_printf(("1cupsGetDests2: Returning %d destinations.", data.num_dests)); ++ ++ return (data.num_dests); + } + + + /* +- * '_cupsGetDests()' - Get destinations from a server. +- * +- * "op" is IPP_OP_CUPS_GET_PRINTERS to get a full list, IPP_OP_CUPS_GET_DEFAULT +- * to get the system-wide default printer, or IPP_OP_GET_PRINTER_ATTRIBUTES for +- * a known printer. ++ * 'cupsGetNamedDest()' - Get options for the named destination. + * +- * "name" is the name of an existing printer and is only used when "op" is +- * IPP_OP_GET_PRINTER_ATTRIBUTES. ++ * This function is optimized for retrieving a single destination and should ++ * be used instead of @link cupsGetDests2@ and @link cupsGetDest@ when you ++ * either know the name of the destination or want to print to the default ++ * destination. If @code NULL@ is returned, the destination does not exist or ++ * there is no default destination. + * +- * "dest" is initialized to point to the array of destinations. ++ * If "http" is @code CUPS_HTTP_DEFAULT@, the connection to the default print ++ * server will be used. + * +- * 0 is returned if there are no printers, no default printer, or the named +- * printer does not exist, respectively. ++ * If "name" is @code NULL@, the default printer for the current user will be ++ * returned. + * +- * Free the memory used by the destination array using the @link cupsFreeDests@ +- * function. ++ * The returned destination must be freed using @link cupsFreeDests@ with a ++ * "num_dests" value of 1. + * +- * Note: On macOS this function also gets the default paper from the system +- * preferences (~/L/P/org.cups.PrintingPrefs.plist) and includes it in the +- * options array for each destination that supports it. ++ * @since CUPS 1.4/macOS 10.6@ + */ + +-int /* O - Number of destinations */ +-_cupsGetDests(http_t *http, /* I - Connection to server or +- * @code CUPS_HTTP_DEFAULT@ */ +- ipp_op_t op, /* I - IPP operation */ +- const char *name, /* I - Name of destination */ +- cups_dest_t **dests, /* IO - Destinations */ +- cups_ptype_t type, /* I - Printer type bits */ +- cups_ptype_t mask) /* I - Printer type mask */ ++cups_dest_t * /* O - Destination or @code NULL@ */ ++cupsGetNamedDest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ ++ const char *name, /* I - Destination name or @code NULL@ for the default destination */ ++ const char *instance) /* I - Instance name or @code NULL@ */ + { +- int num_dests = 0; /* Number of destinations */ +- cups_dest_t *dest; /* Current destination */ +- ipp_t *request, /* IPP Request */ +- *response; /* IPP Response */ +- ipp_attribute_t *attr; /* Current attribute */ +- const char *printer_name; /* printer-name attribute */ +- char uri[1024]; /* printer-uri value */ +- int num_options; /* Number of options */ +- cups_option_t *options; /* Options */ +-#ifdef __APPLE__ +- char media_default[41]; /* Default paper size */ +-#endif /* __APPLE__ */ +- char optname[1024], /* Option name */ +- value[2048], /* Option value */ +- *ptr; /* Pointer into name/value */ +- static const char * const pattrs[] = /* Attributes we're interested in */ +- { +- "auth-info-required", +- "device-uri", +- "job-sheets-default", +- "marker-change-time", +- "marker-colors", +- "marker-high-levels", +- "marker-levels", +- "marker-low-levels", +- "marker-message", +- "marker-names", +- "marker-types", +-#ifdef __APPLE__ +- "media-supported", +-#endif /* __APPLE__ */ +- "printer-commands", +- "printer-defaults", +- "printer-info", +- "printer-is-accepting-jobs", +- "printer-is-shared", +- "printer-is-temporary", +- "printer-location", +- "printer-make-and-model", +- "printer-mandatory-job-attributes", +- "printer-name", +- "printer-state", +- "printer-state-change-time", +- "printer-state-reasons", +- "printer-type", +- "printer-uri-supported" +- }; ++ const char *dest_name; /* Working destination name */ ++ cups_dest_t *dest; /* Destination */ ++ char filename[1024], /* Path to lpoptions */ ++ defname[256]; /* Default printer name */ ++ const char *home = getenv("HOME"); /* Home directory */ ++ int set_as_default = 0; /* Set returned destination as default */ ++ ipp_op_t op = IPP_OP_GET_PRINTER_ATTRIBUTES; ++ /* IPP operation to get server ops */ ++ _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ + + +- DEBUG_printf(("_cupsGetDests(http=%p, op=%x(%s), name=\"%s\", dests=%p, type=%x, mask=%x)", (void *)http, op, ippOpString(op), name, (void *)dests, type, mask)); ++ DEBUG_printf(("cupsGetNamedDest(http=%p, name=\"%s\", instance=\"%s\")", (void *)http, name, instance)); + +-#ifdef __APPLE__ + /* +- * Get the default paper size... ++ * If "name" is NULL, find the default destination... + */ + +- appleGetPaperSize(media_default, sizeof(media_default)); +- DEBUG_printf(("1_cupsGetDests: Default media is '%s'.", media_default)); +-#endif /* __APPLE__ */ +- +- /* +- * Build a IPP_OP_CUPS_GET_PRINTERS or IPP_OP_GET_PRINTER_ATTRIBUTES request, which +- * require the following attributes: +- * +- * attributes-charset +- * attributes-natural-language +- * requesting-user-name +- * printer-uri [for IPP_OP_GET_PRINTER_ATTRIBUTES] +- */ ++ dest_name = name; + +- request = ippNewRequest(op); ++ if (!dest_name) ++ { ++ set_as_default = 1; ++ dest_name = _cupsUserDefault(defname, sizeof(defname)); + +- ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, +- "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]), +- NULL, pattrs); ++ if (dest_name) ++ { ++ char *ptr; /* Temporary pointer... */ + +- ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, +- "requesting-user-name", NULL, cupsUser()); ++ if ((ptr = strchr(defname, '/')) != NULL) ++ { ++ *ptr++ = '\0'; ++ instance = ptr; ++ } ++ else ++ instance = NULL; ++ } ++ else if (home) ++ { ++ /* ++ * No default in the environment, try the user's lpoptions files... ++ */ + +- if (name && op != IPP_OP_CUPS_GET_DEFAULT) +- { +- httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, +- "localhost", ippPort(), "/printers/%s", name); +- ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, +- uri); +- } +- else if (mask) +- { +- ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type", (int)type); +- ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask", (int)mask); +- } ++ snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); + +- /* +- * Do the request and get back a response... +- */ ++ dest_name = cups_get_default(filename, defname, sizeof(defname), &instance); ++ } + +- if ((response = cupsDoRequest(http, request, "/")) != NULL) +- { +- for (attr = response->attrs; attr != NULL; attr = attr->next) ++ if (!dest_name) + { + /* +- * Skip leading attributes until we hit a printer... ++ * Still not there? Try the system lpoptions file... + */ + +- while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) +- attr = attr->next; +- +- if (attr == NULL) +- break; ++ snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); ++ dest_name = cups_get_default(filename, defname, sizeof(defname), &instance); ++ } + ++ if (!dest_name) ++ { + /* +- * Pull the needed attributes from this printer... ++ * No locally-set default destination, ask the server... + */ + +- printer_name = NULL; +- num_options = 0; +- options = NULL; ++ op = IPP_OP_CUPS_GET_DEFAULT; + +- for (; attr && attr->group_tag == IPP_TAG_PRINTER; attr = attr->next) +- { +- if (attr->value_tag != IPP_TAG_INTEGER && +- attr->value_tag != IPP_TAG_ENUM && +- attr->value_tag != IPP_TAG_BOOLEAN && +- attr->value_tag != IPP_TAG_TEXT && +- attr->value_tag != IPP_TAG_TEXTLANG && +- attr->value_tag != IPP_TAG_NAME && +- attr->value_tag != IPP_TAG_NAMELANG && +- attr->value_tag != IPP_TAG_KEYWORD && +- attr->value_tag != IPP_TAG_RANGE && +- attr->value_tag != IPP_TAG_URI) +- continue; ++ DEBUG_puts("1cupsGetNamedDest: Asking server for default printer..."); ++ } ++ else ++ DEBUG_printf(("1cupsGetNamedDest: Using name=\"%s\"...", name)); ++ } + +- if (!strcmp(attr->name, "auth-info-required") || +- !strcmp(attr->name, "device-uri") || +- !strcmp(attr->name, "marker-change-time") || +- !strcmp(attr->name, "marker-colors") || +- !strcmp(attr->name, "marker-high-levels") || +- !strcmp(attr->name, "marker-levels") || +- !strcmp(attr->name, "marker-low-levels") || +- !strcmp(attr->name, "marker-message") || +- !strcmp(attr->name, "marker-names") || +- !strcmp(attr->name, "marker-types") || +- !strcmp(attr->name, "printer-commands") || +- !strcmp(attr->name, "printer-info") || +- !strcmp(attr->name, "printer-is-shared") || +- !strcmp(attr->name, "printer-is-temporary") || +- !strcmp(attr->name, "printer-make-and-model") || +- !strcmp(attr->name, "printer-mandatory-job-attributes") || +- !strcmp(attr->name, "printer-state") || +- !strcmp(attr->name, "printer-state-change-time") || +- !strcmp(attr->name, "printer-type") || +- !strcmp(attr->name, "printer-is-accepting-jobs") || +- !strcmp(attr->name, "printer-location") || +- !strcmp(attr->name, "printer-state-reasons") || +- !strcmp(attr->name, "printer-uri-supported")) +- { +- /* +- * Add a printer description attribute... +- */ ++ /* ++ * Get the printer's attributes... ++ */ + +- num_options = cupsAddOption(attr->name, +- cups_make_string(attr, value, +- sizeof(value)), +- num_options, &options); +- } +-#ifdef __APPLE__ +- else if (!strcmp(attr->name, "media-supported") && media_default[0]) +- { +- /* +- * See if we can set a default media size... +- */ ++ if (!_cupsGetDests(http, op, dest_name, &dest, 0, 0)) ++ { ++ if (name) ++ { ++ _cups_namedata_t data; /* Callback data */ + +- int i; /* Looping var */ ++ DEBUG_puts("1cupsGetNamedDest: No queue found for printer, looking on network..."); + +- for (i = 0; i < attr->num_values; i ++) +- if (!_cups_strcasecmp(media_default, attr->values[i].string.text)) +- { +- DEBUG_printf(("1_cupsGetDests: Setting media to '%s'.", media_default)); +- num_options = cupsAddOption("media", media_default, num_options, &options); +- break; +- } +- } +-#endif /* __APPLE__ */ +- else if (!strcmp(attr->name, "printer-name") && +- attr->value_tag == IPP_TAG_NAME) +- printer_name = attr->values[0].string.text; +- else if (strncmp(attr->name, "notify-", 7) && +- strncmp(attr->name, "print-quality-", 14) && +- (attr->value_tag == IPP_TAG_BOOLEAN || +- attr->value_tag == IPP_TAG_ENUM || +- attr->value_tag == IPP_TAG_INTEGER || +- attr->value_tag == IPP_TAG_KEYWORD || +- attr->value_tag == IPP_TAG_NAME || +- attr->value_tag == IPP_TAG_RANGE) && +- (ptr = strstr(attr->name, "-default")) != NULL) +- { +- /* +- * Add a default option... +- */ ++ data.name = name; ++ data.dest = NULL; + +- strlcpy(optname, attr->name, sizeof(optname)); +- optname[ptr - attr->name] = '\0'; ++ cupsEnumDests(0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_name_cb, &data); + +- if (_cups_strcasecmp(optname, "media") || !cupsGetOption("media", num_options, options)) +- num_options = cupsAddOption(optname, cups_make_string(attr, value, sizeof(value)), num_options, &options); +- } +- } ++ if (!data.dest) ++ return (NULL); + +- /* +- * See if we have everything needed... +- */ ++ dest = data.dest; ++ } ++ else ++ return (NULL); ++ } + +- if (!printer_name) +- { +- cupsFreeOptions(num_options, options); ++ DEBUG_printf(("1cupsGetNamedDest: Got dest=%p", (void *)dest)); + +- if (attr == NULL) +- break; +- else +- continue; +- } ++ if (instance) ++ dest->instance = _cupsStrAlloc(instance); + +- if ((dest = cups_add_dest(printer_name, NULL, &num_dests, dests)) != NULL) +- { +- dest->num_options = num_options; +- dest->options = options; +- } +- else +- cupsFreeOptions(num_options, options); ++ if (set_as_default) ++ dest->is_default = 1; + +- if (attr == NULL) +- break; +- } ++ /* ++ * Then add local options... ++ */ + +- ippDelete(response); ++ snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); ++ cups_get_dests(filename, dest_name, instance, 1, 1, &dest); ++ ++ if (home) ++ { ++ snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); ++ ++ cups_get_dests(filename, dest_name, instance, 1, 1, &dest); + } + + /* +- * Return the count... ++ * Return the result... + */ + +- return (num_dests); ++ return (dest); + } + + + /* +- * 'cupsGetDests()' - Get the list of destinations from the default server. +- * +- * Starting with CUPS 1.2, the returned list of destinations include the +- * "printer-info", "printer-is-accepting-jobs", "printer-is-shared", +- * "printer-make-and-model", "printer-state", "printer-state-change-time", +- * "printer-state-reasons", "printer-type", and "printer-uri-supported" +- * attributes as options. +- * +- * CUPS 1.4 adds the "marker-change-time", "marker-colors", +- * "marker-high-levels", "marker-levels", "marker-low-levels", "marker-message", +- * "marker-names", "marker-types", and "printer-commands" attributes as options. +- * +- * CUPS 2.2 adds accessible IPP printers to the list of destinations that can +- * be used. The "printer-uri-supported" option will be present for those IPP +- * printers that have been recently used. ++ * 'cupsRemoveDest()' - Remove a destination from the destination list. + * +- * Use the @link cupsFreeDests@ function to free the destination list and +- * the @link cupsGetDest@ function to find a particular destination. ++ * Removing a destination/instance does not delete the class or printer ++ * queue, merely the lpoptions for that destination/instance. Use the ++ * @link cupsSetDests@ or @link cupsSetDests2@ functions to save the new ++ * options for the user. + * +- * @exclude all@ ++ * @since CUPS 1.3/macOS 10.5@ + */ + +-int /* O - Number of destinations */ +-cupsGetDests(cups_dest_t **dests) /* O - Destinations */ ++int /* O - New number of destinations */ ++cupsRemoveDest(const char *name, /* I - Destination name */ ++ const char *instance, /* I - Instance name or @code NULL@ */ ++ int num_dests, /* I - Number of destinations */ ++ cups_dest_t **dests) /* IO - Destinations */ + { +- return (cupsGetDests2(CUPS_HTTP_DEFAULT, dests)); +-} ++ int i; /* Index into destinations */ ++ cups_dest_t *dest; /* Pointer to destination */ + + +-/* +- * 'cupsGetDests2()' - Get the list of destinations from the specified server. +- * +- * Starting with CUPS 1.2, the returned list of destinations include the +- * "printer-info", "printer-is-accepting-jobs", "printer-is-shared", +- * "printer-make-and-model", "printer-state", "printer-state-change-time", +- * "printer-state-reasons", "printer-type", and "printer-uri-supported" +- * attributes as options. +- * +- * CUPS 1.4 adds the "marker-change-time", "marker-colors", +- * "marker-high-levels", "marker-levels", "marker-low-levels", "marker-message", +- * "marker-names", "marker-types", and "printer-commands" attributes as options. +- * +- * CUPS 2.2 adds accessible IPP printers to the list of destinations that can +- * be used. The "printer-uri-supported" option will be present for those IPP +- * printers that have been recently used. +- * +- * Use the @link cupsFreeDests@ function to free the destination list and +- * the @link cupsGetDest@ function to find a particular destination. +- * +- * @since CUPS 1.1.21/macOS 10.4@ +- */ +- +-int /* O - Number of destinations */ +-cupsGetDests2(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ +- cups_dest_t **dests) /* O - Destinations */ +-{ +- _cups_getdata_t data; /* Enumeration data */ +- cups_dest_t *dest; /* Current destination */ +- const char *home; /* HOME environment variable */ +- char filename[1024]; /* Local ~/.cups/lpoptions file */ +- const char *defprinter; /* Default printer */ +- char name[1024], /* Copy of printer name */ +- *instance, /* Pointer to instance name */ +- *user_default; /* User default printer */ +- int num_reals; /* Number of real queues */ +- cups_dest_t *reals; /* Real queues */ +- _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ +- ++ /* ++ * Find the destination... ++ */ + +- DEBUG_printf(("cupsGetDests2(http=%p, dests=%p)", (void *)http, (void *)dests)); ++ if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL) ++ return (num_dests); + +-/* +- * Range check the input... ++ /* ++ * Free memory... + */ + +- if (!dests) +- { +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad NULL dests pointer"), 1); +- DEBUG_puts("1cupsGetDests2: NULL dests pointer, returning 0."); +- return (0); +- } ++ _cupsStrFree(dest->name); ++ _cupsStrFree(dest->instance); ++ cupsFreeOptions(dest->num_options, dest->options); + + /* +- * Grab the printers and classes... ++ * Remove the destination from the array... + */ + +- data.num_dests = 0; +- data.dests = NULL; ++ num_dests --; ++ ++ i = (int)(dest - *dests); ++ ++ if (i < num_dests) ++ memmove(dest, dest + 1, (size_t)(num_dests - i) * sizeof(cups_dest_t)); ++ ++ return (num_dests); ++} ++ ++ ++/* ++ * 'cupsSetDefaultDest()' - Set the default destination. ++ * ++ * @since CUPS 1.3/macOS 10.5@ ++ */ ++ ++void ++cupsSetDefaultDest( ++ const char *name, /* I - Destination name */ ++ const char *instance, /* I - Instance name or @code NULL@ */ ++ int num_dests, /* I - Number of destinations */ ++ cups_dest_t *dests) /* I - Destinations */ ++{ ++ int i; /* Looping var */ ++ cups_dest_t *dest; /* Current destination */ + +- cupsEnumDests(0, _CUPS_DNSSD_GET_DESTS, NULL, 0, 0, (cups_dest_cb_t)cups_get_cb, &data); + + /* +- * Make a copy of the "real" queues for a later sanity check... ++ * Range check input... + */ + +- if (data.num_dests > 0) +- { +- num_reals = data.num_dests; +- reals = calloc((size_t)num_reals, sizeof(cups_dest_t)); +- +- if (reals) +- memcpy(reals, data.dests, (size_t)num_reals * sizeof(cups_dest_t)); +- else +- num_reals = 0; +- } +- else +- { +- num_reals = 0; +- reals = NULL; +- } ++ if (!name || num_dests <= 0 || !dests) ++ return; + + /* +- * Grab the default destination... ++ * Loop through the array and set the "is_default" flag for the matching ++ * destination... + */ + +- if ((user_default = _cupsUserDefault(name, sizeof(name))) != NULL) +- defprinter = name; +- else if ((defprinter = cupsGetDefault2(http)) != NULL) +- { +- strlcpy(name, defprinter, sizeof(name)); +- defprinter = name; +- } ++ for (i = num_dests, dest = dests; i > 0; i --, dest ++) ++ dest->is_default = !_cups_strcasecmp(name, dest->name) && ++ ((!instance && !dest->instance) || ++ (instance && dest->instance && ++ !_cups_strcasecmp(instance, dest->instance))); ++} + +- if (defprinter) +- { +- /* +- * Separate printer and instance name... +- */ + +- if ((instance = strchr(name, '/')) != NULL) +- *instance++ = '\0'; ++/* ++ * 'cupsSetDests()' - Save the list of destinations for the default server. ++ * ++ * This function saves the destinations to /etc/cups/lpoptions when run ++ * as root and ~/.cups/lpoptions when run as a normal user. ++ * ++ * @exclude all@ ++ */ + +- /* +- * Lookup the printer and instance and make it the default... +- */ ++void ++cupsSetDests(int num_dests, /* I - Number of destinations */ ++ cups_dest_t *dests) /* I - Destinations */ ++{ ++ cupsSetDests2(CUPS_HTTP_DEFAULT, num_dests, dests); ++} ++ ++ ++/* ++ * 'cupsSetDests2()' - Save the list of destinations for the specified server. ++ * ++ * This function saves the destinations to /etc/cups/lpoptions when run ++ * as root and ~/.cups/lpoptions when run as a normal user. ++ * ++ * @since CUPS 1.1.21/macOS 10.4@ ++ */ ++ ++int /* O - 0 on success, -1 on error */ ++cupsSetDests2(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ ++ int num_dests, /* I - Number of destinations */ ++ cups_dest_t *dests) /* I - Destinations */ ++{ ++ int i, j; /* Looping vars */ ++ int wrote; /* Wrote definition? */ ++ cups_dest_t *dest; /* Current destination */ ++ cups_option_t *option; /* Current option */ ++ _ipp_option_t *match; /* Matching attribute for option */ ++ FILE *fp; /* File pointer */ ++#ifndef WIN32 ++ const char *home; /* HOME environment variable */ ++#endif /* WIN32 */ ++ char filename[1024]; /* lpoptions file */ ++ int num_temps; /* Number of temporary destinations */ ++ cups_dest_t *temps = NULL, /* Temporary destinations */ ++ *temp; /* Current temporary dest */ ++ const char *val; /* Value of temporary option */ ++ _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ + +- if ((dest = cupsGetDest(name, instance, data.num_dests, data.dests)) != NULL) +- dest->is_default = 1; +- } +- else +- instance = NULL; + + /* +- * Load the /etc/cups/lpoptions and ~/.cups/lpoptions files... ++ * Range check the input... + */ + +- snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); +- data.num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL, data.num_dests, &data.dests); ++ if (!num_dests || !dests) ++ return (-1); + +- if ((home = getenv("HOME")) != NULL) +- { +- snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); ++ /* ++ * Get the server destinations... ++ */ + +- data.num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL, data.num_dests, &data.dests); ++ num_temps = _cupsGetDests(http, IPP_OP_CUPS_GET_PRINTERS, NULL, &temps, 0, 0); ++ ++ if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE) ++ { ++ cupsFreeDests(num_temps, temps); ++ return (-1); + } + + /* +- * Validate the current default destination - this prevents old +- * Default lines in /etc/cups/lpoptions and ~/.cups/lpoptions from +- * pointing to a non-existent printer or class... ++ * Figure out which file to write to... + */ + +- if (num_reals) ++ snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); ++ ++#ifndef WIN32 ++ if (getuid()) + { + /* +- * See if we have a default printer... ++ * Merge in server defaults... + */ + +- if ((dest = cupsGetDest(NULL, NULL, data.num_dests, data.dests)) != NULL) ++ num_temps = cups_get_dests(filename, NULL, NULL, 0, num_temps, &temps); ++ ++ /* ++ * Point to user defaults... ++ */ ++ ++ if ((home = getenv("HOME")) != NULL) + { + /* +- * Have a default; see if it is real... ++ * Create ~/.cups subdirectory... + */ + +- if (!cupsGetDest(dest->name, NULL, num_reals, reals)) +- { +- /* +- * Remove the non-real printer from the list, since we don't want jobs +- * going to an unexpected printer... () +- */ ++ snprintf(filename, sizeof(filename), "%s/.cups", home); ++ if (access(filename, 0)) ++ mkdir(filename, 0700); + +- data.num_dests = cupsRemoveDest(dest->name, dest->instance, data.num_dests, &data.dests); +- } ++ snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); + } ++ } ++#endif /* !WIN32 */ + +- /* +- * Free memory... +- */ ++ /* ++ * Try to open the file... ++ */ + +- free(reals); ++ if ((fp = fopen(filename, "w")) == NULL) ++ { ++ cupsFreeDests(num_temps, temps); ++ return (-1); + } + ++#ifndef WIN32 + /* +- * Return the number of destinations... ++ * Set the permissions to 0644 when saving to the /etc/cups/lpoptions ++ * file... + */ + +- *dests = data.dests; ++ if (!getuid()) ++ fchmod(fileno(fp), 0644); ++#endif /* !WIN32 */ + +- if (data.num_dests > 0) +- _cupsSetError(IPP_STATUS_OK, NULL, 0); ++ /* ++ * Write each printer; each line looks like: ++ * ++ * Dest name[/instance] options ++ * Default name[/instance] options ++ */ + +- DEBUG_printf(("1cupsGetDests2: Returning %d destinations.", data.num_dests)); ++ for (i = num_dests, dest = dests; i > 0; i --, dest ++) ++ if (dest->instance != NULL || dest->num_options != 0 || dest->is_default) ++ { ++ if (dest->is_default) ++ { ++ fprintf(fp, "Default %s", dest->name); ++ if (dest->instance) ++ fprintf(fp, "/%s", dest->instance); + +- return (data.num_dests); +-} ++ wrote = 1; ++ } ++ else ++ wrote = 0; + ++ if ((temp = cupsGetDest(dest->name, dest->instance, num_temps, temps)) == NULL) ++ temp = cupsGetDest(dest->name, NULL, num_temps, temps); + +-/* +- * 'cupsGetNamedDest()' - Get options for the named destination. +- * +- * This function is optimized for retrieving a single destination and should +- * be used instead of @link cupsGetDests2@ and @link cupsGetDest@ when you +- * either know the name of the destination or want to print to the default +- * destination. If @code NULL@ is returned, the destination does not exist or +- * there is no default destination. +- * +- * If "http" is @code CUPS_HTTP_DEFAULT@, the connection to the default print +- * server will be used. +- * +- * If "name" is @code NULL@, the default printer for the current user will be +- * returned. +- * +- * The returned destination must be freed using @link cupsFreeDests@ with a +- * "num_dests" value of 1. +- * +- * @since CUPS 1.4/macOS 10.6@ +- */ ++ for (j = dest->num_options, option = dest->options; j > 0; j --, option ++) ++ { ++ /* ++ * See if this option is a printer attribute; if so, skip it... ++ */ + +-cups_dest_t * /* O - Destination or @code NULL@ */ +-cupsGetNamedDest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ +- const char *name, /* I - Destination name or @code NULL@ for the default destination */ +- const char *instance) /* I - Instance name or @code NULL@ */ +-{ +- const char *dest_name; /* Working destination name */ +- cups_dest_t *dest; /* Destination */ +- char filename[1024], /* Path to lpoptions */ +- defname[256]; /* Default printer name */ +- const char *home = getenv("HOME"); /* Home directory */ +- int set_as_default = 0; /* Set returned destination as default */ +- ipp_op_t op = IPP_OP_GET_PRINTER_ATTRIBUTES; +- /* IPP operation to get server ops */ +- _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ ++ if ((match = _ippFindOption(option->name)) != NULL && ++ match->group_tag == IPP_TAG_PRINTER) ++ continue; + ++ /* ++ * See if the server/global options match these; if so, don't ++ * write 'em. ++ */ + +- DEBUG_printf(("cupsGetNamedDest(http=%p, name=\"%s\", instance=\"%s\")", (void *)http, name, instance)); ++ if (temp && ++ (val = cupsGetOption(option->name, temp->num_options, ++ temp->options)) != NULL && ++ !_cups_strcasecmp(val, option->value)) ++ continue; + +- /* +- * If "name" is NULL, find the default destination... +- */ ++ /* ++ * Options don't match, write to the file... ++ */ + +- dest_name = name; ++ if (!wrote) ++ { ++ fprintf(fp, "Dest %s", dest->name); ++ if (dest->instance) ++ fprintf(fp, "/%s", dest->instance); ++ wrote = 1; ++ } + +- if (!dest_name) +- { +- set_as_default = 1; +- dest_name = _cupsUserDefault(defname, sizeof(defname)); ++ if (option->value[0]) ++ { ++ if (strchr(option->value, ' ') || ++ strchr(option->value, '\\') || ++ strchr(option->value, '\"') || ++ strchr(option->value, '\'')) ++ { ++ /* ++ * Quote the value... ++ */ + +- if (dest_name) +- { +- char *ptr; /* Temporary pointer... */ ++ fprintf(fp, " %s=\"", option->name); + +- if ((ptr = strchr(defname, '/')) != NULL) +- { +- *ptr++ = '\0'; +- instance = ptr; +- } +- else +- instance = NULL; +- } +- else if (home) +- { +- /* +- * No default in the environment, try the user's lpoptions files... +- */ ++ for (val = option->value; *val; val ++) ++ { ++ if (strchr("\"\'\\", *val)) ++ putc('\\', fp); + +- snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); ++ putc(*val, fp); ++ } + +- dest_name = cups_get_default(filename, defname, sizeof(defname), &instance); +- } ++ putc('\"', fp); ++ } ++ else ++ { ++ /* ++ * Store the literal value... ++ */ + +- if (!dest_name) +- { +- /* +- * Still not there? Try the system lpoptions file... +- */ ++ fprintf(fp, " %s=%s", option->name, option->value); ++ } ++ } ++ else ++ fprintf(fp, " %s", option->name); ++ } + +- snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); +- dest_name = cups_get_default(filename, defname, sizeof(defname), &instance); ++ if (wrote) ++ fputs("\n", fp); + } + +- if (!dest_name) +- { +- /* +- * No locally-set default destination, ask the server... +- */ ++ /* ++ * Free the temporary destinations and close the file... ++ */ + +- op = IPP_OP_CUPS_GET_DEFAULT; ++ cupsFreeDests(num_temps, temps); + +- DEBUG_puts("1cupsGetNamedDest: Asking server for default printer..."); +- } +- else +- DEBUG_printf(("1cupsGetNamedDest: Using name=\"%s\"...", name)); +- } ++ fclose(fp); + ++#ifdef __APPLE__ + /* +- * Get the printer's attributes... ++ * Set the default printer for this location - this allows command-line ++ * and GUI applications to share the same default destination... + */ + +- if (!_cupsGetDests(http, op, dest_name, &dest, 0, 0)) ++ if ((dest = cupsGetDest(NULL, NULL, num_dests, dests)) != NULL) + { ++ CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, ++ dest->name, ++ kCFStringEncodingUTF8); ++ /* Default printer name */ ++ + if (name) + { +- _cups_namedata_t data; /* Callback data */ ++ _cupsAppleSetDefaultPrinter(name); ++ CFRelease(name); ++ } ++ } ++#endif /* __APPLE__ */ + +- DEBUG_puts("1cupsGetNamedDest: No queue found for printer, looking on network..."); ++#ifdef HAVE_NOTIFY_POST ++ /* ++ * Send a notification so that macOS applications can know about the ++ * change, too. ++ */ + +- data.name = name; +- data.dest = NULL; ++ notify_post("com.apple.printerListChange"); ++#endif /* HAVE_NOTIFY_POST */ + +- cupsEnumDests(0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_name_cb, &data); ++ return (0); ++} + +- if (!data.dest) +- return (NULL); + +- dest = data.dest; +- } +- else +- return (NULL); +- } ++/* ++ * '_cupsUserDefault()' - Get the user default printer from environment ++ * variables and location information. ++ */ + +- DEBUG_printf(("1cupsGetNamedDest: Got dest=%p", (void *)dest)); ++char * /* O - Default printer or NULL */ ++_cupsUserDefault(char *name, /* I - Name buffer */ ++ size_t namesize) /* I - Size of name buffer */ ++{ ++ const char *env; /* LPDEST or PRINTER env variable */ ++#ifdef __APPLE__ ++ CFStringRef locprinter; /* Last printer as this location */ ++#endif /* __APPLE__ */ + +- if (instance) +- dest->instance = _cupsStrAlloc(instance); + +- if (set_as_default) +- dest->is_default = 1; ++ if ((env = getenv("LPDEST")) == NULL) ++ if ((env = getenv("PRINTER")) != NULL && !strcmp(env, "lp")) ++ env = NULL; ++ ++ if (env) ++ { ++ strlcpy(name, env, namesize); ++ return (name); ++ } + ++#ifdef __APPLE__ + /* +- * Then add local options... ++ * Use location-based defaults if "use last printer" is selected in the ++ * system preferences... + */ + +- snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); +- cups_get_dests(filename, dest_name, instance, 1, 1, &dest); +- +- if (home) ++ if ((locprinter = _cupsAppleCopyDefaultPrinter()) != NULL) + { +- snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); +- +- cups_get_dests(filename, dest_name, instance, 1, 1, &dest); ++ CFStringGetCString(locprinter, name, (CFIndex)namesize, kCFStringEncodingUTF8); ++ CFRelease(locprinter); + } ++ else ++ name[0] = '\0'; ++ ++ DEBUG_printf(("1_cupsUserDefault: Returning \"%s\".", name)); ++ ++ return (*name ? name : NULL); + ++#else + /* +- * Return the result... ++ * No location-based defaults on this platform... + */ + +- return (dest); ++ name[0] = '\0'; ++ return (NULL); ++#endif /* __APPLE__ */ + } + + ++#if _CUPS_LOCATION_DEFAULTS + /* +- * 'cupsRemoveDest()' - Remove a destination from the destination list. +- * +- * Removing a destination/instance does not delete the class or printer +- * queue, merely the lpoptions for that destination/instance. Use the +- * @link cupsSetDests@ or @link cupsSetDests2@ functions to save the new +- * options for the user. +- * +- * @since CUPS 1.3/macOS 10.5@ ++ * 'appleCopyLocations()' - Copy the location history array. + */ + +-int /* O - New number of destinations */ +-cupsRemoveDest(const char *name, /* I - Destination name */ +- const char *instance, /* I - Instance name or @code NULL@ */ +- int num_dests, /* I - Number of destinations */ +- cups_dest_t **dests) /* IO - Destinations */ ++static CFArrayRef /* O - Location array or NULL */ ++appleCopyLocations(void) + { +- int i; /* Index into destinations */ +- cups_dest_t *dest; /* Pointer to destination */ ++ CFArrayRef locations; /* Location array */ + + + /* +- * Find the destination... ++ * Look up the location array in the preferences... + */ + +- if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL) +- return (num_dests); ++ if ((locations = CFPreferencesCopyAppValue(kLastUsedPrintersKey, ++ kCUPSPrintingPrefs)) == NULL) ++ return (NULL); + +- /* +- * Free memory... +- */ ++ if (CFGetTypeID(locations) != CFArrayGetTypeID()) ++ { ++ CFRelease(locations); ++ return (NULL); ++ } + +- _cupsStrFree(dest->name); +- _cupsStrFree(dest->instance); +- cupsFreeOptions(dest->num_options, dest->options); ++ return (locations); ++} + +- /* +- * Remove the destination from the array... +- */ + +- num_dests --; ++/* ++ * 'appleCopyNetwork()' - Get the network ID for the current location. ++ */ + +- i = (int)(dest - *dests); ++static CFStringRef /* O - Network ID */ ++appleCopyNetwork(void) ++{ ++ SCDynamicStoreRef dynamicStore; /* System configuration data */ ++ CFStringRef key; /* Current network configuration key */ ++ CFDictionaryRef ip_dict; /* Network configuration data */ ++ CFStringRef network = NULL; /* Current network ID */ + +- if (i < num_dests) +- memmove(dest, dest + 1, (size_t)(num_dests - i) * sizeof(cups_dest_t)); + +- return (num_dests); ++ if ((dynamicStore = SCDynamicStoreCreate(NULL, CFSTR("libcups"), NULL, ++ NULL)) != NULL) ++ { ++ /* ++ * First use the IPv6 router address, if available, since that will generally ++ * be a globally-unique link-local address. ++ */ ++ ++ if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity( ++ NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6)) != NULL) ++ { ++ if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL) ++ { ++ if ((network = CFDictionaryGetValue(ip_dict, ++ kSCPropNetIPv6Router)) != NULL) ++ CFRetain(network); ++ ++ CFRelease(ip_dict); ++ } ++ ++ CFRelease(key); ++ } ++ ++ /* ++ * If that doesn't work, try the IPv4 router address. This isn't as unique ++ * and will likely be a 10.x.y.z or 192.168.y.z address... ++ */ ++ ++ if (!network) ++ { ++ if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity( ++ NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)) != NULL) ++ { ++ if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL) ++ { ++ if ((network = CFDictionaryGetValue(ip_dict, ++ kSCPropNetIPv4Router)) != NULL) ++ CFRetain(network); ++ ++ CFRelease(ip_dict); ++ } ++ ++ CFRelease(key); ++ } ++ } ++ ++ CFRelease(dynamicStore); ++ } ++ ++ return (network); + } ++#endif /* _CUPS_LOCATION_DEFAULTS */ + + ++#ifdef __APPLE__ + /* +- * 'cupsSetDefaultDest()' - Set the default destination. +- * +- * @since CUPS 1.3/macOS 10.5@ ++ * 'appleGetPaperSize()' - Get the default paper size. + */ + +-void +-cupsSetDefaultDest( +- const char *name, /* I - Destination name */ +- const char *instance, /* I - Instance name or @code NULL@ */ +- int num_dests, /* I - Number of destinations */ +- cups_dest_t *dests) /* I - Destinations */ ++static char * /* O - Default paper size */ ++appleGetPaperSize(char *name, /* I - Paper size name buffer */ ++ size_t namesize) /* I - Size of buffer */ + { +- int i; /* Looping var */ +- cups_dest_t *dest; /* Current destination */ +- ++ CFStringRef defaultPaperID; /* Default paper ID */ ++ pwg_media_t *pwgmedia; /* PWG media size */ + +- /* +- * Range check input... +- */ + +- if (!name || num_dests <= 0 || !dests) +- return; ++ defaultPaperID = _cupsAppleCopyDefaultPaperID(); ++ if (!defaultPaperID || ++ CFGetTypeID(defaultPaperID) != CFStringGetTypeID() || ++ !CFStringGetCString(defaultPaperID, name, (CFIndex)namesize, kCFStringEncodingUTF8)) ++ name[0] = '\0'; ++ else if ((pwgmedia = pwgMediaForLegacy(name)) != NULL) ++ strlcpy(name, pwgmedia->pwg, namesize); + +- /* +- * Loop through the array and set the "is_default" flag for the matching +- * destination... +- */ ++ if (defaultPaperID) ++ CFRelease(defaultPaperID); + +- for (i = num_dests, dest = dests; i > 0; i --, dest ++) +- dest->is_default = !_cups_strcasecmp(name, dest->name) && +- ((!instance && !dest->instance) || +- (instance && dest->instance && +- !_cups_strcasecmp(instance, dest->instance))); ++ return (name); + } ++#endif /* __APPLE__ */ + + ++#if _CUPS_LOCATION_DEFAULTS + /* +- * 'cupsSetDests()' - Save the list of destinations for the default server. +- * +- * This function saves the destinations to /etc/cups/lpoptions when run +- * as root and ~/.cups/lpoptions when run as a normal user. +- * +- * @exclude all@ ++ * 'appleGetPrinter()' - Get a printer from the history array. + */ + +-void +-cupsSetDests(int num_dests, /* I - Number of destinations */ +- cups_dest_t *dests) /* I - Destinations */ ++static CFStringRef /* O - Printer name or NULL */ ++appleGetPrinter(CFArrayRef locations, /* I - Location array */ ++ CFStringRef network, /* I - Network name */ ++ CFIndex *locindex) /* O - Index in array */ + { +- cupsSetDests2(CUPS_HTTP_DEFAULT, num_dests, dests); ++ CFIndex i, /* Looping var */ ++ count; /* Number of locations */ ++ CFDictionaryRef location; /* Current location */ ++ CFStringRef locnetwork, /* Current network */ ++ locprinter; /* Current printer */ ++ ++ ++ for (i = 0, count = CFArrayGetCount(locations); i < count; i ++) ++ if ((location = CFArrayGetValueAtIndex(locations, i)) != NULL && ++ CFGetTypeID(location) == CFDictionaryGetTypeID()) ++ { ++ if ((locnetwork = CFDictionaryGetValue(location, ++ kLocationNetworkKey)) != NULL && ++ CFGetTypeID(locnetwork) == CFStringGetTypeID() && ++ CFStringCompare(network, locnetwork, 0) == kCFCompareEqualTo && ++ (locprinter = CFDictionaryGetValue(location, ++ kLocationPrinterIDKey)) != NULL && ++ CFGetTypeID(locprinter) == CFStringGetTypeID()) ++ { ++ if (locindex) ++ *locindex = i; ++ ++ return (locprinter); ++ } ++ } ++ ++ return (NULL); + } ++#endif /* _CUPS_LOCATION_DEFAULTS */ + + + /* +- * 'cupsSetDests2()' - Save the list of destinations for the specified server. +- * +- * This function saves the destinations to /etc/cups/lpoptions when run +- * as root and ~/.cups/lpoptions when run as a normal user. ++ * 'cups_add_dest()' - Add a destination to the array. + * +- * @since CUPS 1.1.21/macOS 10.4@ ++ * Unlike cupsAddDest(), this function does not check for duplicates. + */ + +-int /* O - 0 on success, -1 on error */ +-cupsSetDests2(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ +- int num_dests, /* I - Number of destinations */ +- cups_dest_t *dests) /* I - Destinations */ ++static cups_dest_t * /* O - New destination */ ++cups_add_dest(const char *name, /* I - Name of destination */ ++ const char *instance, /* I - Instance or NULL */ ++ int *num_dests, /* IO - Number of destinations */ ++ cups_dest_t **dests) /* IO - Destinations */ + { +- int i, j; /* Looping vars */ +- int wrote; /* Wrote definition? */ +- cups_dest_t *dest; /* Current destination */ +- cups_option_t *option; /* Current option */ +- _ipp_option_t *match; /* Matching attribute for option */ +- FILE *fp; /* File pointer */ +-#ifndef WIN32 +- const char *home; /* HOME environment variable */ +-#endif /* WIN32 */ +- char filename[1024]; /* lpoptions file */ +- int num_temps; /* Number of temporary destinations */ +- cups_dest_t *temps = NULL, /* Temporary destinations */ +- *temp; /* Current temporary dest */ +- const char *val; /* Value of temporary option */ +- _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ ++ int insert, /* Insertion point */ ++ diff; /* Result of comparison */ ++ cups_dest_t *dest; /* Destination pointer */ + + + /* +- * Range check the input... ++ * Add new destination... + */ + +- if (!num_dests || !dests) +- return (-1); ++ if (*num_dests == 0) ++ dest = malloc(sizeof(cups_dest_t)); ++ else ++ dest = realloc(*dests, sizeof(cups_dest_t) * (size_t)(*num_dests + 1)); ++ ++ if (!dest) ++ return (NULL); ++ ++ *dests = dest; + + /* +- * Get the server destinations... ++ * Find where to insert the destination... + */ + +- num_temps = _cupsGetDests(http, IPP_OP_CUPS_GET_PRINTERS, NULL, &temps, 0, 0); +- +- if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE) ++ if (*num_dests == 0) ++ insert = 0; ++ else + { +- cupsFreeDests(num_temps, temps); +- return (-1); ++ insert = cups_find_dest(name, instance, *num_dests, *dests, *num_dests - 1, ++ &diff); ++ ++ if (diff > 0) ++ insert ++; + } + + /* +- * Figure out which file to write to... ++ * Move the array elements as needed... + */ + +- snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); ++ if (insert < *num_dests) ++ memmove(*dests + insert + 1, *dests + insert, (size_t)(*num_dests - insert) * sizeof(cups_dest_t)); + +-#ifndef WIN32 +- if (getuid()) +- { +- /* +- * Merge in server defaults... +- */ ++ (*num_dests) ++; + +- num_temps = cups_get_dests(filename, NULL, NULL, 0, num_temps, &temps); ++ /* ++ * Initialize the destination... ++ */ + +- /* +- * Point to user defaults... +- */ ++ dest = *dests + insert; ++ dest->name = _cupsStrAlloc(name); ++ dest->instance = _cupsStrAlloc(instance); ++ dest->is_default = 0; ++ dest->num_options = 0; ++ dest->options = (cups_option_t *)0; + +- if ((home = getenv("HOME")) != NULL) +- { +- /* +- * Create ~/.cups subdirectory... +- */ ++ return (dest); ++} + +- snprintf(filename, sizeof(filename), "%s/.cups", home); +- if (access(filename, 0)) +- mkdir(filename, 0700); + +- snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); +- } +- } +-#endif /* !WIN32 */ ++# ifdef __BLOCKS__ ++/* ++ * 'cups_block_cb()' - Enumeration callback for block API. ++ */ + +- /* +- * Try to open the file... +- */ ++static int /* O - 1 to continue, 0 to stop */ ++cups_block_cb( ++ cups_dest_block_t block, /* I - Block */ ++ unsigned flags, /* I - Destination flags */ ++ cups_dest_t *dest) /* I - Destination */ ++{ ++ return ((block)(flags, dest)); ++} ++# endif /* __BLOCKS__ */ + +- if ((fp = fopen(filename, "w")) == NULL) +- { +- cupsFreeDests(num_temps, temps); +- return (-1); +- } + +-#ifndef WIN32 ++/* ++ * 'cups_compare_dests()' - Compare two destinations. ++ */ ++ ++static int /* O - Result of comparison */ ++cups_compare_dests(cups_dest_t *a, /* I - First destination */ ++ cups_dest_t *b) /* I - Second destination */ ++{ ++ int diff; /* Difference */ ++ ++ ++ if ((diff = _cups_strcasecmp(a->name, b->name)) != 0) ++ return (diff); ++ else if (a->instance && b->instance) ++ return (_cups_strcasecmp(a->instance, b->instance)); ++ else ++ return ((a->instance && !b->instance) - (!a->instance && b->instance)); ++} ++ ++ ++#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) ++# ifdef HAVE_DNSSD ++/* ++ * 'cups_dnssd_browse_cb()' - Browse for printers. ++ */ ++ ++static void ++cups_dnssd_browse_cb( ++ DNSServiceRef sdRef, /* I - Service reference */ ++ DNSServiceFlags flags, /* I - Option flags */ ++ uint32_t interfaceIndex, /* I - Interface number */ ++ DNSServiceErrorType errorCode, /* I - Error, if any */ ++ const char *serviceName, /* I - Name of service/device */ ++ const char *regtype, /* I - Type of service */ ++ const char *replyDomain, /* I - Service domain */ ++ void *context) /* I - Enumeration data */ ++{ ++ _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; ++ /* Enumeration data */ ++ ++ ++ DEBUG_printf(("5cups_dnssd_browse_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\", context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain, context)); ++ + /* +- * Set the permissions to 0644 when saving to the /etc/cups/lpoptions +- * file... ++ * Don't do anything on error... + */ + +- if (!getuid()) +- fchmod(fileno(fp), 0644); +-#endif /* !WIN32 */ ++ if (errorCode != kDNSServiceErr_NoError) ++ return; + + /* +- * Write each printer; each line looks like: +- * +- * Dest name[/instance] options +- * Default name[/instance] options ++ * Get the device... + */ + +- for (i = num_dests, dest = dests; i > 0; i --, dest ++) +- if (dest->instance != NULL || dest->num_options != 0 || dest->is_default) +- { +- if (dest->is_default) +- { +- fprintf(fp, "Default %s", dest->name); +- if (dest->instance) +- fprintf(fp, "/%s", dest->instance); ++ cups_dnssd_get_device(data, serviceName, regtype, replyDomain); ++} + +- wrote = 1; +- } +- else +- wrote = 0; + +- if ((temp = cupsGetDest(dest->name, dest->instance, num_temps, temps)) == NULL) +- temp = cupsGetDest(dest->name, NULL, num_temps, temps); ++# else /* HAVE_AVAHI */ ++/* ++ * 'cups_dnssd_browse_cb()' - Browse for printers. ++ */ + +- for (j = dest->num_options, option = dest->options; j > 0; j --, option ++) +- { +- /* +- * See if this option is a printer attribute; if so, skip it... +- */ ++static void ++cups_dnssd_browse_cb( ++ AvahiServiceBrowser *browser, /* I - Browser */ ++ AvahiIfIndex interface, /* I - Interface index (unused) */ ++ AvahiProtocol protocol, /* I - Network protocol (unused) */ ++ AvahiBrowserEvent event, /* I - What happened */ ++ const char *name, /* I - Service name */ ++ const char *type, /* I - Registration type */ ++ const char *domain, /* I - Domain */ ++ AvahiLookupResultFlags flags, /* I - Flags */ ++ void *context) /* I - Devices array */ ++{ ++#ifdef DEBUG ++ AvahiClient *client = avahi_service_browser_get_client(browser); ++ /* Client information */ ++#endif /* DEBUG */ ++ _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; ++ /* Enumeration data */ + +- if ((match = _ippFindOption(option->name)) != NULL && +- match->group_tag == IPP_TAG_PRINTER) +- continue; + +- /* +- * See if the server/global options match these; if so, don't +- * write 'em. +- */ ++ (void)interface; ++ (void)protocol; ++ (void)context; + +- if (temp && +- (val = cupsGetOption(option->name, temp->num_options, +- temp->options)) != NULL && +- !_cups_strcasecmp(val, option->value)) +- continue; ++ DEBUG_printf(("cups_dnssd_browse_cb(..., name=\"%s\", type=\"%s\", domain=\"%s\", ...);", name, type, domain)); ++ ++ switch (event) ++ { ++ case AVAHI_BROWSER_FAILURE: ++ DEBUG_printf(("cups_dnssd_browse_cb: %s", avahi_strerror(avahi_client_errno(client)))); ++ avahi_simple_poll_quit(data->simple_poll); ++ break; + ++ case AVAHI_BROWSER_NEW: + /* +- * Options don't match, write to the file... ++ * This object is new on the network. + */ + +- if (!wrote) ++ if (flags & AVAHI_LOOKUP_RESULT_LOCAL) + { +- fprintf(fp, "Dest %s", dest->name); +- if (dest->instance) +- fprintf(fp, "/%s", dest->instance); +- wrote = 1; +- } ++ /* ++ * This comes from the local machine so ignore it. ++ */ + +- if (option->value[0]) ++ DEBUG_printf(("cups_dnssd_browse_cb: Ignoring local service \"%s\".", name)); ++ } ++ else + { +- if (strchr(option->value, ' ') || +- strchr(option->value, '\\') || +- strchr(option->value, '\"') || +- strchr(option->value, '\'')) +- { +- /* +- * Quote the value... +- */ ++ /* ++ * Create a device entry for it if it doesn't yet exist. ++ */ + +- fprintf(fp, " %s=\"", option->name); ++ cups_dnssd_get_device(data, name, type, domain); ++ } ++ break; + +- for (val = option->value; *val; val ++) +- { +- if (strchr("\"\'\\", *val)) +- putc('\\', fp); ++ case AVAHI_BROWSER_REMOVE : ++ case AVAHI_BROWSER_CACHE_EXHAUSTED : ++ break; + +- putc(*val, fp); +- } ++ case AVAHI_BROWSER_ALL_FOR_NOW : ++ DEBUG_puts("cups_dnssd_browse_cb: ALL_FOR_NOW"); ++ data->browsers --; ++ break; ++ } ++} + +- putc('\"', fp); +- } +- else +- { +- /* +- * Store the literal value... +- */ + +- fprintf(fp, " %s=%s", option->name, option->value); +- } +- } +- else +- fprintf(fp, " %s", option->name); +- } ++/* ++ * 'cups_dnssd_client_cb()' - Avahi client callback function. ++ */ + +- if (wrote) +- fputs("\n", fp); +- } ++static void ++cups_dnssd_client_cb( ++ AvahiClient *client, /* I - Client information (unused) */ ++ AvahiClientState state, /* I - Current state */ ++ void *context) /* I - User data (unused) */ ++{ ++ _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; ++ /* Enumeration data */ + +- /* +- * Free the temporary destinations and close the file... +- */ + +- cupsFreeDests(num_temps, temps); ++ (void)client; + +- fclose(fp); ++ DEBUG_printf(("cups_dnssd_client_cb(client=%p, state=%d, context=%p)", client, state, context)); + +-#ifdef __APPLE__ + /* +- * Set the default printer for this location - this allows command-line +- * and GUI applications to share the same default destination... ++ * If the connection drops, quit. + */ + +- if ((dest = cupsGetDest(NULL, NULL, num_dests, dests)) != NULL) ++ if (state == AVAHI_CLIENT_FAILURE) + { +- CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, +- dest->name, +- kCFStringEncodingUTF8); +- /* Default printer name */ +- +- if (name) +- { +- _cupsAppleSetDefaultPrinter(name); +- CFRelease(name); +- } ++ DEBUG_puts("cups_dnssd_client_cb: Avahi connection failed."); ++ avahi_simple_poll_quit(data->simple_poll); + } +-#endif /* __APPLE__ */ +- +-#ifdef HAVE_NOTIFY_POST +- /* +- * Send a notification so that macOS applications can know about the +- * change, too. +- */ +- +- notify_post("com.apple.printerListChange"); +-#endif /* HAVE_NOTIFY_POST */ +- +- return (0); + } ++# endif /* HAVE_DNSSD */ + + + /* +- * '_cupsUserDefault()' - Get the user default printer from environment +- * variables and location information. ++ * 'cups_dnssd_compare_device()' - Compare two devices. + */ + +-char * /* O - Default printer or NULL */ +-_cupsUserDefault(char *name, /* I - Name buffer */ +- size_t namesize) /* I - Size of name buffer */ ++static int /* O - Result of comparison */ ++cups_dnssd_compare_devices( ++ _cups_dnssd_device_t *a, /* I - First device */ ++ _cups_dnssd_device_t *b) /* I - Second device */ + { +- const char *env; /* LPDEST or PRINTER env variable */ +-#ifdef __APPLE__ +- CFStringRef locprinter; /* Last printer as this location */ +-#endif /* __APPLE__ */ +- +- +- if ((env = getenv("LPDEST")) == NULL) +- if ((env = getenv("PRINTER")) != NULL && !strcmp(env, "lp")) +- env = NULL; ++ return (strcmp(a->dest.name, b->dest.name)); ++} + +- if (env) +- { +- strlcpy(name, env, namesize); +- return (name); +- } + +-#ifdef __APPLE__ +- /* +- * Use location-based defaults if "use last printer" is selected in the +- * system preferences... +- */ ++/* ++ * 'cups_dnssd_free_device()' - Free the memory used by a device. ++ */ + +- if ((locprinter = _cupsAppleCopyDefaultPrinter()) != NULL) +- { +- CFStringGetCString(locprinter, name, (CFIndex)namesize, kCFStringEncodingUTF8); +- CFRelease(locprinter); +- } +- else +- name[0] = '\0'; ++static void ++cups_dnssd_free_device( ++ _cups_dnssd_device_t *device, /* I - Device */ ++ _cups_dnssd_data_t *data) /* I - Enumeration data */ ++{ ++ DEBUG_printf(("5cups_dnssd_free_device(device=%p(%s), data=%p)", (void *)device, device->dest.name, (void *)data)); + +- DEBUG_printf(("1_cupsUserDefault: Returning \"%s\".", name)); ++# ifdef HAVE_DNSSD ++ if (device->ref) ++ DNSServiceRefDeallocate(device->ref); ++# else /* HAVE_AVAHI */ ++ if (device->ref) ++ avahi_record_browser_free(device->ref); ++# endif /* HAVE_DNSSD */ + +- return (*name ? name : NULL); ++ _cupsStrFree(device->domain); ++ _cupsStrFree(device->fullName); ++ _cupsStrFree(device->regtype); ++ _cupsStrFree(device->dest.name); + +-#else +- /* +- * No location-based defaults on this platform... +- */ ++ cupsFreeOptions(device->dest.num_options, device->dest.options); + +- name[0] = '\0'; +- return (NULL); +-#endif /* __APPLE__ */ ++ free(device); + } + + +-#if _CUPS_LOCATION_DEFAULTS + /* +- * 'appleCopyLocations()' - Copy the location history array. ++ * 'cups_dnssd_get_device()' - Lookup a device and create it as needed. + */ + +-static CFArrayRef /* O - Location array or NULL */ +-appleCopyLocations(void) ++static _cups_dnssd_device_t * /* O - Device */ ++cups_dnssd_get_device( ++ _cups_dnssd_data_t *data, /* I - Enumeration data */ ++ const char *serviceName, /* I - Service name */ ++ const char *regtype, /* I - Registration type */ ++ const char *replyDomain) /* I - Domain name */ + { +- CFArrayRef locations; /* Location array */ ++ _cups_dnssd_device_t key, /* Search key */ ++ *device; /* Device */ ++ char fullName[kDNSServiceMaxDomainName], ++ /* Full name for query */ ++ name[128]; /* Queue name */ + + ++ DEBUG_printf(("5cups_dnssd_get_device(data=%p, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\")", (void *)data, serviceName, regtype, replyDomain)); ++ + /* +- * Look up the location array in the preferences... ++ * See if this is an existing device... + */ + +- if ((locations = CFPreferencesCopyAppValue(kLastUsedPrintersKey, +- kCUPSPrintingPrefs)) == NULL) +- return (NULL); ++ cups_queue_name(name, serviceName, sizeof(name)); + +- if (CFGetTypeID(locations) != CFArrayGetTypeID()) +- { +- CFRelease(locations); +- return (NULL); +- } ++ key.dest.name = name; + +- return (locations); +-} ++ if ((device = cupsArrayFind(data->devices, &key)) != NULL) ++ { ++ /* ++ * Yes, see if we need to do anything with this... ++ */ + ++ int update = 0; /* Non-zero if we need to update */ + +-/* +- * 'appleCopyNetwork()' - Get the network ID for the current location. +- */ ++ if (!_cups_strcasecmp(replyDomain, "local.") && ++ _cups_strcasecmp(device->domain, replyDomain)) ++ { ++ /* ++ * Update the "global" listing to use the .local domain name instead. ++ */ + +-static CFStringRef /* O - Network ID */ +-appleCopyNetwork(void) +-{ +- SCDynamicStoreRef dynamicStore; /* System configuration data */ +- CFStringRef key; /* Current network configuration key */ +- CFDictionaryRef ip_dict; /* Network configuration data */ +- CFStringRef network = NULL; /* Current network ID */ ++ _cupsStrFree(device->domain); ++ device->domain = _cupsStrAlloc(replyDomain); + ++ DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use local " ++ "domain.", device->dest.name)); + +- if ((dynamicStore = SCDynamicStoreCreate(NULL, CFSTR("libcups"), NULL, +- NULL)) != NULL) +- { +- /* +- * First use the IPv6 router address, if available, since that will generally +- * be a globally-unique link-local address. +- */ ++ update = 1; ++ } + +- if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity( +- NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6)) != NULL) ++ if (!_cups_strcasecmp(regtype, "_ipps._tcp") && ++ _cups_strcasecmp(device->regtype, regtype)) + { +- if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL) +- { +- if ((network = CFDictionaryGetValue(ip_dict, +- kSCPropNetIPv6Router)) != NULL) +- CFRetain(network); ++ /* ++ * Prefer IPPS over IPP. ++ */ + +- CFRelease(ip_dict); +- } ++ _cupsStrFree(device->regtype); ++ device->regtype = _cupsStrAlloc(regtype); + +- CFRelease(key); ++ DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use IPPS.", ++ device->dest.name)); ++ ++ update = 1; + } + ++ if (!update) ++ { ++ DEBUG_printf(("6cups_dnssd_get_device: No changes to '%s'.", ++ device->dest.name)); ++ return (device); ++ } ++ } ++ else ++ { + /* +- * If that doesn't work, try the IPv4 router address. This isn't as unique +- * and will likely be a 10.x.y.z or 192.168.y.z address... ++ * No, add the device... + */ + +- if (!network) +- { +- if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity( +- NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)) != NULL) +- { +- if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL) +- { +- if ((network = CFDictionaryGetValue(ip_dict, +- kSCPropNetIPv4Router)) != NULL) +- CFRetain(network); ++ DEBUG_printf(("6cups_dnssd_get_device: Adding '%s' for %s with domain " ++ "'%s'.", serviceName, ++ !strcmp(regtype, "_ipps._tcp") ? "IPPS" : "IPP", ++ replyDomain)); + +- CFRelease(ip_dict); +- } ++ device = calloc(sizeof(_cups_dnssd_device_t), 1); ++ device->dest.name = _cupsStrAlloc(name); ++ device->domain = _cupsStrAlloc(replyDomain); ++ device->regtype = _cupsStrAlloc(regtype); + +- CFRelease(key); +- } +- } ++ device->dest.num_options = cupsAddOption("printer-info", serviceName, 0, &device->dest.options); + +- CFRelease(dynamicStore); ++ cupsArrayAdd(data->devices, device); + } + +- return (network); +-} +-#endif /* _CUPS_LOCATION_DEFAULTS */ ++ /* ++ * Set the "full name" of this service, which is used for queries... ++ */ + ++# ifdef HAVE_DNSSD ++ DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); ++# else /* HAVE_AVAHI */ ++ avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, regtype, replyDomain); ++# endif /* HAVE_DNSSD */ + +-#ifdef __APPLE__ +-/* +- * 'appleGetPaperSize()' - Get the default paper size. +- */ ++ _cupsStrFree(device->fullName); ++ device->fullName = _cupsStrAlloc(fullName); + +-static char * /* O - Default paper size */ +-appleGetPaperSize(char *name, /* I - Paper size name buffer */ +- size_t namesize) /* I - Size of buffer */ +-{ +- CFStringRef defaultPaperID; /* Default paper ID */ +- pwg_media_t *pwgmedia; /* PWG media size */ ++ if (device->ref) ++ { ++# ifdef HAVE_DNSSD ++ DNSServiceRefDeallocate(device->ref); ++# else /* HAVE_AVAHI */ ++ avahi_record_browser_free(device->ref); ++# endif /* HAVE_DNSSD */ + ++ device->ref = 0; ++ } + +- defaultPaperID = _cupsAppleCopyDefaultPaperID(); +- if (!defaultPaperID || +- CFGetTypeID(defaultPaperID) != CFStringGetTypeID() || +- !CFStringGetCString(defaultPaperID, name, (CFIndex)namesize, kCFStringEncodingUTF8)) +- name[0] = '\0'; +- else if ((pwgmedia = pwgMediaForLegacy(name)) != NULL) +- strlcpy(name, pwgmedia->pwg, namesize); ++ if (device->state == _CUPS_DNSSD_ACTIVE) ++ { ++ DEBUG_printf(("6cups_dnssd_get_device: Remove callback for \"%s\".", device->dest.name)); + +- if (defaultPaperID) +- CFRelease(defaultPaperID); ++ (*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest); ++ device->state = _CUPS_DNSSD_NEW; ++ } + +- return (name); ++ return (device); + } +-#endif /* __APPLE__ */ + + +-#if _CUPS_LOCATION_DEFAULTS ++# ifdef HAVE_DNSSD + /* +- * 'appleGetPrinter()' - Get a printer from the history array. ++ * 'cups_dnssd_local_cb()' - Browse for local printers. + */ + +-static CFStringRef /* O - Printer name or NULL */ +-appleGetPrinter(CFArrayRef locations, /* I - Location array */ +- CFStringRef network, /* I - Network name */ +- CFIndex *locindex) /* O - Index in array */ ++static void ++cups_dnssd_local_cb( ++ DNSServiceRef sdRef, /* I - Service reference */ ++ DNSServiceFlags flags, /* I - Option flags */ ++ uint32_t interfaceIndex, /* I - Interface number */ ++ DNSServiceErrorType errorCode, /* I - Error, if any */ ++ const char *serviceName, /* I - Name of service/device */ ++ const char *regtype, /* I - Type of service */ ++ const char *replyDomain, /* I - Service domain */ ++ void *context) /* I - Devices array */ + { +- CFIndex i, /* Looping var */ +- count; /* Number of locations */ +- CFDictionaryRef location; /* Current location */ +- CFStringRef locnetwork, /* Current network */ +- locprinter; /* Current printer */ +- +- +- for (i = 0, count = CFArrayGetCount(locations); i < count; i ++) +- if ((location = CFArrayGetValueAtIndex(locations, i)) != NULL && +- CFGetTypeID(location) == CFDictionaryGetTypeID()) +- { +- if ((locnetwork = CFDictionaryGetValue(location, +- kLocationNetworkKey)) != NULL && +- CFGetTypeID(locnetwork) == CFStringGetTypeID() && +- CFStringCompare(network, locnetwork, 0) == kCFCompareEqualTo && +- (locprinter = CFDictionaryGetValue(location, +- kLocationPrinterIDKey)) != NULL && +- CFGetTypeID(locprinter) == CFStringGetTypeID()) +- { +- if (locindex) +- *locindex = i; +- +- return (locprinter); +- } +- } +- +- return (NULL); +-} +-#endif /* _CUPS_LOCATION_DEFAULTS */ +- +- +-/* +- * 'cups_add_dest()' - Add a destination to the array. +- * +- * Unlike cupsAddDest(), this function does not check for duplicates. +- */ ++ _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; ++ /* Enumeration data */ ++ _cups_dnssd_device_t *device; /* Device */ + +-static cups_dest_t * /* O - New destination */ +-cups_add_dest(const char *name, /* I - Name of destination */ +- const char *instance, /* I - Instance or NULL */ +- int *num_dests, /* IO - Number of destinations */ +- cups_dest_t **dests) /* IO - Destinations */ +-{ +- int insert, /* Insertion point */ +- diff; /* Result of comparison */ +- cups_dest_t *dest; /* Destination pointer */ + ++ DEBUG_printf(("5cups_dnssd_local_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\", context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain, context)); + + /* +- * Add new destination... ++ * Only process "add" data... + */ + +- if (*num_dests == 0) +- dest = malloc(sizeof(cups_dest_t)); +- else +- dest = realloc(*dests, sizeof(cups_dest_t) * (size_t)(*num_dests + 1)); +- +- if (!dest) +- return (NULL); +- +- *dests = dest; ++ if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) ++ return; + + /* +- * Find where to insert the destination... ++ * Get the device... + */ + +- if (*num_dests == 0) +- insert = 0; +- else +- { +- insert = cups_find_dest(name, instance, *num_dests, *dests, *num_dests - 1, +- &diff); +- +- if (diff > 0) +- insert ++; +- } ++ device = cups_dnssd_get_device(data, serviceName, regtype, replyDomain); + + /* +- * Move the array elements as needed... ++ * Hide locally-registered devices... + */ + +- if (insert < *num_dests) +- memmove(*dests + insert + 1, *dests + insert, (size_t)(*num_dests - insert) * sizeof(cups_dest_t)); +- +- (*num_dests) ++; ++ DEBUG_printf(("6cups_dnssd_local_cb: Hiding local printer '%s'.", ++ serviceName)); + +- /* +- * Initialize the destination... +- */ ++ if (device->ref) ++ { ++ DNSServiceRefDeallocate(device->ref); ++ device->ref = 0; ++ } + +- dest = *dests + insert; +- dest->name = _cupsStrAlloc(name); +- dest->instance = _cupsStrAlloc(instance); +- dest->is_default = 0; +- dest->num_options = 0; +- dest->options = (cups_option_t *)0; ++ if (device->state == _CUPS_DNSSD_ACTIVE) ++ { ++ DEBUG_printf(("6cups_dnssd_local_cb: Remove callback for \"%s\".", device->dest.name)); ++ (*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest); ++ } + +- return (dest); ++ device->state = _CUPS_DNSSD_LOCAL; + } ++# endif /* HAVE_DNSSD */ + + +-# ifdef __BLOCKS__ ++# ifdef HAVE_AVAHI + /* +- * 'cups_block_cb()' - Enumeration callback for block API. ++ * 'cups_dnssd_poll_cb()' - Wait for input on the specified file descriptors. ++ * ++ * Note: This function is needed because avahi_simple_poll_iterate is broken ++ * and always uses a timeout of 0 (!) milliseconds. ++ * (https://github.com/lathiat/avahi/issues/127) ++ * ++ * @private@ + */ + +-static int /* O - 1 to continue, 0 to stop */ +-cups_block_cb( +- cups_dest_block_t block, /* I - Block */ +- unsigned flags, /* I - Destination flags */ +- cups_dest_t *dest) /* I - Destination */ ++static int /* O - Number of file descriptors matching */ ++cups_dnssd_poll_cb( ++ struct pollfd *pollfds, /* I - File descriptors */ ++ unsigned int num_pollfds, /* I - Number of file descriptors */ ++ int timeout, /* I - Timeout in milliseconds (unused) */ ++ void *context) /* I - User data (unused) */ + { +- return ((block)(flags, dest)); +-} +-# endif /* __BLOCKS__ */ ++ _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; ++ /* Enumeration data */ ++ int val; /* Return value */ + + +-/* +- * 'cups_compare_dests()' - Compare two destinations. +- */ ++ DEBUG_printf(("cups_dnssd_poll_cb(pollfds=%p, num_pollfds=%d, timeout=%d, context=%p)", pollfds, num_pollfds, timeout, context)); + +-static int /* O - Result of comparison */ +-cups_compare_dests(cups_dest_t *a, /* I - First destination */ +- cups_dest_t *b) /* I - Second destination */ +-{ +- int diff; /* Difference */ ++ (void)timeout; + ++ val = poll(pollfds, num_pollfds, _CUPS_DNSSD_MAXTIME); + +- if ((diff = _cups_strcasecmp(a->name, b->name)) != 0) +- return (diff); +- else if (a->instance && b->instance) +- return (_cups_strcasecmp(a->instance, b->instance)); +- else +- return ((a->instance && !b->instance) - (!a->instance && b->instance)); ++ DEBUG_printf(("cups_dnssd_poll_cb: poll() returned %d", val)); ++ ++ if (val < 0) ++ { ++ DEBUG_printf(("cups_dnssd_poll_cb: %s", strerror(errno))); ++ } ++ else if (val > 0) ++ { ++ data->got_data = 1; ++ } ++ ++ return (val); + } ++# endif /* HAVE_AVAHI */ + + +-#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +-# ifdef HAVE_DNSSD + /* +- * 'cups_dnssd_browse_cb()' - Browse for printers. ++ * 'cups_dnssd_query_cb()' - Process query data. + */ + ++# ifdef HAVE_DNSSD + static void +-cups_dnssd_browse_cb( ++cups_dnssd_query_cb( + DNSServiceRef sdRef, /* I - Service reference */ +- DNSServiceFlags flags, /* I - Option flags */ +- uint32_t interfaceIndex, /* I - Interface number */ ++ DNSServiceFlags flags, /* I - Data flags */ ++ uint32_t interfaceIndex, /* I - Interface */ + DNSServiceErrorType errorCode, /* I - Error, if any */ +- const char *serviceName, /* I - Name of service/device */ +- const char *regtype, /* I - Type of service */ +- const char *replyDomain, /* I - Service domain */ ++ const char *fullName, /* I - Full service name */ ++ uint16_t rrtype, /* I - Record type */ ++ uint16_t rrclass, /* I - Record class */ ++ uint16_t rdlen, /* I - Length of record data */ ++ const void *rdata, /* I - Record data */ ++ uint32_t ttl, /* I - Time-to-live */ + void *context) /* I - Enumeration data */ + { ++# else /* HAVE_AVAHI */ ++static void ++cups_dnssd_query_cb( ++ AvahiRecordBrowser *browser, /* I - Record browser */ ++ AvahiIfIndex interfaceIndex, ++ /* I - Interface index (unused) */ ++ AvahiProtocol protocol, /* I - Network protocol (unused) */ ++ AvahiBrowserEvent event, /* I - What happened? */ ++ const char *fullName, /* I - Service name */ ++ uint16_t rrclass, /* I - Record class */ ++ uint16_t rrtype, /* I - Record type */ ++ const void *rdata, /* I - TXT record */ ++ size_t rdlen, /* I - Length of TXT record */ ++ AvahiLookupResultFlags flags, /* I - Flags */ ++ void *context) /* I - Enumeration data */ ++{ ++# ifdef DEBUG ++ AvahiClient *client = avahi_record_browser_get_client(browser); ++ /* Client information */ ++# endif /* DEBUG */ ++# endif /* HAVE_DNSSD */ + _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; + /* Enumeration data */ ++ char serviceName[256],/* Service name */ ++ name[128], /* Queue name */ ++ *ptr; /* Pointer into string */ ++ _cups_dnssd_device_t dkey, /* Search key */ ++ *device; /* Device */ + + +- DEBUG_printf(("5cups_dnssd_browse_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\", context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain, context)); ++# ifdef HAVE_DNSSD ++ DEBUG_printf(("5cups_dnssd_query_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, fullName=\"%s\", rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, fullName, rrtype, rrclass, rdlen, rdata, ttl, context)); + + /* +- * Don't do anything on error... ++ * Only process "add" data... + */ + +- if (errorCode != kDNSServiceErr_NoError) ++ if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) + return; + ++# else /* HAVE_AVAHI */ ++ DEBUG_printf(("cups_dnssd_query_cb(browser=%p, interfaceIndex=%d, protocol=%d, event=%d, fullName=\"%s\", rrclass=%u, rrtype=%u, rdata=%p, rdlen=%u, flags=%x, context=%p)", browser, interfaceIndex, protocol, event, fullName, rrclass, rrtype, rdata, (unsigned)rdlen, flags, context)); ++ + /* +- * Get the device... ++ * Only process "add" data... + */ + +- cups_dnssd_get_device(data, serviceName, regtype, replyDomain); +-} ++ if (event != AVAHI_BROWSER_NEW) ++ { ++ if (event == AVAHI_BROWSER_FAILURE) ++ DEBUG_printf(("cups_dnssd_query_cb: %s", avahi_strerror(avahi_client_errno(client)))); + ++ return; ++ } ++# endif /* HAVE_DNSSD */ + +-# else /* HAVE_AVAHI */ +-/* +- * 'cups_dnssd_browse_cb()' - Browse for printers. +- */ ++ /* ++ * Lookup the service in the devices array. ++ */ + +-static void +-cups_dnssd_browse_cb( +- AvahiServiceBrowser *browser, /* I - Browser */ +- AvahiIfIndex interface, /* I - Interface index (unused) */ +- AvahiProtocol protocol, /* I - Network protocol (unused) */ +- AvahiBrowserEvent event, /* I - What happened */ +- const char *name, /* I - Service name */ +- const char *type, /* I - Registration type */ +- const char *domain, /* I - Domain */ +- AvahiLookupResultFlags flags, /* I - Flags */ +- void *context) /* I - Devices array */ +-{ +-#ifdef DEBUG +- AvahiClient *client = avahi_service_browser_get_client(browser); +- /* Client information */ +-#endif /* DEBUG */ +- _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; +- /* Enumeration data */ ++ cups_dnssd_unquote(serviceName, fullName, sizeof(serviceName)); + ++ if ((ptr = strstr(serviceName, "._")) != NULL) ++ *ptr = '\0'; + +- (void)interface; +- (void)protocol; +- (void)context; ++ cups_queue_name(name, serviceName, sizeof(name)); + +- DEBUG_printf(("cups_dnssd_browse_cb(..., name=\"%s\", type=\"%s\", domain=\"%s\", ...);", name, type, domain)); ++ dkey.dest.name = name; + +- switch (event) ++ if ((device = cupsArrayFind(data->devices, &dkey)) != NULL && device->state == _CUPS_DNSSD_NEW) + { +- case AVAHI_BROWSER_FAILURE: +- DEBUG_printf(("cups_dnssd_browse_cb: %s", avahi_strerror(avahi_client_errno(client)))); +- avahi_simple_poll_quit(data->simple_poll); +- break; ++ /* ++ * Found it, pull out the make and model from the TXT record and save it... ++ */ + +- case AVAHI_BROWSER_NEW: +- /* +- * This object is new on the network. +- */ ++ const uint8_t *txt, /* Pointer into data */ ++ *txtnext, /* Next key/value pair */ ++ *txtend; /* End of entire TXT record */ ++ uint8_t txtlen; /* Length of current key/value pair */ ++ char key[256], /* Key string */ ++ value[256], /* Value string */ ++ make_and_model[512], ++ /* Manufacturer and model */ ++ model[256], /* Model */ ++ uriname[1024], /* Name for URI */ ++ uri[1024]; /* Printer URI */ ++ cups_ptype_t type = CUPS_PRINTER_DISCOVERED | CUPS_PRINTER_BW; ++ /* Printer type */ ++ int saw_printer_type = 0; ++ /* Did we see a printer-type key? */ + +- if (flags & AVAHI_LOOKUP_RESULT_LOCAL) +- { +- /* +- * This comes from the local machine so ignore it. +- */ ++ device->state = _CUPS_DNSSD_PENDING; ++ make_and_model[0] = '\0'; + +- DEBUG_printf(("cups_dnssd_browse_cb: Ignoring local service \"%s\".", name)); +- } +- else +- { +- /* +- * Create a device entry for it if it doesn't yet exist. +- */ ++ strlcpy(model, "Unknown", sizeof(model)); + +- cups_dnssd_get_device(data, name, type, domain); +- } ++ for (txt = rdata, txtend = txt + rdlen; ++ txt < txtend; ++ txt = txtnext) ++ { ++ /* ++ * Read a key/value pair starting with an 8-bit length. Since the ++ * length is 8 bits and the size of the key/value buffers is 256, we ++ * don't need to check for overflow... ++ */ ++ ++ txtlen = *txt++; ++ ++ if (!txtlen || (txt + txtlen) > txtend) + break; + +- case AVAHI_BROWSER_REMOVE : +- case AVAHI_BROWSER_CACHE_EXHAUSTED : +- break; ++ txtnext = txt + txtlen; + +- case AVAHI_BROWSER_ALL_FOR_NOW : +- DEBUG_puts("cups_dnssd_browse_cb: ALL_FOR_NOW"); +- data->browsers --; +- break; +- } +-} ++ for (ptr = key; txt < txtnext && *txt != '='; txt ++) ++ *ptr++ = (char)*txt; ++ *ptr = '\0'; + ++ if (txt < txtnext && *txt == '=') ++ { ++ txt ++; + +-/* +- * 'cups_dnssd_client_cb()' - Avahi client callback function. +- */ ++ if (txt < txtnext) ++ memcpy(value, txt, (size_t)(txtnext - txt)); ++ value[txtnext - txt] = '\0'; + +-static void +-cups_dnssd_client_cb( +- AvahiClient *client, /* I - Client information (unused) */ +- AvahiClientState state, /* I - Current state */ +- void *context) /* I - User data (unused) */ +-{ +- _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; +- /* Enumeration data */ ++ DEBUG_printf(("6cups_dnssd_query_cb: %s=%s", key, value)); ++ } ++ else ++ { ++ DEBUG_printf(("6cups_dnssd_query_cb: '%s' with no value.", key)); ++ continue; ++ } + ++ if (!_cups_strcasecmp(key, "usb_MFG") || ++ !_cups_strcasecmp(key, "usb_MANU") || ++ !_cups_strcasecmp(key, "usb_MANUFACTURER")) ++ strlcpy(make_and_model, value, sizeof(make_and_model)); ++ else if (!_cups_strcasecmp(key, "usb_MDL") || ++ !_cups_strcasecmp(key, "usb_MODEL")) ++ strlcpy(model, value, sizeof(model)); ++ else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript")) ++ { ++ if (value[0] == '(') ++ { ++ /* ++ * Strip parenthesis... ++ */ + +- (void)client; ++ if ((ptr = value + strlen(value) - 1) > value && *ptr == ')') ++ *ptr = '\0'; + +- DEBUG_printf(("cups_dnssd_client_cb(client=%p, state=%d, context=%p)", client, state, context)); ++ strlcpy(model, value + 1, sizeof(model)); ++ } ++ else ++ strlcpy(model, value, sizeof(model)); ++ } ++ else if (!_cups_strcasecmp(key, "ty")) ++ { ++ strlcpy(model, value, sizeof(model)); + +- /* +- * If the connection drops, quit. +- */ ++ if ((ptr = strchr(model, ',')) != NULL) ++ *ptr = '\0'; ++ } ++ else if (!_cups_strcasecmp(key, "note")) ++ device->dest.num_options = cupsAddOption("printer-location", value, ++ device->dest.num_options, ++ &device->dest.options); ++ else if (!_cups_strcasecmp(key, "pdl")) ++ { ++ /* ++ * Look for PDF-capable printers; only PDF-capable printers are shown. ++ */ + +- if (state == AVAHI_CLIENT_FAILURE) +- { +- DEBUG_puts("cups_dnssd_client_cb: Avahi connection failed."); +- avahi_simple_poll_quit(data->simple_poll); +- } +-} +-# endif /* HAVE_DNSSD */ ++ const char *start, *next; /* Pointer into value */ ++ int have_pdf = 0, /* Have PDF? */ ++ have_raster = 0;/* Have raster format support? */ + ++ for (start = value; start && *start; start = next) ++ { ++ if (!_cups_strncasecmp(start, "application/pdf", 15) && (!start[15] || start[15] == ',')) ++ { ++ have_pdf = 1; ++ break; ++ } ++ else if ((!_cups_strncasecmp(start, "image/pwg-raster", 16) && (!start[16] || start[16] == ',')) || ++ (!_cups_strncasecmp(start, "image/urf", 9) && (!start[9] || start[9] == ','))) ++ { ++ have_raster = 1; ++ break; ++ } + +-/* +- * 'cups_dnssd_compare_device()' - Compare two devices. +- */ ++ if ((next = strchr(start, ',')) != NULL) ++ next ++; ++ } + +-static int /* O - Result of comparison */ +-cups_dnssd_compare_devices( +- _cups_dnssd_device_t *a, /* I - First device */ +- _cups_dnssd_device_t *b) /* I - Second device */ +-{ +- return (strcmp(a->dest.name, b->dest.name)); +-} ++ if (!have_pdf && !have_raster) ++ device->state = _CUPS_DNSSD_INCOMPATIBLE; ++ } ++ else if (!_cups_strcasecmp(key, "printer-type")) ++ { ++ /* ++ * Value is either NNNN or 0xXXXX ++ */ + ++ saw_printer_type = 1; ++ type = (cups_ptype_t)strtol(value, NULL, 0) | CUPS_PRINTER_DISCOVERED; ++ } ++ else if (!saw_printer_type) ++ { ++ if (!_cups_strcasecmp(key, "air") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_AUTHENTICATED; ++ else if (!_cups_strcasecmp(key, "bind") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_BIND; ++ else if (!_cups_strcasecmp(key, "collate") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_COLLATE; ++ else if (!_cups_strcasecmp(key, "color") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_COLOR; ++ else if (!_cups_strcasecmp(key, "copies") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_COPIES; ++ else if (!_cups_strcasecmp(key, "duplex") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_DUPLEX; ++ else if (!_cups_strcasecmp(key, "fax") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_MFP; ++ else if (!_cups_strcasecmp(key, "papercustom") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_VARIABLE; ++ else if (!_cups_strcasecmp(key, "papermax")) ++ { ++ if (!_cups_strcasecmp(value, "legal-a4")) ++ type |= CUPS_PRINTER_SMALL; ++ else if (!_cups_strcasecmp(value, "isoc-a2")) ++ type |= CUPS_PRINTER_MEDIUM; ++ else if (!_cups_strcasecmp(value, ">isoc-a2")) ++ type |= CUPS_PRINTER_LARGE; ++ } ++ else if (!_cups_strcasecmp(key, "punch") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_PUNCH; ++ else if (!_cups_strcasecmp(key, "scan") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_MFP; ++ else if (!_cups_strcasecmp(key, "sort") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_SORT; ++ else if (!_cups_strcasecmp(key, "staple") && ++ !_cups_strcasecmp(value, "t")) ++ type |= CUPS_PRINTER_STAPLE; ++ } ++ } + +-/* +- * 'cups_dnssd_free_device()' - Free the memory used by a device. +- */ ++ /* ++ * Save the printer-xxx values... ++ */ + +-static void +-cups_dnssd_free_device( +- _cups_dnssd_device_t *device, /* I - Device */ +- _cups_dnssd_data_t *data) /* I - Enumeration data */ +-{ +- DEBUG_printf(("5cups_dnssd_free_device(device=%p(%s), data=%p)", (void *)device, device->dest.name, (void *)data)); ++ if (make_and_model[0]) ++ { ++ strlcat(make_and_model, " ", sizeof(make_and_model)); ++ strlcat(make_and_model, model, sizeof(make_and_model)); + +-# ifdef HAVE_DNSSD +- if (device->ref) +- DNSServiceRefDeallocate(device->ref); +-# else /* HAVE_AVAHI */ +- if (device->ref) +- avahi_record_browser_free(device->ref); +-# endif /* HAVE_DNSSD */ ++ device->dest.num_options = cupsAddOption("printer-make-and-model", make_and_model, device->dest.num_options, &device->dest.options); ++ } ++ else ++ device->dest.num_options = cupsAddOption("printer-make-and-model", model, device->dest.num_options, &device->dest.options); + +- _cupsStrFree(device->domain); +- _cupsStrFree(device->fullName); +- _cupsStrFree(device->regtype); +- _cupsStrFree(device->dest.name); ++ device->type = type; ++ snprintf(value, sizeof(value), "%u", type); ++ device->dest.num_options = cupsAddOption("printer-type", value, device->dest.num_options, &device->dest.options); + +- cupsFreeOptions(device->dest.num_options, device->dest.options); ++ /* ++ * Save the URI... ++ */ + +- free(device); ++ cups_dnssd_unquote(uriname, device->fullName, sizeof(uriname)); ++ httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), ++ !strcmp(device->regtype, "_ipps._tcp") ? "ipps" : "ipp", ++ NULL, uriname, 0, saw_printer_type ? "/cups" : "/"); ++ ++ DEBUG_printf(("6cups_dnssd_query: device-uri=\"%s\"", uri)); ++ ++ device->dest.num_options = cupsAddOption("device-uri", uri, device->dest.num_options, &device->dest.options); ++ } ++ else ++ DEBUG_printf(("6cups_dnssd_query: Ignoring TXT record for '%s'.", ++ fullName)); + } + + + /* +- * 'cups_dnssd_get_device()' - Lookup a device and create it as needed. ++ * 'cups_dnssd_resolve()' - Resolve a Bonjour printer URI. + */ + +-static _cups_dnssd_device_t * /* O - Device */ +-cups_dnssd_get_device( +- _cups_dnssd_data_t *data, /* I - Enumeration data */ +- const char *serviceName, /* I - Service name */ +- const char *regtype, /* I - Registration type */ +- const char *replyDomain) /* I - Domain name */ ++static const char * /* O - Resolved URI or NULL */ ++cups_dnssd_resolve( ++ cups_dest_t *dest, /* I - Destination */ ++ const char *uri, /* I - Current printer URI */ ++ int msec, /* I - Time in milliseconds */ ++ int *cancel, /* I - Pointer to "cancel" variable */ ++ cups_dest_cb_t cb, /* I - Callback */ ++ void *user_data) /* I - User data for callback */ + { +- _cups_dnssd_device_t key, /* Search key */ +- *device; /* Device */ +- char fullName[kDNSServiceMaxDomainName], +- /* Full name for query */ +- name[128]; /* Queue name */ +- ++ char tempuri[1024]; /* Temporary URI buffer */ ++ _cups_dnssd_resolve_t resolve; /* Resolve data */ + +- DEBUG_printf(("5cups_dnssd_get_device(data=%p, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\")", (void *)data, serviceName, regtype, replyDomain)); + + /* +- * See if this is an existing device... ++ * Resolve the URI... + */ + +- cups_queue_name(name, serviceName, sizeof(name)); +- +- key.dest.name = name; +- +- if ((device = cupsArrayFind(data->devices, &key)) != NULL) ++ resolve.cancel = cancel; ++ gettimeofday(&resolve.end_time, NULL); ++ if (msec > 0) + { +- /* +- * Yes, see if we need to do anything with this... +- */ +- +- int update = 0; /* Non-zero if we need to update */ +- +- if (!_cups_strcasecmp(replyDomain, "local.") && +- _cups_strcasecmp(device->domain, replyDomain)) +- { +- /* +- * Update the "global" listing to use the .local domain name instead. +- */ +- +- _cupsStrFree(device->domain); +- device->domain = _cupsStrAlloc(replyDomain); +- +- DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use local " +- "domain.", device->dest.name)); +- +- update = 1; +- } +- +- if (!_cups_strcasecmp(regtype, "_ipps._tcp") && +- _cups_strcasecmp(device->regtype, regtype)) +- { +- /* +- * Prefer IPPS over IPP. +- */ +- +- _cupsStrFree(device->regtype); +- device->regtype = _cupsStrAlloc(regtype); +- +- DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use IPPS.", +- device->dest.name)); +- +- update = 1; +- } ++ resolve.end_time.tv_sec += msec / 1000; ++ resolve.end_time.tv_usec += (msec % 1000) * 1000; + +- if (!update) ++ while (resolve.end_time.tv_usec >= 1000000) + { +- DEBUG_printf(("6cups_dnssd_get_device: No changes to '%s'.", +- device->dest.name)); +- return (device); ++ resolve.end_time.tv_sec ++; ++ resolve.end_time.tv_usec -= 1000000; + } + } + else +- { +- /* +- * No, add the device... +- */ ++ resolve.end_time.tv_sec += 75; + +- DEBUG_printf(("6cups_dnssd_get_device: Adding '%s' for %s with domain " +- "'%s'.", serviceName, +- !strcmp(regtype, "_ipps._tcp") ? "IPPS" : "IPP", +- replyDomain)); ++ if (cb) ++ (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING, dest); + +- device = calloc(sizeof(_cups_dnssd_device_t), 1); +- device->dest.name = _cupsStrAlloc(name); +- device->domain = _cupsStrAlloc(replyDomain); +- device->regtype = _cupsStrAlloc(regtype); ++ if ((uri = _httpResolveURI(uri, tempuri, sizeof(tempuri), _HTTP_RESOLVE_DEFAULT, cups_dnssd_resolve_cb, &resolve)) == NULL) ++ { ++ _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to resolve printer-uri."), 1); + +- device->dest.num_options = cupsAddOption("printer-info", serviceName, 0, &device->dest.options); ++ if (cb) ++ (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, dest); + +- cupsArrayAdd(data->devices, device); ++ return (NULL); + } + + /* +- * Set the "full name" of this service, which is used for queries... ++ * Save the resolved URI... + */ + +-# ifdef HAVE_DNSSD +- DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); +-# else /* HAVE_AVAHI */ +- avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, regtype, replyDomain); +-# endif /* HAVE_DNSSD */ +- +- _cupsStrFree(device->fullName); +- device->fullName = _cupsStrAlloc(fullName); +- +- if (device->ref) +- { +-# ifdef HAVE_DNSSD +- DNSServiceRefDeallocate(device->ref); +-# else /* HAVE_AVAHI */ +- avahi_record_browser_free(device->ref); +-# endif /* HAVE_DNSSD */ +- +- device->ref = 0; +- } +- +- if (device->state == _CUPS_DNSSD_ACTIVE) +- { +- DEBUG_printf(("6cups_dnssd_get_device: Remove callback for \"%s\".", device->dest.name)); +- +- (*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest); +- device->state = _CUPS_DNSSD_NEW; +- } ++ dest->num_options = cupsAddOption("device-uri", uri, dest->num_options, &dest->options); + +- return (device); ++ return (cupsGetOption("device-uri", dest->num_options, dest->options)); + } + + +-# ifdef HAVE_DNSSD + /* +- * 'cups_dnssd_local_cb()' - Browse for local printers. ++ * 'cups_dnssd_resolve_cb()' - See if we should continue resolving. + */ + +-static void +-cups_dnssd_local_cb( +- DNSServiceRef sdRef, /* I - Service reference */ +- DNSServiceFlags flags, /* I - Option flags */ +- uint32_t interfaceIndex, /* I - Interface number */ +- DNSServiceErrorType errorCode, /* I - Error, if any */ +- const char *serviceName, /* I - Name of service/device */ +- const char *regtype, /* I - Type of service */ +- const char *replyDomain, /* I - Service domain */ +- void *context) /* I - Devices array */ ++static int /* O - 1 to continue, 0 to stop */ ++cups_dnssd_resolve_cb(void *context) /* I - Resolve data */ + { +- _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; +- /* Enumeration data */ +- _cups_dnssd_device_t *device; /* Device */ +- ++ _cups_dnssd_resolve_t *resolve = (_cups_dnssd_resolve_t *)context; ++ /* Resolve data */ ++ struct timeval curtime; /* Current time */ + +- DEBUG_printf(("5cups_dnssd_local_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\", context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain, context)); + + /* +- * Only process "add" data... ++ * If the cancel variable is set, return immediately. + */ + +- if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) +- return; ++ if (resolve->cancel && *(resolve->cancel)) ++ { ++ DEBUG_puts("4cups_dnssd_resolve_cb: Canceled."); ++ return (0); ++ } + + /* +- * Get the device... ++ * Otherwise check the end time... + */ + +- device = cups_dnssd_get_device(data, serviceName, regtype, replyDomain); ++ gettimeofday(&curtime, NULL); + +- /* +- * Hide locally-registered devices... +- */ ++ DEBUG_printf(("4cups_dnssd_resolve_cb: curtime=%d.%06d, end_time=%d.%06d", (int)curtime.tv_sec, (int)curtime.tv_usec, (int)resolve->end_time.tv_sec, (int)resolve->end_time.tv_usec)); + +- DEBUG_printf(("6cups_dnssd_local_cb: Hiding local printer '%s'.", +- serviceName)); ++ return (curtime.tv_sec < resolve->end_time.tv_sec || ++ (curtime.tv_sec == resolve->end_time.tv_sec && ++ curtime.tv_usec < resolve->end_time.tv_usec)); ++} + +- if (device->ref) +- { +- DNSServiceRefDeallocate(device->ref); +- device->ref = 0; +- } + +- if (device->state == _CUPS_DNSSD_ACTIVE) ++/* ++ * 'cups_dnssd_unquote()' - Unquote a name string. ++ */ ++ ++static void ++cups_dnssd_unquote(char *dst, /* I - Destination buffer */ ++ const char *src, /* I - Source string */ ++ size_t dstsize) /* I - Size of destination buffer */ ++{ ++ char *dstend = dst + dstsize - 1; /* End of destination buffer */ ++ ++ ++ while (*src && dst < dstend) + { +- DEBUG_printf(("6cups_dnssd_local_cb: Remove callback for \"%s\".", device->dest.name)); +- (*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest); ++ if (*src == '\\') ++ { ++ src ++; ++ if (isdigit(src[0] & 255) && isdigit(src[1] & 255) && ++ isdigit(src[2] & 255)) ++ { ++ *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0'; ++ src += 3; ++ } ++ else ++ *dst++ = *src++; ++ } ++ else ++ *dst++ = *src ++; + } + +- device->state = _CUPS_DNSSD_LOCAL; ++ *dst = '\0'; } +-# endif /* HAVE_DNSSD */ ++#endif /* HAVE_DNSSD */ -+/* -+ * 'cupsEnumDests2()' - same as cupsEnumDests(), but for not default http connection -+ */ -+ -+int /* O - 1 on success, 0 on failure */ -+cupsEnumDests2( -+ http_t *http, /* I - Connection to server */ -+ unsigned flags, /* I - Enumeration flags */ -+ int msec, /* I - Timeout in milliseconds, -1 for indefinite */ -+ int *cancel, /* I - Pointer to "cancel" variable */ -+ cups_ptype_t type, /* I - Printer type bits */ -+ cups_ptype_t mask, /* I - Mask for printer type bits */ -+ cups_dest_cb_t cb, /* I - Callback function */ -+ void *user_data) /* I - User data */ -+{ -+ int i, /* Looping var */ -+ num_dests; /* Number of destinations */ -+ cups_dest_t *dests = NULL, /* Destinations */ -+ *dest; /* Current destination */ -+ const char *defprinter; /* Default printer */ -+ char name[1024], /* Copy of printer name */ -+ *instance, /* Pointer to instance name */ -+ *user_default; /* User default printer */ + +-# ifdef HAVE_AVAHI ++#if defined(HAVE_AVAHI) || defined(HAVE_DNSSD) + /* +- * 'cups_dnssd_poll_cb()' - Wait for input on the specified file descriptors. +- * +- * Note: This function is needed because avahi_simple_poll_iterate is broken +- * and always uses a timeout of 0 (!) milliseconds. +- * (https://github.com/lathiat/avahi/issues/127) +- * +- * @private@ ++ * 'cups_elapsed()' - Return the elapsed time in milliseconds. + */ + +-static int /* O - Number of file descriptors matching */ +-cups_dnssd_poll_cb( +- struct pollfd *pollfds, /* I - File descriptors */ +- unsigned int num_pollfds, /* I - Number of file descriptors */ +- int timeout, /* I - Timeout in milliseconds (unused) */ +- void *context) /* I - User data (unused) */ ++static int /* O - Elapsed time in milliseconds */ ++cups_elapsed(struct timeval *t) /* IO - Previous time */ + { +- _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; +- /* Enumeration data */ +- int val; /* Return value */ +- +- +- DEBUG_printf(("cups_dnssd_poll_cb(pollfds=%p, num_pollfds=%d, timeout=%d, context=%p)", pollfds, num_pollfds, timeout, context)); ++ int msecs; /* Milliseconds */ ++ struct timeval nt; /* New time */ + +- (void)timeout; + +- val = poll(pollfds, num_pollfds, _CUPS_DNSSD_MAXTIME); ++ gettimeofday(&nt, NULL); + +- DEBUG_printf(("cups_dnssd_poll_cb: poll() returned %d", val)); ++ msecs = (int)(1000 * (nt.tv_sec - t->tv_sec) + (nt.tv_usec - t->tv_usec) / 1000); + +- if (val < 0) +- { +- DEBUG_printf(("cups_dnssd_poll_cb: %s", strerror(errno))); +- } +- else if (val > 0) +- { +- data->got_data = 1; +- } ++ *t = nt; + +- return (val); ++ return (msecs); + } +-# endif /* HAVE_AVAHI */ ++#endif /* HAVE_AVAHI || HAVE_DNSSD */ + + + /* +- * 'cups_dnssd_query_cb()' - Process query data. ++ * 'cups_enum_dests()' - Enumerate destinations from a specific server. + */ + +-# ifdef HAVE_DNSSD +-static void +-cups_dnssd_query_cb( +- DNSServiceRef sdRef, /* I - Service reference */ +- DNSServiceFlags flags, /* I - Data flags */ +- uint32_t interfaceIndex, /* I - Interface */ +- DNSServiceErrorType errorCode, /* I - Error, if any */ +- const char *fullName, /* I - Full service name */ +- uint16_t rrtype, /* I - Record type */ +- uint16_t rrclass, /* I - Record class */ +- uint16_t rdlen, /* I - Length of record data */ +- const void *rdata, /* I - Record data */ +- uint32_t ttl, /* I - Time-to-live */ +- void *context) /* I - Enumeration data */ ++static int /* O - 1 on success, 0 on failure */ ++cups_enum_dests( ++ http_t *http, /* I - Connection to scheduler */ ++ unsigned flags, /* I - Enumeration flags */ ++ int msec, /* I - Timeout in milliseconds, -1 for indefinite */ ++ int *cancel, /* I - Pointer to "cancel" variable */ ++ cups_ptype_t type, /* I - Printer type bits */ ++ cups_ptype_t mask, /* I - Mask for printer type bits */ ++ cups_dest_cb_t cb, /* I - Callback function */ ++ void *user_data) /* I - User data */ + { ++ int i, /* Looping var */ ++ num_dests; /* Number of destinations */ ++ cups_dest_t *dests = NULL, /* Destinations */ ++ *dest; /* Current destination */ ++ const char *defprinter; /* Default printer */ ++ char name[1024], /* Copy of printer name */ ++ *instance, /* Pointer to instance name */ ++ *user_default; /* User default printer */ +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) -+ int count, /* Number of queries started */ -+ completed, /* Number of completed queries */ -+ remaining; /* Remainder of timeout */ -+ struct timeval curtime; /* Current time */ -+ _cups_dnssd_data_t data; /* Data for callback */ -+ _cups_dnssd_device_t *device; /* Current device */ ++ int count, /* Number of queries started */ ++ completed, /* Number of completed queries */ ++ remaining; /* Remainder of timeout */ ++ struct timeval curtime; /* Current time */ ++ _cups_dnssd_data_t data; /* Data for callback */ ++ _cups_dnssd_device_t *device; /* Current device */ +# ifdef HAVE_DNSSD -+ int nfds, /* Number of files responded */ -+ main_fd; /* File descriptor for lookups */ -+ DNSServiceRef ipp_ref = NULL, /* IPP browser */ -+ local_ipp_ref = NULL; /* Local IPP browser */ ++ int nfds, /* Number of files responded */ ++ main_fd; /* File descriptor for lookups */ ++ DNSServiceRef ipp_ref = NULL, /* IPP browser */ ++ local_ipp_ref = NULL; /* Local IPP browser */ +# ifdef HAVE_SSL -+ DNSServiceRef ipps_ref = NULL,/* IPPS browser */ -+ local_ipps_ref = NULL; /* Local IPPS browser */ ++ DNSServiceRef ipps_ref = NULL, /* IPPS browser */ ++ local_ipps_ref = NULL; /* Local IPPS browser */ +# endif /* HAVE_SSL */ +# ifdef HAVE_POLL -+ struct pollfd pfd; /* Polling data */ ++ struct pollfd pfd; /* Polling data */ +# else -+ fd_set input; /* Input set for select() */ -+ struct timeval timeout; /* Timeout for select() */ ++ fd_set input; /* Input set for select() */ ++ struct timeval timeout; /* Timeout for select() */ +# endif /* HAVE_POLL */ -+# else /* HAVE_AVAHI */ -+ int error; /* Error value */ -+ AvahiServiceBrowser *ipp_ref = NULL;/* IPP browser */ + # else /* HAVE_AVAHI */ +-static void +-cups_dnssd_query_cb( +- AvahiRecordBrowser *browser, /* I - Record browser */ +- AvahiIfIndex interfaceIndex, +- /* I - Interface index (unused) */ +- AvahiProtocol protocol, /* I - Network protocol (unused) */ +- AvahiBrowserEvent event, /* I - What happened? */ +- const char *fullName, /* I - Service name */ +- uint16_t rrclass, /* I - Record class */ +- uint16_t rrtype, /* I - Record type */ +- const void *rdata, /* I - TXT record */ +- size_t rdlen, /* I - Length of TXT record */ +- AvahiLookupResultFlags flags, /* I - Flags */ +- void *context) /* I - Enumeration data */ +-{ +-# ifdef DEBUG +- AvahiClient *client = avahi_record_browser_get_client(browser); +- /* Client information */ +-# endif /* DEBUG */ ++ int error; /* Error value */ ++ AvahiServiceBrowser *ipp_ref = NULL; /* IPP browser */ +# ifdef HAVE_SSL -+ AvahiServiceBrowser *ipps_ref = NULL; /* IPPS browser */ ++ AvahiServiceBrowser *ipps_ref = NULL; /* IPPS browser */ +# endif /* HAVE_SSL */ -+# endif /* HAVE_DNSSD */ + # endif /* HAVE_DNSSD */ +- _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; +- /* Enumeration data */ +- char serviceName[256],/* Service name */ +- name[128], /* Queue name */ +- *ptr; /* Pointer into string */ +- _cups_dnssd_device_t dkey, /* Search key */ +- *device; /* Device */ +#endif /* HAVE_DNSSD || HAVE_AVAHI */ -+ -+ -+ /* + + +-# ifdef HAVE_DNSSD +- DEBUG_printf(("5cups_dnssd_query_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, fullName=\"%s\", rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, fullName, rrtype, rrclass, rdlen, rdata, ttl, context)); ++ DEBUG_printf(("cups_enum_dests(flags=%x, msec=%d, cancel=%p, type=%x, mask=%x, cb=%p, user_data=%p)", flags, msec, (void *)cancel, type, mask, (void *)cb, (void *)user_data)); + + /* +- * Only process "add" data... + * Range check input... -+ */ -+ + */ + +- if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) +- return; +- +-# else /* HAVE_AVAHI */ +- DEBUG_printf(("cups_dnssd_query_cb(browser=%p, interfaceIndex=%d, protocol=%d, event=%d, fullName=\"%s\", rrclass=%u, rrtype=%u, rdata=%p, rdlen=%u, flags=%x, context=%p)", browser, interfaceIndex, protocol, event, fullName, rrclass, rrtype, rdata, (unsigned)rdlen, flags, context)); +- +- /* +- * Only process "add" data... +- */ + (void)flags; -+ + +- if (event != AVAHI_BROWSER_NEW) + if (!cb) + { +- if (event == AVAHI_BROWSER_FAILURE) +- DEBUG_printf(("cups_dnssd_query_cb: %s", avahi_strerror(avahi_client_errno(client)))); +- +- return; ++ DEBUG_puts("1cups_enum_dests: No callback, returning 0."); + return (0); -+ -+ /* + } +-# endif /* HAVE_DNSSD */ + + /* +- * Lookup the service in the devices array. + * Get ready to enumerate... -+ */ -+ + */ + +- cups_dnssd_unquote(serviceName, fullName, sizeof(serviceName)); +- +- if ((ptr = strstr(serviceName, "._")) != NULL) +- *ptr = '\0'; +- +- cups_queue_name(name, serviceName, sizeof(name)); +- +- dkey.dest.name = name; +- +- if ((device = cupsArrayFind(data->devices, &dkey)) != NULL && device->state == _CUPS_DNSSD_NEW) +- { +- /* +- * Found it, pull out the make and model from the TXT record and save it... +- */ +- +- const uint8_t *txt, /* Pointer into data */ +- *txtnext, /* Next key/value pair */ +- *txtend; /* End of entire TXT record */ +- uint8_t txtlen; /* Length of current key/value pair */ +- char key[256], /* Key string */ +- value[256], /* Value string */ +- make_and_model[512], +- /* Manufacturer and model */ +- model[256], /* Model */ +- uriname[1024], /* Name for URI */ +- uri[1024]; /* Printer URI */ +- cups_ptype_t type = CUPS_PRINTER_DISCOVERED | CUPS_PRINTER_BW; +- /* Printer type */ +- int saw_printer_type = 0; +- /* Did we see a printer-type key? */ +- +- device->state = _CUPS_DNSSD_PENDING; +- make_and_model[0] = '\0'; +- +- strlcpy(model, "Unknown", sizeof(model)); +- +- for (txt = rdata, txtend = txt + rdlen; +- txt < txtend; +- txt = txtnext) +- { +- /* +- * Read a key/value pair starting with an 8-bit length. Since the +- * length is 8 bits and the size of the key/value buffers is 256, we +- * don't need to check for overflow... +- */ +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) + memset(&data, 0, sizeof(data)); -+ + +- txtlen = *txt++; + data.type = type; + data.mask = mask; + data.cb = cb; + data.user_data = user_data; + data.devices = cupsArrayNew3((cups_array_func_t)cups_dnssd_compare_devices, NULL, NULL, 0, NULL, (cups_afree_func_t)cups_dnssd_free_device); +#endif /* HAVE_DNSSD || HAVE_AVAHI */ -+ + +- if (!txtlen || (txt + txtlen) > txtend) +- break; + if (!(mask & CUPS_PRINTER_DISCOVERED) || !(type & CUPS_PRINTER_DISCOVERED)) + { + /* + * Get the list of local printers and pass them to the callback function... + */ -+ -+ num_dests = _cupsGetDests(http, IPP_OP_CUPS_GET_PRINTERS, NULL, -+ &dests, type, mask); -+ + +- txtnext = txt + txtlen; ++ num_dests = _cupsGetDests(http, IPP_OP_CUPS_GET_PRINTERS, NULL, &dests, type, mask); + +- for (ptr = key; txt < txtnext && *txt != '='; txt ++) +- *ptr++ = (char)*txt; +- *ptr = '\0'; + if ((user_default = _cupsUserDefault(name, sizeof(name))) != NULL) + defprinter = name; + else if ((defprinter = cupsGetDefault2(http)) != NULL) @@ -123,202 +4608,445 @@ diff -up cups-2.2.4/cups/dest.c.cupsenumdests2 cups-2.2.4/cups/dest.c + strlcpy(name, defprinter, sizeof(name)); + defprinter = name; + } -+ + +- if (txt < txtnext && *txt == '=') +- { +- txt ++; + if (defprinter) + { + /* + * Separate printer and instance name... + */ -+ + +- if (txt < txtnext) +- memcpy(value, txt, (size_t)(txtnext - txt)); +- value[txtnext - txt] = '\0'; + if ((instance = strchr(name, '/')) != NULL) + *instance++ = '\0'; -+ + +- DEBUG_printf(("6cups_dnssd_query_cb: %s=%s", key, value)); +- } +- else +- { +- DEBUG_printf(("6cups_dnssd_query_cb: '%s' with no value.", key)); +- continue; +- } + /* + * Lookup the printer and instance and make it the default... + */ -+ + +- if (!_cups_strcasecmp(key, "usb_MFG") || +- !_cups_strcasecmp(key, "usb_MANU") || +- !_cups_strcasecmp(key, "usb_MANUFACTURER")) +- strlcpy(make_and_model, value, sizeof(make_and_model)); +- else if (!_cups_strcasecmp(key, "usb_MDL") || +- !_cups_strcasecmp(key, "usb_MODEL")) +- strlcpy(model, value, sizeof(model)); +- else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript")) +- { +- if (value[0] == '(') +- { +- /* +- * Strip parenthesis... +- */ + if ((dest = cupsGetDest(name, instance, num_dests, dests)) != NULL) + dest->is_default = 1; + } -+ + +- if ((ptr = value + strlen(value) - 1) > value && *ptr == ')') +- *ptr = '\0'; + for (i = num_dests, dest = dests; + i > 0 && (!cancel || !*cancel); + i --, dest ++) + { +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) -+ const char *device_uri; /* Device URI */ ++ const char *device_uri; /* Device URI */ +#endif /* HAVE_DNSSD || HAVE_AVAHI */ -+ -+ if (!(*cb)(user_data, i > 1 ? CUPS_DEST_FLAGS_MORE : CUPS_DEST_FLAGS_NONE, -+ dest)) + +- strlcpy(model, value + 1, sizeof(model)); +- } +- else +- strlcpy(model, value, sizeof(model)); +- } +- else if (!_cups_strcasecmp(key, "ty")) +- { +- strlcpy(model, value, sizeof(model)); ++ if (!(*cb)(user_data, i > 1 ? CUPS_DEST_FLAGS_MORE : CUPS_DEST_FLAGS_NONE, dest)) + break; -+ + +- if ((ptr = strchr(model, ',')) != NULL) +- *ptr = '\0'; +- } +- else if (!_cups_strcasecmp(key, "note")) +- device->dest.num_options = cupsAddOption("printer-location", value, +- device->dest.num_options, +- &device->dest.options); +- else if (!_cups_strcasecmp(key, "pdl")) +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) + if (!dest->instance && (device_uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL && !strncmp(device_uri, "dnssd://", 8)) -+ { -+ /* + { + /* +- * Look for PDF-capable printers; only PDF-capable printers are shown. + * Add existing queue using service name, etc. so we don't list it again... -+ */ -+ -+ char scheme[32], /* URI scheme */ + */ + +- const char *start, *next; /* Pointer into value */ +- int have_pdf = 0, /* Have PDF? */ +- have_raster = 0;/* Have raster format support? */ ++ char scheme[32], /* URI scheme */ + userpass[32], /* Username:password */ + serviceName[256], /* Service name (host field) */ + resource[256], /* Resource (options) */ + *regtype, /* Registration type */ + *replyDomain; /* Registration domain */ -+ int port; /* Port number (not used) */ -+ ++ int port; /* Port number (not used) */ + +- for (start = value; start && *start; start = next) + if (httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), serviceName, sizeof(serviceName), &port, resource, sizeof(resource)) >= HTTP_URI_STATUS_OK) -+ { + { +- if (!_cups_strncasecmp(start, "application/pdf", 15) && (!start[15] || start[15] == ',')) +- { +- have_pdf = 1; +- break; +- } +- else if ((!_cups_strncasecmp(start, "image/pwg-raster", 16) && (!start[16] || start[16] == ',')) || +- (!_cups_strncasecmp(start, "image/urf", 9) && (!start[9] || start[9] == ','))) + if ((regtype = strstr(serviceName, "._ipp")) != NULL) -+ { + { +- have_raster = 1; +- break; +- } +- +- if ((next = strchr(start, ',')) != NULL) +- next ++; +- } + *regtype++ = '\0'; -+ + +- if (!have_pdf && !have_raster) +- device->state = _CUPS_DNSSD_INCOMPATIBLE; +- } +- else if (!_cups_strcasecmp(key, "printer-type")) +- { +- /* +- * Value is either NNNN or 0xXXXX +- */ + if ((replyDomain = strstr(regtype, "._tcp.")) != NULL) + { + replyDomain[5] = '\0'; + replyDomain += 6; -+ + +- saw_printer_type = 1; +- type = (cups_ptype_t)strtol(value, NULL, 0) | CUPS_PRINTER_DISCOVERED; +- } +- else if (!saw_printer_type) +- { +- if (!_cups_strcasecmp(key, "air") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_AUTHENTICATED; +- else if (!_cups_strcasecmp(key, "bind") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_BIND; +- else if (!_cups_strcasecmp(key, "collate") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_COLLATE; +- else if (!_cups_strcasecmp(key, "color") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_COLOR; +- else if (!_cups_strcasecmp(key, "copies") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_COPIES; +- else if (!_cups_strcasecmp(key, "duplex") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_DUPLEX; +- else if (!_cups_strcasecmp(key, "fax") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_MFP; +- else if (!_cups_strcasecmp(key, "papercustom") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_VARIABLE; +- else if (!_cups_strcasecmp(key, "papermax")) +- { +- if (!_cups_strcasecmp(value, "legal-a4")) +- type |= CUPS_PRINTER_SMALL; +- else if (!_cups_strcasecmp(value, "isoc-a2")) +- type |= CUPS_PRINTER_MEDIUM; +- else if (!_cups_strcasecmp(value, ">isoc-a2")) +- type |= CUPS_PRINTER_LARGE; +- } +- else if (!_cups_strcasecmp(key, "punch") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_PUNCH; +- else if (!_cups_strcasecmp(key, "scan") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_MFP; +- else if (!_cups_strcasecmp(key, "sort") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_SORT; +- else if (!_cups_strcasecmp(key, "staple") && +- !_cups_strcasecmp(value, "t")) +- type |= CUPS_PRINTER_STAPLE; + if ((device = cups_dnssd_get_device(&data, serviceName, regtype, replyDomain)) != NULL) + device->state = _CUPS_DNSSD_ACTIVE; + } + } + } -+ } + } +#endif /* HAVE_DNSSD || HAVE_AVAHI */ -+ } -+ + } + +- /* +- * Save the printer-xxx values... +- */ + cupsFreeDests(num_dests, dests); -+ + +- if (make_and_model[0]) +- { +- strlcat(make_and_model, " ", sizeof(make_and_model)); +- strlcat(make_and_model, model, sizeof(make_and_model)); + if (i > 0 || msec == 0) + goto enum_finished; + } -+ + +- device->dest.num_options = cupsAddOption("printer-make-and-model", make_and_model, device->dest.num_options, &device->dest.options); +- } +- else +- device->dest.num_options = cupsAddOption("printer-make-and-model", model, device->dest.num_options, &device->dest.options); + /* + * Return early if the caller doesn't want to do discovery... + */ -+ + +- device->type = type; +- snprintf(value, sizeof(value), "%u", type); +- device->dest.num_options = cupsAddOption("printer-type", value, device->dest.num_options, &device->dest.options); + if ((mask & CUPS_PRINTER_DISCOVERED) && !(type & CUPS_PRINTER_DISCOVERED)) + goto enum_finished; -+ + +- /* +- * Save the URI... +- */ +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) + /* + * Get Bonjour-shared printers... + */ -+ + +- cups_dnssd_unquote(uriname, device->fullName, sizeof(uriname)); +- httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), +- !strcmp(device->regtype, "_ipps._tcp") ? "ipps" : "ipp", +- NULL, uriname, 0, saw_printer_type ? "/cups" : "/"); + gettimeofday(&curtime, NULL); -+ + +- DEBUG_printf(("6cups_dnssd_query: device-uri=\"%s\"", uri)); +# ifdef HAVE_DNSSD + if (DNSServiceCreateConnection(&data.main_ref) != kDNSServiceErr_NoError) ++ { ++ DEBUG_puts("1cups_enum_dests: Unable to create service browser, returning 0."); + return (0); -+ ++ } + +- device->dest.num_options = cupsAddOption("device-uri", uri, device->dest.num_options, &device->dest.options); + main_fd = DNSServiceRefSockFD(data.main_ref); + + ipp_ref = data.main_ref; -+ DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, -+ "_ipp._tcp", NULL, -+ (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data); -+ ++ if (DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, "_ipp._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data) != kDNSServiceErr_NoError) ++ { ++ DEBUG_puts("1cups_enum_dests: Unable to create IPP browser, returning 0."); ++ DNSServiceRefDeallocate(data.main_ref); ++ return (0); + } +- else +- DEBUG_printf(("6cups_dnssd_query: Ignoring TXT record for '%s'.", +- fullName)); +-} + + local_ipp_ref = data.main_ref; -+ DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection, -+ kDNSServiceInterfaceIndexLocalOnly, -+ "_ipp._tcp", NULL, -+ (DNSServiceBrowseReply)cups_dnssd_local_cb, &data); ++ if (DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly, "_ipp._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_local_cb, &data) != kDNSServiceErr_NoError) ++ { ++ DEBUG_puts("1cups_enum_dests: Unable to create local IPP browser, returning 0."); ++ DNSServiceRefDeallocate(data.main_ref); ++ return (0); ++ } + +# ifdef HAVE_SSL + ipps_ref = data.main_ref; -+ DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0, -+ "_ipps._tcp", NULL, -+ (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data); ++ if (DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0, "_ipps._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data) != kDNSServiceErr_NoError) ++ { ++ DEBUG_puts("1cups_enum_dests: Unable to create IPPS browser, returning 0."); ++ DNSServiceRefDeallocate(data.main_ref); ++ return (0); ++ } + + local_ipps_ref = data.main_ref; -+ DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection, -+ kDNSServiceInterfaceIndexLocalOnly, -+ "_ipps._tcp", NULL, -+ (DNSServiceBrowseReply)cups_dnssd_local_cb, &data); ++ if (DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly, "_ipps._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_local_cb, &data) != kDNSServiceErr_NoError) ++ { ++ DEBUG_puts("1cups_enum_dests: Unable to create local IPPS browser, returning 0."); ++ DNSServiceRefDeallocate(data.main_ref); ++ return (0); ++ } +# endif /* HAVE_SSL */ + +# else /* HAVE_AVAHI */ + if ((data.simple_poll = avahi_simple_poll_new()) == NULL) + { -+ DEBUG_puts("cupsEnumDests2: Unable to create Avahi simple poll object."); -+ return (1); ++ DEBUG_puts("1cups_enum_dests: Unable to create Avahi poll, returning 0."); ++ return (0); + } -+ + +-/* +- * 'cups_dnssd_resolve()' - Resolve a Bonjour printer URI. +- */ + avahi_simple_poll_set_func(data.simple_poll, cups_dnssd_poll_cb, &data); -+ + +-static const char * /* O - Resolved URI or NULL */ +-cups_dnssd_resolve( +- cups_dest_t *dest, /* I - Destination */ +- const char *uri, /* I - Current printer URI */ +- int msec, /* I - Time in milliseconds */ +- int *cancel, /* I - Pointer to "cancel" variable */ +- cups_dest_cb_t cb, /* I - Callback */ +- void *user_data) /* I - User data for callback */ +-{ +- char tempuri[1024]; /* Temporary URI buffer */ +- _cups_dnssd_resolve_t resolve; /* Resolve data */ + data.client = avahi_client_new(avahi_simple_poll_get(data.simple_poll), -+ 0, cups_dnssd_client_cb, &data, -+ &error); ++ 0, cups_dnssd_client_cb, &data, ++ &error); + if (!data.client) + { -+ DEBUG_puts("cupsEnumDests2: Unable to create Avahi client."); ++ DEBUG_puts("1cups_enum_dests: Unable to create Avahi client, returning 0."); + avahi_simple_poll_free(data.simple_poll); -+ return (1); ++ return (0); + } -+ + + data.browsers = 1; -+ ipp_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipp._tcp", NULL, 0, cups_dnssd_browse_cb, &data); -+ ++ if ((ipp_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipp._tcp", NULL, 0, cups_dnssd_browse_cb, &data)) == NULL) ++ { ++ DEBUG_puts("1cups_enum_dests: Unable to create Avahi IPP browser, returning 0."); + +- /* +- * Resolve the URI... +- */ ++ avahi_client_free(data.client); ++ avahi_simple_poll_free(data.simple_poll); ++ return (0); ++ } + +- resolve.cancel = cancel; +- gettimeofday(&resolve.end_time, NULL); +- if (msec > 0) +# ifdef HAVE_SSL + data.browsers ++; -+ ipps_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipps._tcp", NULL, 0, cups_dnssd_browse_cb, &data); ++ if ((ipps_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipps._tcp", NULL, 0, cups_dnssd_browse_cb, &data)) == NULL) + { +- resolve.end_time.tv_sec += msec / 1000; +- resolve.end_time.tv_usec += (msec % 1000) * 1000; ++ DEBUG_puts("1cups_enum_dests: Unable to create Avahi IPPS browser, returning 0."); + +- while (resolve.end_time.tv_usec >= 1000000) +- { +- resolve.end_time.tv_sec ++; +- resolve.end_time.tv_usec -= 1000000; +- } ++ avahi_service_browser_free(ipp_ref); ++ avahi_client_free(data.client); ++ avahi_simple_poll_free(data.simple_poll); ++ return (0); + } +- else +- resolve.end_time.tv_sec += 75; +# endif /* HAVE_SSL */ +# endif /* HAVE_DNSSD */ -+ + +- if (cb) +- (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING, dest); + if (msec < 0) + remaining = INT_MAX; + else + remaining = msec; -+ + +- if ((uri = _httpResolveURI(uri, tempuri, sizeof(tempuri), _HTTP_RESOLVE_DEFAULT, cups_dnssd_resolve_cb, &resolve)) == NULL) + while (remaining > 0 && (!cancel || !*cancel)) -+ { + { +- _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to resolve printer-uri."), 1); + /* + * Check for input... + */ -+ -+ DEBUG_printf(("1cupsEnumDests2: remaining=%d", remaining)); -+ + +- if (cb) +- (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, dest); ++ DEBUG_printf(("1cups_enum_dests: remaining=%d", remaining)); + +- return (NULL); +- } + cups_elapsed(&curtime); -+ + +- /* +- * Save the resolved URI... +- */ +# ifdef HAVE_DNSSD +# ifdef HAVE_POLL + pfd.fd = main_fd; + pfd.events = POLLIN; -+ + +- dest->num_options = cupsAddOption("device-uri", uri, dest->num_options, &dest->options); + nfds = poll(&pfd, 1, remaining > _CUPS_DNSSD_MAXTIME ? _CUPS_DNSSD_MAXTIME : remaining); -+ + +- return (cupsGetOption("device-uri", dest->num_options, dest->options)); +-} +# else + FD_ZERO(&input); + FD_SET(main_fd, &input); -+ + + timeout.tv_sec = 0; + timeout.tv_usec = 1000 * (remaining > _CUPS_DNSSD_MAXTIME ? _CUPS_DNSSD_MAXTIME : remaining); -+ + +-/* +- * 'cups_dnssd_resolve_cb()' - See if we should continue resolving. +- */ + nfds = select(main_fd + 1, &input, NULL, NULL, &timeout); +# endif /* HAVE_POLL */ -+ + +-static int /* O - 1 to continue, 0 to stop */ +-cups_dnssd_resolve_cb(void *context) /* I - Resolve data */ +-{ +- _cups_dnssd_resolve_t *resolve = (_cups_dnssd_resolve_t *)context; +- /* Resolve data */ +- struct timeval curtime; /* Current time */ + if (nfds > 0) + DNSServiceProcessResult(data.main_ref); -+ ++ else if (nfds < 0 && errno != EINTR && errno != EAGAIN) ++ break; + +# else /* HAVE_AVAHI */ + data.got_data = 0; -+ + +- /* +- * If the cancel variable is set, return immediately. +- */ + if ((error = avahi_simple_poll_iterate(data.simple_poll, _CUPS_DNSSD_MAXTIME)) > 0) + { + /* + * We've been told to exit the loop. Perhaps the connection to + * Avahi failed. + */ -+ + +- if (resolve->cancel && *(resolve->cancel)) +- { +- DEBUG_puts("4cups_dnssd_resolve_cb: Canceled."); +- return (0); +- } + break; + } -+ -+ DEBUG_printf(("1cupsEnumDests2: got_data=%d", data.got_data)); + +- /* +- * Otherwise check the end time... +- */ ++ DEBUG_printf(("1cups_enum_dests: got_data=%d", data.got_data)); +# endif /* HAVE_DNSSD */ -+ + +- gettimeofday(&curtime, NULL); + remaining -= cups_elapsed(&curtime); -+ + +- DEBUG_printf(("4cups_dnssd_resolve_cb: curtime=%d.%06d, end_time=%d.%06d", (int)curtime.tv_sec, (int)curtime.tv_usec, (int)resolve->end_time.tv_sec, (int)resolve->end_time.tv_usec)); + for (device = (_cups_dnssd_device_t *)cupsArrayFirst(data.devices), + count = 0, completed = 0; + device; @@ -326,108 +5054,142 @@ diff -up cups-2.2.4/cups/dest.c.cupsenumdests2 cups-2.2.4/cups/dest.c + { + if (device->ref) + count ++; -+ + +- return (curtime.tv_sec < resolve->end_time.tv_sec || +- (curtime.tv_sec == resolve->end_time.tv_sec && +- curtime.tv_usec < resolve->end_time.tv_usec)); +-} + if (device->state == _CUPS_DNSSD_ACTIVE) + completed ++; -+ + + if (!device->ref && device->state == _CUPS_DNSSD_NEW) + { -+ DEBUG_printf(("1cupsEnumDests2: Querying '%s'.", device->fullName)); -+ ++ DEBUG_printf(("1cups_enum_dests: Querying '%s'.", device->fullName)); + +-/* +- * 'cups_dnssd_unquote()' - Unquote a name string. +- */ +# ifdef HAVE_DNSSD + device->ref = data.main_ref; -+ -+ if (DNSServiceQueryRecord(&(device->ref), -+ kDNSServiceFlagsShareConnection, -+ 0, device->fullName, -+ kDNSServiceType_TXT, -+ kDNSServiceClass_IN, -+ (DNSServiceQueryRecordReply)cups_dnssd_query_cb, -+ &data) == kDNSServiceErr_NoError) -+ { -+ count ++; -+ } -+ else -+ { -+ device->ref = 0; -+ device->state = _CUPS_DNSSD_ERROR; -+ -+ DEBUG_puts("1cupsEnumDests2: Query failed."); -+ } -+ + +-static void +-cups_dnssd_unquote(char *dst, /* I - Destination buffer */ +- const char *src, /* I - Source string */ +- size_t dstsize) /* I - Size of destination buffer */ +-{ +- char *dstend = dst + dstsize - 1; /* End of destination buffer */ ++ if (DNSServiceQueryRecord(&(device->ref), kDNSServiceFlagsShareConnection, 0, device->fullName, kDNSServiceType_TXT, kDNSServiceClass_IN, (DNSServiceQueryRecordReply)cups_dnssd_query_cb, &data) == kDNSServiceErr_NoError) ++ { ++ count ++; ++ } ++ else ++ { ++ device->ref = 0; ++ device->state = _CUPS_DNSSD_ERROR; + ++ DEBUG_puts("1cups_enum_dests: Query failed."); ++ } + +- while (*src && dst < dstend) +- { +- if (*src == '\\') +- { +- src ++; +- if (isdigit(src[0] & 255) && isdigit(src[1] & 255) && +- isdigit(src[2] & 255)) +# else /* HAVE_AVAHI */ -+ if ((device->ref = avahi_record_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, device->fullName, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT, 0, cups_dnssd_query_cb, &data)) != NULL) ++ if ((device->ref = avahi_record_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, device->fullName, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT, 0, cups_dnssd_query_cb, &data)) != NULL) + { -+ DEBUG_printf(("1cupsEnumDests2: Query ref=%p", device->ref)); -+ count ++; -+ } -+ else -+ { -+ device->state = _CUPS_DNSSD_ERROR; ++ DEBUG_printf(("1cups_enum_dests: Query ref=%p", device->ref)); ++ count ++; ++ } ++ else ++ { ++ device->state = _CUPS_DNSSD_ERROR; + -+ DEBUG_printf(("1cupsEnumDests2: Query failed: %s", avahi_strerror(avahi_client_errno(data.client)))); -+ } ++ DEBUG_printf(("1cups_enum_dests: Query failed: %s", avahi_strerror(avahi_client_errno(data.client)))); ++ } +# endif /* HAVE_DNSSD */ + } + else if (device->ref && device->state == _CUPS_DNSSD_PENDING) -+ { + { +- *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0'; +- src += 3; + completed ++; + -+ DEBUG_printf(("1cupsEnumDests2: Query for \"%s\" is complete.", device->fullName)); ++ DEBUG_printf(("1cups_enum_dests: Query for \"%s\" is complete.", device->fullName)); + + if ((device->type & mask) == type) + { -+ DEBUG_printf(("1cupsEnumDests2: Add callback for \"%s\".", device->dest.name)); -+ if (!(*cb)(user_data, CUPS_DEST_FLAGS_NONE, &device->dest)) -+ { -+ remaining = -1; -+ break; -+ } ++ DEBUG_printf(("1cups_enum_dests: Add callback for \"%s\".", device->dest.name)); ++ if (!(*cb)(user_data, CUPS_DEST_FLAGS_NONE, &device->dest)) ++ { ++ remaining = -1; ++ break; ++ } + } + + device->state = _CUPS_DNSSD_ACTIVE; -+ } -+ } + } +- else +- *dst++ = *src++; + } +- else +- *dst++ = *src ++; + +# ifdef HAVE_AVAHI -+ DEBUG_printf(("1cupsEnumDests2: remaining=%d, browsers=%d, completed=%d, count=%d, devices count=%d", remaining, data.browsers, completed, count, cupsArrayCount(data.devices))); ++ DEBUG_printf(("1cups_enum_dests: remaining=%d, browsers=%d, completed=%d, count=%d, devices count=%d", remaining, data.browsers, completed, count, cupsArrayCount(data.devices))); + + if (data.browsers == 0 && completed == cupsArrayCount(data.devices)) + break; +# else -+ DEBUG_printf(("1cupsEnumDests2: remaining=%d, completed=%d, count=%d, devices count=%d", remaining, completed, count, cupsArrayCount(data.devices))); ++ DEBUG_printf(("1cups_enum_dests: remaining=%d, completed=%d, count=%d, devices count=%d", remaining, completed, count, cupsArrayCount(data.devices))); + + if (completed == cupsArrayCount(data.devices)) + break; +# endif /* HAVE_AVAHI */ -+ } + } +#endif /* HAVE_DNSSD || HAVE_AVAHI */ -+ + +- *dst = '\0'; +-} +-#endif /* HAVE_DNSSD */ + /* + * Return... + */ -+ + + enum_finished: -+ + +-#if defined(HAVE_AVAHI) || defined(HAVE_DNSSD) +-/* +- * 'cups_elapsed()' - Return the elapsed time in milliseconds. +- */ +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) + cupsArrayDelete(data.devices); -+ + +-static int /* O - Elapsed time in milliseconds */ +-cups_elapsed(struct timeval *t) /* IO - Previous time */ +-{ +- int msecs; /* Milliseconds */ +- struct timeval nt; /* New time */ +# ifdef HAVE_DNSSD + if (ipp_ref) + DNSServiceRefDeallocate(ipp_ref); + if (local_ipp_ref) + DNSServiceRefDeallocate(local_ipp_ref); -+ + +# ifdef HAVE_SSL + if (ipps_ref) + DNSServiceRefDeallocate(ipps_ref); + if (local_ipps_ref) + DNSServiceRefDeallocate(local_ipps_ref); +# endif /* HAVE_SSL */ -+ + +- gettimeofday(&nt, NULL); + if (data.main_ref) + DNSServiceRefDeallocate(data.main_ref); -+ + +- msecs = (int)(1000 * (nt.tv_sec - t->tv_sec) + (nt.tv_usec - t->tv_usec) / 1000); +# else /* HAVE_AVAHI */ + if (ipp_ref) + avahi_service_browser_free(ipp_ref); @@ -435,75 +5197,24 @@ diff -up cups-2.2.4/cups/dest.c.cupsenumdests2 cups-2.2.4/cups/dest.c + if (ipps_ref) + avahi_service_browser_free(ipps_ref); +# endif /* HAVE_SSL */ -+ + +- *t = nt; + if (data.client) + avahi_client_free(data.client); + if (data.simple_poll) + avahi_simple_poll_free(data.simple_poll); +# endif /* HAVE_DNSSD */ +#endif /* HAVE_DNSSD || HAVE_AVAHI */ + +- return (msecs); ++ DEBUG_puts("1cups_enum_dests: Returning 1."); + + return (1); -+} -+ - - # ifdef __BLOCKS__ - /* -@@ -1413,6 +1830,24 @@ cupsEnumDestsBlock( - return (cupsEnumDests(flags, timeout, cancel, type, mask, - (cups_dest_cb_t)cups_block_cb, (void *)block)); } -+ -+/* -+ * 'cupsEnumDestsBlock2()' - same as cupsEnumDestsBlock(), only with non default http connection -+ */ -+ -+int /* O - 1 on success, 0 on failure */ -+cupsEnumDestsBlock2( -+ http_t *http, /* I - Connection to server@ */ -+ unsigned flags, /* I - Enumeration flags */ -+ int timeout, /* I - Timeout in milliseconds, 0 for indefinite */ -+ int *cancel, /* I - Pointer to "cancel" variable */ -+ cups_ptype_t type, /* I - Printer type bits */ -+ cups_ptype_t mask, /* I - Mask for printer type bits */ -+ cups_dest_block_t block) /* I - Block */ -+{ -+ return (cupsEnumDests2(http, flags, timeout, cancel, type, mask, -+ (cups_dest_cb_t)cups_block_cb, (void *)block)); -+} - # endif /* __BLOCKS__ */ - - -@@ -2057,7 +2492,13 @@ cupsGetDests2(http_t *http, /* I - - data.num_dests = 0; - data.dests = NULL; - -- cupsEnumDests(0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_get_cb, &data); -+ if (!http) -+ http = CUPS_HTTP_DEFAULT; -+ -+ if (http == CUPS_HTTP_DEFAULT) -+ cupsEnumDests(0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_get_cb, &data); -+ else -+ cupsEnumDests2(http, 0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_get_cb, &data); - - /* - * Make a copy of the "real" queues for a later sanity check... -diff -up cups-2.2.4/cups/util.c.cupsenumdests2 cups-2.2.4/cups/util.c ---- cups-2.2.4/cups/util.c.cupsenumdests2 2017-06-30 17:44:38.000000000 +0200 -+++ cups-2.2.4/cups/util.c 2017-10-04 15:36:08.453828842 +0200 -@@ -197,7 +197,13 @@ cupsCreateJob( - data.name = name; - data.dest = NULL; +-#endif /* HAVE_AVAHI || HAVE_DNSSD */ -- cupsEnumDests(0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_create_cb, &data); -+ if (!http) -+ http = CUPS_HTTP_DEFAULT; -+ -+ if (http == CUPS_HTTP_DEFAULT) -+ cupsEnumDests(0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_create_cb, &data); -+ else -+ cupsEnumDests2(http, 0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_create_cb, &data); - if (!data.dest) - { + /* +-- +2.14.3 +