X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=cups%2Fdest.c;h=cdf1c4491570418147556295163f52691143a369;hb=61515785f7de12d8b2a29090020e684988f89977;hp=d7656bb289c9cbdf16e0667eedf76ae3c1814190;hpb=080811b190031b9182e96dc76fc610fadfeaec21;p=thirdparty%2Fcups.git diff --git a/cups/dest.c b/cups/dest.c index d7656bb28..cdf1c4491 100644 --- a/cups/dest.c +++ b/cups/dest.c @@ -1,10 +1,9 @@ /* - * "$Id: dest.c 6943 2007-09-10 23:00:33Z mike $" + * "$Id: dest.c 11141 2013-07-16 14:58:25Z msweet $" * - * User-defined destination (and option) support for the Common UNIX - * Printing System (CUPS). + * User-defined destination (and option) support for CUPS. * - * Copyright 2007-2008 by Apple Inc. + * Copyright 2007-2013 by Apple Inc. * Copyright 1997-2007 by Easy Software Products. * * These coded instructions, statements, and computer programs are the @@ -17,77 +16,271 @@ * * Contents: * - * cupsAddDest() - Add a destination to the list of destinations. - * cupsFreeDests() - Free the memory used by the list of destinations. - * cupsGetDest() - Get the named destination from the list. - * cupsGetDests() - Get the list of destinations from the default - * server. - * cupsGetDests2() - Get the list of destinations from the specified - * server. - * cupsGetNamedDest() - Get options for the named destination. - * cupsRemoveDest() - Remove a destination from the destination list. - * cupsSetDefaultDest() - Set the default destination. - * cupsSetDests() - Save the list of destinations for the default - * server. - * cupsSetDests2() - Save the list of destinations for the specified - * server. - * appleGetDefault() - Get the default printer for this location. - * appleGetLocations() - Get the location history array. - * appleGetNetwork() - Get the network ID for the current location. - * appleGetPrinter() - Get a printer from the history array. - * appleSetDefault() - Set the default printer for this location. - * appleUseLastPrinter() - Get the default printer preference value. - * cups_get_default() - Get the default destination from an lpoptions file. - * cups_get_dests() - Get destinations from a file. - * cups_get_sdests() - Get destinations from a server. + * cupsAddDest() - Add a destination to the list of + * destinations. + * _cupsAppleCopyDefaultPaperID() - Get the default paper ID. + * _cupsAppleCopyDefaultPrinter() - Get the default printer at this location. + * _cupsAppleGetUseLastPrinter() - Get whether to use the last used printer. + * _cupsAppleSetDefaultPaperID() - Set the default paper id. + * _cupsAppleSetDefaultPrinter() - Set the default printer for this + * location. + * _cupsAppleSetUseLastPrinter() - Set whether to use the last used printer. + * cupsConnectDest() - Connect to the server for a destination. + * cupsConnectDestBlock() - Connect to the server for a destination. + * cupsCopyDest() - Copy a destination. + * cupsEnumDests() - Enumerate available destinations with a + * callback function. + * cupsEnumDestsBlock() - Enumerate available destinations with a + * block. + * cupsFreeDests() - Free the memory used by the list of + * destinations. + * cupsGetDest() - Get the named destination from the list. + * _cupsGetDestResource() - Get the resource path and URI for a + * destination. + * _cupsGetDests() - Get destinations from a server. + * cupsGetDests() - Get the list of destinations from the + * default server. + * cupsGetDests2() - Get the list of destinations from the + * specified server. + * cupsGetNamedDest() - Get options for the named destination. + * cupsRemoveDest() - Remove a destination from the destination + * list. + * cupsSetDefaultDest() - Set the default destination. + * cupsSetDests() - Save the list of destinations for the + * default server. + * cupsSetDests2() - Save the list of destinations for the + * specified server. + * _cupsUserDefault() - Get the user default printer from + * environment variables and location + * information. + * appleCopyLocations() - Copy the location history array. + * appleCopyNetwork() - Get the network ID for the current + * location. + * appleGetPaperSize() - Get the default paper size. + * appleGetPrinter() - Get a printer from the history array. + * cups_add_dest() - Add a destination to the array. + * cups_block_cb() - Enumeration callback for block API. + * cups_compare_dests() - Compare two destinations. + * cups_dnssd_browse_cb() - Browse for printers. + * cups_dnssd_browse_cb() - Browse for printers. + * cups_dnssd_client_cb() - Avahi client callback function. + * cups_dnssd_compare_device() - Compare two devices. + * cups_dnssd_free_device() - Free the memory used by a device. + * cups_dnssd_get_device() - Lookup a device and create it as needed. + * cups_dnssd_local_cb() - Browse for local printers. + * cups_dnssd_poll_cb() - Wait for input on the specified file + * descriptors. + * cups_dnssd_query_cb() - Process query data. + * cups_dnssd_resolve() - Resolve a Bonjour printer URI. + * cups_dnssd_resolve_cb() - See if we should continue resolving. + * cups_dnssd_unquote() - Unquote a name string. + * cups_find_dest() - Find a destination using a binary search. + * cups_get_default() - Get the default destination from an + * lpoptions file. + * cups_get_dests() - Get destinations from a file. + * cups_make_string() - Make a comma-separated string of values + * from an IPP attribute. */ /* * Include necessary headers... */ -#include "debug.h" -#include "globals.h" -#include -#include +#include "cups-private.h" #include #ifdef HAVE_NOTIFY_H # include #endif /* HAVE_NOTIFY_H */ +#ifdef HAVE_POLL +# include +#endif /* HAVE_POLL */ + +#ifdef HAVE_DNSSD +# include +#endif /* HAVE_DNSSD */ + +#ifdef HAVE_AVAHI +# include +# include +# include +# include +# include +# include +#define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX +#endif /* HAVE_AVAHI */ + + +/* + * Constants... + */ + #ifdef __APPLE__ -# include -# include # include -# define kLocationHistoryArrayKey CFSTR("kLocationHistoryArrayKeyTMP") -# define kLocationNetworkKey CFSTR("kLocationNetworkKey") -# define kLocationPrinterIDKey CFSTR("kLocationPrinterIDKey") -# define kPMPrintingPreferences CFSTR("com.apple.print.PrintingPrefs") -# define kUseLastPrinterAsCurrentPrinterKey CFSTR("UseLastPrinterAsCurrentPrinter") +# define kCUPSPrintingPrefs CFSTR("org.cups.PrintingPrefs") +# define kDefaultPaperIDKey CFSTR("DefaultPaperID") +# define kLastUsedPrintersKey CFSTR("LastUsedPrinters") +# define kLocationNetworkKey CFSTR("Network") +# define kLocationPrinterIDKey CFSTR("PrinterID") +# define kUseLastPrinter CFSTR("UseLastPrinter") #endif /* __APPLE__ */ +/* + * Types... + */ + +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +typedef enum _cups_dnssd_state_e /* Enumerated device state */ +{ + _CUPS_DNSSD_NEW, + _CUPS_DNSSD_QUERY, + _CUPS_DNSSD_PENDING, + _CUPS_DNSSD_ACTIVE, + _CUPS_DNSSD_LOCAL, + _CUPS_DNSSD_INCOMPATIBLE, + _CUPS_DNSSD_ERROR +} _cups_dnssd_state_t; + +typedef struct _cups_dnssd_data_s /* Enumeration data */ +{ +# ifdef HAVE_DNSSD + DNSServiceRef main_ref; /* Main service reference */ +# else /* HAVE_AVAHI */ + AvahiSimplePoll *simple_poll; /* Polling interface */ + AvahiClient *client; /* Client information */ + int got_data; /* Did we get data? */ +# endif /* HAVE_DNSSD */ + cups_dest_cb_t cb; /* Callback */ + void *user_data; /* User data pointer */ + cups_ptype_t type, /* Printer type filter */ + mask; /* Printer type mask */ + cups_array_t *devices; /* Devices found so far */ +} _cups_dnssd_data_t; + +typedef struct _cups_dnssd_device_s /* Enumerated device */ +{ + _cups_dnssd_state_t state; /* State of device listing */ +# ifdef HAVE_DNSSD + DNSServiceRef ref; /* Service reference for query */ +# else /* HAVE_AVAHI */ + AvahiRecordBrowser *ref; /* Browser for query */ +# endif /* HAVE_DNSSD */ + char *domain, /* Domain name */ + *fullName, /* Full name */ + *regtype; /* Registration type */ + cups_ptype_t type; /* Device registration type */ + cups_dest_t dest; /* Destination record */ +} _cups_dnssd_device_t; + +typedef struct _cups_dnssd_resolve_s /* Data for resolving URI */ +{ + int *cancel; /* Pointer to "cancel" variable */ + struct timeval end_time; /* Ending time */ +} _cups_dnssd_resolve_t; +#endif /* HAVE_DNSSD */ + + /* * Local functions... */ #ifdef __APPLE__ -static char *appleGetDefault(char *name, int namesize); -static CFArrayRef appleGetLocations(void); -static CFStringRef appleGetNetwork(void); -static CFStringRef appleGetPrinter(CFArrayRef locations, CFStringRef network, - CFIndex *locindex); -static void appleSetDefault(const char *name); -static int appleUseLastPrinter(void); +static CFArrayRef appleCopyLocations(void); +static CFStringRef appleCopyNetwork(void); +static char *appleGetPaperSize(char *name, int namesize); +static CFStringRef appleGetPrinter(CFArrayRef locations, + CFStringRef network, CFIndex *locindex); #endif /* __APPLE__ */ -static char *cups_get_default(const char *filename, char *namebuf, - size_t namesize, const char **instance); -static int cups_get_dests(const char *filename, const char *match_name, - const char *match_inst, int num_dests, - cups_dest_t **dests); -static int cups_get_sdests(http_t *http, ipp_op_t op, const char *name, - int num_dests, cups_dest_t **dests); +static cups_dest_t *cups_add_dest(const char *name, const char *instance, + int *num_dests, cups_dest_t **dests); +#ifdef __BLOCKS__ +static int cups_block_cb(cups_dest_block_t block, unsigned flags, + cups_dest_t *dest); +#endif /* __BLOCKS__ */ +static int cups_compare_dests(cups_dest_t *a, cups_dest_t *b); +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) +# ifdef HAVE_DNSSD +static void cups_dnssd_browse_cb(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context); +# else /* HAVE_AVAHI */ +static void cups_dnssd_browse_cb(AvahiServiceBrowser *browser, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *serviceName, + const char *regtype, + const char *replyDomain, + AvahiLookupResultFlags flags, + void *context); +static void cups_dnssd_client_cb(AvahiClient *client, + AvahiClientState state, + void *context); +# endif /* HAVE_DNSSD */ +static int cups_dnssd_compare_devices(_cups_dnssd_device_t *a, + _cups_dnssd_device_t *b); +static void cups_dnssd_free_device(_cups_dnssd_device_t *device, + _cups_dnssd_data_t *data); +static _cups_dnssd_device_t * + cups_dnssd_get_device(_cups_dnssd_data_t *data, + const char *serviceName, + const char *regtype, + const char *replyDomain); +# ifdef HAVE_DNSSD +static void cups_dnssd_local_cb(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context); +static void cups_dnssd_query_cb(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullName, + uint16_t rrtype, uint16_t rrclass, + uint16_t rdlen, const void *rdata, + uint32_t ttl, void *context); +# else /* HAVE_AVAHI */ +static int cups_dnssd_poll_cb(struct pollfd *pollfds, + unsigned int num_pollfds, + int timeout, void *context); +static void cups_dnssd_query_cb(AvahiRecordBrowser *browser, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, uint16_t rrclass, + uint16_t rrtype, const void *rdata, + size_t rdlen, + AvahiLookupResultFlags flags, + void *context); +# endif /* HAVE_DNSSD */ +static const char *cups_dnssd_resolve(cups_dest_t *dest, const char *uri, + int msec, int *cancel, + cups_dest_cb_t cb, void *user_data); +static int cups_dnssd_resolve_cb(void *context); +static void cups_dnssd_unquote(char *dst, const char *src, + size_t dstsize); +#endif /* HAVE_DNSSD || HAVE_AVAHI */ +static int cups_find_dest(const char *name, const char *instance, + int num_dests, cups_dest_t *dests, int prev, + int *rdiff); +static char *cups_get_default(const char *filename, char *namebuf, + size_t namesize, const char **instance); +static int cups_get_dests(const char *filename, const char *match_name, + const char *match_inst, int user_default_set, + int num_dests, cups_dest_t **dests); +static char *cups_make_string(ipp_attribute_t *attr, char *buffer, + size_t bufsize); /* @@ -101,1143 +294,3290 @@ static int cups_get_sdests(http_t *http, ipp_op_t op, const char *name, * returned unchanged. Adding a new instance of a destination creates * a copy of that destination's options. * - * Use the cupsSaveDests() function to save the updated list of + * Use the @link cupsSaveDests@ function to save the updated list of * destinations to the user's lpoptions file. */ int /* O - New number of destinations */ cupsAddDest(const char *name, /* I - Destination name */ - const char *instance, /* I - Instance name or NULL for none/primary */ + const char *instance, /* I - Instance name or @code NULL@ for none/primary */ int num_dests, /* I - Number of destinations */ cups_dest_t **dests) /* IO - Destinations */ { int i; /* Looping var */ cups_dest_t *dest; /* Destination pointer */ - cups_dest_t *parent; /* Parent destination */ - cups_option_t *option; /* Current option */ + cups_dest_t *parent = NULL; /* Parent destination */ + cups_option_t *doption, /* Current destination option */ + *poption; /* Current parent option */ if (!name || !dests) return (0); - if ((dest = cupsGetDest(name, instance, num_dests, *dests)) != NULL) - return (num_dests); + if (!cupsGetDest(name, instance, num_dests, *dests)) + { + if (instance && !cupsGetDest(name, NULL, num_dests, *dests)) + return (num_dests); - /* - * Add new destination... - */ + if ((dest = cups_add_dest(name, instance, &num_dests, dests)) == NULL) + return (num_dests); - if (num_dests == 0) - dest = malloc(sizeof(cups_dest_t)); - else - dest = realloc(*dests, sizeof(cups_dest_t) * (num_dests + 1)); + /* + * Find the base dest again now the array has been realloc'd. + */ - if (dest == NULL) - return (num_dests); + parent = cupsGetDest(name, NULL, num_dests, *dests); + + if (instance && parent && parent->num_options > 0) + { + /* + * Copy options from parent... + */ + + dest->options = calloc(sizeof(cups_option_t), parent->num_options); + + if (dest->options) + { + dest->num_options = parent->num_options; + + for (i = dest->num_options, doption = dest->options, + poption = parent->options; + i > 0; + i --, doption ++, poption ++) + { + doption->name = _cupsStrRetain(poption->name); + doption->value = _cupsStrRetain(poption->value); + } + } + } + } + + return (num_dests); +} + + +#ifdef __APPLE__ +/* + * '_cupsAppleCopyDefaultPaperID()' - Get the default paper ID. + */ + +CFStringRef /* O - Default paper ID */ +_cupsAppleCopyDefaultPaperID(void) +{ + return (CFPreferencesCopyAppValue(kDefaultPaperIDKey, + kCUPSPrintingPrefs)); +} + + +/* + * '_cupsAppleCopyDefaultPrinter()' - Get the default printer at this location. + */ + +CFStringRef /* O - Default printer name */ +_cupsAppleCopyDefaultPrinter(void) +{ + CFStringRef network; /* Network location */ + CFArrayRef locations; /* Location array */ + CFStringRef locprinter; /* Current printer */ - *dests = dest; /* - * Find where to insert the destination... + * Use location-based defaults only if "use last printer" is selected in the + * system preferences... */ - for (i = num_dests; i > 0; i --, dest ++) - if (strcasecmp(name, dest->name) < 0) - break; - else if (!instance && dest->instance) - break; - else if (!strcasecmp(name, dest->name) && - instance && dest->instance && - strcasecmp(instance, dest->instance) < 0) - break; - - if (i > 0) - memmove(dest + 1, dest, i * sizeof(cups_dest_t)); + if (!_cupsAppleGetUseLastPrinter()) + { + DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Not using last printer as " + "default."); + return (NULL); + } /* - * Initialize the destination... + * Get the current location... */ - dest->name = _cupsStrAlloc(name); - dest->is_default = 0; - dest->num_options = 0; - dest->options = (cups_option_t *)0; + if ((network = appleCopyNetwork()) == NULL) + { + DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Unable to get current " + "network."); + return (NULL); + } - if (!instance) - dest->instance = NULL; - else + /* + * Lookup the network in the preferences... + */ + + if ((locations = appleCopyLocations()) == NULL) { /* - * Copy options from the primary instance... + * Missing or bad location array, so no location-based default... */ - dest->instance = _cupsStrAlloc(instance); + DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Missing or bad last used " + "printer array."); - if ((parent = cupsGetDest(name, NULL, num_dests + 1, *dests)) != NULL) - { - for (i = parent->num_options, option = parent->options; - i > 0; - i --, option ++) - dest->num_options = cupsAddOption(option->name, option->value, - dest->num_options, - &(dest->options)); - } + CFRelease(network); + + return (NULL); } - return (num_dests + 1); + DEBUG_printf(("1_cupsAppleCopyDefaultPrinter: Got locations, %d entries.", + (int)CFArrayGetCount(locations))); + + if ((locprinter = appleGetPrinter(locations, network, NULL)) != NULL) + CFRetain(locprinter); + + CFRelease(network); + CFRelease(locations); + + return (locprinter); } /* - * 'cupsFreeDests()' - Free the memory used by the list of destinations. + * '_cupsAppleGetUseLastPrinter()' - Get whether to use the last used printer. */ -void -cupsFreeDests(int num_dests, /* I - Number of destinations */ - cups_dest_t *dests) /* I - Destinations */ +int /* O - 1 to use last printer, 0 otherwise */ +_cupsAppleGetUseLastPrinter(void) { - int i; /* Looping var */ - cups_dest_t *dest; /* Current destination */ + Boolean uselast, /* Use last printer preference value */ + uselast_set; /* Valid is set? */ - if (num_dests == 0 || dests == NULL) - return; + if (getenv("CUPS_DISABLE_APPLE_DEFAULT")) + return (0); - for (i = num_dests, dest = dests; i > 0; i --, dest ++) - { - _cupsStrFree(dest->name); - _cupsStrFree(dest->instance); + uselast = CFPreferencesGetAppBooleanValue(kUseLastPrinter, + kCUPSPrintingPrefs, + &uselast_set); + if (!uselast_set) + return (1); + else + return (uselast); +} - cupsFreeOptions(dest->num_options, dest->options); - } - free(dests); +/* + * '_cupsAppleSetDefaultPaperID()' - Set the default paper id. + */ + +void +_cupsAppleSetDefaultPaperID( + CFStringRef name) /* I - New paper ID */ +{ + CFPreferencesSetAppValue(kDefaultPaperIDKey, name, kCUPSPrintingPrefs); + CFPreferencesAppSynchronize(kCUPSPrintingPrefs); + notify_post("com.apple.printerPrefsChange"); } /* - * 'cupsGetDest()' - Get the named destination from the list. - * - * Use the cupsGetDests() or cupsGetDests2() functions to get a - * list of supported destinations for the current user. + * '_cupsAppleSetDefaultPrinter()' - Set the default printer for this location. */ -cups_dest_t * /* O - Destination pointer or NULL */ -cupsGetDest(const char *name, /* I - Destination name or NULL for the default destination */ - const char *instance, /* I - Instance name or NULL */ - int num_dests, /* I - Number of destinations */ - cups_dest_t *dests) /* I - Destinations */ +void +_cupsAppleSetDefaultPrinter( + CFStringRef name) /* I - Default printer/class name */ { - int comp; /* Result of comparison */ + CFStringRef network; /* Current network */ + CFArrayRef locations; /* Old locations array */ + CFIndex locindex; /* Index in locations array */ + CFStringRef locprinter; /* Current printer */ + CFMutableArrayRef newlocations; /* New locations array */ + CFMutableDictionaryRef newlocation; /* New location */ - if (num_dests <= 0 || !dests) - return (NULL); + /* + * Get the current location... + */ - if (!name) + if ((network = appleCopyNetwork()) == NULL) { - /* - * NULL name for default printer. - */ + DEBUG_puts("1_cupsAppleSetDefaultPrinter: Unable to get current network..."); + return; + } - while (num_dests > 0) - { - if (dests->is_default) - return (dests); + /* + * Lookup the network in the preferences... + */ - num_dests --; - dests ++; - } - } + if ((locations = appleCopyLocations()) != NULL) + locprinter = appleGetPrinter(locations, network, &locindex); else + { + locprinter = NULL; + locindex = -1; + } + + if (!locprinter || CFStringCompare(locprinter, name, 0) != kCFCompareEqualTo) { /* - * Lookup name and optionally the instance... + * Need to change the locations array... */ - while (num_dests > 0) + if (locations) { - if ((comp = strcasecmp(name, dests->name)) < 0) - return (NULL); - else if (comp == 0) - { - if ((!instance && !dests->instance) || - (instance != NULL && dests->instance != NULL && - !strcasecmp(instance, dests->instance))) - return (dests); - } + newlocations = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, + locations); - num_dests --; - dests ++; + if (locprinter) + CFArrayRemoveValueAtIndex(newlocations, locindex); + } + else + newlocations = CFArrayCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeArrayCallBacks); + + newlocation = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (newlocation && newlocations) + { + /* + * Put the new location at the front of the array... + */ + + CFDictionaryAddValue(newlocation, kLocationNetworkKey, network); + CFDictionaryAddValue(newlocation, kLocationPrinterIDKey, name); + CFArrayInsertValueAtIndex(newlocations, 0, newlocation); + + /* + * Limit the number of locations to 10... + */ + + while (CFArrayGetCount(newlocations) > 10) + CFArrayRemoveValueAtIndex(newlocations, 10); + + /* + * Push the changes out... + */ + + CFPreferencesSetAppValue(kLastUsedPrintersKey, newlocations, + kCUPSPrintingPrefs); + CFPreferencesAppSynchronize(kCUPSPrintingPrefs); + notify_post("com.apple.printerPrefsChange"); } + + if (newlocations) + CFRelease(newlocations); + + if (newlocation) + CFRelease(newlocation); } - return (NULL); + if (locations) + CFRelease(locations); + + CFRelease(network); } /* - * '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, and printer-type attributes as options. - * - * Use the cupsFreeDests() function to free the destination list and - * the cupsGetDest() function to find a particular destination. + * '_cupsAppleSetUseLastPrinter()' - Set whether to use the last used printer. */ -int /* O - Number of destinations */ -cupsGetDests(cups_dest_t **dests) /* O - Destinations */ +void +_cupsAppleSetUseLastPrinter( + int uselast) /* O - 1 to use last printer, 0 otherwise */ { - return (cupsGetDests2(CUPS_HTTP_DEFAULT, dests)); + CFPreferencesSetAppValue(kUseLastPrinter, + uselast ? kCFBooleanTrue : kCFBooleanFalse, + kCUPSPrintingPrefs); + CFPreferencesAppSynchronize(kCUPSPrintingPrefs); + notify_post("com.apple.printerPrefsChange"); } +#endif /* __APPLE__ */ /* - * '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, and printer-type attributes as options. + * 'cupsConnectDest()' - Connect to the server for a destination. * - * Use the cupsFreeDests() function to free the destination list and - * the cupsGetDest() function to find a particular destination. + * Connect to the destination, returning a new http_t connection object and + * optionally the resource path to use for the destination. These calls will + * block until a connection is made, the timeout expires, the integer pointed + * to by "cancel" is non-zero, or the callback function (or block) returns 0, + * The caller is responsible for calling httpClose() on the returned object. * - * @since CUPS 1.1.21@ + * @since CUPS 1.6/OS X 10.8@ */ -int /* O - Number of destinations */ -cupsGetDests2(http_t *http, /* I - HTTP connection or CUPS_HTTP_DEFAULT */ - cups_dest_t **dests) /* O - Destinations */ +http_t * /* O - Connection to server or @code NULL@ */ +cupsConnectDest( + cups_dest_t *dest, /* I - Destination */ + unsigned flags, /* I - Connection flags */ + int msec, /* I - Timeout in milliseconds */ + int *cancel, /* I - Pointer to "cancel" variable */ + char *resource, /* I - Resource buffer */ + size_t resourcesize, /* I - Size of resource buffer */ + cups_dest_cb_t cb, /* I - Callback function */ + void *user_data) /* I - User data pointer */ { - int i; /* Looping var */ - int num_dests; /* Number of destinations */ - cups_dest_t *dest; /* Destination pointer */ - 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 */ - int num_reals; /* Number of real queues */ - cups_dest_t *reals; /* Real queues */ - _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ + const char *uri; /* Printer URI */ + char scheme[32], /* URI scheme */ + userpass[256], /* Username and password (unused) */ + hostname[256], /* Hostname */ + tempresource[1024]; /* Temporary resource buffer */ + int port; /* Port number */ + char portstr[16]; /* Port number string */ + http_encryption_t encryption; /* Encryption to use */ + http_addrlist_t *addrlist; /* Address list for server */ + http_t *http; /* Connection to server */ /* - * Range check the input... + * Range check input... */ - if (!dests) - return (0); + if (!dest) + { + if (resource) + *resource = '\0'; - /* - * Initialize destination array... - */ + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + return (NULL); + } - num_dests = 0; - *dests = (cups_dest_t *)0; + if (!resource || resourcesize < 1) + { + resource = tempresource; + resourcesize = sizeof(tempresource); + } /* - * Grab the printers and classes... + * Grab the printer URI... */ - num_dests = cups_get_sdests(http, CUPS_GET_PRINTERS, NULL, num_dests, dests); - num_dests = cups_get_sdests(http, CUPS_GET_CLASSES, NULL, num_dests, dests); + if ((uri = cupsGetOption("printer-uri-supported", dest->num_options, + dest->options)) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOENT), 0); - /* - * Make a copy of the "real" queues for a later sanity check... - */ + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, + dest); - if (num_dests > 0) - { - num_reals = num_dests; - reals = calloc(num_reals, sizeof(cups_dest_t)); + return (NULL); + } - if (reals) - memcpy(reals, *dests, num_reals * sizeof(cups_dest_t)); - else - num_reals = 0; +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) + if (strstr(uri, "._tcp")) + { + if ((uri = cups_dnssd_resolve(dest, uri, msec, cancel, cb, + user_data)) == NULL) + return (NULL); } - else +#endif /* HAVE_DNSSD || HAVE_AVAHI */ + + if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), + userpass, sizeof(userpass), hostname, sizeof(hostname), + &port, resource, resourcesize) < HTTP_URI_STATUS_OK) { - num_reals = 0; - reals = NULL; + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer URI."), 1); + + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, + dest); + + return (NULL); } /* - * Grab the default destination... + * Lookup the address for the server... */ -#ifdef __APPLE__ - if ((defprinter = appleGetDefault(name, sizeof(name))) == NULL) -#endif /* __APPLE__ */ - defprinter = cupsGetDefault2(http); - - if (defprinter) - { - /* - * Grab printer and instance name... - */ - -#ifdef __APPLE__ - if (name != defprinter) -#endif /* __APPLE__ */ - strlcpy(name, defprinter, sizeof(name)); + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING, + dest); - if ((instance = strchr(name, '/')) != NULL) - *instance++ = '\0'; + snprintf(portstr, sizeof(portstr), "%d", port); - /* - * Lookup the printer and instance and make it the default... - */ + if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portstr)) == NULL) + { + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, + dest); - if ((dest = cupsGetDest(name, instance, num_dests, *dests)) != NULL) - dest->is_default = 1; + return (NULL); } - else + + if (cancel && *cancel) { - /* - * This initialization of "instance" is unnecessary, but avoids a - * compiler warning... - */ + httpAddrFreeList(addrlist); - instance = NULL; + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CANCELED, + dest); + + return (NULL); } /* - * Load the /etc/cups/lpoptions and ~/.cups/lpoptions files... + * Create the HTTP object pointing to the server referenced by the URI... */ - snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); - num_dests = cups_get_dests(filename, NULL, NULL, num_dests, dests); - - if ((home = getenv("HOME")) != NULL) - { - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); - if (access(filename, 0)) - snprintf(filename, sizeof(filename), "%s/.lpoptions", home); + if (!strcmp(scheme, "ipps") || port == 443) + encryption = HTTP_ENCRYPTION_ALWAYS; + else + encryption = HTTP_ENCRYPTION_IF_REQUESTED; - num_dests = cups_get_dests(filename, NULL, NULL, num_dests, dests); - } + http = httpConnect2(hostname, port, addrlist, AF_UNSPEC, encryption, 1, 0, + NULL); /* - * 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... + * Connect if requested... */ - if (num_reals) + if (flags & CUPS_DEST_FLAGS_UNCONNECTED) { - /* - * See if we have a default printer... - */ - - if ((dest = cupsGetDest(NULL, NULL, num_dests, *dests)) != NULL) - { - /* - * Have a default; see if it is real... - */ - - dest = cupsGetDest(dest->name, NULL, num_reals, reals); - } - - /* - * If dest is NULL, then no default (that exists) is set, so we - * need to set a default if one exists... - */ + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED, dest); + } + else + { + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CONNECTING, + dest); - if (dest == NULL && defprinter != NULL) + if (!httpReconnect2(http, msec, cancel) && cb) { - for (i = 0; i < num_dests; i ++) - (*dests)[i].is_default = 0; - - if ((dest = cupsGetDest(name, instance, num_dests, *dests)) != NULL) - dest->is_default = 1; + if (cancel && *cancel) + (*cb)(user_data, + CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CONNECTING, dest); + else + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, + dest); } - - /* - * Free memory... - */ - - free(reals); + else if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_NONE, dest); } - /* - * Return the number of destinations... - */ - - return (num_dests); + return (http); } +#ifdef __BLOCKS__ /* - * 'cupsGetNamedDest()' - Get options for the named destination. + * 'cupsConnectDestBlock()' - Connect to the server for a destination. * - * This function is optimized for retrieving a single destination and should - * be used instead of cupsGetDests() and cupsGetDest() when you either know - * the name of the destination or want to print to the default destination. - * If NULL is returned, the destination does not exist or there is no default - * destination. - * - * If "http" is CUPS_HTTP_DEFAULT, the connection to the default print server - * will be used. + * Connect to the destination, returning a new http_t connection object and + * optionally the resource path to use for the destination. These calls will + * block until a connection is made, the timeout expires, the integer pointed + * to by "cancel" is non-zero, or the callback function (or block) returns 0, + * The caller is responsible for calling httpClose() on the returned object. * - * If "name" is NULL, the default printer for the current user will be returned. + * @since CUPS 1.6/OS X 10.8@ + */ + +http_t * /* O - Connection to server or @code NULL@ */ +cupsConnectDestBlock( + cups_dest_t *dest, /* I - Destination */ + unsigned flags, /* I - Connection flags */ + int msec, /* I - Timeout in milliseconds */ + int *cancel, /* I - Pointer to "cancel" variable */ + char *resource, /* I - Resource buffer */ + size_t resourcesize, /* I - Size of resource buffer */ + cups_dest_block_t block) /* I - Callback block */ +{ + return (cupsConnectDest(dest, flags, msec, cancel, resource, resourcesize, + (cups_dest_cb_t)cups_block_cb, (void *)block)); +} +#endif /* __BLOCKS__ */ + + +/* + * 'cupsCopyDest()' - Copy a destination. * - * The returned destination must be freed using cupsFreeDests() with a - * "num_dests" of 1. + * Make a copy of the destination to an array of destinations (or just a single + * copy) - for use with the cupsEnumDests* functions. The caller is responsible + * for calling cupsFreeDests() on the returned object(s). * - * @since CUPS 1.4@ + * @since CUPS 1.6/OS X 10.8@ */ -cups_dest_t * /* O - Destination or NULL */ -cupsGetNamedDest(http_t *http, /* I - HTTP connection or CUPS_HTTP_DEFAULT */ - const char *name, /* I - Destination name or NULL */ - const char *instance) /* I - Instance name or NULL */ +int +cupsCopyDest(cups_dest_t *dest, + int num_dests, + cups_dest_t **dests) { - cups_dest_t *dest; /* Destination */ - char filename[1024], /* Path to lpoptions */ - defname[256]; /* Default printer name */ - const char *home = getenv("HOME"); /* Home directory */ - ipp_op_t op = IPP_GET_PRINTER_ATTRIBUTES; - /* IPP operation to get server ops */ - _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ + int i; /* Looping var */ + cups_dest_t *new_dest; /* New destination pointer */ + cups_option_t *new_option, /* Current destination option */ + *option; /* Current parent option */ /* - * If "name" is NULL, find the default destination... + * Range check input... */ - if (!name) - { - if ((name = getenv("LPDEST")) == NULL) - if ((name = getenv("PRINTER")) != NULL && !strcmp(name, "lp")) - name = NULL; - - if (!name && home) - { - /* - * No default in the environment, try the user's lpoptions files... - */ - - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); - - if ((name = cups_get_default(filename, defname, sizeof(defname), - &instance)) == NULL) - { - snprintf(filename, sizeof(filename), "%s/.lpoptions", home); - name = cups_get_default(filename, defname, sizeof(defname), - &instance); - } - } - - if (!name) - { - /* - * Still not there? Try the system lpoptions file... - */ - - snprintf(filename, sizeof(filename), "%s/lpoptions", - cg->cups_serverroot); - name = cups_get_default(filename, defname, sizeof(defname), &instance); - } - - if (!name) - { - /* - * No locally-set default destination, ask the server... - */ - - op = CUPS_GET_DEFAULT; - } - } + if (!dest || num_dests < 0 || !dests) + return (num_dests); /* - * Get the printer's attributes... + * See if the destination already exists... */ - if (!cups_get_sdests(http, op, name, 0, &dest)) - return (NULL); + if ((new_dest = cupsGetDest(dest->name, dest->instance, num_dests, + *dests)) != NULL) + { + /* + * Protect against copying destination to itself... + */ - if (instance) - dest->instance = _cupsStrAlloc(instance); + if (new_dest == dest) + return (num_dests); - /* - * Then add local options... - */ + /* + * Otherwise, free the options... + */ - snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); - cups_get_dests(filename, name, instance, 1, &dest); + cupsFreeOptions(new_dest->num_options, new_dest->options); - if (home) + new_dest->num_options = 0; + new_dest->options = NULL; + } + else + new_dest = cups_add_dest(dest->name, dest->instance, &num_dests, dests); + + if (new_dest) { - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); + if ((new_dest->options = calloc(sizeof(cups_option_t), + dest->num_options)) == NULL) + return (cupsRemoveDest(dest->name, dest->instance, num_dests, dests)); - if (access(filename, 0)) - snprintf(filename, sizeof(filename), "%s/.lpoptions", home); + new_dest->num_options = dest->num_options; - cups_get_dests(filename, name, instance, 1, &dest); + for (i = dest->num_options, option = dest->options, + new_option = new_dest->options; + i > 0; + i --, option ++, new_option ++) + { + new_option->name = _cupsStrRetain(option->name); + new_option->value = _cupsStrRetain(option->value); + } } - /* - * Return the result... - */ - - return (dest); + return (num_dests); } /* - * 'cupsRemoveDest()' - Remove a destination from the destination list. + * 'cupsEnumDests()' - Enumerate available destinations with a callback function. * - * Removing a destination/instance does not delete the class or printer - * queue, merely the lpoptions for that destination/instance. Use the - * cupsSetDests() or cupsSetDests2() functions to save the new options - * for the user. + * Destinations are enumerated from one or more sources. The callback function + * receives the @code user_data@ pointer, destination name, instance, number of + * options, and options which can be used as input to the @link cupsAddDest@ + * function. The function must return 1 to continue enumeration or 0 to stop. + * + * Enumeration happens on the current thread and does not return until all + * destinations have been enumerated or the callback function returns 0. * - * @since CUPS 1.3@ + * @since CUPS 1.6/OS X 10.8@ */ -int /* O - New number of destinations */ -cupsRemoveDest(const char *name, /* I - Destination name */ - const char *instance, /* I - Instance name or NULL */ - int num_dests, /* I - Number of destinations */ - cups_dest_t **dests) /* IO - Destinations */ +int /* O - 1 on success, 0 on failure */ +cupsEnumDests( + 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; /* Index into destinations */ - cups_dest_t *dest; /* Pointer to destination */ - + int i, /* Looping var */ + num_dests; /* Number of destinations */ + cups_dest_t *dests = NULL, /* Destinations */ + *dest; /* Current destination */ +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) + int count, /* Number of queries started */ + remaining; /* Remainder of timeout */ + _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, /* IPP browser */ + local_ipp_ref; /* Local IPP browser */ +# ifdef HAVE_SSL + DNSServiceRef ipps_ref, /* IPPS browser */ + local_ipps_ref; /* 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; /* IPP browser */ +# ifdef HAVE_SSL + AvahiServiceBrowser *ipps_ref; /* IPPS browser */ +# endif /* HAVE_SSL */ +# endif /* HAVE_DNSSD */ +#endif /* HAVE_DNSSD || HAVE_AVAHI */ /* - * Find the destination... + * Range check input... */ - if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL) - return (num_dests); + (void)flags; + + if (!cb) + return (0); /* - * Free memory... + * Get the list of local printers and pass them to the callback function... */ - _cupsStrFree(dest->name); - _cupsStrFree(dest->instance); - cupsFreeOptions(dest->num_options, dest->options); + num_dests = _cupsGetDests(CUPS_HTTP_DEFAULT, IPP_OP_CUPS_GET_PRINTERS, NULL, + &dests, type, mask); + + for (i = num_dests, dest = dests; + i > 0 && (!cancel || !*cancel); + i --, dest ++) + if (!(*cb)(user_data, i > 1 ? CUPS_DEST_FLAGS_MORE : CUPS_DEST_FLAGS_NONE, + dest)) + break; + + cupsFreeDests(num_dests, dests); + if (i > 0 || msec == 0) + return (1); + +#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI) /* - * Remove the destination from the array... + * Get Bonjour-shared printers... */ - num_dests --; + data.type = type; + data.mask = mask; + data.devices = cupsArrayNew3((cups_array_func_t)cups_dnssd_compare_devices, + NULL, NULL, 0, NULL, + (cups_afree_func_t)cups_dnssd_free_device); - i = dest - *dests; +# ifdef HAVE_DNSSD + if (DNSServiceCreateConnection(&data.main_ref) != kDNSServiceErr_NoError) + return (0); - if (i < num_dests) - memmove(dest, dest + 1, (num_dests - i) * sizeof(cups_dest_t)); + main_fd = DNSServiceRefSockFD(data.main_ref); + + ipp_ref = data.main_ref; + DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, + "_ipp._tcp", NULL, + (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data); + + local_ipp_ref = data.main_ref; + DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection, + kDNSServiceInterfaceIndexLocalOnly, + "_ipp._tcp", NULL, + (DNSServiceBrowseReply)cups_dnssd_local_cb, &data); + +# ifdef HAVE_SSL + ipps_ref = data.main_ref; + DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0, + "_ipps._tcp", NULL, + (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data); + + local_ipps_ref = data.main_ref; + DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection, + kDNSServiceInterfaceIndexLocalOnly, + "_ipps._tcp", NULL, + (DNSServiceBrowseReply)cups_dnssd_local_cb, &data); +# endif /* HAVE_SSL */ + +# else /* HAVE_AVAHI */ + if ((data.simple_poll = avahi_simple_poll_new()) == NULL) + { + DEBUG_puts("cupsEnumDests: Unable to create Avahi simple poll object."); + return (1); + } - return (num_dests); -} + avahi_simple_poll_set_func(data.simple_poll, cups_dnssd_poll_cb, &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("cupsEnumDests: Unable to create Avahi client."); + avahi_simple_poll_free(data.simple_poll); + return (1); + } -/* - * 'cupsSetDefaultDest()' - Set the default destination. - * - * @since CUPS 1.3@ - */ + ipp_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, "_ipp._tcp", NULL, + 0, cups_dnssd_browse_cb, &data); +# ifdef HAVE_SSL + ipps_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, "_ipps._tcp", NULL, + 0, cups_dnssd_browse_cb, &data); +# endif /* HAVE_SSL */ +# endif /* HAVE_DNSSD */ + + if (msec < 0) + remaining = INT_MAX; + else + remaining = msec; -void -cupsSetDefaultDest( - const char *name, /* I - Destination name */ - const char *instance, /* I - Instance name or NULL */ - int num_dests, /* I - Number of destinations */ - cups_dest_t *dests) /* I - Destinations */ -{ - int i; /* Looping var */ - cups_dest_t *dest; /* Current destination */ + while (remaining > 0 && (!cancel || !*cancel)) + { + /* + * Check for input... + */ +# ifdef HAVE_DNSSD +# ifdef HAVE_POLL + pfd.fd = main_fd; + pfd.events = POLLIN; - /* - * Range check input... - */ + nfds = poll(&pfd, 1, remaining > 250 ? 250 : remaining); - if (!name || num_dests <= 0 || !dests) - return; +# else + FD_ZERO(&input); + FD_SET(main_fd, &input); - /* - * Loop through the array and set the "is_default" flag for the matching - * destination... - */ + timeout.tv_sec = 0; + timeout.tv_usec = remaining > 250 ? 250000 : remaining * 1000; - for (i = num_dests, dest = dests; i > 0; i --, dest ++) - dest->is_default = !strcasecmp(name, dest->name) && - ((!instance && !dest->instance) || - (instance && dest->instance && - !strcasecmp(instance, dest->instance))); -} + nfds = select(main_fd + 1, &input, NULL, NULL, &timeout); +# endif /* HAVE_POLL */ + if (nfds > 0) + DNSServiceProcessResult(data.main_ref); + else if (nfds == 0) + remaining -= 250; -/* - * '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. - */ +# else /* HAVE_AVAHI */ + data.got_data = 0; -void -cupsSetDests(int num_dests, /* I - Number of destinations */ - cups_dest_t *dests) /* I - Destinations */ -{ - cupsSetDests2(CUPS_HTTP_DEFAULT, num_dests, dests); -} + if ((error = avahi_simple_poll_iterate(data.simple_poll, 250)) > 0) + { + /* + * We've been told to exit the loop. Perhaps the connection to + * Avahi failed. + */ + break; + } -/* - * '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@ - */ + if (!data.got_data) + remaining -= 250; +# endif /* HAVE_DNSSD */ -int /* O - 0 on success, -1 on error */ -cupsSetDests2(http_t *http, /* I - HTTP connection or CUPS_HTTP_DEFAULT */ - int num_dests, /* I - Number of destinations */ + for (device = (_cups_dnssd_device_t *)cupsArrayFirst(data.devices), + count = 0; + device; + device = (_cups_dnssd_device_t *)cupsArrayNext(data.devices)) + { + if (device->ref) + count ++; + + 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) + { + 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) + { + if ((device->type & mask) == type) + { + if (!(*cb)(user_data, CUPS_DEST_FLAGS_NONE, &device->dest)) + { + remaining = -1; + break; + } + } + + device->state = _CUPS_DNSSD_ACTIVE; + } + } + } + + cupsArrayDelete(data.devices); + +# ifdef HAVE_DNSSD + DNSServiceRefDeallocate(ipp_ref); + DNSServiceRefDeallocate(local_ipp_ref); + +# ifdef HAVE_SSL + DNSServiceRefDeallocate(ipp_ref); + DNSServiceRefDeallocate(local_ipp_ref); +# endif /* HAVE_SSL */ + + DNSServiceRefDeallocate(data.main_ref); + +# else /* HAVE_AVAHI */ + avahi_service_browser_free(ipp_ref); +# ifdef HAVE_SSL + avahi_service_browser_free(ipps_ref); +# endif /* HAVE_SSL */ + + avahi_client_free(data.client); + avahi_simple_poll_free(data.simple_poll); +# endif /* HAVE_DNSSD */ +#endif /* HAVE_DNSSD || HAVE_DNSSD */ + + return (1); +} + + +# ifdef __BLOCKS__ +/* + * 'cupsEnumDestsBlock()' - Enumerate available destinations with a block. + * + * Destinations are enumerated from one or more sources. The block receives the + * destination name, instance, number of options, and options which can be used + * as input to the @link cupsAddDest@ function. The block must return 1 to + * continue enumeration or 0 to stop. + * + * Enumeration happens on the current thread and does not return until all + * destinations have been enumerated or the block returns 0. + * + * @since CUPS 1.6/OS X 10.8@ + */ + +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. + */ + +void +cupsFreeDests(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, /* Temporary destinations */ - *temp; /* Current temporary dest */ - const char *val; /* Value of temporary option */ - _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ + int i; /* Looping var */ + cups_dest_t *dest; /* Current destination */ + + + 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); + } + + free(dests); +} + + +/* + * 'cupsGetDest()' - Get the named destination from the list. + * + * Use the @link cupsGetDests@ or @link cupsGetDests2@ functions to get a + * list of supported destinations for the current user. + */ + +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 (num_dests <= 0 || !dests) + return (NULL); + + if (!name) + { + /* + * NULL name for default printer. + */ + + while (num_dests > 0) + { + if (dests->is_default) + return (dests); + + num_dests --; + dests ++; + } + } + else + { + /* + * Lookup name and optionally the instance... + */ + + match = cups_find_dest(name, instance, num_dests, dests, -1, &diff); + + if (!diff) + return (dests + match); + } + + 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 */ + + + /* + * Range check input... + */ + + if (!dest || !resource || resourcesize < 1) + { + if (resource) + *resource = '\0'; + + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + return (NULL); + } + + /* + * Grab the printer URI... + */ + + if ((uri = cupsGetOption("printer-uri-supported", dest->num_options, + dest->options)) == NULL) + { + if (resource) + *resource = '\0'; + + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOENT), 0); + + return (NULL); + } + +#ifdef HAVE_DNSSD + if (strstr(uri, "._tcp")) + { + if ((uri = cups_dnssd_resolve(dest, uri, 5000, NULL, NULL, NULL)) == NULL) + return (NULL); + } +#endif /* HAVE_DNSSD */ + + if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), + userpass, sizeof(userpass), hostname, sizeof(hostname), + &port, resource, resourcesize) < HTTP_URI_STATUS_OK) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer URI."), 1); + + return (NULL); + } + + return (uri); +} + + +/* + * '_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. + * + * "name" is the name of an existing printer and is only used when "op" is + * IPP_OP_GET_PRINTER_ATTRIBUTES. + * + * "dest" is initialized to point to the array of destinations. + * + * 0 is returned if there are no printers, no default printer, or the named + * printer does not exist, respectively. + * + * Free the memory used by the destination array using the @link cupsFreeDests@ + * function. + * + * Note: On OS X 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. + */ + +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 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-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" + }; + + +#ifdef __APPLE__ + /* + * Get the default paper size... + */ + + appleGetPaperSize(media_default, sizeof(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] + */ + + request = ippNewRequest(op); + + ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]), + NULL, pattrs); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", NULL, cupsUser()); + + 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", + type); + ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask", + mask); + } + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/")) != NULL) + { + for (attr = response->attrs; attr != NULL; attr = attr->next) + { + /* + * Skip leading attributes until we hit a printer... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Pull the needed attributes from this printer... + */ + + printer_name = NULL; + num_options = 0; + options = NULL; + + 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 (!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-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... + */ + + num_options = cupsAddOption(attr->name, + cups_make_string(attr, value, + sizeof(value)), + num_options, &options); + } +#ifdef __APPLE__ + else if (!strcmp(attr->name, "media-supported")) + { + /* + * See if we can set a default media size... + */ + + int i; /* Looping var */ + + for (i = 0; i < attr->num_values; i ++) + if (!_cups_strcasecmp(media_default, attr->values[i].string.text)) + { + 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) && + (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... + */ + + strlcpy(optname, attr->name, sizeof(optname)); + optname[ptr - attr->name] = '\0'; + + if (_cups_strcasecmp(optname, "media") || + !cupsGetOption("media", num_options, options)) + num_options = cupsAddOption(optname, + cups_make_string(attr, value, + sizeof(value)), + num_options, &options); + } + } + + /* + * See if we have everything needed... + */ + + 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); + } + + /* + * Return the count... + */ + + return (num_dests); +} + + +/* + * '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, and printer-type 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 well. + * + * Use the @link cupsFreeDests@ function to free the destination list and + * the @link cupsGetDest@ function to find a particular destination. + */ + +int /* O - Number of destinations */ +cupsGetDests(cups_dest_t **dests) /* O - Destinations */ +{ + return (cupsGetDests2(CUPS_HTTP_DEFAULT, dests)); +} + + +/* + * '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, and printer-type 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 well. + * + * 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/OS X 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 */ +{ + int num_dests; /* Number of destinations */ + cups_dest_t *dest; /* Destination pointer */ + 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 */ + + + /* + * Range check the input... + */ + + if (!dests) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad NULL dests pointer"), 1); + return (0); + } + + /* + * Grab the printers and classes... + */ + + *dests = (cups_dest_t *)0; + num_dests = _cupsGetDests(http, IPP_OP_CUPS_GET_PRINTERS, NULL, dests, 0, 0); + + if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE) + { + cupsFreeDests(num_dests, *dests); + *dests = (cups_dest_t *)0; + return (0); + } + + /* + * Make a copy of the "real" queues for a later sanity check... + */ + + if (num_dests > 0) + { + num_reals = num_dests; + reals = calloc(num_reals, sizeof(cups_dest_t)); + + if (reals) + memcpy(reals, *dests, 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) + { + /* + * Separate printer and instance name... + */ + + if ((instance = strchr(name, '/')) != NULL) + *instance++ = '\0'; + + /* + * Lookup the printer and instance and make it the default... + */ + + if ((dest = cupsGetDest(name, instance, num_dests, *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); + num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL, + num_dests, dests); + + if ((home = getenv("HOME")) != NULL) + { + snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); + + num_dests = cups_get_dests(filename, NULL, NULL, user_default != NULL, + num_dests, 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, num_dests, *dests)) != NULL) + { + /* + * Have a default; see if it is real... + */ + + 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... () + */ + + num_dests = cupsRemoveDest(dest->name, dest->instance, num_dests, + dests); + } + } + + /* + * Free memory... + */ + + free(reals); + } + + /* + * Return the number of destinations... + */ + + if (num_dests > 0) + _cupsSetError(IPP_STATUS_OK, NULL, 0); + + return (num_dests); +} + + +/* + * 'cupsGetNamedDest()' - Get options for the named destination. + * + * This function is optimized for retrieving a single destination and should + * be used instead of @link cupsGetDests@ 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/OS X 10.6@ + */ + +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@ */ +{ + 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 "name" is NULL, find the default destination... + */ + + if (!name) + { + set_as_default = 1; + name = _cupsUserDefault(defname, sizeof(defname)); + + if (name) + { + char *ptr; /* Temporary pointer... */ + + 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... + */ + + snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); + + name = cups_get_default(filename, defname, sizeof(defname), &instance); + } + + if (!name) + { + /* + * Still not there? Try the system lpoptions file... + */ + + snprintf(filename, sizeof(filename), "%s/lpoptions", + cg->cups_serverroot); + name = cups_get_default(filename, defname, sizeof(defname), &instance); + } + + if (!name) + { + /* + * No locally-set default destination, ask the server... + */ + + op = IPP_OP_CUPS_GET_DEFAULT; + } + } + + /* + * Get the printer's attributes... + */ + + if (!_cupsGetDests(http, op, name, &dest, 0, 0)) + return (NULL); + + if (instance) + dest->instance = _cupsStrAlloc(instance); + + if (set_as_default) + dest->is_default = 1; + + /* + * Then add local options... + */ + + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); + cups_get_dests(filename, name, instance, 1, 1, &dest); + + if (home) + { + snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); + + cups_get_dests(filename, name, instance, 1, 1, &dest); + } + + /* + * Return the result... + */ + + return (dest); +} + + +/* + * '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/OS X 10.5@ + */ + +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 */ +{ + int i; /* Index into destinations */ + cups_dest_t *dest; /* Pointer to destination */ + + + /* + * Find the destination... + */ + + if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL) + return (num_dests); + + /* + * Free memory... + */ + + _cupsStrFree(dest->name); + _cupsStrFree(dest->instance); + cupsFreeOptions(dest->num_options, dest->options); + + /* + * Remove the destination from the array... + */ + + num_dests --; + + i = dest - *dests; + + if (i < num_dests) + memmove(dest, dest + 1, (num_dests - i) * sizeof(cups_dest_t)); + + return (num_dests); +} + + +/* + * 'cupsSetDefaultDest()' - Set the default destination. + * + * @since CUPS 1.3/OS X 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 */ + + + /* + * Range check input... + */ + + if (!name || num_dests <= 0 || !dests) + return; + + /* + * Loop through the array and set the "is_default" flag for the matching + * destination... + */ + + 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))); +} + + +/* + * '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. + */ + +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/OS X 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 */ + + + /* + * Range check the input... + */ + + if (!num_dests || !dests) + return (-1); + + /* + * Get the server destinations... + */ + + 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); + } + + /* + * Figure out which file to write to... + */ + + snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot); + +#ifndef WIN32 + if (getuid()) + { + /* + * Merge in server defaults... + */ + + num_temps = cups_get_dests(filename, NULL, NULL, 0, num_temps, &temps); + + /* + * Point to user defaults... + */ + + if ((home = getenv("HOME")) != NULL) + { + /* + * Create ~/.cups subdirectory... + */ + + snprintf(filename, sizeof(filename), "%s/.cups", home); + if (access(filename, 0)) + mkdir(filename, 0700); + + snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); + } + } +#endif /* !WIN32 */ + + /* + * Try to open the file... + */ + + if ((fp = fopen(filename, "w")) == NULL) + { + cupsFreeDests(num_temps, temps); + return (-1); + } + +#ifndef WIN32 + /* + * Set the permissions to 0644 when saving to the /etc/cups/lpoptions + * file... + */ + + if (!getuid()) + fchmod(fileno(fp), 0644); +#endif /* !WIN32 */ + + /* + * Write each printer; each line looks like: + * + * Dest name[/instance] options + * Default name[/instance] options + */ + + 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); + + wrote = 1; + } + else + wrote = 0; + + if ((temp = cupsGetDest(dest->name, dest->instance, num_temps, temps)) == NULL) + temp = cupsGetDest(dest->name, NULL, num_temps, temps); + + for (j = dest->num_options, option = dest->options; j > 0; j --, option ++) + { + /* + * See if this option is a printer attribute; if so, skip it... + */ + + 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. + */ + + if (temp && + (val = cupsGetOption(option->name, temp->num_options, + temp->options)) != NULL && + !_cups_strcasecmp(val, option->value)) + continue; + + /* + * Options don't match, write to the file... + */ + + if (!wrote) + { + fprintf(fp, "Dest %s", dest->name); + if (dest->instance) + fprintf(fp, "/%s", dest->instance); + wrote = 1; + } + + if (option->value[0]) + { + if (strchr(option->value, ' ') || + strchr(option->value, '\\') || + strchr(option->value, '\"') || + strchr(option->value, '\'')) + { + /* + * Quote the value... + */ + + fprintf(fp, " %s=\"", option->name); + + for (val = option->value; *val; val ++) + { + if (strchr("\"\'\\", *val)) + putc('\\', fp); + + putc(*val, fp); + } + + putc('\"', fp); + } + else + { + /* + * Store the literal value... + */ + + fprintf(fp, " %s=%s", option->name, option->value); + } + } + else + fprintf(fp, " %s", option->name); + } + + if (wrote) + fputs("\n", fp); + } + + /* + * Free the temporary destinations and close the file... + */ + + cupsFreeDests(num_temps, temps); + + fclose(fp); + +#ifdef __APPLE__ + /* + * Set the default printer for this location - this allows command-line + * and GUI applications to share the same default destination... + */ + + if ((dest = cupsGetDest(NULL, NULL, num_dests, dests)) != NULL) + { + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, + dest->name, + kCFStringEncodingUTF8); + /* Default printer name */ + + if (name) + { + _cupsAppleSetDefaultPrinter(name); + CFRelease(name); + } + } +#endif /* __APPLE__ */ + +#ifdef HAVE_NOTIFY_POST + /* + * Send a notification so that MacOS X applications can know about the + * change, too. + */ + + notify_post("com.apple.printerListChange"); +#endif /* HAVE_NOTIFY_POST */ + + return (0); +} + + +/* + * '_cupsUserDefault()' - Get the user default printer from environment + * variables and location information. + */ + +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 ((env = getenv("LPDEST")) == NULL) + if ((env = getenv("PRINTER")) != NULL && !strcmp(env, "lp")) + env = NULL; + + if (env) + { + strlcpy(name, env, namesize); + return (name); + } + +#ifdef __APPLE__ + /* + * Use location-based defaults if "use last printer" is selected in the + * system preferences... + */ + + if ((locprinter = _cupsAppleCopyDefaultPrinter()) != NULL) + { + CFStringGetCString(locprinter, name, namesize, kCFStringEncodingUTF8); + CFRelease(locprinter); + } + else + name[0] = '\0'; + + DEBUG_printf(("1_cupsUserDefault: Returning \"%s\".", name)); + + return (*name ? name : NULL); + +#else + /* + * No location-based defaults on this platform... + */ + + name[0] = '\0'; + return (NULL); +#endif /* __APPLE__ */ +} + + +#ifdef __APPLE__ +/* + * 'appleCopyLocations()' - Copy the location history array. + */ + +static CFArrayRef /* O - Location array or NULL */ +appleCopyLocations(void) +{ + CFArrayRef locations; /* Location array */ + + + /* + * Look up the location array in the preferences... + */ + + if ((locations = CFPreferencesCopyAppValue(kLastUsedPrintersKey, + kCUPSPrintingPrefs)) == NULL) + return (NULL); + + if (CFGetTypeID(locations) != CFArrayGetTypeID()) + { + CFRelease(locations); + return (NULL); + } + + return (locations); +} + + +/* + * 'appleCopyNetwork()' - Get the network ID for the current location. + */ + +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 ((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); +} + + +/* + * 'appleGetPaperSize()' - Get the default paper size. + */ + +static char * /* O - Default paper size */ +appleGetPaperSize(char *name, /* I - Paper size name buffer */ + int namesize) /* I - Size of buffer */ +{ + CFStringRef defaultPaperID; /* Default paper ID */ + pwg_media_t *pwgmedia; /* PWG media size */ + + + defaultPaperID = _cupsAppleCopyDefaultPaperID(); + if (!defaultPaperID || + CFGetTypeID(defaultPaperID) != CFStringGetTypeID() || + !CFStringGetCString(defaultPaperID, name, namesize, + kCFStringEncodingUTF8)) + name[0] = '\0'; + else if ((pwgmedia = pwgMediaForLegacy(name)) != NULL) + strlcpy(name, pwgmedia->pwg, namesize); + + if (defaultPaperID) + CFRelease(defaultPaperID); + + return (name); +} + + +/* + * 'appleGetPrinter()' - Get a printer from the history array. + */ + +static CFStringRef /* O - Printer name or NULL */ +appleGetPrinter(CFArrayRef locations, /* I - Location array */ + CFStringRef network, /* I - Network name */ + CFIndex *locindex) /* O - Index in 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 /* __APPLE__ */ + + +/* + * 'cups_add_dest()' - Add a destination to the array. + * + * Unlike cupsAddDest(), this function does not check for duplicates. + */ + +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 */ /* - * 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) * (*num_dests + 1)); + + if (!dest) + return (NULL); + + *dests = dest; /* - * Get the server destinations... + * Find where to insert the destination... */ - num_temps = cups_get_sdests(http, CUPS_GET_PRINTERS, NULL, 0, &temps); - num_temps = cups_get_sdests(http, CUPS_GET_CLASSES, NULL, num_temps, &temps); + if (*num_dests == 0) + insert = 0; + else + { + 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, + (*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, 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) - { - /* - * Remove the old ~/.lpoptions file... - */ + return (dest); +} - snprintf(filename, sizeof(filename), "%s/.lpoptions", home); - unlink(filename); - /* - * Create ~/.cups subdirectory... - */ +# ifdef __BLOCKS__ +/* + * 'cups_block_cb()' - Enumeration callback for block API. + */ - snprintf(filename, sizeof(filename), "%s/.cups", home); - if (access(filename, 0)) - mkdir(filename, 0700); +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__ */ - snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", home); - } - } -#endif /* !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)", + sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, + replyDomain, context)); /* - * Try to open the file... + * Don't do anything on error... */ - if ((fp = fopen(filename, "w")) == NULL) + if (errorCode != kDNSServiceErr_NoError) + return; + + /* + * Get the device... + */ + + cups_dnssd_get_device(data, serviceName, regtype, replyDomain); +} + + +# else /* HAVE_AVAHI */ +/* + * 'cups_dnssd_browse_cb()' - Browse for printers. + */ + +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 */ + + + (void)interface; + (void)protocol; + (void)context; + + switch (event) { - cupsFreeDests(num_temps, temps); - return (-1); + 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: + /* + * This object is new on the network. + */ + + if (flags & AVAHI_LOOKUP_RESULT_LOCAL) + { + /* + * This comes from the local machine so ignore it. + */ + + DEBUG_printf(("cups_dnssd_browse_cb: Ignoring local service \"%s\".", + name)); + } + else + { + /* + * Create a device entry for it if it doesn't yet exist. + */ + + cups_dnssd_get_device(data, name, type, domain); + } + break; + + case AVAHI_BROWSER_REMOVE: + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; } +} + + +/* + * 'cups_dnssd_client_cb()' - Avahi client callback function. + */ + +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 */ + + + (void)client; -#ifndef WIN32 /* - * Set the permissions to 0644 when saving to the /etc/cups/lpoptions - * file... + * If the connection drops, quit. */ - if (!getuid()) - fchmod(fileno(fp), 0644); -#endif /* !WIN32 */ + if (state == AVAHI_CLIENT_FAILURE) + { + DEBUG_puts("cups_dnssd_client_cb: Avahi connection failed."); + avahi_simple_poll_quit(data->simple_poll); + } +} +# endif /* HAVE_DNSSD */ + + +/* + * 'cups_dnssd_compare_device()' - Compare two devices. + */ + +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)); +} + + +/* + * 'cups_dnssd_free_device()' - Free the memory used by a device. + */ + +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)", device, + device->dest.name, data)); + +# ifdef HAVE_DNSSD + if (device->ref) + DNSServiceRefDeallocate(device->ref); +# else /* HAVE_AVAHI */ + if (device->ref) + avahi_record_browser_free(device->ref); +# endif /* HAVE_DNSSD */ + + _cupsStrFree(device->domain); + _cupsStrFree(device->fullName); + _cupsStrFree(device->regtype); + _cupsStrFree(device->dest.name); + + cupsFreeOptions(device->dest.num_options, device->dest.options); + + free(device); +} + + +/* + * 'cups_dnssd_get_device()' - Lookup a device and create it as needed. + */ + +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 */ +{ + _cups_dnssd_device_t key, /* Search key */ + *device; /* Device */ + char fullName[kDNSServiceMaxDomainName]; + /* Full name for query */ + + + DEBUG_printf(("5cups_dnssd_get_device(data=%p, serviceName=\"%s\", " + "regtype=\"%s\", replyDomain=\"%s\")", data, serviceName, + regtype, replyDomain)); /* - * Write each printer; each line looks like: - * - * Dest name[/instance] options - * Default name[/instance] options + * See if this is an existing device... */ - for (i = num_dests, dest = dests; i > 0; i --, dest ++) - if (dest->instance != NULL || dest->num_options != 0 || dest->is_default) + key.dest.name = (char *)serviceName; + + 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 */ + + if (!_cups_strcasecmp(replyDomain, "local.") && + _cups_strcasecmp(device->domain, replyDomain)) { - if (dest->is_default) - { - fprintf(fp, "Default %s", dest->name); - if (dest->instance) - fprintf(fp, "/%s", dest->instance); + /* + * Update the "global" listing to use the .local domain name instead. + */ - wrote = 1; - } - else - wrote = 0; + _cupsStrFree(device->domain); + device->domain = _cupsStrAlloc(replyDomain); - if ((temp = cupsGetDest(dest->name, dest->instance, num_temps, temps)) == NULL) - temp = cupsGetDest(dest->name, NULL, num_temps, temps); + DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use local " + "domain.", device->dest.name)); - for (j = dest->num_options, option = dest->options; j > 0; j --, option ++) - { - /* - * See if this option is a printer attribute; if so, skip it... - */ + update = 1; + } - if ((match = _ippFindOption(option->name)) != NULL && - match->group_tag == IPP_TAG_PRINTER) - continue; + if (!_cups_strcasecmp(regtype, "_ipps._tcp") && + _cups_strcasecmp(device->regtype, regtype)) + { + /* + * Prefer IPPS over IPP. + */ - /* - * See if the server/global options match these; if so, don't - * write 'em. - */ + _cupsStrFree(device->regtype); + device->regtype = _cupsStrAlloc(regtype); - if (temp && - (val = cupsGetOption(option->name, temp->num_options, - temp->options)) != NULL && - !strcasecmp(val, option->value)) - continue; + 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 + { + /* + * No, add the device... + */ + + DEBUG_printf(("6cups_dnssd_get_device: Adding '%s' for %s with domain " + "'%s'.", serviceName, + !strcmp(regtype, "_ipps._tcp") ? "IPPS" : "IPP", + replyDomain)); + + device = calloc(sizeof(_cups_dnssd_device_t), 1); + device->dest.name = _cupsStrAlloc(serviceName); + device->domain = _cupsStrAlloc(replyDomain); + device->regtype = _cupsStrAlloc(regtype); + + cupsArrayAdd(data->devices, device); + } + + /* + * Set the "full name" of this service, which is used for queries... + */ + +# ifdef HAVE_DNSSD + DNSServiceConstructFullName(fullName, device->dest.name, device->regtype, + device->domain); +# else /* HAVE_AVAHI */ + avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, + regtype, replyDomain); +# endif /* HAVE_DNSSD */ - /* - * Options don't match, write to the file... - */ + _cupsStrFree(device->fullName); + device->fullName = _cupsStrAlloc(fullName); - if (!wrote) - { - fprintf(fp, "Dest %s", dest->name); - if (dest->instance) - fprintf(fp, "/%s", dest->instance); - wrote = 1; - } - - if (option->value[0]) - { - if (strchr(option->value, ' ') || - strchr(option->value, '\\') || - strchr(option->value, '\"') || - strchr(option->value, '\'')) - { - /* - * Quote the value... - */ + if (device->ref) + { +# ifdef HAVE_DNSSD + DNSServiceRefDeallocate(device->ref); +# else /* HAVE_AVAHI */ + avahi_record_browser_free(device->ref); +# endif /* HAVE_DNSSD */ - fprintf(fp, " %s=\"", option->name); + device->ref = 0; + } - for (val = option->value; *val; val ++) - { - if (strchr("\"\'\\", *val)) - putc('\\', fp); + if (device->state == _CUPS_DNSSD_ACTIVE) + { + (*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest); + device->state = _CUPS_DNSSD_NEW; + } - putc(*val, fp); - } + return (device); +} - putc('\"', fp); - } - else - { - /* - * Store the literal value... - */ - fprintf(fp, " %s=%s", option->name, option->value); - } - } - else - fprintf(fp, " %s", option->name); - } +# ifdef HAVE_DNSSD +/* + * 'cups_dnssd_local_cb()' - Browse for local printers. + */ - if (wrote) - fputs("\n", fp); - } +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 */ +{ + _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; + /* Enumeration data */ + _cups_dnssd_device_t *device; /* Device */ + + + DEBUG_printf(("5cups_dnssd_local_cb(sdRef=%p, flags=%x, " + "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", " + "regtype=\"%s\", replyDomain=\"%s\", context=%p)", + sdRef, flags, interfaceIndex, errorCode, serviceName, + regtype, replyDomain, context)); /* - * Free the temporary destinations and close the file... + * Only process "add" data... */ - cupsFreeDests(num_temps, temps); - - fclose(fp); + if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) + return; -#ifdef __APPLE__ /* - * Set the default printer for this location - this allows command-line - * and GUI applications to share the same default destination... + * Get the device... */ - if ((dest = cupsGetDest(NULL, NULL, num_dests, dests)) != NULL) - appleSetDefault(dest->name); -#endif /* __APPLE__ */ + device = cups_dnssd_get_device(data, serviceName, regtype, replyDomain); -#ifdef HAVE_NOTIFY_POST /* - * Send a notification so that MacOS X applications can know about the - * change, too. + * Hide locally-registered devices... */ - notify_post("com.apple.printerListChange"); -#endif /* HAVE_NOTIFY_POST */ + DEBUG_printf(("6cups_dnssd_local_cb: Hiding local printer '%s'.", + serviceName)); - return (0); + if (device->ref) + { + DNSServiceRefDeallocate(device->ref); + device->ref = 0; + } + + if (device->state == _CUPS_DNSSD_ACTIVE) + (*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest); + + device->state = _CUPS_DNSSD_LOCAL; } +# endif /* HAVE_DNSSD */ -#ifdef __APPLE__ +# ifdef HAVE_AVAHI /* - * 'appleGetDefault()' - Get the default printer for this location. + * '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. + * (Avahi Ticket #364) */ -static char * /* O - Name or NULL if no default */ -appleGetDefault(char *name, /* I - Name buffer */ - int namesize) /* I - Size of name buffer */ +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) */ { - CFStringRef network; /* Network location */ - CFArrayRef locations; /* Location array */ - CFStringRef locprinter; /* Current printer */ + _cups_dnssd_data_t *data = (_cups_dnssd_data_t *)context; + /* Enumeration data */ + int val; /* Return value */ - /* - * Use location-based defaults if "use last printer" is selected in the - * system preferences... - */ + (void)timeout; + + val = poll(pollfds, num_pollfds, 250); - if (!appleUseLastPrinter()) + if (val < 0) { - DEBUG_puts("appleGetDefault: Not using last printer as default..."); - return (NULL); + DEBUG_printf(("cups_dnssd_poll_cb: %s", strerror(errno))); } + else if (val > 0) + data->got_data = 1; + + return (val); +} +# endif /* HAVE_AVAHI */ + + +/* + * 'cups_dnssd_query_cb()' - Process query data. + */ + +# 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 */ +{ +# 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 name[1024], /* Service name */ + *ptr; /* Pointer into string */ + _cups_dnssd_device_t dkey, /* Search key */ + *device; /* Device */ + + +# 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)", sdRef, flags, interfaceIndex, errorCode, + fullName, rrtype, rrclass, rdlen, rdata, ttl, context)); /* - * Get the current location... + * Only process "add" data... + */ + + if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) + return; + +# else /* HAVE_AVAHI */ + DEBUG_printf(("5cups_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... */ - if ((network = appleGetNetwork()) == NULL) + if (event != AVAHI_BROWSER_NEW) { - DEBUG_puts("appleGetDefault: Unable to get current network..."); - return (NULL); - } + if (event == AVAHI_BROWSER_FAILURE) + DEBUG_printf(("cups_dnssd_query_cb: %s", + avahi_strerror(avahi_client_errno(client)))); -#ifdef DEBUG - CFStringGetCString(network, name, namesize, kCFStringEncodingUTF8); - printf("appleGetDefault: network=\"%s\"\n", name); -#endif /* DEBUG */ + return; + } +# endif /* HAVE_DNSSD */ /* - * Lookup the network in the preferences... + * Lookup the service in the devices array. */ - if ((locations = appleGetLocations()) == NULL) + dkey.dest.name = name; + + cups_dnssd_unquote(name, fullName, sizeof(name)); + + if ((ptr = strstr(name, "._")) != NULL) + *ptr = '\0'; + + if ((device = cupsArrayFind(data->devices, &dkey)) != NULL) { /* - * Missing or bad location array, so no location-based default... + * Found it, pull out the make and model from the TXT record and save it... */ - DEBUG_puts("appleGetDefault: Missing or bad location history array..."); + 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_REMOTE | 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... + */ - CFRelease(network); - return (NULL); - } - - DEBUG_printf(("appleGetDefault: Got location, %d entries...\n", - (int)CFArrayGetCount(locations))); + txtlen = *txt++; - if ((locprinter = appleGetPrinter(locations, network, NULL)) != NULL) - CFStringGetCString(locprinter, name, namesize, kCFStringEncodingUTF8); - else - name[0] = '\0'; + if (!txtlen || (txt + txtlen) > txtend) + break; - CFRelease(locations); - CFRelease(network); + txtnext = txt + txtlen; - DEBUG_printf(("appleGetDefault: Returning \"%s\"...\n", name)); + for (ptr = key; txt < txtnext && *txt != '='; txt ++) + *ptr++ = *txt; + *ptr = '\0'; - return (*name ? name : NULL); -} + if (txt < txtnext && *txt == '=') + { + txt ++; + if (txt < txtnext) + memcpy(value, txt, txtnext - txt); + value[txtnext - txt] = '\0'; -/* - * 'appleGetLocations()' - Get the location history array. - */ + DEBUG_printf(("6cups_dnssd_query_cb: %s=%s", key, value)); + } + else + { + DEBUG_printf(("6cups_dnssd_query_cb: '%s' with no value.", key)); + continue; + } -static CFArrayRef /* O - Location array or NULL */ -appleGetLocations(void) -{ - CFArrayRef locations; /* Location array */ + 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 ((ptr = value + strlen(value) - 1) > value && *ptr == ')') + *ptr = '\0'; - /* - * Look up the location array in the preferences... - */ + strlcpy(model, value + 1, sizeof(model)); + } + else + strlcpy(model, value, sizeof(model)); + } + else if (!_cups_strcasecmp(key, "ty")) + { + strlcpy(model, value, sizeof(model)); - if ((locations = CFPreferencesCopyAppValue(kLocationHistoryArrayKey, - kPMPrintingPreferences)) == NULL) - return (NULL); + 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 (CFGetTypeID(locations) != CFArrayGetTypeID()) - { - CFRelease(locations); - return (NULL); - } + const char *start, *next; /* Pointer into value */ + int have_pdf = 0; /* Have PDF? */ - return (locations); + for (start = value; start && *start; start = next) + { + if (!_cups_strncasecmp(start, "application/pdf", 15) && + (!start[15] || start[15] == ',')) + { + have_pdf = 1; + break; + } + + if ((next = strchr(start, ',')) != NULL) + next ++; + } + + if (!have_pdf) + device->state = _CUPS_DNSSD_INCOMPATIBLE; + } + else if (!_cups_strcasecmp(key, "printer-type")) + { + /* + * Value is either NNNN or 0xXXXX + */ + + saw_printer_type = 1; + type = strtol(value, NULL, 0); + } + 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; + } + } + + /* + * Save the printer-xxx values... + */ + + device->dest.num_options = cupsAddOption("printer-info", name, + device->dest.num_options, + &device->dest.options); + + if (make_and_model[0]) + { + strlcat(make_and_model, " ", sizeof(make_and_model)); + strlcat(make_and_model, model, sizeof(make_and_model)); + + 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); + + device->type = type; + snprintf(value, sizeof(value), "%u", type); + device->dest.num_options = cupsAddOption("printer-type", value, + device->dest.num_options, + &device->dest.options); + + /* + * Save the URI... + */ + + 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: printer-uri-supported=\"%s\"", uri)); + + device->dest.num_options = cupsAddOption("printer-uri-supported", uri, + device->dest.num_options, + &device->dest.options); + } + else + DEBUG_printf(("6cups_dnssd_query: Ignoring TXT record for '%s'.", + fullName)); } /* - * 'appleGetNetwork()' - Get the network ID for the current location. + * 'cups_dnssd_resolve()' - Resolve a Bonjour printer URI. */ -static CFStringRef /* O - Network ID */ -appleGetNetwork(void) +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 */ { - SCDynamicStoreRef dynamicStore; /* System configuration data */ - CFStringRef key; /* Current network configuration key */ - CFDictionaryRef ip_dict; /* Network configuration data */ - CFStringRef network = NULL; /* Current network ID */ - + char tempuri[1024]; /* Temporary URI buffer */ + _cups_dnssd_resolve_t resolve; /* Resolve data */ - if ((dynamicStore = SCDynamicStoreCreate(NULL, CFSTR("Printing"), NULL, - NULL)) != NULL) + + /* + * Resolve the URI... + */ + + resolve.cancel = cancel; + gettimeofday(&resolve.end_time, NULL); + if (msec > 0) { - if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity( - NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)) != NULL) + resolve.end_time.tv_sec += msec / 1000; + resolve.end_time.tv_usec += (msec % 1000) * 1000; + + while (resolve.end_time.tv_usec >= 1000000) { - if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL) - { - if ((network = CFDictionaryGetValue(ip_dict, - kSCPropNetIPv4Router)) != NULL) - CFRetain(network); + resolve.end_time.tv_sec ++; + resolve.end_time.tv_usec -= 1000000; + } + } + else + resolve.end_time.tv_sec += 75; - CFRelease(ip_dict); - } + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING, + dest); - CFRelease(key); - } + if ((uri = _httpResolveURI(uri, tempuri, sizeof(tempuri), + _HTTP_RESOLVE_FQDN, cups_dnssd_resolve_cb, + &resolve)) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to resolve printer URI."), 1); - CFRelease(dynamicStore); + if (cb) + (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, + dest); + + return (NULL); } - return (network); + /* + * Save the resolved URI... + */ + + dest->num_options = cupsAddOption("printer-uri-supported", uri, + dest->num_options, &dest->options); + + return (cupsGetOption("printer-uri-supported", dest->num_options, + dest->options)); } /* - * 'appleGetPrinter()' - Get a printer from the history array. + * 'cups_dnssd_resolve_cb()' - See if we should continue resolving. */ -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 int /* O - 1 to continue, 0 to stop */ +cups_dnssd_resolve_cb(void *context) /* I - Resolve data */ { - CFIndex i, /* Looping var */ - count; /* Number of locations */ - CFDictionaryRef location; /* Current location */ - CFStringRef locnetwork, /* Current network */ - locprinter; /* Current printer */ + _cups_dnssd_resolve_t *resolve = (_cups_dnssd_resolve_t *)context; + /* Resolve data */ + struct timeval curtime; /* Current time */ - 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; + /* + * If the cancel variable is set, return immediately. + */ + + if (*resolve->cancel) + return (0); + + /* + * Otherwise check the end time... + */ - return (locprinter); - } - } + gettimeofday(&curtime, NULL); - return (NULL); + 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)); } /* - * 'appleSetDefault()' - Set the default printer for this location. + * 'cups_dnssd_unquote()' - Unquote a name string. */ static void -appleSetDefault(const char *name) /* I - Default printer/class name */ +cups_dnssd_unquote(char *dst, /* I - Destination buffer */ + const char *src, /* I - Source string */ + size_t dstsize) /* I - Size of destination buffer */ { - CFStringRef network; /* Current network */ - CFArrayRef locations; /* Old locations array */ - CFIndex locindex; /* Index in locations array */ - CFStringRef locprinter; /* Current printer */ - CFMutableArrayRef newlocations; /* New locations array */ - CFMutableDictionaryRef newlocation; /* New location */ - CFStringRef newprinter; /* New printer */ - + char *dstend = dst + dstsize - 1; /* End of destination buffer */ - /* - * Get the current location... - */ - if ((network = appleGetNetwork()) == NULL) + while (*src && dst < dstend) { - DEBUG_puts("appleSetDefault: Unable to get current network..."); - return; + 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 ++; } -#ifdef DEBUG - CFStringGetCString(network, name, namesize, kCFStringEncodingUTF8); - printf("appleSetDefault: network=\"%s\"\n", name); -#endif /* DEBUG */ + *dst = '\0'; +} +#endif /* HAVE_DNSSD */ - if ((newprinter = CFStringCreateWithCString(kCFAllocatorDefault, name, - kCFStringEncodingUTF8)) == NULL) - { - CFRelease(network); - return; - } - /* - * Lookup the network in the preferences... - */ +/* + * 'cups_find_dest()' - Find a destination using a binary search. + */ - if ((locations = appleGetLocations()) != NULL) - locprinter = appleGetPrinter(locations, network, &locindex); - else - { - locprinter = NULL; - locindex = -1; - } +static int /* O - Index of match */ +cups_find_dest(const char *name, /* I - Destination name */ + const char *instance, /* I - Instance or NULL */ + int num_dests, /* I - Number of destinations */ + cups_dest_t *dests, /* I - Destinations */ + int prev, /* I - Previous index */ + int *rdiff) /* O - Difference of match */ +{ + int left, /* Low mark for binary search */ + right, /* High mark for binary search */ + current, /* Current index */ + diff; /* Result of comparison */ + cups_dest_t key; /* Search key */ + + + key.name = (char *)name; + key.instance = (char *)instance; - if (!locprinter || - CFStringCompare(locprinter, newprinter, 0) != kCFCompareEqualTo) + if (prev >= 0) { /* - * Need to change the locations array... + * Start search on either side of previous... */ - if (locations) + if ((diff = cups_compare_dests(&key, dests + prev)) == 0 || + (diff < 0 && prev == 0) || + (diff > 0 && prev == (num_dests - 1))) { - newlocations = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, - locations); - - if (locprinter) - CFArrayRemoveValueAtIndex(newlocations, locindex); + *rdiff = diff; + return (prev); } - else - newlocations = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); - - newlocation = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - - if (newlocation && newlocations) + else if (diff < 0) { /* - * Put the new location at the front of the array... - */ - - CFDictionaryAddValue(newlocation, kLocationNetworkKey, network); - CFDictionaryAddValue(newlocation, kLocationPrinterIDKey, newprinter); - CFArrayInsertValueAtIndex(newlocations, 0, newlocation); - - /* - * Limit the number of locations to 10... + * Start with previous on right side... */ - while (CFArrayGetCount(newlocations) > 10) - CFArrayRemoveValueAtIndex(newlocations, 10); - + left = 0; + right = prev; + } + else + { /* - * Push the changes out... + * Start wih previous on left side... */ - CFPreferencesSetAppValue(kLocationHistoryArrayKey, newlocations, - kPMPrintingPreferences); - CFPreferencesAppSynchronize(kPMPrintingPreferences); + left = prev; + right = num_dests - 1; } - - if (newlocations) - CFRelease(newlocations); - - if (newlocation) - CFRelease(newlocation); } - - CFRelease(locations); - CFRelease(network); - CFRelease(newprinter); -} - + else + { + /* + * Start search in the middle... + */ -/* - * 'appleUseLastPrinter()' - Get the default printer preference value. - */ + left = 0; + right = num_dests - 1; + } -static int /* O - 1 to use last printer, 0 otherwise */ -appleUseLastPrinter(void) -{ - CFPropertyListRef uselast; /* Use last printer preference value */ + do + { + current = (left + right) / 2; + diff = cups_compare_dests(&key, dests + current); + if (diff == 0) + break; + else if (diff < 0) + right = current; + else + left = current; + } + while ((right - left) > 1); - if ((uselast = CFPreferencesCopyAppValue(kUseLastPrinterAsCurrentPrinterKey, - kPMPrintingPreferences)) != NULL) + if (diff != 0) { - CFRelease(uselast); + /* + * Check the last 1 or 2 elements... + */ - if (uselast == kCFBooleanFalse) - return (0); + if ((diff = cups_compare_dests(&key, dests + left)) <= 0) + current = left; + else + { + diff = cups_compare_dests(&key, dests + right); + current = right; + } } - return (1); + /* + * Return the closest destination and the difference... + */ + + *rdiff = diff; + + return (current); } -#endif /* __APPLE__ */ /* @@ -1254,7 +3594,7 @@ cups_get_default(const char *filename, /* I - File to read */ char line[8192], /* Line from file */ *value, /* Value for line */ *nameptr; /* Pointer into name */ - int linenum; /* Current line */ + int linenum; /* Current line */ *namebuf = '\0'; @@ -1265,7 +3605,7 @@ cups_get_default(const char *filename, /* I - File to read */ while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { - if (!strcasecmp(line, "default") && value) + if (!_cups_strcasecmp(line, "default") && value) { strlcpy(namebuf, value, namesize); @@ -1294,11 +3634,13 @@ cups_get_default(const char *filename, /* I - File to read */ */ static int /* O - Number of destinations */ -cups_get_dests(const char *filename, /* I - File to read from */ - const char *match_name, /* I - Destination name we want */ - const char *match_inst, /* I - Instance name we want */ - int num_dests, /* I - Number of destinations */ - cups_dest_t **dests) /* IO - Destinations */ +cups_get_dests( + const char *filename, /* I - File to read from */ + const char *match_name, /* I - Destination name we want */ + const char *match_inst, /* I - Instance name we want */ + int user_default_set, /* I - User default printer set? */ + int num_dests, /* I - Number of destinations */ + cups_dest_t **dests) /* IO - Destinations */ { int i; /* Looping var */ cups_dest_t *dest; /* Current destination */ @@ -1308,13 +3650,12 @@ cups_get_dests(const char *filename, /* I - File to read from */ *name, /* Name of destination/option */ *instance; /* Instance of destination */ int linenum; /* Current line number */ - const char *printer; /* PRINTER or LPDEST */ - DEBUG_printf(("cups_get_dests(filename=\"%s\", match_name=\"%s\", " - "match_inst=\"%s\", num_dests=%d, dests=%p)\n", filename, - match_name ? match_name : "(null)", - match_inst ? match_inst : "(null)", num_dests, dests)); + DEBUG_printf(("7cups_get_dests(filename=\"%s\", match_name=\"%s\", " + "match_inst=\"%s\", user_default_set=%d, num_dests=%d, " + "dests=%p)", filename, match_name, match_inst, + user_default_set, num_dests, dests)); /* * Try to open the file... @@ -1323,18 +3664,6 @@ cups_get_dests(const char *filename, /* I - File to read from */ if ((fp = cupsFileOpen(filename, "r")) == NULL) return (num_dests); - /* - * Check environment variables... - */ - - if ((printer = getenv("LPDEST")) == NULL) - if ((printer = getenv("PRINTER")) != NULL) - if (strcmp(printer, "lp") == 0) - printer = NULL; - - DEBUG_printf(("cups_get_dests: printer=\"%s\"\n", - printer ? printer : "(null)")); - /* * Read each printer; each line looks like: * @@ -1350,12 +3679,12 @@ cups_get_dests(const char *filename, /* I - File to read from */ * See what type of line it is... */ - DEBUG_printf(("cups_get_dests: linenum=%d line=\"%s\" lineptr=\"%s\"\n", - linenum, line, lineptr ? lineptr : "(null)")); + DEBUG_printf(("9cups_get_dests: linenum=%d line=\"%s\" lineptr=\"%s\"", + linenum, line, lineptr)); - if ((strcasecmp(line, "dest") && strcasecmp(line, "default")) || !lineptr) + if ((_cups_strcasecmp(line, "dest") && _cups_strcasecmp(line, "default")) || !lineptr) { - DEBUG_puts("cups_get_dests: Not a dest or default line..."); + DEBUG_puts("9cups_get_dests: Not a dest or default line..."); continue; } @@ -1390,7 +3719,7 @@ cups_get_dests(const char *filename, /* I - File to read from */ if (*lineptr) *lineptr++ = '\0'; - DEBUG_printf(("cups_get_dests: name=\"%s\", instance=\"%s\"\n", name, + DEBUG_printf(("9cups_get_dests: name=\"%s\", instance=\"%s\"", name, instance)); /* @@ -1400,17 +3729,17 @@ cups_get_dests(const char *filename, /* I - File to read from */ if (match_name) { - if (strcasecmp(name, match_name) || + if (_cups_strcasecmp(name, match_name) || (!instance && match_inst) || (instance && !match_inst) || - (instance && strcasecmp(instance, match_inst))) + (instance && _cups_strcasecmp(instance, match_inst))) continue; dest = *dests; } else if (cupsGetDest(name, NULL, num_dests, *dests) == NULL) { - DEBUG_puts("cups_get_dests: Not found!"); + DEBUG_puts("9cups_get_dests: Not found!"); continue; } else @@ -1427,7 +3756,7 @@ cups_get_dests(const char *filename, /* I - File to read from */ * Out of memory! */ - DEBUG_puts("cups_get_dests: Out of memory!"); + DEBUG_puts("9cups_get_dests: Out of memory!"); break; } } @@ -1450,9 +3779,9 @@ cups_get_dests(const char *filename, /* I - File to read from */ * Set this as default if needed... */ - if (!printer && !strcasecmp(line, "default")) + if (!user_default_set && !_cups_strcasecmp(line, "default")) { - DEBUG_puts("cups_get_dests: Setting as default..."); + DEBUG_puts("9cups_get_dests: Setting as default..."); for (i = 0; i < num_dests; i ++) (*dests)[i].is_default = 0; @@ -1465,374 +3794,102 @@ cups_get_dests(const char *filename, /* I - File to read from */ * Close the file and return... */ - cupsFileClose(fp); + cupsFileClose(fp); return (num_dests); } /* - * 'cups_get_sdests()' - Get destinations from a server. + * 'cups_make_string()' - Make a comma-separated string of values from an IPP + * attribute. */ -static int /* O - Number of destinations */ -cups_get_sdests(http_t *http, /* I - HTTP connection or CUPS_HTTP_DEFAULT */ - ipp_op_t op, /* I - IPP operation */ - const char *name, /* I - Name of destination */ - int num_dests, /* I - Number of destinations */ - cups_dest_t **dests) /* IO - Destinations */ +static char * /* O - New string */ +cups_make_string( + ipp_attribute_t *attr, /* I - Attribute to convert */ + char *buffer, /* I - Buffer */ + size_t bufsize) /* I - Size of buffer */ { int i; /* Looping var */ - cups_dest_t *dest; /* Current destination */ - ipp_t *request, /* IPP Request */ - *response; /* IPP Response */ - ipp_attribute_t *attr; /* Current attribute */ - int accepting, /* printer-is-accepting-jobs attribute */ - shared, /* printer-is-shared attribute */ - state, /* printer-state attribute */ - change_time, /* printer-state-change-time attribute */ - type; /* printer-type attribute */ - const char *info, /* printer-info attribute */ - *location, /* printer-location attribute */ - *make_model, /* printer-make-and-model attribute */ - *printer_name; /* printer-name attribute */ - char uri[1024], /* printer-uri value */ - job_sheets[1024], /* job-sheets-default attribute */ - auth_info_req[1024], /* auth-info-required attribute */ - reasons[1024]; /* printer-state-reasons attribute */ - int num_options; /* Number of options */ - cups_option_t *options; /* Options */ - 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", - "job-sheets-default", - "printer-info", - "printer-is-accepting-jobs", - "printer-is-shared", - "printer-location", - "printer-make-and-model", - "printer-name", - "printer-state", - "printer-state-change-time", - "printer-state-reasons", - "printer-type", - "printer-defaults" - }; + char *ptr, /* Pointer into buffer */ + *end, /* Pointer to end of buffer */ + *valptr; /* Pointer into string attribute */ /* - * Build a CUPS_GET_PRINTERS or CUPS_GET_CLASSES request, which require - * the following attributes: - * - * attributes-charset - * attributes-natural-language - * requesting-user-name + * Return quickly if we have a single string value... */ - request = ippNewRequest(op); - - ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, - "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]), - NULL, pattrs); - - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, - "requesting-user-name", NULL, cupsUser()); - - if (name) - { - 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); - } + if (attr->num_values == 1 && + attr->value_tag != IPP_TAG_INTEGER && + attr->value_tag != IPP_TAG_ENUM && + attr->value_tag != IPP_TAG_BOOLEAN && + attr->value_tag != IPP_TAG_RANGE) + return (attr->values[0].string.text); /* - * Do the request and get back a response... + * Copy the values to the string, separating with commas and escaping strings + * as needed... */ - if ((response = cupsDoRequest(http, request, "/")) != NULL) - { - for (attr = response->attrs; attr != NULL; attr = attr->next) - { - /* - * Skip leading attributes until we hit a printer... - */ - - while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) - attr = attr->next; - - if (attr == NULL) - break; - - /* - * Pull the needed attributes from this printer... - */ - - accepting = 0; - change_time = 0; - info = NULL; - location = NULL; - make_model = NULL; - printer_name = NULL; - num_options = 0; - options = NULL; - shared = 1; - state = IPP_PRINTER_IDLE; - type = CUPS_PRINTER_LOCAL; + end = buffer + bufsize - 1; - auth_info_req[0] = '\0'; - job_sheets[0] = '\0'; - reasons[0] = '\0'; + for (i = 0, ptr = buffer; i < attr->num_values && ptr < end; i ++) + { + if (i) + *ptr++ = ','; - while (attr != NULL && attr->group_tag == IPP_TAG_PRINTER) - { - if (!strcmp(attr->name, "auth-info-required") && - attr->value_tag == IPP_TAG_KEYWORD) - { - strlcpy(auth_info_req, attr->values[0].string.text, - sizeof(auth_info_req)); + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + snprintf(ptr, end - ptr + 1, "%d", attr->values[i].integer); + break; - for (i = 1, ptr = auth_info_req + strlen(auth_info_req); - i < attr->num_values; - i ++) - { - snprintf(ptr, sizeof(auth_info_req) - (ptr - auth_info_req), ",%s", - attr->values[i].string.text); - ptr += strlen(ptr); - } - } - else if (!strcmp(attr->name, "job-sheets-default") && - (attr->value_tag == IPP_TAG_KEYWORD || - attr->value_tag == IPP_TAG_NAME)) - { - if (attr->num_values == 2) - snprintf(job_sheets, sizeof(job_sheets), "%s,%s", - attr->values[0].string.text, attr->values[1].string.text); + case IPP_TAG_BOOLEAN : + if (attr->values[i].boolean) + strlcpy(ptr, "true", end - ptr + 1); else - strlcpy(job_sheets, attr->values[0].string.text, - sizeof(job_sheets)); - } - else if (!strcmp(attr->name, "printer-info") && - attr->value_tag == IPP_TAG_TEXT) - info = attr->values[0].string.text; - else if (!strcmp(attr->name, "printer-is-accepting-jobs") && - attr->value_tag == IPP_TAG_BOOLEAN) - accepting = attr->values[0].boolean; - else if (!strcmp(attr->name, "printer-is-shared") && - attr->value_tag == IPP_TAG_BOOLEAN) - shared = attr->values[0].boolean; - else if (!strcmp(attr->name, "printer-location") && - attr->value_tag == IPP_TAG_TEXT) - location = attr->values[0].string.text; - else if (!strcmp(attr->name, "printer-make-and-model") && - attr->value_tag == IPP_TAG_TEXT) - make_model = attr->values[0].string.text; - else if (!strcmp(attr->name, "printer-name") && - attr->value_tag == IPP_TAG_NAME) - printer_name = attr->values[0].string.text; - else if (!strcmp(attr->name, "printer-state") && - attr->value_tag == IPP_TAG_ENUM) - state = attr->values[0].integer; - else if (!strcmp(attr->name, "printer-state-change-time") && - attr->value_tag == IPP_TAG_INTEGER) - change_time = attr->values[0].integer; - else if (!strcmp(attr->name, "printer-state-reasons") && - attr->value_tag == IPP_TAG_KEYWORD) - { - strlcpy(reasons, attr->values[0].string.text, sizeof(reasons)); - for (i = 1, ptr = reasons + strlen(reasons); - i < attr->num_values; - i ++) - { - snprintf(ptr, sizeof(reasons) - (ptr - reasons), ",%s", - attr->values[i].string.text); - ptr += strlen(ptr); - } - } - else if (!strcmp(attr->name, "printer-type") && - attr->value_tag == IPP_TAG_ENUM) - type = attr->values[0].integer; - else if (strncmp(attr->name, "notify-", 7) && - (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) && - strstr(attr->name, "-default")) - { - char *valptr; /* Pointer into attribute value */ - - - /* - * Add a default option... - */ + strlcpy(ptr, "false", end - ptr + 1); + break; - strlcpy(optname, attr->name, sizeof(optname)); - if ((ptr = strstr(optname, "-default")) != NULL) - *ptr = '\0'; + case IPP_TAG_RANGE : + if (attr->values[i].range.lower == attr->values[i].range.upper) + snprintf(ptr, end - ptr + 1, "%d", attr->values[i].range.lower); + else + snprintf(ptr, end - ptr + 1, "%d-%d", attr->values[i].range.lower, + attr->values[i].range.upper); + break; - value[0] = '\0'; - for (i = 0, ptr = value; i < attr->num_values; i ++) + default : + for (valptr = attr->values[i].string.text; + *valptr && ptr < end;) { - if (ptr >= (value + sizeof(value) - 1)) - break; - - if (i) - *ptr++ = ','; - - switch (attr->value_tag) + if (strchr(" \t\n\\\'\"", *valptr)) { - case IPP_TAG_INTEGER : - case IPP_TAG_ENUM : - snprintf(ptr, sizeof(value) - (ptr - value), "%d", - attr->values[i].integer); - break; - - case IPP_TAG_BOOLEAN : - if (attr->values[i].boolean) - strlcpy(ptr, "true", sizeof(value) - (ptr - value)); - else - strlcpy(ptr, "false", sizeof(value) - (ptr - value)); - break; - - case IPP_TAG_RANGE : - if (attr->values[i].range.lower == - attr->values[i].range.upper) - snprintf(ptr, sizeof(value) - (ptr - value), "%d", - attr->values[i].range.lower); - else - snprintf(ptr, sizeof(value) - (ptr - value), "%d-%d", - attr->values[i].range.lower, - attr->values[i].range.upper); - break; - - default : - for (valptr = attr->values[i].string.text; - *valptr && ptr < (value + sizeof(value) - 2);) - { - if (strchr(" \t\n\\\'\"", *valptr)) - *ptr++ = '\\'; - - *ptr++ = *valptr++; - } - - *ptr = '\0'; - break; - } - - ptr += strlen(ptr); - } - - num_options = cupsAddOption(optname, value, num_options, &options); - } - - attr = attr->next; - } + if (ptr >= (end - 1)) + break; - /* - * See if we have everything needed... - */ + *ptr++ = '\\'; + } - if (!printer_name) - { - cupsFreeOptions(num_options, options); + *ptr++ = *valptr++; + } - if (attr == NULL) + *ptr = '\0'; break; - else - continue; - } - - num_dests = cupsAddDest(printer_name, NULL, num_dests, dests); - - if ((dest = cupsGetDest(printer_name, NULL, num_dests, *dests)) != NULL) - { - dest->num_options = num_options; - dest->options = options; - - num_options = 0; - options = NULL; - - if (auth_info_req[0]) - dest->num_options = cupsAddOption("auth-info-required", auth_info_req, - dest->num_options, - &(dest->options)); - - if (job_sheets[0]) - dest->num_options = cupsAddOption("job-sheets", job_sheets, - dest->num_options, - &(dest->options)); - - if (info) - dest->num_options = cupsAddOption("printer-info", info, - dest->num_options, - &(dest->options)); - - sprintf(value, "%d", accepting); - dest->num_options = cupsAddOption("printer-is-accepting-jobs", value, - dest->num_options, - &(dest->options)); - - sprintf(value, "%d", shared); - dest->num_options = cupsAddOption("printer-is-shared", value, - dest->num_options, - &(dest->options)); - - if (location) - dest->num_options = cupsAddOption("printer-location", - location, dest->num_options, - &(dest->options)); - - if (make_model) - dest->num_options = cupsAddOption("printer-make-and-model", - make_model, dest->num_options, - &(dest->options)); - - sprintf(value, "%d", state); - dest->num_options = cupsAddOption("printer-state", value, - dest->num_options, - &(dest->options)); - - if (change_time) - { - sprintf(value, "%d", change_time); - dest->num_options = cupsAddOption("printer-state-change-time", value, - dest->num_options, - &(dest->options)); - } - - if (reasons[0]) - dest->num_options = cupsAddOption("printer-state-reasons", reasons, - dest->num_options, - &(dest->options)); - - sprintf(value, "%d", type); - dest->num_options = cupsAddOption("printer-type", value, - dest->num_options, - &(dest->options)); - } - - cupsFreeOptions(num_options, options); - - if (attr == NULL) - break; } - ippDelete(response); + ptr += strlen(ptr); } - /* - * Return the count... - */ + *ptr = '\0'; - return (num_dests); + return (buffer); } /* - * End of "$Id: dest.c 6943 2007-09-10 23:00:33Z mike $". + * End of "$Id: dest.c 11141 2013-07-16 14:58:25Z msweet $". */