/*
*  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*  Copyright (C) 2011-2013 Sourcefire, Inc.
*
*  Authors: Tomasz Kojm <tkojm@clamav.net>
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License version 2 as
*  published by the Free Software Foundation.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
*  MA 02110-1301, USA.
*/

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#include "gif.h"
#include "scanners.h"
#include "clamav.h"

#ifndef HAVE_ATTRIB_PACKED
#define __attribute__(x)
#endif
#ifdef HAVE_PRAGMA_PACK
#pragma pack(1)
#endif
#ifdef HAVE_PRAGMA_PACK_HPPA
#pragma pack 1
#endif

struct gif_screen_desc {
    uint16_t width;
    uint16_t height;
    uint8_t flags;
    uint8_t bgcolor;
    uint8_t aspect;
} __attribute__((packed));

struct gif_graphic_control_ext {
    uint8_t blksize;
    uint8_t flags;
    uint16_t delaytime;
    uint8_t tcoloridx;
    uint8_t blkterm;
} __attribute__((packed));

struct gif_image_desc {
    uint16_t leftpos;
    uint16_t toppos;
    uint16_t width;
    uint16_t height;
    uint8_t flags;
} __attribute__((packed));

#ifdef HAVE_PRAGMA_PACK
#pragma pack()
#endif
#ifdef HAVE_PRAGMA_PACK_HPPA
#pragma pack
#endif

#define EC16(x) le16_to_host(x)

#define GETDATA(v)                                                      \
    {                                                                   \
        if (fmap_readn(map, &v, offset, sizeof(v)) == sizeof(v)) {      \
            offset += sizeof(v);                                        \
        } else {                                                        \
            cli_errmsg("cli_parsegif: Can't read file (truncated?)\n"); \
            return CL_EPARSE;                                           \
        }                                                               \
    }

cl_error_t cli_parsegif(cli_ctx *ctx)
{
    fmap_t *map         = *ctx->fmap;
    unsigned char v     = 0;
    unsigned int offset = 6;
    struct gif_screen_desc screen_desc;
    struct gif_image_desc image_desc;
    cl_error_t retVal = CL_SUCCESS;

    cli_dbgmsg("in cli_parsegif()\n");

    GETDATA(screen_desc);
    cli_dbgmsg("GIF: Screen size %ux%u, gctsize: %u\n", EC16(screen_desc.width), EC16(screen_desc.height), screen_desc.flags & 0x7);
    if (screen_desc.flags & 0x80)
        offset += 3 * (1 << ((screen_desc.flags & 0x7) + 1));

    while (1) {
        GETDATA(v);
        if (v == 0x21) {
            GETDATA(v);
            if (v == 0xf9) {
                offset += sizeof(struct gif_graphic_control_ext);
            } else {
                while (1) {
                    GETDATA(v);
                    if (!v)
                        break;

                    if (offset + v > map->len) {
                        retVal = CL_EPARSE;
                        goto scan_overlay;
                    }
                    offset += v;
                }
            }
        } else if (v == 0x2c) {
            GETDATA(image_desc);
            cli_dbgmsg("GIF: Image size %ux%u, left pos: %u, top pos: %u\n", EC16(image_desc.width), EC16(image_desc.height), EC16(image_desc.leftpos), EC16(image_desc.toppos));

            offset++;
            if (image_desc.flags & 0x80)
                offset += 3 * (1 << ((image_desc.flags & 0x7) + 1));

            while (1) {
                GETDATA(v);
                if (!v)
                    break;

                if (offset + v > map->len) {
                    retVal = CL_EPARSE;
                    goto scan_overlay;
                }
                offset += v;
            }
        } else if (v == 0x3b) {
            break;
        } else {
            // An unknown code: break.
            retVal = CL_EPARSE;
            goto scan_overlay;
        }
    }

scan_overlay:
    // Some recovery (I saw some "GIF89a;" or things like this)
    if (retVal == CL_EPARSE &&
        offset == (6 + sizeof(screen_desc) + 1))
        offset = 6;

    // Is there an overlay?
    if (offset < map->len) {
        cl_error_t recRetVal = cli_magic_scan_nested_fmap_type(map, offset, map->len - offset, ctx, CL_TYPE_ANY, NULL);
        retVal               = recRetVal != CL_SUCCESS ? recRetVal : retVal;
    }

    return retVal;
}