ippValidateAttribute(
    ipp_attribute_t *attr)		/* I - Attribute */
{
  int		i;			/* Looping var */
  char		scheme[64],		/* Scheme from URI */
		userpass[256],		/* Username/password from URI */
		hostname[256],		/* Hostname from URI */
		resource[1024];		/* Resource from URI */
  int		port,			/* Port number from URI */
		uri_status;		/* URI separation status */
  const char	*ptr;			/* Pointer into string */
  ipp_attribute_t *colattr;		/* Collection attribute */
  regex_t	re;			/* Regular expression */
  ipp_uchar_t	*date;			/* Current date value */


 /*
  * Skip separators.
  */

  if (!attr->name)
    return (1);

 /*
  * Validate the attribute name.
  */

  for (ptr = attr->name; *ptr; ptr ++)
    if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' && *ptr != '_')
      break;

  if (*ptr || ptr == attr->name)
  {
    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
                  _("\"%s\": Bad attribute name - invalid character "
		    "(RFC 8011 section 5.1.4)."), attr->name);
    return (0);
  }

  if ((ptr - attr->name) > 255)
  {
    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
                  _("\"%s\": Bad attribute name - bad length %d "
		    "(RFC 8011 section 5.1.4)."), attr->name,
		  (int)(ptr - attr->name));
    return (0);
  }

  switch (attr->value_tag)
  {
    case IPP_TAG_INTEGER :
        break;

    case IPP_TAG_BOOLEAN :
        for (i = 0; i < attr->num_values; i ++)
	{
	  if (attr->values[i].boolean != 0 &&
	      attr->values[i].boolean != 1)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
                          _("\"%s\": Bad boolen value %d "
			    "(RFC 8011 section 5.1.21)."), attr->name,
			  attr->values[i].boolean);
	    return (0);
	  }
	}
        break;

    case IPP_TAG_ENUM :
        for (i = 0; i < attr->num_values; i ++)
	{
	  if (attr->values[i].integer < 1)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad enum value %d - out of range "
			    "(RFC 8011 section 5.1.5)."), attr->name,
			    attr->values[i].integer);
            return (0);
	  }
	}
        break;

    case IPP_TAG_STRING :
        for (i = 0; i < attr->num_values; i ++)
	{
	  if (attr->values[i].unknown.length > IPP_MAX_OCTETSTRING)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad octetString value - bad length %d "
			    "(RFC 8011 section 5.1.20)."), attr->name,
			    attr->values[i].unknown.length);
	    return (0);
	  }
	}
        break;

    case IPP_TAG_DATE :
        for (i = 0; i < attr->num_values; i ++)
	{
	  date = attr->values[i].date;

          if (date[2] < 1 || date[2] > 12)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime month %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[2]);
	    return (0);
	  }

          if (date[3] < 1 || date[3] > 31)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime day %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[3]);
	    return (0);
	  }

          if (date[4] > 23)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime hours %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[4]);
	    return (0);
	  }

          if (date[5] > 59)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime minutes %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[5]);
	    return (0);
	  }

          if (date[6] > 60)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime seconds %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[6]);
	    return (0);
	  }

          if (date[7] > 9)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime deciseconds %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[7]);
	    return (0);
	  }

          if (date[8] != '-' && date[8] != '+')
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime UTC sign '%c' "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[8]);
	    return (0);
	  }

          if (date[9] > 11)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime UTC hours %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[9]);
	    return (0);
	  }

          if (date[10] > 59)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad dateTime UTC minutes %u "
			    "(RFC 8011 section 5.1.15)."), attr->name, date[10]);
	    return (0);
	  }
	}
        break;

    case IPP_TAG_RESOLUTION :
        for (i = 0; i < attr->num_values; i ++)
	{
	  if (attr->values[i].resolution.xres <= 0)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad resolution value %dx%d%s - cross "
			    "feed resolution must be positive "
			    "(RFC 8011 section 5.1.16)."), attr->name,
			  attr->values[i].resolution.xres,
			  attr->values[i].resolution.yres,
			  attr->values[i].resolution.units ==
			      IPP_RES_PER_INCH ? "dpi" :
			      attr->values[i].resolution.units ==
				  IPP_RES_PER_CM ? "dpcm" : "unknown");
	    return (0);
	  }

	  if (attr->values[i].resolution.yres <= 0)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad resolution value %dx%d%s - feed "
			    "resolution must be positive "
			    "(RFC 8011 section 5.1.16)."), attr->name,
			  attr->values[i].resolution.xres,
			  attr->values[i].resolution.yres,
			  attr->values[i].resolution.units ==
			      IPP_RES_PER_INCH ? "dpi" :
			      attr->values[i].resolution.units ==
				  IPP_RES_PER_CM ? "dpcm" : "unknown");
            return (0);
	  }

	  if (attr->values[i].resolution.units != IPP_RES_PER_INCH &&
	      attr->values[i].resolution.units != IPP_RES_PER_CM)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad resolution value %dx%d%s - bad "
			    "units value (RFC 8011 section 5.1.16)."),
			  attr->name, attr->values[i].resolution.xres,
			  attr->values[i].resolution.yres,
			  attr->values[i].resolution.units ==
			      IPP_RES_PER_INCH ? "dpi" :
			      attr->values[i].resolution.units ==
				  IPP_RES_PER_CM ? "dpcm" : "unknown");
	    return (0);
	  }
	}
        break;

    case IPP_TAG_RANGE :
        for (i = 0; i < attr->num_values; i ++)
	{
	  if (attr->values[i].range.lower > attr->values[i].range.upper)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad rangeOfInteger value %d-%d - lower "
			    "greater than upper (RFC 8011 section 5.1.14)."),
			  attr->name, attr->values[i].range.lower,
			  attr->values[i].range.upper);
	    return (0);
	  }
	}
        break;

    case IPP_TAG_BEGIN_COLLECTION :
        for (i = 0; i < attr->num_values; i ++)
	{
	  for (colattr = attr->values[i].collection->attrs;
	       colattr;
	       colattr = colattr->next)
	  {
	    if (!ippValidateAttribute(colattr))
	      return (0);
	  }
	}
        break;

    case IPP_TAG_TEXT :
    case IPP_TAG_TEXTLANG :
        for (i = 0; i < attr->num_values; i ++)
	{
	  for (ptr = attr->values[i].string.text; *ptr; ptr ++)
	  {
	    if ((*ptr & 0xe0) == 0xc0)
	    {
	      if ((ptr[1] & 0xc0) != 0x80)
	        break;

	      ptr ++;
	    }
	    else if ((*ptr & 0xf0) == 0xe0)
	    {
	      if ((ptr[1] & 0xc0) != 0x80 || (ptr[2] & 0xc0) != 0x80)
	        break;

	      ptr += 2;
	    }
	    else if ((*ptr & 0xf8) == 0xf0)
	    {
	      if ((ptr[1] & 0xc0) != 0x80 || (ptr[2] & 0xc0) != 0x80 || (ptr[3] & 0xc0) != 0x80)
	        break;

	      ptr += 3;
	    }
	    else if (*ptr & 0x80)
	      break;
	    else if ((*ptr < ' ' && *ptr != '\n' && *ptr != '\r' && *ptr != '\t') || *ptr == 0x7f)
	      break;
	  }

          if (*ptr)
          {
	    if (*ptr < ' ' || *ptr == 0x7f)
	    {
	      ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad text value \"%s\" - bad control character (PWG 5100.14 section 8.3)."), attr->name, attr->values[i].string.text);
	      return (0);
	    }
	    else
	    {
	      ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad text value \"%s\" - bad UTF-8 sequence (RFC 8011 section 5.1.2)."), attr->name, attr->values[i].string.text);
	      return (0);
	    }
          }

	  if ((ptr - attr->values[i].string.text) > (IPP_MAX_TEXT - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad text value \"%s\" - bad length %d "
			    "(RFC 8011 section 5.1.2)."), attr->name,
			  attr->values[i].string.text,
			  (int)(ptr - attr->values[i].string.text));
	    return (0);
	  }
	}
        break;

    case IPP_TAG_NAME :
    case IPP_TAG_NAMELANG :
        for (i = 0; i < attr->num_values; i ++)
	{
	  for (ptr = attr->values[i].string.text; *ptr; ptr ++)
	  {
	    if ((*ptr & 0xe0) == 0xc0)
	    {
	      if ((ptr[1] & 0xc0) != 0x80)
	        break;

	      ptr ++;
	    }
	    else if ((*ptr & 0xf0) == 0xe0)
	    {
	      if ((ptr[1] & 0xc0) != 0x80 || (ptr[2] & 0xc0) != 0x80)
	        break;

	      ptr += 2;
	    }
	    else if ((*ptr & 0xf8) == 0xf0)
	    {
	      if ((ptr[1] & 0xc0) != 0x80 || (ptr[2] & 0xc0) != 0x80 || (ptr[3] & 0xc0) != 0x80)
	        break;

	      ptr += 3;
	    }
	    else if (*ptr & 0x80)
	      break;
	    else if (*ptr < ' ' || *ptr == 0x7f)
	      break;
	  }

	  if (*ptr)
	  {
	    if (*ptr < ' ' || *ptr == 0x7f)
	    {
	      ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad name value \"%s\" - bad control character (PWG 5100.14 section 8.1)."), attr->name, attr->values[i].string.text);
	      return (0);
	    }
	    else
	    {
	      ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad name value \"%s\" - bad UTF-8 sequence (RFC 8011 section 5.1.3)."), attr->name, attr->values[i].string.text);
	      return (0);
	    }
          }

	  if ((ptr - attr->values[i].string.text) > (IPP_MAX_NAME - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad name value \"%s\" - bad length %d "
			    "(RFC 8011 section 5.1.3)."), attr->name,
			  attr->values[i].string.text,
			  (int)(ptr - attr->values[i].string.text));
	    return (0);
	  }
	}
        break;

    case IPP_TAG_KEYWORD :
        for (i = 0; i < attr->num_values; i ++)
	{
	  for (ptr = attr->values[i].string.text; *ptr; ptr ++)
	    if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' &&
	        *ptr != '_')
	      break;

	  if (*ptr || ptr == attr->values[i].string.text)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad keyword value \"%s\" - invalid "
			    "character (RFC 8011 section 5.1.4)."),
			  attr->name, attr->values[i].string.text);
	    return (0);
	  }

	  if ((ptr - attr->values[i].string.text) > (IPP_MAX_KEYWORD - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad keyword value \"%s\" - bad "
			    "length %d (RFC 8011 section 5.1.4)."),
			  attr->name, attr->values[i].string.text,
			  (int)(ptr - attr->values[i].string.text));
	    return (0);
	  }
	}
        break;

    case IPP_TAG_URI :
        for (i = 0; i < attr->num_values; i ++)
	{
	  uri_status = httpSeparateURI(HTTP_URI_CODING_ALL,
	                               attr->values[i].string.text,
				       scheme, sizeof(scheme),
				       userpass, sizeof(userpass),
				       hostname, sizeof(hostname),
				       &port, resource, sizeof(resource));

	  if (uri_status < HTTP_URI_STATUS_OK)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad URI value \"%s\" - %s (RFC 8011 section 5.1.6)."), attr->name, attr->values[i].string.text, httpURIStatusString(uri_status));
	    return (0);
	  }

	  if (strlen(attr->values[i].string.text) > (IPP_MAX_URI - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad URI value \"%s\" - bad length %d "
			    "(RFC 8011 section 5.1.6)."), attr->name,
			  attr->values[i].string.text,
			  (int)strlen(attr->values[i].string.text));
	  }
	}
        break;

    case IPP_TAG_URISCHEME :
        for (i = 0; i < attr->num_values; i ++)
	{
	  ptr = attr->values[i].string.text;
	  if (islower(*ptr & 255))
	  {
	    for (ptr ++; *ptr; ptr ++)
	      if (!islower(*ptr & 255) && !isdigit(*ptr & 255) &&
	          *ptr != '+' && *ptr != '-' && *ptr != '.')
                break;
	  }

	  if (*ptr || ptr == attr->values[i].string.text)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad uriScheme value \"%s\" - bad "
			    "characters (RFC 8011 section 5.1.7)."),
			  attr->name, attr->values[i].string.text);
	    return (0);
	  }

	  if ((ptr - attr->values[i].string.text) > (IPP_MAX_URISCHEME - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad uriScheme value \"%s\" - bad "
			    "length %d (RFC 8011 section 5.1.7)."),
			  attr->name, attr->values[i].string.text,
			  (int)(ptr - attr->values[i].string.text));
	    return (0);
	  }
	}
        break;

    case IPP_TAG_CHARSET :
        for (i = 0; i < attr->num_values; i ++)
	{
	  for (ptr = attr->values[i].string.text; *ptr; ptr ++)
	    if (!isprint(*ptr & 255) || isupper(*ptr & 255) ||
	        isspace(*ptr & 255))
	      break;

	  if (*ptr || ptr == attr->values[i].string.text)
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad charset value \"%s\" - bad "
			    "characters (RFC 8011 section 5.1.8)."),
			  attr->name, attr->values[i].string.text);
	    return (0);
	  }

	  if ((ptr - attr->values[i].string.text) > (IPP_MAX_CHARSET - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad charset value \"%s\" - bad "
			    "length %d (RFC 8011 section 5.1.8)."),
			  attr->name, attr->values[i].string.text,
			  (int)(ptr - attr->values[i].string.text));
	    return (0);
	  }
	}
        break;

    case IPP_TAG_LANGUAGE :
       /*
        * The following regular expression is derived from the ABNF for
	* language tags in RFC 4646.  All I can say is that this is the
	* easiest way to check the values...
	*/

        if ((i = regcomp(&re,
			 "^("
			 "(([a-z]{2,3}(-[a-z][a-z][a-z]){0,3})|[a-z]{4,8})"
								/* language */
			 "(-[a-z][a-z][a-z][a-z]){0,1}"		/* script */
			 "(-([a-z][a-z]|[0-9][0-9][0-9])){0,1}"	/* region */
			 "(-([a-z]{5,8}|[0-9][0-9][0-9]))*"	/* variant */
			 "(-[a-wy-z](-[a-z0-9]{2,8})+)*"	/* extension */
			 "(-x(-[a-z0-9]{1,8})+)*"		/* privateuse */
			 "|"
			 "x(-[a-z0-9]{1,8})+"			/* privateuse */
			 "|"
			 "[a-z]{1,3}(-[a-z][0-9]{2,8}){1,2}"	/* grandfathered */
			 ")$",
			 REG_NOSUB | REG_EXTENDED)) != 0)
        {
          char	temp[256];		/* Temporary error string */

          regerror(i, &re, temp, sizeof(temp));
	  ipp_set_error(IPP_STATUS_ERROR_INTERNAL,
			_("Unable to compile naturalLanguage regular "
			  "expression: %s."), temp);
	  return (0);
        }

        for (i = 0; i < attr->num_values; i ++)
	{
	  if (regexec(&re, attr->values[i].string.text, 0, NULL, 0))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad naturalLanguage value \"%s\" - bad "
			    "characters (RFC 8011 section 5.1.9)."),
			  attr->name, attr->values[i].string.text);
	    regfree(&re);
	    return (0);
	  }

	  if (strlen(attr->values[i].string.text) > (IPP_MAX_LANGUAGE - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad naturalLanguage value \"%s\" - bad "
			    "length %d (RFC 8011 section 5.1.9)."),
			  attr->name, attr->values[i].string.text,
			  (int)strlen(attr->values[i].string.text));
	    regfree(&re);
	    return (0);
	  }
	}

	regfree(&re);
        break;

    case IPP_TAG_MIMETYPE :
       /*
        * The following regular expression is derived from the ABNF for
	* MIME media types in RFC 2045 and 4288.  All I can say is that this is
	* the easiest way to check the values...
	*/

        if ((i = regcomp(&re,
			 "^"
			 "[-a-zA-Z0-9!#$&.+^_]{1,127}"		/* type-name */
			 "/"
			 "[-a-zA-Z0-9!#$&.+^_]{1,127}"		/* subtype-name */
			 "(;[-a-zA-Z0-9!#$&.+^_]{1,127}="	/* parameter= */
			 "([-a-zA-Z0-9!#$&.+^_]{1,127}|\"[^\"]*\"))*"
			 					/* value */
			 "$",
			 REG_NOSUB | REG_EXTENDED)) != 0)
        {
          char	temp[256];		/* Temporary error string */

          regerror(i, &re, temp, sizeof(temp));
	  ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			_("Unable to compile mimeMediaType regular "
			  "expression: %s."), temp);
	  return (0);
        }

        for (i = 0; i < attr->num_values; i ++)
	{
	  if (regexec(&re, attr->values[i].string.text, 0, NULL, 0))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad mimeMediaType value \"%s\" - bad "
			    "characters (RFC 8011 section 5.1.10)."),
			  attr->name, attr->values[i].string.text);
	    regfree(&re);
	    return (0);
	  }

	  if (strlen(attr->values[i].string.text) > (IPP_MAX_MIMETYPE - 1))
	  {
	    ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST,
			  _("\"%s\": Bad mimeMediaType value \"%s\" - bad "
			    "length %d (RFC 8011 section 5.1.10)."),
			  attr->name, attr->values[i].string.text,
			  (int)strlen(attr->values[i].string.text));
	    regfree(&re);
	    return (0);
	  }
	}

	regfree(&re);
        break;

    default :
        break;
  }

  return (1);
}