/*
% Copyright (C) 2003-2025 GraphicsMagick Group
% Copyright (C) 2002 ImageMagick Studio
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                        M   M  EEEEE  TTTTT   AAA                            %
%                        MM MM  E        T    A   A                           %
%                        M M M  EEE      T    AAAAA                           %
%                        M   M  E        T    A   A                           %
%                        M   M  EEEEE    T    A   A                           %
%                                                                             %
%                                                                             %
%                   Read/Write Embedded Image Profiles                        %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                             William Radcliffe                               %
%                                 July 2001                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/blob.h"
#include "magick/log.h"
#include "magick/magick.h"
#include "magick/profile.h"
#include "magick/utility.h"

/*
  Forward declarations.
*/
static unsigned int
  WriteMETAImage(const ImageInfo *,Image *);

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s M E T A                                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method IsMETA returns True if the image format type, identified by the
%  magick string, is META.
%
%  The format of the IsMETA method is:
%
%      unsigned int IsMETA(const unsigned char *magick,const size_t length)
%
%  A description of each parameter follows:
%
%    o status:  Method IsMETA returns True if the image format type is META.
%
%    o magick: This string is generally the first few bytes of an image file
%      or blob.
%
%    o length: Specifies the length of the magick string.
%
%
*/
#ifdef IMPLEMENT_IS_FUNCTION
static unsigned int IsMETA(const unsigned char *magick,const size_t length)
{
  if (length < 4)
    return(False);
  if (LocaleNCompare((char *) magick,"8BIM",4) == 0)
    return(True);
  if (LocaleNCompare((char *) magick,"APP1",4) == 0)
    return(True);
  if (LocaleNCompare((char *) magick,"\034\002",2) == 0)
    return(True);
  return(False);
}
#endif

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d M E T A I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ReadMETAImage reads a META image file and returns it.  It
%  allocates the memory necessary for the new Image structure and returns a
%  pointer to the new image.
%
%  The format of the ReadMETAImage method is:
%
%      Image *ReadMETAImage(const ImageInfo *image_info,
%        ExceptionInfo *exception)
%
%  Decompression code contributed by Kyle Shorter.
%
%  A description of each parameter follows:
%
%    o image: Method ReadMETAImage returns a pointer to the image after
%      reading.  A null image is returned if there is a memory shortage or
%      if the image cannot be read.
%
%    o image_info: Specifies a pointer to an ImageInfo structure.
%
%    o exception: return any errors or warnings in this structure.
%
%
*/
#define BUFFER_SZ 4096

static const struct
{
  const unsigned char
    len;

  const char
    code[7],
    val;
} html_codes[] = {
#ifdef HANDLE_GT_LT
  { 4,"&lt;",'<' },
  { 4,"&gt;",'>' },
#endif
  { 5,"&amp;",'&' },
  { 6,"&quot;",'"' },
  { 6,"&apos;",'\''}
};

static int stringnicmp(const char *p,const char *q,size_t n)
{
  register long
    i,
    j;

  if (p == q)
    return(0);
  if (p == (char *) NULL)
    return(-1);
  if (q == (char *) NULL)
    return(1);
  while ((*p != '\0') && (*q != '\0'))
  {
    if ((*p == '\0') || (*q == '\0'))
      break;
    i=(*p);
    if (islower(i))
      i=toupper(i);
    j=(*q);
    if (islower(j))
      j=toupper(j);
    if (i != j)
      break;
    n--;
    if (n == 0)
      break;
    p++;
    q++;
  }
  return(toupper((int) *p)-toupper((int) *q));
}

/*
   This routine parses the next HTML ampersand code ("&FOO;") from the
   provided string and replaces the HTML ampersand code with the
   equivalent character, moving the remainder of the string forward.
   The return value is the number of characters consumed (length of
   code minus one character).
 */
static size_t convertHTMLcodes(char *s)
{
  size_t len;
  size_t i;
  int val;

  for (i = 0, len = 0; (i < 7U) && (s[i] != '\0') ; i++)
    {
      if (s[i] == ';')
        {
          len=i+1;
          break;
        }
    }


  if ((len == 0) || (s == (char*) NULL) || (*s=='\0'))
    return 0;

  /*
    Check for hex or decimal character escape.

    e.g. hex: "&#xA0;", decimal "&#160;"

    Note that scanf is expected to parse the number in hex or decimal
    and that it does not require the terminating ';' to appear in
    order to parse the numeric argument.
  */
  if ((len > 3) && (s[1] == '#') && (sscanf(s,"&#%d;",&val) == 1))
    {
      size_t o = 3;
      while (s[o] != ';')
        {
          o++;
          if (o > 5)
            break;
        }
      if (o < 6)
        (void) memmove(s+1,s+1+o,strlen(s+1+o)+1);
      *s = val;
      return o;
    }
  else /* Symbolic code, e.g. "&amp;" */
    {
      int
        i,
        codes = ArraySize(html_codes);

      for (i=0; i < codes; i++)
      {
        if (html_codes[i].len <= len)
          if (stringnicmp(s, html_codes[i].code, (size_t) (html_codes[i].len)) == 0)
            {
              (void) memmove(s+1, s+html_codes[i].len, strlen(s+html_codes[i].len)+1);
              *s = html_codes[i].val;
              return html_codes[i].len-1;
            }
      }
    }
  return 0;
}

static char *super_fgets(char **b, size_t *blen, Image *file)
{
  int
    c;

  size_t
    len;

  unsigned char
    *p,
    *q;

  len=*blen;
  p=(unsigned char *) (*b);
  for (q=p; ; q++)
  {
    c=ReadBlobByte(file);
    if (c == EOF || c == '\n')
      break;
    if ((size_t) (q-p+1) >= len)
      {
        unsigned char
          *new_p;

        size_t
          tlen;

        tlen=(size_t) (q-p);
        len += MaxTextExtent;
        new_p=MagickReallocateResourceLimitedMemory(unsigned char *,p,(len+2));
        if (new_p == (unsigned char *) NULL)
          {
            MagickFreeResourceLimitedMemory(p);
            break;
          }
        p=new_p;
        q=p+tlen;
      }
    *q=(unsigned char) c;
  }
  *b=(char *)p;
  *blen=0;
  if (p != (unsigned char *) NULL)
    {
      size_t
        tlen;

      tlen=(size_t) (q-p);
      if (tlen == 0)
        return (char *) NULL;
      p[tlen] = '\0';
      *blen=++tlen;
    }
  return *b;
}

#define BUFFER_SZ 4096
#define IPTC_ID 1028
#define THUMBNAIL_ID 1033

static long parse8BIM(Image *ifile, Image *ofile)
{
  char
    brkused,
    quoted,
    *line = (char *) NULL,
    *token = (char *) NULL,
    *newstr = (char *) NULL,
    *name = (char *) NULL;

  int
    state,
    next;

  unsigned char
    dataset;

  unsigned int
    recnum;

  size_t
    inputlen = BUFFER_SZ;

  ssize_t
    savedolen = 0L,
    outputlen = 0L;

  ExtendedSignedIntegralType
    savedpos,
    currentpos;

  TokenInfo
    token_info;

  dataset = 0;
  recnum = 0;
  line = MagickAllocateResourceLimitedMemory(char *,inputlen);
  if (line == (char *) NULL)
    goto parse8BIM_failure;
  savedpos = 0;
  while(super_fgets(&line,&inputlen,ifile)!=NULL)
  {
    state=0;
    next=0;

    token = MagickAllocateResourceLimitedMemory(char *,inputlen);
    if (token == (char *) NULL)
      goto parse8BIM_failure;
    newstr = MagickAllocateResourceLimitedMemory(char *,inputlen);
    if (newstr == (char *) NULL)
      goto parse8BIM_failure;
    while (Tokenizer(&token_info, 0, token, inputlen, line,
          (char *) "", (char *) "=",
      (char *) "\"", 0, &brkused,&next,&quoted)==0)
    {
      if (state == 0)
        {
          size_t
            name_alloc;

          int
            state,
            next;

          char
            brkused,
            quoted;

          state=0;
          next=0;
          while(Tokenizer(&token_info, 0, newstr, inputlen, token, (char *) "",
            (char *) "#", (char *) "", 0, &brkused, &next, &quoted)==0)
          {
            switch (state)
            {
              case 0:
                if (strcmp(newstr,"8BIM")==0)
                  dataset = 255;
                else
                  dataset = (unsigned char) MagickAtoI(newstr);
                break;
              case 1:
                recnum = MagickAtoI(newstr);
                break;
              case 2:
                name_alloc = strlen(newstr)+1;
                name = MagickAllocateResourceLimitedMemory(char *,name_alloc);
                if (name == (char *) NULL)
                  goto parse8BIM_failure;
                (void) strlcpy(name,newstr,name_alloc);
                break;
            }
            state++;
          }
        }
      else
        if (state == 1)
          {
            int
              next;

            size_t
              len;

            char
              brkused,
              quoted;

            next=0;
            len = (unsigned long) strlen(token);
            while (Tokenizer(&token_info,0, newstr, inputlen, token, (char *) "",
              (char *) "&", (char *) "", 0, &brkused, &next, &quoted)==0)
            {
              if (brkused && next > 0)
                {
                  size_t
                    codes_len;

                  char
                    *s = &token[next-1];

                  codes_len = convertHTMLcodes(s);
                  if (codes_len > len)
                    len = 0;
                  else
                    len -= codes_len;
                }
            }

            if (dataset == 255U)
              {
                unsigned char
                  nlen = 0;

                int
                  i;

                if (savedolen > 0)
                  {
                    long diff = outputlen - savedolen;
                    currentpos = TellBlob(ofile);
                    if (currentpos < 0)
                      goto parse8BIM_failure;
                    if (SeekBlob(ofile,savedpos,SEEK_SET) != savedpos)
                      goto parse8BIM_failure;
                    if (WriteBlobMSBLong(ofile,diff) < 4)
                      goto parse8BIM_failure;
                    if (SeekBlob(ofile,currentpos,SEEK_SET) != currentpos)
                      goto parse8BIM_failure;
                    savedolen = 0L;
                  }
                if (outputlen & 1)
                  {
                    if (WriteBlobByte(ofile,0x00) < 1)
                      goto parse8BIM_failure;
                    outputlen++;
                  }
                if (WriteBlobString(ofile,"8BIM") < 4)
                  goto parse8BIM_failure;
                if (WriteBlobMSBShort(ofile,recnum) < 2)
                  goto parse8BIM_failure;
                outputlen += 6;
                if (name)
                  nlen = (unsigned char) strlen(name);
                if (WriteBlobByte(ofile,nlen) < 1)
                  goto parse8BIM_failure;
                outputlen++;
                for (i=0; i<nlen; i++)
                  if (WriteBlobByte(ofile,name[i]) < 1)
                    goto parse8BIM_failure;
                outputlen += nlen;
                if (!(nlen&1))
                  {
                    if (WriteBlobByte(ofile,0x00) < 1)
                      goto parse8BIM_failure;
                    outputlen++;
                  }
                if (recnum != IPTC_ID)
                  {
                    if (WriteBlobMSBLong(ofile, (const magick_uint32_t) len) < 4)
                      goto parse8BIM_failure;
                    outputlen += 4;

                    next=0;
                    outputlen += len;
                    while (len--)
                      if (WriteBlobByte(ofile,token[next++]) < 1)
                        goto parse8BIM_failure;

                    if (outputlen & 1)
                      {
                        if (WriteBlobByte(ofile,0x00) < 1)
                          goto parse8BIM_failure;
                        outputlen++;
                      }
                  }
                else
                  {
                    /* patch in a fake length for now and fix it later */
                    savedpos = TellBlob(ofile);
                    if (savedpos < 0)
                      goto parse8BIM_failure;
                    if (WriteBlobMSBLong(ofile,0xFFFFFFFFUL) < 4)
                      goto parse8BIM_failure;
                    outputlen += 4;
                    savedolen = outputlen;
                  }
              }
            else
              {
                if (len <= 0x7FFF)
                  {
                    if (WriteBlobByte(ofile,0x1c) < 1)
                      goto parse8BIM_failure;
                    if (WriteBlobByte(ofile,dataset) < 1)
                      goto parse8BIM_failure;
                    if (WriteBlobByte(ofile,recnum & 255) < 1)
                      goto parse8BIM_failure;
                    if (WriteBlobMSBShort(ofile,(const magick_uint16_t) len) < 2)
                      goto parse8BIM_failure;
                    outputlen += 5;
                    next=0;
                    outputlen += len;
                    while (len--)
                      if (WriteBlobByte(ofile,token[next++]) < 1)
                        goto parse8BIM_failure;
                  }
              }
          }
      state++;
    }
    MagickFreeResourceLimitedMemory(token);
    MagickFreeResourceLimitedMemory(newstr);
    MagickFreeResourceLimitedMemory(name);
  }
  MagickFreeResourceLimitedMemory(line);
  if (savedolen > 0)
    {
      long diff = outputlen - savedolen;

      currentpos = TellBlob(ofile);
      if (currentpos < 0)
        goto parse8BIM_failure;

      if (SeekBlob(ofile,savedpos,SEEK_SET) != savedpos)
        goto parse8BIM_failure;
      if (WriteBlobMSBLong(ofile,diff) < 4)
        goto parse8BIM_failure;
      if (SeekBlob(ofile,currentpos,SEEK_SET) != currentpos)
        goto parse8BIM_failure;
      savedolen = 0L;
    }
  return outputlen;

 parse8BIM_failure:
  MagickFreeResourceLimitedMemory(token);
  MagickFreeResourceLimitedMemory(newstr);
  MagickFreeResourceLimitedMemory(name);
  MagickFreeResourceLimitedMemory(line);
  return 0L;
}

static char *super_fgets_w(char **b, size_t *blen, Image *file)
{
  size_t
    len;

  int
    c;

  unsigned char
    *p,
    *q;

  len=*blen;
  p=(unsigned char *) (*b);
  for (q=p; ; q++)
  {
    c=ReadBlobLSBShort(file);
    if (c == ((unsigned short) ~0U) || c == '\n')
      break;
   if (EOFBlob(file))
      break;
   if ((size_t) (q-p+1) >= len)
      {
        unsigned char
          *new_p;

        size_t
          tlen;

        tlen=(size_t) (q-p);
        len += MaxTextExtent;
        new_p=MagickReallocateResourceLimitedMemory(unsigned char *,p,(len+2));
        if (new_p == (unsigned char *) NULL)
          {
            MagickFreeResourceLimitedMemory(p);
            break;
          }
        p=new_p;
        q=p+tlen;
      }
    *q=(unsigned char) c;
  }
  *b=(char *)p;
  *blen=0;
  if (p != (unsigned char *) NULL)
    {
      size_t
        tlen;

      tlen=(size_t) (q-p);
      if (tlen == 0)
        return (char *) NULL;
      p[tlen] = '\0';
      *blen=++tlen;
    }
  return *b;
}

static long parse8BIMW(Image *ifile, Image *ofile)
{
  char
    brkused,
    quoted,
    *line = (char *) NULL,
    *token = (char *) NULL,
    *newstr = (char *) NULL,
    *name = (char *) NULL;

  int
    state,
    next;

  unsigned char
    dataset;

  unsigned int
    recnum;

  size_t
    inputlen = BUFFER_SZ;

  ssize_t
    savedolen = 0L,
    outputlen = 0L;

  ExtendedSignedIntegralType
    savedpos,
    currentpos;

  TokenInfo
    token_info;

  dataset = 0;
  recnum = 0;
  line = MagickAllocateResourceLimitedMemory(char *,inputlen);
  if (line == (char *) NULL)
    goto parse8BIMW_failure;
  name = token = (char *)NULL;
  savedpos = 0;
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                        "META CODER Parse8BIM");
  while(super_fgets_w(&line,&inputlen,ifile)!=NULL)
  {
    state=0;
    next=0;

    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "META CODER Parse8BIM: %s (%lu)",line, (unsigned long) inputlen);
    token = MagickAllocateResourceLimitedMemory(char *,inputlen);
    if (token == (char *) NULL)
      goto parse8BIMW_failure;
    newstr = MagickAllocateResourceLimitedMemory(char *,inputlen);
    if (newstr == (char *) NULL)
      goto parse8BIMW_failure;
    while (Tokenizer(&token_info, 0, token, inputlen, line,
          (char *) "", (char *) "=",
      (char *) "\"", 0, &brkused,&next,&quoted)==0)
    {
      if (state == 0)
        {
          size_t
            name_alloc;

          int
            state,
            next;

          char
            brkused,
            quoted;

          state=0;
          next=0;
          while(Tokenizer(&token_info, 0, newstr, inputlen, token, (char *) "",
            (char *) "#", (char *) "", 0, &brkused, &next, &quoted)==0)
          {
            switch (state)
            {
              case 0:
                if (strcmp(newstr,"8BIM")==0)
                  dataset = 255;
                else
                  dataset = (unsigned char) MagickAtoI(newstr);
                break;
              case 1:
                recnum = MagickAtoI(newstr);
                break;
              case 2:
                name_alloc = strlen(newstr)+1;
                name = MagickAllocateResourceLimitedMemory(char *,name_alloc);
                if (name == (char *) NULL)
                  goto parse8BIMW_failure;
                (void) strlcpy(name,newstr,name_alloc);
                break;
            }
            state++;
          }
        }
      else
        if (state == 1)
          {
            int
              next;

            size_t
              len;

            char
              brkused,
              quoted;

            next=0;
            len = (unsigned long) strlen(token);
            while (Tokenizer(&token_info,0, newstr, inputlen, token, (char *) "",
              (char *) "&", (char *) "", 0, &brkused, &next, &quoted)==0)
            {
              if (brkused && next > 0)
                {
                  size_t
                    codes_len;

                  char
                    *s = &token[next-1];

                  codes_len = convertHTMLcodes(s);
                  if (codes_len > len)
                    len = 0;
                  else
                    len -= codes_len;
                }
            }

            if (dataset == 255)
              {
                unsigned char
                  nlen = 0;

                int
                  i;

                if (savedolen > 0)
                  {
                    long diff = outputlen - savedolen;
                    currentpos = TellBlob(ofile);
                    if (currentpos < 0)
                      goto parse8BIMW_failure;
                    if (SeekBlob(ofile,savedpos,SEEK_SET) != savedpos)
                      goto parse8BIMW_failure;
                    if (WriteBlobMSBLong(ofile,diff) != 4)
                      goto parse8BIMW_failure;
                    if (SeekBlob(ofile,currentpos,SEEK_SET) != currentpos)
                      goto parse8BIMW_failure;
                    savedolen = 0L;
                  }
                if (outputlen & 1)
                  {
                    if (WriteBlobByte(ofile,0x00) != 1)
                      goto parse8BIMW_failure;
                    outputlen++;
                  }
                if (WriteBlobString(ofile,"8BIM") < 4)
                  goto parse8BIMW_failure;
                if (WriteBlobMSBShort(ofile,recnum) < 2)
                  goto parse8BIMW_failure;
                outputlen += 6;
                if (name)
                  nlen = (unsigned char) strlen(name);
                if (WriteBlobByte(ofile,nlen) != 1)
                  goto parse8BIMW_failure;
                outputlen++;
                for (i=0; i<nlen; i++)
                  if (WriteBlobByte(ofile,name[i]) != 1)
                    goto parse8BIMW_failure;
                outputlen += nlen;
                if (!(nlen&1))
                  {
                    if (WriteBlobByte(ofile,0x00) != 1)
                      goto parse8BIMW_failure;
                    outputlen++;
                  }
                if (recnum != IPTC_ID)
                  {
                    if (WriteBlobMSBLong(ofile, (const magick_uint32_t) len) != 4)
                      goto parse8BIMW_failure;
                    outputlen += 4;

                    next=0;
                    outputlen += len;
                    while (len--)
                      if (WriteBlobByte(ofile,token[next++]) != 1)
                        goto parse8BIMW_failure;

                    if (outputlen & 1)
                      {
                        if (WriteBlobByte(ofile,0x00) != 1)
                          goto parse8BIMW_failure;
                        outputlen++;
                      }
                  }
                else
                  {
                    /* patch in a fake length for now and fix it later */
                    savedpos = TellBlob(ofile);
                    if (savedpos < 0)
                      goto parse8BIMW_failure;
                    if (WriteBlobMSBLong(ofile,0xFFFFFFFFUL) != 4)
                      goto parse8BIMW_failure;
                    outputlen += 4;
                    savedolen = outputlen;
                  }
              }
            else
              {
                if (len <= 0x7FFF)
                  {
                    if (WriteBlobByte(ofile,0x1c) != 1)
                      goto parse8BIMW_failure;
                    if (WriteBlobByte(ofile,dataset) != 1)
                      goto parse8BIMW_failure;
                    if (WriteBlobByte(ofile,recnum & 255) != 1)
                      goto parse8BIMW_failure;
                    if (WriteBlobMSBShort(ofile,(const magick_uint16_t) len) != 2)
                      goto parse8BIMW_failure;
                    outputlen += 5;
                    next=0;
                    outputlen += (long) len;
                    while (len--)
                      if (WriteBlobByte(ofile,token[next++]) != 1)
                        goto parse8BIMW_failure;
                  }
              }
          }
      state++;
    }
    MagickFreeResourceLimitedMemory(token);
    MagickFreeResourceLimitedMemory(newstr);
    MagickFreeResourceLimitedMemory(name);
  }
  MagickFreeResourceLimitedMemory(line);
  if (savedolen > 0)
    {
      long diff = outputlen - savedolen;

      currentpos = TellBlob(ofile);
      if (currentpos < 0)
        goto parse8BIMW_failure;
      if (SeekBlob(ofile,savedpos,SEEK_SET) != savedpos)
        goto parse8BIMW_failure;
      if (WriteBlobMSBLong(ofile,diff) != 4)
        goto parse8BIMW_failure;
      if (SeekBlob(ofile,currentpos,SEEK_SET) != currentpos)
        goto parse8BIMW_failure;
      savedolen = 0L;
    }
  return outputlen;

 parse8BIMW_failure:
  MagickFreeResourceLimitedMemory(token);
  MagickFreeResourceLimitedMemory(newstr);
  MagickFreeResourceLimitedMemory(name);
  MagickFreeResourceLimitedMemory(line);
  return 0L;
}

/* some defines for the different JPEG block types */
#define M_SOF0  0xC0            /* Start Of Frame N */
#define M_SOF1  0xC1            /* N indicates which compression process */
#define M_SOF2  0xC2            /* Only SOF0-SOF2 are now in common use */
#define M_SOF3  0xC3
#define M_SOF5  0xC5            /* NB: codes C4 and CC are NOT SOF markers */
#define M_SOF6  0xC6
#define M_SOF7  0xC7
#define M_SOF9  0xC9
#define M_SOF10 0xCA
#define M_SOF11 0xCB
#define M_SOF13 0xCD
#define M_SOF14 0xCE
#define M_SOF15 0xCF
#define M_SOI   0xD8
#define M_EOI   0xD9            /* End Of Image (end of datastream) */
#define M_SOS   0xDA            /* Start Of Scan (begins compressed data) */
#define M_APP0  0xe0
#define M_APP1  0xe1
#define M_APP2  0xe2
#define M_APP3  0xe3
#define M_APP4  0xe4
#define M_APP5  0xe5
#define M_APP6  0xe6
#define M_APP7  0xe7
#define M_APP8  0xe8
#define M_APP9  0xe9
#define M_APP10 0xea
#define M_APP11 0xeb
#define M_APP12 0xec
#define M_APP13 0xed
#define M_APP14 0xee
#define M_APP15 0xef

static int jpeg_transfer_1(Image *ifile, Image *ofile)
{
  int c;

  c = ReadBlobByte(ifile);
  if (c == EOF)
    return EOF;
  if (WriteBlobByte(ofile,c) != 1)
    return EOF;
  return c;
}

#if 0
static int jpeg_skip_1(Image *ifile)
{
  int c;

  c = ReadBlobByte(ifile);
  if (c == EOF)
    return EOF;
  return c;
}
#endif

static int jpeg_read_remaining(Image *ifile, Image *ofile)
{
  while ((jpeg_transfer_1(ifile, ofile)) != EOF)
    continue;
  return M_EOI;
}

static int jpeg_skip_variable(Image *ifile, Image *ofile)
{
  unsigned int  length;
  int c1,c2;

  if ((c1 = jpeg_transfer_1(ifile, ofile)) == EOF)
    return M_EOI;
  if ((c2 = jpeg_transfer_1(ifile, ofile)) == EOF)
    return M_EOI;

  length = (((unsigned char) c1) << 8) + ((unsigned char) c2);
  length -= 2;

  while (length--)
    if (jpeg_transfer_1(ifile, ofile) == EOF)
      return M_EOI;

  return 0;
}

static int jpeg_skip_variable2(Image *ifile, Image *ofile)
{
  unsigned int  length;
  int c1,c2;

  ARG_NOT_USED(ofile);
  if ((c1 = ReadBlobByte(ifile)) == EOF) return M_EOI;
  if ((c2 = ReadBlobByte(ifile)) == EOF) return M_EOI;

  length = (((unsigned char) c1) << 8) + ((unsigned char) c2);
  length -= 2;

  while (length--)
    if (ReadBlobByte(ifile) == EOF)
      return M_EOI;

  return 0;
}

static int jpeg_nextmarker(Image *ifile, Image *ofile)
{
  int c;

  /* transfer anything until we hit 0xff */
  do
  {
    c = ReadBlobByte(ifile);
    if (c == EOF)
      return M_EOI; /* we hit EOF */
    else
      if (c != 0xff)
        if (WriteBlobByte(ofile,c) != 1)
          return M_EOI; /* pretend we hit EOF */
  } while (c != 0xff);

  /* get marker byte, swallowing possible padding */
  do
  {
    c = ReadBlobByte(ifile);
    if (c == EOF)
      return M_EOI; /* we hit EOF */
  } while (c == 0xff);

  return c;
}

#if 0
static int jpeg_skip_till_marker(Image *ifile, int marker)
{
  int c, i;

  do
  {
    /* skip anything until we hit 0xff */
    i = 0;
    do
    {
      c = ReadBlobByte(ifile);
      i++;
      if (c == EOF)
        return M_EOI; /* we hit EOF */
    } while (c != 0xff);

    /* get marker byte, swallowing possible padding */
    do
    {
      c = ReadBlobByte(ifile);
      if (c == EOF)
        return M_EOI; /* we hit EOF */
    } while (c == 0xff);
  } while (c != marker);
  return c;
}
#endif

/* Embed binary IPTC data into a JPEG image. */
static int jpeg_embed(Image *ifile, Image *ofile, Image *iptc)
{
  unsigned int marker;
  unsigned int done = 0;
  unsigned int len;
  int inx;

  if (jpeg_transfer_1(ifile, ofile) != 0xFF)
    return 0;
  if (jpeg_transfer_1(ifile, ofile) != M_SOI)
    return 0;

  while (!done)
  {
    marker = jpeg_nextmarker(ifile, ofile);
    if (marker == M_EOI)
      { /* EOF */
        break;
      }
    else
      {
        if (marker != M_APP13)
          {
            (void) WriteBlobByte(ofile,0xff);
            (void) WriteBlobByte(ofile,marker);
          }
      }

    switch (marker)
    {
      case M_APP13:
        /* we are going to write a new APP13 marker, so don't output the old one */
        (void) jpeg_skip_variable2(ifile, ofile);
        break;

      case M_APP0:
        /* APP0 is in each and every JPEG, so when we hit APP0 we insert our new APP13! */
        (void) jpeg_skip_variable(ifile, ofile);

        if (iptc != (Image *)NULL)
          {
            static const char psheader_base[] = "\xFF\xED\0\0Photoshop 3.0\08BIM\x04\x04\0\0\0\0";
            char psheader[sizeof(psheader_base)];
            len=GetBlobSize(iptc);
            if (len & 1)
              len++; /* make the length even */
            (void) strlcpy(psheader,psheader_base,sizeof(psheader));
            psheader[ 2 ] = (len+16)>>8;
            psheader[ 3 ] = (len+16)&0xff;
            for (inx = 0; inx < 18; inx++)
              (void) WriteBlobByte(ofile,psheader[inx]);
            (void) jpeg_read_remaining(iptc, ofile);
            len=GetBlobSize(iptc);
            if (len & 1)
              (void) WriteBlobByte(ofile,0);
          }
        break;

      case M_SOS:
        /* we hit data, no more marker-inserting can be done! */
        (void) jpeg_read_remaining(ifile, ofile);
        done = 1;
        break;

      default:
        (void) jpeg_skip_variable(ifile, ofile);
        break;
    }
  }
  return 1;
}

#if 0
/* handle stripping the APP13 data out of a JPEG */
static void jpeg_strip(Image *ifile, Image *ofile)
{
  unsigned int marker;

  marker = jpeg_skip_till_marker(ifile, M_SOI);
  if (marker == M_SOI)
  {
    WriteBlobByte(ofile,0xff);
    WriteBlobByte(ofile,M_SOI);
    jpeg_read_remaining(ifile, ofile);
  }
}

/* Extract any APP13 binary data into a file. */
static int jpeg_extract(Image *ifile, Image *ofile)
{
  unsigned int marker;
  unsigned int done = 0;

  if (jpeg_skip_1(ifile) != 0xff)
    return 0;
  if (jpeg_skip_1(ifile) != M_SOI)
    return 0;

  while (!done)
  {
    marker = jpeg_skip_till_marker(ifile, M_APP13);
    if (marker == M_APP13)
      {
        marker = jpeg_nextmarker(ifile, ofile);
        break;
      }
  }
  return 1;
}
#endif /* NOTUSED */

static Image *ReadMETAImage(const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  Image
    *buff,
    *image;

  int
    c;

  size_t
    length;

  MagickPassFail
    status;

  unsigned char
    *blob;

  /*
    Open file containing binary metadata
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  image=AllocateImage(image_info);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFail)
    ThrowReaderException(FileOpenError,UnableToOpenFile,image);
  image->columns=1;
  image->rows=1;
  (void) SetImage(image,OpaqueOpacity);
  length=1;
  if (LocaleNCompare(image_info->magick,"8BIM",4) == 0)
    {
      /*
        Read 8BIM binary metadata.
      */
      buff=AllocateImage((ImageInfo *) NULL);
      if (buff == (Image *) NULL)
        ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
      blob=MagickAllocateMemory(unsigned char *,length);
      if (blob == (unsigned char *) NULL)
        {
          DestroyImage(buff);
          ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                               image);
        }
      (void) memset(blob,0,length);
      AttachBlob(buff->blob,blob,length);
      if (LocaleCompare(image_info->magick,"8BIMTEXT") == 0)
        {
          length=parse8BIM(image, buff);
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "META CODER Parse8BIM returned: %lu",
                                (unsigned long) length);
          if (length == 0)
            {
              status = MagickFail;
              goto t8bim_failure;
            }
          if (length & 1)
            (void) WriteBlobByte(buff,0x0);
        }
      else if (LocaleCompare(image_info->magick,"8BIMWTEXT") == 0)
        {
          length=parse8BIMW(image, buff);
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "META CODER Parse8BIMW returned: %lu",
                                (unsigned long) length);
          if (length == 0)
            {
              status = MagickFail;
              goto t8bim_failure;
            }
          if (length & 1)
            (void) WriteBlobByte(buff,0x0);
        }
      else
        {
          for ( ; ; )
          {
            c=ReadBlobByte(image);
            if (c == EOF)
              break;
            (void) WriteBlobByte(buff,c);
          }
        }
      blob=GetBlobStreamData(buff);  /* blob may be realloced */
      length=GetBlobSize(buff);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Store IPTC profile, size %lu bytes",
                            (unsigned long) length);
      (void) SetImageProfile(image,"IPTC",blob,length);
    t8bim_failure:;
      blob=GetBlobStreamData(buff); /* blob may be realloced */
      DetachBlob(buff->blob);
      MagickFreeMemory(blob);
      DestroyImage(buff);
      if (status == MagickFail)
        ThrowReaderException(CorruptImageError,CorruptImage,image);
    }
  if (LocaleNCompare(image_info->magick,"APP1",4) == 0)
    {
      /*
        Read APP1 binary metadata.
      */
      buff=AllocateImage((ImageInfo *) NULL);
      if (buff == (Image *) NULL)
        ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
      blob=MagickAllocateMemory(unsigned char *,length);
      if (blob == (unsigned char *) NULL)
        {
          DestroyImage(buff);
          ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                               image);
        }
      AttachBlob(buff->blob,blob,length);
      if (LocaleCompare(image_info->magick,"APP1JPEG") == 0)
        {
          Image
            *iptc;

          int
            result;

          ProfileInfo
            *pinfo;

          pinfo = (ProfileInfo *) image_info->client_data;
          if ((pinfo == (ProfileInfo *) NULL) ||
              (pinfo->info == (unsigned char *) NULL) || (pinfo->length == 0))
            {
              blob=GetBlobStreamData(buff); /* blob may be realloced */
              DetachBlob(buff->blob);
              MagickFreeMemory(blob);
              DestroyImage(buff);
              ThrowReaderException(CoderError,NoIPTCProfileAvailable,image)
            }
          iptc=AllocateImage((ImageInfo *) NULL);
          if (iptc == (Image *) NULL)
            {
              blob=GetBlobStreamData(buff); /* blob may be realloced */
              DetachBlob(buff->blob);
              MagickFreeMemory(blob);
              DestroyImage(buff);
              ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                image)
            }
          AttachBlob(iptc->blob,pinfo->info,pinfo->length);
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "META CODER APP1JPEG embed: %p (%ld)",
                                pinfo->info, /* unsigned char * */
                                (long) pinfo->length); /* size_t */
          result=jpeg_embed(image,buff,iptc);
          DetachBlob(iptc->blob);
          DestroyImage(iptc);
          if (result == 0)
            {
              blob=GetBlobStreamData(buff); /* blob may be realloced */
              DetachBlob(buff->blob);
              MagickFreeMemory(blob);
              DestroyImage(buff);
              ThrowReaderException(CoderError,JPEGEmbeddingFailed,image)
            }
        }
      else
        {
#ifdef SLOW_METHOD
          for ( ; ; )
          {
            /* Really - really slow - FIX ME PLEASE!!!! */
            c=ReadBlobByte(image);
            if (c == EOF)
              break;
            if (WriteBlobByte(buff,c) != 1)
              break;
          }
#else
#define MaxBufferSize  65541
          char
            *buffer;

          size_t
            count,
            i,
            length;

          buffer=MagickAllocateResourceLimitedMemory(char *,MaxBufferSize);
          if (buffer == (char *) NULL)
            {
              DestroyImage(buff);
              ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
            }
          i=0;
          while ((length=ReadBlob(image,MaxBufferSize,buffer)) != 0)
            {
              count=0;
              for (i=0; i < length; i+=count)
                {
                  count=WriteBlob(buff,length-i,buffer+i);
                  if (count != (length-i))
                    break;
                }
              if (i < length)
                break;
            }
          MagickFreeResourceLimitedMemory(buffer);
#endif
        }
      blob=GetBlobStreamData(buff); /* blob may be realloced */
      length=GetBlobSize(buff);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Store APP1 profile, size %lu bytes",
                            (unsigned long) length);
      (void) SetImageProfile(image,"APP1",blob,length);
      DetachBlob(buff->blob);
      MagickFreeMemory(blob);
      DestroyImage(buff);
    }
  if ((LocaleCompare(image_info->magick,"ICC") == 0) ||
      (LocaleCompare(image_info->magick,"ICM") == 0))
    {
      buff=AllocateImage((ImageInfo *) NULL);
      if (buff == (Image *) NULL)
        ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
      blob=MagickAllocateMemory(unsigned char *,length);
      if (blob == (unsigned char *) NULL)
        {
          DestroyImage(buff);
          ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                               image);
        }
      AttachBlob(buff->blob,blob,length);
      for ( ; ; )
      {
        c=ReadBlobByte(image);
        if (c == EOF)
          break;
        (void) WriteBlobByte(buff,c);
      }
      blob=GetBlobStreamData(buff); /* blob may be realloced */
      length=GetBlobSize(buff);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Store ICM profile, size %lu bytes",
                            (unsigned long) length);
      (void) SetImageProfile(image,"ICM",blob,length);
      DetachBlob(buff->blob);
      MagickFreeMemory(blob);

      DestroyImage(buff);
    }
  if (LocaleNCompare(image_info->magick,"IPTC",4) == 0)
    {
      buff=AllocateImage((ImageInfo *) NULL);
      if (buff == (Image *) NULL)
        ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
      blob=MagickAllocateMemory(unsigned char *,length);
      if (blob == (unsigned char *) NULL)
        {
          DestroyImage(buff);
          ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                               image);
        }
      AttachBlob(buff->blob,blob,length);
      /* write out the header - length field patched below */
      (void) WriteBlob(buff,12,"8BIM\04\04\0\0\0\0\0\0");
      if (LocaleCompare(image_info->magick,"IPTCTEXT") == 0)
        {
          length=parse8BIM(image, buff);
          if (length & 1)
            (void) WriteBlobByte(buff,0x0);
        }
      else if (LocaleCompare(image_info->magick,"IPTCWTEXT") == 0)
        {
        }
      else
        {
          for ( ; ; )
          {
            c=ReadBlobByte(image);
            if (c == EOF)
              break;
            if (WriteBlobByte(buff,c) != 1)
              break;
          }
        }

      /* subtract off the length of the 8BIM stuff */
      blob=GetBlobStreamData(buff); /* blob may be realloced */
      if ((blob == (const unsigned char *) NULL) || (GetBlobSize(buff) < 12))
        {
          DestroyImage(buff);
          ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                               image);
        }
      length=GetBlobSize(buff)-12;
      blob[10]=(unsigned char) ((length >> 8) & 0xff);
      blob[11]=(unsigned char) (length & 0xff);
      blob=GetBlobStreamData(buff); /* blob may be realloced */
      length=GetBlobSize(buff);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Store IPTC profile, size %" MAGICK_SIZE_T_F
                            "u bytes",(MAGICK_SIZE_T) length);
      (void) SetImageProfile(image,"IPTC",blob,length);
      DetachBlob(buff->blob);
      MagickFreeMemory(blob);
      DestroyImage(buff);
    }
  CloseBlob(image);
  return(image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r M E T A I m a g e                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RegisterMETAImage adds attributes for the META image format to
%  the list of supported formats.  The attributes include the image format
%  tag, a method to read and/or write the format, whether the format
%  supports the saving of more than one frame to the same file or blob,
%  whether the format supports native in-memory I/O, and a brief
%  description of the format.
%
%  The format of the RegisterMETAImage method is:
%
%      RegisterMETAImage(void)
%
*/
ModuleExport void RegisterMETAImage(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("8BIM");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="Photoshop resource format";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("8BIMTEXT");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="Photoshop resource text format";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("8BIMWTEXT");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="Photoshop resource wide text format";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("APP1");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="Raw application information";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("APP1JPEG");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="Raw JPEG binary data";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("EXIF");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="Exif digital camera binary data";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("XMP");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="Adobe XML metadata";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("ICM");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="ICC Color Profile";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("ICC");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="ICC Color Profile";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("IPTC");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="IPTC Newsphoto";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("IPTCTEXT");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="IPTC Newsphoto text format";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("IPTCWTEXT");
  entry->decoder=(DecoderHandler) ReadMETAImage;
  entry->encoder=(EncoderHandler) WriteMETAImage;
  entry->adjoin=False;
  entry->seekable_stream=True;
  entry->description="IPTC Newsphoto text format";
  entry->module="META";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r M E T A I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method UnregisterMETAImage removes format registrations made by the
%  META module from the list of supported formats.
%
%  The format of the UnregisterMETAImage method is:
%
%      UnregisterMETAImage(void)
%
*/
ModuleExport void UnregisterMETAImage(void)
{
  (void) UnregisterMagickInfo("8BIM");
  (void) UnregisterMagickInfo("8BIMTEXT");
  (void) UnregisterMagickInfo("8BIMWTEXT");
  (void) UnregisterMagickInfo("APP1");
  (void) UnregisterMagickInfo("APP1JPEG");
  (void) UnregisterMagickInfo("EXIF");
  (void) UnregisterMagickInfo("ICC");
  (void) UnregisterMagickInfo("ICC");
  (void) UnregisterMagickInfo("ICCTEXT");
  (void) UnregisterMagickInfo("ICM");
  (void) UnregisterMagickInfo("IPTC");
  (void) UnregisterMagickInfo("IPTCTEXT");
  (void) UnregisterMagickInfo("IPTCWTEXT");
  (void) UnregisterMagickInfo("PICON");
  (void) UnregisterMagickInfo("XMP");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e M E T A I m a g e                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method WriteMETAImage writes a META image to a file.
%
%  The format of the WriteMETAImage method is:
%
%      unsigned int WriteMETAImage(const ImageInfo *image_info,Image *image)
%
%  Compression code contributed by Kyle Shorter.
%
%  A description of each parameter follows:
%
%    o status: Method WriteMETAImage return True if the image is written.
%      False is returned if there is a memory shortage or if the image file
%      fails to write.
%
%    o image_info: Specifies a pointer to an ImageInfo structure.
%
%    o image: A pointer to a Image structure.
%
%
*/

static size_t GetIPTCStream(const unsigned char *blob, size_t blob_length, size_t *offset)
{
  size_t
    info_length;

  register long
    i;

  register const unsigned char
    *p;

  unsigned char
    c;

  unsigned int
    marker;

  size_t
    tag_length,
    blob_remaining;

  *offset=0;
  p=blob;
  blob_remaining=blob_length;
  if ((*p == 0x1c) && (*(p+1) == 0x02))
    {
      /* This looks like a plain IPTC record 2 block so drop through */
      return blob_length;
    }

  /* Scan in case we are in an 8BIM block */
  while (blob_remaining >= 12)
  {
    /* Check for 8BIM string ID */
    if (strncmp((const char*)p,"8BIM",4)) break;
    p+=4;
    blob_remaining -= 4;

    /* Read marker ID */
    marker=(unsigned int)(*p) << 8 | *(p+1);
    p+=2;
    blob_remaining-=2;

    /* Read length of Pascal style description string */
    c=*p++;
    blob_remaining--;
    /* ...allow for possible padding byte */
    c |= 1;
    /* ...and skip the string data */
    if (c >= blob_remaining) break;
    p+=c;
    blob_remaining-=c;

    /* Read data length */
    if (blob_remaining < 4) break;
    tag_length=(((unsigned long)*p) << 24) | (((unsigned long)*(p+1)) << 16) |
               (((unsigned long)*(p+2)) << 8) | ((unsigned long)*(p+3));
    p+=4;
    blob_remaining-=4;
    if (tag_length > blob_remaining) break;
    /* Check whether we have the IPTC tag data */
    if (marker == IPTC_ID)
      {
        /* All looks good so return IPTC block "as is" */
        *offset=(unsigned long)(p-blob);
        return tag_length;
      }

    /* Allow for padding of data to even size */
    if (tag_length & 1) tag_length++;

    /* Skip unwanted data */
    p+=tag_length;
    blob_remaining-=tag_length;
  }

  /* Find the beginning of the IPTC info */
  p=blob;
  tag_length=0;
iptc_find:
  info_length=0;
  marker=False;
  while (blob_length != 0)
  {
    c=(*p++);
    blob_length--;
    if (blob_length == 0)
      break;
    if (c == 0x1c)
      {
        p--;
        *offset=(unsigned long) (p-blob); /* let the caller know were it is */
        break;
      }
  }
  /*
    Determine the length of the IPTC info.
  */
  while (blob_length != 0)
  {
    c=(*p++);
    blob_length--;
    if (blob_length == 0)
      break;
    if (c == 0x1c)
      marker=True;
    else
      if (marker)
        break;
      else
        continue;
    info_length++;
    /*
      Found the 0x1c tag; skip the dataset and record number tags.
    */
    c=(*p++); /* should be 2 */
    blob_length--;
    if (blob_length == 0)
      break;
    if ((info_length == 1) && (c != 2))
      goto iptc_find;
    info_length++;
    c=(*p++); /* should be 0 */
    blob_length--;
    if (blob_length == 0)
      break;
    if ((info_length == 2) && (c != 0))
      goto iptc_find;
    info_length++;
    /*
      Decode the length of the block that follows - long or short format.
    */
    c=(*p++);
    blob_length--;
    if (blob_length == 0)
      break;
    info_length++;
    if (c & (unsigned char) 0x80)
      {
        /* long format */
        tag_length=0;
        for (i=0; i < 4; i++)
        {
          tag_length <<= 8;
          tag_length |= (*p++);
          blob_length--;
          if (blob_length == 0)
            break;
          info_length++;
        }
      }
    else
      {
        /* short format */
        tag_length=((size_t) c) << 8;
        c=(*p++);
        blob_length--;
        if (blob_length == 0)
          break;
        info_length++;
        tag_length|=(long) c;
      }
    if (tag_length > blob_length)
      break;
    p+=tag_length;
    blob_length-=tag_length;
    if (blob_length == 0)
      break;
    info_length+=tag_length;
  }
  return(info_length);
}

static void formatString(Image *ofile, const char *s, int len)
{
  char
    temp[MaxTextExtent];

  (void) WriteBlobByte(ofile,'"');
  for (; len > 0; --len, ++s) {
    int c = (*s) & 255;
    switch (c) {
    case '&':
      (void) WriteBlobString(ofile,"&amp;");
      break;
#ifdef HANDLE_GT_LT
    case '<':
      WriteBlobString(ofile,"&lt;");
      break;
    case '>':
      WriteBlobString(ofile,"&gt;");
      break;
#endif
    case '"':
      (void) WriteBlobString(ofile,"&quot;");
      break;
    default:
      if (isprint(c))
        (void) WriteBlobByte(ofile,*s);
      else
        {
          FormatString(temp, "&#%d;", c & 255);
          (void) WriteBlobString(ofile,temp);
        }
      break;
    }
  }
#if defined(MSWINDOWS)
  (void) WriteBlobString(ofile,"\"\r\n");
#else
  (void) WriteBlobString(ofile,"\"\n");
#endif
}

static const struct
{
  unsigned char
    id;

  const char
    name[32];
} tags[] = {
  { 5, "Image Name" },
  { 7, "Edit Status" },
  { 10, "Priority" },
  { 15, "Category" },
  { 20, "Supplemental Category" },
  { 22, "Fixture Identifier" },
  { 25, "Keyword" },
  { 30, "Release Date" },
  { 35, "Release Time" },
  { 40, "Special Instructions" },
  { 45, "Reference Service" },
  { 47, "Reference Date" },
  { 50, "Reference Number" },
  { 55, "Created Date" },
  { 60, "Created Time" },
  { 65, "Originating Program" },
  { 70, "Program Version" },
  { 75, "Object Cycle" },
  { 80, "Byline" },
  { 85, "Byline Title" },
  { 90, "City" },
  { 95, "Province State" },
  { 100, "Country Code" },
  { 101, "Country" },
  { 103, "Original Transmission Reference" },
  { 105, "Headline" },
  { 110, "Credit" },
  { 115, "Source" },
  { 116, "Copyright String" },
  { 120, "Caption" },
  { 121, "Image Orientation" },
  { 122, "Caption Writer" },
  { 131, "Local Caption" },
  { 200, "Custom Field 1" },
  { 201, "Custom Field 2" },
  { 202, "Custom Field 3" },
  { 203, "Custom Field 4" },
  { 204, "Custom Field 5" },
  { 205, "Custom Field 6" },
  { 206, "Custom Field 7" },
  { 207, "Custom Field 8" },
  { 208, "Custom Field 9" },
  { 209, "Custom Field 10" },
  { 210, "Custom Field 11" },
  { 211, "Custom Field 12" },
  { 212, "Custom Field 13" },
  { 213, "Custom Field 14" },
  { 214, "Custom Field 15" },
  { 215, "Custom Field 16" },
  { 216, "Custom Field 17" },
  { 217, "Custom Field 18" },
  { 218, "Custom Field 19" },
  { 219, "Custom Field 20" }
};

static int formatIPTC(Image *ifile, Image *ofile)
{
  char
    temp[MaxTextExtent];

  unsigned int
    foundiptc,
    tagsfound;

  unsigned char
    recnum,
    dataset;

  unsigned char
    *readable,
    *str;

  long
    tagindx,
    taglen;

  int
    i,
    tagcount = ArraySize(tags);

  int
    c;

  foundiptc = 0; /* found the IPTC-Header */
  tagsfound = 0; /* number of tags found */

  c = ReadBlobByte(ifile);
  while (c != EOF)
  {
    if (c == 0x1c)
      foundiptc = 1;
    else
      {
        if (foundiptc)
          {
            return -1;
          }
        else
          {
            c = ReadBlobByte(ifile);
            continue;
          }
      }

    /* we found the 0x1c tag and now grab the dataset and record number tags */
    c = ReadBlobByte(ifile);
    if (c == EOF) return -1;
    dataset = (unsigned char) c;
    c = ReadBlobByte(ifile);
    if (c == EOF) return -1;
    recnum = (unsigned char) c;
    /* try to match this record to one of the ones in our named table */
    for (i=0; i< tagcount; i++)
    {
      if (tags[i].id == recnum)
          break;
    }
    if (i < tagcount)
      readable = (unsigned char *) tags[i].name;
    else
      readable = (unsigned char *) "";

    /* then we decode the length of the block that follows - long or short fmt */
    c=ReadBlobByte(ifile);
    if (c == EOF) return -1;
    if (c & (unsigned char) 0x80)
      return 0;
    else
      {
        int
          c0;

        c0=ReadBlobByte(ifile);
        if (c0 == EOF) return -1;
        taglen = (c << 8) | c0;
      }
    if (taglen < 0) return -1;
    /* make a buffer to hold the tag data and snag it from the input stream */
    str=MagickAllocateResourceLimitedMemory(unsigned char *, (size_t) taglen+1);
    if (str == (unsigned char *) NULL)
      {
        (void) printf("MemoryAllocationFailed");
        return 0;
      }
    for (tagindx=0; tagindx<taglen; tagindx++)
    {
      c=ReadBlobByte(ifile);
      if (c == EOF)
        {
          MagickFreeResourceLimitedMemory(str);
          return -1;
        }
      str[tagindx] = (unsigned char) c;
    }
    str[taglen] = 0;

    /* now finish up by formatting this binary data into ASCII equivalent */
    if (strlen((char *)readable) != 0)
      FormatString(temp, "%d#%d#%s=",(unsigned int)dataset, (unsigned int) recnum, readable);
    else
      FormatString(temp, "%d#%d=",(unsigned int)dataset, (unsigned int) recnum);
    if (WriteBlobString(ofile,temp) == 0)
      {
        MagickFreeResourceLimitedMemory(str);
        return -1;
      }
    formatString( ofile, (char *)str, taglen );
    MagickFreeResourceLimitedMemory(str);

    tagsfound++;

    c=ReadBlobByte(ifile);
  }
  return tagsfound;
}

static int readWordFromBuffer(char **s, long *len)
{
  unsigned char
    buffer[2];

  int
    i,
    c;

  for (i=0; i<2; i++)
  {
    c = *(*s)++; (*len)--;
    if (*len < 0) return -1;
    buffer[i] = (unsigned char) c;
  }
  return (((int) buffer[ 0 ]) <<  8) |
         (((int) buffer[ 1 ]));
}

static int formatIPTCfromBuffer(Image *ofile, char *s, long len)
{
  char
    temp[MaxTextExtent];

  unsigned int
    foundiptc,
    tagsfound;

  unsigned char
    recnum,
    dataset;

  unsigned char
    *readable,
    *str;

  long
    tagindx,
    taglen;

  int
    i,
    tagcount = ArraySize(tags);

  int
    c;

  foundiptc = 0; /* found the IPTC-Header */
  tagsfound = 0; /* number of tags found */

  while (len > 0)
  {
    c = *s++; len--;
    if (c == 0x1c)
      foundiptc = 1;
    else
      {
        if (foundiptc)
          return -1;
        else
          continue;
      }

    /* we found the 0x1c tag and now grab the dataset and record number tags */
    c = *s++; len--;
    if (len < 0) return -1;
    dataset = (unsigned char) c;
    c = *s++; len--;
    if (len < 0) return -1;
    recnum = (unsigned char) c;
    /* try to match this record to one of the ones in our named table */
    for (i=0; i< tagcount; i++)
    {
      if (tags[i].id == recnum)
          break;
    }
    if (i < tagcount)
      readable = (unsigned char *) tags[i].name;
    else
      readable = (unsigned char *) "";

    /* then we decode the length of the block that follows - long or short fmt */
    c = *s++; len--;
    if (len < 0) return -1;
    if (c & (unsigned char) 0x80)
      return 0;
    else
      {
        s--; len++;
        taglen = readWordFromBuffer(&s, &len);
      }
    if (taglen < 0) return -1;
    /* make a buffer to hold the tag data and snag it from the input stream */
    str=MagickAllocateResourceLimitedMemory(unsigned char *,(size_t) taglen+1);
    if (str == (unsigned char *) NULL)
      {
        (void) printf("MemoryAllocationFailed");
        return 0;
      }
    for (tagindx=0; tagindx<taglen; tagindx++)
    {
      c = *s++; len--;
      if (len < 0)
        {
          MagickFreeResourceLimitedMemory(str);
          return -1;
        }
      str[tagindx] = (unsigned char) c;
    }
    str[ taglen ] = 0;

    /* now finish up by formatting this binary data into ASCII equivalent */
    if (strlen((char *)readable) != 0)
      FormatString(temp, "%d#%d#%s=",(unsigned int)dataset, (unsigned int) recnum, readable);
    else
      FormatString(temp, "%d#%d=",(unsigned int)dataset, (unsigned int) recnum);
    (void) WriteBlobString(ofile,temp);
    formatString( ofile, (char *)str, taglen );
    MagickFreeResourceLimitedMemory(str);

    tagsfound++;
  }
  return tagsfound;
}

#define Format8BIMLiberate()                    \
  do                                            \
    {                                           \
      MagickFreeResourceLimitedMemory(PString);                \
      MagickFreeResourceLimitedMemory(str);                    \
    } while(0);

static int format8BIM(Image *ifile, Image *ofile)
{
  char
    temp[MaxTextExtent];

  ExtendedSignedIntegralType
    Size;

  int
    ID,
    resCount,
    i,
    c;

  unsigned char
    *PString=0,
    *str=0;

  magick_off_t
    file_size;

  file_size=GetBlobSize(ifile);
  resCount = 0;
  c = ReadBlobByte(ifile);
  while (c != EOF)
    {
      if (c == '8')
        {
          unsigned char
            buffer[5];

          buffer[0] = (unsigned char) c;
          for (i=1; i<4; i++)
            {
              c=ReadBlobByte(ifile);
              if (c == EOF)
                {
                  ThrowException(&ofile->exception,CorruptImageError,UnexpectedEndOfFile,
                                 ofile->filename);
                  goto format8BIMError;
                }
              buffer[i] = (unsigned char) c;
            }
          buffer[4] = 0;
          if (strcmp((const char *)buffer, "8BIM") != 0)
            continue;
        }
      else
        {
          c=ReadBlobByte(ifile);
          continue;
        }

      /* we found the OSType (8BIM) and now grab the ID, PString, and Size fields */
      ID = ReadBlobMSBShort(ifile);
      if (ID < 0)
        {
          ThrowException(&ofile->exception,CorruptImageError,ImproperImageHeader,
                         ofile->filename);
          goto format8BIMError;
        }
      {
        unsigned char
          plen;

        c=ReadBlobByte(ifile);
        if (c == EOF)
          {
            ThrowException(&ofile->exception,CorruptImageError,UnexpectedEndOfFile,
                           ofile->filename);
            goto format8BIMError;
          }
        plen = (unsigned char) c;
        PString=MagickAllocateResourceLimitedMemory(unsigned char *,(size_t) plen+1);
        if (PString == (unsigned char *) NULL)
          {
            ThrowException(&ofile->exception,ResourceLimitError,MemoryAllocationFailed,
                           ofile->filename);
            goto format8BIMError;
          }
        for (i=0; i<plen; i++)
          {
            c=ReadBlobByte(ifile);
            if (c == EOF)
              {
                ThrowException(&ofile->exception,CorruptImageError,UnexpectedEndOfFile,
                               ofile->filename);
                goto format8BIMError;
              }
            PString[i] = (unsigned char) c;
          }
        PString[ plen ] = 0;
        if (!(plen&1))
          {
            c=ReadBlobByte(ifile);
            if (c == EOF)
              {
                ThrowException(&ofile->exception,CorruptImageError,UnexpectedEndOfFile,
                               ofile->filename);
                goto format8BIMError;
              }
          }
      }
      Size = ReadBlobMSBLong(ifile);
      if ((Size <= 0) || (Size > (file_size - TellBlob(ifile))))
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Invalid chunk size: %" MAGICK_OFF_F "d", Size);
          ThrowException(&ofile->exception,CorruptImageError,ImproperImageHeader,
                         ofile->filename);
          goto format8BIMError;
        }
      /* make a buffer to hold the data and snag it from the input stream */
      str=MagickAllocateResourceLimitedMemory(unsigned char *,(size_t) Size+1);
      if (str == (unsigned char *) NULL)
        {
          ThrowException(&ofile->exception,ResourceLimitError,MemoryAllocationFailed,
                           ofile->filename);
          goto format8BIMError;
        }
      for (i=0; i < Size; i++)
        {
          c=ReadBlobByte(ifile);
          if (c == EOF)
            {
              ThrowException(&ofile->exception,CorruptImageError,UnexpectedEndOfFile,
                             ofile->filename);
              goto format8BIMError;
            }
          str[i] = (unsigned char) c;
        }
      str[i] = '\0';

      /* we currently skip thumbnails, since it does not make
       * any sense preserving them in a real world application
       */
      if (ID != THUMBNAIL_ID)
        {
          /* now finish up by formatting this binary data into
           * ASCII equivalent
           */
          if (strlen((const char *) PString) != 0)
            FormatString(temp, "8BIM#%d#%s=", ID, PString);
          else
            FormatString(temp, "8BIM#%d=", ID);
          (void) WriteBlobString(ofile,temp);
          if (ID == IPTC_ID)
            {
              formatString(ofile, "IPTC", 4);
              (void) formatIPTCfromBuffer(ofile, (char *)str, (long) Size);
            }
          else
            formatString(ofile, (char *)str, (long) Size);
        }
      Format8BIMLiberate();

      resCount++;

      c=ReadBlobByte(ifile);
    }
  return resCount;

  /* Error return path */
 format8BIMError:

  Format8BIMLiberate();
  return -1;
}

static MagickPassFail WriteMETAImage(const ImageInfo *image_info,Image *image)
{
  const unsigned char
    *profile;

  size_t
    profile_length;

  MagickPassFail
    status;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  if (LocaleCompare(image_info->magick,"8BIM") == 0)
    {
      /*
        Write 8BIM image.
      */
      if((profile=GetImageProfile(image,"8BIM",&profile_length)) == 0)
        ThrowWriterException(CoderError,No8BIMDataIsAvailable,image);
      status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
      if (status == MagickFail)
        ThrowWriterException(FileOpenError,UnableToOpenFile,image);
      (void) WriteBlob(image,profile_length,(void *) profile);
      status &= CloseBlob(image);
      return status;
    }
  if (LocaleCompare(image_info->magick,"IPTC") == 0)
    {
      size_t
        iptc_offset,
        length;

      const unsigned char
        *info;

      if((profile=GetImageProfile(image,"IPTC",&profile_length)) == 0)
        ThrowWriterException(CoderError,NoIPTCProfileAvailable,image);
      status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
      length=GetIPTCStream(profile,profile_length,&iptc_offset);
      if (length == 0)
        ThrowWriterException(CoderError,NoIPTCInfoWasFound,image);
      info=profile+iptc_offset;
      (void) WriteBlob(image,length,info);
      status &= CloseBlob(image);
      return status;
    }
  if (LocaleCompare(image_info->magick,"8BIMTEXT") == 0)
    {
      Image
        *buff;

      if((profile=GetImageProfile(image,"8BIM",&profile_length)) == 0)
        ThrowWriterException(CoderError,No8BIMDataIsAvailable,image);
      status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
      if (status == MagickFail)
        ThrowWriterException(FileOpenError,UnableToOpenFile,image);
      buff=AllocateImage((ImageInfo *) NULL);
      if (buff == (Image *) NULL)
        {
          ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
        }
      AttachBlob(buff->blob,profile,profile_length);
      status = MagickPass;
      if (format8BIM(buff,image) <= 0)
        status = MagickFail;
      DetachBlob(buff->blob);
      DestroyImage(buff);
      status &= CloseBlob(image);
      return status;
    }
  if (LocaleCompare(image_info->magick,"8BIMWTEXT") == 0)
    {
      return(MagickFail);
    }
  if (LocaleCompare(image_info->magick,"IPTCTEXT") == 0)
    {
      Image
        *buff;

      size_t
        iptc_offset = 0,
        length;

      const unsigned char
        *info;

      if((profile=GetImageProfile(image,"IPTC",&profile_length)) == 0)
        ThrowWriterException(CoderError,NoIPTCProfileAvailable,image);
      length=GetIPTCStream(profile,profile_length,&iptc_offset);
      if (length == 0)
        ThrowWriterException(CoderError,NoIPTCInfoWasFound,image);
      info=profile+iptc_offset;
      status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
      if (status == MagickFail)
        ThrowWriterException(FileOpenError,UnableToOpenFile,image);
      buff=AllocateImage((ImageInfo *) NULL);
      if (buff == (Image *) NULL)
        {
          ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
        }
      AttachBlob(buff->blob,info,length);
      (void) formatIPTC(buff,image); /* returns -1 on failure */
      DetachBlob(buff->blob);
      DestroyImage(buff);
      status &= CloseBlob(image);
      return status;
    }
  if (LocaleCompare(image_info->magick,"IPTCWTEXT") == 0)
    {
      return(MagickFail);
    }
  if ((LocaleCompare(image_info->magick,"APP1") == 0) ||
      (LocaleCompare(image_info->magick,"EXIF") == 0) ||
      (LocaleCompare(image_info->magick,"XMP") == 0))
    {
      /*
        Write APP1 image.
      */
      if((profile=GetImageProfile(image,image_info->magick,&profile_length)) == 0)
        ThrowWriterException(CoderError,NoAPP1DataIsAvailable,image);
      status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
      if (status == MagickFail)
        ThrowWriterException(FileOpenError,UnableToOpenFile,image);
      (void) WriteBlob(image,(int) profile_length, (char *) profile);
      status &= CloseBlob(image);
      return status;
    }
  if ((LocaleCompare(image_info->magick,"ICC") == 0) ||
      (LocaleCompare(image_info->magick,"ICM") == 0))
    {
      /*
        Write ICM image.
      */

      if((profile=GetImageProfile(image,"ICM",&profile_length)) == 0)
        ThrowWriterException(CoderError,NoColorProfileAvailable,image);
      status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
      if (status == MagickFail)
        ThrowWriterException(FileOpenError,UnableToOpenFile,image);
      (void) WriteBlob(image,profile_length,(void *) profile);
      status &= CloseBlob(image);
      return status;
    }
  return MagickFail;
}
