/*
 * inetprot.cxx
 *
 * Internet Protocol ancestor class.
 *
 * Portable Windows Library
 *
 * Copyright (c) 1993-2002 Equivalence Pty. Ltd.
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Portable Windows Library.
 *
 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
 *
 * Contributor(s): ______________________________________.
 *
 * $Log: inetprot.cxx,v $
 * Revision 1.60  2005/04/15 10:49:38  dsandras
 * Allow reading on the transport until there is an EOF or it becomes bad. Fixes interoperability problem with QSC.DE which is sending keep-alive messages, leading to a timeout (transport.good() fails, but the stream is still usable).
 *
 * Revision 1.59  2005/04/06 19:34:14  dsandras
 * Fixed typo in previous commit.
 *
 * Revision 1.58  2005/04/06 07:56:58  dsandras
 * Added continuation line support in MimeInfo to fix problem reported by Jan Schiefer thanks to Craig Southeren.
 *
 * Revision 1.57  2004/04/03 08:22:20  csoutheren
 * Remove pseudo-RTTI and replaced with real RTTI
 *
 * Revision 1.56  2004/03/23 05:59:17  csoutheren
 * Moved the Base64 routines into cypher.cxx, which is a more sensible
 * place and reduces the inclusion of unrelated code
 *
 * Revision 1.55  2002/12/19 03:35:01  craigs
 * Fixed problem with PInternetProtocol::Write returning wrong values
 *
 * Revision 1.54  2002/11/06 22:47:25  robertj
 * Fixed header comment (copyright etc)
 *
 * Revision 1.53  2002/01/06 05:40:47  robertj
 * Fixed wrong number of columns in base 64 encoder, thanks Lars Güsmar
 *
 * Revision 1.52  2001/10/03 00:25:25  robertj
 * Split out function for adding a single line of MIME info, reduces
 *    duplicated code and is useful in some other areas such as HTTP/1.1
 *
 * Revision 1.51  2001/09/28 00:44:15  robertj
 * Added SetInteger() function to set numeric MIME fields.
 *
 * Revision 1.50  2001/09/11 03:27:46  robertj
 * Improved error processing on high level protocol failures, usually
 *   caused by unexpected shut down of a socket.
 *
 * Revision 1.49  2001/09/10 02:51:23  robertj
 * Major change to fix problem with error codes being corrupted in a
 *   PChannel when have simultaneous reads and writes in threads.
 *
 * Revision 1.48  2000/11/27 00:58:06  robertj
 * Fixed crash if used PBase64::ProcessEncoding() with zero length.
 *
 * Revision 1.47  2000/11/16 07:16:58  robertj
 * Fixed maximum line length of base64 output to be 76 columns not 304.
 *
 * Revision 1.46  2000/11/14 08:28:44  robertj
 * Fixed bug in base64 encoder, overwriting memory buffer.
 *
 * Revision 1.45  2000/05/05 10:08:29  robertj
 * Fixed some GNU compiler warnings
 *
 * Revision 1.44  2000/05/02 08:29:07  craigs
 * Removed "memory leaks" caused by brain-dead GNU linker
 *
 * Revision 1.43  1999/05/04 15:26:01  robertj
 * Improved HTTP/1.1 compatibility (pass through user commands).
 * Fixed problems with quicktime installer.
 *
 * Revision 1.42  1998/12/04 10:08:01  robertj
 * Fixed bug in PMIMInfo read functions, should clear entries before loading.
 *
 * Revision 1.41  1998/11/30 04:52:02  robertj
 * New directory structure
 *
 * Revision 1.40  1998/11/03 01:03:09  robertj
 * Fixed problem with multiline response that is non-numeric.
 *
 * Revision 1.39  1998/10/16 02:05:55  robertj
 * Tried to make ReadLine more forgiving of CR CR LF combination.
 *
 * Revision 1.38  1998/09/23 06:22:20  robertj
 * Added open source copyright license.
 *
 * Revision 1.37  1998/07/24 06:55:00  robertj
 * Improved robustness of base64 decoding.
 *
 * Revision 1.36  1998/02/03 06:20:25  robertj
 * Fixed bug in Accept() function passing on to IP Accept().
 *
 * Revision 1.35  1998/01/26 02:49:20  robertj
 * GNU support.
 *
 * Revision 1.34  1998/01/26 00:46:48  robertj
 * Fixed Connect functions on PInternetProtocol so propagates read timeout variable so can adjust the connect timeout..
 *
 * Revision 1.33  1997/11/06 10:26:48  robertj
 * Fixed bug in debug dump of MIME dictionary, did not have linefeeds between entries.
 *
 * Revision 1.32  1997/06/09 04:30:03  robertj
 * Fixed multiple MIME field bug.
 *
 * Revision 1.31  1997/06/06 08:53:51  robertj
 * Fixed bug with multiple cookies (MIME fields) are sent to IE.
 *
 * Revision 1.30  1997/03/28 13:04:37  robertj
 * Fixed bug for multiple fields in MIME headers, especially cookies.
 *
 * Revision 1.29  1997/03/18 21:26:46  robertj
 * Fixed stream write of MIME putting double CR's in text files..
 *
 * Revision 1.28  1997/02/05 11:53:13  robertj
 * Changed construction of MIME dictionary to be delayed untill it is used.
 *
 * Revision 1.27  1996/12/05 11:41:12  craigs
 * Fix problem with STAT command response containing lines not starting
 * with response number
 *
 * Revision 1.26  1996/10/08 13:07:39  robertj
 * Changed default for assert to be ignore, not abort.
 *
 * Revision 1.25  1996/09/16 12:57:07  robertj
 * Fixed missing propagationof errors on open of subchannel.
 *
 * Revision 1.24  1996/09/14 13:09:36  robertj
 * Major upgrade:
 *   rearranged sockets to help support IPX.
 *   added indirect channel class and moved all protocols to descend from it,
 *   separating the protocol from the low level byte transport.
 *
 * Revision 1.23  1996/08/25 09:35:47  robertj
 * Added bug in appsock that last response is set on an I/O error.
 *
 * Revision 1.22  1996/07/15 10:33:14  robertj
 * Changed memory block base64 conversion functions to be void *.
 *
 * Revision 1.21  1996/06/03 11:58:43  robertj
 * Fixed bug in reading successive UnRead() calls getting save in wrong order.
 *
 * Revision 1.20  1996/05/26 03:46:22  robertj
 * Compatibility to GNU 2.7.x
 *
 * Revision 1.19  1996/05/15 10:15:15  robertj
 * Added access function to set intercharacter line read timeout.
 *
 * Revision 1.18  1996/05/09 12:14:04  robertj
 * Rewrote the "unread" buffer usage and then used it to improve ReadLine() performance.
 *
 * Revision 1.17  1996/03/31 08:57:34  robertj
 * Changed MIME type for no extension from binary to text.
 * Added flush of data before sending a command.
 * Added version of WriteCommand() and ExecteCommand() without argument string.
 *
 * Revision 1.15  1996/03/18 13:33:13  robertj
 * Fixed incompatibilities to GNU compiler where PINDEX != int.
 *
 * Revision 1.14  1996/03/16 04:53:07  robertj
 * Changed all the get host name and get host address functions to be more consistent.
 * Added ParseReponse() for splitting reponse line into code and info.
 * Changed lastResponseCode to an integer.
 * Fixed bug in MIME write function, should be const.
 * Added PString parameter version of UnRead().
 *
 * Revision 1.13  1996/03/04 12:20:41  robertj
 * Split file into mailsock.cxx
 *
 * Revision 1.12  1996/02/25 11:16:07  robertj
 * Fixed bug in ReadResponse() for multi-line responses under FTP..
 *
 * Revision 1.11  1996/02/25 03:05:12  robertj
 * Added decoding of Base64 to a block of memory instead of PBYTEArray.
 *
 * Revision 1.10  1996/02/19 13:31:26  robertj
 * Changed stuff to use new & operator..
 *
 * Revision 1.9  1996/02/15 14:42:41  robertj
 * Fixed warning for long to int conversion.
 *
 * Revision 1.8  1996/02/13 12:57:49  robertj
 * Added access to the last response in an application socket.
 *
 * Revision 1.7  1996/02/03 11:33:17  robertj
 * Changed RadCmd() so can distinguish between I/O error and unknown command.
 *
 * Revision 1.6  1996/01/28 14:11:11  robertj
 * Fixed bug in MIME content types for non caseless strings.
 * Added default value in string for service name.
 *
 * Revision 1.5  1996/01/28 02:48:27  robertj
 * Removal of MemoryPointer classes as usage didn't work for GNU.
 *
 * Revision 1.4  1996/01/26 02:24:29  robertj
 * Further implemetation.
 *
 * Revision 1.3  1996/01/23 13:18:43  robertj
 * Major rewrite for HTTP support.
 *
 * Revision 1.2  1995/11/09 12:19:29  robertj
 * Fixed missing state assertion in state machine.
 *
 * Revision 1.1  1995/06/17 00:50:37  robertj
 * Initial revision
 *
 */

#ifdef __GNUC__
#pragma implementation "inetprot.h"
#pragma implementation "mime.h"
#endif

#include <ptlib.h>
#include <ptlib/sockets.h>
#include <ptclib/inetprot.h>
#include <ptclib/mime.h>


static const char * CRLF = "\r\n";


//////////////////////////////////////////////////////////////////////////////
// PInternetProtocol

PInternetProtocol::PInternetProtocol(const char * svcName,
                                     PINDEX cmdCount,
                                     char const * const * cmdNames)
  : defaultServiceName(svcName),
    commandNames(cmdCount, cmdNames, TRUE),
    readLineTimeout(0, 10)   // 10 seconds
{
  SetReadTimeout(PTimeInterval(0, 0, 10));  // 10 minutes
  stuffingState = DontStuff;
  newLineToCRLF = TRUE;
  unReadCount = 0;
}


void PInternetProtocol::SetReadLineTimeout(const PTimeInterval & t)
{
  readLineTimeout = t;
}


BOOL PInternetProtocol::Read(void * buf, PINDEX len)
{
  lastReadCount = PMIN(unReadCount, len);
  const char * unReadPtr = ((const char *)unReadBuffer)+unReadCount;
  char * bufptr = (char *)buf;
  while (unReadCount > 0 && len > 0) {
    *bufptr++ = *--unReadPtr;
    unReadCount--;
    len--;
  }

  if (unReadCount == 0)
    unReadBuffer.SetSize(0);

  if (len > 0) {
    PINDEX saveCount = lastReadCount;
    PIndirectChannel::Read(bufptr, len);
    lastReadCount += saveCount;
  }

  return lastReadCount > 0;
}


BOOL PInternetProtocol::Write(const void * buf, PINDEX len)
{
  if (len == 0 || stuffingState == DontStuff)
    return PIndirectChannel::Write(buf, len);

  PINDEX totalWritten = 0;
  const char * base = (const char *)buf;
  const char * current = base;
  while (len-- > 0) {
    switch (stuffingState) {
      case StuffIdle :
        switch (*current) {
          case '\r' :
            stuffingState = StuffCR;
            break;

          case '\n' :
            if (newLineToCRLF) {
              if (current > base) {
                if (!PIndirectChannel::Write(base, current - base))
                  return FALSE;
                totalWritten += lastWriteCount;
              }
              if (!PIndirectChannel::Write("\r", 1))
                return FALSE;
              totalWritten += lastWriteCount;
              base = current;
            }
        }
        break;

      case StuffCR :
        stuffingState = *current != '\n' ? StuffIdle : StuffCRLF;
        break;

      case StuffCRLF :
        if (*current == '.') {
          if (current > base) {
            if (!PIndirectChannel::Write(base, current - base))
              return FALSE;
            totalWritten += lastWriteCount;
          }
          if (!PIndirectChannel::Write(".", 1))
            return FALSE;
          totalWritten += lastWriteCount;
          base = current;
        }
        // Then do default state

      default :
        stuffingState = StuffIdle;
        break;
    }
    current++;
  }

  if (current > base) {  
    if (!PIndirectChannel::Write(base, current - base))  
      return FALSE;  
    totalWritten += lastWriteCount;  
  }  
  
  lastWriteCount = totalWritten;  
  return lastWriteCount > 0;
}


BOOL PInternetProtocol::AttachSocket(PIPSocket * socket)
{
  if (socket->IsOpen()) {
    if (Open(socket))
      return TRUE;
    Close();
    SetErrorValues(Miscellaneous, 0x41000000);
  }
  else {
    SetErrorValues(socket->GetErrorCode(), socket->GetErrorNumber());
    delete socket;
  }

  return FALSE;
}


BOOL PInternetProtocol::Connect(const PString & address, WORD port)
{
  if (port == 0)
    return Connect(address, defaultServiceName);

  if (readTimeout == PMaxTimeInterval)
    return AttachSocket(new PTCPSocket(address, port));

  PTCPSocket * s = new PTCPSocket(port);
  s->SetReadTimeout(readTimeout);
  s->Connect(address);
  return AttachSocket(s);
}


BOOL PInternetProtocol::Connect(const PString & address, const PString & service)
{
  if (readTimeout == PMaxTimeInterval)
    return AttachSocket(new PTCPSocket(address, service));

  PTCPSocket * s = new PTCPSocket;
  s->SetReadTimeout(readTimeout);
  s->SetPort(service);
  s->Connect(address);
  return AttachSocket(s);
}


BOOL PInternetProtocol::Accept(PSocket & listener)
{
  if (readTimeout == PMaxTimeInterval)
    return AttachSocket(new PTCPSocket(listener));

  PTCPSocket * s = new PTCPSocket;
  s->SetReadTimeout(readTimeout);
  s->Accept(listener);
  return AttachSocket(s);
}


const PString & PInternetProtocol::GetDefaultService() const
{
  return defaultServiceName;
}


PIPSocket * PInternetProtocol::GetSocket() const
{
  PChannel * channel = GetBaseReadChannel();
  if (channel != NULL && PIsDescendant(channel, PIPSocket))
    return (PIPSocket *)channel;
  return NULL;
}


BOOL PInternetProtocol::WriteLine(const PString & line)
{
  if (line.FindOneOf(CRLF) == P_MAX_INDEX)
    return WriteString(line + CRLF);

  PStringArray lines = line.Lines();
  for (PINDEX i = 0; i < lines.GetSize(); i++)
    if (!WriteString(lines[i] + CRLF))
      return FALSE;

  return TRUE;
}


BOOL PInternetProtocol::ReadLine(PString & str, BOOL allowContinuation)
{
  str = PString();

  PCharArray line(100);
  PINDEX count = 0;
  BOOL gotEndOfLine = FALSE;

  int c = ReadChar();
  if (c < 0)
    return FALSE;

  PTimeInterval oldTimeout = GetReadTimeout();
  SetReadTimeout(readLineTimeout);

  while (c >= 0 && !gotEndOfLine) {
    if (unReadCount == 0) {
      char readAhead[1000];
      SetReadTimeout(0);
      if (PIndirectChannel::Read(readAhead, sizeof(readAhead)))
        UnRead(readAhead, GetLastReadCount());
      SetReadTimeout(readLineTimeout);
    }
    switch (c) {
      case '\b' :
      case '\177' :
        if (count > 0)
          count--;
        c = ReadChar();
        break;

      case '\r' :
        c = ReadChar();
        switch (c) {
          case -1 :
          case '\n' :
            break;

          case '\r' :
            c = ReadChar();
            if (c == '\n')
              break;
            UnRead(c);
            c = '\r';
            // Then do default case

          default :
            UnRead(c);
        }
        // Then do line feed case

      case '\n' :
        if (count == 0 || !allowContinuation || (c = ReadChar()) < 0)
          gotEndOfLine = TRUE;
        else if (c != ' ' && c != '\t') {
          UnRead(c);
          gotEndOfLine = TRUE;
        }
        break;

      default :
        if (count >= line.GetSize())
          line.SetSize(count + 100);
        line[count++] = (char)c;
        c = ReadChar();
    }
  }

  SetReadTimeout(oldTimeout);

  if (count > 0)
    str = PString(line, count);
  return gotEndOfLine;
}


void PInternetProtocol::UnRead(int ch)
{
  unReadBuffer.SetSize((unReadCount+256)&~255);
  unReadBuffer[unReadCount++] = (char)ch;
}


void PInternetProtocol::UnRead(const PString & str)
{
  UnRead((const char *)str, str.GetLength());
}


void PInternetProtocol::UnRead(const void * buffer, PINDEX len)
{
  char * unreadptr =
               unReadBuffer.GetPointer((unReadCount+len+255)&~255)+unReadCount;
  const char * bufptr = ((const char *)buffer)+len;
  unReadCount += len;
  while (len-- > 0)
    *unreadptr++ = *--bufptr;
}


BOOL PInternetProtocol::WriteCommand(PINDEX cmdNumber)
{
  if (cmdNumber >= commandNames.GetSize())
    return FALSE;
  return WriteLine(commandNames[cmdNumber]);
}


BOOL PInternetProtocol::WriteCommand(PINDEX cmdNumber, const PString & param)
{
  if (cmdNumber >= commandNames.GetSize())
    return FALSE;
  if (param.IsEmpty())
    return WriteLine(commandNames[cmdNumber]);
  else
    return WriteLine(commandNames[cmdNumber] & param);
}


BOOL PInternetProtocol::ReadCommand(PINDEX & num, PString & args)
{
  do {
    if (!ReadLine(args))
      return FALSE;
  } while (args.IsEmpty());

  PINDEX endCommand = args.Find(' ');
  if (endCommand == P_MAX_INDEX)
    endCommand = args.GetLength();
  PCaselessString cmd = args.Left(endCommand);

  num = commandNames.GetValuesIndex(cmd);
  if (num != P_MAX_INDEX)
    args = args.Mid(endCommand+1);

  return TRUE;
}


BOOL PInternetProtocol::WriteResponse(unsigned code, const PString & info)
{
  return WriteResponse(psprintf("%03u", code), info);
}


BOOL PInternetProtocol::WriteResponse(const PString & code,
                                       const PString & info)
{
  if (info.FindOneOf(CRLF) == P_MAX_INDEX)
    return WriteString(code & info + CRLF);

  PStringArray lines = info.Lines();
  PINDEX i;
  for (i = 0; i < lines.GetSize()-1; i++)
    if (!WriteString(code + '-' + lines[i] + CRLF))
      return FALSE;

  return WriteString(code & lines[i] + CRLF);
}


BOOL PInternetProtocol::ReadResponse()
{
  PString line;
  if (!ReadLine(line)) {
    lastResponseCode = -1;
    if (GetErrorCode(LastReadError) != NoError)
      lastResponseInfo = GetErrorText(LastReadError);
    else {
      lastResponseInfo = "Remote shutdown";
      SetErrorValues(ProtocolFailure, 0, LastReadError);
    }
    return FALSE;
  }

  PINDEX continuePos = ParseResponse(line);
  if (continuePos == 0)
    return TRUE;

  PString prefix = line.Left(continuePos);
  char continueChar = line[continuePos];
  while (line[continuePos] == continueChar ||
         (!isdigit(line[0]) && strncmp(line, prefix, continuePos) != 0)) {
    lastResponseInfo += '\n';
    if (!ReadLine(line)) {
      if (GetErrorCode(LastReadError) != NoError)
        lastResponseInfo += GetErrorText(LastReadError);
      else
        SetErrorValues(ProtocolFailure, 0, LastReadError);
      return FALSE;
    }
    if (line.Left(continuePos) == prefix)
      lastResponseInfo += line.Mid(continuePos+1);
    else
      lastResponseInfo += line;
  }

  return TRUE;
}


BOOL PInternetProtocol::ReadResponse(int & code, PString & info)
{
  BOOL retval = ReadResponse();

  code = lastResponseCode;
  info = lastResponseInfo;

  return retval;
}


PINDEX PInternetProtocol::ParseResponse(const PString & line)
{
  PINDEX endCode = line.FindOneOf(" -");
  if (endCode == P_MAX_INDEX) {
    lastResponseCode = -1;
    lastResponseInfo = line;
    return 0;
  }

  lastResponseCode = line.Left(endCode).AsInteger();
  lastResponseInfo = line.Mid(endCode+1);
  return line[endCode] != ' ' ? endCode : 0;
}


int PInternetProtocol::ExecuteCommand(PINDEX cmd)
{
  return ExecuteCommand(cmd, PString());
}


int PInternetProtocol::ExecuteCommand(PINDEX cmd,
                                       const PString & param)
{
  PTimeInterval oldTimeout = GetReadTimeout();
  SetReadTimeout(0);
  while (ReadChar() >= 0)
    ;
  SetReadTimeout(oldTimeout);
  return WriteCommand(cmd, param) && ReadResponse() ? lastResponseCode : -1;
}


int PInternetProtocol::GetLastResponseCode() const
{
  return lastResponseCode;
}


PString PInternetProtocol::GetLastResponseInfo() const
{
  return lastResponseInfo;
}


//////////////////////////////////////////////////////////////////////////////
// PMIMEInfo

PMIMEInfo::PMIMEInfo(istream & strm)
{
  ReadFrom(strm);
}


PMIMEInfo::PMIMEInfo(PInternetProtocol & socket)
{
  Read(socket);
}


void PMIMEInfo::PrintOn(ostream &strm) const
{
  BOOL output_cr = strm.fill() == '\r';
  strm.fill(' ');
  for (PINDEX i = 0; i < GetSize(); i++) {
    PString name = GetKeyAt(i) + ": ";
    PString value = GetDataAt(i);
    if (value.FindOneOf("\r\n") != P_MAX_INDEX) {
      PStringArray vals = value.Lines();
      for (PINDEX j = 0; j < vals.GetSize(); j++) {
        strm << name << vals[j];
        if (output_cr)
          strm << '\r';
        strm << '\n';
      }
    }
    else {
      strm << name << value;
      if (output_cr)
        strm << '\r';
      strm << '\n';
    }
  }
  if (output_cr)
    strm << '\r';
  strm << endl;
}


void PMIMEInfo::ReadFrom(istream &strm)
{
  RemoveAll();

  PString line;
  PString lastLine;
  while (!strm.bad() && !strm.eof()) {
    strm >> line;
    if (line.IsEmpty())
      break;
    if (line[0] == ' ') 
      lastLine += line;
    else {
      AddMIME(lastLine);
      lastLine = line;
    }
  }

  if (!lastLine.IsEmpty()) {
    AddMIME(lastLine);
  }
}


BOOL PMIMEInfo::Read(PInternetProtocol & socket)
{
  RemoveAll();

  PString line;
  while (socket.ReadLine(line, TRUE)) {
    if (line.IsEmpty())
      return TRUE;
    AddMIME(line);
  }

  return FALSE;
}


BOOL PMIMEInfo::AddMIME(const PString & line)
{
  PINDEX colonPos = line.Find(':');
  if (colonPos == P_MAX_INDEX)
    return FALSE;

  PCaselessString fieldName  = line.Left(colonPos).Trim();
  PString fieldValue = line(colonPos+1, P_MAX_INDEX).Trim();

  if (Contains(fieldName))
    fieldValue = (*this)[fieldName] + '\n' + fieldValue;

  SetAt(fieldName, fieldValue);

  return TRUE;
}


BOOL PMIMEInfo::Write(PInternetProtocol & socket) const
{
  for (PINDEX i = 0; i < GetSize(); i++) {
    PString name = GetKeyAt(i) + ": ";
    PString value = GetDataAt(i);
    if (value.FindOneOf("\r\n") != P_MAX_INDEX) {
      PStringArray vals = value.Lines();
      for (PINDEX j = 0; j < vals.GetSize(); j++) {
        if (!socket.WriteLine(name + vals[j]))
          return FALSE;
      }
    }
    else {
      if (!socket.WriteLine(name + value))
        return FALSE;
    }
  }

  return socket.WriteString(CRLF);
}


PString PMIMEInfo::GetString(const PString & key, const PString & dflt) const
{
  if (GetAt(PCaselessString(key)) == NULL)
    return dflt;
  return operator[](key);
}


long PMIMEInfo::GetInteger(const PString & key, long dflt) const
{
  if (GetAt(PCaselessString(key)) == NULL)
    return dflt;
  return operator[](key).AsInteger();
}


void PMIMEInfo::SetInteger(const PCaselessString & key, long value)
{
  SetAt(key, PString(PString::Unsigned, value));
}


static const PStringToString::Initialiser DefaultContentTypes[] = {
  { ".txt", "text/plain" },
  { ".text", "text/plain" },
  { ".html", "text/html" },
  { ".htm", "text/html" },
  { ".aif", "audio/aiff" },
  { ".aiff", "audio/aiff" },
  { ".au", "audio/basic" },
  { ".snd", "audio/basic" },
  { ".wav", "audio/wav" },
  { ".gif", "image/gif" },
  { ".xbm", "image/x-bitmap" },
  { ".tif", "image/tiff" },
  { ".tiff", "image/tiff" },
  { ".jpg", "image/jpeg" },
  { ".jpe", "image/jpeg" },
  { ".jpeg", "image/jpeg" },
  { ".avi", "video/avi" },
  { ".mpg", "video/mpeg" },
  { ".mpeg", "video/mpeg" },
  { ".qt", "video/quicktime" },
  { ".mov", "video/quicktime" }
};

PStringToString & PMIMEInfo::GetContentTypes()
{
  static PStringToString contentTypes(PARRAYSIZE(DefaultContentTypes),
                                      DefaultContentTypes,
                                      TRUE);
  return contentTypes;
}


void PMIMEInfo::SetAssociation(const PStringToString & allTypes, BOOL merge)
{
  PStringToString & types = GetContentTypes();
  if (!merge)
    types.RemoveAll();
  for (PINDEX i = 0; i < allTypes.GetSize(); i++)
    types.SetAt(allTypes.GetKeyAt(i), allTypes.GetDataAt(i));
}


PString PMIMEInfo::GetContentType(const PString & fType)
{
  if (fType.IsEmpty())
    return "text/plain";

  PStringToString & types = GetContentTypes();
  if (types.Contains(fType))
    return types[fType];

  return "application/octet-stream";
}

// End Of File ///////////////////////////////////////////////////////////////


syntax highlighted by Code2HTML, v. 0.9.1