/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 *  JAR.C
 *
 *  Jarnature.
 *  Routines common to signing and validating.
 *
 */

#include "jar.h"
#include "jarint.h"

static void jar_destroy_list (ZZList *list);

static int jar_find_first_cert 
    (JAR_Signer *signer, int type, JAR_Item **it);

/*
 *  J A R _ n e w 
 *
 *  Create a new instantiation of a manifest representation.
 *  Use this as a token to any calls to this API.
 *
 */

JAR *JAR_new (void)
  {
  JAR *jar;

  if ((jar = (JAR*)PORT_ZAlloc (sizeof (JAR))) == NULL)
    goto loser;

  if ((jar->manifest = ZZ_NewList()) == NULL)
    goto loser;

  if ((jar->hashes = ZZ_NewList()) == NULL)
    goto loser;

  if ((jar->phy = ZZ_NewList()) == NULL)
    goto loser;

  if ((jar->metainfo = ZZ_NewList()) == NULL)
    goto loser;

  if ((jar->signers = ZZ_NewList()) == NULL)
    goto loser;

  return jar;

loser:

  if (jar)
    {
    if (jar->manifest)
      ZZ_DestroyList (jar->manifest);

    if (jar->hashes)
      ZZ_DestroyList (jar->hashes);

    if (jar->phy)
      ZZ_DestroyList (jar->phy);

    if (jar->metainfo)
      ZZ_DestroyList (jar->metainfo);

    if (jar->signers)
      ZZ_DestroyList (jar->signers);

    PORT_Free (jar);
    }

  return NULL;
  }

/* 
 *  J A R _ d e s t r o y
 *
 *  Godzilla.
 *
 */

void PR_CALLBACK JAR_destroy (JAR *jar)
  {
  PORT_Assert( jar != NULL );

  if (jar == NULL)
    return;

  if (jar->fp) JAR_FCLOSE ((PRFileDesc*)jar->fp);

  if (jar->url)      PORT_Free (jar->url);
  if (jar->filename) PORT_Free (jar->filename);

  /* Free the linked list elements */

  jar_destroy_list (jar->manifest);
  ZZ_DestroyList (jar->manifest);

  jar_destroy_list (jar->hashes);
  ZZ_DestroyList (jar->hashes);

  jar_destroy_list (jar->phy);
  ZZ_DestroyList (jar->phy);

  jar_destroy_list (jar->metainfo);
  ZZ_DestroyList (jar->metainfo);

  jar_destroy_list (jar->signers);
  ZZ_DestroyList (jar->signers);

  PORT_Free (jar);
  }

static void jar_destroy_list (ZZList *list)
  {
  ZZLink *link, *oldlink;

  JAR_Item *it;

  JAR_Physical *phy;
  JAR_Digest *dig;
  JAR_Cert *fing;
  JAR_Metainfo *met;
  JAR_Signer *signer;

  if (list && !ZZ_ListEmpty (list))
    {
    link = ZZ_ListHead (list);

    while (!ZZ_ListIterDone (list, link))
      {
      it = link->thing;
      if (!it) goto next;

      if (it->pathname) PORT_Free (it->pathname);
 
      switch (it->type)
        {
        case jarTypeMeta:

          met = (JAR_Metainfo *) it->data;
          if (met)
            {
            if (met->header) PORT_Free (met->header);
            if (met->info) PORT_Free (met->info);
            PORT_Free (met);
            }
          break;

	case jarTypePhy:

          phy = (JAR_Physical *) it->data;
          if (phy)
            PORT_Free (phy);
          break;

        case jarTypeSign:

          fing = (JAR_Cert *) it->data;
          if (fing)
            {
            if (fing->cert)
              CERT_DestroyCertificate (fing->cert);
            if (fing->key)
              PORT_Free (fing->key);
            PORT_Free (fing);
            }
	  break;

        case jarTypeSect:
        case jarTypeMF:
        case jarTypeSF:

          dig = (JAR_Digest *) it->data;
          if (dig)
            {
            PORT_Free (dig);
            }
          break;

        case jarTypeOwner:

          signer = (JAR_Signer *) it->data;

          if (signer)
            JAR_destroy_signer (signer);

          break;

        default:

          /* PORT_Assert( 1 != 2 ); */
          break;
        }

      PORT_Free (it);

    next:

      oldlink = link;
      link = link->next;

      ZZ_DestroyLink (oldlink);
      }
    }
  }

/*
 *  J A R _ g e t _ m e t a i n f o
 *
 *  Retrieve meta information from the manifest file.
 *  It doesn't matter whether it's from .MF or .SF, does it?
 *
 */

int JAR_get_metainfo
    (JAR *jar, char *name, char *header, void **info, unsigned long *length)
  {
  JAR_Item *it;

  ZZLink *link;
  ZZList *list;

  JAR_Metainfo *met;

  PORT_Assert( jar != NULL && header != NULL );

  if (jar == NULL || header == NULL)
    return JAR_ERR_PNF;

  list = jar->metainfo;

  if (ZZ_ListEmpty (list))
    return JAR_ERR_PNF;

  for (link = ZZ_ListHead (list); 
       !ZZ_ListIterDone (list, link); 
       link = link->next)
    {
    it = link->thing;
    if (it->type == jarTypeMeta)
      {
      if ((name && !it->pathname) || (!name && it->pathname))
        continue;

      if (name && it->pathname && strcmp (it->pathname, name))
        continue;

      met = (JAR_Metainfo *) it->data;

      if (!PORT_Strcasecmp (met->header, header))
        {
        *info = PORT_Strdup (met->info);
        *length = PORT_Strlen (met->info);
        return 0;
        }
      }
    }

  return JAR_ERR_PNF;
  }

/*
 *  J A R _ f i n d
 *
 *  Establish the search pattern for use
 *  by JAR_find_next, to traverse the filenames
 *  or certificates in the JAR structure.
 *
 *  See jar.h for a description on how to use.
 *
 */

JAR_Context *JAR_find (JAR *jar, char *pattern, jarType type)
  {
  JAR_Context *ctx;

  PORT_Assert( jar != NULL );

  if (!jar)
    return NULL;

  ctx = (JAR_Context *) PORT_ZAlloc (sizeof (JAR_Context));

  if (ctx == NULL)
    return NULL;

  ctx->jar = jar;

  if (pattern)
    {
    if ((ctx->pattern = PORT_Strdup (pattern)) == NULL)
      {
      PORT_Free (ctx);
      return NULL;
      }
    }

  ctx->finding = type;

  switch (type)
    {
    case jarTypeMF:     ctx->next = ZZ_ListHead (jar->hashes);
                        break;

    case jarTypeSF:
    case jarTypeSign:   ctx->next = NULL;
                        ctx->nextsign = ZZ_ListHead (jar->signers);
                        break;

    case jarTypeSect:   ctx->next = ZZ_ListHead (jar->manifest);
                        break;

    case jarTypePhy:    ctx->next = ZZ_ListHead (jar->phy);
                        break;

    case jarTypeOwner:  if (jar->signers)
                          ctx->next = ZZ_ListHead (jar->signers);
                        else
                          ctx->next = NULL;
                        break;

    case jarTypeMeta:   ctx->next = ZZ_ListHead (jar->metainfo);
                        break;

    default:            PORT_Assert( 1 != 2);
                        break;
    }

  return ctx;
  }

/*
 *  J A R _ f i n d _ e n d
 *
 *  Destroy the find iterator context.
 *
 */

void JAR_find_end (JAR_Context *ctx)
  {
  PORT_Assert( ctx != NULL );

  if (ctx)
    {
    if (ctx->pattern) 
      PORT_Free (ctx->pattern);
    PORT_Free (ctx);
    }
  }

/*
 *  J A R _ f i n d _ n e x t
 *
 *  Return the next item of the given type
 *  from one of the JAR linked lists.
 *
 */

int JAR_find_next (JAR_Context *ctx, JAR_Item **it)
  {
  JAR *jar;
  ZZList *list = NULL;

  int finding;

  JAR_Signer *signer = NULL;

  PORT_Assert( ctx != NULL );
  PORT_Assert( ctx->jar != NULL );

  jar = ctx->jar;

  /* Internally, convert jarTypeSign to jarTypeSF, and return
     the actual attached certificate later */

  finding = (ctx->finding == jarTypeSign) ? jarTypeSF : ctx->finding;

  if (ctx->nextsign)
    {
      if (ZZ_ListIterDone (jar->signers, ctx->nextsign))
       {
       *it = NULL;
       return -1;
       }
    PORT_Assert (ctx->nextsign->thing != NULL);
    signer = (JAR_Signer*)ctx->nextsign->thing->data;
    }


  /* Find out which linked list to traverse. Then if
     necessary, advance to the next linked list. */

  while (1)
    {
    switch (finding)
      {
      case jarTypeSign:    /* not any more */
                           PORT_Assert( finding != jarTypeSign );
                           list = signer->certs;
                           break;

      case jarTypeSect:    list = jar->manifest;
                           break;

      case jarTypePhy:     list = jar->phy;
                           break;

      case jarTypeSF:      /* signer, not jar */
                           PORT_Assert( signer != NULL );
                           list = signer->sf;
                           break;

      case jarTypeMF:      list = jar->hashes;
                           break;

      case jarTypeOwner:   list = jar->signers;
                           break;

      case jarTypeMeta:    list = jar->metainfo;
                           break;

      default:             PORT_Assert( 1 != 2 );
                           break;
      }

    if (list == NULL)
      {
      *it = NULL;
      return -1;
      }

    /* When looping over lists of lists, advance
       to the next signer. This is done when multiple
       signers are possible. */

    if (ZZ_ListIterDone (list, ctx->next))
      {
      if (ctx->nextsign && jar->signers) 
        {
        ctx->nextsign = ctx->nextsign->next;
        if (!ZZ_ListIterDone (jar->signers, ctx->nextsign)) 
          {
          PORT_Assert (ctx->nextsign->thing != NULL);

          signer = (JAR_Signer*)ctx->nextsign->thing->data;
          PORT_Assert( signer != NULL );

          ctx->next = NULL;
          continue;
          }
        }
      *it = NULL;
      return -1;
      }

    /* if the signer changed, still need to fill
       in the "next" link */

    if (ctx->nextsign && ctx->next == NULL)
      {
      switch (finding)
        {
        case jarTypeSF:

          ctx->next = ZZ_ListHead (signer->sf);
          break;

        case jarTypeSign:

          ctx->next = ZZ_ListHead (signer->certs);
          break;
        }
      }

    PORT_Assert( ctx->next != NULL );


    while (!ZZ_ListIterDone (list, ctx->next))
      {
      *it = ctx->next->thing;
      ctx->next = ctx->next->next;

      if (!*it || (*it)->type != finding)
        continue;

      if (ctx->pattern && *ctx->pattern)
        {
        if (PORT_Strcmp ((*it)->pathname, ctx->pattern))
          continue;
        }

      /* We have a valid match. If this is a jarTypeSign
         return the certificate instead.. */

      if (ctx->finding == jarTypeSign)
        {
        JAR_Item *itt;

        /* just the first one for now */
        if (jar_find_first_cert (signer, jarTypeSign, &itt) >= 0) 
           {
           *it = itt;
           return 0;
           }

        continue;      
        }

      return 0;
      }

    } /* end while */
  }

static int jar_find_first_cert 
    (JAR_Signer *signer, int type, JAR_Item **it)
  {
  ZZLink *link;
  ZZList *list;

  int status = JAR_ERR_PNF;

  list = signer->certs;

  *it = NULL;

  if (ZZ_ListEmpty (list))
    {
    /* empty list */
    return JAR_ERR_PNF;
    }

  for (link = ZZ_ListHead (list); 
       !ZZ_ListIterDone (list, link); 
       link = link->next)
    {
    if (link->thing->type == type)
      {
      *it = link->thing;
      status = 0;
      break;
      }
    }

  return status;
  }

JAR_Signer *JAR_new_signer (void)
  {
  JAR_Signer *signer;

  signer = (JAR_Signer *) PORT_ZAlloc (sizeof (JAR_Signer));

  if (signer == NULL)
    goto loser;


  /* certs */
  signer->certs = ZZ_NewList();

  if (signer->certs == NULL)
    goto loser;


  /* sf */
  signer->sf = ZZ_NewList();

  if (signer->sf == NULL)
    goto loser;


  return signer;


loser:

  if (signer)
    {
    if (signer->certs) 
      ZZ_DestroyList (signer->certs);

    if (signer->sf) 
      ZZ_DestroyList (signer->sf);

    PORT_Free (signer);
    }

  return NULL;
  }

void JAR_destroy_signer (JAR_Signer *signer)
  {
  if (signer)
    {
    if (signer->owner) PORT_Free (signer->owner);
    if (signer->digest) PORT_Free (signer->digest);

    jar_destroy_list (signer->sf);
    ZZ_DestroyList (signer->sf);

    jar_destroy_list (signer->certs);
    ZZ_DestroyList (signer->certs);

    PORT_Free (signer);
    }
  }

JAR_Signer *jar_get_signer (JAR *jar, char *basename)
  {
  JAR_Item *it;
  JAR_Context *ctx;

  JAR_Signer *candidate;
  JAR_Signer *signer = NULL;

  ctx = JAR_find (jar, NULL, jarTypeOwner);

  if (ctx == NULL)
    return NULL;

  while (JAR_find_next (ctx, &it) >= 0)
    {
    candidate = (JAR_Signer *) it->data;
    if (*basename == '*' || !PORT_Strcmp (candidate->owner, basename))
      {
      signer = candidate;
      break;
      }
    }

  JAR_find_end (ctx);

  return signer;
  }

/*
 *  J A R _ g e t _ f i l e n a m e
 *
 *  Returns the filename associated with
 *  a JAR structure.
 *
 */

char *JAR_get_filename (JAR *jar)
  {
  return jar->filename;
  }

/*
 *  J A R _ g e t _ u r l
 *
 *  Returns the URL associated with
 *  a JAR structure. Nobody really uses this now.
 *
 */

char *JAR_get_url (JAR *jar)
  {
  return jar->url;
  }

/*
 *  J A R _ s e t _ c a l l b a c k
 *
 *  Register some manner of callback function for this jar. 
 *
 */

int JAR_set_callback (int type, JAR *jar, 
      int (*fn) (int status, JAR *jar, 
         const char *metafile, char *pathname, char *errortext))
  {
  if (type == JAR_CB_SIGNAL)
    {
    jar->signal = fn;
    return 0;
    }
  else
    return -1;
  }

/*
 *  Callbacks
 *
 */

/* To return an error string */
char *(*jar_fn_GetString) (int) = NULL;

/* To return an MWContext for Java */
void *(*jar_fn_FindSomeContext) (void) = NULL;

/* To fabricate an MWContext for FE_GetPassword */
void *(*jar_fn_GetInitContext) (void) = NULL;

void
JAR_init_callbacks
     ( 
     char *(*string_cb)(int), 
     void *(*find_cx)(void), 
     void *(*init_cx)(void) 
     )
  {
  jar_fn_GetString = string_cb;
  jar_fn_FindSomeContext = find_cx;
  jar_fn_GetInitContext = init_cx;
  }

/*
 *  J A R _ g e t _ e r r o r
 *
 *  This is provided to map internal JAR errors to strings for
 *  the Java console. Also, a DLL may call this function if it does
 *  not have access to the XP_GetString function.
 *
 *  These strings aren't UI, since they are Java console only.
 *
 */

char *JAR_get_error (int status)
  { 
  char *errstring = NULL;

  switch (status)
    {
    case JAR_ERR_GENERAL:
      errstring = "General JAR file error";
      break;

    case JAR_ERR_FNF:
      errstring = "JAR file not found";
      break;

    case JAR_ERR_CORRUPT:
      errstring = "Corrupt JAR file";
      break;

    case JAR_ERR_MEMORY:
      errstring = "Out of memory";
      break;

    case JAR_ERR_DISK:
      errstring = "Disk error (perhaps out of space)";
      break;

    case JAR_ERR_ORDER:
      errstring = "Inconsistent files in META-INF directory";
      break;

    case JAR_ERR_SIG:
      errstring = "Invalid digital signature file";
      break;

    case JAR_ERR_METADATA:
      errstring = "JAR metadata failed verification";
      break;

    case JAR_ERR_ENTRY:
      errstring = "No Manifest entry for this JAR entry";
      break;

    case JAR_ERR_HASH:
      errstring = "Invalid Hash of this JAR entry";
      break;

    case JAR_ERR_PK7:
      errstring = "Strange PKCS7 or RSA failure";
      break;

    case JAR_ERR_PNF:
      errstring = "Path not found inside JAR file";
      break;

    default:
      if (jar_fn_GetString)
        {
        errstring = jar_fn_GetString (status);
        }
      else
        {
        /* this is not a normal situation, and would only be
           called in cases of improper initialization */

        char *err;

        err = (char*)PORT_Alloc (40);
        if (err)
          PR_snprintf (err, 39,  "Error %d\n", status);
        else
          err = "Error! Bad! Out of memory!";

        return err;
        }
      break;
    }

  return errstring;
  }
