/*  -*- Mode: C;  -*- */

/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe is free software; you can redistribute it and/or modify            *
*   it under the terms of the GNU General Public License as published by      *
*   the Free Software Foundation; either version 2 of the License, or         *
*   (at your option) any later version.                                       *
*                                                                             *
*   Nprobe 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 Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <assert.h>
#include <sys/param.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#ifdef __alpha__
#include <sys/mbuf.h>
#endif
#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#undef __STDC__
#include <netinet/ip.h>

#ifdef __alpha__
#include <netinet/ip_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <limits.h>
#include <linux/limits.h>

#ifdef WRITE_OBJECTS
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <errno.h>
#endif

#include "list.h"
#include "pkt.h"
#include "interface.h"
#include "flows.h"
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "udp_ns.h"
#include "seq.h"
#include "fingerprint.h"
#include "pool.h"
#include "if_nprobe.h"
#include "output.h"
#include "interesting.h"
#include "content_t.h"

#ifdef PRINT_OUT
#include "print_util.h"
#endif

int 
chain_new_imgcharbuf(tcp_conn_t *tconnp)
{
  il_imgchars_buf_t *current;
  il_imgchars_buf_t *next;

  current = HTML_IMG_CHARS.current;
  current->nchars = HTML_IMG_CHARS_LEN;

  if (HTML_IMG_CHARS.nbufs == HTML_IMG_CHARS_MAX_BUFS)
    return 0;

  HTML_IMG_CHARS_TOTLEN += current->nchars;
  next = get_il_imgchars_buffer();
  HTML_IMG_CHARS.nbufs++;
  current->next = next;
  HTML_IMG_CHARS_LEN = 0U;
  HTML_IMG_CHARS_BUF = next->buf;
  HTML_IMG_CHARS.current = next;

  return 1;
}

int 
chain_new_link_urls_buf(tcp_conn_t *tconnp)
{
  il_imgchars_buf_t *current;
  il_imgchars_buf_t *next;

  current = HTML_LINK_URLS.current;
  current->nchars = HTML_LINK_URLS_LEN;

  if (HTML_LINK_URLS.nbufs == HTML_IMG_CHARS_MAX_BUFS)
    return 0;

  HTML_LINK_URLS_TOTLEN += current->nchars;
  next = get_il_imgchars_buffer();
  HTML_LINK_URLS.nbufs++;
  current->next = next;
  HTML_LINK_URLS_LEN = 0U;
  HTML_LINK_URLS_BUF = next->buf;
  HTML_LINK_URLS.current = next;

  return 1;
}

/*
 * some HTML doesn't end the IMG SRC with a " 
 */
char *
find_img_src_end(char *s, int len)
{
  char c;
  char *endp = s+len;
  while (s < endp)
    {
      c = *s;
      if (c == '"' || c == '>' || c == '?' || c == ';' || c == ' ')
	return s;
      s++;
    }

  return NULL;
}

/*
 * some HTML doesn't end the HREF URL with a " 
 */
char *
find_href_end(char *s, int len)
{
  char c;
  char *endp = s+len;
  while (s < endp)
    {
      c = *s;
      if (c == '"' || c == '>' || c == '?' || c == ';' || c == ' ')
	return s;
      s++;
    }

  return NULL;
}

/*
 * some HTML doesn't end the HREF URL with a " 
 */
char *
find_linkref_end(char *s, int len)
{
  char c;
  char *endp = s+len;
  while (s < endp)
    {
      c = *s;
      if (c == '"' || c == '>' || c == ' ')
	return s;
      s++;
    }

  return NULL;
}

struct buf 
{
  int len;
  char *buf;
};

#define START buf.buf
#define LEN buf.len

#define PULL(buf, adj) \
MACRO_BEGIN   \
assert(adj <= buf.len);  \
(buf).len-=(adj);                  \
(buf).buf+=(adj); \
MACRO_END

#define JUMP_SPACE(buf)              \
MACRO_BEGIN  \
while (*(buf).buf == ' ' && LEN > 0)  \
  PULL(buf, 1);              \
MACRO_END

#define CLEAR_CHARS(buf, chars)              \
MACRO_BEGIN  \
int i;  \
int l = strlen(chars);  \
for (i = 0; i < l && (buf).len > 0; i++)  \
{  \
  if (*(buf).buf == chars[i])  \
    {  \
      PULL(buf, 1);  \
      i = 0;  \
    }  \
}  \
MACRO_END



int
parse_rep_body(prec_t *pp, tcp_conn_t *tconnp, int len, short code)
{
  char *cp;
  /* 
   * for keeping track within this parse 
   * - START and LEN refer to this structure */
  struct buf buf;
  buf.buf = pp->buf;
  buf.len = len;
  
  if (HTML_IMG_CHARS_CHAIN == NULL)
    {
      /* need buffer */
      il_imgchars_buf_t *bp = get_il_imgchars_buffer();
      HTML_IMG_CHARS_BUF = bp->buf;
      HTML_IMG_CHARS_CHAIN = bp;
      HTML_IMG_CHARS_CURRBUF = bp;
      HTML_IMG_CHARS.nbufs = 1;
    }
  
  if (HTML_LINK_URLS_CHAIN == NULL)
    {
      /* need buffer */
      il_imgchars_buf_t *bp = get_il_imgchars_buffer();
      HTML_LINK_URLS_BUF = bp->buf;
      HTML_LINK_URLS_CHAIN = bp;
      HTML_LINK_URLS_CURRBUF = bp;
      HTML_LINK_URLS.nbufs = 1;
    }
  
  /* New packet - need timestamp */
  HTML_PARSE_STATE |= HTML_P_NEED_IMG_TIMESTAMP;
  HTML_PARSE_STATE |= HTML_P_NEED_URL_TIMESTAMP;
  
  /* What's the status of the parse? - picking up in a new packet? */
  
  switch (HTML_PARSE_STATE & 0xfffffff) /* don't want high nibble */
    {
    case HTML_P_GOT_ELEMENT: goto p_got_element; break;
      
    case HTML_P_IN_MATCH_IMG: goto p_in_match_img; break;
    case HTML_P_GOT_MATCH_IMG: goto p_got_match_img; break;
    case HTML_P_IN_IMG_SRC: goto p_in_img_src; break;
    case HTML_P_IN_IMG_SUNDRYCHARS: goto p_in_img_sundrychars; break;
    case HTML_P_IN_IMG_URL: goto p_in_img_url; break;
      
    case HTML_P_IN_CLEAR_ANCHOR: goto p_in_clear_anchor; break;
    case HTML_P_IN_MATCH_HREF: goto p_in_match_href; break;
    case HTML_P_IN_HREF_SUNDRYCHARS: goto p_in_href_sundrychars; break;
    case HTML_P_IN_HREF_URL: goto p_in_href_url; break;
      
    case HTML_P_IN_MATCH_BASE: goto p_in_match_base; break;
    case HTML_P_IN_BASE_SUNDRYCHARS: goto p_in_base_sundrychars; break;
    case  HTML_P_IN_BASE_URL: goto  p_in_base_url; break;
      
    case HTML_P_IN_MATCH_LINK: goto p_in_match_link; break;
    case HTML_P_IN_MATCH_LINK_HREF: goto p_got_match_link; break;
    case HTML_P_IN_LINK_SUNDRYCHARS: goto p_in_link_sundrychars; break;
    case HTML_P_IN_LINK_URL: goto p_in_link_url; break;
      
    case HTML_P_NOT_STARTED:
    default:
      break;
    }
  
  while ((cp = seqchr(START, '<', LEN)) != NULL)
    {
      /* seqchr() won't run us past the end */
      int status;
      int charlen, copylen;
      char *match, *top, *fromp;
      
      PULL(buf, (cp+1) - START);
      if (!LEN)
	{
	  SET_HTML_PARSE_STATE(HTML_P_GOT_ELEMENT);
	  goto out;
	}
    p_got_element:   
      switch (*START)
	{
	  
	  /*****************************************************************************/
	  /* Looking for in-line image */
	  
	  /*case 'i':*/
	case 'I':
	p_in_match_img:
	  match = "img ";
	  status = 
	    ci_seqstrncmp(START, match + HTML_PARSE_STATE_MATCH_INDX, LEN);
	  /* can't run off end */
	  if (status == 0)
	    {
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	      continue;
	    }
	  else if (status < 0)
	    {
	      HTML_PARSE_STATE_MATCH_INDX -= status;
	      SET_HTML_PARSE_STATE(HTML_P_IN_MATCH_IMG);
	      goto out;
	    }
	  else
	    {
	      PULL(buf, status);
	    }
	p_got_match_img:
	  /* find "src" */
	  while (1)
	    {
	      cp = ci_seqchr(START, 's', LEN);
	      if (cp)
		{
		  PULL(buf, cp-START);
		  HTML_PARSE_STATE_MATCH_INDX = 0;
		p_in_img_src:
		  match = "src";
		  status = 
		    ci_seqstrncmp(START, 
				  match + HTML_PARSE_STATE_MATCH_INDX, LEN);
		  if (status > 0)
		    {
		      PULL(buf, status);
		      break;
		    }
		  else if (status == 0)
		    {
		      /* reset */
		      HTML_PARSE_STATE_MATCH_INDX = 0;
		      PULL(buf, sizeof(char));
		      continue;
		    }
		  else
		    /* run out of packet */
		    {
		      HTML_PARSE_STATE_MATCH_INDX -= status;
		      SET_HTML_PARSE_STATE(HTML_P_IN_IMG_SRC);
		      goto out;
		    }
		}
	      else 
		/* not going to find SRC attribute */
		{
		  SET_HTML_PARSE_STATE(HTML_P_GOT_MATCH_IMG);
		  goto out;
		}
	    }
	  /* get up to start of URL string */
	p_in_img_sundrychars:
	  CLEAR_CHARS(buf, " =\"");
	  if (!LEN)
	    {
	      SET_HTML_PARSE_STATE(HTML_P_IN_IMG_SUNDRYCHARS);
	      goto out;
	    }
	    p_in_img_url:
	  /* find end of image URL string */
	  /*cp = seqchr(START, '\"', LEN);*/
	  cp = find_img_src_end(START, LEN);
	  if (cp)
	    /* have whole string */
	    {
	      charlen = cp - START;
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	    }
	  else
	    {
	      charlen = LEN;
	      SET_HTML_PARSE_STATE(HTML_P_IN_IMG_URL);
	    }
	  
	  /* room in current buffer? */
	  if (charlen + HTML_IMG_CHARS_LEN > HTTP_IMAGECHARS_BUFLEN 
	      && !chain_new_imgcharbuf(tconnp))
	    {
	      HTTP_REP_TRANS_STATUS |=  TRANS_IMG_CHARS_EX;
	      goto out;
	    }
	  
	  
	  top = HTML_IMG_CHARS_BUF;
	  fromp = START;
	  HTTP_ASSERT(charlen>=0, pp, tconnp, 
		      HTTP_SERV_ASSERT_FAIL_IMGSRC_COPLEN, SERVER, 
		      NULL, 
		      *top = '\0'; *(top+1) = '\0');
	  copylen = charlen;
	  memcpy(top, fromp, copylen);
	  top += copylen;
	  if (cp && copylen)
	    {
	      *top = '\0';	/* record delimiter */
	      copylen++;
	      if (HTML_PARSE_STATE & HTML_P_NEED_IMG_TIMESTAMP)
		{
		  //int offset_us = (int)(TCP_SSOLID_TM - HTTP_FIRSTREP_SEEN);
		  int offset_us = (int)(TCP_SSOLID_TM - HTTP_FIRSTREP_SEEN);
		  *(++top) = '\0'; /* time stamp preamble */
		  // memcpy(++top, &offset_us, sizeof(uint)); /* time stamp */
		  *((unsigned int *)(++top)) = offset_us; /* time stamp */
		  *(top + sizeof(uint)) = '\0';	/* record delimiter */
		  copylen += (2*sizeof(char) + sizeof(uint));
		  HTML_PARSE_STATE &= ~HTML_P_NEED_IMG_TIMESTAMP;
		}
	    }
	  HTML_IMG_CHARS_LEN += copylen;
	  HTML_IMG_CHARS_BUF += copylen;
	  PULL(buf, charlen);
	  break;
	  
	  /*****************************************************************************/
	  
	  /* Looking for link */
	case 'a':
	case 'A':
	  PULL(buf, sizeof(char));
	p_in_clear_anchor:
	  SET_HTML_PARSE_STATE(HTML_P_IN_CLEAR_ANCHOR);
	  CLEAR_CHARS(buf, " \"");
	p_in_match_href:
	  //fprintf(stderr, "XXX Found anchor XXX\n");
	  match = "href";
	  status = 
	    ci_seqstrncmp(START, match + HTML_PARSE_STATE_MATCH_INDX, LEN);
	  if (status == 0)
	    {
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	      continue;
	    }
	  else if (status < 0)
	    {
	      HTML_PARSE_STATE_MATCH_INDX -= status;
	      SET_HTML_PARSE_STATE(HTML_P_IN_MATCH_HREF);
	      goto out;
	    }
	  else
	    {
	      //fprintf(stderr, "XXX Found href XXX\n");
	      PULL(buf, status);
	    }
	  
	  /* get up to start of URL string */
	p_in_href_sundrychars:
	  CLEAR_CHARS(buf, " =\"");
	  if (!LEN)
	    {
	      SET_HTML_PARSE_STATE(HTML_P_IN_HREF_SUNDRYCHARS);
	      goto out;
	    }
	p_in_href_url:
	  /* not interested in internal links */
	  if (*START == '#')
	    {
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	      continue;
	    }
	  
	  /* find end of link URL string */
	  cp = find_href_end(START, LEN);
	      if (cp)
		/* have whole string */
		{
		  charlen = cp - START;
		  /* reset */
		  HTML_PARSE_STATE_MATCH_INDX = 0;
		  SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
		}
	      else
		{
		  charlen = LEN;
		  SET_HTML_PARSE_STATE(HTML_P_IN_HREF_URL);
		}
	  
	  /* room in current buffer? */
	  if (charlen + HTML_LINK_URLS_LEN > HTTP_IMAGECHARS_BUFLEN 
	      && !chain_new_link_urls_buf(tconnp))
	    {
	      HTTP_REP_TRANS_STATUS |=  TRANS_URL_CHARS_EX;
	      goto out;
	    }
	  
	  
	  top = HTML_LINK_URLS_BUF;
	  fromp = START;
#if 0
	  /* TMP */
	  if (charlen <= 0)
	    {
	      fprintf(stderr, "BAD URL len %d\n", charlen);
	      fprintf(stderr, "buf len = %d\n", buf.len);
	    }
	  HTTP_ASSERT(charlen>0, pp, tconnp, 
		      HTTP_SERV_ASSERT_FAIL_LINKURL_COPLEN, SERVER, 
		      NULL, 
		      *top = '\0'; *(top+1) = '\0');
#endif
	  
	  HTTP_ASSERT(charlen>=0, pp, tconnp, 
		      HTTP_SERV_ASSERT_FAIL_LINKURL_COPLEN, SERVER, 
		      NULL, 
		      *top = '\0'; *(top+1) = '\0');
	  copylen = charlen;
	  memcpy(top, fromp, copylen);
	  //fprintf(stderr, "XXX Copying link XXX\n");
	  top += copylen;
	  if (cp && copylen)
	    {
	      *top = '\0';	/* record delimiter */
	      copylen++;
	      if (HTML_PARSE_STATE & HTML_P_NEED_URL_TIMESTAMP)
		{
		  //int offset_us = (int)(TCP_SSOLID_TM - HTTP_FIRSTREP_SEEN);
		  int offset_us = (int)(TCP_SSOLID_TM - HTTP_FIRSTREP_SEEN);
		  *(++top) = '\0'; /* time stamp preamble */
		  // memcpy(++top, &offset_us, sizeof(uint)); /* time stamp */
		  *((unsigned int *)(++top)) = offset_us; /* time stamp */
		  *(top + sizeof(uint)) = '\0';	/* record delimiter */
		  copylen += (2*sizeof(char) + sizeof(uint));
		  HTML_PARSE_STATE &= ~HTML_P_NEED_URL_TIMESTAMP;
		}
	    }
	  HTML_LINK_URLS_LEN += copylen;
	  HTML_LINK_URLS_BUF += copylen;
	  PULL(buf, charlen);
	  break;
	  
	  /*****************************************************************************/
	  
	  
	  /* Looking for HTML header base */
	case 'b':
	case 'B':
	p_in_match_base:
	  match = "base href";
	  status = 
	    ci_seqstrncmp(START, match + HTML_PARSE_STATE_MATCH_INDX, LEN);
	  if (status == 0)
	    {
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	      continue;
	    }
	  else if (status < 0)
	    {
	      HTML_PARSE_STATE_MATCH_INDX -= status;
	      SET_HTML_PARSE_STATE(HTML_P_IN_MATCH_BASE);
	      goto out;
	    }
	  else
	    {
	      //fprintf(stderr, "XXX Found href XXX\n");
	      PULL(buf, status);
	    }
	  
	  /* get up to start of URL string */
	p_in_base_sundrychars:
	  CLEAR_CHARS(buf, " =\"");
	  if (!LEN)
	    {
	      SET_HTML_PARSE_STATE(HTML_P_IN_BASE_SUNDRYCHARS);
	      goto out;
	    }
	p_in_base_url:
	  /* find end of link URL string */
	  cp = find_href_end(START, LEN);
	  if (cp)
	    /* have whole string */
	    {
	      charlen = cp - START;
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	    }
	  else
	    {
	      charlen = LEN;
	      SET_HTML_PARSE_STATE(HTML_P_IN_BASE_URL);
	    }
	  
	  
	  top = HTML_BASESTR + HTML_BASESTRLEN;
	  fromp = START;
	  copylen = charlen;
	  
	  /* room in buffer? */
	  if (copylen + HTML_BASESTRLEN > HTTP_REQSTR_LEN)
	    {
	      /* XXX TODO - record truncation */
	      copylen =  HTTP_REQSTR_LEN - HTML_BASESTRLEN;
	    }
	  //fprintf(stderr, "XXX Copying HTML base\n");
	  memcpy(top, fromp, copylen);
	  HTML_BASESTRLEN += copylen;
	  //fprintf(stderr, "XXX Copying link XXX\n");
	  PULL(buf, charlen);
	  break;
	  
	  /*****************************************************************************/
	  
	  
	  /* Looking for header style sheet reference */
	case 'l':
	case 'L':
	  HTML_PARSE_STATE_MATCH_INDX = 0;
	p_in_match_link:
	  match = "link ";
	  status = 
	    ci_seqstrncmp(START, match + HTML_PARSE_STATE_MATCH_INDX, LEN);
	  /* can't run off end */
	  if (status == 0)
	    {
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	      continue;
	    }
	  else if (status < 0)
	    {
	      HTML_PARSE_STATE_MATCH_INDX -= status;
	      SET_HTML_PARSE_STATE(HTML_P_IN_MATCH_LINK);
	      goto out;
	    }
	  else
	    {
	      PULL(buf, status);
	    }
	  HTML_PARSE_STATE_MATCH_INDX = 0;
	p_got_match_link:
	  /* find "href" */
	  match = "href";
	  status = 
	    ci_seqstrncmp(START, match + HTML_PARSE_STATE_MATCH_INDX, LEN);
	  /* can't run off end */
	  if (status == 0)
	    {
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	      continue;
	    }
	  else if (status < 0)
	    {
	      HTML_PARSE_STATE_MATCH_INDX -= status;
	      SET_HTML_PARSE_STATE(HTML_P_IN_MATCH_LINK_HREF);
	      goto out;
	    }
	  else
	    {
	      PULL(buf, status);
	    }
	p_in_link_sundrychars:
	  /* get up to start of link URL string */
	  CLEAR_CHARS(buf, " =\"");
	  if (!LEN)
	    {
	      SET_HTML_PARSE_STATE(HTML_P_IN_LINK_SUNDRYCHARS);
	      goto out;
	    }
	p_in_link_url:
	  /* find end of link URL string */
	  cp = find_linkref_end(START, LEN);
	  if (cp)
	    /* have whole string */
	    {
	      charlen = cp - START;
	      /* reset */
	      HTML_PARSE_STATE_MATCH_INDX = 0;
	      SET_HTML_PARSE_STATE(HTML_P_NOT_STARTED);
	    }
	  else
	    {
	      charlen = LEN;
	      SET_HTML_PARSE_STATE(HTML_P_IN_LINK_URL);
	    }
	  
	  /* room in current buffer? */
	  if (charlen + HTML_IMG_CHARS_LEN > HTTP_IMAGECHARS_BUFLEN 
	      && !chain_new_imgcharbuf(tconnp))
	    {
	      HTTP_REP_TRANS_STATUS |=  TRANS_IMG_CHARS_EX;
	      goto out;
	    }
	  
	  
	  top = HTML_IMG_CHARS_BUF;
	  HTML_PARSE_STATE_TMPP = top;
	  fromp = START;
	  HTTP_ASSERT(charlen>=0, pp, tconnp, 
		      HTTP_SERV_ASSERT_FAIL_IMGSRC_COPLEN, SERVER, 
		      NULL, 
		      *top = '\0'; *(top+1) = '\0');
	  copylen = charlen;
	  memcpy(top, fromp, copylen);
	  top += copylen;
	  if (cp && copylen)
	    {
	      *top = '\0';	/* record delimiter */
	      copylen++;
	      /* got a URL - but is it to a style sheet? */
	      if (strcmp(top-4, ".css"))
		{
		  /* no - cancel everything */
		  top = HTML_PARSE_STATE_TMPP;
		  copylen = charlen = 0;
		}
	      else
		{
		  //fprintf(stderr, "XXXXX GOT ONE XXXX\n");
		  if (HTML_PARSE_STATE & HTML_P_NEED_IMG_TIMESTAMP)
		    {
		      int offset_us = (int)(TCP_SSOLID_TM - HTTP_FIRSTREP_SEEN);
		      *(++top) = '\0'; /* time stamp preamble */
		      *((unsigned int *)(++top)) = offset_us; /* time stamp */
		      *(top + sizeof(uint)) = '\0';	/* record delimiter */
		      copylen += (2*sizeof(char) + sizeof(uint));
		      HTML_PARSE_STATE &= ~HTML_P_NEED_IMG_TIMESTAMP;
		    }
		}
	    }
	  HTML_IMG_CHARS_LEN += copylen;
	  HTML_IMG_CHARS_BUF += copylen;
	  PULL(buf, charlen);
	  break;
	  
	  /*****************************************************************************/
	  
	default:
	  continue;
	  break;
	  
	} /* end switch */
      assert(buf.len >= 0);
    } /* end while */

 out:

  return 0;
}


/*
 * end parse_object.c 
 */


