From: Michael Sweet Date: Wed, 11 Oct 2017 17:27:36 +0000 (-0400) Subject: `cupsGetDests2` was not using the supplied HTTP connection (Issue #5135) X-Git-Tag: v2.2.5~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9554d4e748afc212ff68d56098f5c430c48325d3;p=thirdparty%2Fcups.git `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. --- diff --git a/CHANGES.md b/CHANGES.md index 19947a2efc..aee9f94a8e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -45,6 +45,7 @@ CHANGES IN CUPS V2.2.5 directives (Issue #5117) - Added USB quirk rule for HP LaserJet 1160 printer (Issue #5121) - The network backends now retry on more error conditions (Issue #5123) +- `cupsGetDests2` was not using the supplied HTTP connection (Issue #5135) - `httpAddrConnect` leaked sockets in certain circumstances, causing some printers to hang (rdar://31965686) - Fixed an issue with Chinese localizations on macOS (rdar://32419311) diff --git a/cups/dest.c b/cups/dest.c index b3f1e8b792..45626884b3 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 */ -# 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 */ +# 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 */ -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 */ +# endif /* HAVE_SSL */ # 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... + */ - 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) + { + 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 */ +#endif /* HAVE_DNSSD || HAVE_AVAHI */ - 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... */ - 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) */ - 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; + 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; + 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; + 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; + 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("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); + if (!data.client) + { + DEBUG_puts("1cups_enum_dests: Unable to create Avahi client, returning 0."); + avahi_simple_poll_free(data.simple_poll); + return (0); + } + 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) + { + 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 ++; + 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... + */ - 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; + } - /* - * 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; + device = (_cups_dnssd_device_t *)cupsArrayNext(data.devices)) + { + 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(("1cups_enum_dests: Querying '%s'.", device->fullName)); -/* - * 'cups_dnssd_unquote()' - Unquote a name string. - */ +# ifdef HAVE_DNSSD + device->ref = data.main_ref; -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) + { + DEBUG_printf(("1cups_enum_dests: Query ref=%p", device->ref)); + count ++; + } + else + { + device->state = _CUPS_DNSSD_ERROR; + + 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(("1cups_enum_dests: Query for \"%s\" is complete.", device->fullName)); + + if ((device->type & mask) == type) + { + 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(("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(("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); +# ifdef HAVE_SSL + 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); } -#endif /* HAVE_AVAHI || HAVE_DNSSD */ /*