move_job(cupsd_client_t  *con,		/* I - Client connection */
	 ipp_attribute_t *uri)		/* I - Job URI */
{
  http_status_t	status;			/* Policy status */
  ipp_attribute_t *attr;		/* Current attribute */
  int		jobid;			/* Job ID */
  cupsd_job_t	*job;			/* Current job */
  const char	*src;			/* Source printer/class */
  cups_ptype_t	stype,			/* Source type (printer or class) */
		dtype;			/* Destination type (printer/class) */
  char		scheme[HTTP_MAX_URI],	/* Scheme portion of URI */
		username[HTTP_MAX_URI],	/* Username portion of URI */
		host[HTTP_MAX_URI],	/* Host portion of URI */
		resource[HTTP_MAX_URI];	/* Resource portion of URI */
  int		port;			/* Port portion of URI */
  cupsd_printer_t *sprinter,		/* Source printer */
		*dprinter;		/* Destination printer */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "move_job(%p[%d], %s)", con, con->number,
                  uri->values[0].string.text);

 /*
  * Get the new printer or class...
  */

  if ((attr = ippFindAttribute(con->request, "job-printer-uri",
                               IPP_TAG_URI)) == NULL)
  {
   /*
    * Need job-printer-uri...
    */

    send_ipp_status(con, IPP_BAD_REQUEST,
                    _("job-printer-uri attribute missing."));
    return;
  }

  if (!cupsdValidateDest(attr->values[0].string.text, &dtype, &dprinter))
  {
   /*
    * Bad URI...
    */

    send_ipp_status(con, IPP_NOT_FOUND,
                    _("The printer or class does not exist."));
    return;
  }

 /*
  * See if we have a job URI or a printer URI...
  */

  httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme,
                  sizeof(scheme), username, sizeof(username), host,
		  sizeof(host), &port, resource, sizeof(resource));

  if (!strcmp(uri->name, "printer-uri"))
  {
   /*
    * Got a printer URI; see if we also have a job-id attribute...
    */

    if ((attr = ippFindAttribute(con->request, "job-id",
                                 IPP_TAG_INTEGER)) == NULL)
    {
     /*
      * Move all jobs...
      */

      if ((src = cupsdValidateDest(uri->values[0].string.text, &stype,
                                   &sprinter)) == NULL)
      {
       /*
	* Bad URI...
	*/

	send_ipp_status(con, IPP_NOT_FOUND,
                	_("The printer or class does not exist."));
	return;
      }

      job = NULL;
    }
    else
    {
     /*
      * Otherwise, just move a single job...
      */

      if ((job = cupsdFindJob(attr->values[0].integer)) == NULL)
      {
       /*
	* Nope - return a "not found" error...
	*/

	send_ipp_status(con, IPP_NOT_FOUND,
                	_("Job #%d does not exist."), attr->values[0].integer);
	return;
      }
      else
      {
       /*
        * Job found, initialize source pointers...
	*/

	src      = NULL;
	sprinter = NULL;
      }
    }
  }
  else
  {
   /*
    * Got a job URI; parse it to get the job ID...
    */

    if (strncmp(resource, "/jobs/", 6))
    {
     /*
      * Not a valid URI!
      */

      send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-uri \"%s\"."),
                      uri->values[0].string.text);
      return;
    }

   /*
    * See if the job exists...
    */

    jobid = atoi(resource + 6);

    if ((job = cupsdFindJob(jobid)) == NULL)
    {
     /*
      * Nope - return a "not found" error...
      */

      send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist."), jobid);
      return;
    }
    else
    {
     /*
      * Job found, initialize source pointers...
      */

      src      = NULL;
      sprinter = NULL;
    }
  }

 /*
  * Check the policy of the destination printer...
  */

  if ((status = cupsdCheckPolicy(dprinter->op_policy_ptr, con,
                                 job ? job->username : NULL)) != HTTP_OK)
  {
    send_http_error(con, status, dprinter);
    return;
  }

 /*
  * Now move the job or jobs...
  */

  if (job)
  {
   /*
    * See if the job has been completed...
    */

    if (job->state_value > IPP_JOB_STOPPED)
    {
     /*
      * Return a "not-possible" error...
      */

      send_ipp_status(con, IPP_NOT_POSSIBLE,
                      _("Job #%d is finished and cannot be altered."),
		      job->id);
      return;
    }

   /*
    * See if the job is owned by the requesting user...
    */

    if (!validate_user(job, con, job->username, username, sizeof(username)))
    {
      send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
                      cupsdFindDest(job->dest));
      return;
    }

   /*
    * Move the job to a different printer or class...
    */

    cupsdMoveJob(job, dprinter);
  }
  else
  {
   /*
    * Got the source printer, now look through the jobs...
    */

    for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
         job;
	 job = (cupsd_job_t *)cupsArrayNext(Jobs))
    {
     /*
      * See if the job is pointing at the source printer or has not been
      * completed...
      */

      if (_cups_strcasecmp(job->dest, src) ||
          job->state_value > IPP_JOB_STOPPED)
	continue;

     /*
      * See if the job can be moved by the requesting user...
      */

      if (!validate_user(job, con, job->username, username, sizeof(username)))
        continue;

     /*
      * Move the job to a different printer or class...
      */

      cupsdMoveJob(job, dprinter);
    }
  }

 /*
  * Start jobs if possible...
  */

  cupsdCheckJobs();

 /*
  * Return with "everything is OK" status...
  */

  con->response->request.status.status_code = IPP_OK;
}