/*
//
//   ADOBE SYSTEMS INCORPORATED
//   Copyright (C) 2000-2003 Adobe Systems Incorporated
//   All rights reserved.
//
//   NOTICE: Adobe permits you to use, modify, and distribute this file
//   in accordance with the terms of the Adobe license agreement
//   accompanying it. If you have received this file from a source other
//   than Adobe, then your use, modification, or distribution of it
//   requires the prior written permission of Adobe.
//
*/

/*
//
// Decryption.cpp
//
*/

#include <iostream>
#include <memory.h>
#include "Decryption.h"
#include "MyPDFLibUtils.h"
#include "stdlib.h"
#include "ASExtraCalls.h"

#ifdef MAC_ENV
#include "macUtils.h"
#endif

using namespace  std;

// default constructor: init PDFL and data member
Decryption::Decryption()
{
               this->orgDoc  =  NULL;
}

// constructor: init PDFL and attemp to authorize
// open permission on input encrypted document
Decryption::Decryption( const   char  *inputDoc)
{
#ifdef MAC_ENV
                ASPathName  asPath =  GetMacPath(( char  *)inputDoc);
#else
                ASPathName  asPath =  ASFileSysCreatePathName(
                                ASGetDefaultFileSys(),
                                ASAtomFromString( "Cstring" ),
                               ( void *)inputDoc,
                                NULL);
#endif

                ASErrorCode  errCode = 0;

                DURING
                                /* open encrypted document with an authorization callback proc */
                               this->orgDoc  =  PDDocOpenEx(asPath,  ASGetDefaultFileSys(),
                                                ASCallbackCreateProto(PDAuthProcEx, &(this->ClientAuthUserProc)), 0,  true );
                HANDLER
                               errCode =  ERRORCODE;
                END_HANDLER
               
                ASFileSysReleasePath(ASGetDefaultFileSys(), asPath);
               
                if (errCode)
                                ASRaise(errCode);
}

// destrcutor: terminate PDFL and clean up if necessary
Decryption::~Decryption()
{
                if (this->orgDoc)
                                PDDocClose(this->orgDoc);
}


// helper function to set contents extracted from the encrypted document
// to pages of the non-encrypted output document
void   Decryption::SetPageContent(PDPage  pdPage,  PDEContent  pdeContent)
{
                PDEFilterArray  filterRec;
               memset( &filterRec, 0,  sizeof (PDEFilterArray) );
               filterRec.numFilters  = 1;
               filterRec.spec[0].name  =  ASAtomFromString( "FlateDecode" );

        CosObj  cosPage =  PDPageGetCosObj(pdPage);
        CosDoc  cosDoc =  CosObjGetDoc(cosPage);
        CosObj  cosContents;
        CosObj  cosResources;
DURING
                // extract content and resource objects from
                // the page of the encrypted document
                PDEContentToCosObj(pdeContent,  kPDEContentToPage, 0, 0, cosDoc, &filterRec,
                                             &cosContents, &cosResources);

                // place content and resource objects into page */
                CosDictPut(cosPage,  ASAtomFromString( "Contents" ), cosContents);
                CosDictPut(cosPage,  ASAtomFromString( "Resources" ), cosResources);
HANDLER
                RERAISE();
END_HANDLER
}

// member function to operate on the decrypted document
// operation performed:
// extracting pages from the decrypted document and save
// them in reverse order to a new document with no security
// settings.
void   Decryption::ProcessDocument( const   char  *outputDoc)
{
                PDPage  orgPage;
                PDEContent  orgContent;

DURING
                // output document to save the extraced pages to
                PDDoc  newDoc =  PDDocCreate();

                // loop through the pages of the originally encrypted
                // document and set their contents to new pages of
                // the new output document
                for  ( int  i = 0; i <  PDDocGetNumPages(this->orgDoc); i++)
               {
                               orgPage =  PDDocAcquirePage(this->orgDoc, i);
                                ASFixedRect  mediaBox;
                                ASErrorCode  errCode = 0;

                                PDPageGetMediaBox(orgPage, &mediaBox);
                               orgContent =  PDPageAcquirePDEContent(orgPage, 0);

                                PDPage  newPage =  PDDocCreatePage(newDoc,  PDBeforeFirstPage, mediaBox);
                                DURING
                                                SetPageContent(newPage, orgContent);
                                HANDLER
                                               errCode =  ERRORCODE;
                                END_HANDLER;
                               
                                PDPageReleasePDEContent(orgPage, 0);
                                PDPageRelease(newPage);
                                PDPageRelease(orgPage);

                                if (errCode)
                                                ASRaise(errCode);
               }

#ifdef MAC_ENV
                ASPathName  asPath =  GetMacPath(( char  *)outputDoc);
#else
                ASPathName  asPath =  ASFileSysCreatePathName(
                                ASGetDefaultFileSys(),
                                ASAtomFromString( "Cstring" ),
                               ( void *)outputDoc,
                                NULL);

                //ASPathName asPath = ASPathFromPlatformPath((void *)outputDoc);
#endif
                // save the new non-encrypted output document with pages
                // in reverse order of the original
                PDDocSave(newDoc,  PDSaveFull  |  PDSaveLinearized  |  PDSaveCollectGarbage,
                               asPath,  ASGetDefaultFileSys(), 0, 0);
                ASFileSysReleasePath(ASGetDefaultFileSys(), asPath);
HANDLER
                RERAISE();
END_HANDLER
               cout <<  "Decrypted document "  << outputDoc <<  " generated successfully."  << endl << endl;
               
}

static   ASBool   useUTF  =  false ;
#define MAX_PWCHARS 255

void   useUtf(PDDoc  pdDoc)
{
                if  (pdDoc !=  NULL) {
                                StdSecurityData  secData = (StdSecurityData)PDDocGetNewSecurityData(pdDoc);
                                if  (!secData)
                                               secData = (StdSecurityData)PDDocGetSecurityData(pdDoc);
                                if  (secData)
                                               useUTF = (secData->revision  >= 5);
               }
}

const   ASUTF16Val  *  GetPasswordFromFile( const   char * input)
{
               
               #ifdef MAC_ENV
                                ASPathName  filePath =  GetMacPath(( char  *)input);
               #else
                                ASPathName  filePath =  ASFileSysCreatePathName(ASGetDefaultFileSys(),ASAtomFromString( "Cstring" ),( void *)input,NULL);
               #endif

                                const   ASUTF16Val  * uniPwd = 0;
                                ASFile  asFile;
                                ASErrorCode  errCode;
                                DURING
                                               errCode =  ASFileSysOpenFile(NULL, filePath,  ASFILE_READ, &asFile);
                                if  (!errCode)
                                               {                                                             
                                                                char  readPwd[MAX_PWCHARS];
                                                                ASTArraySize  bytesRead =  ASFileRead(asFile, readPwd,  MAX_PWCHARS);
                                                                ASFileClose(asFile);
                                                                ASText  asReadPwd =  ASTextFromSizedPDText(readPwd, bytesRead);
                                                               uniPwd =  ASTextGetUnicodeCopy(asReadPwd,  kUTF16HostEndian);
                                                                if  (asReadPwd){
                                                                                ASTextMakeEmptyClear(asReadPwd);
                                                                                ASTextDestroy(asReadPwd);
                                                               }
                                               }

                                HANDLER
                                               errCode =  ERRORCODE;
                                END_HANDLER

                                ASFileSysReleasePath(ASGetDefaultFileSys(), filePath);
                                if  (errCode){
                                               cerr <<  "Error in opening/reading of given password file" << endl;
                                                ASRaise(errCode);
                               }
                                return  uniPwd;
}

// PDAuthProcEx callback called by PDDocOpenEx to authorize open permission
ACCB1  ASBool   ACCB2   Decryption::ClientAuthUserProc(PDDoc  pdDoc,  void  *clientData)
{
                // supply the user password
                //ASUCS_GetPasswordFromUnicode expects unicode password in utf-16 host-endian
                //file containing password should be encoded in utf-16 big-endian with BOM
                const   char  *userPwdFile =  "../ExampleFiles/user-password.txt" ;
                ASUTF16Val* userPwd = (ASUTF16Val*)GetPasswordFromFile(userPwdFile);
               
                //performing a check on security handler version
                useUtf(pdDoc);

                //Converts user input of a password to a form that can be used by PDFL to open a file.
                void  *prfldPasswd = 0;
                ASUCS_GetPasswordFromUnicode(userPwd, ( void **)(&prfldPasswd), useUTF);

                // request permission to change security settings
                PDPermReqObj  permReqObj =  PDPermReqObjDoc;
                PDPermReqOpr  permReqOpr =  PDPermReqOprOpen;
                PDPermReqStatus  permReqStatus;
DURING
                /* Request Open permission by passing in the user password */
               permReqStatus =  PDDocPermRequest(pdDoc, permReqObj, permReqOpr, ( void  *)prfldPasswd);
HANDLER
                if  (prfldPasswd)
                                ASfree(prfldPasswd);
                if  (userPwd)
                                ASfree(userPwd);
                RERAISE();
END_HANDLER
                switch  (permReqStatus)
               {
                                case   PDPermReqGranted:
                                               cout <<  "Password verified. Permission to open document granted!"  << endl;
                                                break ;
                                case   PDPermReqDenied:
                                               cout <<  "Invalid user password.\nDocument open request denied!"  << endl;
                                                break ;
                                case   PDPermReqUnknownObject:
                                               cout <<  "Target object unknown for the permisson request."  << endl;
                                                break ;
                                case   PDPermReqUnknownOperation:
                                               cout <<  "Target operation unknown for the permission request."  << endl;
                                                break ;
                                default :
                                               cout <<  "Something is wrong!"  << endl;
               }
                if  (prfldPasswd)
                                ASfree(prfldPasswd);
                if  (userPwd)
                                ASfree(userPwd);
                return  (permReqStatus ==  PDPermReqGranted);
}

// member function to authorize permissions to operate on the document
ACCB1  ASBool   ACCB2   Decryption::ClientAuthMasterProc()
{
                // provide the master password
                //ASUCS_GetPasswordFromUnicode expects unicode password in utf-16 host-endian
                //file containing password should be encoded in utf-16 big-endian with BOM
                const   char  *masterPwdFile =  "../ExampleFiles/master-password.txt" ;
                ASUTF16Val* masterPwd = (ASUTF16Val*)GetPasswordFromFile(masterPwdFile);

                //Converts user input of a password to a form that can be used by PDFL to open a file.
                void  *prfldPasswd = 0;
                ASUCS_GetPasswordFromUnicode(masterPwd, ( void **)(&prfldPasswd), useUTF);

                // request permission to change security settings
                PDPermReqObj  permReqObj =  PDPermReqObjDoc;
                PDPermReqOpr  permReqOpr =  PDPermReqOprSecure;
                PDPermReqStatus  permReqStatus;

                DURING
                                /* Request permission by passing in the master password */
                               permReqStatus =  PDDocPermRequest(this->orgDoc, permReqObj, permReqOpr, ( void  *)prfldPasswd);
                HANDLER
                                char  errmsg[256];
                               cout <<  "ClientAuthMasterProc> Error "  <<  ErrGetCode(ERRORCODE) <<  ": "  <<
                                                ASGetErrorString(ERRORCODE, errmsg,  sizeof (errmsg)) << endl;
                                if  (prfldPasswd)
                                                ASfree(prfldPasswd);
                                if  (masterPwd)
                                                ASfree(masterPwd);
                                return   false ;
                END_HANDLER

                switch  (permReqStatus)
               {
                                case   PDPermReqGranted:
                                               cout <<  "Requested permissions granted!"  << endl;
                                                break ;
                                case   PDPermReqDenied:
                                               cout <<  "Permission request denied!"  << endl;
                                                break ;
                                case   PDPermReqUnknownObject:
                                               cout <<  "Target object unknown for the permisson request."  << endl;
                                                break ;
                                case   PDPermReqUnknownOperation:
                                               cout <<  "Target operation unknown for the permission request."  << endl;
                                                break ;
                                default :
                                               cout <<  "Something is wrong!"  << endl;
               }
                if  (prfldPasswd)
                                ASfree(prfldPasswd);
                if  (masterPwd)
                                ASfree(masterPwd);
                return  (permReqStatus ==  PDPermReqGranted);
}