/*****************************************************************************\
* MODULE: webipp.cxx
*
* This module contains routines which handle the encoding/decoding of data
* sent across the HTTP wire to represent IPP packets.
*
* Public Interfaces
* -----------------
* WebIppRcvOpen    : returns a handle to an ipp-request stream
* WebIppRcvClose   : closes the handle to the ipp-request stream
* WebIppRcvData    : converts (Ipp -> W32)
* WebIppSndData    : converts (W32 -> Ipp)
* WebIppGetError   : returns ipp-error if WebIppSndData/WebIppRcvData fails
* WebIppLeToRsp    : returns an ipp-error mapping for a win32 error
* WebIppGetReqId   : returns the request-id for the ipp-stream
* WebIppGetUnsAttr : returns object with unsupported attribute strings
* WebIppGetReqFlag : returns a flag of requested-attributes from ipp-stream
* WebIppGetReqCp   : returns codepage that ipp-stream requests
* WebIppFreeMem    : used to free pointers returned from WebIpp* routines
*
* Definintions:
* ------------
* Ipp - Denotes Ipp-Formatted information according to the IPP protocol.
* W32 - Denotes win32 data that NT-Spooler understands.
*
* Copyright (C) 1996-1998 Microsoft Corporation
* Copyright (C) 1996-1998 Hewlett Packard
*
* history:
*   27-Oct-1997 <chriswil/v-chrisw> created.
*
\*****************************************************************************/

#include "spllibp.hxx"
#include <time.h>
#include <sys\timeb.h>
#include <wininet.h>
#include <winsock.h>

/*****************************************************************************\
* Static Strings
*
\*****************************************************************************/
static CONST TCHAR s_szJobLimit          [] = TEXT("limit");
static CONST TCHAR s_szJobName           [] = TEXT("job-name");
static CONST TCHAR s_szJobReqUser        [] = TEXT("requesting-user-name");
static CONST TCHAR s_szJobOrgUser        [] = TEXT("job-originating-user-name");
static CONST TCHAR s_szDocName           [] = TEXT("document-name");
static CONST TCHAR s_szJobId             [] = TEXT("job-id");
static CONST TCHAR s_szJobUri            [] = TEXT("job-uri");
static CONST TCHAR s_szJobState          [] = TEXT("job-state");
static CONST TCHAR s_szJobPri            [] = TEXT("job-priority");
static CONST TCHAR s_szJobKOctets        [] = TEXT("job-k-octets");
static CONST TCHAR s_szJobKOctetsProcess [] = TEXT("job-k-octets-processed");
static CONST TCHAR s_szJobSheets         [] = TEXT("job-media-sheets");
static CONST TCHAR s_szJobPrtUri         [] = TEXT("job-printer-uri");
static CONST TCHAR s_szTimeAtCreation    [] = TEXT("time-at-creation");
static CONST TCHAR s_szJobSheetsCompleted[] = TEXT("job-media-sheets-completed");
static CONST TCHAR s_szPrtUri            [] = TEXT("printer-uri");
static CONST TCHAR s_szPrtUriSupported   [] = TEXT("printer-uri-supported");
static CONST TCHAR s_szPrtUriSecurity    [] = TEXT("uri-security-supported");
static CONST TCHAR s_szPrtSecNone        [] = TEXT("none");
static CONST TCHAR s_szPrtOpsSupported   [] = TEXT("operations-supported");
static CONST TCHAR s_szPrtName           [] = TEXT("printer-name");
static CONST TCHAR s_szPrtState          [] = TEXT("printer-state");
static CONST TCHAR s_szPrtJobs           [] = TEXT("queued-job-count");
static CONST TCHAR s_szPrtMake           [] = TEXT("printer-make-and-model");
static CONST TCHAR s_szPrtAcceptingJobs  [] = TEXT("printer-is-accepting-jobs");
static CONST TCHAR s_szPrtUpTime         [] = TEXT("printer-up-time");
static CONST TCHAR s_szCharSetSupported  [] = TEXT("charset-supported");
static CONST TCHAR s_szCharSetConfigured [] = TEXT("charset-configured");
static CONST TCHAR s_szNatLangConfigured [] = TEXT("natural-language-configured");
static CONST TCHAR s_szNatLangSupported  [] = TEXT("generated-natural-language-supported");
static CONST TCHAR s_szUnknown           [] = TEXT("unknown");
static CONST TCHAR s_szWhichJobs         [] = TEXT("which-jobs");
static CONST TCHAR s_szCharSet           [] = TEXT("attributes-charset");
static CONST TCHAR s_szNaturalLanguage   [] = TEXT("attributes-natural-language");
static CONST TCHAR s_szReqAttr           [] = TEXT("requested-attributes");
static CONST TCHAR s_szUtf8              [] = TEXT("utf-8");
static CONST TCHAR s_szUsAscii           [] = TEXT("us-ascii");
static CONST TCHAR s_szEnUS              [] = TEXT("en-us");
static CONST TCHAR s_szDocFormatDefault  [] = TEXT("document-format-default");
static CONST TCHAR s_szDocFormatSupported[] = TEXT("document-format-supported");
static CONST TCHAR s_szStaMsg            [] = TEXT("status-message");
static CONST TCHAR s_szPdlOverride       [] = TEXT("pdl-override-supported");
static CONST TCHAR s_szNotAttempted      [] = TEXT("not-attempted");
static CONST TCHAR s_szDocFormat         [] = TEXT("document-format");
static CONST TCHAR s_szCompleted         [] = TEXT("completed");
static CONST TCHAR s_szNotCompleted      [] = TEXT("not-completed");
static CONST TCHAR s_szMimeTxtHtml       [] = TEXT("text/html");
static CONST TCHAR s_szMimeTxtPlain      [] = TEXT("text/plain");
static CONST TCHAR s_szMimePostScript    [] = TEXT("application/postscript");
static CONST TCHAR s_szMimePCL           [] = TEXT("application/vnd.hppcl");
static CONST TCHAR s_szMimeOctStream     [] = TEXT("application/octet-stream");
static CONST TCHAR s_szAll               [] = TEXT("all");
static CONST TCHAR s_szJobTemplate       [] = TEXT("job-template");
static CONST TCHAR s_szJobDescription    [] = TEXT("job-description");
static CONST TCHAR s_szPrtDescription    [] = TEXT("printer-description");
static CONST TCHAR s_szUnsupported       [] = TEXT("unsupported");
static CONST TCHAR s_szAtrFidelity       [] = TEXT("ipp-attribute-fidelity");
static CONST TCHAR s_szTrue              [] = TEXT("true");
static CONST TCHAR s_szFalse             [] = TEXT("false");

/*****************************************************************************\
* Ipp Error-Mapping
*
* These tables define the mappings for Win32 LastErrors and Ipp-http errors.
*
\*****************************************************************************/
static IPPERROR s_LEIpp[] = {

    IPPRSP_ERROR_400, ERROR_INVALID_DATA          , TEXT("Client: (400) BadRequest")                   ,
    IPPRSP_ERROR_401, ERROR_ACCESS_DENIED         , TEXT("Client: (401) Forbidden Access")             ,
    IPPRSP_ERROR_402, ERROR_ACCESS_DENIED         , TEXT("Client: (402) Not Authenticated")            ,
    IPPRSP_ERROR_403, ERROR_ACCESS_DENIED         , TEXT("Client: (403) Not Authorized")               ,
    IPPRSP_ERROR_404, ERROR_INVALID_DATA          , TEXT("Client: (404) Not Possible")                 ,
    IPPRSP_ERROR_405, ERROR_TIMEOUT               , TEXT("Client: (405) Time Out")                     ,
    IPPRSP_ERROR_406, ERROR_INVALID_DATA          , TEXT("Client: (406) Not Found")                    ,
    IPPRSP_ERROR_407, ERROR_INVALID_DATA          , TEXT("Client: (407) Gone")                         ,
    IPPRSP_ERROR_408, ERROR_INVALID_DATA          , TEXT("Client: (408) Entity Too Large")             ,
    IPPRSP_ERROR_409, ERROR_INVALID_DATA          , TEXT("Client: (409) Uri Too Long")                 ,
    IPPRSP_ERROR_40A, ERROR_INVALID_DATA          , TEXT("Client: (40A) Document Format Not Supported"),
    IPPRSP_ERROR_40B, ERROR_INVALID_DATA          , TEXT("Client: (40B) Attributes Not Supported")     ,
    IPPRSP_ERROR_40C, ERROR_INVALID_DATA          , TEXT("Client: (40C) Uri Scheme Not Supported")     ,
    IPPRSP_ERROR_40D, ERROR_INVALID_DATA          , TEXT("Client: (40D) Charset Not Supported")        ,
    IPPRSP_ERROR_40E, ERROR_INVALID_DATA          , TEXT("Client: (40E) Conflicting Attributes")       ,
    IPPRSP_ERROR_500, ERROR_INVALID_DATA          , TEXT("Server: (500) Internal Error")               ,
    IPPRSP_ERROR_501, ERROR_INVALID_DATA          , TEXT("Server: (501) Operation Not Supported")      ,
    IPPRSP_ERROR_502, ERROR_NOT_READY             , TEXT("Server: (502) Service Unavailable")          ,
    IPPRSP_ERROR_503, ERROR_INVALID_DATA          , TEXT("Server: (503) Version Not Supported")        ,
    IPPRSP_ERROR_504, ERROR_NOT_READY             , TEXT("Server: (504) Device Error")                 ,
    IPPRSP_ERROR_505, ERROR_OUTOFMEMORY           , TEXT("Server: (505) Temporary Error")              ,
    IPPRSP_ERROR_506, ERROR_INVALID_DATA          , TEXT("Server: (506) Not Accepting Jobs")           ,
    IPPRSP_ERROR_540, ERROR_LICENSE_QUOTA_EXCEEDED, TEXT("Server: (540) Too Many Users")
};

static IPPDEFERROR s_LEDef[] = {

    ERROR_INVALID_DATA          , IPPRSP_ERROR_400,
    ERROR_ACCESS_DENIED         , IPPRSP_ERROR_401,
    ERROR_INVALID_PARAMETER     , IPPRSP_ERROR_404,
    ERROR_TIMEOUT               , IPPRSP_ERROR_405,
    ERROR_NOT_READY             , IPPRSP_ERROR_504,
    ERROR_OUTOFMEMORY           , IPPRSP_ERROR_505,
    ERROR_LICENSE_QUOTA_EXCEEDED, IPPRSP_ERROR_540
};


/*****************************************************************************\
* Request/Response attributes that are written to the ipp-stream.
*
*
\*****************************************************************************/
static IPPATTRX s_PJQ[] = { // PrtJob, ValJob Request

    IPP_TAG_CHR_URI , RA_PRNURI , IPP_ATR_OFFSET  , s_szPrtUri    , (LPVOID)offs(PIPPREQ_PRTJOB, pPrnUri)  ,
    IPP_TAG_CHR_NAME, RA_JOBNAME, IPP_ATR_OFFSET  , s_szJobName   , (LPVOID)offs(PIPPREQ_PRTJOB, pDocument),
    IPP_TAG_CHR_NAME, RA_JOBUSER, IPP_ATR_OFFSET  , s_szJobReqUser, (LPVOID)offs(PIPPREQ_PRTJOB, pUserName),
    IPP_TAG_DEL_JOB , 0         , IPP_ATR_TAG     , NULL          , (LPVOID)NULL
};

static IPPATTRX s_EJQ[] = { // GetJobs Request

    IPP_TAG_CHR_URI    , RA_PRNURI         , IPP_ATR_OFFSET  , s_szPrtUri  , (LPVOID)offs(PIPPREQ_ENUJOB, pPrnUri),
    IPP_TAG_INT_INTEGER, RA_JOBCOUNT       , IPP_ATR_OFFSET  , s_szJobLimit, (LPVOID)offs(PIPPREQ_ENUJOB, cJobs)  ,
    IPP_TAG_CHR_KEYWORD, 0                 , IPP_ATR_ABSOLUTE, s_szReqAttr , (LPVOID)s_szAll
};

static IPPATTRX s_SJQ[] = { // PauJob, CanJob, RsmJob, RstJob Request

    IPP_TAG_CHR_URI    , 0, IPP_ATR_OFFSET, s_szPrtUri, (LPVOID)offs(PIPPREQ_SETJOB, pPrnUri),
    IPP_TAG_INT_INTEGER, 0, IPP_ATR_OFFSET, s_szJobId , (LPVOID)offs(PIPPREQ_SETJOB, idJob)
};


static IPPATTRX s_GJQ[] = { // GetJobAtr Request

    IPP_TAG_CHR_URI    , 0, IPP_ATR_OFFSET, s_szPrtUri, (LPVOID)offs(PIPPREQ_GETJOB, pPrnUri),
    IPP_TAG_INT_INTEGER, 0, IPP_ATR_OFFSET, s_szJobId , (LPVOID)offs(PIPPREQ_GETJOB, idJob)
};

static IPPATTRX s_SPQ[] = { // PauPrn, CanPrn, RsmPrn, RstPrn Request

    IPP_TAG_CHR_URI , 0, IPP_ATR_OFFSET, s_szPrtUri    , (LPVOID)offs(PIPPREQ_SETPRN, pPrnUri) ,
    IPP_TAG_CHR_NAME, 0, IPP_ATR_OFFSET, s_szJobReqUser, (LPVOID)offs(PIPPREQ_SETPRN, pUserName)
};

static IPPATTRX s_GPQ[] = { // GetPrnAtr Request

    IPP_TAG_CHR_URI, 0, IPP_ATR_OFFSET, s_szPrtUri, (LPVOID)offs(PIPPREQ_GETPRN, pPrnUri)
};

static IPPATTRX s_PJR[] = { // PrintJob Response

    IPP_TAG_DEL_JOB    , 0                 , IPP_ATR_TAG       , NULL                  , (LPVOID)NULL                                  ,
    IPP_TAG_INT_INTEGER, RA_JOBID          , IPP_ATR_OFFSET    , s_szJobId             , (LPVOID)offs(PIPPRET_JOB, ji.ji2.JobId)       ,
    IPP_TAG_INT_ENUM   , RA_JOBSTATE       , IPP_ATR_OFFSETCONV, s_szJobState          , (LPVOID)offs(PIPPRET_JOB, ji.ji2.Status)      ,
    IPP_TAG_INT_INTEGER, RA_JOBPRIORITY    , IPP_ATR_OFFSET    , s_szJobPri            , (LPVOID)offs(PIPPRET_JOB, ji.ji2.Priority)    ,
    IPP_TAG_INT_INTEGER, RA_JOBSIZE        , IPP_ATR_OFFSETCONV, s_szJobKOctetsProcess , (LPVOID)offs(PIPPRET_JOB, ji.ji2.Size)        ,
    IPP_TAG_INT_INTEGER, RA_SHEETSTOTAL    , IPP_ATR_OFFSET    , s_szJobSheets         , (LPVOID)offs(PIPPRET_JOB, ji.ji2.TotalPages)  ,
    IPP_TAG_INT_INTEGER, RA_SHEETSCOMPLETED, IPP_ATR_OFFSET    , s_szJobSheetsCompleted, (LPVOID)offs(PIPPRET_JOB, ji.ji2.PagesPrinted),
    IPP_TAG_CHR_NAME   , RA_JOBNAME        , IPP_ATR_OFFSET    , s_szJobName           , (LPVOID)offs(PIPPRET_JOB, ji.ji2.pDocument)   ,
    IPP_TAG_CHR_NAME   , RA_JOBUSER        , IPP_ATR_OFFSET    , s_szJobOrgUser        , (LPVOID)offs(PIPPRET_JOB, ji.ji2.pUserName)   ,
    IPP_TAG_CHR_URI    , RA_JOBURI         , IPP_ATR_OFFSET    , s_szJobUri            , (LPVOID)offs(PIPPRET_JOB, ji.ipp.pJobUri)     ,
    IPP_TAG_CHR_URI    , RA_PRNURI         , IPP_ATR_OFFSET    , s_szJobPrtUri         , (LPVOID)offs(PIPPRET_JOB, ji.ipp.pPrnUri)
};

static IPPATTRX s_EJR[] = { // GetJobs Response

    IPP_TAG_DEL_JOB    , 0                 , IPP_ATR_TAG   , NULL                  , (LPVOID)NULL                           ,
    IPP_TAG_INT_INTEGER, RA_JOBID          , IPP_ATR_OFFSET, s_szJobId             , (LPVOID)offs(PIPPJI2, ji2.JobId)       ,
    IPP_TAG_INT_ENUM   , RA_JOBSTATE       , IPP_ATR_OFFSET, s_szJobState          , (LPVOID)offs(PIPPJI2, ji2.Status)      ,
    IPP_TAG_INT_INTEGER, RA_JOBPRIORITY    , IPP_ATR_OFFSET, s_szJobPri            , (LPVOID)offs(PIPPJI2, ji2.Priority)    ,
    IPP_TAG_INT_INTEGER, RA_JOBSIZE        , IPP_ATR_OFFSET, s_szJobKOctetsProcess , (LPVOID)offs(PIPPJI2, ji2.Size)        ,
    IPP_TAG_INT_INTEGER, RA_SHEETSTOTAL    , IPP_ATR_OFFSET, s_szJobSheets         , (LPVOID)offs(PIPPJI2, ji2.TotalPages)  ,
    IPP_TAG_INT_INTEGER, RA_SHEETSCOMPLETED, IPP_ATR_OFFSET, s_szJobSheetsCompleted, (LPVOID)offs(PIPPJI2, ji2.PagesPrinted),
    IPP_TAG_INT_INTEGER, RA_TIMEATCREATION , IPP_ATR_OFFSET, s_szTimeAtCreation    , (LPVOID)offs(PIPPJI2, ji2.Submitted)   ,
    IPP_TAG_CHR_NAME   , RA_JOBNAME        , IPP_ATR_OFFSET, s_szJobName           , (LPVOID)offs(PIPPJI2, ji2.pDocument)   ,
    IPP_TAG_CHR_NAME   , RA_JOBUSER        , IPP_ATR_OFFSET, s_szJobOrgUser        , (LPVOID)offs(PIPPJI2, ji2.pUserName)   ,
    IPP_TAG_CHR_URI    , RA_JOBURI         , IPP_ATR_OFFSET, s_szJobUri            , (LPVOID)offs(PIPPJI2, ipp.pJobUri)     ,
    IPP_TAG_CHR_URI    , RA_PRNURI         , IPP_ATR_OFFSET, s_szJobPrtUri         , (LPVOID)offs(PIPPJI2, ipp.pPrnUri)
};

static IPPATTRX s_GPR[] = { // GetPrnAtr Response

    IPP_TAG_DEL_PRINTER, 0                  , IPP_ATR_TAG       , NULL                  , (LPVOID)NULL                                  ,
    IPP_TAG_INT_ENUM   , RA_PRNSTATE        , IPP_ATR_OFFSETCONV, s_szPrtState          , (LPVOID)offs(PIPPRET_PRN, pi.pi2.Status)      ,
    IPP_TAG_INT_INTEGER, RA_JOBCOUNT        , IPP_ATR_OFFSET    , s_szPrtJobs           , (LPVOID)offs(PIPPRET_PRN, pi.pi2.cJobs)       ,
    IPP_TAG_CHR_URI    , RA_URISUPPORTED    , IPP_ATR_OFFSET    , s_szPrtUriSupported   , (LPVOID)offs(PIPPRET_PRN, pi.ipp.pPrnUri)     ,
    IPP_TAG_CHR_KEYWORD, RA_URISECURITY     , IPP_ATR_ABSOLUTE  , s_szPrtUriSecurity    , (LPVOID)s_szPrtSecNone                        ,
    IPP_TAG_CHR_NAME   , RA_PRNNAME         , IPP_ATR_OFFSET    , s_szPrtName           , (LPVOID)offs(PIPPRET_PRN, pi.pi2.pPrinterName),
    IPP_TAG_CHR_TEXT   , RA_PRNMAKE         , IPP_ATR_OFFSET    , s_szPrtMake           , (LPVOID)offs(PIPPRET_PRN, pi.pi2.pDriverName) ,
    IPP_TAG_INT_BOOLEAN, RA_ACCEPTINGJOBS   , IPP_ATR_ABSOLUTE  , s_szPrtAcceptingJobs  , (LPVOID)TRUE                                  ,
    IPP_TAG_CHR_CHARSET, RA_CHRSETCONFIGURED, IPP_ATR_ABSOLUTE  , s_szCharSetConfigured , (LPVOID)s_szUtf8                              ,
    IPP_TAG_CHR_CHARSET, RA_CHRSETSUPPORTED , IPP_ATR_ABSOLUTE  , s_szCharSetSupported  , (LPVOID)s_szUtf8                              ,
    IPP_TAG_CHR_CHARSET, 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)s_szUsAscii                           ,
    IPP_TAG_CHR_NATURAL, RA_NATLNGCONFIGURED, IPP_ATR_ABSOLUTE  , s_szNatLangConfigured , (LPVOID)s_szEnUS                              ,
    IPP_TAG_CHR_NATURAL, RA_NATLNGSUPPORTED , IPP_ATR_ABSOLUTE  , s_szNatLangSupported  , (LPVOID)s_szEnUS                              ,
    IPP_TAG_CHR_MEDIA  , RA_DOCDEFAULT      , IPP_ATR_ABSOLUTE  , s_szDocFormatDefault  , (LPVOID)s_szMimeOctStream                     ,
    IPP_TAG_CHR_MEDIA  , RA_DOCSUPPORTED    , IPP_ATR_ABSOLUTE  , s_szDocFormatSupported, (LPVOID)s_szMimeOctStream                     ,
    IPP_TAG_CHR_KEYWORD, RA_PDLOVERRIDE     , IPP_ATR_ABSOLUTE  , s_szPdlOverride       , (LPVOID)s_szNotAttempted                      ,
    IPP_TAG_INT_INTEGER, RA_UPTIME          , IPP_ATR_ABSOLUTE  , s_szPrtUpTime         , (LPVOID)1                                     ,
    IPP_TAG_INT_ENUM   , RA_OPSSUPPORTED    , IPP_ATR_ABSOLUTE  , s_szPrtOpsSupported   , (LPVOID)IPP_REQ_PRINTJOB                      ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_VALIDATEJOB                   ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_CANCELJOB                     ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_GETJOB                        ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_ENUJOB                        ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_GETPRN                        ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_PAUSEJOB                      ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_RESUMEJOB                     ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_RESTARTJOB                    ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_PAUSEPRN                      ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_RESUMEPRN                     ,
    IPP_TAG_INT_ENUM   , 0                  , IPP_ATR_ABSOLUTE  , NULL                  , (LPVOID)IPP_REQ_CANCELPRN
};


/*****************************************************************************\
* Request/Response string-mappings.
*
*
\*****************************************************************************/
static FLGSTR s_ReqRspStr[] = {

    RA_JOBUSER, s_szJobReqUser,
    RA_JOBSIZE, s_szJobKOctets
};


/*****************************************************************************\
* Receive/Response group forms.
*
* These tables defines the order and layout of ipp group tags.
*
\*****************************************************************************/
static BYTE s_FormA[] = {

    IPP_TAG_DEL_OPERATION | IPP_MANDITORY,
    IPP_TAG_DEL_JOB       | IPP_OPTIONAL,
    IPP_TAG_DEL_DATA      | IPP_MANDITORY,
    0
};

static BYTE s_FormB[] = {

    IPP_TAG_DEL_OPERATION | IPP_MANDITORY,
    IPP_TAG_DEL_DATA      | IPP_MANDITORY,
    0
};

static BYTE s_FormC[] = {

    IPP_TAG_DEL_OPERATION   | IPP_MANDITORY,
    IPP_TAG_DEL_UNSUPPORTED | IPP_OPTIONAL ,
    IPP_TAG_DEL_JOB         | IPP_OPTIONAL ,
    IPP_TAG_DEL_DATA        | IPP_MANDITORY,
    0
};

static BYTE s_FormD[] = {

    IPP_TAG_DEL_OPERATION   | IPP_MANDITORY,
    IPP_TAG_DEL_UNSUPPORTED | IPP_OPTIONAL ,
    IPP_TAG_DEL_PRINTER     | IPP_OPTIONAL ,
    IPP_TAG_DEL_DATA        | IPP_MANDITORY,
    0
};

static BYTE s_FormE[] = {

    IPP_TAG_DEL_OPERATION   | IPP_MANDITORY,
    IPP_TAG_DEL_UNSUPPORTED | IPP_OPTIONAL ,
    IPP_TAG_DEL_DATA        | IPP_MANDITORY,
    0
};

static BYTE s_FormF[] = {

    IPP_TAG_DEL_OPERATION   | IPP_MANDITORY              ,
    IPP_TAG_DEL_UNSUPPORTED | IPP_OPTIONAL               ,
    IPP_TAG_DEL_JOB         | IPP_OPTIONAL | IPP_MULTIPLE,
    IPP_TAG_DEL_DATA        | IPP_MANDITORY              ,
    0
};


/*****************************************************************************\
* Structure Offsets
*
*
\*****************************************************************************/
static DWORD s_IPPJI2Offs[] = {

    offs(LPIPPJI2, ji2.pPrinterName),
    offs(LPIPPJI2, ji2.pMachineName),
    offs(LPIPPJI2, ji2.pUserName),
    offs(LPIPPJI2, ji2.pDocument),
    offs(LPIPPJI2, ji2.pNotifyName),
    offs(LPIPPJI2, ji2.pDatatype),
    offs(LPIPPJI2, ji2.pPrintProcessor),
    offs(LPIPPJI2, ji2.pParameters),
    offs(LPIPPJI2, ji2.pDriverName),
    offs(LPIPPJI2, ji2.pDevMode),
    offs(LPIPPJI2, ji2.pStatus),
    offs(LPIPPJI2, ji2.pSecurityDescriptor),
    offs(LPIPPJI2, ipp.pPrnUri),
    offs(LPIPPJI2, ipp.pJobUri),
    0xFFFFFFFF
};

static DWORD s_IPPPI2Offs[] = {

    offs(LPIPPPI2, pi2.pServerName),
    offs(LPIPPPI2, pi2.pPrinterName),
    offs(LPIPPPI2, pi2.pShareName),
    offs(LPIPPPI2, pi2.pPortName),
    offs(LPIPPPI2, pi2.pDriverName),
    offs(LPIPPPI2, pi2.pComment),
    offs(LPIPPPI2, pi2.pLocation),
    offs(LPIPPPI2, pi2.pDevMode),
    offs(LPIPPPI2, pi2.pSepFile),
    offs(LPIPPPI2, pi2.pPrintProcessor),
    offs(LPIPPPI2, pi2.pDatatype),
    offs(LPIPPPI2, pi2.pParameters),
    offs(LPIPPPI2, pi2.pSecurityDescriptor),
    offs(LPIPPPI2, ipp.pPrnUri),
    offs(LPIPPPI2, ipp.pUsrName),
    0xFFFFFFFF
};

static DWORD s_JI2Off[] = {

    offs(LPJOB_INFO_2, pPrinterName),
    offs(LPJOB_INFO_2, pMachineName),
    offs(LPJOB_INFO_2, pUserName),
    offs(LPJOB_INFO_2, pDocument),
    offs(LPJOB_INFO_2, pNotifyName),
    offs(LPJOB_INFO_2, pDatatype),
    offs(LPJOB_INFO_2, pPrintProcessor),
    offs(LPJOB_INFO_2, pParameters),
    offs(LPJOB_INFO_2, pDriverName),
    // Do not include DEVMODE
    offs(LPJOB_INFO_2, pStatus),
    // Do not include SECURITY-DESCRIPTOR
    0xFFFFFFFF
};

static DWORD s_PI2Off[] = {

    offs(LPPRINTER_INFO_2, pServerName),
    offs(LPPRINTER_INFO_2, pPrinterName),
    offs(LPPRINTER_INFO_2, pShareName),
    offs(LPPRINTER_INFO_2, pPortName),
    offs(LPPRINTER_INFO_2, pDriverName),
    offs(LPPRINTER_INFO_2, pComment),
    offs(LPPRINTER_INFO_2, pLocation),
    // Do not include DEVMODE
    offs(LPPRINTER_INFO_2, pSepFile),
    offs(LPPRINTER_INFO_2, pPrintProcessor),
    offs(LPPRINTER_INFO_2, pDatatype),
    offs(LPPRINTER_INFO_2, pParameters),
    // Do not include SECURITY-DESCRIPTOR
    0xFFFFFFFF
};

static DWORD s_IPJOff[] = {

    offs(LPJOB_INFO_IPP, pPrnUri),
    offs(LPJOB_INFO_IPP, pJobUri),
    0xFFFFFFFF
};

static DWORD s_IPPOff[] = {

    offs(LPPRINTER_INFO_IPP, pPrnUri) ,
    offs(LPPRINTER_INFO_IPP, pUsrName),
    0xFFFFFFFF
};


/*****************************************************************************\
* ipp_SetReq (Local Routine)
*
* Sets a bit in the request flag.  If the index (upper 4 bits) is greater
* than 7, then we use this as a special enum flag.
*
\*****************************************************************************/
VOID x_SetReq(
    PDWORD pfReq,
    DWORD  fSet)
{
    DWORD  idz;
    PDWORD pFlg;
    DWORD  cFlg = 0;
    DWORD  idx  = ((fSet >> 28) & 0x0000000F);

    static DWORD s_fReqEnu[] = {

        RA_JOBID,
        RA_JOBURI
    };

    static DWORD s_fJobTmp[] = {

        RA_JOBPRIORITY     ,
        RA_SHEETSTOTAL     ,
        RA_SHEETSCOMPLETED
    };

    static DWORD s_fJobDsc[] = {

        RA_JOBURI          ,
        RA_JOBID           ,
        RA_JOBNAME         ,
        RA_JOBUSER         ,
        RA_JOBSTATE        ,
        RA_JOBSTATE_REASONS,
        RA_JOBSTATE_MESSAGE,
        RA_JOBSIZE
    };

    static DWORD s_fPrtDsc[] = {

        RA_URISUPPORTED    ,
        RA_URISECURITY     ,
        RA_PRNNAME         ,
        RA_PRNMAKE         ,
        RA_PRNSTATE        ,
        RA_OPSSUPPORTED    ,
        RA_CHRSETCONFIGURED,
        RA_CHRSETSUPPORTED ,
        RA_NATLNGCONFIGURED,
        RA_NATLNGSUPPORTED ,
        RA_DOCDEFAULT      ,
        RA_DOCSUPPORTED    ,
        RA_ACCEPTINGJOBS   ,
        RA_JOBCOUNT        ,
        RA_PDLOVERRIDE     ,
        RA_UPTIME
    };


    switch (idx) {

    case IPP_REQALL_IDX:
        pfReq[0] = 0x0FFFFFFF;
        pfReq[1] = 0x0FFFFFFF;
        break;

    case IPP_REQCLEAR_IDX:
        pfReq[0] = 0x00000000;
        pfReq[1] = 0x00000000;
        break;

    case IPP_REQENU_IDX:
        pFlg = s_fReqEnu;
        cFlg = sizeof(s_fReqEnu) / sizeof(s_fReqEnu[0]);
        break;

    case IPP_REQJDSC_IDX:
        pFlg = s_fJobDsc;
        cFlg = sizeof(s_fJobDsc) / sizeof(s_fJobDsc[0]);
        break;

    case IPP_REQJTMP_IDX:
        pFlg = s_fJobTmp;
        cFlg = sizeof(s_fJobTmp) / sizeof(s_fJobTmp[0]);
        break;

    case IPP_REQPDSC_IDX:
        pFlg = s_fPrtDsc;
        cFlg = sizeof(s_fPrtDsc) / sizeof(s_fPrtDsc[0]);
        break;
    }


    if (idx >= IPP_REQALL_IDX) {

        for (idz = 0; idz < cFlg; idz++) {

            idx = ((pFlg[idz] >> 28) & 0x0000000F);

            pfReq[idx] |= (pFlg[idz] & 0x0FFFFFFF);
        }

    } else {

        pfReq[idx] |= (fSet & 0x0FFFFFFF);
    }
}


/*****************************************************************************\
* ipp_ChkReq (Local Routine)
*
* Checks to se if bit-flag is set in the request flag.
*
\*****************************************************************************/
BOOL x_ChkReq(
    PDWORD pfReq,
    DWORD  fChk)
{
    DWORD idx = ((fChk >> 28) & 0x0000000F);

    return pfReq[idx] & (fChk & 0x0FFFFFFF);
}


/*****************************************************************************\
* ipp_CopyAligned (Local Routine)
*
* Copies memory to an aligned-buffer.
*
\*****************************************************************************/
inline LPBYTE ipp_CopyAligned(
    LPBYTE lpDta,
    DWORD            cbDta)
{
    LPBYTE lpAln;


    if (lpAln = (LPBYTE)webAlloc(cbDta))
        CopyMemory((LPVOID)lpAln, lpDta, cbDta);

    return lpAln;
}


/*****************************************************************************\
* ipp_WriteData (Local Routine)
*
* Sets the data in an IPP-Data-Stream.  This adjusts the pointer to the
* next byte-location in the stream.
*
\*****************************************************************************/
inline VOID ipp_WriteData(
    LPBYTE* lplpPtr,
    LPVOID  lpData,
    DWORD   cbData)
{
    CopyMemory(*lplpPtr, lpData, cbData);

    *lplpPtr += cbData;
}


/*****************************************************************************\
* ipp_WriteByte (Local Routine)
*
* Write out a byte to the stream.
*
\*****************************************************************************/
inline VOID ipp_WriteByte(
    LPBYTE* lplpIppPtr,
    BYTE    bVal)
{
    ipp_WriteData(lplpIppPtr, (LPVOID)&bVal, IPP_SIZEOFTAG);
}


/*****************************************************************************\
* ipp_ReadByte (Local Routine)
*
* Read a byte from the stream.
*
\*****************************************************************************/
inline BYTE ipp_ReadByte(
    LPBYTE lpbPtr,
    DWORD  cbIdx)
{
    return (*(BYTE *)((LPBYTE)(lpbPtr) + cbIdx));
}


/*****************************************************************************\
* ipp_WriteWord (Local Routine)
*
* Write out a word to the stream.
*
\*****************************************************************************/
inline VOID ipp_WriteWord(
    LPBYTE* lplpIppPtr,
    WORD    wVal)
{
    WORD wNBW = htons (wVal);

    ipp_WriteData(lplpIppPtr, (LPVOID)&wNBW, IPP_SIZEOFLEN);
}


/*****************************************************************************\
* ipp_ReadWord (Local Routine)
*
* Read a word from the stream.
*
\*****************************************************************************/
inline WORD ipp_ReadWord(
    LPBYTE lpbPtr,
    DWORD  cbIdx)
{
    WORD wVal = (*(WORD UNALIGNED *)((LPBYTE)(lpbPtr) + cbIdx));

    return ntohs (wVal);
}


/*****************************************************************************\
* ipp_WriteDWord (Local Routine)
*
* Write out a dword to the stream.
*
\*****************************************************************************/
inline VOID ipp_WriteDWord(
    LPBYTE* lplpIppPtr,
    DWORD   dwVal)
{
    DWORD dwNBDW = htonl(dwVal);

    ipp_WriteData(lplpIppPtr, (LPVOID)&dwNBDW, IPP_SIZEOFINT);
}


/*****************************************************************************\
* ipp_ReadDWord (Local Routine)
*
* Read a dword from the stream.
*
\*****************************************************************************/
inline DWORD ipp_ReadDWord(
    LPBYTE lpbPtr,
    DWORD  cbIdx)
{
    DWORD dwVal = (*(DWORD UNALIGNED *)((LPBYTE)(lpbPtr) + cbIdx));

    return ntohl(dwVal);
}


/*****************************************************************************\
* ipp_MapReqToJobCmd (Local Routine)
*
* Returns a job-command from a request.
*
\*****************************************************************************/
inline DWORD ipp_MapReqToJobCmd(
    WORD wReq)
{
    if (wReq == IPP_REQ_CANCELJOB)
        return JOB_CONTROL_DELETE;

    if (wReq == IPP_REQ_PAUSEJOB)
        return JOB_CONTROL_PAUSE;

    if (wReq == IPP_REQ_RESUMEJOB)
        return JOB_CONTROL_RESUME;

    if (wReq == IPP_REQ_RESTARTJOB)
        return JOB_CONTROL_RESTART;

    return 0;
}


/*****************************************************************************\
* ipp_MapReqToPrnCmd (Local Routine)
*
* Returns a printer-command from a request.
*
\*****************************************************************************/
inline DWORD ipp_MapReqToPrnCmd(
    WORD wReq)
{
    if (wReq == IPP_REQ_RESUMEPRN)
        return PRINTER_CONTROL_RESUME;

    if (wReq == IPP_REQ_PAUSEPRN)
        return PRINTER_CONTROL_PAUSE;

    if (wReq == IPP_REQ_CANCELPRN)
        return PRINTER_CONTROL_PURGE;

    return 0;
}


/*****************************************************************************\
* ipp_W32ToIppJobPriority (Local Routine)
*
* Maps a JOB_INFO_2 priority to an IPP priority.
*
\*****************************************************************************/
inline DWORD ipp_W32ToIppJobPriority(
    DWORD dwPriority)
{
    return dwPriority;
}


/*****************************************************************************\
* ipp_IppToW32JobPriority (Local Routine)
*
* Maps an IPP job priority to a JOB_INFO_2 priority.
*
\*****************************************************************************/
inline DWORD ipp_IppToW32JobPriority(
    DWORD dwPriority)
{
    return dwPriority;
}

/*****************************************************************************\
* ipp_W32ToIppJobSize (Local Routine)
*
* Maps a JOB_INFO_2 size to an IPP size.
*
\*****************************************************************************/
inline DWORD ipp_W32ToIppJobSize(
    DWORD dwSize)
{
    return (1023 + dwSize) / 1024;
}


/*****************************************************************************\
* ipp_IppToW32JobSize (Local Routine)
*
* Maps an IPP job size to a JOB_INFO_2 size.
*
\*****************************************************************************/
inline DWORD ipp_IppToW32JobSize(
    DWORD dwSize)
{
    return dwSize * 1024;
}


/*****************************************************************************\
* ipp_W32ToIppJobTotalPages (Local Routine)
*
* Maps a JOB_INFO_2 TotalPages to an IPP priority.
*
\*****************************************************************************/
inline DWORD ipp_W32ToIppJobTotalPages(
    DWORD dwTotalPages)
{
    return dwTotalPages;
}


/*****************************************************************************\
* ipp_IppToW32JobTotalPages (Local Routine)
*
* Maps an IPP TotalPages to a JOB_INFO_2 priority.
*
\*****************************************************************************/
inline DWORD ipp_IppToW32JobTotalPages(
    DWORD dwTotalPages)
{
    return dwTotalPages;
}


/*****************************************************************************\
* ipp_W32ToIppJobPagesPrinted (Local Routine)
*
* Maps a JOB_INFO_2 PagesPrinted to an IPP priority.
*
\*****************************************************************************/
inline DWORD ipp_W32ToIppJobPagesPrinted(
    DWORD dwPagesPrinted)
{
    return dwPagesPrinted;
}


/*****************************************************************************\
* ipp_IppToW32JobPagesPrinted (Local Routine)
*
* Maps an IPP PagesPrinted to a JOB_INFO_2 priority.
*
\*****************************************************************************/
inline DWORD ipp_IppToW32JobPagesPrinted(
    DWORD dwPagesPrinted)
{
    return dwPagesPrinted;
}


/*****************************************************************************\
* ipp_W32ToIppJobState (Local Routine)
*
* Maps a Job-Status flag to that of an IPP State flag.
*
\*****************************************************************************/
DWORD ipp_W32ToIppJobState(
    DWORD dwState)
{
    if (dwState & (JOB_STATUS_OFFLINE | JOB_STATUS_PAPEROUT | JOB_STATUS_ERROR | JOB_STATUS_USER_INTERVENTION | JOB_STATUS_BLOCKED_DEVQ))
        return IPP_JOBSTATE_PROCESSEDSTOPPED;

    if (dwState & JOB_STATUS_DELETED)
        return IPP_JOBSTATE_CANCELLED;

    if (dwState & JOB_STATUS_PAUSED)
        return IPP_JOBSTATE_PENDINGHELD;

    if (dwState & JOB_STATUS_PRINTED)
        return IPP_JOBSTATE_COMPLETED;

    if (dwState & (JOB_STATUS_PRINTING | JOB_STATUS_SPOOLING | JOB_STATUS_DELETING))
        return IPP_JOBSTATE_PROCESSING;

    if ((dwState == 0) || (dwState & JOB_STATUS_RESTART))
        return IPP_JOBSTATE_PENDING;

    return IPP_JOBSTATE_UNKNOWN;
}


/*****************************************************************************\
* ipp_IppToW32JobState (Local Routine)
*
* Maps a IPP State flag to that of a W32 Status flag.
*
\*****************************************************************************/
DWORD ipp_IppToW32JobState(
    DWORD dwState)
{
    switch (dwState) {

    case IPP_JOBSTATE_PENDINGHELD:
        return JOB_STATUS_PAUSED;

    case IPP_JOBSTATE_PROCESSEDSTOPPED:
        return JOB_STATUS_ERROR;

    case IPP_JOBSTATE_PROCESSING:
        return JOB_STATUS_PRINTING;

    case IPP_JOBSTATE_CANCELLED:
    case IPP_JOBSTATE_ABORTED:
        return JOB_STATUS_DELETING;

    case IPP_JOBSTATE_COMPLETED:
        return JOB_STATUS_PRINTED;

    default:
    case IPP_JOBSTATE_PENDING:
        return 0;
    }
}


/*****************************************************************************\
* ipp_W32ToIppPrnState (Local Routine)
*
* Maps a W32-Prn-State to Ipp-Prn-State.
*
\*****************************************************************************/
DWORD ipp_W32ToIppPrnState(
    DWORD dwState)
{
    if (dwState == 0)
        return IPP_PRNSTATE_IDLE;

    if (dwState & PRINTER_STATUS_PAUSED)
        return IPP_PRNSTATE_STOPPED;

    if (dwState & (PRINTER_STATUS_PROCESSING | PRINTER_STATUS_PRINTING))
        return IPP_PRNSTATE_PROCESSING;

    return IPP_PRNSTATE_UNKNOWN;
}


/*****************************************************************************\
* ipp_IppToW32PrnState (Local Routine)
*
* Maps a Ipp-Prn-State to W32-Prn-State.
*
\*****************************************************************************/
DWORD ipp_IppToW32PrnState(
    DWORD dwState)
{
    switch (dwState) {

    case IPP_PRNSTATE_STOPPED:
        return PRINTER_STATUS_PAUSED;

    case IPP_PRNSTATE_PROCESSING:
        return PRINTER_STATUS_PROCESSING;

    default:
    case IPP_PRNSTATE_IDLE:
        return 0;
    }
}

/*****************************************************************************\
* ipp_IppCurTime (Local Routine)
*
* Returns the base seconds printer has been alive.  This is used for the
* printer-up-time attribute.  Since our implementation can't determine the
* true printer up-time, we're going to use the relative seconds returned
* from the time() function.
*
\*****************************************************************************/
DWORD ipp_IppCurTime(VOID)
{
    time_t tTime;

    ZeroMemory(&tTime, sizeof(time_t));
    time(&tTime);

    return (DWORD) tTime;
}


/*****************************************************************************\
* ipp_IppToW32Time (Local Routine)
*
* Converts an IPP (DWORD) time to a win32 SYSTEMTIME. Note that we pass in the 
* printers' normalised start time as a straight overwrite of the first fields
* of the LPSYSTEMTIME structure. This is nasty but since the code has no concept
* of session, we have to pass it back to code that does.
*
\*****************************************************************************/
BOOL ipp_IppToW32Time(
    time_t        dwTime,
    LPSYSTEMTIME pst)
{

#if 1
    // All we do is brutally overwrite the structure with the time and send it back
    // 
    // *(time_t *)pst = dwTime;
    // Change to use CopyMemory to avoid 64bit alignment error
    //
    CopyMemory (pst, &dwTime, sizeof (time_t));

    return TRUE;

#else

    FILETIME ft;

    DosDateTimeToFileTime(HIWORD(dwTime), LOWORD(dwTime), &ft);

    return FileTimeToSystemTime(&ft, pst);


#endif

}

/*******************************************************************************
** ippConvertSystemTime 
** 
** This receives the system time (which has actually been packed with the time
** retrieved from the printers) and converts it to the Real System time based
** on the original T0 of the printer
**
******************************************************************************/
BOOL WebIppConvertSystemTime(
    IN OUT LPSYSTEMTIME pST,
    IN     time_t       dwPrinterT0) {

    // First we need to get the time stored in the LPSYSTEMTIME structure
    // time_t      dwSubmitTime = *(time_t *)pST;
    // Use CopyMemory to avoid alignment error in 64bit machine.
    time_t      dwSubmitTime;
     
    CopyMemory (&dwSubmitTime, pST, sizeof (time_t));


    SYSTEMTIME TmpST;

    // If the submitted time is zero, it means that either the job was submitted before
    // the printer was rebooted, or, the printer does not support the submitted time of the
    // job

    if (!dwSubmitTime) {
        ZeroMemory( &pST, sizeof(LPSYSTEMTIME));
    } else {
        // Next we have to normalise the time to that of the PrinterT0
        dwSubmitTime += dwPrinterT0;

        tm *ptm;


        // Convert the time into a struct and return the SYSTEMTIME
        // structure.
        //
        ptm = gmtime(&dwSubmitTime);

        if (ptm) {
    
            TmpST.wYear      = (WORD)(1900 + ptm->tm_year);
            TmpST.wMonth     = (WORD)(ptm->tm_mon + 1);
            TmpST.wDayOfWeek = (WORD)ptm->tm_wday;
            TmpST.wDay       = (WORD)ptm->tm_mday;
            TmpST.wHour      = (WORD)ptm->tm_hour;
            TmpST.wMinute    = (WORD)ptm->tm_min;
            TmpST.wSecond    = (WORD)ptm->tm_sec;
            TmpST.wMilliseconds = 0;
    
            CopyMemory (pST, &TmpST, sizeof (SYSTEMTIME));
        }
        else
            ZeroMemory( &pST, sizeof(LPSYSTEMTIME));


    }
    
    return TRUE;

}



/*****************************************************************************\
* ipp_W32ToIppTime (Local Routine)
*
* Converts a Win32 SYSTEMTIME to UCT. 
*
\*****************************************************************************/
DWORD ipp_W32ToIppTime(
    LPSYSTEMTIME pst)               // We pass in the T0 for the system in here
{

#if 1

    tm   tmCvt;
    struct _timeb tiTimeb;

    _ftime(&tiTimeb);               // We obtain the time zone difference from here,
                                    // mktime assumes local time in doing the conversion

    ZeroMemory(&tmCvt, sizeof(tm));
    tmCvt.tm_sec   = (int)(short)pst->wSecond;
    tmCvt.tm_min   = (int)(short)pst->wMinute;
    tmCvt.tm_hour  = (int)(short)pst->wHour;
    tmCvt.tm_mday  = (int)(short)pst->wDay;
    tmCvt.tm_mon   = (int)(short)(pst->wMonth - 1);
    tmCvt.tm_year  = ((int)(short)pst->wYear - 1900);
    tmCvt.tm_wday  = (int)(short)pst->wDayOfWeek;
                     
    INT iUCT = (INT)mktime(&tmCvt);

    iUCT -= tiTimeb.timezone * 60;      // Normalise for timezone difference

     return (DWORD)iUCT;

#else

    WORD     wDate;
    WORD     wTime;
    FILETIME ft;

    SystemTimeToFileTime(pst, &ft);

    FileTimeToDosDateTime(&ft, &wDate, &wTime);

    return (DWORD)MAKELONG(wTime, wDate);

#endif

}


/*****************************************************************************\
* ipp_JidFromUri
*
* Returns a job-id from a job-uri string.
*
\*****************************************************************************/
DWORD ipp_JidFromUri(
   LPTSTR lpszUri)
{
    LPTSTR lpszPtr;
    DWORD  jid = 0;

    if (lpszPtr = webFindRChar(lpszUri, TEXT('=')))
        jid = webAtoI(++lpszPtr);

    return jid;
}


/*****************************************************************************\
* ipp_PackStrings
*
* This routine packs strings to the end of a buffer.  This is used for
* building a JOB_INFO_2 list from IPP information.
*
\*****************************************************************************/
LPBYTE ipp_PackStrings(
   LPTSTR* ppszSrc,
   LPBYTE  pbDst,
   LPDWORD pdwDstOffsets,
   LPBYTE  pbEnd)
{
    DWORD cbStr;


    while (*pdwDstOffsets != (DWORD)-1) {
        // We fill in the strings from the end of the structure and fill in the 
        // structure forwards, if our string pointer is ever less than the address 
        // we are copying into, the initial block allocated was too small

        if (*ppszSrc) {

            cbStr  = webStrSize(*ppszSrc);
            pbEnd -= cbStr;

            CopyMemory(pbEnd, *ppszSrc, cbStr);

            LPTSTR *strWriteLoc = (LPTSTR *)(pbDst + *pdwDstOffsets);

            WEB_IPP_ASSERT( (LPBYTE)pbEnd >= (LPBYTE)strWriteLoc );
                        
            *strWriteLoc = (LPTSTR)pbEnd;

        } else {

            *(LPTSTR *)(pbDst + *pdwDstOffsets) = TEXT('\0');
        }

        ppszSrc++;
        pdwDstOffsets++;
    }

    return pbEnd;
}


/*****************************************************************************\
* ipp_NextVal (Local Routine)
*
* Returns next value-field in a tag-attribute.
*
* Parameters:
* ----------
* lpIppHdr - Pointer to the IPP-Stream.
* lpcbIdx  - Current Byte offset into the IPP-Stream.
* cbIppHdr - Size of the IPP-Stream.
*
\*****************************************************************************/
LPBYTE ipp_NextVal(
    LPBYTE  lpIppHdr,
    LPDWORD lpcbIdx,
    DWORD   cbIppHdr)
{
    DWORD cbIdx;
    DWORD cbSize;


    // The (cbIdx) is positioned at the location where a length
    // is to be read.
    //
    cbIdx = *lpcbIdx;


    // Make sure we have enough to read a WORD value.
    //
    if ((cbIdx + IPP_SIZEOFTAG) >= cbIppHdr)
        return NULL;


    // Get the name-length of the attribute.  Adjust our
    // offset by this amount and add size of length-field to
    // position to the next attribute-length.
    //
    cbSize  = (DWORD)ipp_ReadWord(lpIppHdr, cbIdx);
    cbIdx  += (cbSize + IPP_SIZEOFLEN);

    if (cbIdx >= cbIppHdr)
        return NULL;

    *lpcbIdx = cbIdx;

    return lpIppHdr + cbIdx;
}


/*****************************************************************************\
* ipp_NextTag (Local Routine)
*
* Returns a pointer to the next tag in the header.  If this routine returns
* NULL, then we do not have enough data to advance to the next-tag.
*
* Parameters:
* ----------
* lpTag   - Pointer to the current-tag postion.
* lpcbIdx - Bytes offset from the header.
* cbHdr   - Size of the header-stream we're working with.
*
\*****************************************************************************/
LPBYTE ipp_NextTag(
    LPBYTE  lpIppHdr,
    LPDWORD lpcbIdx,
    DWORD   cbIppHdr)
{
    BYTE  bTag;
    DWORD cbIdx;
    DWORD cbSize;


    // Out current byte-offset is at a tag.  Grab the tag, and advance
    // our index past it to proced to get past the possible attribute.
    //
    cbIdx  = *lpcbIdx;
    bTag   = ipp_ReadByte(lpIppHdr, cbIdx);
    cbIdx += IPP_SIZEOFTAG;


    // If our tag is a deliminator, then we need only advance to the
    // next byte where the next tag should be.
    //
    if (IS_TAG_DELIMITER(bTag)) {

        // Make sure we have enough bytes to return an offset
        // to the next tag.
        //
        if (cbIdx >= cbIppHdr)
            return NULL;

        *lpcbIdx = cbIdx;

        return lpIppHdr + cbIdx;
    }


    // Otherwise, we are currently at an attribute-tag.  We need to
    // calculate bytes offset to the next tag.
    //
    if (IS_TAG_ATTRIBUTE(bTag)) {

        // This logic calculates the byte-offsets to the
        // value-tags.  We need to do two value adjustments
        // since there is both a (name) and a (value) component
        // to an attribute.
        //
        if (ipp_NextVal(lpIppHdr, &cbIdx, cbIppHdr)) {

            // This last adjustment will return the position
            // of the next tag.
            //
            if (ipp_NextVal(lpIppHdr, &cbIdx, cbIppHdr)) {

                *lpcbIdx = cbIdx;

                return lpIppHdr + cbIdx;
            }
        }
    }

    return NULL;
}


/*****************************************************************************\
* ipp_RelAttr (Local Routine)
*
* Release (Free) the attribute block.
*
\*****************************************************************************/
BOOL ipp_RelAttr(
    LPIPPATTR lpAttr)
{
    if (lpAttr) {

        webFree(lpAttr->lpszName);
        webFree(lpAttr->lpValue);
        webFree(lpAttr);

        return TRUE;
    }

    return FALSE;
}


/*****************************************************************************\
* ipp_GetAttr (Local Routine)
*
* Returns an attribute in a structured-from.
*
\*****************************************************************************/
LPIPPATTR ipp_GetAttr(
    LPBYTE   lpTag,
    DWORD    cbIdx,
    LPIPPOBJ lpObj)
{
    LPIPPATTR lpAttr = NULL;
    WORD      wIdx;
    BYTE      bTag   = ipp_ReadByte(lpTag, 0);
    DWORD     cbSize;


    if (IS_TAG_ATTRIBUTE(bTag)) {

        if (lpAttr = (LPIPPATTR)webAlloc(sizeof(IPPATTR))) {

            __try {

                lpAttr->bTag    = bTag;
                lpTag          += IPP_SIZEOFTAG;

                lpAttr->cbName  = ipp_ReadWord(lpTag, 0);
                lpTag          += IPP_SIZEOFLEN;


                if (lpAttr->cbName) {

                    lpAttr->lpszName  = webMBtoTC(CP_UTF8, (LPSTR)lpTag, lpAttr->cbName);
                    lpTag            += lpAttr->cbName;
                }

#if 1
    // hack. This is added to support name-with-language attributes.  To
    // do this temporarily, this code will work but ignore the language
    // part of the attribute.  In the future, we can look at dealing with
    // the language appropriately.
    //
    // 15-Mar-1999 : ChrisWil (HP).
    //

                if (IS_TAG_COMPOUND(bTag)) {

                    if (ipp_ReadWord(lpTag, 0)) {

                        lpTag += IPP_SIZEOFLEN;
                        lpTag += ipp_ReadWord(lpTag, 0);
                        lpTag += IPP_SIZEOFLEN;
                    }
                }
#endif



                lpAttr->cbValue  = ipp_ReadWord(lpTag, 0);
                lpTag           += IPP_SIZEOFLEN;


                // If there's a value, then make sure that the size doesn't
                // exceed our IPP-Stream.
                //
                if (lpAttr->cbValue && (lpAttr->cbValue < (lpObj->cbIppHdr - cbIdx))) {

                    // Convert the value to the appropriate format.  This
                    // block currently makes the assumption that all strings
                    // are dealt with as Octet-Strings.  When this parser
                    // supports other character-sets, then the conversion
                    // for Character-Strings can utilize a different codepage.
                    //
                    if (IS_TAG_OCTSTR(lpAttr->bTag)) {

                        lpAttr->lpValue = (LPVOID)webMBtoTC(CP_UTF8, (LPSTR)lpTag, lpAttr->cbValue);

                    } else if (IS_TAG_CHARSETSTR(lpAttr->bTag)) {

                        lpAttr->lpValue = (LPVOID)webMBtoTC(lpObj->uCPRcv, (LPSTR)lpTag, lpAttr->cbValue);

                    } else if (IS_TAG_CHRSTR(lpAttr->bTag)) {

                        lpAttr->lpValue = (LPVOID)webMBtoTC(CP_ACP, (LPSTR)lpTag, lpAttr->cbValue);

                    } else {

                        if (lpAttr->cbValue <= sizeof(DWORD))
                            lpAttr->lpValue = (LPVOID)webAlloc(sizeof(DWORD));
                        else
                            lpAttr->lpValue = (LPVOID)webAlloc(lpAttr->cbValue);

                        if (lpAttr->lpValue) {

                            if (lpAttr->cbValue == sizeof(BYTE))
                                *(LPDWORD)(lpAttr->lpValue) = (DWORD)ipp_ReadByte(lpTag, 0);
                            else if (lpAttr->cbValue == sizeof(WORD))
                                *(LPDWORD)(lpAttr->lpValue) = (DWORD)ipp_ReadWord(lpTag, 0);
                            else if (lpAttr->cbValue == sizeof(DWORD))
                                *(LPDWORD)(lpAttr->lpValue) = ipp_ReadDWord(lpTag, 0);
                            else
                                CopyMemory((LPVOID)lpAttr->lpValue, (LPVOID)lpTag, lpAttr->cbValue);
                        }
                    }
                }

            } __except (1) {

                ipp_RelAttr(lpAttr);

                lpAttr = NULL;
            }
        }
    }

    return lpAttr;
}


/*****************************************************************************\
* ipp_WriteAttr (Local Routine)
*
* Write out the attribute.  If NULL is passed in as the (lplpIppPtr), then
* this routine returns the size necessary to write the info.
*
\*****************************************************************************/
DWORD ipp_WriteAttr(
    LPBYTE* lplpIppPtr,
    BYTE    bTag,
    DWORD   cbName,
    LPVOID  lpName,
    DWORD   cbValue,
    LPVOID  lpValue)
{
    DWORD cbSize;


    // Set the size that this attribute occupies.
    //
    cbSize = (cbName + cbValue + IPP_SIZEOFTAG + IPP_SIZEOFLEN + IPP_SIZEOFLEN);


    // Write out the attribute to the buffer (if available).
    //
    if (lplpIppPtr) {

        ipp_WriteByte(lplpIppPtr, bTag);

        if (cbName) {

            ipp_WriteWord(lplpIppPtr, (WORD)cbName);
            ipp_WriteData(lplpIppPtr, (LPVOID)lpName, cbName);

        } else {

            ipp_WriteWord(lplpIppPtr, (WORD)cbName);
        }

        ipp_WriteWord(lplpIppPtr, (WORD)cbValue);

        switch (bTag) {
        case IPP_TAG_INT_INTEGER:
        case IPP_TAG_INT_ENUM:
            ipp_WriteDWord(lplpIppPtr, * (DWORD*)lpValue);
            break;
        case IPP_TAG_INT_BOOLEAN:
            ipp_WriteByte(lplpIppPtr, * (BYTE*)lpValue);
            break;
        default:
            ipp_WriteData(lplpIppPtr, (LPVOID)lpValue , cbValue);
            break;
        }
    }

    return cbSize;
}


/*****************************************************************************\
* ipp_SizeAttr (Local Routine)
*
* Return the size necessary to store the attribute.
*
\*****************************************************************************/
inline DWORD ipp_SizeAttr(
    DWORD cbName,
    DWORD cbValue)
{
    return ipp_WriteAttr(NULL, 0, cbName, NULL, cbValue, NULL);
}


/*****************************************************************************\
* ipp_WriteHead (Local Routine)
*
* Write out our "generic" type header.  This includes the character-set
* that we support.
*
\*****************************************************************************/
DWORD ipp_WriteHead(
    LPBYTE* lplpIppPtr,
    WORD    wReq,
    DWORD   idReq,
    UINT    cpReq)
{
    DWORD   cbNamCS;
    DWORD   cbValCS;
    DWORD   cbNamNL;
    DWORD   cbValNL;
    LPCTSTR lpszCS;
    LPSTR   lputfNamCS;
    LPSTR   lputfValCS;
    LPSTR   lputfNamNL;
    LPSTR   lputfValNL;
    DWORD   cbSize = 0;


    // Encode in the specified character-set.
    //
    lpszCS = ((cpReq == CP_ACP) ? s_szUsAscii : s_szUtf8);


    lputfNamCS = webTCtoMB(CP_ACP, s_szCharSet        , &cbNamCS);
    lputfValCS = webTCtoMB(CP_ACP, lpszCS             , &cbValCS);
    lputfNamNL = webTCtoMB(CP_ACP, s_szNaturalLanguage, &cbNamNL);
    lputfValNL = webTCtoMB(CP_ACP, s_szEnUS           , &cbValNL);


    if (lputfNamCS && lputfValCS && lputfNamNL && lputfValNL) {

        // Calculate the size necessary to hold the IPP-Header.
        //
        cbSize = IPP_SIZEOFHDR                   +   // Version-Request.
                 IPP_SIZEOFTAG                   +   // Operation Tag
                 ipp_SizeAttr(cbNamCS, cbValCS)  +   // CharSet Attribute.
                 ipp_SizeAttr(cbNamNL, cbValNL);     // NaturalLang Attribute.


        if (lplpIppPtr) {

            ipp_WriteWord(lplpIppPtr, IPP_VERSION);
            ipp_WriteWord(lplpIppPtr, wReq);
            ipp_WriteDWord(lplpIppPtr, idReq);
            ipp_WriteByte(lplpIppPtr, IPP_TAG_DEL_OPERATION);
            ipp_WriteAttr(lplpIppPtr, IPP_TAG_CHR_CHARSET, cbNamCS, lputfNamCS, cbValCS, lputfValCS);
            ipp_WriteAttr(lplpIppPtr, IPP_TAG_CHR_NATURAL, cbNamNL, lputfNamNL, cbValNL, lputfValNL);
        }
    }

    webFree(lputfValCS);
    webFree(lputfNamCS);
    webFree(lputfValNL);
    webFree(lputfNamNL);

    return cbSize;
}


/*****************************************************************************\
* ipp_SizeHdr (Local Routine)
*
* Return the size necessary to store the header and operation tags.
*
\*****************************************************************************/
inline DWORD ipp_SizeHdr(
    UINT cpReq)
{
    return ipp_WriteHead(NULL, 0, 0, cpReq);
}


/*****************************************************************************\
* ipp_ValDocFormat (Local Routine)
*
* Validates the document-format.
*
\*****************************************************************************/
BOOL ipp_ValDocFormat(
    LPCTSTR lpszFmt)
{
    DWORD idx;
    DWORD cCnt;

    static PCTSTR s_szFmts[] = {

        s_szMimeTxtHtml   ,
        s_szMimeTxtPlain  ,
        s_szMimePostScript,
        s_szMimePCL       ,
        s_szMimeOctStream
    };

    cCnt = sizeof(s_szFmts) / sizeof(s_szFmts[0]);

    for (idx = 0; idx < cCnt; idx++) {

        if (lstrcmpi(lpszFmt, s_szFmts[idx]) == 0)
            return TRUE;
    }

    return FALSE;
}


/*****************************************************************************\
* ipp_ValAtrFidelity (Local Routine)
*
* Validates the attribute-fidelity.
*
\*****************************************************************************/
BOOL ipp_ValAtrFidelity(
    DWORD  dwVal,
    LPBOOL lpbFidelity)
{
    if (dwVal == 1) {

        *lpbFidelity = TRUE;

    } else if (dwVal == 0) {

        *lpbFidelity = FALSE;

    } else {

        return FALSE;
    }

    return TRUE;
}


/*****************************************************************************\
* ipp_ValWhichJobs (Local Routine)
*
* Validates the which-jobs.
*
\*****************************************************************************/
BOOL ipp_ValWhichJobs(
    PDWORD  pfReq,
    LPCTSTR lpszWJ)
{
    DWORD idx;
    DWORD cCnt;

    static FLGSTR s_fsVal[] = {

        RA_JOBSCOMPLETED  , s_szCompleted   ,
        RA_JOBSUNCOMPLETED, s_szNotCompleted
    };

    cCnt = sizeof(s_fsVal) / sizeof(s_fsVal[0]);

    for (idx = 0; idx < cCnt; idx++) {

        if (lstrcmpi(lpszWJ, s_fsVal[idx].pszStr) == 0) {

            x_SetReq(pfReq, s_fsVal[idx].fFlag);

            return TRUE;
        }
    }

    return FALSE;
}


/*****************************************************************************\
* ipp_GetRspSta (Local Routine)
*
* Returns the response-code and any status messages if failure.
*
\*****************************************************************************/
WORD ipp_GetRspSta(
    WORD    wRsp,
    UINT    cpReq,
    LPSTR*  lplputfNamSta,
    LPDWORD lpcbNamSta,
    LPSTR*  lplputfValSta,
    LPDWORD lpcbValSta)
{
    DWORD idx;
    DWORD cErrors;


    *lplputfNamSta = NULL;
    *lplputfValSta = NULL;
    *lpcbNamSta    = 0;
    *lpcbValSta    = 0;


    if (SUCCESS_RANGE(wRsp) == FALSE) {

        // Get the status-name.
        //
        *lplputfNamSta = webTCtoMB(CP_ACP, s_szStaMsg, lpcbNamSta);


        // Get the string we will be using to encode the error.
        //
        cErrors = sizeof(s_LEIpp) / sizeof(s_LEIpp[0]);

        for (idx = 0; idx < cErrors; idx++) {

            if (wRsp == s_LEIpp[idx].wRsp) {

                *lplputfValSta = webTCtoMB(cpReq, s_LEIpp[idx].pszStr, lpcbValSta);

                break;
            }
        }
    }

    return TRUE;
}


/*****************************************************************************\
* ipp_CvtW32Val (Local Routine - Server)
*
* Converts a value to the appropriate ipp-value.
*
\*****************************************************************************/
VOID ipp_CvtW32Val(
    LPCTSTR lpszName,
    LPVOID  lpvVal)
{
    if (lstrcmpi(lpszName, s_szPrtState) == 0) {

        *(LPDWORD)lpvVal = ipp_W32ToIppPrnState(*(LPDWORD)lpvVal);

    } else if (lstrcmpi(lpszName, s_szJobState) == 0) {

        *(LPDWORD)lpvVal = ipp_W32ToIppJobState(*(LPDWORD)lpvVal);

    } else if (lstrcmpi(lpszName, s_szJobKOctets) == 0) {

        *(LPDWORD)lpvVal = ipp_W32ToIppJobSize(*(LPDWORD)lpvVal);

    } else if (lstrcmpi(lpszName, s_szJobKOctetsProcess) == 0) {

        *(LPDWORD)lpvVal = ipp_W32ToIppJobSize(*(LPDWORD)lpvVal);
    }
}


/*****************************************************************************\
* ipp_AllocUnsVals
*
* Allocates an array of ipp-values used to write to a stream.
*
\*****************************************************************************/
LPIPPATTRY ipp_AllocUnsVals(
    PWEBLST pwlUns,
    LPDWORD pcUns,
    LPDWORD lpcbAtrs)
{
    DWORD      idx;
    DWORD      cUns;
    DWORD      cbUns;
    PCTSTR     pszStr;
    LPIPPATTRY pUns = NULL;


    *pcUns = 0;

    if (pwlUns && (cUns = pwlUns->Count())) {

        if (pUns = (LPIPPATTRY)webAlloc(cUns * sizeof(IPPATTRY))) {

            *lpcbAtrs += IPP_SIZEOFTAG;
            *pcUns     = cUns;


            // Loop through each item and convert for addition to stream.
            //
            pwlUns->Reset();

            for (idx = 0; idx < cUns; idx++) {

                if (pszStr = pwlUns->Get()) {

                    pUns[idx].pszNam = webTCtoMB(CP_ACP, pszStr         , &pUns[idx].cbNam);


                    // Unsupported-values should be null.
                    //
                    pUns[idx].pszVal = NULL;
                    pUns[idx].cbVal  = 0;

                    *lpcbAtrs += ipp_SizeAttr(pUns[idx].cbNam, pUns[idx].cbVal);
                }

                pwlUns->Next();
            }
        }
    }

    return pUns;
}


/*****************************************************************************\
* ipp_AllocAtrVals
*
* Allocates an array of ipp-values used to write to a stream.
*
\*****************************************************************************/
LPIPPATTRY ipp_AllocAtrVals(
    WORD       wReq,
    PDWORD     pfReq,
    UINT       cpReq,
    LPBYTE     lpbData,
    LPIPPATTRX pRsp,
    DWORD      cAtr,
    LPDWORD    lpcbAtrs)
{
    BOOL       bRet = FALSE;
    BOOL       fWr;
    DWORD      idx;
    BOOL       bDel;
    LPVOID     lpvVal;
    LPIPPATTRY pAtr = NULL;


    if (cAtr && (pAtr = (LPIPPATTRY)webAlloc(cAtr * sizeof(IPPATTRY)))) {

        // Allocate the attribute-values.
        //
        for (idx = 0, fWr = TRUE; idx < cAtr; idx++) {

            bDel = FALSE;

            // Build the attribute-name.
            //
            if (pRsp[idx].pszNam) {

                pAtr[idx].pszNam = webTCtoMB(CP_ACP, pRsp[idx].pszNam, &pAtr[idx].cbNam);

                // If the value is absolute, then assign the
                // attribute directly.  Otherwise, it's an offset into
                // the return-structure, and as such, must be indirectly
                // built.
                //
                if (pRsp[idx].nVal == IPP_ATR_ABSOLUTE) {

                    // Special-case the printer-up-time to reflect the number
                    // of seconds it's been up and running.  Since we can't
                    // determine the time the printer's been up, use the time
                    // windows has been started.
                    //
                    if (lstrcmpi(pRsp[idx].pszNam, s_szPrtUpTime) == 0)
                        pRsp[idx].pvVal = (LPVOID)ULongToPtr (ipp_IppCurTime());
                    else
                        lpvVal = (LPVOID)&pRsp[idx].pvVal;

                } else {

                    lpvVal = (LPVOID)(lpbData + (DWORD_PTR)pRsp[idx].pvVal);
                }

                fWr = x_ChkReq(pfReq, pRsp[idx].fReq);
            }


            // Add it to the stream if it is a request or if
            // the response requires it.
            //
            if (fWr || !(wReq & IPP_RESPONSE)) {

                // If the value is absolute, then assign the
                // attribute directly.  Otherwise, it's an offset into
                // the return-structure, and as such, must be indirectly
                // built.
                //
                if (pRsp[idx].nVal == IPP_ATR_ABSOLUTE)
                    lpvVal = (LPVOID)&pRsp[idx].pvVal;
                else
                    lpvVal = (LPVOID)(lpbData + (DWORD_PTR)pRsp[idx].pvVal);


                // Build the attribute-value.
                //
                if (IS_TAG_DELIMITER(pRsp[idx].bTag)) {

                    bDel = TRUE;

                } else if (IS_TAG_OCTSTR(pRsp[idx].bTag)) {

                    pAtr[idx].pszVal = webTCtoMB(CP_UTF8, *(LPTSTR*)lpvVal, &pAtr[idx].cbVal);

                } else if (IS_TAG_CHARSETSTR(pRsp[idx].bTag)) {

                    pAtr[idx].pszVal = webTCtoMB(cpReq, *(LPTSTR*)lpvVal, &pAtr[idx].cbVal);

                } else if (IS_TAG_CHRSTR(pRsp[idx].bTag)) {

                    pAtr[idx].pszVal = webTCtoMB(CP_ACP, *(LPTSTR*)lpvVal, &pAtr[idx].cbVal);

                } else {

                    pAtr[idx].pszVal = (LPSTR)webAlloc(sizeof(DWORD));

                    if ( !pAtr[idx].pszVal )
                        goto Cleanup;

                    if (pRsp[idx].bTag == IPP_TAG_INT_BOOLEAN) {

                        pAtr[idx].cbVal = IPP_SIZEOFBYTE;

                    } else {

                        pAtr[idx].cbVal = IPP_SIZEOFINT;
                    }

                    CopyMemory(pAtr[idx].pszVal, lpvVal, pAtr[idx].cbVal);


                    // Do we need to convert the value.
                    //
                    if (pRsp[idx].nVal == IPP_ATR_OFFSETCONV)
                        ipp_CvtW32Val(pRsp[idx].pszNam, (LPVOID)pAtr[idx].pszVal);
                }


                // If this is a delimiter then it only occupies 1 byte.
                //
                if (bDel)
                    *lpcbAtrs += IPP_SIZEOFTAG;
                else
                    *lpcbAtrs += ipp_SizeAttr(pAtr[idx].cbNam, pAtr[idx].cbVal);
            }
        }
    }

    bRet = TRUE;

Cleanup:
    if ( !bRet && pAtr ) {

        for (idx = 0 ; idx < cAtr; ++idx)
            if ( pAtr[idx].pszVal )
                webFree(pAtr[idx].pszVal);

        webFree(pAtr);
        pAtr = NULL;
    }

    return pAtr;
}


/*****************************************************************************\
* ipp_WriteUnsVals
*
* Writes an array of ipp-values to an ipp-stream.
*
\*****************************************************************************/
BOOL ipp_WriteUnsVals(
    LPBYTE*    lplpIppPtr,
    LPIPPATTRY pUns,
    DWORD      cUns)
{
    DWORD idx;


    if (pUns && cUns) {

        ipp_WriteByte(lplpIppPtr, IPP_TAG_DEL_UNSUPPORTED);


        // Unsupported values should be null.
        //
        for (idx = 0; idx < cUns; idx++)
            ipp_WriteAttr(lplpIppPtr, IPP_TAG_OUT_UNSUPPORTED, pUns[idx].cbNam, pUns[idx].pszNam, 0, NULL);
    }

    return TRUE;
}


/*****************************************************************************\
* ipp_WriteAtrVals
*
* Writes an array of ipp-values to an ipp-stream.
*
\*****************************************************************************/
BOOL ipp_WriteAtrVals(
    WORD       wReq,
    PDWORD     pfReq,
    LPBYTE*    lplpIppPtr,
    LPIPPATTRX pRsp,
    LPIPPATTRY pAtr,
    DWORD      cAtr)
{
    BOOL  fWr;
    DWORD idx;


    for (idx = 0, fWr = TRUE; idx < cAtr; idx++) {

        // If this item has a name-tag, then determine if the
        // originator wants it in the stream.
        //
        if (pRsp[idx].pszNam)
            fWr = x_ChkReq(pfReq, pRsp[idx].fReq);


        // Only write out the item if it is requested, or if
        // it is a request-operation.
        //
        if (fWr || !(wReq & IPP_RESPONSE)) {

            if (pRsp[idx].nVal == IPP_ATR_TAG)
                ipp_WriteByte(lplpIppPtr, pRsp[idx].bTag);
            else
                ipp_WriteAttr(lplpIppPtr, pRsp[idx].bTag, pAtr[idx].cbNam, pAtr[idx].pszNam, pAtr[idx].cbVal, pAtr[idx].pszVal);
        }
    }

    return TRUE;
}


/*****************************************************************************\
* ipp_FreeAtrVals
*
* Frees array of attribute values.
*
\*****************************************************************************/
VOID ipp_FreeAtrVals(
    LPIPPATTRY pAtr,
    DWORD      cAtr)
{
    DWORD idx;


    // Free up the attribute-values.
    //
    for (idx = 0; idx < cAtr; idx++) {

        webFree(pAtr[idx].pszNam);
        webFree(pAtr[idx].pszVal);
    }

    webFree(pAtr);
}


/*****************************************************************************\
* ipp_FreeIPPJI2 (Local Routine)
*
* Frees up the IPPJI2 memory.
*
\*****************************************************************************/
VOID ipp_FreeIPPJI2(
    LPIPPJI2 lpji)
{
    DWORD cCnt;
    DWORD idx;


    // Free JI2-Data.
    //
    cCnt = ((sizeof(s_JI2Off) / sizeof(s_JI2Off[0])) - 1);

    for (idx = 0; idx < cCnt; idx++)
        webFree(*(LPBYTE *)(((LPBYTE)&lpji->ji2) + s_JI2Off[idx]));


    // Free IPP-Data.
    //
    cCnt = ((sizeof(s_IPJOff) / sizeof(s_IPJOff[0])) - 1);

    for (idx = 0; idx < cCnt; idx++)
        webFree(*(LPBYTE *)(((LPBYTE)&lpji->ipp) + s_IPJOff[idx]));
}


/*****************************************************************************\
* ipp_FreeIPPPI2 (Local Routine)
*
* Frees up the IPPPI2 memory.
*
\*****************************************************************************/
VOID ipp_FreeIPPPI2(
    LPIPPPI2 lppi)
{
    DWORD cCnt;
    DWORD idx;


    // Free PI2-Data.
    //
    cCnt = ((sizeof(s_PI2Off) / sizeof(s_PI2Off[0])) - 1);

    for (idx = 0; idx < cCnt; idx++)
        webFree(*(LPBYTE *)(((LPBYTE)&lppi->pi2) + s_PI2Off[idx]));


    // Free IPP-Data.
    //
    cCnt = ((sizeof(s_IPPOff) / sizeof(s_IPPOff[0])) - 1);

    for (idx = 0; idx < cCnt; idx++)
        webFree(*(LPBYTE *)(((LPBYTE)&lppi->ipp) + s_IPPOff[idx]));

}


/*****************************************************************************\
* ipp_GetIPPJI2 (Local Routine)
*
* Returns the info for a complete job in the IPP stream.  We essentially
* loop through the attributes looking for the next IPP_TAG_DEL_JOB to
* signify another job-info-item.
*
\*****************************************************************************/
LPBYTE ipp_GetIPPJI2(
    LPBYTE   lpbTag,
    LPIPPJI2 lpji,
    LPDWORD  lpcbIdx,
    LPIPPOBJ lpObj)
{
    LPIPPATTR lpAttr;
    BYTE      bTag;
    DWORD     idx;
    DWORD     cAtr;
    BOOL      bReq;
    BOOL      bFound;
    DWORD     fAtr[IPPOBJ_MASK_SIZE];
    BOOL      bFid = FALSE;
    BOOL      bAtr = FALSE;
    BOOL      bEnu = FALSE;


    x_SetReq(fAtr, IPP_REQALL);

    bTag = ipp_ReadByte(lpbTag, 0);
    bReq = ((lpObj->wReq & IPP_RESPONSE) ? FALSE : TRUE);
    bEnu = (BOOL)(lpObj->wReq & IPP_REQ_ENUJOB);

    while ((!bEnu || (bTag != IPP_TAG_DEL_JOB)) && (bTag != IPP_TAG_DEL_DATA)) {

        if (lpAttr = ipp_GetAttr(lpbTag, *lpcbIdx, lpObj)) {

            if (lpAttr->lpszName && lpAttr->lpValue) {

                if (lstrcmpi(lpAttr->lpszName, s_szCharSet) == 0) {

                    if (lpAttr->cbValue > SIZE_CHARSET)
                        lpObj->wError = IPPRSP_ERROR_409;

                } else if (lstrcmpi(lpAttr->lpszName, s_szNaturalLanguage) == 0) {

                    if (lpAttr->cbValue > SIZE_NATLANG)
                        lpObj->wError = IPPRSP_ERROR_409;

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobId) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ji2.JobId = *(LPDWORD)lpAttr->lpValue;

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobLimit) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ipp.cJobs = *(LPDWORD)lpAttr->lpValue;

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobState) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ji2.Status = ipp_IppToW32JobState(*(LPDWORD)lpAttr->lpValue);

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobPri) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ji2.Priority = ipp_IppToW32JobPriority(*(LPDWORD)lpAttr->lpValue);

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobKOctets) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ji2.Size = ipp_IppToW32JobSize(*(LPDWORD)lpAttr->lpValue);

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobKOctetsProcess) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ji2.Size = ipp_IppToW32JobSize(*(LPDWORD)lpAttr->lpValue);

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobSheets) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ji2.TotalPages = ipp_IppToW32JobTotalPages(*(LPDWORD)lpAttr->lpValue);

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobSheetsCompleted) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lpji->ji2.PagesPrinted = ipp_IppToW32JobPagesPrinted(*(LPDWORD)lpAttr->lpValue);

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobName) == 0) {

                    if (lpAttr->cbValue > SIZE_NAME) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lpji->ji2.pDocument);
                        lpji->ji2.pDocument = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szDocName) == 0) {

                    if (lpAttr->cbValue > SIZE_NAME) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lpji->ji2.pDocument);
                        lpji->ji2.pDocument = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobOrgUser) == 0) {

                    if (lpAttr->cbValue > SIZE_NAME) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lpji->ji2.pUserName);
                        lpji->ji2.pUserName = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobReqUser) == 0) {

                    if (lpAttr->cbValue > SIZE_NAME) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lpji->ji2.pUserName);
                        lpji->ji2.pUserName = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobUri) == 0) {

                    if (lpAttr->cbValue > SIZE_URI) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lpji->ipp.pJobUri);
                        lpji->ipp.pJobUri = webAllocStr((LPTSTR)lpAttr->lpValue);

                        if (bReq && lpji->ipp.pJobUri)
                            lpji->ji2.JobId = ipp_JidFromUri(lpji->ipp.pJobUri);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobPrtUri) == 0) {

                    if (lpAttr->cbValue > SIZE_URI) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lpji->ipp.pPrnUri);
                        lpji->ipp.pPrnUri = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szPrtUri) == 0) {

                    if (lpAttr->cbValue > SIZE_URI) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lpji->ipp.pPrnUri);
                        lpji->ipp.pPrnUri = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szDocFormat) == 0) {

                    if (lpAttr->cbValue > SIZE_MIMEMEDIA) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        if (ipp_ValDocFormat((PCTSTR)lpAttr->lpValue) == FALSE)
                            lpObj->wError = IPPRSP_ERROR_40A;
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szAtrFidelity) == 0) {

                    if (lpAttr->cbValue != SIZE_BOOLEAN) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        if (ipp_ValAtrFidelity(*(LPDWORD)lpAttr->lpValue, &bFid) == FALSE)
                            lpObj->wError = IPPRSP_ERROR_400;
                        else
                            lpObj->fState |= (bFid ? IPPFLG_USEFIDELITY : 0);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szWhichJobs) == 0) {

                    if (lpAttr->cbValue > SIZE_KEYWORD) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        if (ipp_ValWhichJobs(fAtr, (PCTSTR)lpAttr->lpValue) == FALSE)
                            lpObj->wError = IPPRSP_ERROR_40B;
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szTimeAtCreation) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        ipp_IppToW32Time(*(LPDWORD)lpAttr->lpValue, &lpji->ji2.Submitted);
                    }

                } else if (bReq && (lstrcmpi(lpAttr->lpszName, s_szReqAttr) == 0)) {

                    bAtr = TRUE;

                    x_SetReq(fAtr, IPP_REQCLEAR);

                    goto ProcessVal;

                } else {

                    lpObj->pwlUns->Add(lpAttr->lpszName);
                }

            } else if (bAtr && lpAttr->lpValue) {

ProcessVal:
                if (lpAttr->cbValue > SIZE_KEYWORD) {

                    lpObj->wError = IPPRSP_ERROR_409;

                } else {

                    if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_szAll) == 0) {

                        x_SetReq(fAtr, IPP_REQALL);

                    } else if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_szJobTemplate) == 0) {

                        x_SetReq(fAtr, IPP_REQJTMP);

                    } else if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_szJobDescription) == 0) {

                        x_SetReq(fAtr, IPP_REQJDSC);

                    } else {

                        // Walk through the possible response attributes
                        // and look for those requested.
                        //
                        cAtr = sizeof(s_PJR) / sizeof(s_PJR[0]);

                        for (idx = 0, bFound = FALSE; idx < cAtr; idx++) {

                            if (s_PJR[idx].pszNam) {

                                if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_PJR[idx].pszNam) == 0) {

                                    x_SetReq(fAtr, s_PJR[idx].fReq);

                                    bFound = TRUE;

                                    break;
                                }
                            }
                        }


                        // Look through potential request/response mappings.  This
                        // is necessary for request that have a different name
                        // than that we give back in a response.  i.e. JobReqUser
                        // verses JobOrgUser.
                        //
                        if (bFound == FALSE) {

                            cAtr = sizeof(s_ReqRspStr) / sizeof(s_ReqRspStr[0]);

                            for (idx = 0; idx < cAtr; idx++) {

                                if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_ReqRspStr[idx].pszStr) == 0) {

                                    x_SetReq(fAtr, s_ReqRspStr[idx].fFlag);

                                    bFound = TRUE;

                                    break;
                                }
                            }
                        }

                        if (!bFound)
                            lpObj->pwlUns->Add((PCTSTR)lpAttr->lpValue);
                    }
                }
            }

            ipp_RelAttr(lpAttr);
        }

        if (ERROR_RANGE(lpObj->wError))
            break;


        // Advance to next Tag.  This routine also increments
        // the (cbIdx) count.  If we run out of bytes in the
        // header before we can get to the next-tag, then this
        // will return NULL.
        //
        if (lpbTag = ipp_NextTag(lpObj->lpIppHdr, lpcbIdx, lpObj->cbIppHdr))
            bTag = ipp_ReadByte(lpbTag, 0);
        else
            break;
    }


    // If the fidelity is desired, then we should have
    // no unsupported attributes.
    //
    if (bFid && (lpObj->pwlUns->Count()))
        lpObj->wError = IPPRSP_ERROR_40B;


    // Set the internal-state
    //
    if (bAtr)
        CopyMemory(lpObj->fReq, fAtr, IPPOBJ_MASK_SIZE * sizeof(DWORD));

    return lpbTag;
}


/*****************************************************************************\
* ipp_GetIPPPI2 (Local Routine)
*
* Returns the info for a complete job in the IPP stream.  We essentially
* loop through the attributes looking for the next IPP_TAG_DEL_JOB to
* signify another printer-info-item.
*
\*****************************************************************************/
LPBYTE ipp_GetIPPPI2(
    LPBYTE   lpbTag,
    LPIPPPI2 lppi,
    LPDWORD  lpcbIdx,
    LPIPPOBJ lpObj)
{
    LPIPPATTR lpAttr;
    BYTE      bTag;
    DWORD     cAtr;
    DWORD     idx;
    BOOL      bReq;
    BOOL      bFound;
    DWORD     fAtr[IPPOBJ_MASK_SIZE];
    BOOL      bAtr = FALSE;


    x_SetReq(fAtr, IPP_REQALL);


    bTag = ipp_ReadByte(lpbTag, 0);
    bReq = ((lpObj->wReq & IPP_RESPONSE) ? FALSE : TRUE);


    while ((bTag != IPP_TAG_DEL_PRINTER) && (bTag != IPP_TAG_DEL_DATA)) {

        if (lpAttr = ipp_GetAttr(lpbTag, *lpcbIdx, lpObj)) {

            // Check the name-type to see how to handle the value.
            //
            if (lpAttr->lpszName && lpAttr->lpValue) {

                if (lstrcmpi(lpAttr->lpszName, s_szPrtUpTime) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER) {
                        lpObj->wError = IPPRSP_ERROR_409;
                    } else {
                        // What we want to do is get the current time in seconds and then
                        // work out what the T0 of the printer must be in this renormalised
                        // time

                        // These will be positive, we assume [0..2^31-1]
                        DWORD dwCurTime = ipp_IppCurTime();  
                        DWORD  dwPrtTime = *(LPDWORD) lpAttr->lpValue;   

                        lppi->ipp.dwPowerUpTime = (time_t)dwCurTime - (time_t)dwPrtTime;
                   }

                } else if (lstrcmpi(lpAttr->lpszName, s_szCharSet) == 0) {

                    if (lpAttr->cbValue > SIZE_CHARSET)
                        lpObj->wError = IPPRSP_ERROR_409;

                } else if (lstrcmpi(lpAttr->lpszName, s_szNaturalLanguage) == 0) {

                    if (lpAttr->cbValue > SIZE_NATLANG)
                        lpObj->wError = IPPRSP_ERROR_409;

                } else if (lstrcmpi(lpAttr->lpszName, s_szPrtState) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lppi->pi2.Status = ipp_IppToW32PrnState(*(LPDWORD)lpAttr->lpValue);

                } else if (lstrcmpi(lpAttr->lpszName, s_szPrtJobs) == 0) {

                    if (lpAttr->cbValue != SIZE_INTEGER)
                        lpObj->wError = IPPRSP_ERROR_409;
                    else
                        lppi->pi2.cJobs = *(LPDWORD)lpAttr->lpValue;

                } else if (lstrcmpi(lpAttr->lpszName, s_szPrtName) == 0) {

                    if (lpAttr->cbValue > SIZE_NAME) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lppi->pi2.pPrinterName);
                        lppi->pi2.pPrinterName = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szPrtUri) == 0) {

                    if (lpAttr->cbValue > SIZE_URI) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lppi->ipp.pPrnUri);
                        lppi->ipp.pPrnUri = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobReqUser) == 0) {

                    if (lpAttr->cbValue > SIZE_NAME) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lppi->ipp.pUsrName);
                        lppi->ipp.pUsrName = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szPrtUriSupported) == 0) {

                    if (lpAttr->cbValue > SIZE_URI) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lppi->ipp.pPrnUri);
                        lppi->ipp.pPrnUri = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szPrtMake) == 0) {

                    if (lpAttr->cbValue > SIZE_TEXT) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        webFree(lppi->pi2.pDriverName);
                        lppi->pi2.pDriverName = webAllocStr((LPTSTR)lpAttr->lpValue);
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szDocFormat) == 0) {

                    if (lpAttr->cbValue > SIZE_MIMEMEDIA) {

                        lpObj->wError = IPPRSP_ERROR_409;

                    } else {

                        if (ipp_ValDocFormat((PCTSTR)lpAttr->lpValue) == FALSE)
                            lpObj->wError = IPPRSP_ERROR_40A;
                    }

                } else if (lstrcmpi(lpAttr->lpszName, s_szJobReqUser) == 0) {

                    if (lpAttr->cbValue > SIZE_NAME)
                        lpObj->wError = IPPRSP_ERROR_409;

                } else if (bReq && (lstrcmpi(lpAttr->lpszName, s_szReqAttr) == 0)) {

                    bAtr = TRUE;

                    x_SetReq(fAtr, IPP_REQCLEAR);

                    goto ProcessVal;

                } else {

                    lpObj->pwlUns->Add(lpAttr->lpszName);
                }

            } else if (bAtr && lpAttr->lpValue) {

ProcessVal:
                if (lpAttr->cbValue > SIZE_KEYWORD) {

                    lpObj->wError = IPPRSP_ERROR_409;

                } else {

                    if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_szAll) == 0) {

                        x_SetReq(fAtr, IPP_REQALL);

                    } else if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_szJobTemplate) == 0) {

                        x_SetReq(fAtr, IPP_REQPTMP);

                    } else if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_szPrtDescription) == 0) {

                        x_SetReq(fAtr, IPP_REQPDSC);

                    } else {

                        // Walk through the possible response attributes
                        // and look for those requested.
                        //
                        cAtr = sizeof(s_GPR) / sizeof(s_GPR[0]);

                        for (idx = 0, bFound = FALSE; idx < cAtr; idx++) {

                            if (s_GPR[idx].pszNam) {

                                if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_GPR[idx].pszNam) == 0) {

                                    x_SetReq(fAtr, s_GPR[idx].fReq);

                                    bFound = TRUE;

                                    break;
                                }
                            }
                        }


                        // Look through potential request/response mappings.  This
                        // is necessary for request that have a different name
                        // than that we give back in a response.  i.e. JobReqUser
                        // verses JobOrgUser.
                        //
                        if (bFound == FALSE) {

                            cAtr = sizeof(s_ReqRspStr) / sizeof(s_ReqRspStr[0]);

                            for (idx = 0; idx < cAtr; idx++) {

                                if (lstrcmpi((PCTSTR)lpAttr->lpValue, s_ReqRspStr[idx].pszStr) == 0)
                                    x_SetReq(fAtr, s_ReqRspStr[idx].fFlag);
                            }
                        }

                        if (!bFound)
                            lpObj->pwlUns->Add((PCTSTR)lpAttr->lpValue);
                    }
                }
            }

            ipp_RelAttr(lpAttr);
        }


        if (ERROR_RANGE(lpObj->wError))
            break;


        // Advance to next Tag.  This routine also increments
        // the (cbIdx) count.  If we run out of bytes in the
        // header before we can get to the next-tag, then this
        // will return NULL.
        //
        if (lpbTag = ipp_NextTag(lpObj->lpIppHdr, lpcbIdx, lpObj->cbIppHdr))
            bTag = ipp_ReadByte(lpbTag, 0);
        else
            break;
    }


    // Set the internal-state
    //
    if (bAtr)
        CopyMemory(lpObj->fReq, fAtr, IPPOBJ_MASK_SIZE * sizeof(DWORD));

    return lpbTag;
}


/*****************************************************************************\
* ipp_CopyJI2toIPPJI2 (Local Routine)
*
* Copies a JOB_INFO_2 to IPPJI2.
*
\*****************************************************************************/
LPBYTE ipp_CopyJI2toIPPJI2(
    LPIPPJI2     lpjiDst,
    LPJOB_INFO_2 lpJI2,
    LPTSTR       lpszJobBase,
    LPBYTE       lpbEnd)
{
    LPTSTR* lpszSrc;
    LPTSTR  lpszPtr;
    LPTSTR  lpszJobUri;
    LPTSTR  lpszPrnUri;
    LPTSTR  aszSrc[(sizeof(IPPJI2) / sizeof(LPTSTR))];


    // Set the start of the string-buffer.
    //
    ZeroMemory(aszSrc , sizeof(aszSrc));
    ZeroMemory(lpjiDst, sizeof(IPPJI2));


    // Copy fixed values.
    //
    lpjiDst->ji2.JobId        = lpJI2->JobId;
    lpjiDst->ji2.Status       = ipp_W32ToIppJobState(lpJI2->Status);
    lpjiDst->ji2.Priority     = ipp_W32ToIppJobPriority(lpJI2->Priority);
    lpjiDst->ji2.Size         = ipp_W32ToIppJobSize(lpJI2->Size);
    lpjiDst->ji2.TotalPages   = ipp_W32ToIppJobTotalPages(lpJI2->TotalPages);
    lpjiDst->ji2.PagesPrinted = ipp_W32ToIppJobPagesPrinted(lpJI2->PagesPrinted);

    *((LPDWORD)&lpjiDst->ji2.Submitted) = ipp_W32ToIppTime(&lpJI2->Submitted);


    // Build a job-uri.
    //
    if (lpszJobUri = (LPTSTR)webAlloc(webStrSize(lpszJobBase) + 80))
        wsprintf(lpszJobUri, TEXT("%s%d"), lpszJobBase, lpJI2->JobId);


    // Build a printer-uri.
    //
    lpszPrnUri = NULL;

    if (lpszJobBase && (lpszPtr = webFindRChar(lpszJobBase, TEXT('?')))) {

        *lpszPtr = TEXT('\0');
        lpszPrnUri = (LPTSTR)webAllocStr(lpszJobBase);
        *lpszPtr = TEXT('?');
    }


    // Copy strings.  Make sure we place the strings in the appropriate
    // offset.
    //
    lpszSrc = aszSrc;

    *lpszSrc++ = lpJI2->pPrinterName;
    *lpszSrc++ = lpJI2->pMachineName;
    *lpszSrc++ = lpJI2->pUserName;
    *lpszSrc++ = lpJI2->pDocument;
    *lpszSrc++ = lpJI2->pNotifyName;
    *lpszSrc++ = lpJI2->pDatatype;
    *lpszSrc++ = lpJI2->pPrintProcessor;
    *lpszSrc++ = lpJI2->pParameters;
    *lpszSrc++ = lpJI2->pDriverName;
    *lpszSrc++ = NULL;
    *lpszSrc++ = lpJI2->pStatus;
    *lpszSrc++ = NULL;
    *lpszSrc++ = lpszPrnUri;
    *lpszSrc++ = lpszJobUri;

    lpbEnd = ipp_PackStrings(aszSrc, (LPBYTE)lpjiDst, s_IPPJI2Offs, lpbEnd);

    webFree(lpszJobUri);
    webFree(lpszPrnUri);

    return lpbEnd;
}


/*****************************************************************************\
* ipp_SizeofIPPPI2 (Local Routine)
*
* Returns the size necessary to store a IPPPI2 struct.  This excludes the
* DEVMODE and SECURITYDESCRIPTOR fields.
*
\*****************************************************************************/
DWORD ipp_SizeofIPPPI2(
    LPPRINTER_INFO_2   lppi2,
    LPPRINTER_INFO_IPP lpipp)
{
    DWORD  cCnt;
    DWORD  idx;
    DWORD  cbSize;
    LPTSTR lpszStr;

    // Default Size.
    //
    cbSize = 0;


    // Get the size necessary for PRINTER_INFO_2 structure.
    //
    if (lppi2) {

        cCnt = ((sizeof(s_PI2Off) / sizeof(s_PI2Off[0])) - 1);

        for (idx = 0; idx < cCnt; idx++) {

            lpszStr = *(LPTSTR*)(((LPBYTE)lppi2) + s_PI2Off[idx]);

            cbSize += (lpszStr ? webStrSize(lpszStr) : 0);
        }
    }


    // Get the size necessary for PRINTER_INFO_IPP structure.
    //
    if (lpipp) {

       cCnt = ((sizeof(s_IPPOff) / sizeof(s_IPPOff[0])) - 1);

       for (idx = 0; idx < cCnt; idx++) {

           lpszStr = *(LPTSTR*)(((LPBYTE)lpipp) + s_IPPOff[idx]);

           cbSize += (lpszStr ? webStrSize(lpszStr) : 0);
       }
   }

    return cbSize;
}


/*****************************************************************************\
* ipp_SizeofIPPJI2 (Local Routine)
*
* Returns the size necessary to store a IPPJI2 struct.  This excludes the
* DEVMODE and SECURITYDESCRIPTOR fields.
*
\*****************************************************************************/
DWORD ipp_SizeofIPPJI2(
    LPJOB_INFO_2   lpji2,
    LPJOB_INFO_IPP lpipp)
{
    DWORD  cCnt;
    DWORD  idx;
    DWORD  cbSize;
    LPTSTR lpszStr;

    // Default Size.
    //
    cbSize = 0;


    // Get the size necessary for JOB_INFO_2 structure.
    //
    if (lpji2) {

        cCnt = ((sizeof(s_JI2Off) / sizeof(s_JI2Off[0])) - 1);

        for (idx = 0; idx < cCnt; idx++) {

            lpszStr = *(LPTSTR*)(((LPBYTE)lpji2) + s_JI2Off[idx]);

            cbSize += (lpszStr ? webStrSize(lpszStr) : 0);
        }
    }


    // Get the size necessary for JOB_INFO_IPP structure.
    //
    if (lpipp) {

        cCnt = ((sizeof(s_IPJOff) / sizeof(s_IPJOff[0])) - 1);

        for (idx = 0; idx < cCnt; idx++) {

            lpszStr = *(LPTSTR*)(((LPBYTE)lpipp) + s_IPJOff[idx]);

            cbSize += (lpszStr ? webStrSize(lpszStr) : 0);
        }
    }

    return cbSize;
}


/*****************************************************************************\
* ipp_BuildPI2 (Local Routine)
*
* Builds a IPPPI2 struct from PRINTER_INFO_2 and PRINTER_INFO_IPP.
*
\*****************************************************************************/
LPBYTE ipp_BuildPI2(
    LPIPPPI2           lppi,
    LPPRINTER_INFO_2   lppi2,
    LPPRINTER_INFO_IPP lpipp,
    LPBYTE             lpbEnd)
{
    LPTSTR* lpszSrc;
    LPTSTR  aszSrc[(sizeof(IPPPI2) / sizeof(LPTSTR))];


    // Set the start of the string-buffer.
    //
    ZeroMemory(aszSrc, sizeof(aszSrc));
    ZeroMemory(lppi  , sizeof(IPPPI2));


    // Copy fixed values.
    //
    if (lppi2) {

        lppi->pi2.Attributes      = lppi2->Attributes;
        lppi->pi2.Priority        = lppi2->Priority;
        lppi->pi2.DefaultPriority = lppi2->DefaultPriority;
        lppi->pi2.StartTime       = lppi2->StartTime;
        lppi->pi2.UntilTime       = lppi2->UntilTime;
        lppi->pi2.Status          = lppi2->Status;
        lppi->pi2.cJobs           = lppi2->cJobs;
        lppi->pi2.AveragePPM      = lppi2->AveragePPM;
     }


    lppi->ipp.dwPowerUpTime       = (lpipp ? lpipp->dwPowerUpTime : 0);
    // Copy strings.  Make sure we place the strings in the appropriate
    // offset.
    //
    lpszSrc = aszSrc;

    *lpszSrc++ = (lppi2 ? lppi2->pServerName     : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pPrinterName    : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pShareName      : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pPortName       : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pDriverName     : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pComment        : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pLocation       : NULL);
    *lpszSrc++ = NULL;
    *lpszSrc++ = (lppi2 ? lppi2->pSepFile        : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pPrintProcessor : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pDatatype       : NULL);
    *lpszSrc++ = (lppi2 ? lppi2->pParameters     : NULL);
    *lpszSrc++ = NULL;
    *lpszSrc++ = (lpipp ? lpipp->pPrnUri         : NULL);
    *lpszSrc++ = (lpipp ? lpipp->pUsrName        : NULL);

    return ipp_PackStrings(aszSrc, (LPBYTE)lppi, s_IPPPI2Offs, lpbEnd);
}


/*****************************************************************************\
* ipp_BuildJI2 (Local Routine)
*
* Builds a IPPJI2 struct from JOB_INFO_2 and JOB_INFO_IPP.
*
\*****************************************************************************/
LPBYTE ipp_BuildJI2(
    LPIPPJI2       lpji,
    LPJOB_INFO_2   lpji2,
    LPJOB_INFO_IPP lpipp,
    LPBYTE         lpbEnd)
{
    LPTSTR* lpszSrc;
    LPTSTR  aszSrc[(sizeof(IPPJI2) / sizeof(LPTSTR))];

    // Set the start of the string-buffer.
    //
    ZeroMemory(aszSrc, sizeof(aszSrc));
    ZeroMemory(lpji, sizeof(IPPJI2));


    // Copy fixed values.
    //
    if (lpji2) {

        lpji->ji2.JobId        = lpji2->JobId;
        lpji->ji2.Status       = lpji2->Status;
        lpji->ji2.Priority     = lpji2->Priority;
        lpji->ji2.Position     = lpji2->Position;
        lpji->ji2.StartTime    = lpji2->StartTime;
        lpji->ji2.UntilTime    = lpji2->UntilTime;
        lpji->ji2.TotalPages   = lpji2->TotalPages;
        lpji->ji2.Size         = lpji2->Size;
        lpji->ji2.Time         = lpji2->Time;
        lpji->ji2.PagesPrinted = lpji2->PagesPrinted;
        lpji->ji2.StartTime    = lpji2->StartTime;

        CopyMemory(&lpji->ji2.Submitted, &lpji2->Submitted, sizeof(SYSTEMTIME));
    }


    // Copy strings.  Make sure we place the strings in the appropriate
    // offset.
    //
    lpszSrc = aszSrc;

    *lpszSrc++ = (lpji2 ? lpji2->pPrinterName    : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pMachineName    : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pUserName       : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pDocument       : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pNotifyName     : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pDatatype       : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pPrintProcessor : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pParameters     : NULL);
    *lpszSrc++ = (lpji2 ? lpji2->pDriverName     : NULL);
    *lpszSrc++ = NULL;
    *lpszSrc++ = (lpji2 ? lpji2->pStatus         : NULL);
    *lpszSrc++ = NULL;
    *lpszSrc++ = (lpipp ? lpipp->pPrnUri         : NULL);
    *lpszSrc++ = (lpipp ? lpipp->pJobUri         : NULL);

    return ipp_PackStrings(aszSrc, (LPBYTE)lpji, s_IPPJI2Offs, lpbEnd);
}


/*****************************************************************************\
* ipp_GetJobCount (Local Routine)
*
* Returns the total number of jobs in an enumerated GETJOB response.
*
\*****************************************************************************/
DWORD ipp_GetJobCount(
    LPBYTE lpbHdr,
    DWORD  cbHdr)
{
    DWORD  cbIdx;
    LPBYTE lpbTag;
    DWORD  cJobs = 0;


    // Position the tag at the start of the header.
    //
    lpbTag = lpbHdr + IPP_SIZEOFHDR;


    for (cbIdx = IPP_SIZEOFHDR; lpbTag && (ipp_ReadByte(lpbTag, 0) != IPP_TAG_DEL_DATA); ) {

        // If we hit a job-deliminator, then we have a job-info item.
        //
        if (ipp_ReadByte(lpbTag, 0) == IPP_TAG_DEL_JOB)
            cJobs++;


        // Advance to next Tag.  This routine also increments
        // the (cbIdx) count.  If we run out of bytes in the
        // header before we can get to the next-tag, then this
        // will return NULL.
        //
        lpbTag = ipp_NextTag(lpbHdr, &cbIdx, cbHdr);
    }

    return cJobs;
}


/*****************************************************************************\
* ipp_IppToW32 (Local Routine - Client/Server)
*
* Converts an Ipp-Header to a W32-Structure.
*
\*****************************************************************************/
DWORD ipp_IppToW32(
    LPIPPOBJ lpObj,
    LPBYTE*  lplpRawHdr,
    LPDWORD  lpcbRawHdr)
{
    DWORD        cbIdx;
    DWORD        dwCmd;
    IPPJI2       ji;
    IPPPI2       pi;
    PIPPREQ_ALL  pr;
    UINT         uType = IPPTYPE_UNKNOWN;
    DWORD        dwRet = WEBIPP_FAIL;


    // Position the tag at the Tag/Attributes and fetch the information
    // for the request.
    //
    cbIdx = IPP_SIZEOFHDR;

    switch (lpObj->wReq) {

    case IPP_REQ_PRINTJOB:
    case IPP_REQ_VALIDATEJOB:
    case IPP_REQ_GETJOB:
    case IPP_REQ_CANCELJOB:
    case IPP_REQ_PAUSEJOB:
    case IPP_REQ_RESUMEJOB:
    case IPP_REQ_RESTARTJOB:
    case IPP_REQ_ENUJOB:
        ZeroMemory(&ji, sizeof(IPPJI2));
        ji.ipp.cJobs = IPP_GETJOB_ALL;

        ipp_GetIPPJI2(lpObj->lpIppHdr + IPP_SIZEOFHDR, &ji, &cbIdx, lpObj);

        uType = IPPTYPE_JOB;
        break;

    case IPP_REQ_GETPRN:
    case IPP_REQ_PAUSEPRN:
    case IPP_REQ_CANCELPRN:
    case IPP_REQ_RESUMEPRN:
        ZeroMemory(&pi, sizeof(IPPPI2));
        ipp_GetIPPPI2(lpObj->lpIppHdr + IPP_SIZEOFHDR, &pi, &cbIdx, lpObj);

        uType = IPPTYPE_PRT;
        break;

    case IPP_REQ_FORCEAUTH:
        uType = IPPTYPE_AUTH;
        break;
    }


    // If a failure occured, then there's no need to proceed.
    //
    if (ERROR_RANGE(lpObj->wError))
        goto EndCvt;


    // Initialize any default-values, that may have been overlooked
    // in the request-stream.
    //
    switch (uType) {

    case IPPTYPE_JOB:

        if (ji.ji2.pUserName == NULL)
            ji.ji2.pUserName = webAllocStr(s_szUnknown);

        if (ji.ji2.pDocument == NULL)
            ji.ji2.pDocument = webAllocStr(s_szUnknown);
        break;

    case IPPTYPE_PRT:

        if (pi.pi2.pPrinterName == NULL)
            pi.pi2.pPrinterName = webAllocStr(s_szUnknown);
        break;
    }


    // Build the request structure based upon the request command.
    //
    switch (lpObj->wReq) {

    case IPP_REQ_PRINTJOB:
        pr = (PIPPREQ_ALL)WebIppCreatePrtJobReq(FALSE, ji.ji2.pUserName, ji.ji2.pDocument, ji.ipp.pPrnUri);
        break;

    case IPP_REQ_VALIDATEJOB:
        pr = (PIPPREQ_ALL)WebIppCreatePrtJobReq(TRUE, ji.ji2.pUserName, ji.ji2.pDocument, ji.ipp.pPrnUri);
        break;

    case IPP_REQ_ENUJOB:
        pr = (PIPPREQ_ALL)WebIppCreateEnuJobReq(ji.ipp.cJobs, ji.ipp.pPrnUri);
        break;

    case IPP_REQ_CANCELJOB:
    case IPP_REQ_PAUSEJOB:
    case IPP_REQ_RESUMEJOB:
    case IPP_REQ_RESTARTJOB:
        dwCmd = ipp_MapReqToJobCmd(lpObj->wReq);
        pr = (PIPPREQ_ALL)WebIppCreateSetJobReq(ji.ji2.JobId, dwCmd, ji.ipp.pPrnUri);
        break;

    case IPP_REQ_GETJOB:
        pr = (PIPPREQ_ALL)WebIppCreateGetJobReq(ji.ji2.JobId, ji.ipp.pPrnUri);
        break;

    case IPP_REQ_GETPRN:
        pr = (PIPPREQ_ALL)WebIppCreateGetPrnReq(0, pi.ipp.pPrnUri);
        break;

    case IPP_REQ_PAUSEPRN:
    case IPP_REQ_CANCELPRN:
    case IPP_REQ_RESUMEPRN:
        dwCmd = ipp_MapReqToPrnCmd(lpObj->wReq);
        pr = (PIPPREQ_ALL)WebIppCreateSetPrnReq(dwCmd, pi.ipp.pUsrName, pi.ipp.pPrnUri);
        break;

    case IPP_REQ_FORCEAUTH:
        pr = (PIPPREQ_AUTH)WebIppCreateAuthReq();
        break;

    default:
        pr = NULL;
        break;
    }


    // Set the return values.
    //
    if (pr) {

        *lplpRawHdr = (LPBYTE)pr;
        *lpcbRawHdr = pr->cbSize;

        dwRet = WEBIPP_OK;

    } else {

        dwRet = WEBIPP_NOMEMORY;
    }


EndCvt:

    // Cleanup.
    //
    switch (uType) {

    case IPPTYPE_JOB:
        ipp_FreeIPPJI2(&ji);
        break;

    case IPPTYPE_PRT:
        ipp_FreeIPPPI2(&pi);
        break;
    }

    return dwRet;
}


/*****************************************************************************\
* ipp_W32ToIpp (Local Routine - Client/Server)
*
* Converts a W32 information to an IPP Header (both request and responses).
*
\*****************************************************************************/
DWORD ipp_W32ToIpp(
    WORD       wReq,
    LPREQINFO  lpri,
    LPBYTE     lpbData,
    LPIPPATTRX pSnd,
    DWORD      cSnd,
    LPBYTE*    lplpIppHdr,
    LPDWORD    lpcbIppHdr)
{
    LPIPPRET_ENUJOB pej;
    LPBYTE          lpIppHdr;
    LPBYTE          lpIppPtr;
    DWORD           cbIppHdr;
    LPIPPJI2        lpji;
    DWORD           cUns;
    DWORD           idx;
    DWORD           cbSize;
    DWORD           dwRet;
    DWORD           dwState;
    DWORD           cbUns;
    WORD            wOut;
    LPIPPATTRY      pAtr = NULL;
    LPIPPATTRY      pUns = NULL;


    // Zero out our return pointer/count.
    //
    *lplpIppHdr = NULL;
    *lpcbIppHdr = 0;


    // Is this a request or response.
    //
    if (wReq & IPP_RESPONSE) {

        if (((LPIPPRET_ALL)lpbData)->wRsp == IPPRSP_SUCCESS) {

            wOut = ((lpri->pwlUns && lpri->pwlUns->Count()) ? IPPRSP_SUCCESS1 : IPPRSP_SUCCESS);

        } else {

            wOut = ((LPIPPRET_ALL)lpbData)->wRsp;
        }

    } else {

        wOut = wReq;
    }


    // Minimum header size.
    //
    cbIppHdr = ipp_SizeHdr(lpri->cpReq) + IPP_SIZEOFTAG;


    // Treat the EnumJob response differently from the others, since this
    // returns a dynamic list of jobs.
    //
    if (wReq == IPP_RET_ENUJOB) {


        // Build the unsupported-attributes if there
        // are any.
        //
        cbUns = 0;
        pUns  = ipp_AllocUnsVals(lpri->pwlUns, &cUns, &cbUns);


        pej = (PIPPRET_ENUJOB)lpbData;

        cbSize = cbIppHdr +
                 cbUns    +
                 ((pej->cItems && pej->cbItems) ? (3 * pej->cbItems) : 0);

        if (lpIppHdr = (LPBYTE)webAlloc(cbSize)) {

            cbIppHdr += cbUns;
            lpIppPtr  = lpIppHdr;


            // Output the ipp-stream.
            //
            ipp_WriteHead(&lpIppPtr, wOut, lpri->idReq, lpri->cpReq);
            ipp_WriteUnsVals(&lpIppPtr, pUns, cUns);


            for (idx = 0, lpji = pej->pItems; idx < pej->cItems; idx++) {

                dwState = ipp_IppToW32JobState(lpji[idx].ji2.Status);

                // Check for any requested-attributes that include this job-entry.
                //
                if ((x_ChkReq(lpri->fReq, RA_JOBSCOMPLETED)   &&  (dwState & JOB_STATUS_PRINTED)) ||
                    (x_ChkReq(lpri->fReq, RA_JOBSUNCOMPLETED) && !(dwState & JOB_STATUS_PRINTED)) ||
                    (x_ChkReq(lpri->fReq, (RA_JOBSCOMPLETED | RA_JOBSUNCOMPLETED)) == FALSE)) {

                    if (pAtr = ipp_AllocAtrVals(wReq, lpri->fReq, lpri->cpReq, (LPBYTE)&lpji[idx], pSnd, cSnd, &cbIppHdr)) {

                        ipp_WriteAtrVals(wReq, lpri->fReq, &lpIppPtr, pSnd, pAtr, cSnd);

                        ipp_FreeAtrVals(pAtr, cSnd);
                    }
                }
            }

            ipp_WriteByte(&lpIppPtr, IPP_TAG_DEL_DATA);


            // Set the return values for the IPP-Stream-Header
            // as well as the size.
            //
            dwRet = WEBIPP_OK;

            *lplpIppHdr = lpIppHdr;
            *lpcbIppHdr = cbIppHdr;

        } else {

            dwRet = WEBIPP_NOMEMORY;
        }

        ipp_FreeAtrVals(pUns, cUns);

    } else {

        if ((cSnd == 0) ||
            (pAtr = ipp_AllocAtrVals(wReq, lpri->fReq, lpri->cpReq, lpbData, pSnd, cSnd, &cbIppHdr))) {


            // Build the unsupported-attributes if there
            // are any.
            //
            pUns = ipp_AllocUnsVals(lpri->pwlUns, &cUns, &cbIppHdr);


            // Write the IPP-Stream.
            //
            if (lpIppHdr = (LPBYTE)webAlloc(cbIppHdr)) {

                lpIppPtr = lpIppHdr;

                ipp_WriteHead(&lpIppPtr, wOut, lpri->idReq, lpri->cpReq);
                ipp_WriteUnsVals(&lpIppPtr, pUns, cUns);
                ipp_WriteAtrVals(wReq, lpri->fReq, &lpIppPtr, pSnd, pAtr, cSnd);
                ipp_WriteByte(&lpIppPtr, IPP_TAG_DEL_DATA);


                // Set the return values for the IPP-Stream-Header
                // as well as the size.
                //
                dwRet = WEBIPP_OK;

                *lplpIppHdr = lpIppHdr;
                *lpcbIppHdr = cbIppHdr;

            } else {

                dwRet = WEBIPP_NOMEMORY;
            }

            if (pUns)
            {
                ipp_FreeAtrVals(pUns, cUns);
            }

            if (pAtr)
            {
                ipp_FreeAtrVals(pAtr, cSnd);
            }


        } else {

            dwRet = WEBIPP_NOMEMORY;
        }
    }

    return dwRet;
}


/*****************************************************************************\
* ipp_IppToFailure (Local Routine - Client)
*
* Converts an Ipp-Header to a IPPRET_ALL  From the stream we need
* to pull out the following information:
*
\*****************************************************************************/
DWORD ipp_IppToFailure(
    LPIPPOBJ lpObj,
    LPBYTE*  lplpRawHdr,
    LPDWORD  lpcbRawHdr)
{
    PIPPRET_ALL pbr;
    WORD        wRsp;
    BOOL        bRet;
    DWORD       dwRet;


    // Pull out the response.
    //
    wRsp = ipp_ReadWord(lpObj->lpIppHdr, IPP_SIZEOFVER);
    bRet = SUCCESS_RANGE(wRsp);


    // Build the response structure.
    //
    if (pbr = WebIppCreateBadRet(wRsp, bRet)) {

        *lplpRawHdr = (LPBYTE)pbr;
        *lpcbRawHdr = pbr->cbSize;

        dwRet = WEBIPP_OK;

    } else {

        dwRet = WEBIPP_NOMEMORY;
    }

    return dwRet;
}


/*****************************************************************************\
* ipp_IppToJobRet (Local Routine - Client)
*
* Converts an Ipp-Header to a IPPRET_JOB.  From the stream we need
* to pull out the following information:
*
\*****************************************************************************/
DWORD ipp_IppToJobRet(
    LPIPPOBJ lpObj,
    LPBYTE*  lplpRawHdr,
    LPDWORD  lpcbRawHdr)
{
    LPBYTE       lpbTag;
    PIPPRET_JOB  pj;
    WORD         wRsp;
    IPPJI2       ji;
    DWORD        cbIdx;
    BOOL         bRet;
    DWORD        dwRet;
    BOOL         bValidate = FALSE;


    // Set our default-settings necessary for our PIPPRET_JOB.
    //
    ZeroMemory(&ji, sizeof(IPPJI2));


    // Position the tag at the Tag/Attributes.
    //
    lpbTag = lpObj->lpIppHdr + IPP_SIZEOFHDR;


    // Pull out the response.
    //
    wRsp = ipp_ReadWord(lpObj->lpIppHdr, IPP_SIZEOFVER);


    // If this is a failure-response then call the routine to
    // generate a failure-structure.
    //
    if (SUCCESS_RANGE(wRsp) == FALSE)
        return ipp_IppToFailure(lpObj, lplpRawHdr, lpcbRawHdr);


    // Traverse through the header, advancing through the attributes,
    // until the IPP_TAG_DEL_DATA is encountered.
    //
    for (cbIdx = IPP_SIZEOFHDR; lpbTag && (ipp_ReadByte(lpbTag, 0) != IPP_TAG_DEL_DATA); ) {

        // Look for a IPP_TAG_DEL_JOB to indicate we have a job-info
        // item.  Otherwise, skip to the next attribute.
        //
        if (ipp_ReadByte(lpbTag, 0) == IPP_TAG_DEL_JOB) {

            // Since were currently at a deliminator, we need to get to
            // the next for the start of the job.
            //
            if (lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr)) {

                lpbTag = ipp_GetIPPJI2(lpbTag, &ji, &cbIdx, lpObj);
            }

        } else {

            lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr);
        }
    }


    // Determine the correct return-code based upon the request response.
    //
    switch (lpObj->wReq) {

    case IPP_RET_PRINTJOB:
        bRet = (SUCCESS_RANGE(wRsp) ? (BOOL)ji.ji2.JobId : FALSE);
        break;

    case IPP_RET_VALIDATEJOB:
        bValidate = TRUE;

        // Fall Through.
        //

    default:
        bRet = SUCCESS_RANGE(wRsp);
        break;
    }


    // Build the response structure.
    //
    if (pj = WebIppCreateJobRet(wRsp, bRet, bValidate, &ji.ji2, &ji.ipp)) {

        *lplpRawHdr = (LPBYTE)pj;
        *lpcbRawHdr = pj->cbSize;

        dwRet = WEBIPP_OK;

    } else {

        dwRet = WEBIPP_NOMEMORY;
    }


    // Cleanup.
    //
    ipp_FreeIPPJI2(&ji);

    return dwRet;
}


/*****************************************************************************\
* ipp_IppToPrnRet (Local Routine - Client)
*
* Converts an Ipp-Header to a IPPRET_PRN.  From the stream we need
* to pull out the following information:
*
\*****************************************************************************/
DWORD ipp_IppToPrnRet(
    LPIPPOBJ lpObj,
    LPBYTE*  lplpRawHdr,
    LPDWORD  lpcbRawHdr)
{
    PIPPRET_PRN pp;
    IPPPI2      pi;
    WORD        wRsp;
    LPBYTE      lpbTag;
    LPBYTE      lpbEnd;
    DWORD       cbIdx;
    DWORD       idx;
    DWORD       dwRet;


    // Set our default-settings necessary for our PIPPRET_PRN.
    //
    ZeroMemory(&pi, sizeof(IPPPI2));


    // Position the tag at the Tag/Attributes.
    //
    lpbTag = lpObj->lpIppHdr + IPP_SIZEOFHDR;


    // Pull out response code.
    //
    wRsp = ipp_ReadWord(lpObj->lpIppHdr, IPP_SIZEOFVER);


    // If this is a failure-response then call the routine to
    // generate a failure-structure.
    //
    if (SUCCESS_RANGE(wRsp) == FALSE)
        return ipp_IppToFailure(lpObj, lplpRawHdr, lpcbRawHdr);


    // Traverse through the header, advancing through the attributes,
    // until the IPP_TAG_DEL_DATA is encountered.
    //
    for (cbIdx = IPP_SIZEOFHDR; lpbTag && (ipp_ReadByte(lpbTag, 0) != IPP_TAG_DEL_DATA); ) {

        // Look for a IPP_TAG_DEL_PRINTER to indicate we have a printer-info
        // item.  Otherwise, skip to the next attribute.
        //
        if (ipp_ReadByte(lpbTag, 0) == IPP_TAG_DEL_PRINTER) {

            // Since were currently at a deliminator, we need to get to
            // the next for the start of the job.
            //
            if (lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr))
                lpbTag = ipp_GetIPPPI2(lpbTag, &pi, &cbIdx, lpObj);

        } else {

            lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr);
        }
    }


    // If none is specified for the pertinent information, then
    // use a default-str.
    //
    if (pi.ipp.pPrnUri == NULL)
        pi.ipp.pPrnUri = webAllocStr(s_szUnknown);

    if (pi.ipp.pUsrName == NULL)
        pi.ipp.pUsrName = webAllocStr(s_szUnknown);

    if (pi.pi2.pPrinterName == NULL)
        pi.pi2.pPrinterName = webAllocStr(s_szUnknown);

    if (pi.pi2.pDriverName == NULL)
        pi.pi2.pDriverName = webAllocStr(s_szUnknown);


    // Build the response structure.
    //
    pp = WebIppCreatePrnRet(wRsp, SUCCESS_RANGE(wRsp), &pi.pi2, &pi.ipp);

    if (pp != NULL) {

        *lplpRawHdr = (LPBYTE)pp;
        *lpcbRawHdr = pp->cbSize;

        dwRet = WEBIPP_OK;

    } else {

        dwRet = WEBIPP_NOMEMORY;
    }


    // Cleanup.
    //
    ipp_FreeIPPPI2(&pi);

    return dwRet;
}


/*****************************************************************************\
* ipp_IppToEnuRet (Local Routine - Client)
*
* Converts an Ipp-Header to a IPPRET_ENUJOB.
*
\*****************************************************************************/
DWORD ipp_IppToEnuRet(
    LPIPPOBJ lpObj,
    LPBYTE*  lplpRawHdr,
    LPDWORD  lpcbRawHdr)
{
    PIPPRET_ENUJOB pgj;
    LPIPPJI2       lpjiSrc;
    LPIPPJI2       lpjiDst;
    WORD           wRsp;
    DWORD          cJobs;
    DWORD          cbJobs;
    LPBYTE         lpbTag;
    LPBYTE         lpbEnd;
    DWORD          cbIdx;
    DWORD          idx;
    DWORD          dwRet;


    // Set our default-settings necessary for our PIPPRET_ENUJOB.
    //
    cJobs   = 0;
    cbJobs  = 0;
    lpjiDst = NULL;


    // Get the response-code.
    //
    wRsp = ipp_ReadWord(lpObj->lpIppHdr, IPP_SIZEOFVER);


    // If this is a failure-response then call the routine to
    // generate a failure-structure.
    //
    if (SUCCESS_RANGE(wRsp) == FALSE)
        return ipp_IppToFailure(lpObj, lplpRawHdr, lpcbRawHdr);


    // See if we have jobs to enumerate.
    //
    if (cJobs = ipp_GetJobCount(lpObj->lpIppHdr, lpObj->cbIppHdr)) {

        if (lpjiSrc = (LPIPPJI2)webAlloc(cJobs * sizeof(IPPJI2))) {

            // Get the job-info.
            //
            lpbTag = lpObj->lpIppHdr + IPP_SIZEOFHDR;

            for (idx = 0, cbIdx = IPP_SIZEOFHDR; lpbTag && (idx < cJobs); ) {

                // Look for a IPP_TAG_DEL_JOB to indicate we have a job-info
                // item.  Otherwise, skipp to the next attribute.
                //
                if (ipp_ReadByte(lpbTag, 0) == IPP_TAG_DEL_JOB) {

                    // Since were currently at a deliminator, we need to get to
                    // the next for the start of the job.
                    //
                    if (lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr))
                        lpbTag = ipp_GetIPPJI2(lpbTag, &lpjiSrc[idx++], &cbIdx, lpObj);

                } else {

                    lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr);
                }
            }


            // Get storage necessary for packed IPPJI2 structures.
            //
            cbJobs = (cJobs * sizeof(IPPJI2));

            for (idx = 0; idx < cJobs; idx++)
                cbJobs += ipp_SizeofIPPJI2(&lpjiSrc[idx].ji2, &lpjiSrc[idx].ipp);


            // Allocate an array of JI2 structs to contain our
            // enumeration.
            //
            if (lpjiDst = (LPIPPJI2)webAlloc(cbJobs)) {

                // For each job-item, initialize.
                //
                lpbEnd = ((LPBYTE)lpjiDst) + cbJobs;

                for (idx = 0; idx < cJobs; idx++)
                    lpbEnd = ipp_BuildJI2(&lpjiDst[idx], &lpjiSrc[idx].ji2, &lpjiSrc[idx].ipp, lpbEnd);
            }


            // Free the memory allocated for the job-item.
            //
            for (idx = 0; idx < cJobs; idx++)
                ipp_FreeIPPJI2(&lpjiSrc[idx]);

            webFree(lpjiSrc);
        }
    }


    // Build the response structure.
    //
    pgj = WebIppCreateEnuJobRet(wRsp, SUCCESS_RANGE(wRsp), cbJobs, cJobs, lpjiDst);

    if (pgj != NULL) {

        *lplpRawHdr = (LPBYTE)pgj;
        *lpcbRawHdr = pgj->cbSize;

        dwRet = WEBIPP_OK;

    } else {

        dwRet = WEBIPP_NOMEMORY;
    }


    // Cleanup.
    //
    webFree(lpjiDst);

    return dwRet;
}


/*****************************************************************************\
* ipp_IppToAthRet (Local Routine - Client)
*
* Converts an Ipp-Header to a IPPRET_AUTH.  From the stream we need
* to pull out the following information:
*
\*****************************************************************************/
DWORD ipp_IppToAthRet(
    LPIPPOBJ lpObj,
    LPBYTE*  lplpRawHdr,
    LPDWORD  lpcbRawHdr)
{
    PIPPRET_AUTH pfa;
    WORD         wRsp;
    BOOL         bRet;
    DWORD        dwRet;


    // Pull out the response.
    //
    wRsp = ipp_ReadWord(lpObj->lpIppHdr, IPP_SIZEOFVER);
    bRet = SUCCESS_RANGE(wRsp);


    // Build the response structure.
    //
    if (pfa = WebIppCreateAuthRet(wRsp, bRet)) {

        *lplpRawHdr = (LPBYTE)pfa;
        *lpcbRawHdr = pfa->cbSize;

        dwRet = WEBIPP_OK;

    } else {

        dwRet = WEBIPP_NOMEMORY;
    }

    return dwRet;
}


/*****************************************************************************\
* ipp_FailureToIpp (Local Routine - Server)
*
* Converts a IPPRET_ALL to an IPP Header.  This is used for responding to
* clients that an operation is not supported, or returning a failure.
*
\*****************************************************************************/
DWORD ipp_FailureToIpp(
    WORD      wReq,
    LPREQINFO lpri,
    LPBYTE    lpbData,
    LPBYTE*   lplpIppHdr,
    LPDWORD   lpcbIppHdr)
{
    LPBYTE      lpIppHdr;
    LPBYTE      lpIppPtr;
    DWORD       cbIppHdr;
    DWORD       dwRet;
    DWORD       cbNamSta;
    DWORD       cbValSta;
    LPSTR       lputfNamSta;
    LPSTR       lputfValSta;
    PIPPRET_ALL pbr = (PIPPRET_ALL)lpbData;


    // Zero out our return pointer/count.
    //
    *lplpIppHdr = NULL;
    *lpcbIppHdr = 0;


    ipp_GetRspSta(pbr->wRsp, lpri->cpReq, &lputfNamSta, &cbNamSta, &lputfValSta, &cbValSta);


    // Calculate the space necessary to generate our
    // IPP-Header-Stream.
    //
    cbIppHdr = ipp_SizeHdr(lpri->cpReq)                                                  +
               (lputfNamSta && lputfValSta ? ipp_SizeAttr(cbNamSta, cbValSta) : 0) +
               IPP_SIZEOFTAG;


    // Allocate the header for the IPP-Stream.
    //
    if (lpIppHdr = (LPBYTE)webAlloc(cbIppHdr)) {

        // Initialize the pointer which will keep track
        // of where we are in writing the IPP-Stream-Header.
        //
        lpIppPtr = lpIppHdr;


        // Write out the IPP-Header-Stream.
        //
        ipp_WriteHead(&lpIppPtr, pbr->wRsp, lpri->idReq, lpri->cpReq);

        if (lputfNamSta && lputfValSta)
            ipp_WriteAttr(&lpIppPtr, IPP_TAG_CHR_TEXT, cbNamSta, lputfNamSta, cbValSta, lputfValSta);

        ipp_WriteByte(&lpIppPtr, IPP_TAG_DEL_DATA);


        // Set the return values for the IPP-Stream-Header
        // as well as the size.
        //
        dwRet = WEBIPP_OK;

        *lplpIppHdr = lpIppHdr;
        *lpcbIppHdr = cbIppHdr;

    } else {

        dwRet = WEBIPP_NOMEMORY;
    }


    // Cleanup
    //
    webFree(lputfNamSta);
    webFree(lputfValSta);

    return dwRet;
}


/*****************************************************************************\
* Ipp Send/Receive Table
*
*
*
\*****************************************************************************/
static IPPSNDRCV s_pfnIpp[] = {

    // Operation         Req Form Rsp Form Req X  Req X Size     Rsp X  Rsp X Size     Rsp (cli)
    // ----------------- -------- -------- ------ -------------- ------ -------------- ----------------
    //
    IPP_REQ_PRINTJOB   , s_FormA, s_FormC, s_PJQ, sizeof(s_PJQ), s_PJR, sizeof(s_PJR), ipp_IppToJobRet,
    IPP_REQ_VALIDATEJOB, s_FormA, s_FormE, s_PJQ, sizeof(s_PJQ), NULL , 0            , ipp_IppToJobRet,
    IPP_REQ_CANCELJOB  , s_FormB, s_FormE, s_SJQ, sizeof(s_SJQ), NULL , 0            , ipp_IppToJobRet,
    IPP_REQ_GETJOB     , s_FormB, s_FormC, s_GJQ, sizeof(s_GJQ), s_PJR, sizeof(s_PJR), ipp_IppToJobRet,
    IPP_REQ_ENUJOB     , s_FormB, s_FormF, s_EJQ, sizeof(s_EJQ), s_EJR, sizeof(s_EJR), ipp_IppToEnuRet,
    IPP_REQ_GETPRN     , s_FormB, s_FormD, s_GPQ, sizeof(s_GPQ), s_GPR, sizeof(s_GPR), ipp_IppToPrnRet,
    IPP_REQ_PAUSEJOB   , s_FormB, s_FormE, s_SJQ, sizeof(s_SJQ), NULL , 0            , ipp_IppToJobRet,
    IPP_REQ_RESUMEJOB  , s_FormB, s_FormE, s_SJQ, sizeof(s_SJQ), NULL , 0            , ipp_IppToJobRet,
    IPP_REQ_RESTARTJOB , s_FormB, s_FormE, s_SJQ, sizeof(s_SJQ), NULL , 0            , ipp_IppToJobRet,
    IPP_REQ_PAUSEPRN   , s_FormB, s_FormE, s_SPQ, sizeof(s_SPQ), NULL , 0            , ipp_IppToPrnRet,
    IPP_REQ_RESUMEPRN  , s_FormB, s_FormE, s_SPQ, sizeof(s_SPQ), NULL , 0            , ipp_IppToPrnRet,
    IPP_REQ_CANCELPRN  , s_FormB, s_FormE, s_SPQ, sizeof(s_SPQ), NULL , 0            , ipp_IppToPrnRet,
    IPP_REQ_FORCEAUTH  , s_FormB, s_FormB, NULL , 0            , NULL , 0            , ipp_IppToAthRet
};


/*****************************************************************************\
* ipp_ValidateRcvReq (Local Routine)
*
* Returns whether the header is a supported request.
*
\*****************************************************************************/
DWORD ipp_ValidateRcvReq(
    LPIPPOBJ lpObj)
{
    DWORD idx;
    DWORD cCnt;
    DWORD dwId = ipp_ReadDWord(lpObj->lpIppHdr, IPP_SIZEOFINT);
    WORD  wVer = ipp_ReadWord(lpObj->lpIppHdr, 0);
    WORD  wReq = ipp_ReadWord(lpObj->lpIppHdr, IPP_SIZEOFVER);


    // First check that we are the correct-version, then proceed
    // to validate the request.
    //
    if (wVer == IPP_VERSION) {

        if (REQID_RANGE(dwId)) {

            // See if we're in the range of response codes.
            //
            if (SUCCESS_RANGE(wReq) || ERROR_RANGE(wReq))
                return WEBIPP_OK;


            // Validate supported operations.
            //
            cCnt = sizeof(s_pfnIpp) / sizeof(s_pfnIpp[0]);

            for (idx = 0; idx < cCnt; idx++) {

                if (wReq == s_pfnIpp[idx].wReq)
                    return WEBIPP_OK;
            }

            lpObj->wError = IPPRSP_ERROR_400;

        } else {

            lpObj->wError = IPPRSP_ERROR_400;
        }

    } else {

        lpObj->wError = IPPRSP_ERROR_503;
    }

    return WEBIPP_FAIL;
}


/*****************************************************************************\
* ipp_ValForm (Local Routine)
*
* Checks the tag-order for delimiters.
*
\*****************************************************************************/
BOOL ipp_ValForm(
    BYTE  bTag,
    PBYTE pbVal)
{
    DWORD idx;


    // Look to see if the tag is one of our supported groups for
    // this request.
    //
    for (idx = 0; pbVal[idx] != (BYTE)0; idx++) {

        // Is this a tag we're interested in.
        //
        if ((pbVal[idx] & 0x0F) == bTag) {

            // If we're already encountered this tag, then we
            // have an error (duplication of groups).
            //
            if ((pbVal[idx] & IPP_HIT) && !(pbVal[idx] & IPP_MULTIPLE))
                return FALSE;


            // Otherwise, mark this group as hit.
            //
            pbVal[idx] |= IPP_HIT;

            return TRUE;

        } else {

            // If this is not our tag, then we need to check
            // that this has at least been hit, or is an
            // optional group (verifies order).
            //
            if (IS_RANGE_DELIMITER(bTag))
                if (!(pbVal[idx] & (IPP_HIT | IPP_OPTIONAL)))
                    return FALSE;
        }
    }

    return TRUE;
}


/*****************************************************************************\
* ipp_AllocVal (Local Routine)
*
* Allocates a byte-array of tags that specify order.
*
\*****************************************************************************/
PBYTE ipp_AllocVal(
    WORD wReq)
{
    DWORD cCnt;
    DWORD idx;
    PBYTE pbGrp;
    PBYTE pbVal = NULL;


    // Build the byte-array for validation of form.
    //
    cCnt = sizeof(s_pfnIpp) / sizeof(s_pfnIpp[0]);

    for (idx = 0, pbGrp = NULL; idx < cCnt; idx++) {

        if (wReq == s_pfnIpp[idx].wReq) {

            pbGrp = s_pfnIpp[idx].pbReqForm;
            break;

        } else if (wReq == (IPP_RESPONSE | s_pfnIpp[idx].wReq)) {

            pbGrp = s_pfnIpp[idx].pbRspForm;
            break;
        }
    }


    if (pbGrp) {

        for (idx = 0; pbGrp[idx++] != (BYTE)0; );

        if (idx && (pbVal = (PBYTE)webAlloc(idx)))
            CopyMemory(pbVal, pbGrp, idx);
    }

    return pbVal;
}


/*****************************************************************************\
* ipp_ValidateRcvForm (Local Routine)
*
* Returns whether the header is well-formed.
*
\*****************************************************************************/
DWORD ipp_ValidateRcvForm(
    LPIPPOBJ lpObj,
    LPDWORD  lpcbSize)
{
    LPBYTE lpbTag;
    BYTE   bTag;
    DWORD  cbIdx;
    PBYTE  pbVal;
    DWORD  dwRet = WEBIPP_MOREDATA;


    // Zero out the return buffer.
    //
    *lpcbSize = 0;


    // Allocate our array of tags that represent order.
    //
    if (pbVal = ipp_AllocVal(lpObj->wReq)) {

        // Advance our pointer to the start of our attributes
        // in the byte-stream (i.e. skip version/request).
        //
        lpbTag = lpObj->lpIppHdr + IPP_SIZEOFHDR;


        // Hack!  Check to be sure that our headers always start
        // off with an operations-attribute-tag.
        //
        if (IS_TAG_DELIMITER(ipp_ReadByte(lpbTag, 0))) {

            // Traverse through the header, advancing through the attributes,
            // until the IPP_TAG_DEL_DATA is encountered.  This will verify
            // that we have a well-formed header.
            //
            for (cbIdx = IPP_SIZEOFHDR; lpbTag; ) {

                // Get the tag.
                //
                bTag = ipp_ReadByte(lpbTag, 0);


                // Only check our delimiter tags for this
                // validation.
                //
                if (IS_TAG_DELIMITER(bTag)) {

                    if (bTag == IPP_TAG_DEL_DATA) {

                        if (ipp_ValForm(bTag, pbVal)) {

                            *lpcbSize = (cbIdx + 1);

                            dwRet = WEBIPP_OK;

                            goto EndVal;

                        } else {

                            goto BadFrm;
                        }

                    } else {

                        if (ipp_ValForm(bTag, pbVal) == FALSE)
                            goto BadFrm;
                    }
                }


                // Advance to next Tag.  This routine also increments
                // the (cbIdx) count.  If we run out of bytes in the
                // header before we can get to the next-tag, then this
                // will return NULL.
                //
                lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr);
            }

        } else {

BadFrm:
            lpObj->wError = IPPRSP_ERROR_400;

            dwRet = WEBIPP_FAIL;
        }

EndVal:
        webFree(pbVal);

    } else {

        lpObj->wError = IPPRSP_ERROR_505;

        dwRet = WEBIPP_NOMEMORY;
    }

    return dwRet;
}


/*****************************************************************************\
* ipp_ValidateRcvCharSet (Local Routine)
*
* Returns whether we support the character-set.
*
\*****************************************************************************/
DWORD ipp_ValidateRcvCharSet(
    LPIPPOBJ lpObj)
{
    LPIPPATTR lpAttr;
    LPBYTE    lpbTag;
    BYTE      bTag;
    DWORD     cbIdx;
    DWORD     dwRet = WEBIPP_FAIL;


    // Advance our pointer to the start of our attributes
    // in the byte-stream (i.e. skip version/request).
    //
    lpbTag = lpObj->lpIppHdr + IPP_SIZEOFHDR;


    // Traverse through the header, advancing through the attributes,
    // until the IPP_TAG_DEL_DATA is encountered.  This will verify
    // that we have a well-formed header.
    //
    for (cbIdx = IPP_SIZEOFHDR; lpbTag; ) {

        bTag = ipp_ReadByte(lpbTag, 0);


        // If we walked through the end of our stream, then
        // the stream does not contain character-set information.
        //
        if (IS_TAG_DELIMITER(bTag) && (bTag != IPP_TAG_DEL_OPERATION))
            break;


        // If we are pointing at an attribute, then retrieve
        // it in a format we understand.
        //
        if (lpAttr = ipp_GetAttr(lpbTag, cbIdx, lpObj)) {

            // Check the name-type to see how to handle the value.
            //
            if (lpAttr->lpszName) {

                if (lstrcmpi(lpAttr->lpszName, s_szCharSet) == 0) {

                    if (lpObj->fState & IPPFLG_CHARSET) {

                        lpObj->wError = IPPRSP_ERROR_400;
                        dwRet         = WEBIPP_FAIL;

                    } else {

                        lpObj->fState |= IPPFLG_CHARSET;

                        if (lpAttr->cbValue > SIZE_CHARSET) {

                            lpObj->wError = IPPRSP_ERROR_409;

                        } else {

                            if (lstrcmpi((LPTSTR)lpAttr->lpValue, s_szUtf8) == 0) {

                                lpObj->uCPRcv = CP_UTF8;

                                dwRet = WEBIPP_OK;

                            } else if (lstrcmpi((LPTSTR)lpAttr->lpValue, s_szUsAscii) == 0) {

                                lpObj->uCPRcv = CP_ACP;

                                dwRet = WEBIPP_OK;

                            } else {

                                lpObj->wError = IPPRSP_ERROR_40D;
                            }
                        }
                    }
                }
            }

            ipp_RelAttr(lpAttr);
        }


        if (ERROR_RANGE(lpObj->wError))
            break;


        // Advance to next Tag.  This routine also increments
        // the (cbIdx) count.  If we run out of bytes in the
        // header before we can get to the next-tag, then this
        // will return NULL.
        //
        lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr);
    }

    if ((dwRet != WEBIPP_OK) && (lpObj->wError == IPPRSP_SUCCESS))
        lpObj->wError = IPPRSP_ERROR_400;

    return dwRet;
}


/*****************************************************************************\
* ipp_ValidateRcvLang (Local Routine)
*
* Returns whether we support the natural-language.
*
\*****************************************************************************/
DWORD ipp_ValidateRcvLang(
    LPIPPOBJ lpObj)
{
    LPIPPATTR lpAttr;
    LPBYTE    lpbTag;
    BYTE      bTag;
    DWORD     cbIdx;
    DWORD     dwRet = WEBIPP_FAIL;


    // Advance our pointer to the start of our attributes
    // in the byte-stream (i.e. skip version/request).
    //
    lpbTag = lpObj->lpIppHdr + IPP_SIZEOFHDR;


    // Traverse through the header, advancing through the attributes,
    // until the IPP_TAG_DEL_DATA is encountered.  This will verify
    // that we have a well-formed header.
    //
    for (cbIdx = IPP_SIZEOFHDR; lpbTag; ) {

        bTag = ipp_ReadByte(lpbTag, 0);

        // If we walked through the end of our stream, then
        // the stream does not contain natural-language information.
        //
        if (IS_TAG_DELIMITER(bTag) && (bTag != IPP_TAG_DEL_OPERATION))
            break;


        // If we are pointing at an attribute, then retrieve
        // it in a format we understand.
        //
        if (lpAttr = ipp_GetAttr(lpbTag, cbIdx, lpObj)) {

            // Check the name-type to see how to handle the value.
            //
            if (lpAttr->lpszName) {

                if (lstrcmpi(lpAttr->lpszName, s_szNaturalLanguage) == 0) {

                    if (lpObj->fState & IPPFLG_NATLANG) {

                        lpObj->wError = IPPRSP_ERROR_400;
                        dwRet         = WEBIPP_FAIL;

                    } else {

                        lpObj->fState |= IPPFLG_NATLANG;

                        if (lpAttr->cbValue > SIZE_NATLANG) {

                            lpObj->wError = IPPRSP_ERROR_409;

                        } else {

                            if (lstrcmpi((LPTSTR)lpAttr->lpValue, s_szEnUS) == 0) {

                                dwRet = WEBIPP_OK;

                            } else {

                                dwRet = WEBIPP_OK;
                            }
                        }
                    }
                }
            }

            ipp_RelAttr(lpAttr);
        }


        if (ERROR_RANGE(lpObj->wError))
            break;


        // Advance to next Tag.  This routine also increments
        // the (cbIdx) count.  If we run out of bytes in the
        // header before we can get to the next-tag, then this
        // will return NULL.
        //
        lpbTag = ipp_NextTag(lpObj->lpIppHdr, &cbIdx, lpObj->cbIppHdr);
    }

    if ((dwRet != WEBIPP_OK) && (lpObj->wError == IPPRSP_SUCCESS))
        lpObj->wError = IPPRSP_ERROR_400;

    return dwRet;
}


/*****************************************************************************\
* ipp_ValidateRcvHdr (Local Routine)
*
* Parses through the (lpbHdr) and returns whether it's a full (complete)
* header.  Essentially, this need only look through the byte-stream until
* it finds the IPP_TAG_DEL_DATA attribute.
*
* Returns the size of the header (in bytes).
*
\*****************************************************************************/
DWORD ipp_ValidateRcvHdr(
    LPIPPOBJ lpObj,
    LPDWORD  lpcbSize)
{
    LPBYTE lpbTag;
    DWORD  cbIdx;
    DWORD  cbSize;
    DWORD  dwRet;


    // Initialize our return-size so that we are reporting
    // clean data.
    //
    *lpcbSize = 0;


    // Make sure we have enough in our header to handle
    // the basic verification.
    //
    if (lpObj->cbIppHdr <= (IPP_SIZEOFHDR + IPP_SIZEOFTAG))
        return WEBIPP_MOREDATA;


    // Set the request-type for the header.  This will help
    // us determine the appropriate conversion to the data-
    // structure.
    //
    lpObj->idReq = ipp_ReadDWord(lpObj->lpIppHdr, IPP_SIZEOFINT);


    // Validate the fixed header values, then proceed with
    // other validation of the operational-attributes.
    //
    if ((dwRet = ipp_ValidateRcvReq(lpObj)) == WEBIPP_OK) {

        if ((dwRet = ipp_ValidateRcvForm(lpObj, &cbSize)) == WEBIPP_OK) {

            if ((dwRet = ipp_ValidateRcvCharSet(lpObj)) == WEBIPP_OK) {

                if ((dwRet = ipp_ValidateRcvLang(lpObj)) == WEBIPP_OK) {

                    *lpcbSize = cbSize;
                }
            }
        }
    }

    return dwRet;
}


/*****************************************************************************\
* ipp_ConvertIppToW32 (Local Routine)
*
* This routine takes in an IPP stream-buffer and generates the appropriate
* structure in which NT-Spooler-API's can process.
*
* Returns the pointer to the converted-header as well as the bytes that
* this converted header occupies.
*
\*****************************************************************************/
DWORD ipp_ConvertIppToW32(
    LPIPPOBJ lpObj,
    LPBYTE*  lplpRawHdr,
    LPDWORD  lpcbRawHdr)
{
    DWORD cCnt;
    DWORD idx;


    // Perform the request.  If the request has NULL function-pointers, then
    // the request/response is not supported.
    //
    cCnt = sizeof(s_pfnIpp) / sizeof(s_pfnIpp[0]);

    for (idx = 0; idx < cCnt; idx++) {

        // Check for request
        //
        if (lpObj->wReq == s_pfnIpp[idx].wReq)
            return ipp_IppToW32(lpObj, lplpRawHdr, lpcbRawHdr);


        // Check for response
        //
        if (lpObj->wReq == (IPP_RESPONSE | s_pfnIpp[idx].wReq))
            return s_pfnIpp[idx].pfnRcvRet(lpObj, lplpRawHdr, lpcbRawHdr);

    }

    lpObj->wError = IPPRSP_ERROR_501;

    return WEBIPP_FAIL;
}


/*****************************************************************************\
* WebIppSndData
*
* This routine takes the (lpRawHdr) and packages it up in IPP 1.1 protocol
* and returns the pointer to the Ipp-Header.
*
* Parameters:
* ----------
* dwReq      - Describes the type of IPP-Header to package.
* lpRawHdr   - Input pointer to raw (spooler) data-structure.
* cbRawHdr   - Input byte-count of (lpRawHdr).
* lplpIppHdr - Output pointer to IPP-Header stream.
* lpcbIppHdr - Output to byte-count of Ipp-Header stream (lplpIppHdr).
*
\*****************************************************************************/
DWORD WebIppSndData(
    WORD      wReq,
    LPREQINFO lpri,
    LPBYTE    lpRawHdr,
    DWORD     cbRawHdr,
    LPBYTE*   lplpIppHdr,
    LPDWORD   lpcbIppHdr)
{
    DWORD      cCnt;
    DWORD      idx;
    LPIPPATTRX pSnd;
    DWORD      cSnd;


    // Zero out the return pointers/sizes.
    //
    *lplpIppHdr = NULL;
    *lpcbIppHdr = 0;


    // Make sure the code-pages are something we can support.
    //
    if ((lpri->cpReq == CP_ACP) || (lpri->cpReq == CP_UTF8)) {

        // Perform the request.  If the request has NULL function-pointers,
        // then the request/response is not supported.
        //
        cCnt = sizeof(s_pfnIpp) / sizeof(s_pfnIpp[0]);

        for (idx = 0; idx < cCnt; idx++) {

            // Check for request.
            //
            if (wReq == s_pfnIpp[idx].wReq) {

                pSnd = s_pfnIpp[idx].paReq;
                cSnd = s_pfnIpp[idx].cbReq / sizeof(IPPATTRX);

                return ipp_W32ToIpp(wReq, lpri, lpRawHdr, pSnd, cSnd, lplpIppHdr, lpcbIppHdr);
            }


            // Check for response.
            //
            if (wReq == (IPP_RESPONSE | s_pfnIpp[idx].wReq)) {

                // Check response for any fail-cases.
                //
                if (SUCCESS_RANGE(((LPIPPRET_ALL)lpRawHdr)->wRsp)) {

                    pSnd = s_pfnIpp[idx].paRsp;
                    cSnd = s_pfnIpp[idx].cbRsp / sizeof(IPPATTRX);

                    return ipp_W32ToIpp(wReq, lpri, lpRawHdr, pSnd, cSnd, lplpIppHdr, lpcbIppHdr);
                }

                break;
            }
        }


        // If this was sent by our server, then we want to reply to the client
        // with an ipp-stream.
        //
        return ipp_FailureToIpp(wReq, lpri, lpRawHdr, lplpIppHdr, lpcbIppHdr);
    }

    return WEBIPP_FAIL;
}


/*****************************************************************************\
* WebIppRcvOpen
*
* This routine creates an IPP-state-object which parses IPP stream-data.  The
* parameter (dwReq) specifies which request we expect the stream to provide.
*
* We allocate a default-size buffer to contain the header.  If more memory
* is necessary, then it is reallocated to append the data.
*
\*****************************************************************************/
HANDLE WebIppRcvOpen(
    WORD wReq)
{
    LPIPPOBJ lpObj;


    if (lpObj = (LPIPPOBJ)webAlloc(sizeof(IPPOBJ))) {

        if (lpObj->pwlUns = (PWEBLST)new CWebLst()) {

            lpObj->wReq     = wReq;
            lpObj->wError   = IPPRSP_SUCCESS;
            lpObj->idReq    = 0;
            lpObj->uCPRcv   = CP_UTF8;
            lpObj->fState   = 0;
            lpObj->cbIppHdr = 0;
            lpObj->cbIppMax = IPP_BLOCK_SIZE;
            lpObj->lpRawDta = NULL;

            x_SetReq(lpObj->fReq, (wReq == IPP_REQ_ENUJOB ? IPP_REQENU : IPP_REQALL));


            // Allocate a default buffer-size to hold the IPP
            // header.  This may not be large enough to hold
            // the complete header so we reserve the right to
            // reallocate it until it contains the entire header.
            //
            if (lpObj->lpIppHdr = (LPBYTE)webAlloc(lpObj->cbIppMax)) {

                return (HANDLE)lpObj;
            }

            delete lpObj->pwlUns;
        }

        webFree(lpObj);
    }

    return NULL;
}


/*****************************************************************************\
* WebIppRcvData
*
* This routine takes in IPP stream-data and builds a complete IPP-Header.  It
* is possible that the header-information is not provided in one chunk of
* stream-data.  Therefore, we will not return the converted header information
* until our header is complete.
*
* Once the header is complete, it's returned in the output-buffer in the
* format that the caller can utilized in spooler-related calls.
*
* Not only does this handle the header, but it is also used to process raw
* stream-data which is returned unmodified to the caller.  In the case that
* we encounter data during the processing of the IPP-Header, we need to
* return an allocated buffer pointing to DWORD-Aligned bits.
*
* Parameters:
* ----------
* hObj       - Handle to the Ipp-Parse-Object.
* lpIppDta   - Input pointer to ipp-stream-data.  This is header and/or data.
* cbIppDta   - Input byte-count contained in the (lpIppData).
* lplpRawHdr - Output pointer to spooler-define (raw) structure.
* lpcbRawHdr - Output pointer to byte-count in (lplpRawHdr).
* lplpRawDta - Output pointer to data-stream.
* lpcbRawDta - Output pointer to byte-count in (lplpRawDta).
*
* Returns:
* -------
* WEBIPP_OK           - Information in lplpHdr or lplpData is valid.
* WEBIPP_FAIL         - Failed in validation.  Use WebIppGetError.
* WEBIPP_MOREDATA     - Needs more data to complete header.
* WEBIPP_BADHANDLE    - Ipp-Object-Handle is invalid.
* WEBIPP_NOMEMORY     - Failed allocation request (out-of-memory).
*
\*****************************************************************************/
DWORD WebIppRcvData(
    HANDLE  hObj,
    LPBYTE  lpIppDta,
    DWORD   cbIppDta,
    LPBYTE* lplpRawHdr,
    LPDWORD lpcbRawHdr,
    LPBYTE* lplpRawDta,
    LPDWORD lpcbRawDta)
{
    LPIPPOBJ lpObj;
    LPBYTE   lpNew;
    DWORD    cbSize;
    DWORD    cbData;
    DWORD    cBlks;
    DWORD    dwRet;


    // Initialize the output pointers to NULL.  We return two distinct
    // references since it is possible that the buffer passed in contains
    // both header and data.
    //
    *lplpRawHdr = NULL;
    *lpcbRawHdr = 0;
    *lplpRawDta = NULL;
    *lpcbRawDta = 0;


    // Process the stream-data.
    //
    if (lpObj = (LPIPPOBJ)hObj) {

        // If our header is complete, then the stream is raw-data meant
        // to be return directly.  In this case we only need to return the
        // data-stream passed in.
        //
        // Otherwise, the default-case is that we are building our header
        // from the stream-data.
        //
        if (lpObj->fState & IPPFLG_VALID) {

            // Free up the memory occupied by our header (only done on
            // the first hit of this block).  Since we aren't using
            // this anymore during the processing of the stream, we
            // shouldn't occupy any more memory than necessary.
            //
            if (lpObj->lpIppHdr) {

                webFree(lpObj->lpIppHdr);
                lpObj->lpIppHdr = NULL;
                lpObj->cbIppHdr = 0;


                // Likewise, if we had need of a temporary data-buffer
                // to hold aligned-bits, then we need to free this up
                // as well.
                //
                webFree(lpObj->lpRawDta);
                lpObj->lpRawDta = NULL;
            }


            // Return the data-stream passed in.
            //
            dwRet       = WEBIPP_OK;
            *lplpRawDta = lpIppDta;
            *lpcbRawDta = cbIppDta;

        } else {

            // Check to see if our buffer can accomodate the
            // size of the buffer being passed in.  If not, realloc
            // a new buffer to accomodate the new chunk.
            //
            if ((lpObj->cbIppHdr + cbIppDta) >= lpObj->cbIppMax) {

                // Determine the number of memory-blocks that we
                // need to hold the (cbData) coming in.
                //
                cBlks = (cbIppDta / IPP_BLOCK_SIZE) + 1;

                cbSize = lpObj->cbIppMax + (IPP_BLOCK_SIZE * cBlks);

                lpNew = (LPBYTE)webRealloc(lpObj->lpIppHdr,
                                           lpObj->cbIppMax,
                                           cbSize);

                if (lpNew != NULL) {

                    lpObj->lpIppHdr = lpNew;
                    lpObj->cbIppMax = cbSize;

                } else {

                    return WEBIPP_NOMEMORY;
                }
            }


            // Copy/Append the stream-data to our header-buffer.
            //
            memcpy(lpObj->lpIppHdr + lpObj->cbIppHdr, lpIppDta, cbIppDta);
            lpObj->cbIppHdr += cbIppDta;


            // Validate the header.  If this is successful, then we have
            // a well-formed-header.  Otherwise, we need to request
            // more data from the caller.  This returns the actual size
            // of the header in (cbSize).
            //
            if ((dwRet = ipp_ValidateRcvHdr(lpObj, &cbSize)) == WEBIPP_OK) {

                // Convert the IPP-Heade to a structure (stream)
                // that the caller understands (depends on dwReq).
                //
                dwRet = ipp_ConvertIppToW32(lpObj, lplpRawHdr, lpcbRawHdr);

                if (dwRet == WEBIPP_OK) {

                    // The validation returns the actual-size occupied by
                    // the header.  Therefore, if our (cbHdr) is larger, then
                    // the remaining information is pointing at data.
                    //
                    if (cbSize < lpObj->cbIppHdr) {

                        cbData = (lpObj->cbIppHdr - cbSize);

                        lpObj->lpRawDta = ipp_CopyAligned(lpObj->lpIppHdr + cbSize, cbData);

                        *lplpRawDta = lpObj->lpRawDta;
                        *lpcbRawDta = cbData;
                    }


                    // Set the flag indicating we have a full-header.  This
                    // assures that subsequent calls to this routine returns
                    // only the lpData.
                    //
                    lpObj->fState |= IPPFLG_VALID;
                }
            }
        }

    } else {

        dwRet = WEBIPP_BADHANDLE;
    }

    return dwRet;
}


/*****************************************************************************\
* WebIppRcvClose
*
* This routine Frees up our IPP object.  This is called once the caller wishes
* to end the parsing/handling of ipp-stream-data.
*
\*****************************************************************************/
BOOL WebIppRcvClose(
    HANDLE hObj)
{
    LPIPPOBJ lpObj;


    if (lpObj = (LPIPPOBJ)hObj) {

        webFree(lpObj->lpIppHdr);
        webFree(lpObj->lpRawDta);

        delete lpObj->pwlUns;

        webFree(lpObj);

        return TRUE;
    }

    return FALSE;
}


/*****************************************************************************\
* WebIppGetError
*
* This routine returns the specific error in the ipp-object.  This is called
* on a failure during the receive.
*
\*****************************************************************************/
WORD WebIppGetError(
    HANDLE hIpp)
{
    LPIPPOBJ lpObj;

    if (lpObj = (LPIPPOBJ)hIpp)
        return lpObj->wError;

    return IPPRSP_ERROR_500;
}


/*****************************************************************************\
* WebIppGetReqInfo
*
* This routine returns the request-info.
*
\*****************************************************************************/
BOOL WebIppGetReqInfo(
    HANDLE    hIpp,
    LPREQINFO lpri)
{
    LPIPPOBJ lpObj;


    if ((lpObj = (LPIPPOBJ)hIpp) && lpri) {

        lpri->idReq     = lpObj->idReq;
        lpri->cpReq     = lpObj->uCPRcv;
        lpri->pwlUns    = lpObj->pwlUns;
        lpri->bFidelity = (lpObj->fState & IPPFLG_USEFIDELITY);

        CopyMemory(lpri->fReq, lpObj->fReq, IPPOBJ_MASK_SIZE * sizeof(DWORD));

        return TRUE;
    }

    return FALSE;
}


/*****************************************************************************\
* WebIppLeToRsp
*
* This routine returns an IPP-Response-Code from a Win32-LastError.
*
\*****************************************************************************/
WORD WebIppLeToRsp(
    DWORD dwLastError)
{
    DWORD idx;
    DWORD cErrors;

    if (dwLastError == ERROR_SUCCESS)
        return IPPRSP_SUCCESS;


    // Lookup lasterror.
    //
    cErrors = sizeof(s_LEDef) / sizeof(s_LEDef[0]);

    for (idx = 0; idx < cErrors; idx++) {

        if (dwLastError == s_LEDef[idx].dwLE)
            return s_LEDef[idx].wRsp;
    }

    return IPPRSP_ERROR_500;
}


/*****************************************************************************\
* WebIppRspToLe
*
* This routine returns a Win32 LastError from an IPP-Response-Code.
*
\*****************************************************************************/
DWORD WebIppRspToLe(
    WORD wRsp)
{
    DWORD idx;
    DWORD cErrors;

    if (SUCCESS_RANGE(wRsp))
        return ERROR_SUCCESS;


    // Lookup lasterror.
    //
    cErrors = sizeof(s_LEIpp) / sizeof(s_LEIpp[0]);

    for (idx = 0; idx < cErrors; idx++) {

        if (wRsp == s_LEIpp[idx].wRsp)
            return s_LEIpp[idx].dwLE;
    }

    return ERROR_INVALID_DATA;
}


/*****************************************************************************\
* WebIppCreatePrtJobReq
*
* Creates a IPPREQ_PRTJOB structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPREQ_PRTJOB WebIppCreatePrtJobReq(
    BOOL    bValidate,
    LPCTSTR lpszUser,
    LPCTSTR lpszDoc,
    LPCTSTR lpszPrnUri)
{
    PIPPREQ_PRTJOB ppj;
    DWORD          cbSize;
    LPTSTR*        lpszSrc;
    LPTSTR         aszSrc[(sizeof(IPPREQ_PRTJOB) / sizeof(LPTSTR))];

    static DWORD s_Offs[] = {

        offs(LPIPPREQ_PRTJOB, pDocument),
        offs(LPIPPREQ_PRTJOB, pUserName),
        offs(LPIPPREQ_PRTJOB, pPrnUri),
        0xFFFFFFFF
    };


    // Calculate the size in bytes that are necesary for
    // holding the IPPREQ_PRTJOB information.
    //
    cbSize = sizeof(IPPREQ_PRTJOB)    +
             webStrSize(lpszUser)     +
             webStrSize(lpszDoc)      +
             webStrSize(lpszPrnUri);


    // Allocate the print-job-request structure.  The string
    // values are appended at the end of the structure.
    //
    if (ppj = (PIPPREQ_PRTJOB)webAlloc(cbSize)) {

        ppj->cbSize    = cbSize;
        ppj->bValidate = bValidate;

        lpszSrc    = aszSrc;
        *lpszSrc++ = (LPTSTR)lpszDoc;
        *lpszSrc++ = (LPTSTR)lpszUser;
        *lpszSrc++ = (LPTSTR)lpszPrnUri;

        ipp_PackStrings(aszSrc, (LPBYTE)ppj, s_Offs, ((LPBYTE)ppj) + cbSize);
    }

    return ppj;
}


/*****************************************************************************\
* WebIppCreateGetJobReq
*
* Creates a IPPREQ_GETJOB structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPREQ_GETJOB WebIppCreateGetJobReq(
    DWORD   idJob,
    LPCTSTR lpszPrnUri)
{
    PIPPREQ_GETJOB pgj;
    DWORD          cbSize;
    LPTSTR*        lpszSrc;
    LPTSTR         aszSrc[(sizeof(IPPREQ_GETJOB) / sizeof(LPTSTR))];

    static DWORD s_Offs[] = {

        offs(LPIPPREQ_GETJOB, pPrnUri),
        0xFFFFFFFF
    };


    // Calculate the size in bytes that are necesary for
    // holding the IPPREQ_GETJOB information.
    //
    cbSize = sizeof(IPPREQ_GETJOB) + webStrSize(lpszPrnUri);


    // Allocate the cancel-job-request structure.  The string
    // values are appended at the end of the structure.
    //
    if (pgj = (PIPPREQ_GETJOB)webAlloc(cbSize)) {

        pgj->cbSize = cbSize;
        pgj->idJob  = idJob;

        lpszSrc    = aszSrc;
        *lpszSrc++ = (LPTSTR)lpszPrnUri;

        ipp_PackStrings(aszSrc, (LPBYTE)pgj, s_Offs, ((LPBYTE)pgj) + cbSize);
    }

    return pgj;
}


/*****************************************************************************\
* WebIppCreateSetJobReq
*
* Creates a IPPREQ_SETJOB structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPREQ_SETJOB WebIppCreateSetJobReq(
    DWORD   idJob,
    DWORD   dwCmd,
    LPCTSTR lpszPrnUri)
{
    PIPPREQ_SETJOB psj;
    DWORD          cbSize;
    LPTSTR*        lpszSrc;
    LPTSTR         aszSrc[(sizeof(IPPREQ_SETJOB) / sizeof(LPTSTR))];

    static DWORD s_Offs[] = {

        offs(LPIPPREQ_SETJOB, pPrnUri),
        0xFFFFFFFF
    };


    // Calculate the size in bytes that are necesary for
    // holding the IPPREQ_SETJOB information.
    //
    cbSize = sizeof(IPPREQ_SETJOB) + webStrSize(lpszPrnUri);


    // Allocate the cancel-job-request structure.  The string
    // values are appended at the end of the structure.
    //
    if (psj = (PIPPREQ_SETJOB)webAlloc(cbSize)) {

        psj->cbSize = cbSize;
        psj->idJob  = idJob;
        psj->dwCmd  = dwCmd;

        lpszSrc    = aszSrc;
        *lpszSrc++ = (LPTSTR)lpszPrnUri;

        ipp_PackStrings(aszSrc, (LPBYTE)psj, s_Offs, ((LPBYTE)psj) + cbSize);
    }

    return psj;
}


/*****************************************************************************\
* WebIppCreateEnuJobReq
*
* Creates a IPPREQ_ENUJOB structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPREQ_ENUJOB WebIppCreateEnuJobReq(
    DWORD   cJobs,
    LPCTSTR lpszPrnUri)
{
    PIPPREQ_ENUJOB pgj;
    DWORD          cbSize;
    LPTSTR*        lpszSrc;
    LPTSTR         aszSrc[(sizeof(IPPREQ_ENUJOB) / sizeof(LPTSTR))];

    static DWORD s_Offs[] = {

        offs(LPIPPREQ_ENUJOB, pPrnUri),
        0xFFFFFFFF
    };


    // Calculate the size in bytes that are necesary for
    // holding the IPPREQ_ENUJOB information.
    //
    cbSize = sizeof(IPPREQ_ENUJOB) + webStrSize(lpszPrnUri);


    // Allocate the cancel-job-request structure.  The string
    // values are appended at the end of the structure.
    //
    if (pgj = (PIPPREQ_ENUJOB)webAlloc(cbSize)) {

        pgj->cbSize = cbSize;
        pgj->cJobs  = cJobs;

        lpszSrc    = aszSrc;
        *lpszSrc++ = (LPTSTR)lpszPrnUri;

        ipp_PackStrings(aszSrc, (LPBYTE)pgj, s_Offs, ((LPBYTE)pgj) + cbSize);
    }

    return pgj;
}


/*****************************************************************************\
* WebIppCreateSetPrnReq
*
* Creates a IPPREQ_SETPRN structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPREQ_SETPRN WebIppCreateSetPrnReq(
    DWORD   dwCmd,
    LPCTSTR lpszUsrName,
    LPCTSTR lpszPrnUri)
{
    PIPPREQ_SETPRN psp;
    DWORD          cbSize;
    LPTSTR*        lpszSrc;
    LPTSTR         aszSrc[(sizeof(IPPREQ_SETPRN) / sizeof(LPTSTR))];

    static DWORD s_Offs[] = {

        offs(LPIPPREQ_SETPRN, pUserName),
        offs(LPIPPREQ_SETPRN, pPrnUri),
        0xFFFFFFFF
    };


    // Calculate the size in bytes that are necesary for
    // holding the IPPREQ_SETPRN information.
    //
    cbSize = sizeof(IPPREQ_SETPRN)    +
             webStrSize(lpszUsrName)  +
             webStrSize(lpszPrnUri);


    // Allocate the set-prn-request structure.  The string
    // values are appended at the end of the structure.
    //
    if (psp = (PIPPREQ_SETPRN)webAlloc(cbSize)) {

        psp->cbSize = cbSize;
        psp->dwCmd  = dwCmd;

        lpszSrc    = aszSrc;
        *lpszSrc++ = (LPTSTR)lpszUsrName;
        *lpszSrc++ = (LPTSTR)lpszPrnUri;

        ipp_PackStrings(aszSrc, (LPBYTE)psp, s_Offs, ((LPBYTE)psp) + cbSize);
    }

    return psp;
}


/*****************************************************************************\
* WebIppCreateGetPrnReq
*
* Creates a IPPREQ_GETPRN structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPREQ_GETPRN WebIppCreateGetPrnReq(
    DWORD   dwAttr,
    LPCTSTR lpszPrnUri)
{
    PIPPREQ_GETPRN pgp;
    DWORD          cbSize;
    LPTSTR*        lpszSrc;
    LPTSTR         aszSrc[(sizeof(IPPREQ_GETPRN) / sizeof(LPTSTR))];

    static DWORD s_Offs[] = {

        offs(LPIPPREQ_GETPRN, pPrnUri),
        0xFFFFFFFF
    };


    // Calculate the size in bytes that are necesary for
    // holding the IPPREQ_GETPRN information.
    //
    cbSize = sizeof(IPPREQ_GETPRN) + webStrSize(lpszPrnUri);


    // Allocate the get-prt-attribute-request structure.  The string
    // values are appended at the end of the structure.
    //
    if (pgp = (PIPPREQ_GETPRN)webAlloc(cbSize)) {

        pgp->cbSize = cbSize;
        pgp->dwAttr = dwAttr;

        lpszSrc    = aszSrc;
        *lpszSrc++ = (LPTSTR)lpszPrnUri;

        ipp_PackStrings(aszSrc, (LPBYTE)pgp, s_Offs, ((LPBYTE)pgp) + cbSize);
    }

    return pgp;
}


/*****************************************************************************\
* WebIppCreateAuthReq
*
* Creates a IPPREQ_AUTH structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPREQ_AUTH WebIppCreateAuthReq(VOID)
{
    PIPPREQ_AUTH pfa;
    DWORD        cbSize;


    // Calculate the size in bytes that are necesary for
    // holding the IPPREQ_AUTH information.
    //
    cbSize = sizeof(IPPREQ_AUTH);


    // Allocate the request structure.
    //
    if (pfa = (PIPPREQ_AUTH)webAlloc(cbSize)) {

        pfa->cbSize = cbSize;
    }

    return pfa;
}


/*****************************************************************************\
* WebIppCreateJobRet
*
* Creates a IPPRET_JOB structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPRET_JOB WebIppCreateJobRet(
    WORD           wRsp,
    BOOL           bRet,
    BOOL           bValidate,
    LPJOB_INFO_2   lpji2,
    LPJOB_INFO_IPP lpipp)
{
    PIPPRET_JOB pjr;
    DWORD       cbSize;


    // Calculate our structure size.
    //
    cbSize = sizeof(IPPRET_JOB) + ipp_SizeofIPPJI2(lpji2, lpipp);


    // Build our response.
    //
    if (pjr = (PIPPRET_JOB)webAlloc(cbSize)) {

        pjr->cbSize      = cbSize;
        pjr->dwLastError = WebIppRspToLe(wRsp);
        pjr->wRsp        = wRsp;
        pjr->bRet        = bRet;
        pjr->bValidate   = bValidate;

        ipp_BuildJI2(&pjr->ji, lpji2, lpipp, ((LPBYTE)pjr) + cbSize);
    }

    return pjr;
}


/*****************************************************************************\
* WebIppCreatePrnRet
*
* Creates a IPPRET_PRN structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPRET_PRN WebIppCreatePrnRet(
    WORD               wRsp,
    BOOL               bRet,
    LPPRINTER_INFO_2   lppi2,
    LPPRINTER_INFO_IPP lpipp)
{
    PIPPRET_PRN ppr;
    DWORD       cbSize;


    // Calculate our structure size.
    //
    cbSize = sizeof(IPPRET_PRN) + ipp_SizeofIPPPI2(lppi2, lpipp);


    // Build our response.
    //
    if (ppr = (PIPPRET_PRN)webAlloc(cbSize)) {

        ppr->cbSize      = cbSize;
        ppr->dwLastError = WebIppRspToLe(wRsp);
        ppr->wRsp        = wRsp;
        ppr->bRet        = bRet;

        ipp_BuildPI2(&ppr->pi, lppi2, lpipp, ((LPBYTE)ppr) + cbSize);
    }

    return ppr;
}


/*****************************************************************************\
* WebIppCreateEnuJobRet
*
* Creates a IPPRET_ENUJOB structure.  This is the structure necessary
* for calling WebIpp* API's.
*  
\*****************************************************************************/
PIPPRET_ENUJOB WebIppCreateEnuJobRet(
    WORD     wRsp,
    BOOL     bRet,
    DWORD    cbJobs,
    DWORD    cJobs,
    LPIPPJI2 lpjiSrc)
{
    PIPPRET_ENUJOB pgj;
    LPIPPJI2       lpjiDst;
    LPBYTE         lpbEnd;
    DWORD          idx;
    DWORD          cbSize;

    cbSize = sizeof(IPPRET_ENUJOB) + ((cJobs && cbJobs && lpjiSrc) ? cbJobs : 0);

    if (pgj = (PIPPRET_ENUJOB)webAlloc(cbSize)) {

        pgj->cbSize      = cbSize;
        pgj->dwLastError = WebIppRspToLe(wRsp);
        pgj->wRsp        = wRsp;
        pgj->bRet        = bRet;
        pgj->cItems      = 0;
        pgj->cbItems     = 0;
        pgj->pItems      = NULL;


        if (cJobs && cbJobs && lpjiSrc) {

            // Initialize defaults.
            //
            pgj->cItems  = cJobs;
            pgj->cbItems = cbJobs;
            pgj->pItems  = (LPIPPJI2)(((LPBYTE)pgj) + sizeof(IPPRET_ENUJOB));


            lpjiDst = pgj->pItems;
            lpbEnd  = ((LPBYTE)lpjiDst) + cbJobs;

            for (idx = 0; idx < cJobs; idx++) {

                lpbEnd = ipp_BuildJI2(&lpjiDst[idx], &lpjiSrc[idx].ji2, &lpjiSrc[idx].ipp, lpbEnd);
            }
        }
    }

    return pgj;
}


/*****************************************************************************\
* WebIppCreateBadRet
*
* Creates a IPPRET_ALL structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPRET_ALL WebIppCreateBadRet(
    WORD wRsp,
    BOOL bRet)
{
    PIPPRET_ALL pra;
    DWORD       cbSize;

    cbSize = sizeof(IPPRET_ALL);

    if (pra = (PIPPRET_ALL)webAlloc(cbSize)) {

        pra->cbSize      = cbSize;
        pra->dwLastError = WebIppRspToLe(wRsp);
        pra->wRsp        = wRsp;
        pra->bRet        = bRet;
    }

    return pra;
}


/*****************************************************************************\
* WebIppCreateAuthRet
*
* Creates a IPPRET_AUTH structure.  This is the structure necessary
* for calling WebIpp* API's.
*
\*****************************************************************************/
PIPPRET_AUTH WebIppCreateAuthRet(
    WORD wRsp,
    BOOL bRet)
{
    return (PIPPRET_AUTH)WebIppCreateBadRet(wRsp, bRet);
}


/*****************************************************************************\
* WebIppFreeMem
*
* Free memory allocated through the WebIpp routines.
*
\*****************************************************************************/
BOOL WebIppFreeMem(
    LPVOID lpMem)
{
    return webFree(lpMem);
}


/*****************************************************************************\
* WebIppCvtJI2toIPPJI2
*
* Converts an array of JOB_INFO_2 structures to an array of IPPJI2 structures.
*
* This code is only called from inetsrv/spool.cxx. It was better to change the
* the buffer calculation here than in the calling function since IPPJI2 is a 
* web printing only call. This will return the new required Job size in the 
* cbJobs Parameter that is passed in.
*
\*****************************************************************************/
LPIPPJI2 WebIppCvtJI2toIPPJI2(
    LPCTSTR      lpszJobBase,
    LPDWORD      lpcbJobs,
    DWORD        cJobs,
    LPJOB_INFO_2 lpjiSrc)
{
    LPBYTE   lpbEnd;
    DWORD    idx;
    DWORD    cbSize;
    DWORD    cbUri;
    LPIPPJI2 lpjiDst = NULL;

    WEB_IPP_ASSERT(lpcbJobs);

    if (*lpcbJobs && cJobs && lpjiSrc) {

        // For each job, we need to add enough to hold the extra
        // information.
        //
        cbUri  = 2*(webStrSize(lpszJobBase) + sizeof(DWORD)) * cJobs;
        // There can be two of these strings allocated, one for the JobUri and the 
        // other for the Printer Uri
        cbSize = (sizeof(IPPJI2) - sizeof(JOB_INFO_2)) * cJobs + *lpcbJobs + cbUri;
        // cbJobs already contains the correct size for the JOB_INFO_2 structure and its
        // strings we need the space for the JOB_INFO_IPP part of the structure plus the 
        // extra strings that will be added.

        *lpcbJobs = cbSize;  // Pass the required size back


        if (lpjiDst = (LPIPPJI2)webAlloc(cbSize)) {

            // Position string end at the end of our buffer.
            //
            lpbEnd = ((LPBYTE)lpjiDst) + cbSize;


            // For each job, copy.
            //
            for (idx = 0; idx < cJobs; idx++) {

                lpbEnd = ipp_CopyJI2toIPPJI2(&lpjiDst[idx],
                                             &lpjiSrc[idx],
                                             (LPTSTR)lpszJobBase,
                                             lpbEnd);
            }
        }
    }

    return lpjiDst;
}

/*****************************************************************************\
* WebIppPackJI2
*
* This takes in a JOB_INFO_2 structure whose members are note packed correctly
* and returns a correctly filled out and allocated JOB_INFO_2. It does not 
* copy the DEVMODE and SECURITY-DESCRIPTOR fields.
*
\*****************************************************************************/
LPJOB_INFO_2 WebIppPackJI2(
    IN  LPJOB_INFO_2  lpji2,
    OUT LPDWORD       lpcbSize,
    IN  ALLOCATORFN   pfnAlloc
    )  {

    WEB_IPP_ASSERT(lpji2);
    WEB_IPP_ASSERT(pfnAlloc);
    WEB_IPP_ASSERT(lpcbSize);

    *lpcbSize = 0;

    // This is used to perform the ipp_PackStrings operation
    LPTSTR  aszSrc[(sizeof(IPPJI2) / sizeof(LPTSTR))];
    
    // First get the required allocation size
    DWORD dwSize = ipp_SizeofIPPJI2( lpji2, NULL ) + sizeof(JOB_INFO_2);
    
    // Allocate the memory required to store the data

    LPJOB_INFO_2 pji2out = (LPJOB_INFO_2)pfnAlloc( dwSize );
    
    if (pji2out) {
        // First, do a straight copy of the memory from the incoming JI2 to the outgoing
        // ji2

        LPTSTR* lpszSrc = aszSrc;
        LPBYTE  lpbEnd  = (LPBYTE)pji2out + dwSize;

        *lpcbSize = dwSize;     

        CopyMemory( pji2out, lpji2, sizeof(JOB_INFO_2) );

        pji2out->pDevMode            = NULL; // These two pointers cannot be set
        pji2out->pSecurityDescriptor = NULL;

        *lpszSrc++ = lpji2->pPrinterName;
        *lpszSrc++ = lpji2->pMachineName;
        *lpszSrc++ = lpji2->pUserName;
        *lpszSrc++ = lpji2->pDocument;
        *lpszSrc++ = lpji2->pNotifyName;
        *lpszSrc++ = lpji2->pDatatype;
        *lpszSrc++ = lpji2->pPrintProcessor;
        *lpszSrc++ = lpji2->pParameters;
        *lpszSrc++ = lpji2->pDriverName;
        *lpszSrc++ = lpji2->pStatus;

        ipp_PackStrings(aszSrc, (LPBYTE)pji2out, s_JI2Off, lpbEnd);
    }

    return pji2out;
 }