/*
 *
 *  Multimedia Messaging Service Daemon - The Next Generation
 *
 *  Copyright (C) 2010-2011, Intel Corporation
 *                2021, Chris Talbot <chris@talbothome.com>
 *
 *  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 St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <glib.h>

#include "wsputil.h"

static const unsigned char mms_msg1[] = {
  0x8C, 0x82, 0x98, 0x4F, 0x67, 0x51, 0x4B, 0x4B,
  0x42, 0x00, 0x8D, 0x90, 0x89, 0x08, 0x80, 0x45,
  0x72, 0x6F, 0x74, 0x69, 0x6B, 0x00, 0x96, 0x50,
  0x69, 0x6E, 0x2D, 0x55, 0x70, 0x73, 0x00, 0x8A,
  0x80, 0x8E, 0x02, 0x40, 0x00, 0x88, 0x05, 0x81,
  0x03, 0x03, 0xF4, 0x80, 0x83, 0x68, 0x74, 0x74,
  0x70, 0x3A, 0x2F, 0x2F, 0x65, 0x70, 0x73, 0x33,
  0x2E, 0x64, 0x65, 0x2F, 0x4F, 0x2F, 0x5A, 0x39,
  0x49, 0x5A, 0x4F, 0x00
};

#define MMS_HDR_BCC                     0x01
#define MMS_HDR_CC                      0x02
#define MMS_HDR_CONTENT_LOCATION        0x03
#define MMS_HDR_CONTENT_TYPE            0x04
#define MMS_HDR_DATE                    0x05
#define MMS_HDR_DELIVERY_REPORT         0x06
#define MMS_HDR_DELIVERY_TIME           0x07
#define MMS_HDR_EXPIRY                  0x08
#define MMS_HDR_FROM                    0x09
#define MMS_HDR_MESSAGE_CLASS           0x0a
#define MMS_HDR_MESSAGE_ID              0x0b
#define MMS_HDR_MESSAGE_TYPE            0x0c
#define MMS_HDR_MMS_VERSION             0x0d
#define MMS_HDR_MESSAGE_SIZE            0x0e
#define MMS_HDR_PRIORITY                0x0f
#define MMS_HDR_READ_REPLY              0x10
#define MMS_HDR_REPORT_ALLOWED          0x11
#define MMS_HDR_RESPONSE_STATUS         0x12
#define MMS_HDR_RESPONSE_TEXT           0x13
#define MMS_HDR_SENDER_VISIBILITY       0x14
#define MMS_HDR_STATUS                  0x15
#define MMS_HDR_SUBJECT                 0x16
#define MMS_HDR_TO                      0x17
#define MMS_HDR_TRANSACTION_ID          0x18


static const char *mms_header[] = {
  NULL,
  "Bcc",
  "Cc",
  "X-Mms-Content-Location",
  "Content-Type",
  "Date",
  "X-Mms-Delivery-Report",
  "X-Mms-Delivery-Time",
  "X-Mms-Expiry",
  "From",
  "X-Mms-Message-Class",
  "Message-ID",
  "X-Mms-Message-Type",
  "X-Mms-MMS-Version",
  "X-Mms-Message-Size",
  "X-Mms-Priority",
  "X-Mms-Read-Reply",
  "X-Mms-Report-Allowed",
  "X-Mms-Response-Status",
  "X-Mms-Response-Text",
  "X-Mms-Sender-Visibility",
  "X-Mms-Status",
  "Subject",
  "To",
  "X-Mms-Transaction-Id",
};

static const char *wap_header[] = {
  "Accept",
  "Accept-Charset (deprecated)",
  "Accept-Encoding (deprecated)",
  "Accept-Language",
  "Accept-Ranges",
  "Age",
  "Allow",
  "Authorization",
  "Cache-Control (deprecated)",
  "Connection",
  "Content-Base (deprecated)",
  "Content-Encoding",
  "Content-Language",
  "Content-Length",
  "Content-Location",
  "Content-MD5",
  "Content-Range (deprecated)",
  "Content-Type",
  "Date",
  "Etag",
  "Expires",
  "From",
  "Host",
  "If-Modified-Since",
  "If-Match",
  "If-None-Match",
  "If-Range",
  "If-Unmodified-Since",
  "Location",
  "Last-Modified",
  "Max-Forwards",
  "Pragma",
  "Proxy-Authenticate",
  "Proxy-Authorization",
  "Public",
  "Range",
  "Referer",
  "Retry-After",
  "Server",
  "Transfer-Encoding",
  "Upgrade",
  "User-Agent",
  "Vary",
  "Via",
  "Warning",
  "WWW-Authenticate",
  "Content-Disposition (deprecated)",
  "X-Wap-Application-Id",
  "X-Wap-Content-URI",
  "X-Wap-Initiator-URI",
  "Accept-Application",
  "Bearer-Indication",
  "Push-Flag",
  "Profile",
  "Profile-Diff",
  "Profile-Warning (deprecated)",
  "Expect",
  "TE",
  "Trailer",
  "Accept-Charset",
  "Accept-Encoding",
  "Cache-Control (deprecated)",
  "Content-Range",
  "X-Wap-Tod",
  "Content-ID",
  "Set-Cookie",
  "Cookie",
  "Encoding-Version",
  "Profile-Warning",
  "Content-Disposition",
  "X-WAP-Security",
  "Cache-Control",
};

static const char *wap_param[] = {
  "Q",
  "Charset",
  "Level",
  "Type",
  "Unused",
  "Name (deprecated)",
  "Filename (deprecated)",
  "Differences",
  "Padding",
  "Type (multipart)",
  "Start (deprecated)",
  "Start-Info (deprecated)",
  "Comment (deprecated)",
  "Domain (deprecated)",
  "Max-Age",
  "Path (deprecated)",
  "Secure",
  "SEC",
  "MAC",
  "Creatione-Date",
  "Modification-Date",
  "Read-Date",
  "Size",
  "Name",
  "Filename",
  "Start",
  "Start-Info",
  "Comment",
  "Domain",
  "Path",
};

static void
decode_headers (struct wsp_header_iter *iter,
                const char             *header_lut[],
                const char             *prefix)
{
  while (wsp_header_iter_next (iter))
    {
      const unsigned char *hdr = wsp_header_iter_get_hdr (iter);
      const unsigned char *val = wsp_header_iter_get_val (iter);
      enum wsp_value_type type = wsp_header_iter_get_val_type (iter);
      unsigned int len, i;

      switch (wsp_header_iter_get_hdr_type (iter))
        {
        case WSP_HEADER_TYPE_WELL_KNOWN:
          printf ("%s%s: ", prefix, header_lut[hdr[0] & 0x7f]);
          break;
        case WSP_HEADER_TYPE_APPLICATION:
          printf ("%s%s: ", prefix, (const char *) hdr);
          break;
        }

      len = wsp_header_iter_get_val_len (iter);

      switch (type)
        {
        case WSP_VALUE_TYPE_TEXT:
          printf ("%s", (const char *) val);
          break;
        default:
          for (i = 0; i < len; i++)
            printf ("%02x ", val[i]);
          printf ("(len %d, ", len);

          if (type == WSP_VALUE_TYPE_SHORT)
            printf ("Short)");
          else
            printf ("Long)");

          break;
        }

      printf ("\n");
    }
}

static void
decode_parameters (const unsigned char *pdu,
                   unsigned int         len,
                   const char          *prefix)
{
  struct wsp_parameter_iter iter;
  struct wsp_parameter param;
  char buf[128];

  printf ("%sParameter Size: %d\n", prefix, len);

  wsp_parameter_iter_init (&iter, pdu, len);

  while (wsp_parameter_iter_next (&iter, &param))
    {
      if (param.type == WSP_PARAMETER_TYPE_UNTYPED)
        printf ("%s%s: ", prefix, param.untyped);
      else
        printf ("%s%s: ", prefix, wap_param[param.type]);

      switch (param.value)
        {
        case WSP_PARAMETER_VALUE_TEXT:
          printf ("%s\n", param.text);
          break;
        case WSP_PARAMETER_VALUE_INT:
          printf ("%u\n", param.integer);
          break;
        case WSP_PARAMETER_VALUE_DATE:
          strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z",
                    localtime (&param.date));
          buf[127] = '\0';
          printf ("%s\n", buf);
          break;
        case WSP_PARAMETER_VALUE_Q:
          printf ("\n");
        }
    }

  printf ("\n");
}

static void
decode_message (const unsigned char *data,
                unsigned int         size)
{
  struct wsp_header_iter iter;
  struct wsp_multipart_iter mi;
  unsigned int flags = 0;
  const void *ct;
  unsigned int ct_len;
  const void *multipart_mimetype;
  unsigned int consumed;

  flags |= WSP_HEADER_ITER_FLAG_REJECT_CP;
  flags |= WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART;

  wsp_header_iter_init (&iter, data, size, flags);

  decode_headers (&iter, mms_header, "");

  if (wsp_header_iter_at_end (&iter) == TRUE)
    goto done;

  if (wsp_header_iter_is_multipart (&iter) == FALSE)
    {
      printf ("Not a multipart header, but not at the end"
              "... something is wrong\n");
      return;
    }

  if (wsp_multipart_iter_init (&mi, &iter, &ct, &ct_len) == FALSE)
    {
      printf ("multipart_iter_init failed\n");
      return;
    }

  if (wsp_decode_content_type (ct, ct_len, &multipart_mimetype,
                               &consumed, NULL) == FALSE)
    {
      printf ("Unable to decode multipart-mimetype\n");
      return;
    }

  printf ("Content-Type: %s\n", (const char *) multipart_mimetype);
  decode_parameters (ct + consumed, ct_len - consumed, "\t");

  while (wsp_multipart_iter_next (&mi) == TRUE)
    {
      struct wsp_header_iter hi;

      ct = wsp_multipart_iter_get_content_type (&mi);
      ct_len = wsp_multipart_iter_get_content_type_len (&mi);

      if (wsp_decode_content_type (ct, ct_len, &multipart_mimetype,
                                   &consumed, NULL) == FALSE)
        {
          printf ("Unable to decode part multipart-mimetype\n");
          return;
        }

      printf ("\tContent-Type: %s\n",
              (const char *) multipart_mimetype);
      decode_parameters (ct + consumed, ct_len - consumed, "\t\t");
      printf ("\tHeader Size: %d\n",
              wsp_multipart_iter_get_hdr_len (&mi));

      wsp_header_iter_init (&hi, wsp_multipart_iter_get_hdr (&mi),
                            wsp_multipart_iter_get_hdr_len (&mi),
                            0);
      decode_headers (&hi, wap_header, "\t");

      if (wsp_header_iter_at_end (&hi) == FALSE)
        {
          printf ("Unable to fully decode part headers\n");
          return;
        }

      printf ("\tBody Size: %d\n\n",
              wsp_multipart_iter_get_body_len (&mi));
    }

  if (wsp_multipart_iter_close (&mi, &iter) == FALSE)
    {
      printf ("Unable to close multipart iter\n");
      return;
    }

  if (wsp_header_iter_at_end (&iter) == FALSE)
    {
      printf ("Didn't consume the entire message\n");
      return;
    }

 done:
  printf ("Done\n");
}

static int
open_file (const char *pathname)
{
  struct stat st;
  unsigned char *map;
  size_t size;
  int fd;

  fd = open (pathname, O_RDONLY);
  if (fd < 0)
    return -1;

  if (fstat (fd, &st) < 0)
    {
      close (fd);
      return -1;
    }

  size = st.st_size;

  map = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0);
  if (!map || map == MAP_FAILED)
    {
      close (fd);
      return -1;
    }

  decode_message (map, size);

  munmap (map, size);

  close (fd);

  return 0;
}

int
main (int   argc,
      char *argv[])
{
  if (argc < 2)
    {
      decode_message (mms_msg1, sizeof(mms_msg1));
      return 0;
    }

  if (open_file (argv[1]) < 0)
    {
      fprintf (stderr, "Failed to open file\n");
      return 1;
    }

  return 0;
}
