1 /******************************************************************************
   2 
   3 dgif_lib.c - GIF decoding
   4 
   5 The functions here and in egif_lib.c are partitioned carefully so that
   6 if you only require one of read and write capability, only one of these
   7 two modules will be linked.  Preserve this property!
   8 
   9 *****************************************************************************/
  10 
  11 #include <stdlib.h>
  12 #include <limits.h>
  13 #include <stdint.h>
  14 #include <fcntl.h>
  15 #include <unistd.h>
  16 #include <stdio.h>
  17 #include <string.h>
  18 
  19 #ifdef _WIN32
  20 #include <io.h>


  21 #endif /* _WIN32 */
  22 
  23 #include "gif_lib.h"
  24 #include "gif_lib_private.h"
  25 
  26 /* compose unsigned little endian value */
  27 #define UNSIGNED_LITTLE_ENDIAN(lo, hi)  ((lo) | ((hi) << 8))
  28 
  29 /* avoid extra function call in case we use fread (TVT) */
  30 #define READ(_gif,_buf,_len)                                     \
  31   (((GifFilePrivateType*)_gif->Private)->Read ?                   \
  32     ((GifFilePrivateType*)_gif->Private)->Read(_gif,_buf,_len) : \
  33     fread(_buf,1,_len,((GifFilePrivateType*)_gif->Private)->File))
  34 
  35 static int DGifGetWord(GifFileType *GifFile, GifWord *Word);
  36 static int DGifSetupDecompress(GifFileType *GifFile);
  37 static int DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line,
  38                               int LineLen);
  39 static int DGifGetPrefixChar(GifPrefixType *Prefix, int Code, int ClearCode);
  40 static int DGifDecompressInput(GifFileType *GifFile, int *Code);
  41 static int DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf,
  42                              GifByteType *NextByte);
  43 
  44 /******************************************************************************
  45  Open a new GIF file for read, given by its name.
  46  Returns dynamically allocated GifFileType pointer which serves as the GIF
  47  info record.
  48 ******************************************************************************/
  49 GifFileType *
  50 DGifOpenFileName(const char *FileName, int *Error)
  51 {
  52     int FileHandle;
  53     GifFileType *GifFile;
  54 
  55     if ((FileHandle = open(FileName, O_RDONLY)) == -1) {
  56         if (Error != NULL)
  57             *Error = D_GIF_ERR_OPEN_FAILED;
  58         return NULL;
  59     }
  60 
  61     GifFile = DGifOpenFileHandle(FileHandle, Error);
  62     return GifFile;
  63 }
  64 
  65 /******************************************************************************
  66  Update a new GIF file, given its file handle.
  67  Returns dynamically allocated GifFileType pointer which serves as the GIF
  68  info record.
  69 ******************************************************************************/
  70 GifFileType *
  71 DGifOpenFileHandle(int FileHandle, int *Error)
  72 {
  73     char Buf[GIF_STAMP_LEN + 1];
  74     GifFileType *GifFile;
  75     GifFilePrivateType *Private;
  76     FILE *f;
  77 
  78     GifFile = (GifFileType *)malloc(sizeof(GifFileType));
  79     if (GifFile == NULL) {
  80         if (Error != NULL)
  81             *Error = D_GIF_ERR_NOT_ENOUGH_MEM;
  82         (void)close(FileHandle);
  83         return NULL;
  84     }
  85 
  86     /*@i1@*/memset(GifFile, '\0', sizeof(GifFileType));
  87 
  88     /* Belt and suspenders, in case the null pointer isn't zero */
  89     GifFile->SavedImages = NULL;
  90     GifFile->SColorMap = NULL;
  91 
  92     Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType));
  93     if (Private == NULL) {
  94         if (Error != NULL)
  95             *Error = D_GIF_ERR_NOT_ENOUGH_MEM;
  96         (void)close(FileHandle);
  97         free((char *)GifFile);
  98         return NULL;
  99     }
 100 #ifdef _WIN32
 101     _setmode(FileHandle, O_BINARY);    /* Make sure it is in binary mode. */
 102 #endif /* _WIN32 */
 103 
 104     f = fdopen(FileHandle, "rb");    /* Make it into a stream: */
 105 
 106     /*@-mustfreeonly@*/
 107     GifFile->Private = (void *)Private;
 108     Private->FileHandle = FileHandle;
 109     Private->File = f;
 110     Private->FileState = FILE_STATE_READ;
 111     Private->Read = NULL;        /* don't use alternate input method (TVT) */
 112     GifFile->UserData = NULL;    /* TVT */
 113     /*@=mustfreeonly@*/
 114 
 115     /* Let's see if this is a GIF file: */
 116     /* coverity[check_return] */
 117     if (READ(GifFile, (unsigned char *)Buf, GIF_STAMP_LEN) != GIF_STAMP_LEN) {
 118         if (Error != NULL)
 119             *Error = D_GIF_ERR_READ_FAILED;
 120         (void)fclose(f);
 121         free((char *)Private);
 122         free((char *)GifFile);
 123         return NULL;
 124     }
 125 
 126     /* Check for GIF prefix at start of file */
 127     Buf[GIF_STAMP_LEN] = 0;
 128     if (strncmp(GIF_STAMP, Buf, GIF_VERSION_POS) != 0) {
 129         if (Error != NULL)
 130             *Error = D_GIF_ERR_NOT_GIF_FILE;
 131         (void)fclose(f);
 132         free((char *)Private);
 133         free((char *)GifFile);
 134         return NULL;
 135     }
 136 
 137     if (DGifGetScreenDesc(GifFile) == GIF_ERROR) {
 138         (void)fclose(f);
 139         free((char *)Private);
 140         free((char *)GifFile);
 141         return NULL;
 142     }
 143 
 144     GifFile->Error = 0;
 145 
 146     /* What version of GIF? */
 147     Private->gif89 = (Buf[GIF_VERSION_POS] == '9');
 148 
 149     return GifFile;
 150 }
 151 
 152 /******************************************************************************
 153  GifFileType constructor with user supplied input function (TVT)
 154 ******************************************************************************/
 155 GifFileType *
 156 DGifOpen(void *userData, InputFunc readFunc, int *Error)
 157 {
 158     char Buf[GIF_STAMP_LEN + 1];
 159     GifFileType *GifFile;
 160     GifFilePrivateType *Private;
 161 
 162     GifFile = (GifFileType *)malloc(sizeof(GifFileType));
 163     if (GifFile == NULL) {
 164         if (Error != NULL)
 165             *Error = D_GIF_ERR_NOT_ENOUGH_MEM;
 166         return NULL;
 167     }
 168 
 169     memset(GifFile, '\0', sizeof(GifFileType));
 170 
 171     /* Belt and suspenders, in case the null pointer isn't zero */
 172     GifFile->SavedImages = NULL;
 173     GifFile->SColorMap = NULL;
 174 
 175     Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType));
 176     if (!Private) {
 177         if (Error != NULL)
 178             *Error = D_GIF_ERR_NOT_ENOUGH_MEM;
 179         free((char *)GifFile);
 180         return NULL;
 181     }
 182 
 183     GifFile->Private = (void *)Private;
 184     Private->FileHandle = 0;
 185     Private->File = NULL;
 186     Private->FileState = FILE_STATE_READ;
 187 
 188     Private->Read = readFunc;    /* TVT */
 189     GifFile->UserData = userData;    /* TVT */
 190 
 191     /* Lets see if this is a GIF file: */
 192     /* coverity[check_return] */
 193     if (READ(GifFile, (unsigned char *)Buf, GIF_STAMP_LEN) != GIF_STAMP_LEN) {
 194         if (Error != NULL)
 195             *Error = D_GIF_ERR_READ_FAILED;
 196         free((char *)Private);
 197         free((char *)GifFile);
 198         return NULL;
 199     }
 200 
 201     /* Check for GIF prefix at start of file */
 202     Buf[GIF_STAMP_LEN] = '\0';
 203     if (strncmp(GIF_STAMP, Buf, GIF_VERSION_POS) != 0) {
 204         if (Error != NULL)
 205             *Error = D_GIF_ERR_NOT_GIF_FILE;
 206         free((char *)Private);
 207         free((char *)GifFile);
 208         return NULL;
 209     }
 210 
 211     if (DGifGetScreenDesc(GifFile) == GIF_ERROR) {
 212         free((char *)Private);
 213         free((char *)GifFile);
 214         if (Error != NULL)
 215             *Error = D_GIF_ERR_NO_SCRN_DSCR;
 216         return NULL;
 217     }
 218 
 219     GifFile->Error = 0;
 220 
 221     /* What version of GIF? */
 222     Private->gif89 = (Buf[GIF_VERSION_POS] == '9');
 223 
 224     return GifFile;
 225 }
 226 
 227 /******************************************************************************
 228  This routine should be called before any other DGif calls. Note that
 229  this routine is called automatically from DGif file open routines.
 230 ******************************************************************************/
 231 int
 232 DGifGetScreenDesc(GifFileType *GifFile)
 233 {
 234     int BitsPerPixel;
 235     bool SortFlag;
 236     GifByteType Buf[3];
 237     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 238 
 239     if (!IS_READABLE(Private)) {
 240         /* This file was NOT open for reading: */
 241         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 242         return GIF_ERROR;
 243     }
 244 
 245     /* Put the screen descriptor into the file: */
 246     if (DGifGetWord(GifFile, &GifFile->SWidth) == GIF_ERROR ||
 247         DGifGetWord(GifFile, &GifFile->SHeight) == GIF_ERROR)
 248         return GIF_ERROR;
 249 
 250     if (READ(GifFile, Buf, 3) != 3) {
 251         GifFile->Error = D_GIF_ERR_READ_FAILED;
 252         GifFreeMapObject(GifFile->SColorMap);
 253         GifFile->SColorMap = NULL;
 254         return GIF_ERROR;
 255     }
 256     GifFile->SColorResolution = (((Buf[0] & 0x70) + 1) >> 4) + 1;
 257     SortFlag = (Buf[0] & 0x08) != 0;
 258     BitsPerPixel = (Buf[0] & 0x07) + 1;
 259     GifFile->SBackGroundColor = Buf[1];
 260     GifFile->AspectByte = Buf[2]; 
 261     if (Buf[0] & 0x80) {    /* Do we have global color map? */
 262         int i;
 263 
 264         GifFile->SColorMap = GifMakeMapObject(1 << BitsPerPixel, NULL);
 265         if (GifFile->SColorMap == NULL) {
 266             GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
 267             return GIF_ERROR;
 268         }
 269 
 270         /* Get the global color map: */
 271         GifFile->SColorMap->SortFlag = SortFlag;
 272         for (i = 0; i < GifFile->SColorMap->ColorCount; i++) {
 273             /* coverity[check_return] */
 274             if (READ(GifFile, Buf, 3) != 3) {
 275                 GifFreeMapObject(GifFile->SColorMap);
 276                 GifFile->SColorMap = NULL;
 277                 GifFile->Error = D_GIF_ERR_READ_FAILED;
 278                 return GIF_ERROR;
 279             }
 280             GifFile->SColorMap->Colors[i].Red = Buf[0];
 281             GifFile->SColorMap->Colors[i].Green = Buf[1];
 282             GifFile->SColorMap->Colors[i].Blue = Buf[2];
 283         }
 284     } else {
 285         GifFile->SColorMap = NULL;
 286     }
 287 
 288     return GIF_OK;
 289 }
 290 
 291 /******************************************************************************
 292  This routine should be called before any attempt to read an image.
 293 ******************************************************************************/
 294 int
 295 DGifGetRecordType(GifFileType *GifFile, GifRecordType* Type)
 296 {
 297     GifByteType Buf;
 298     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 299 
 300     if (!IS_READABLE(Private)) {
 301         /* This file was NOT open for reading: */
 302         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 303         return GIF_ERROR;
 304     }
 305 
 306     /* coverity[check_return] */
 307     if (READ(GifFile, &Buf, 1) != 1) {
 308         GifFile->Error = D_GIF_ERR_READ_FAILED;
 309         return GIF_ERROR;
 310     }
 311 
 312     switch (Buf) {
 313       case DESCRIPTOR_INTRODUCER:
 314           *Type = IMAGE_DESC_RECORD_TYPE;
 315           break;
 316       case EXTENSION_INTRODUCER:
 317           *Type = EXTENSION_RECORD_TYPE;
 318           break;
 319       case TERMINATOR_INTRODUCER:
 320           *Type = TERMINATE_RECORD_TYPE;
 321           break;
 322       default:
 323           *Type = UNDEFINED_RECORD_TYPE;
 324           GifFile->Error = D_GIF_ERR_WRONG_RECORD;
 325           return GIF_ERROR;
 326     }
 327 
 328     return GIF_OK;
 329 }
 330 
 331 /******************************************************************************
 332  This routine should be called before any attempt to read an image.
 333  Note it is assumed the Image desc. header has been read.
 334 ******************************************************************************/
 335 int
 336 DGifGetImageDesc(GifFileType *GifFile)
 337 {
 338     unsigned int BitsPerPixel;
 339     GifByteType Buf[3];
 340     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 341     SavedImage *sp;
 342 
 343     if (!IS_READABLE(Private)) {
 344         /* This file was NOT open for reading: */
 345         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 346         return GIF_ERROR;
 347     }
 348 
 349     if (DGifGetWord(GifFile, &GifFile->Image.Left) == GIF_ERROR ||
 350         DGifGetWord(GifFile, &GifFile->Image.Top) == GIF_ERROR ||
 351         DGifGetWord(GifFile, &GifFile->Image.Width) == GIF_ERROR ||
 352         DGifGetWord(GifFile, &GifFile->Image.Height) == GIF_ERROR)
 353         return GIF_ERROR;
 354     if (READ(GifFile, Buf, 1) != 1) {
 355         GifFile->Error = D_GIF_ERR_READ_FAILED;
 356         GifFreeMapObject(GifFile->Image.ColorMap);
 357         GifFile->Image.ColorMap = NULL;
 358         return GIF_ERROR;
 359     }
 360     BitsPerPixel = (Buf[0] & 0x07) + 1;
 361     GifFile->Image.Interlace = (Buf[0] & 0x40) ? true : false;
 362 
 363     /* Setup the colormap */
 364     if (GifFile->Image.ColorMap) {
 365         GifFreeMapObject(GifFile->Image.ColorMap);
 366         GifFile->Image.ColorMap = NULL;
 367     }
 368     /* Does this image have local color map? */
 369     if (Buf[0] & 0x80) {
 370         unsigned int i;
 371 
 372         GifFile->Image.ColorMap = GifMakeMapObject(1 << BitsPerPixel, NULL);
 373         if (GifFile->Image.ColorMap == NULL) {
 374             GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
 375             return GIF_ERROR;
 376         }
 377 
 378         /* Get the image local color map: */
 379         for (i = 0; i < GifFile->Image.ColorMap->ColorCount; i++) {
 380             /* coverity[check_return] */
 381             if (READ(GifFile, Buf, 3) != 3) {
 382                 GifFreeMapObject(GifFile->Image.ColorMap);
 383                 GifFile->Error = D_GIF_ERR_READ_FAILED;
 384                 GifFile->Image.ColorMap = NULL;
 385                 return GIF_ERROR;
 386             }
 387             GifFile->Image.ColorMap->Colors[i].Red = Buf[0];
 388             GifFile->Image.ColorMap->Colors[i].Green = Buf[1];
 389             GifFile->Image.ColorMap->Colors[i].Blue = Buf[2];
 390         }
 391     }
 392 
 393     if (GifFile->SavedImages) {
 394         SavedImage* new_saved_images =
 395             (SavedImage *)realloc(GifFile->SavedImages,
 396                             sizeof(SavedImage) * (GifFile->ImageCount + 1));
 397         if (new_saved_images == NULL) {
 398             GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
 399             return GIF_ERROR;
 400         }
 401         GifFile->SavedImages = new_saved_images;
 402     } else {
 403         if ((GifFile->SavedImages =
 404              (SavedImage *) malloc(sizeof(SavedImage))) == NULL) {
 405             GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
 406             return GIF_ERROR;
 407         }
 408     }
 409 
 410     sp = &GifFile->SavedImages[GifFile->ImageCount];
 411     memcpy(&sp->ImageDesc, &GifFile->Image, sizeof(GifImageDesc));
 412     if (GifFile->Image.ColorMap != NULL) {
 413         sp->ImageDesc.ColorMap = GifMakeMapObject(
 414                                  GifFile->Image.ColorMap->ColorCount,
 415                                  GifFile->Image.ColorMap->Colors);
 416         if (sp->ImageDesc.ColorMap == NULL) {
 417             GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
 418             return GIF_ERROR;
 419         }
 420     }
 421     sp->RasterBits = (unsigned char *)NULL;
 422     sp->ExtensionBlockCount = 0;
 423     sp->ExtensionBlocks = (ExtensionBlock *) NULL;
 424 
 425     GifFile->ImageCount++;
 426 
 427     Private->PixelCount = (long)GifFile->Image.Width *
 428        (long)GifFile->Image.Height;
 429 
 430     /* Reset decompress algorithm parameters. */
 431     return DGifSetupDecompress(GifFile);
 432 }
 433 
 434 /******************************************************************************
 435  Get one full scanned line (Line) of length LineLen from GIF file.
 436 ******************************************************************************/
 437 int
 438 DGifGetLine(GifFileType *GifFile, GifPixelType *Line, int LineLen)
 439 {
 440     GifByteType *Dummy;
 441     GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private;
 442 
 443     if (!IS_READABLE(Private)) {
 444         /* This file was NOT open for reading: */
 445         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 446         return GIF_ERROR;
 447     }
 448 
 449     if (!LineLen)
 450         LineLen = GifFile->Image.Width;
 451 
 452     if ((Private->PixelCount -= LineLen) > 0xffff0000UL) {
 453         GifFile->Error = D_GIF_ERR_DATA_TOO_BIG;
 454         return GIF_ERROR;
 455     }
 456 
 457     if (DGifDecompressLine(GifFile, Line, LineLen) == GIF_OK) {
 458         if (Private->PixelCount == 0) {
 459             /* We probably won't be called any more, so let's clean up
 460              * everything before we return: need to flush out all the
 461              * rest of image until an empty block (size 0)
 462              * detected. We use GetCodeNext.
 463              */
 464             do
 465                 if (DGifGetCodeNext(GifFile, &Dummy) == GIF_ERROR)
 466                     return GIF_ERROR;
 467             while (Dummy != NULL) ;
 468         }
 469         return GIF_OK;
 470     } else
 471         return GIF_ERROR;
 472 }
 473 
 474 /******************************************************************************
 475  Put one pixel (Pixel) into GIF file.
 476 ******************************************************************************/
 477 int
 478 DGifGetPixel(GifFileType *GifFile, GifPixelType Pixel)
 479 {
 480     GifByteType *Dummy;
 481     GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private;
 482 
 483     if (!IS_READABLE(Private)) {
 484         /* This file was NOT open for reading: */
 485         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 486         return GIF_ERROR;
 487     }
 488     if (--Private->PixelCount > 0xffff0000UL)
 489     {
 490         GifFile->Error = D_GIF_ERR_DATA_TOO_BIG;
 491         return GIF_ERROR;
 492     }
 493 
 494     if (DGifDecompressLine(GifFile, &Pixel, 1) == GIF_OK) {
 495         if (Private->PixelCount == 0) {
 496             /* We probably won't be called any more, so let's clean up
 497              * everything before we return: need to flush out all the
 498              * rest of image until an empty block (size 0)
 499              * detected. We use GetCodeNext.
 500              */
 501             do
 502                 if (DGifGetCodeNext(GifFile, &Dummy) == GIF_ERROR)
 503                     return GIF_ERROR;
 504             while (Dummy != NULL) ;
 505         }
 506         return GIF_OK;
 507     } else
 508         return GIF_ERROR;
 509 }
 510 
 511 /******************************************************************************
 512  Get an extension block (see GIF manual) from GIF file. This routine only
 513  returns the first data block, and DGifGetExtensionNext should be called
 514  after this one until NULL extension is returned.
 515  The Extension should NOT be freed by the user (not dynamically allocated).
 516  Note it is assumed the Extension description header has been read.
 517 ******************************************************************************/
 518 int
 519 DGifGetExtension(GifFileType *GifFile, int *ExtCode, GifByteType **Extension)
 520 {
 521     GifByteType Buf;
 522     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 523 
 524     if (!IS_READABLE(Private)) {
 525         /* This file was NOT open for reading: */
 526         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 527         return GIF_ERROR;
 528     }
 529 
 530     /* coverity[check_return] */
 531     if (READ(GifFile, &Buf, 1) != 1) {
 532         GifFile->Error = D_GIF_ERR_READ_FAILED;
 533         return GIF_ERROR;
 534     }
 535     *ExtCode = Buf;
 536 
 537     return DGifGetExtensionNext(GifFile, Extension);
 538 }
 539 
 540 /******************************************************************************
 541  Get a following extension block (see GIF manual) from GIF file. This
 542  routine should be called until NULL Extension is returned.
 543  The Extension should NOT be freed by the user (not dynamically allocated).
 544 ******************************************************************************/
 545 int
 546 DGifGetExtensionNext(GifFileType *GifFile, GifByteType ** Extension)
 547 {
 548     GifByteType Buf;
 549     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 550 
 551     if (READ(GifFile, &Buf, 1) != 1) {
 552         GifFile->Error = D_GIF_ERR_READ_FAILED;
 553         return GIF_ERROR;
 554     }
 555     if (Buf > 0) {
 556         *Extension = Private->Buf;    /* Use private unused buffer. */
 557         (*Extension)[0] = Buf;  /* Pascal strings notation (pos. 0 is len.). */
 558         /* coverity[tainted_data,check_return] */
 559         if (READ(GifFile, &((*Extension)[1]), Buf) != Buf) {
 560             GifFile->Error = D_GIF_ERR_READ_FAILED;
 561             return GIF_ERROR;
 562         }
 563     } else
 564         *Extension = NULL;
 565 
 566     return GIF_OK;
 567 }
 568 
 569 /******************************************************************************
 570  Extract a Graphics Control Block from raw extension data
 571 ******************************************************************************/
 572 
 573 int DGifExtensionToGCB(const size_t GifExtensionLength,
 574                        const GifByteType *GifExtension,
 575                        GraphicsControlBlock *GCB)
 576 {
 577     if (GifExtensionLength != 4) {
 578         return GIF_ERROR;
 579     }
 580 
 581     GCB->DisposalMode = (GifExtension[0] >> 2) & 0x07;
 582     GCB->UserInputFlag = (GifExtension[0] & 0x02) != 0;
 583     GCB->DelayTime = UNSIGNED_LITTLE_ENDIAN(GifExtension[1], GifExtension[2]);
 584     if (GifExtension[0] & 0x01)
 585         GCB->TransparentColor = (int)GifExtension[3];
 586     else
 587         GCB->TransparentColor = NO_TRANSPARENT_COLOR;
 588 
 589     return GIF_OK;
 590 }
 591 
 592 /******************************************************************************
 593  Extract the Graphics Control Block for a saved image, if it exists.
 594 ******************************************************************************/
 595 
 596 int DGifSavedExtensionToGCB(GifFileType *GifFile,
 597                             int ImageIndex, GraphicsControlBlock *GCB)
 598 {
 599     int i;
 600 
 601     if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1)
 602         return GIF_ERROR;
 603 
 604     GCB->DisposalMode = DISPOSAL_UNSPECIFIED;
 605     GCB->UserInputFlag = false;
 606     GCB->DelayTime = 0;
 607     GCB->TransparentColor = NO_TRANSPARENT_COLOR;
 608 
 609     for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
 610         ExtensionBlock *ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
 611         if (ep->Function == GRAPHICS_EXT_FUNC_CODE)
 612             return DGifExtensionToGCB(ep->ByteCount, ep->Bytes, GCB);
 613     }
 614 
 615     return GIF_ERROR;
 616 }
 617 
 618 /******************************************************************************
 619  This routine should be called last, to close the GIF file.
 620 ******************************************************************************/
 621 int
 622 DGifCloseFile(GifFileType *GifFile, int *ErrorCode)
 623 {
 624     GifFilePrivateType *Private;
 625 
 626     if (GifFile == NULL || GifFile->Private == NULL)
 627         return GIF_ERROR;
 628 
 629     if (GifFile->Image.ColorMap) {
 630         GifFreeMapObject(GifFile->Image.ColorMap);
 631         GifFile->Image.ColorMap = NULL;
 632     }
 633 
 634     if (GifFile->SColorMap) {
 635         GifFreeMapObject(GifFile->SColorMap);
 636         GifFile->SColorMap = NULL;
 637     }
 638 
 639     if (GifFile->SavedImages) {
 640         GifFreeSavedImages(GifFile);
 641         GifFile->SavedImages = NULL;
 642     }
 643 
 644     GifFreeExtensions(&GifFile->ExtensionBlockCount, &GifFile->ExtensionBlocks);
 645 
 646     Private = (GifFilePrivateType *) GifFile->Private;
 647 
 648     if (!IS_READABLE(Private)) {
 649         /* This file was NOT open for reading: */
 650         if (ErrorCode != NULL)
 651             *ErrorCode = D_GIF_ERR_NOT_READABLE;
 652         free((char *)GifFile->Private);
 653         free(GifFile);
 654         return GIF_ERROR;
 655     }
 656 
 657     if (Private->File && (fclose(Private->File) != 0)) {
 658         if (ErrorCode != NULL)
 659             *ErrorCode = D_GIF_ERR_CLOSE_FAILED;
 660         free((char *)GifFile->Private);
 661         free(GifFile);
 662         return GIF_ERROR;
 663     }
 664 
 665     free((char *)GifFile->Private);
 666     free(GifFile);
 667     if (ErrorCode != NULL)
 668         *ErrorCode = D_GIF_SUCCEEDED;
 669     return GIF_OK;
 670 }
 671 
 672 /******************************************************************************
 673  Get 2 bytes (word) from the given file:
 674 ******************************************************************************/
 675 static int
 676 DGifGetWord(GifFileType *GifFile, GifWord *Word)
 677 {
 678     unsigned char c[2];
 679 
 680     /* coverity[check_return] */
 681     if (READ(GifFile, c, 2) != 2) {
 682         GifFile->Error = D_GIF_ERR_READ_FAILED;
 683         return GIF_ERROR;
 684     }
 685 
 686     *Word = (GifWord)UNSIGNED_LITTLE_ENDIAN(c[0], c[1]);
 687     return GIF_OK;
 688 }
 689 
 690 /******************************************************************************
 691  Get the image code in compressed form.  This routine can be called if the
 692  information needed to be piped out as is. Obviously this is much faster
 693  than decoding and encoding again. This routine should be followed by calls
 694  to DGifGetCodeNext, until NULL block is returned.
 695  The block should NOT be freed by the user (not dynamically allocated).
 696 ******************************************************************************/
 697 int
 698 DGifGetCode(GifFileType *GifFile, int *CodeSize, GifByteType **CodeBlock)
 699 {
 700     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 701 
 702     if (!IS_READABLE(Private)) {
 703         /* This file was NOT open for reading: */
 704         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 705         return GIF_ERROR;
 706     }
 707 
 708     *CodeSize = Private->BitsPerPixel;
 709 
 710     return DGifGetCodeNext(GifFile, CodeBlock);
 711 }
 712 
 713 /******************************************************************************
 714  Continue to get the image code in compressed form. This routine should be
 715  called until NULL block is returned.
 716  The block should NOT be freed by the user (not dynamically allocated).
 717 ******************************************************************************/
 718 int
 719 DGifGetCodeNext(GifFileType *GifFile, GifByteType **CodeBlock)
 720 {
 721     GifByteType Buf;
 722     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 723 
 724     /* coverity[tainted_data_argument] */
 725     /* coverity[check_return] */
 726     if (READ(GifFile, &Buf, 1) != 1) {
 727         GifFile->Error = D_GIF_ERR_READ_FAILED;
 728         return GIF_ERROR;
 729     }
 730 
 731     /* coverity[lower_bounds] */
 732     if (Buf > 0) {
 733         *CodeBlock = Private->Buf;    /* Use private unused buffer. */
 734         (*CodeBlock)[0] = Buf;  /* Pascal strings notation (pos. 0 is len.). */
 735         /* coverity[tainted_data] */
 736         if (READ(GifFile, &((*CodeBlock)[1]), Buf) != Buf) {
 737             GifFile->Error = D_GIF_ERR_READ_FAILED;
 738             return GIF_ERROR;
 739         }
 740     } else {
 741         *CodeBlock = NULL;
 742         Private->Buf[0] = 0;    /* Make sure the buffer is empty! */
 743         Private->PixelCount = 0;    /* And local info. indicate image read. */
 744     }
 745 
 746     return GIF_OK;
 747 }
 748 
 749 /******************************************************************************
 750  Setup the LZ decompression for this image:
 751 ******************************************************************************/
 752 static int
 753 DGifSetupDecompress(GifFileType *GifFile)
 754 {
 755     int i, BitsPerPixel;
 756     GifByteType CodeSize;
 757     GifPrefixType *Prefix;
 758     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 759 
 760     /* coverity[check_return] */
 761     if (READ(GifFile, &CodeSize, 1) < 1) {    /* Read Code size from file. */
 762         return GIF_ERROR;    /* Failed to read Code size. */
 763     }
 764     BitsPerPixel = CodeSize;
 765 
 766     Private->Buf[0] = 0;    /* Input Buffer empty. */
 767     Private->BitsPerPixel = BitsPerPixel;
 768     Private->ClearCode = (1 << BitsPerPixel);
 769     Private->EOFCode = Private->ClearCode + 1;
 770     Private->RunningCode = Private->EOFCode + 1;
 771     Private->RunningBits = BitsPerPixel + 1;    /* Number of bits per code. */
 772     Private->MaxCode1 = 1 << Private->RunningBits;    /* Max. code + 1. */
 773     Private->StackPtr = 0;    /* No pixels on the pixel stack. */
 774     Private->LastCode = NO_SUCH_CODE;
 775     Private->CrntShiftState = 0;    /* No information in CrntShiftDWord. */
 776     Private->CrntShiftDWord = 0;
 777 
 778     Prefix = Private->Prefix;
 779     for (i = 0; i <= LZ_MAX_CODE; i++)
 780         Prefix[i] = NO_SUCH_CODE;
 781 
 782     return GIF_OK;
 783 }
 784 
 785 /******************************************************************************
 786  The LZ decompression routine:
 787  This version decompress the given GIF file into Line of length LineLen.
 788  This routine can be called few times (one per scan line, for example), in
 789  order the complete the whole image.
 790 ******************************************************************************/
 791 static int
 792 DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, int LineLen)
 793 {
 794     int i = 0;
 795     int j, CrntCode, EOFCode, ClearCode, CrntPrefix, LastCode, StackPtr;
 796     GifByteType *Stack, *Suffix;
 797     GifPrefixType *Prefix;
 798     GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private;
 799 
 800     StackPtr = Private->StackPtr;
 801     Prefix = Private->Prefix;
 802     Suffix = Private->Suffix;
 803     Stack = Private->Stack;
 804     EOFCode = Private->EOFCode;
 805     ClearCode = Private->ClearCode;
 806     LastCode = Private->LastCode;
 807 
 808     if (StackPtr > LZ_MAX_CODE) {
 809         return GIF_ERROR;
 810     }
 811 
 812     if (StackPtr != 0) {
 813         /* Let pop the stack off before continueing to read the GIF file: */
 814         while (StackPtr != 0 && i < LineLen)
 815             Line[i++] = Stack[--StackPtr];
 816     }
 817 
 818     while (i < LineLen) {    /* Decode LineLen items. */
 819         if (DGifDecompressInput(GifFile, &CrntCode) == GIF_ERROR)
 820             return GIF_ERROR;
 821 
 822         if (CrntCode == EOFCode) {
 823             /* Note however that usually we will not be here as we will stop
 824              * decoding as soon as we got all the pixel, or EOF code will
 825              * not be read at all, and DGifGetLine/Pixel clean everything.  */
 826             GifFile->Error = D_GIF_ERR_EOF_TOO_SOON;
 827             return GIF_ERROR;
 828         } else if (CrntCode == ClearCode) {
 829             /* We need to start over again: */
 830             for (j = 0; j <= LZ_MAX_CODE; j++)
 831                 Prefix[j] = NO_SUCH_CODE;
 832             Private->RunningCode = Private->EOFCode + 1;
 833             Private->RunningBits = Private->BitsPerPixel + 1;
 834             Private->MaxCode1 = 1 << Private->RunningBits;
 835             LastCode = Private->LastCode = NO_SUCH_CODE;
 836         } else {
 837             /* Its regular code - if in pixel range simply add it to output
 838              * stream, otherwise trace to codes linked list until the prefix
 839              * is in pixel range: */
 840             if (CrntCode < ClearCode) {
 841                 /* This is simple - its pixel scalar, so add it to output: */
 842                 Line[i++] = CrntCode;
 843             } else {
 844                 /* Its a code to needed to be traced: trace the linked list
 845                  * until the prefix is a pixel, while pushing the suffix
 846                  * pixels on our stack. If we done, pop the stack in reverse
 847                  * (thats what stack is good for!) order to output.  */
 848                 if (Prefix[CrntCode] == NO_SUCH_CODE) {
 849                     CrntPrefix = LastCode;
 850 
 851                     /* Only allowed if CrntCode is exactly the running code:
 852                      * In that case CrntCode = XXXCode, CrntCode or the
 853                      * prefix code is last code and the suffix char is
 854                      * exactly the prefix of last code! */
 855                     if (CrntCode == Private->RunningCode - 2) {
 856                         Suffix[Private->RunningCode - 2] =
 857                            Stack[StackPtr++] = DGifGetPrefixChar(Prefix,
 858                                                                  LastCode,
 859                                                                  ClearCode);
 860                     } else {
 861                         Suffix[Private->RunningCode - 2] =
 862                            Stack[StackPtr++] = DGifGetPrefixChar(Prefix,
 863                                                                  CrntCode,
 864                                                                  ClearCode);
 865                     }
 866                 } else
 867                     CrntPrefix = CrntCode;
 868 
 869                 /* Now (if image is O.K.) we should not get a NO_SUCH_CODE
 870                  * during the trace. As we might loop forever, in case of
 871                  * defective image, we use StackPtr as loop counter and stop
 872                  * before overflowing Stack[]. */
 873                 while (StackPtr < LZ_MAX_CODE &&
 874                        CrntPrefix > ClearCode && CrntPrefix <= LZ_MAX_CODE) {
 875                     Stack[StackPtr++] = Suffix[CrntPrefix];
 876                     CrntPrefix = Prefix[CrntPrefix];
 877                 }
 878                 if (StackPtr >= LZ_MAX_CODE || CrntPrefix > LZ_MAX_CODE) {
 879                     GifFile->Error = D_GIF_ERR_IMAGE_DEFECT;
 880                     return GIF_ERROR;
 881                 }
 882                 /* Push the last character on stack: */
 883                 Stack[StackPtr++] = CrntPrefix;
 884 
 885                 /* Now lets pop all the stack into output: */
 886                 while (StackPtr != 0 && i < LineLen)
 887                     Line[i++] = Stack[--StackPtr];
 888             }
 889             if (LastCode != NO_SUCH_CODE && Prefix[Private->RunningCode - 2] == NO_SUCH_CODE) {
 890                 Prefix[Private->RunningCode - 2] = LastCode;
 891 
 892                 if (CrntCode == Private->RunningCode - 2) {
 893                     /* Only allowed if CrntCode is exactly the running code:
 894                      * In that case CrntCode = XXXCode, CrntCode or the
 895                      * prefix code is last code and the suffix char is
 896                      * exactly the prefix of last code! */
 897                     Suffix[Private->RunningCode - 2] =
 898                        DGifGetPrefixChar(Prefix, LastCode, ClearCode);
 899                 } else {
 900                     Suffix[Private->RunningCode - 2] =
 901                        DGifGetPrefixChar(Prefix, CrntCode, ClearCode);
 902                 }
 903             }
 904             LastCode = CrntCode;
 905         }
 906     }
 907 
 908     Private->LastCode = LastCode;
 909     Private->StackPtr = StackPtr;
 910 
 911     return GIF_OK;
 912 }
 913 
 914 /******************************************************************************
 915  Routine to trace the Prefixes linked list until we get a prefix which is
 916  not code, but a pixel value (less than ClearCode). Returns that pixel value.
 917  If image is defective, we might loop here forever, so we limit the loops to
 918  the maximum possible if image O.k. - LZ_MAX_CODE times.
 919 ******************************************************************************/
 920 static int
 921 DGifGetPrefixChar(GifPrefixType *Prefix, int Code, int ClearCode)
 922 {
 923     int i = 0;
 924 
 925     while (Code > ClearCode && i++ <= LZ_MAX_CODE) {
 926         if (Code > LZ_MAX_CODE) {
 927             return NO_SUCH_CODE;
 928         }
 929         Code = Prefix[Code];
 930     }
 931     return Code;
 932 }
 933 
 934 /******************************************************************************
 935  Interface for accessing the LZ codes directly. Set Code to the real code
 936  (12bits), or to -1 if EOF code is returned.
 937 ******************************************************************************/
 938 int
 939 DGifGetLZCodes(GifFileType *GifFile, int *Code)
 940 {
 941     GifByteType *CodeBlock;
 942     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 943 
 944     if (!IS_READABLE(Private)) {
 945         /* This file was NOT open for reading: */
 946         GifFile->Error = D_GIF_ERR_NOT_READABLE;
 947         return GIF_ERROR;
 948     }
 949 
 950     if (DGifDecompressInput(GifFile, Code) == GIF_ERROR)
 951         return GIF_ERROR;
 952 
 953     if (*Code == Private->EOFCode) {
 954         /* Skip rest of codes (hopefully only NULL terminating block): */
 955         do {
 956             if (DGifGetCodeNext(GifFile, &CodeBlock) == GIF_ERROR)
 957                 return GIF_ERROR;
 958         } while (CodeBlock != NULL) ;
 959 
 960         *Code = -1;
 961     } else if (*Code == Private->ClearCode) {
 962         /* We need to start over again: */
 963         Private->RunningCode = Private->EOFCode + 1;
 964         Private->RunningBits = Private->BitsPerPixel + 1;
 965         Private->MaxCode1 = 1 << Private->RunningBits;
 966     }
 967 
 968     return GIF_OK;
 969 }
 970 
 971 /******************************************************************************
 972  The LZ decompression input routine:
 973  This routine is responsable for the decompression of the bit stream from
 974  8 bits (bytes) packets, into the real codes.
 975  Returns GIF_OK if read successfully.
 976 ******************************************************************************/
 977 static int
 978 DGifDecompressInput(GifFileType *GifFile, int *Code)
 979 {
 980     static const unsigned short CodeMasks[] = {
 981         0x0000, 0x0001, 0x0003, 0x0007,
 982         0x000f, 0x001f, 0x003f, 0x007f,
 983         0x00ff, 0x01ff, 0x03ff, 0x07ff,
 984         0x0fff
 985     };
 986 
 987     GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private;
 988 
 989     GifByteType NextByte;
 990 
 991     /* The image can't contain more than LZ_BITS per code. */
 992     if (Private->RunningBits > LZ_BITS) {
 993         GifFile->Error = D_GIF_ERR_IMAGE_DEFECT;
 994         return GIF_ERROR;
 995     }
 996     
 997     while (Private->CrntShiftState < Private->RunningBits) {
 998         /* Needs to get more bytes from input stream for next code: */
 999         if (DGifBufferedInput(GifFile, Private->Buf, &NextByte) == GIF_ERROR) {
1000             return GIF_ERROR;
1001         }
1002         Private->CrntShiftDWord |=
1003             ((unsigned long)NextByte) << Private->CrntShiftState;
1004         Private->CrntShiftState += 8;
1005     }
1006     *Code = Private->CrntShiftDWord & CodeMasks[Private->RunningBits];
1007 
1008     Private->CrntShiftDWord >>= Private->RunningBits;
1009     Private->CrntShiftState -= Private->RunningBits;
1010 
1011     /* If code cannot fit into RunningBits bits, must raise its size. Note
1012      * however that codes above 4095 are used for special signaling.
1013      * If we're using LZ_BITS bits already and we're at the max code, just
1014      * keep using the table as it is, don't increment Private->RunningCode.
1015      */
1016     if (Private->RunningCode < LZ_MAX_CODE + 2 &&
1017         ++Private->RunningCode > Private->MaxCode1 &&
1018         Private->RunningBits < LZ_BITS) {
1019         Private->MaxCode1 <<= 1;
1020         Private->RunningBits++;
1021     }
1022     return GIF_OK;
1023 }
1024 
1025 /******************************************************************************
1026  This routines read one GIF data block at a time and buffers it internally
1027  so that the decompression routine could access it.
1028  The routine returns the next byte from its internal buffer (or read next
1029  block in if buffer empty) and returns GIF_OK if succesful.
1030 ******************************************************************************/
1031 static int
1032 DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, GifByteType *NextByte)
1033 {
1034     if (Buf[0] == 0) {
1035         /* Needs to read the next buffer - this one is empty: */
1036         /* coverity[check_return] */
1037         if (READ(GifFile, Buf, 1) != 1) {
1038             GifFile->Error = D_GIF_ERR_READ_FAILED;
1039             return GIF_ERROR;
1040         }
1041         /* There shouldn't be any empty data blocks here as the LZW spec
1042          * says the LZW termination code should come first.  Therefore we
1043          * shouldn't be inside this routine at that point.
1044          */
1045         if (Buf[0] == 0) {
1046             GifFile->Error = D_GIF_ERR_IMAGE_DEFECT;
1047             return GIF_ERROR;
1048         }
1049         if (READ(GifFile, &Buf[1], Buf[0]) != Buf[0]) {
1050             GifFile->Error = D_GIF_ERR_READ_FAILED;
1051             return GIF_ERROR;
1052         }
1053         *NextByte = Buf[1];
1054         Buf[1] = 2;    /* We use now the second place as last char read! */
1055         Buf[0]--;
1056     } else {
1057         *NextByte = Buf[Buf[1]++];
1058         Buf[0]--;
1059     }
1060 
1061     return GIF_OK;
1062 }
1063 
1064 /******************************************************************************
1065  This routine reads an entire GIF into core, hanging all its state info off
1066  the GifFileType pointer.  Call DGifOpenFileName() or DGifOpenFileHandle()
1067  first to initialize I/O.  Its inverse is EGifSpew().
1068 *******************************************************************************/
1069 int
1070 DGifSlurp(GifFileType *GifFile)
1071 {
1072     size_t ImageSize;
1073     GifRecordType RecordType;
1074     SavedImage *sp;
1075     GifByteType *ExtData;
1076     int ExtFunction;
1077 
1078     GifFile->ExtensionBlocks = NULL;
1079     GifFile->ExtensionBlockCount = 0;
1080 
1081     do {
1082         if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR)
1083             return (GIF_ERROR);
1084 
1085         switch (RecordType) {
1086           case IMAGE_DESC_RECORD_TYPE:
1087               if (DGifGetImageDesc(GifFile) == GIF_ERROR)
1088                   return (GIF_ERROR);
1089 
1090               sp = &GifFile->SavedImages[GifFile->ImageCount - 1];
1091               /* Allocate memory for the image */
1092               if (sp->ImageDesc.Width < 0 && sp->ImageDesc.Height < 0 &&
1093                       sp->ImageDesc.Width > (INT_MAX / sp->ImageDesc.Height)) {
1094                   return GIF_ERROR;
1095               }
1096               ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height;
1097 
1098               if (ImageSize > (SIZE_MAX / sizeof(GifPixelType))) {
1099                   return GIF_ERROR;
1100               }
1101               sp->RasterBits = (unsigned char *)malloc(ImageSize *
1102                       sizeof(GifPixelType));
1103 
1104               if (sp->RasterBits == NULL) {
1105                   return GIF_ERROR;
1106               }
1107 
1108               if (sp->ImageDesc.Interlace) {
1109                   int i, j;
1110                    /* 
1111                     * The way an interlaced image should be read - 
1112                     * offsets and jumps...
1113                     */
1114                   int InterlacedOffset[] = { 0, 4, 2, 1 };
1115                   int InterlacedJumps[] = { 8, 8, 4, 2 };
1116                   /* Need to perform 4 passes on the image */
1117                   for (i = 0; i < 4; i++)
1118                       for (j = InterlacedOffset[i]; 
1119                            j < sp->ImageDesc.Height;
1120                            j += InterlacedJumps[i]) {
1121                           if (DGifGetLine(GifFile, 
1122                                           sp->RasterBits+j*sp->ImageDesc.Width, 
1123                                           sp->ImageDesc.Width) == GIF_ERROR)
1124                               return GIF_ERROR;
1125                       }
1126               }
1127               else {
1128                   if (DGifGetLine(GifFile,sp->RasterBits,ImageSize)==GIF_ERROR)
1129                       return (GIF_ERROR);
1130               }
1131 
1132               if (GifFile->ExtensionBlocks) {
1133                   sp->ExtensionBlocks = GifFile->ExtensionBlocks;
1134                   sp->ExtensionBlockCount = GifFile->ExtensionBlockCount;
1135 
1136                   GifFile->ExtensionBlocks = NULL;
1137                   GifFile->ExtensionBlockCount = 0;
1138               }
1139               break;
1140 
1141           case EXTENSION_RECORD_TYPE:
1142               if (DGifGetExtension(GifFile,&ExtFunction,&ExtData) == GIF_ERROR)
1143                   return (GIF_ERROR);
1144               /* Create an extension block with our data */
1145               if (ExtData != NULL) {
1146                   if (GifAddExtensionBlock(&GifFile->ExtensionBlockCount,
1147                                            &GifFile->ExtensionBlocks, 
1148                                            ExtFunction, ExtData[0], &ExtData[1])
1149                       == GIF_ERROR)
1150                       return (GIF_ERROR);
1151               }
1152               while (ExtData != NULL) {
1153                   if (DGifGetExtensionNext(GifFile, &ExtData) == GIF_ERROR)
1154                       return (GIF_ERROR);
1155                   /* Continue the extension block */
1156                   if (ExtData != NULL)
1157                       if (GifAddExtensionBlock(&GifFile->ExtensionBlockCount,
1158                                                &GifFile->ExtensionBlocks,
1159                                                CONTINUE_EXT_FUNC_CODE, 
1160                                                ExtData[0], &ExtData[1]) == GIF_ERROR)
1161                       return (GIF_ERROR);
1162               }
1163               break;
1164 
1165           case TERMINATE_RECORD_TYPE:
1166               break;
1167 
1168           default:    /* Should be trapped by DGifGetRecordType */
1169               break;
1170         }
1171     } while (RecordType != TERMINATE_RECORD_TYPE);
1172 
1173     /* Sanity check for corrupted file */
1174     if (GifFile->ImageCount == 0) {
1175         GifFile->Error = D_GIF_ERR_NO_IMAG_DSCR;
1176         return(GIF_ERROR);
1177     }
1178 
1179     return (GIF_OK);
1180 }
1181 
1182 /* end */
--- EOF ---