]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/http-support.c
License change: Apache License, Version 2.0.
[thirdparty/cups.git] / cups / http-support.c
index cd78c628b67277d5bbf996e4449a22823bbe66f9..95135e0493f92cd67957a2424dbdd78a9acef72b 100644 (file)
@@ -1,58 +1,30 @@
 /*
- * "$Id: http-support.c 7952 2008-09-17 00:56:20Z mike $"
+ * HTTP support routines for CUPS.
  *
- *   HTTP support routines for the Common UNIX Printing System (CUPS) scheduler.
+ * Copyright 2007-2017 by Apple Inc.
+ * Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
- *   Copyright 2007-2009 by Apple Inc.
- *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
- *
- *   These coded instructions, statements, and computer programs are the
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
- *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- *   httpAssembleURI()    - Assemble a uniform resource identifier from its
- *                          components.
- *   httpAssembleURIf()   - Assemble a uniform resource identifier from its
- *                          components with a formatted resource.
- *   httpDecode64()       - Base64-decode a string.
- *   httpDecode64_2()     - Base64-decode a string.
- *   httpEncode64()       - Base64-encode a string.
- *   httpEncode64_2()     - Base64-encode a string.
- *   httpGetDateString()  - Get a formatted date/time string from a time value.
- *   httpGetDateString2() - Get a formatted date/time string from a time value.
- *   httpGetDateTime()    - Get a time value from a formatted date/time string.
- *   httpSeparate()       - Separate a Universal Resource Identifier into its
- *                          components.
- *   httpSeparate2()      - Separate a Universal Resource Identifier into its
- *                          components.
- *   httpSeparateURI()    - Separate a Universal Resource Identifier into its
- *                          components.
- *   httpStatus()         - Return a short string describing a HTTP status code.
- *   _cups_hstrerror()    - hstrerror() emulation function for Solaris and
- *                          others...
- *   _httpEncodeURI()     - Percent-encode a HTTP request URI.
- *   _httpResolveURI()    - Resolve a DNS-SD URI.
- *   http_copy_decode()   - Copy and decode a URI.
- *   http_copy_encode()   - Copy and encode a URI.
- *   resolve_callback()   - Build a device URI for the given service name.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  */
 
 /*
  * Include necessary headers...
  */
 
-#include "debug.h"
-#include "globals.h"
-#include <stdlib.h>
+#include "cups-private.h"
 #ifdef HAVE_DNSSD
 #  include <dns_sd.h>
-#  include <poll.h>
+#  ifdef WIN32
+#    include <io.h>
+#  elif defined(HAVE_POLL)
+#    include <poll.h>
+#  else
+#    include <sys/select.h>
+#  endif /* WIN32 */
+#elif defined(HAVE_AVAHI)
+#  include <avahi-client/client.h>
+#  include <avahi-client/lookup.h>
+#  include <avahi-common/simple-watch.h>
 #endif /* HAVE_DNSSD */
 
 
 
 typedef struct _http_uribuf_s          /* URI buffer */
 {
-  char         *buffer;                /* Pointer to buffer */
-  size_t       bufsize;                /* Size of buffer */
+#ifdef HAVE_AVAHI
+  AvahiSimplePoll      *poll;          /* Poll state */
+#endif /* HAVE_AVAHI */
+  char                 *buffer;        /* Pointer to buffer */
+  size_t               bufsize;        /* Size of buffer */
+  int                  options;        /* Options passed to _httpResolveURI */
+  const char           *resource;      /* Resource from URI */
+  const char           *uuid;          /* UUID from URI */
 } _http_uribuf_t;
 
 
@@ -71,7 +49,7 @@ typedef struct _http_uribuf_s         /* URI buffer */
  * Local globals...
  */
 
-static const char * const http_days[7] =
+static const char * const http_days[7] =/* Days of the week */
                        {
                          "Sun",
                          "Mon",
@@ -82,7 +60,7 @@ static const char * const http_days[7] =
                          "Sat"
                        };
 static const char * const http_months[12] =
-                       {
+                       {               /* Months of the year */
                          "Jan",
                          "Feb",
                          "Mar",
@@ -96,6 +74,26 @@ static const char * const http_months[12] =
                          "Nov",
                          "Dec"
                        };
+static const char * const http_states[] =
+                       {               /* HTTP state strings */
+                         "HTTP_STATE_ERROR",
+                         "HTTP_STATE_WAITING",
+                         "HTTP_STATE_OPTIONS",
+                         "HTTP_STATE_GET",
+                         "HTTP_STATE_GET_SEND",
+                         "HTTP_STATE_HEAD",
+                         "HTTP_STATE_POST",
+                         "HTTP_STATE_POST_RECV",
+                         "HTTP_STATE_POST_SEND",
+                         "HTTP_STATE_PUT",
+                         "HTTP_STATE_PUT_RECV",
+                         "HTTP_STATE_DELETE",
+                         "HTTP_STATE_TRACE",
+                         "HTTP_STATE_CONNECT",
+                         "HTTP_STATE_STATUS",
+                         "HTTP_STATE_UNKNOWN_METHOD",
+                         "HTTP_STATE_UNKNOWN_VERSION"
+                       };
 
 
 /*
@@ -109,17 +107,33 @@ static char               *http_copy_encode(char *dst, const char *src,
                                          char *dstend, const char *reserved,
                                          const char *term, int encode);
 #ifdef HAVE_DNSSD
-static void            resolve_callback(DNSServiceRef sdRef,
-                                        DNSServiceFlags flags,
-                                        uint32_t interfaceIndex,
-                                        DNSServiceErrorType errorCode,
-                                        const char *fullName,
-                                        const char *hostTarget,
-                                        uint16_t port, uint16_t txtLen,
-                                        const unsigned char *txtRecord,
-                                        void *context);
+static void DNSSD_API  http_resolve_cb(DNSServiceRef sdRef,
+                                       DNSServiceFlags flags,
+                                       uint32_t interfaceIndex,
+                                       DNSServiceErrorType errorCode,
+                                       const char *fullName,
+                                       const char *hostTarget,
+                                       uint16_t port, uint16_t txtLen,
+                                       const unsigned char *txtRecord,
+                                       void *context);
 #endif /* HAVE_DNSSD */
 
+#ifdef HAVE_AVAHI
+static void    http_client_cb(AvahiClient *client,
+                              AvahiClientState state, void *simple_poll);
+static int     http_poll_cb(struct pollfd *pollfds, unsigned int num_pollfds,
+                            int timeout, void *context);
+static void    http_resolve_cb(AvahiServiceResolver *resolver,
+                               AvahiIfIndex interface,
+                               AvahiProtocol protocol,
+                               AvahiResolverEvent event,
+                               const char *name, const char *type,
+                               const char *domain, const char *host_name,
+                               const AvahiAddress *address, uint16_t port,
+                               AvahiStringList *txt,
+                               AvahiLookupResultFlags flags, void *context);
+#endif /* HAVE_AVAHI */
+
 
 /*
  * 'httpAssembleURI()' - Assemble a uniform resource identifier from its
@@ -130,7 +144,7 @@ static void         resolve_callback(DNSServiceRef sdRef,
  * place of traditional string functions whenever you need to create a
  * URI string.
  *
- * @since CUPS 1.2/Mac OS X 10.5@
+ * @since CUPS 1.2/macOS 10.5@
  */
 
 http_uri_status_t                      /* O - URI status */
@@ -157,7 +171,7 @@ httpAssembleURI(
     if (uri)
       *uri = '\0';
 
-    return (HTTP_URI_BAD_ARGUMENTS);
+    return (HTTP_URI_STATUS_BAD_ARGUMENTS);
   }
 
  /*
@@ -170,10 +184,10 @@ httpAssembleURI(
   if (!ptr)
     goto assemble_overflow;
 
-  if (!strcmp(scheme, "mailto"))
+  if (!strcmp(scheme, "geo") || !strcmp(scheme, "mailto") || !strcmp(scheme, "tel"))
   {
    /*
-    * mailto: only has :, no //...
+    * geo:, mailto:, and tel: only have :, no //...
     */
 
     if (ptr < end)
@@ -184,7 +198,7 @@ httpAssembleURI(
   else
   {
    /*
-    * Schemes other than mailto: all have //...
+    * Schemes other than geo:, mailto:, and tel: typically have //...
     */
 
     if ((ptr + 2) < end)
@@ -203,13 +217,16 @@ httpAssembleURI(
 
   if (host)
   {
+    const char *hostptr;               /* Pointer into hostname */
+    int                have_ipv6;              /* Do we have an IPv6 address? */
+
     if (username && *username)
     {
      /*
       * Add username@ first...
       */
 
-      ptr = http_copy_encode(ptr, username, end, "/?@", NULL,
+      ptr = http_copy_encode(ptr, username, end, "/?#[]@", NULL,
                              encoding & HTTP_URI_CODING_USERNAME);
 
       if (!ptr)
@@ -230,13 +247,23 @@ httpAssembleURI(
     * too...
     */
 
-    if (host[0] != '[' && strchr(host, ':') && !strstr(host, "._tcp"))
+    for (hostptr = host,
+             have_ipv6 = strchr(host, ':') && !strstr(host, "._tcp");
+         *hostptr && have_ipv6;
+         hostptr ++)
+      if (*hostptr != ':' && !isxdigit(*hostptr & 255))
+      {
+        have_ipv6 = *hostptr == '%';
+        break;
+      }
+
+    if (have_ipv6)
     {
      /*
       * We have a raw IPv6 address...
       */
 
-      if (strchr(host, '%'))
+      if (strchr(host, '%') && !(encoding & HTTP_URI_CODING_RFC6874))
       {
        /*
         * We have a link-local address, add "[v1." prefix...
@@ -255,7 +282,7 @@ httpAssembleURI(
       else
       {
        /*
-        * We have a normal address, add "[" prefix...
+        * We have a normal (or RFC 6874 link-local) address, add "[" prefix...
        */
 
        if (ptr < end)
@@ -271,8 +298,23 @@ httpAssembleURI(
       while (ptr < end && *host)
       {
         if (*host == '%')
-       {
-          *ptr++ = '+';                        /* Convert zone separator */
+        {
+         /*
+          * Convert/encode zone separator
+          */
+
+          if (encoding & HTTP_URI_CODING_RFC6874)
+          {
+            if (ptr >= (end - 2))
+              goto assemble_overflow;
+
+            *ptr++ = '%';
+            *ptr++ = '2';
+            *ptr++ = '5';
+          }
+          else
+           *ptr++ = '+';
+
          host ++;
        }
        else
@@ -290,10 +332,12 @@ httpAssembleURI(
     else
     {
      /*
-      * Otherwise, just copy the host string...
+      * Otherwise, just copy the host string (the extra chars are not in the
+      * "reg-name" ABNF rule; anything <= SP or >= DEL plus % gets automatically
+      * percent-encoded.
       */
 
-      ptr = http_copy_encode(ptr, host, end, ":/?#[]@\\", NULL,
+      ptr = http_copy_encode(ptr, host, end, "\"#/:<>?@[\\]^`{|}", NULL,
                              encoding & HTTP_URI_CODING_HOSTNAME);
 
       if (!ptr)
@@ -306,7 +350,7 @@ httpAssembleURI(
 
     if (port > 0)
     {
-      snprintf(ptr, end - ptr + 1, ":%d", port);
+      snprintf(ptr, (size_t)(end - ptr + 1), ":%d", port);
       ptr += strlen(ptr);
 
       if (ptr >= end)
@@ -356,7 +400,7 @@ httpAssembleURI(
 
   *ptr = '\0';
 
-  return (HTTP_URI_OK);
+  return (HTTP_URI_STATUS_OK);
 
  /*
   * Clear the URI string and return an overflow error; I don't usually
@@ -366,7 +410,7 @@ httpAssembleURI(
   assemble_overflow:
 
   *uri = '\0';
-  return (HTTP_URI_OVERFLOW);
+  return (HTTP_URI_STATUS_OVERFLOW);
 }
 
 
@@ -380,7 +424,7 @@ httpAssembleURI(
  * this function in place of traditional string functions whenever
  * you need to create a URI string.
  *
- * @since CUPS 1.2/Mac OS X 10.5@
+ * @since CUPS 1.2/macOS 10.5@
  */
 
 http_uri_status_t                      /* O - URI status */
@@ -409,7 +453,7 @@ httpAssembleURIf(
     if (uri)
       *uri = '\0';
 
-    return (HTTP_URI_BAD_ARGUMENTS);
+    return (HTTP_URI_STATUS_BAD_ARGUMENTS);
   }
 
  /*
@@ -420,10 +464,10 @@ httpAssembleURIf(
   bytes = vsnprintf(resource, sizeof(resource), resourcef, ap);
   va_end(ap);
 
-  if (bytes >= sizeof(resource))
+  if ((size_t)bytes >= sizeof(resource))
   {
     *uri = '\0';
-    return (HTTP_URI_OVERFLOW);
+    return (HTTP_URI_STATUS_OVERFLOW);
   }
   else
     return (httpAssembleURI(encoding,  uri, urilen, scheme, username, host,
@@ -431,13 +475,66 @@ httpAssembleURIf(
 }
 
 
+/*
+ * 'httpAssembleUUID()' - Assemble a name-based UUID URN conforming to RFC 4122.
+ *
+ * This function creates a unique 128-bit identifying number using the server
+ * name, port number, random data, and optionally an object name and/or object
+ * number.  The result is formatted as a UUID URN as defined in RFC 4122.
+ *
+ * The buffer needs to be at least 46 bytes in size.
+ *
+ * @since CUPS 1.7/macOS 10.9@
+ */
+
+char *                                 /* I - UUID string */
+httpAssembleUUID(const char *server,   /* I - Server name */
+                int        port,       /* I - Port number */
+                const char *name,      /* I - Object name or NULL */
+                int        number,     /* I - Object number or 0 */
+                char       *buffer,    /* I - String buffer */
+                size_t     bufsize)    /* I - Size of buffer */
+{
+  char                 data[1024];     /* Source string for MD5 */
+  unsigned char                md5sum[16];     /* MD5 digest/sum */
+
+
+ /*
+  * Build a version 3 UUID conforming to RFC 4122.
+  *
+  * Start with the MD5 sum of the server, port, object name and
+  * number, and some random data on the end.
+  */
+
+  snprintf(data, sizeof(data), "%s:%d:%s:%d:%04x:%04x", server,
+           port, name ? name : server, number,
+          (unsigned)CUPS_RAND() & 0xffff, (unsigned)CUPS_RAND() & 0xffff);
+
+  cupsHashData("md5", (unsigned char *)data, strlen(data), md5sum, sizeof(md5sum));
+
+ /*
+  * Generate the UUID from the MD5...
+  */
+
+  snprintf(buffer, bufsize,
+           "urn:uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
+          "%02x%02x%02x%02x%02x%02x",
+          md5sum[0], md5sum[1], md5sum[2], md5sum[3], md5sum[4], md5sum[5],
+          (md5sum[6] & 15) | 0x30, md5sum[7], (md5sum[8] & 0x3f) | 0x40,
+          md5sum[9], md5sum[10], md5sum[11], md5sum[12], md5sum[13],
+          md5sum[14], md5sum[15]);
+
+  return (buffer);
+}
+
+
 /*
  * 'httpDecode64()' - Base64-decode a string.
  *
  * This function is deprecated. Use the httpDecode64_2() function instead
  * which provides buffer length arguments.
  *
- * @deprecated@
+ * @deprecated@ @exclude all@
  */
 
 char *                                 /* O - Decoded string */
@@ -460,7 +557,11 @@ httpDecode64(char       *out,              /* I - String to write to */
 /*
  * 'httpDecode64_2()' - Base64-decode a string.
  *
- * @since CUPS 1.1.21/Mac OS X 10.4@
+ * The caller must initialize "outlen" to the maximum size of the decoded
+ * string before calling @code httpDecode64_2@.  On return "outlen" contains the
+ * decoded length of the string.
+ *
+ * @since CUPS 1.1.21/macOS 10.4@
  */
 
 char *                                 /* O  - Decoded string */
@@ -468,10 +569,10 @@ httpDecode64_2(char       *out,           /* I  - String to write to */
               int        *outlen,      /* IO - Size of output string */
                const char *in)         /* I  - String to read from */
 {
-  int  pos,                            /* Bit position */
-       base64;                         /* Value of this character */
-  char *outptr,                        /* Output pointer */
-       *outend;                        /* End of output buffer */
+  int          pos;                    /* Bit position */
+  unsigned     base64;                 /* Value of this character */
+  char         *outptr,                /* Output pointer */
+               *outend;                /* End of output buffer */
 
 
  /*
@@ -500,11 +601,11 @@ httpDecode64_2(char       *out,           /* I  - String to write to */
     */
 
     if (*in >= 'A' && *in <= 'Z')
-      base64 = *in - 'A';
+      base64 = (unsigned)(*in - 'A');
     else if (*in >= 'a' && *in <= 'z')
-      base64 = *in - 'a' + 26;
+      base64 = (unsigned)(*in - 'a' + 26);
     else if (*in >= '0' && *in <= '9')
-      base64 = *in - '0' + 52;
+      base64 = (unsigned)(*in - '0' + 52);
     else if (*in == '+')
       base64 = 62;
     else if (*in == '/')
@@ -522,26 +623,26 @@ httpDecode64_2(char       *out,           /* I  - String to write to */
     {
       case 0 :
           if (outptr < outend)
-            *outptr = base64 << 2;
+            *outptr = (char)(base64 << 2);
          pos ++;
          break;
       case 1 :
           if (outptr < outend)
-            *outptr++ |= (base64 >> 4) & 3;
+            *outptr++ |= (char)((base64 >> 4) & 3);
           if (outptr < outend)
-           *outptr = (base64 << 4) & 255;
+           *outptr = (char)((base64 << 4) & 255);
          pos ++;
          break;
       case 2 :
           if (outptr < outend)
-            *outptr++ |= (base64 >> 2) & 15;
+            *outptr++ |= (char)((base64 >> 2) & 15);
           if (outptr < outend)
-           *outptr = (base64 << 6) & 255;
+           *outptr = (char)((base64 << 6) & 255);
          pos ++;
          break;
       case 3 :
           if (outptr < outend)
-            *outptr++ |= base64;
+            *outptr++ |= (char)base64;
          pos = 0;
          break;
     }
@@ -565,7 +666,7 @@ httpDecode64_2(char       *out,             /* I  - String to write to */
  * This function is deprecated. Use the httpEncode64_2() function instead
  * which provides buffer length arguments.
  *
- * @deprecated@
+ * @deprecated@ @exclude all@
  */
 
 char *                                 /* O - Encoded string */
@@ -579,12 +680,12 @@ httpEncode64(char       *out,             /* I - String to write to */
 /*
  * 'httpEncode64_2()' - Base64-encode a string.
  *
- * @since CUPS 1.1.21/Mac OS X 10.4@
+ * @since CUPS 1.1.21/macOS 10.4@
  */
 
 char *                                 /* O - Encoded string */
 httpEncode64_2(char       *out,                /* I - String to write to */
-              int        outlen,       /* I - Size of output string */
+              int        outlen,       /* I - Maximum size of output string */
                const char *in,         /* I - String to read from */
               int        inlen)        /* I - Size of input string */
 {
@@ -672,11 +773,11 @@ httpEncode64_2(char       *out,           /* I - String to write to */
 /*
  * 'httpGetDateString()' - Get a formatted date/time string from a time value.
  *
- * @deprecated@
+ * @deprecated@ @exclude all@
  */
 
 const char *                           /* O - Date/time string */
-httpGetDateString(time_t t)            /* I - UNIX time */
+httpGetDateString(time_t t)            /* I - Time in seconds */
 {
   _cups_globals_t *cg = _cupsGlobals();        /* Pointer to library globals */
 
@@ -688,11 +789,11 @@ httpGetDateString(time_t t)               /* I - UNIX time */
 /*
  * 'httpGetDateString2()' - Get a formatted date/time string from a time value.
  *
- * @since CUPS 1.2/Mac OS X 10.5@
+ * @since CUPS 1.2/macOS 10.5@
  */
 
 const char *                           /* O - Date/time string */
-httpGetDateString2(time_t t,           /* I - UNIX time */
+httpGetDateString2(time_t t,           /* I - Time in seconds */
                    char   *s,          /* I - String buffer */
                   int    slen)         /* I - Size of string buffer */
 {
@@ -700,10 +801,10 @@ httpGetDateString2(time_t t,              /* I - UNIX time */
 
 
   tdate = gmtime(&t);
-  snprintf(s, slen, "%s, %02d %s %d %02d:%02d:%02d GMT",
-           http_days[tdate->tm_wday], tdate->tm_mday,
-          http_months[tdate->tm_mon], tdate->tm_year + 1900,
-          tdate->tm_hour, tdate->tm_min, tdate->tm_sec);
+  if (tdate)
+    snprintf(s, (size_t)slen, "%s, %02d %s %d %02d:%02d:%02d GMT", http_days[tdate->tm_wday], tdate->tm_mday, http_months[tdate->tm_mon], tdate->tm_year + 1900, tdate->tm_hour, tdate->tm_min, tdate->tm_sec);
+  else
+    s[0] = '\0';
 
   return (s);
 }
@@ -713,7 +814,7 @@ httpGetDateString2(time_t t,                /* I - UNIX time */
  * 'httpGetDateTime()' - Get a time value from a formatted date/time string.
  */
 
-time_t                                 /* O - UNIX time */
+time_t                                 /* O - Time in seconds */
 httpGetDateTime(const char *s)         /* I - Date/time string */
 {
   int          i;                      /* Looping var */
@@ -744,7 +845,7 @@ httpGetDateTime(const char *s)              /* I - Date/time string */
   */
 
   for (i = 0; i < 12; i ++)
-    if (!strcasecmp(mon, http_months[i]))
+    if (!_cups_strcasecmp(mon, http_months[i]))
       break;
 
   if (i >= 12)
@@ -782,7 +883,7 @@ httpGetDateTime(const char *s)              /* I - Date/time string */
  *
  * This function is deprecated; use the httpSeparateURI() function instead.
  *
- * @deprecated@
+ * @deprecated@ @exclude all@
  */
 
 void
@@ -805,8 +906,8 @@ httpSeparate(const char *uri,               /* I - Universal Resource Identifier */
  *
  * This function is deprecated; use the httpSeparateURI() function instead.
  *
- * @since CUPS 1.1.21/Mac OS X 10.4@
- * @deprecated@
+ * @since CUPS 1.1.21/macOS 10.4@
+ * @deprecated@ @exclude all@
  */
 
 void
@@ -830,7 +931,7 @@ httpSeparate2(const char *uri,              /* I - Universal Resource Identifier */
  * 'httpSeparateURI()' - Separate a Universal Resource Identifier into its
  *                       components.
  *
- * @since CUPS 1.2/Mac OS X 10.5@
+ * @since CUPS 1.2/macOS 10.5@
  */
 
 http_uri_status_t                      /* O - Result of separation */
@@ -879,16 +980,16 @@ httpSeparateURI(
   if (!uri || !port || !scheme || schemelen <= 0 || !username ||
       usernamelen <= 0 || !host || hostlen <= 0 || !resource ||
       resourcelen <= 0)
-    return (HTTP_URI_BAD_ARGUMENTS);
+    return (HTTP_URI_STATUS_BAD_ARGUMENTS);
 
   if (!*uri)
-    return (HTTP_URI_BAD_URI);
+    return (HTTP_URI_STATUS_BAD_URI);
 
  /*
   * Grab the scheme portion of the URI...
   */
 
-  status = HTTP_URI_OK;
+  status = HTTP_URI_STATUS_OK;
 
   if (!strncmp(uri, "//", 2))
   {
@@ -896,8 +997,8 @@ httpSeparateURI(
     * Workaround for HP IPP client bug...
     */
 
-    strlcpy(scheme, "ipp", schemelen);
-    status = HTTP_URI_MISSING_SCHEME;
+    strlcpy(scheme, "ipp", (size_t)schemelen);
+    status = HTTP_URI_STATUS_MISSING_SCHEME;
   }
   else if (*uri == '/')
   {
@@ -905,8 +1006,8 @@ httpSeparateURI(
     * Filename...
     */
 
-    strlcpy(scheme, "file", schemelen);
-    status = HTTP_URI_MISSING_SCHEME;
+    strlcpy(scheme, "file", (size_t)schemelen);
+    status = HTTP_URI_STATUS_MISSING_SCHEME;
   }
   else
   {
@@ -916,7 +1017,9 @@ httpSeparateURI(
 
     for (ptr = scheme, end = scheme + schemelen - 1;
          *uri && *uri != ':' && ptr < end;)
-      if (isalnum(*uri & 255) || *uri == '-' || *uri == '+' || *uri == '.')
+      if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                 "abcdefghijklmnopqrstuvwxyz"
+                "0123456789-+.", *uri) != NULL)
         *ptr++ = *uri++;
       else
         break;
@@ -926,7 +1029,7 @@ httpSeparateURI(
     if (*uri != ':')
     {
       *scheme = '\0';
-      return (HTTP_URI_BAD_SCHEME);
+      return (HTTP_URI_STATUS_BAD_SCHEME);
     }
 
     uri ++;
@@ -940,14 +1043,14 @@ httpSeparateURI(
     *port = 80;
   else if (!strcmp(scheme, "https"))
     *port = 443;
-  else if (!strcmp(scheme, "ipp"))
+  else if (!strcmp(scheme, "ipp") || !strcmp(scheme, "ipps"))
     *port = 631;
-  else if (!strcasecmp(scheme, "lpd"))
+  else if (!_cups_strcasecmp(scheme, "lpd"))
     *port = 515;
   else if (!strcmp(scheme, "socket"))  /* Not yet registered with IANA... */
     *port = 9100;
-  else if (strcmp(scheme, "file") && strcmp(scheme, "mailto"))
-    status = HTTP_URI_UNKNOWN_SCHEME;
+  else if (strcmp(scheme, "file") && strcmp(scheme, "mailto") && strcmp(scheme, "tel"))
+    status = HTTP_URI_STATUS_UNKNOWN_SCHEME;
 
  /*
   * Now see if we have a hostname...
@@ -977,7 +1080,7 @@ httpSeparateURI(
       if (!uri)
       {
         *username = '\0';
-        return (HTTP_URI_BAD_USERNAME);
+        return (HTTP_URI_STATUS_BAD_USERNAME);
       }
 
       uri ++;
@@ -994,8 +1097,25 @@ httpSeparateURI(
       */
 
       uri ++;
-      if (!strncmp(uri, "v1.", 3))
-        uri += 3;                      /* Skip IPvN leader... */
+      if (*uri == 'v')
+      {
+       /*
+        * Skip IPvFuture ("vXXXX.") prefix...
+        */
+
+        uri ++;
+
+        while (isxdigit(*uri & 255))
+          uri ++;
+
+        if (*uri != '.')
+        {
+         *host = '\0';
+         return (HTTP_URI_STATUS_BAD_HOSTNAME);
+        }
+
+        uri ++;
+      }
 
       uri = http_copy_decode(host, uri, hostlen, "]",
                              decoding & HTTP_URI_CODING_HOSTNAME);
@@ -1003,7 +1123,7 @@ httpSeparateURI(
       if (!uri)
       {
         *host = '\0';
-        return (HTTP_URI_BAD_HOSTNAME);
+        return (HTTP_URI_STATUS_BAD_HOSTNAME);
       }
 
      /*
@@ -1013,7 +1133,7 @@ httpSeparateURI(
       if (*uri != ']')
       {
         *host = '\0';
-        return (HTTP_URI_BAD_HOSTNAME);
+        return (HTTP_URI_STATUS_BAD_HOSTNAME);
       }
 
       uri ++;
@@ -1028,10 +1148,18 @@ httpSeparateURI(
          *ptr = '%';
          break;
        }
+       else if (*ptr == '%')
+       {
+        /*
+         * Stop at zone separator (RFC 6874)
+         */
+
+         break;
+       }
        else if (*ptr != ':' && *ptr != '.' && !isxdigit(*ptr & 255))
        {
          *host = '\0';
-         return (HTTP_URI_BAD_HOSTNAME);
+         return (HTTP_URI_STATUS_BAD_HOSTNAME);
        }
     }
     else
@@ -1043,15 +1171,16 @@ httpSeparateURI(
       for (ptr = (char *)uri; *ptr; ptr ++)
         if (strchr(":?/", *ptr))
          break;
-        else if (!strchr("abcdefghijklmnopqrstuvwxyz"
-                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                        "0123456789"
-                        "-._~"
-                        "%"
-                        "!$&'()*+,;=\\", *ptr))
+        else if (!strchr("abcdefghijklmnopqrstuvwxyz"  /* unreserved */
+                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"   /* unreserved */
+                        "0123456789"                   /* unreserved */
+                        "-._~"                         /* unreserved */
+                        "%"                            /* pct-encoded */
+                        "!$&'()*+,;="                  /* sub-delims */
+                        "\\", *ptr))                   /* SMB domain */
        {
          *host = '\0';
-         return (HTTP_URI_BAD_HOSTNAME);
+         return (HTTP_URI_STATUS_BAD_HOSTNAME);
        }
 
      /*
@@ -1064,7 +1193,7 @@ httpSeparateURI(
       if (!uri)
       {
         *host = '\0';
-        return (HTTP_URI_BAD_HOSTNAME);
+        return (HTTP_URI_STATUS_BAD_HOSTNAME);
       }
     }
 
@@ -1076,7 +1205,7 @@ httpSeparateURI(
     if (!strcmp(scheme, "file") && strcmp(host, "localhost") && host[0])
     {
       *host = '\0';
-      return (HTTP_URI_BAD_HOSTNAME);
+      return (HTTP_URI_STATUS_BAD_HOSTNAME);
     }
 
    /*
@@ -1092,15 +1221,21 @@ httpSeparateURI(
       if (!isdigit(uri[1] & 255))
       {
         *port = 0;
-        return (HTTP_URI_BAD_PORT);
+        return (HTTP_URI_STATUS_BAD_PORT);
       }
 
-      *port = strtol(uri + 1, (char **)&uri, 10);
+      *port = (int)strtol(uri + 1, (char **)&uri, 10);
+
+      if (*port <= 0 || *port > 65535)
+      {
+        *port = 0;
+        return (HTTP_URI_STATUS_BAD_PORT);
+      }
 
       if (*uri != '/' && *uri)
       {
         *port = 0;
-        return (HTTP_URI_BAD_PORT);
+        return (HTTP_URI_STATUS_BAD_PORT);
       }
     }
   }
@@ -1115,7 +1250,7 @@ httpSeparateURI(
     * Hostname but no path...
     */
 
-    status    = HTTP_URI_MISSING_RESOURCE;
+    status    = HTTP_URI_STATUS_MISSING_RESOURCE;
     *resource = '/';
 
    /*
@@ -1141,15 +1276,16 @@ httpSeparateURI(
 
       char *resptr = resource + strlen(resource);
 
-      uri = http_copy_decode(resptr, uri, resourcelen - (int)(resptr - resource),
-                             NULL, decoding & HTTP_URI_CODING_QUERY);
+      uri = http_copy_decode(resptr, uri,
+                             resourcelen - (int)(resptr - resource), NULL,
+                             decoding & HTTP_URI_CODING_QUERY);
     }
   }
 
   if (!uri)
   {
     *resource = '\0';
-    return (HTTP_URI_BAD_RESOURCE);
+    return (HTTP_URI_STATUS_BAD_RESOURCE);
   }
 
  /*
@@ -1161,101 +1297,206 @@ httpSeparateURI(
 
 
 /*
- * 'httpStatus()' - Return a short string describing a HTTP status code.
+ * 'httpStateString()' - Return the string describing a HTTP state value.
  *
- * The returned string is localized to the current POSIX locale and is based
- * on the status strings defined in RFC 2616.
+ * @since CUPS 2.0/OS 10.10@
+ */
+
+const char *                           /* O - State string */
+httpStateString(http_state_t state)    /* I - HTTP state value */
+{
+  if (state < HTTP_STATE_ERROR || state > HTTP_STATE_UNKNOWN_VERSION)
+    return ("HTTP_STATE_???");
+  else
+    return (http_states[state - HTTP_STATE_ERROR]);
+}
+
+
+/*
+ * '_httpStatus()' - Return the localized string describing a HTTP status code.
+ *
+ * The returned string is localized using the passed message catalog.
  */
 
 const char *                           /* O - Localized status string */
-httpStatus(http_status_t status)       /* I - HTTP status code */
+_httpStatus(cups_lang_t   *lang,       /* I - Language */
+            http_status_t status)      /* I - HTTP status code */
 {
   const char   *s;                     /* Status string */
-  _cups_globals_t *cg = _cupsGlobals();        /* Global data */
 
 
-  if (!cg->lang_default)
-    cg->lang_default = cupsLangDefault();
-
   switch (status)
   {
-    case HTTP_CONTINUE :
+    case HTTP_STATUS_ERROR :
+        s = strerror(errno);
+        break;
+    case HTTP_STATUS_CONTINUE :
         s = _("Continue");
        break;
-    case HTTP_SWITCHING_PROTOCOLS :
+    case HTTP_STATUS_SWITCHING_PROTOCOLS :
         s = _("Switching Protocols");
        break;
-    case HTTP_OK :
+    case HTTP_STATUS_OK :
         s = _("OK");
        break;
-    case HTTP_CREATED :
+    case HTTP_STATUS_CREATED :
         s = _("Created");
        break;
-    case HTTP_ACCEPTED :
+    case HTTP_STATUS_ACCEPTED :
         s = _("Accepted");
        break;
-    case HTTP_NO_CONTENT :
+    case HTTP_STATUS_NO_CONTENT :
         s = _("No Content");
        break;
-    case HTTP_MOVED_PERMANENTLY :
+    case HTTP_STATUS_MOVED_PERMANENTLY :
         s = _("Moved Permanently");
        break;
-    case HTTP_SEE_OTHER :
+    case HTTP_STATUS_SEE_OTHER :
         s = _("See Other");
        break;
-    case HTTP_NOT_MODIFIED :
+    case HTTP_STATUS_NOT_MODIFIED :
         s = _("Not Modified");
        break;
-    case HTTP_BAD_REQUEST :
+    case HTTP_STATUS_BAD_REQUEST :
         s = _("Bad Request");
        break;
-    case HTTP_UNAUTHORIZED :
-    case HTTP_AUTHORIZATION_CANCELED :
+    case HTTP_STATUS_UNAUTHORIZED :
+    case HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED :
         s = _("Unauthorized");
        break;
-    case HTTP_FORBIDDEN :
+    case HTTP_STATUS_FORBIDDEN :
         s = _("Forbidden");
        break;
-    case HTTP_NOT_FOUND :
+    case HTTP_STATUS_NOT_FOUND :
         s = _("Not Found");
        break;
-    case HTTP_REQUEST_TOO_LARGE :
+    case HTTP_STATUS_REQUEST_TOO_LARGE :
         s = _("Request Entity Too Large");
        break;
-    case HTTP_URI_TOO_LONG :
+    case HTTP_STATUS_URI_TOO_LONG :
         s = _("URI Too Long");
        break;
-    case HTTP_UPGRADE_REQUIRED :
+    case HTTP_STATUS_UPGRADE_REQUIRED :
         s = _("Upgrade Required");
        break;
-    case HTTP_NOT_IMPLEMENTED :
+    case HTTP_STATUS_NOT_IMPLEMENTED :
         s = _("Not Implemented");
        break;
-    case HTTP_NOT_SUPPORTED :
+    case HTTP_STATUS_NOT_SUPPORTED :
         s = _("Not Supported");
        break;
-    case HTTP_EXPECTATION_FAILED :
+    case HTTP_STATUS_EXPECTATION_FAILED :
         s = _("Expectation Failed");
        break;
-    case HTTP_SERVICE_UNAVAILABLE :
+    case HTTP_STATUS_SERVICE_UNAVAILABLE :
         s = _("Service Unavailable");
        break;
-    case HTTP_SERVER_ERROR :
+    case HTTP_STATUS_SERVER_ERROR :
         s = _("Internal Server Error");
        break;
+    case HTTP_STATUS_CUPS_PKI_ERROR :
+        s = _("SSL/TLS Negotiation Error");
+       break;
+    case HTTP_STATUS_CUPS_WEBIF_DISABLED :
+        s = _("Web Interface is Disabled");
+       break;
 
     default :
         s = _("Unknown");
        break;
   }
 
+  return (_cupsLangString(lang, s));
+}
+
+
+/*
+ * 'httpStatus()' - Return a short string describing a HTTP status code.
+ *
+ * The returned string is localized to the current POSIX locale and is based
+ * on the status strings defined in RFC 7231.
+ */
+
+const char *                           /* O - Localized status string */
+httpStatus(http_status_t status)       /* I - HTTP status code */
+{
+  _cups_globals_t *cg = _cupsGlobals();        /* Global data */
+
+
+  if (!cg->lang_default)
+    cg->lang_default = cupsLangDefault();
+
+  return (_httpStatus(cg->lang_default, status));
+}
+
+/*
+ * 'httpURIStatusString()' - Return a string describing a URI status code.
+ *
+ * @since CUPS 2.0/OS 10.10@
+ */
+
+const char *                           /* O - Localized status string */
+httpURIStatusString(
+    http_uri_status_t status)          /* I - URI status code */
+{
+  const char   *s;                     /* Status string */
+  _cups_globals_t *cg = _cupsGlobals();        /* Global data */
+
+
+  if (!cg->lang_default)
+    cg->lang_default = cupsLangDefault();
+
+  switch (status)
+  {
+    case HTTP_URI_STATUS_OVERFLOW :
+       s = _("URI too large");
+       break;
+    case HTTP_URI_STATUS_BAD_ARGUMENTS :
+       s = _("Bad arguments to function");
+       break;
+    case HTTP_URI_STATUS_BAD_RESOURCE :
+       s = _("Bad resource in URI");
+       break;
+    case HTTP_URI_STATUS_BAD_PORT :
+       s = _("Bad port number in URI");
+       break;
+    case HTTP_URI_STATUS_BAD_HOSTNAME :
+       s = _("Bad hostname/address in URI");
+       break;
+    case HTTP_URI_STATUS_BAD_USERNAME :
+       s = _("Bad username in URI");
+       break;
+    case HTTP_URI_STATUS_BAD_SCHEME :
+       s = _("Bad scheme in URI");
+       break;
+    case HTTP_URI_STATUS_BAD_URI :
+       s = _("Bad/empty URI");
+       break;
+    case HTTP_URI_STATUS_OK :
+       s = _("OK");
+       break;
+    case HTTP_URI_STATUS_MISSING_SCHEME :
+       s = _("Missing scheme in URI");
+       break;
+    case HTTP_URI_STATUS_UNKNOWN_SCHEME :
+       s = _("Unknown scheme in URI");
+       break;
+    case HTTP_URI_STATUS_MISSING_RESOURCE :
+       s = _("Missing resource in URI");
+       break;
+
+    default:
+        s = _("Unknown");
+       break;
+  }
+
   return (_cupsLangString(cg->lang_default, s));
 }
 
 
 #ifndef HAVE_HSTRERROR
 /*
- * '_cups_hstrerror()' - hstrerror() emulation function for Solaris and others...
+ * '_cups_hstrerror()' - hstrerror() emulation function for Solaris and others.
  */
 
 const char *                           /* O - Error string */
@@ -1279,6 +1520,22 @@ _cups_hstrerror(int error)               /* I - Error number */
 #endif /* !HAVE_HSTRERROR */
 
 
+/*
+ * '_httpDecodeURI()' - Percent-decode a HTTP request URI.
+ */
+
+char *                                 /* O - Decoded URI or NULL on error */
+_httpDecodeURI(char       *dst,                /* I - Destination buffer */
+               const char *src,                /* I - Source URI */
+              size_t     dstsize)      /* I - Size of destination buffer */
+{
+  if (http_copy_decode(dst, src, (int)dstsize, NULL, 1))
+    return (dst);
+  else
+    return (NULL);
+}
+
+
 /*
  * '_httpEncodeURI()' - Percent-encode a HTTP request URI.
  */
@@ -1302,7 +1559,9 @@ _httpResolveURI(
     const char *uri,                   /* I - DNS-SD URI */
     char       *resolved_uri,          /* I - Buffer for resolved URI */
     size_t     resolved_size,          /* I - Size of URI buffer */
-    int        logit)                  /* I - Log progress to stderr? */
+    int        options,                        /* I - Resolve options */
+    int        (*cb)(void *context),   /* I - Continue callback function */
+    void       *context)               /* I - Context pointer for callback */
 {
   char                 scheme[32],     /* URI components... */
                        userpass[256],
@@ -1314,9 +1573,7 @@ _httpResolveURI(
 #endif /* DEBUG */
 
 
-  DEBUG_printf(("4_httpResolveURI(uri=\"%s\", resolved_uri=%p, "
-                "resolved_size=" CUPS_LLFMT ")", uri, resolved_uri,
-               CUPS_LLCAST resolved_size));
+  DEBUG_printf(("_httpResolveURI(uri=\"%s\", resolved_uri=%p, resolved_size=" CUPS_LLFMT ", options=0x%x, cb=%p, context=%p)", uri, (void *)resolved_uri, CUPS_LLCAST resolved_size, options, (void *)cb, context));
 
  /*
   * Get the device URI...
@@ -1326,19 +1583,19 @@ _httpResolveURI(
   if ((status = httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme,
                                 sizeof(scheme), userpass, sizeof(userpass),
                                hostname, sizeof(hostname), &port, resource,
-                               sizeof(resource))) < HTTP_URI_OK)
+                               sizeof(resource))) < HTTP_URI_STATUS_OK)
 #else
   if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme,
                      sizeof(scheme), userpass, sizeof(userpass),
                      hostname, sizeof(hostname), &port, resource,
-                     sizeof(resource)) < HTTP_URI_OK)
+                     sizeof(resource)) < HTTP_URI_STATUS_OK)
 #endif /* DEBUG */
   {
-    if (logit)
-      _cupsLangPrintf(stderr, _("Bad device URI \"%s\"!\n"), uri);
+    if (options & _HTTP_RESOLVE_STDERR)
+      _cupsLangPrintFilter(stderr, "ERROR", _("Bad device-uri \"%s\"."), uri);
 
-    DEBUG_printf(("6_httpResolveURI: httpSeparateURI returned %d!", status));
-    DEBUG_puts("5_httpResolveURI: Returning NULL");
+    DEBUG_printf(("2_httpResolveURI: httpSeparateURI returned %d!", status));
+    DEBUG_puts("2_httpResolveURI: Returning NULL");
     return (NULL);
   }
 
@@ -1348,18 +1605,35 @@ _httpResolveURI(
 
   if (strstr(hostname, "._tcp"))
   {
-#ifdef HAVE_DNSSD
-    DNSServiceRef      ref,            /* DNS-SD master service reference */
-                       domainref,      /* DNS-SD service reference for domain */
-                       localref;       /* DNS-SD service reference for .local */
-    int                        domainsent = 0; /* Send the domain resolve? */
+#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
     char               *regtype,       /* Pointer to type in hostname */
-                       *domain;        /* Pointer to domain in hostname */
+                       *domain,        /* Pointer to domain in hostname */
+                       *uuid,          /* Pointer to UUID in URI */
+                       *uuidend;       /* Pointer to end of UUID in URI */
     _http_uribuf_t     uribuf;         /* URI buffer */
+    int                        offline = 0;    /* offline-report state set? */
+#  ifdef HAVE_DNSSD
+#    ifdef WIN32
+#      pragma comment(lib, "dnssd.lib")
+#    endif /* WIN32 */
+    DNSServiceRef      ref,            /* DNS-SD master service reference */
+                       domainref = NULL,/* DNS-SD service reference for domain */
+                       ippref = NULL,  /* DNS-SD service reference for network IPP */
+                       ippsref = NULL, /* DNS-SD service reference for network IPPS */
+                       localref;       /* DNS-SD service reference for .local */
+    int                        extrasent = 0;  /* Send the domain/IPP/IPPS resolves? */
+#    ifdef HAVE_POLL
     struct pollfd      polldata;       /* Polling data */
-
-
-    if (logit)
+#    else /* select() */
+    fd_set             input_set;      /* Input set for select() */
+    struct timeval     stimeout;       /* Timeout value for select() */
+#    endif /* HAVE_POLL */
+#  elif defined(HAVE_AVAHI)
+    AvahiClient                *client;        /* Client information */
+    int                        error;          /* Status */
+#  endif /* HAVE_DNSSD */
+
+    if (options & _HTTP_RESOLVE_STDERR)
       fprintf(stderr, "DEBUG: Resolving \"%s\"...\n", hostname);
 
    /*
@@ -1381,7 +1655,7 @@ _httpResolveURI(
 
     if (regtype <= hostname)
     {
-      DEBUG_puts("5_httpResolveURI: Bad hostname, returning NULL");
+      DEBUG_puts("2_httpResolveURI: Bad hostname, returning NULL");
       return (NULL);
     }
 
@@ -1394,104 +1668,334 @@ _httpResolveURI(
     if (domain)
       *domain++ = '\0';
 
-    uribuf.buffer  = resolved_uri;
-    uribuf.bufsize = resolved_size;
+    if ((uuid = strstr(resource, "?uuid=")) != NULL)
+    {
+      *uuid = '\0';
+      uuid  += 6;
+      if ((uuidend = strchr(uuid, '&')) != NULL)
+        *uuidend = '\0';
+    }
 
     resolved_uri[0] = '\0';
 
-    DEBUG_printf(("6_httpResolveURI: Resolving hostname=\"%s\", regtype=\"%s\", "
+    uribuf.buffer   = resolved_uri;
+    uribuf.bufsize  = resolved_size;
+    uribuf.options  = options;
+    uribuf.resource = resource;
+    uribuf.uuid     = uuid;
+
+    DEBUG_printf(("2_httpResolveURI: Resolving hostname=\"%s\", regtype=\"%s\", "
                   "domain=\"%s\"\n", hostname, regtype, domain));
-    if (logit)
+    if (options & _HTTP_RESOLVE_STDERR)
     {
       fputs("STATE: +connecting-to-device\n", stderr);
       fprintf(stderr, "DEBUG: Resolving \"%s\", regtype=\"%s\", "
                       "domain=\"local.\"...\n", hostname, regtype);
-      _cupsLangPuts(stderr, _("INFO: Looking for printer...\n"));
     }
 
     uri = NULL;
 
+#  ifdef HAVE_DNSSD
     if (DNSServiceCreateConnection(&ref) == kDNSServiceErr_NoError)
     {
+      uint32_t myinterface = kDNSServiceInterfaceIndexAny;
+                                       /* Lookup on any interface */
+
+      if (!strcmp(scheme, "ippusb"))
+        myinterface = kDNSServiceInterfaceIndexLocalOnly;
+
       localref = ref;
-      if (DNSServiceResolve(&localref, kDNSServiceFlagsShareConnection, 0,
-                           hostname, regtype, "local.", resolve_callback,
+      if (DNSServiceResolve(&localref,
+                            kDNSServiceFlagsShareConnection, myinterface,
+                            hostname, regtype, "local.", http_resolve_cb,
                            &uribuf) == kDNSServiceErr_NoError)
       {
-        if (strcasecmp(domain, "local."))
+       int     fds;                    /* Number of ready descriptors */
+       time_t  timeout,                /* Poll timeout */
+               start_time = time(NULL),/* Start time */
+               end_time = start_time + 90;
+                                       /* End time */
+
+       while (time(NULL) < end_time)
        {
+         if (options & _HTTP_RESOLVE_STDERR)
+           _cupsLangPrintFilter(stderr, "INFO", _("Looking for printer."));
+
+         if (cb && !(*cb)(context))
+         {
+           DEBUG_puts("2_httpResolveURI: callback returned 0 (stop)");
+           break;
+         }
+
         /*
-         * Wait 2 seconds for a response to the local resolve; if nothing comes
-         * in, do an additional domain resolution...
+         * Wakeup every 2 seconds to emit a "looking for printer" message...
          */
 
+         if ((timeout = end_time - time(NULL)) > 2)
+           timeout = 2;
+
+#    ifdef HAVE_POLL
          polldata.fd     = DNSServiceRefSockFD(ref);
          polldata.events = POLLIN;
 
-         if (poll(&polldata, 1, 2000) != 1)
+         fds = poll(&polldata, 1, (int)(1000 * timeout));
+
+#    else /* select() */
+         FD_ZERO(&input_set);
+         FD_SET(DNSServiceRefSockFD(ref), &input_set);
+
+#      ifdef WIN32
+         stimeout.tv_sec  = (long)timeout;
+#      else
+         stimeout.tv_sec  = timeout;
+#      endif /* WIN32 */
+         stimeout.tv_usec = 0;
+
+         fds = select(DNSServiceRefSockFD(ref)+1, &input_set, NULL, NULL,
+                      &stimeout);
+#    endif /* HAVE_POLL */
+
+         if (fds < 0)
+         {
+           if (errno != EINTR && errno != EAGAIN)
+           {
+             DEBUG_printf(("2_httpResolveURI: poll error: %s", strerror(errno)));
+             break;
+           }
+         }
+         else if (fds == 0)
          {
           /*
-           * OK, send the domain name resolve...
+           * Wait 2 seconds for a response to the local resolve; if nothing
+           * comes in, do an additional domain resolution...
            */
 
-           if (logit)
-             fprintf(stderr, "DEBUG: Resolving \"%s\", regtype=\"%s\", "
-                             "domain=\"%s\"...\n", hostname, regtype, domain);
+           if (extrasent == 0 && domain && _cups_strcasecmp(domain, "local."))
+           {
+             if (options & _HTTP_RESOLVE_STDERR)
+               fprintf(stderr,
+                       "DEBUG: Resolving \"%s\", regtype=\"%s\", "
+                       "domain=\"%s\"...\n", hostname, regtype,
+                       domain ? domain : "");
+
+             domainref = ref;
+             if (DNSServiceResolve(&domainref,
+                                   kDNSServiceFlagsShareConnection,
+                                   myinterface, hostname, regtype, domain,
+                                   http_resolve_cb,
+                                   &uribuf) == kDNSServiceErr_NoError)
+               extrasent = 1;
+           }
+           else if (extrasent == 0 && !strcmp(scheme, "ippusb"))
+           {
+             if (options & _HTTP_RESOLVE_STDERR)
+               fprintf(stderr, "DEBUG: Resolving \"%s\", regtype=\"_ipps._tcp\", domain=\"local.\"...\n", hostname);
+
+             ippsref = ref;
+             if (DNSServiceResolve(&ippsref,
+                                   kDNSServiceFlagsShareConnection,
+                                   kDNSServiceInterfaceIndexAny, hostname,
+                                   "_ipps._tcp", domain, http_resolve_cb,
+                                   &uribuf) == kDNSServiceErr_NoError)
+               extrasent = 1;
+           }
+           else if (extrasent == 1 && !strcmp(scheme, "ippusb"))
+           {
+             if (options & _HTTP_RESOLVE_STDERR)
+               fprintf(stderr, "DEBUG: Resolving \"%s\", regtype=\"_ipp._tcp\", domain=\"local.\"...\n", hostname);
+
+             ippref = ref;
+             if (DNSServiceResolve(&ippref,
+                                   kDNSServiceFlagsShareConnection,
+                                   kDNSServiceInterfaceIndexAny, hostname,
+                                   "_ipp._tcp", domain, http_resolve_cb,
+                                   &uribuf) == kDNSServiceErr_NoError)
+               extrasent = 2;
+           }
 
-           domainref = ref;
-           if (DNSServiceResolve(&domainref, kDNSServiceFlagsShareConnection, 0,
-                                 hostname, regtype, domain, resolve_callback,
-                                 &uribuf) == kDNSServiceErr_NoError)
-             domainsent = 1;
-         }
-        }
+          /*
+           * If it hasn't resolved within 5 seconds set the offline-report
+           * printer-state-reason...
+           */
 
-       if (DNSServiceProcessResult(ref) == kDNSServiceErr_NoError)
-         uri = resolved_uri;
+           if ((options & _HTTP_RESOLVE_STDERR) && offline == 0 &&
+               time(NULL) > (start_time + 5))
+           {
+             fputs("STATE: +offline-report\n", stderr);
+             offline = 1;
+           }
+         }
+         else
+         {
+           if (DNSServiceProcessResult(ref) == kDNSServiceErr_NoError &&
+               resolved_uri[0])
+           {
+             uri = resolved_uri;
+             break;
+           }
+         }
+       }
 
-       if (domainsent)
-         DNSServiceRefDeallocate(domainref);
+       if (extrasent)
+       {
+         if (domainref)
+           DNSServiceRefDeallocate(domainref);
+         if (ippref)
+           DNSServiceRefDeallocate(ippref);
+         if (ippsref)
+           DNSServiceRefDeallocate(ippsref);
+       }
 
        DNSServiceRefDeallocate(localref);
       }
 
       DNSServiceRefDeallocate(ref);
     }
+#  else /* HAVE_AVAHI */
+    if ((uribuf.poll = avahi_simple_poll_new()) != NULL)
+    {
+      avahi_simple_poll_set_func(uribuf.poll, http_poll_cb, NULL);
+
+      if ((client = avahi_client_new(avahi_simple_poll_get(uribuf.poll),
+                                     0, http_client_cb,
+                                     &uribuf, &error)) != NULL)
+      {
+       if (avahi_service_resolver_new(client, AVAHI_IF_UNSPEC,
+                                      AVAHI_PROTO_UNSPEC, hostname,
+                                      regtype, "local.", AVAHI_PROTO_UNSPEC, 0,
+                                      http_resolve_cb, &uribuf) != NULL)
+       {
+         time_t        start_time = time(NULL),
+                                       /* Start time */
+                       end_time = start_time + 90;
+                                       /* End time */
+          int           pstatus;       /* Poll status */
+
+         pstatus = avahi_simple_poll_iterate(uribuf.poll, 2000);
+
+         if (pstatus == 0 && !resolved_uri[0] && domain &&
+             _cups_strcasecmp(domain, "local."))
+         {
+          /*
+           * Resolve for .local hasn't returned anything, try the listed
+           * domain...
+           */
+
+           avahi_service_resolver_new(client, AVAHI_IF_UNSPEC,
+                                      AVAHI_PROTO_UNSPEC, hostname,
+                                      regtype, domain, AVAHI_PROTO_UNSPEC, 0,
+                                      http_resolve_cb, &uribuf);
+          }
+
+         while (!pstatus && !resolved_uri[0] && time(NULL) < end_time)
+          {
+           if ((pstatus = avahi_simple_poll_iterate(uribuf.poll, 2000)) != 0)
+             break;
+
+          /*
+           * If it hasn't resolved within 5 seconds set the offline-report
+           * printer-state-reason...
+           */
+
+           if ((options & _HTTP_RESOLVE_STDERR) && offline == 0 &&
+               time(NULL) > (start_time + 5))
+           {
+             fputs("STATE: +offline-report\n", stderr);
+             offline = 1;
+           }
+          }
+
+        /*
+         * Collect the result (if we got one).
+         */
+
+         if (resolved_uri[0])
+           uri = resolved_uri;
+       }
+
+       avahi_client_free(client);
+      }
+
+      avahi_simple_poll_free(uribuf.poll);
+    }
+#  endif /* HAVE_DNSSD */
 
-    if (logit)
+    if (options & _HTTP_RESOLVE_STDERR)
     {
       if (uri)
+      {
         fprintf(stderr, "DEBUG: Resolved as \"%s\"...\n", uri);
+       fputs("STATE: -connecting-to-device,offline-report\n", stderr);
+      }
       else
-        fputs("DEBUG: Unable to resolve URI!\n", stderr);
-
-      fputs("STATE: -connecting-to-device\n", stderr);
+      {
+        fputs("DEBUG: Unable to resolve URI\n", stderr);
+       fputs("STATE: -connecting-to-device\n", stderr);
+      }
     }
 
-#else
+#else /* HAVE_DNSSD || HAVE_AVAHI */
    /*
     * No DNS-SD support...
     */
 
     uri = NULL;
-#endif /* HAVE_DNSSD */
+#endif /* HAVE_DNSSD || HAVE_AVAHI */
 
-    if (logit && !uri)
-      _cupsLangPuts(stderr, _("Unable to find printer!\n"));
+    if ((options & _HTTP_RESOLVE_STDERR) && !uri)
+      _cupsLangPrintFilter(stderr, "INFO", _("Unable to find printer."));
+  }
+  else
+  {
+   /*
+    * Nothing more to do...
+    */
+
+    strlcpy(resolved_uri, uri, resolved_size);
+    uri = resolved_uri;
   }
 
-  DEBUG_printf(("5_httpResolveURI: Returning \"%s\"", uri));
+  DEBUG_printf(("2_httpResolveURI: Returning \"%s\"", uri));
 
   return (uri);
 }
 
 
+#ifdef HAVE_AVAHI
+/*
+ * 'http_client_cb()' - Client callback for resolving URI.
+ */
+
+static void
+http_client_cb(
+    AvahiClient      *client,          /* I - Client information */
+    AvahiClientState state,            /* I - Current state */
+    void             *context)         /* I - Pointer to URI buffer */
+{
+  DEBUG_printf(("7http_client_cb(client=%p, state=%d, context=%p)", client,
+                state, context));
+
+ /*
+  * If the connection drops, quit.
+  */
+
+  if (state == AVAHI_CLIENT_FAILURE)
+  {
+    _http_uribuf_t *uribuf = (_http_uribuf_t *)context;
+                                       /* URI buffer */
+
+    avahi_simple_poll_quit(uribuf->poll);
+  }
+}
+#endif /* HAVE_AVAHI */
+
+
 /*
  * 'http_copy_decode()' - Copy and decode a URI.
  */
 
 static const char *                    /* O - New source pointer or NULL on error */
-http_copy_decode(char       *dst,      /* O - Destination buffer */ 
+http_copy_decode(char       *dst,      /* O - Destination buffer */
                  const char *src,      /* I - Source pointer */
                 int        dstsize,    /* I - Destination size */
                 const char *term,      /* I - Terminating characters */
@@ -1532,7 +2036,7 @@ http_copy_decode(char       *dst, /* O - Destination buffer */
          else
            quoted |= *src - '0';
 
-          *ptr++ = quoted;
+          *ptr++ = (char)quoted;
        }
        else
        {
@@ -1544,6 +2048,11 @@ http_copy_decode(char       *dst,        /* O - Destination buffer */
          return (NULL);
        }
       }
+      else if ((*src & 255) <= 0x20 || (*src & 255) >= 0x7f)
+      {
+        *ptr = '\0';
+        return (NULL);
+      }
       else
        *ptr++ = *src;
     }
@@ -1559,7 +2068,7 @@ http_copy_decode(char       *dst, /* O - Destination buffer */
  */
 
 static char *                          /* O - End of current URI */
-http_copy_encode(char       *dst,      /* O - Destination buffer */ 
+http_copy_encode(char       *dst,      /* O - Destination buffer */
                  const char *src,      /* I - Source pointer */
                 char       *dstend,    /* I - End of destination buffer */
                  const char *reserved, /* I - Extra reserved characters */
@@ -1605,11 +2114,11 @@ http_copy_encode(char       *dst,       /* O - Destination buffer */
 
 #ifdef HAVE_DNSSD
 /*
- * 'resolve_callback()' - Build a device URI for the given service name.
+ * 'http_resolve_cb()' - Build a device URI for the given service name.
  */
 
-static void
-resolve_callback(
+static void DNSSD_API
+http_resolve_cb(
     DNSServiceRef       sdRef,         /* I - Service reference */
     DNSServiceFlags     flags,         /* I - Results flags */
     uint32_t            interfaceIndex,        /* I - Interface number */
@@ -1621,25 +2130,57 @@ resolve_callback(
     const unsigned char *txtRecord,    /* I - TXT record data */
     void                *context)      /* I - Pointer to URI buffer */
 {
-  const char           *scheme;        /* URI scheme */
-  char                 rp[257];        /* Remote printer */
+  _http_uribuf_t       *uribuf = (_http_uribuf_t *)context;
+                                       /* URI buffer */
+  const char           *scheme,        /* URI scheme */
+                       *hostptr,       /* Pointer into hostTarget */
+                       *reskey,        /* "rp" or "rfo" */
+                       *resdefault;    /* Default path */
+  char                 resource[257],  /* Remote path */
+                       fqdn[256];      /* FQDN of the .local name */
   const void           *value;         /* Value from TXT record */
   uint8_t              valueLen;       /* Length of value */
-  _http_uribuf_t       *uribuf;        /* URI buffer */
 
 
-  DEBUG_printf(("7resolve_callback(sdRef=%p, flags=%x, interfaceIndex=%u, "
-               "errorCode=%d, fullName=\"%s\", hostTarget=\"%s\", port=%u, "
-               "txtLen=%u, txtRecord=%p, context=%p)", sdRef, flags,
-               interfaceIndex, errorCode, fullName, hostTarget, port, txtLen,
-               txtRecord, context));
+  DEBUG_printf(("4http_resolve_cb(sdRef=%p, flags=%x, interfaceIndex=%u, errorCode=%d, fullName=\"%s\", hostTarget=\"%s\", port=%u, txtLen=%u, txtRecord=%p, context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, fullName, hostTarget, port, txtLen, (void *)txtRecord, context));
+
+ /*
+  * If we have a UUID, compare it...
+  */
+
+  if (uribuf->uuid &&
+      (value = TXTRecordGetValuePtr(txtLen, txtRecord, "UUID",
+                                    &valueLen)) != NULL)
+  {
+    char       uuid[256];              /* UUID value */
+
+    memcpy(uuid, value, valueLen);
+    uuid[valueLen] = '\0';
+
+    if (_cups_strcasecmp(uuid, uribuf->uuid))
+    {
+      if (uribuf->options & _HTTP_RESOLVE_STDERR)
+       fprintf(stderr, "DEBUG: Found UUID %s, looking for %s.", uuid,
+               uribuf->uuid);
+
+      DEBUG_printf(("5http_resolve_cb: Found UUID %s, looking for %s.", uuid,
+                    uribuf->uuid));
+      return;
+    }
+  }
 
  /*
   * Figure out the scheme from the full name...
   */
 
-  if (strstr(fullName, "._ipp") || strstr(fullName, "._fax-ipp"))
+  if (strstr(fullName, "._ipps") || strstr(fullName, "._ipp-tls"))
+    scheme = "ipps";
+  else if (strstr(fullName, "._ipp") || strstr(fullName, "._fax-ipp"))
     scheme = "ipp";
+  else if (strstr(fullName, "._http."))
+    scheme = "http";
+  else if (strstr(fullName, "._https."))
+    scheme = "https";
   else if (strstr(fullName, "._printer."))
     scheme = "lpd";
   else if (strstr(fullName, "._pdl-datastream."))
@@ -1651,35 +2192,347 @@ resolve_callback(
   * Extract the "remote printer" key from the TXT record...
   */
 
-  if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "rp",
+  if ((uribuf->options & _HTTP_RESOLVE_FAXOUT) &&
+      (!strcmp(scheme, "ipp") || !strcmp(scheme, "ipps")) &&
+      !TXTRecordGetValuePtr(txtLen, txtRecord, "printer-type", &valueLen))
+  {
+    reskey     = "rfo";
+    resdefault = "/ipp/faxout";
+  }
+  else
+  {
+    reskey     = "rp";
+    resdefault = "/";
+  }
+
+  if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, reskey,
                                     &valueLen)) != NULL)
+  {
+    if (((char *)value)[0] == '/')
+    {
+     /*
+      * Value (incorrectly) has a leading slash already...
+      */
+
+      memcpy(resource, value, valueLen);
+      resource[valueLen] = '\0';
+    }
+    else
+    {
+     /*
+      * Convert to resource by concatenating with a leading "/"...
+      */
+
+      resource[0] = '/';
+      memcpy(resource + 1, value, valueLen);
+      resource[valueLen + 1] = '\0';
+    }
+  }
+  else
   {
    /*
-    * Convert to resource by concatenating with a leading "/"...
+    * Use the default value...
     */
 
-    rp[0] = '/';
-    memcpy(rp + 1, value, valueLen);
-    rp[valueLen + 1] = '\0';
+    strlcpy(resource, resdefault, sizeof(resource));
+  }
+
+ /*
+  * Lookup the FQDN if needed...
+  */
+
+  if ((uribuf->options & _HTTP_RESOLVE_FQDN) &&
+      (hostptr = hostTarget + strlen(hostTarget) - 7) > hostTarget &&
+      !_cups_strcasecmp(hostptr, ".local."))
+  {
+   /*
+    * OK, we got a .local name but the caller needs a real domain.  Start by
+    * getting the IP address of the .local name and then do reverse-lookups...
+    */
+
+    http_addrlist_t    *addrlist,      /* List of addresses */
+                       *addr;          /* Current address */
+
+    DEBUG_printf(("5http_resolve_cb: Looking up \"%s\".", hostTarget));
+
+    snprintf(fqdn, sizeof(fqdn), "%d", ntohs(port));
+    if ((addrlist = httpAddrGetList(hostTarget, AF_UNSPEC, fqdn)) != NULL)
+    {
+      for (addr = addrlist; addr; addr = addr->next)
+      {
+        int error = getnameinfo(&(addr->addr.addr), (socklen_t)httpAddrLength(&(addr->addr)), fqdn, sizeof(fqdn), NULL, 0, NI_NAMEREQD);
+
+        if (!error)
+       {
+         DEBUG_printf(("5http_resolve_cb: Found \"%s\".", fqdn));
+
+         if ((hostptr = fqdn + strlen(fqdn) - 6) <= fqdn ||
+             _cups_strcasecmp(hostptr, ".local"))
+         {
+           hostTarget = fqdn;
+           break;
+         }
+       }
+#ifdef DEBUG
+       else
+         DEBUG_printf(("5http_resolve_cb: \"%s\" did not resolve: %d",
+                       httpAddrString(&(addr->addr), fqdn, sizeof(fqdn)),
+                       error));
+#endif /* DEBUG */
+      }
+
+      httpAddrFreeList(addrlist);
+    }
   }
-  else
-    rp[0] = '\0';
 
  /*
   * Assemble the final device URI...
   */
 
-  uribuf = (_http_uribuf_t *)context;
+  if ((!strcmp(scheme, "ipp") || !strcmp(scheme, "ipps")) &&
+      !strcmp(uribuf->resource, "/cups"))
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uribuf->buffer, (int)uribuf->bufsize, scheme, NULL, hostTarget, ntohs(port), "%s?snmp=false", resource);
+  else
+    httpAssembleURI(HTTP_URI_CODING_ALL, uribuf->buffer, (int)uribuf->bufsize, scheme, NULL, hostTarget, ntohs(port), resource);
+
+  DEBUG_printf(("5http_resolve_cb: Resolved URI is \"%s\"...", uribuf->buffer));
+}
+
+#elif defined(HAVE_AVAHI)
+/*
+ * 'http_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)
+ *
+ * @private@
+ */
 
-  httpAssembleURI(HTTP_URI_CODING_ALL, uribuf->buffer, uribuf->bufsize, scheme,
-                  NULL, hostTarget, ntohs(port), rp);
+static int                             /* O - Number of file descriptors matching */
+http_poll_cb(
+    struct pollfd *pollfds,            /* I - File descriptors */
+    unsigned int  num_pollfds,         /* I - Number of file descriptors */
+    int           timeout,             /* I - Timeout in milliseconds (used) */
+    void          *context)            /* I - User data (unused) */
+{
+  (void)timeout;
+  (void)context;
 
-  DEBUG_printf(("8resolve_callback: Resolved URI is \"%s\"...",
-                uribuf->buffer));
+  return (poll(pollfds, num_pollfds, 2000));
 }
-#endif /* HAVE_DNSSD */
 
 
 /*
- * End of "$Id: http-support.c 7952 2008-09-17 00:56:20Z mike $".
+ * 'http_resolve_cb()' - Build a device URI for the given service name.
  */
+
+static void
+http_resolve_cb(
+    AvahiServiceResolver   *resolver,  /* I - Resolver (unused) */
+    AvahiIfIndex           interface,  /* I - Interface index (unused) */
+    AvahiProtocol          protocol,   /* I - Network protocol (unused) */
+    AvahiResolverEvent     event,      /* I - Event (found, etc.) */
+    const char             *name,      /* I - Service name */
+    const char             *type,      /* I - Registration type */
+    const char             *domain,    /* I - Domain (unused) */
+    const char             *hostTarget,        /* I - Hostname */
+    const AvahiAddress     *address,   /* I - Address (unused) */
+    uint16_t               port,       /* I - Port number */
+    AvahiStringList        *txt,       /* I - TXT record */
+    AvahiLookupResultFlags flags,      /* I - Lookup flags (unused) */
+    void                   *context)   /* I - Pointer to URI buffer */
+{
+  _http_uribuf_t       *uribuf = (_http_uribuf_t *)context;
+                                       /* URI buffer */
+  const char           *scheme,        /* URI scheme */
+                       *hostptr,       /* Pointer into hostTarget */
+                       *reskey,        /* "rp" or "rfo" */
+                       *resdefault;    /* Default path */
+  char                 resource[257],  /* Remote path */
+                       fqdn[256];      /* FQDN of the .local name */
+  AvahiStringList      *pair;          /* Current TXT record key/value pair */
+  char                 *value;         /* Value for "rp" key */
+  size_t               valueLen = 0;   /* Length of "rp" key */
+
+
+  DEBUG_printf(("4http_resolve_cb(resolver=%p, "
+               "interface=%d, protocol=%d, event=%d, name=\"%s\", "
+               "type=\"%s\", domain=\"%s\", hostTarget=\"%s\", address=%p, "
+               "port=%d, txt=%p, flags=%d, context=%p)",
+               resolver, interface, protocol, event, name, type, domain,
+               hostTarget, address, port, txt, flags, context));
+
+  if (event != AVAHI_RESOLVER_FOUND)
+  {
+    avahi_service_resolver_free(resolver);
+    avahi_simple_poll_quit(uribuf->poll);
+    return;
+  }
+
+ /*
+  * If we have a UUID, compare it...
+  */
+
+  if (uribuf->uuid && (pair = avahi_string_list_find(txt, "UUID")) != NULL)
+  {
+    char       uuid[256];              /* UUID value */
+
+    avahi_string_list_get_pair(pair, NULL, &value, &valueLen);
+
+    memcpy(uuid, value, valueLen);
+    uuid[valueLen] = '\0';
+
+    if (_cups_strcasecmp(uuid, uribuf->uuid))
+    {
+      if (uribuf->options & _HTTP_RESOLVE_STDERR)
+       fprintf(stderr, "DEBUG: Found UUID %s, looking for %s.", uuid,
+               uribuf->uuid);
+
+      DEBUG_printf(("5http_resolve_cb: Found UUID %s, looking for %s.", uuid,
+                    uribuf->uuid));
+      return;
+    }
+  }
+
+ /*
+  * Figure out the scheme from the full name...
+  */
+
+  if (strstr(type, "_ipp."))
+    scheme = "ipp";
+  else if (strstr(type, "_printer."))
+    scheme = "lpd";
+  else if (strstr(type, "_pdl-datastream."))
+    scheme = "socket";
+  else
+    scheme = "riousbprint";
+
+  if (!strncmp(type, "_ipps.", 6) || !strncmp(type, "_ipp-tls.", 9))
+    scheme = "ipps";
+  else if (!strncmp(type, "_ipp.", 5) || !strncmp(type, "_fax-ipp.", 9))
+    scheme = "ipp";
+  else if (!strncmp(type, "_http.", 6))
+    scheme = "http";
+  else if (!strncmp(type, "_https.", 7))
+    scheme = "https";
+  else if (!strncmp(type, "_printer.", 9))
+    scheme = "lpd";
+  else if (!strncmp(type, "_pdl-datastream.", 16))
+    scheme = "socket";
+  else
+  {
+    avahi_service_resolver_free(resolver);
+    avahi_simple_poll_quit(uribuf->poll);
+    return;
+  }
+
+ /*
+  * Extract the remote resource key from the TXT record...
+  */
+
+  if ((uribuf->options & _HTTP_RESOLVE_FAXOUT) &&
+      (!strcmp(scheme, "ipp") || !strcmp(scheme, "ipps")) &&
+      !avahi_string_list_find(txt, "printer-type"))
+  {
+    reskey     = "rfo";
+    resdefault = "/ipp/faxout";
+  }
+  else
+  {
+    reskey     = "rp";
+    resdefault = "/";
+  }
+
+  if ((pair = avahi_string_list_find(txt, reskey)) != NULL)
+  {
+    avahi_string_list_get_pair(pair, NULL, &value, &valueLen);
+
+    if (value[0] == '/')
+    {
+     /*
+      * Value (incorrectly) has a leading slash already...
+      */
+
+      memcpy(resource, value, valueLen);
+      resource[valueLen] = '\0';
+    }
+    else
+    {
+     /*
+      * Convert to resource by concatenating with a leading "/"...
+      */
+
+      resource[0] = '/';
+      memcpy(resource + 1, value, valueLen);
+      resource[valueLen + 1] = '\0';
+    }
+  }
+  else
+  {
+   /*
+    * Use the default value...
+    */
+
+    strlcpy(resource, resdefault, sizeof(resource));
+  }
+
+ /*
+  * Lookup the FQDN if needed...
+  */
+
+  if ((uribuf->options & _HTTP_RESOLVE_FQDN) &&
+      (hostptr = hostTarget + strlen(hostTarget) - 6) > hostTarget &&
+      !_cups_strcasecmp(hostptr, ".local"))
+  {
+   /*
+    * OK, we got a .local name but the caller needs a real domain.  Start by
+    * getting the IP address of the .local name and then do reverse-lookups...
+    */
+
+    http_addrlist_t    *addrlist,      /* List of addresses */
+                       *addr;          /* Current address */
+
+    DEBUG_printf(("5http_resolve_cb: Looking up \"%s\".", hostTarget));
+
+    snprintf(fqdn, sizeof(fqdn), "%d", ntohs(port));
+    if ((addrlist = httpAddrGetList(hostTarget, AF_UNSPEC, fqdn)) != NULL)
+    {
+      for (addr = addrlist; addr; addr = addr->next)
+      {
+        int error = getnameinfo(&(addr->addr.addr), (socklen_t)httpAddrLength(&(addr->addr)), fqdn, sizeof(fqdn), NULL, 0, NI_NAMEREQD);
+
+        if (!error)
+       {
+         DEBUG_printf(("5http_resolve_cb: Found \"%s\".", fqdn));
+
+         if ((hostptr = fqdn + strlen(fqdn) - 6) <= fqdn ||
+             _cups_strcasecmp(hostptr, ".local"))
+         {
+           hostTarget = fqdn;
+           break;
+         }
+       }
+#ifdef DEBUG
+       else
+         DEBUG_printf(("5http_resolve_cb: \"%s\" did not resolve: %d",
+                       httpAddrString(&(addr->addr), fqdn, sizeof(fqdn)),
+                       error));
+#endif /* DEBUG */
+      }
+
+      httpAddrFreeList(addrlist);
+    }
+  }
+
+ /*
+  * Assemble the final device URI using the resolved hostname...
+  */
+
+  httpAssembleURI(HTTP_URI_CODING_ALL, uribuf->buffer, (int)uribuf->bufsize, scheme,
+                  NULL, hostTarget, port, resource);
+  DEBUG_printf(("5http_resolve_cb: Resolved URI is \"%s\".", uribuf->buffer));
+
+  avahi_simple_poll_quit(uribuf->poll);
+}
+#endif /* HAVE_DNSSD */