/*
    This file is part of libqobex.

    Copyright (c) 2003-2004 Mathias Froehlich <Mathias.Froehlich@web.de>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <qstring.h>
#include <qstringlist.h>
#include <qfile.h>
#include <qdir.h>
#include <qdom.h>
#include <qcstring.h>
#include <qbuffer.h>
#include <qdatetime.h>

#include <qobex/qobexuuid.h>
#include <qobex/qobexobject.h>

#include <qobex/qobexserverops.h>
#include "qobexfbsserverops.h"

#undef DEBUG
#define DEBUG
#ifdef DEBUG
#define myDebug(a) qDebug a
#else
#define myDebug(a) (void)0
#endif

QObexFBSServerOps::QObexFBSServerOps() {
  mFolderListingMode = false;
}

QObexFBSServerOps::~QObexFBSServerOps() {
}

void QObexFBSServerOps::connect( const QObexObject&, QObexObject& resp ) {
  myDebug(( "QObexFBSServerOps::connect()" ));
  if ( !isAuthenticated() )
    resp.setCode( QObexObject::Unauthorized );
}

void QObexFBSServerOps::disconnect( const QObexObject& ) {
  myDebug(( "QObexFBSServerOps::disconnect()" ));
}

void QObexFBSServerOps::get( const QObexObject& req, QObexObject& resp ) {
  myDebug(( "QObexFBSServerOps::get(): cwd = %s", getCwd().ascii() ));
 
  QString name = req.getHeader( QObexHeader::Name ).stringData();
  if ( checkForDotDot( name, resp ) )
    return;
  QString fullname = composePath( name );
  QString type = req.getHeader( QObexHeader::Type ).stringData();

  if ( !name.isEmpty() || !type.isEmpty() ) {
    if ( type == "x-obex/folder-listing" ) {
      mFolderListingMode = true;

      if ( fullname.isEmpty() )
	fullname = QString( "." );

      bool ok;
      mXmlBuf.setBuffer( getObexFolderListing( fullname, &ok ) );
      if ( ok )
	enterDirectory( name );

      mXmlBuf.open( IO_ReadOnly );
      resp.addHeader( QObexHeader( QObexHeader::Length, Q_UINT32( mXmlBuf.size() ) ) );
    } else {
      mFolderListingMode = false;
      if ( !f.isOpen() ) {
	f.setName( fullname );
	f.open( IO_ReadOnly );
	resp.addHeader( QObexHeader( QObexHeader::Length, Q_UINT32( f.size() ) ) );
      }
    }
  }
}

bool QObexFBSServerOps::dataReq( QByteArray& data, Q_LONG sz ) {
  myDebug(( "QObexFBSServerOps::dataReq( ... )" ));
  if ( 0 < sz ) {
    if ( mFolderListingMode ) {
      if ( mXmlBuf.isOpen() ) {
	data.resize( sz );
	sz = mXmlBuf.readBlock( data.data(), sz );
	data.resize( sz );
	if ( mXmlBuf.atEnd() ) {
	  mXmlBuf.close();
	  mXmlBuf.setBuffer( QByteArray() );
	}
      }
    } else {
      if ( f.isOpen() ) {
	data.resize( sz );
	sz = f.readBlock( data.data(), sz );
	if ( f.atEnd() )
	  f.close();
	if ( sz < 0 ) {
	  data.resize( 0 );
	  return false;
	}
	data.resize( sz );
      }
    }
  }
  return true;
}

void QObexFBSServerOps::del( const QObexObject& req, QObexObject& resp ) {
  myDebug(( "QObexFBSServerOps::del( ... ): cwd = %s", getCwd().ascii() ));

  QString name = req.getHeader( QObexHeader::Name ).stringData();
  if ( checkForDotDot( name, resp ) )
    return;
  if ( !name.isEmpty() ) {
    // Use locale for name ...
    QString fullname = composePath( name );

    QFileInfo fi( fullname );
    if ( fi.isDir() ) {
      QDir dir( fullname );
      QString dn = dir.dirName();
      if ( dn.isEmpty() ) {
	resp.setCode( QObexObject::Forbidden );
	resp.addHeader( QObexHeader( QObexHeader::Description, "Can not remove root directory" ) );
      } else {
	dir.setFilter( QDir::All | QDir::Hidden | QDir::System );
	dir.setSorting( QDir::Unsorted );
	if ( 2 < dir.count() ) {
	  resp.setCode( QObexObject::PreconditionFailed );
	  resp.addHeader( QObexHeader( QObexHeader::Description, "Directory not empty" ) );
	  return;
	}
	dir.cdUp();
	if ( !dir.rmdir( dn ) ) {
	  resp.setCode( QObexObject::Forbidden );
	  resp.addHeader( QObexHeader( QObexHeader::Description, "Can not remove directory" ) );
	}
      }
    } else {
      if ( !QFile::remove( fullname ) ) {
	resp.setCode( QObexObject::Forbidden );
	resp.addHeader( QObexHeader( QObexHeader::Description, "Can not remove file" ) );
      }
    }
  }
}

void QObexFBSServerOps::put( const QObexObject& req, QObexObject& resp ) {
  myDebug(( "QObexFBSServerOps::put(): cwd = %s", getCwd().ascii() ));
  if ( !f.isOpen() ) {
    QString name = req.getHeader( QObexHeader::Name ).stringData();
    if ( checkForDotDot( name, resp ) )
      return;
    QString fullname = composePath( name );
    f.setName( fullname );
    f.open( IO_WriteOnly );
  }
}

bool QObexFBSServerOps::data( const QValueList<QByteArray>& bodies ) {
  myDebug(( "QObexFBSServerOps::data( ... )" ));
  if ( f.isOpen() ) {
    QValueList<QByteArray>::ConstIterator it;
    for ( it = bodies.begin(); it != bodies.end(); ++it )
      f.writeBlock( (*it) );
  }
  // Empty bodies signal end of operation.
  if ( bodies.isEmpty() )
    f.close();
  return true;
}

void QObexFBSServerOps::setPath( const QObexObject& req, QObexObject& resp ) {
  myDebug(( "QObexFBSServerOps::setPath( ... ): cwd = %s", getCwd().ascii() ));

  bool backup = req.getFlags() & QObexObject::Backup;
  if ( backup && isRootDir() ) {
    resp.setCode( QObexObject::NotAcceptable );
    resp.addHeader( QObexHeader( QObexHeader::Description, "Can not backup from root directory" ) );
    return;
  }

  if ( !req.hasHeader( QObexHeader::Name ) ) {
    // accept silently the noop: no name header and no backup flag
    if ( backup )
      oneDirectoryUp();
  } else {
    QObexHeader hdr = req.getHeader( QObexHeader::Name );

    if ( hdr.isEmpty() )
      enterRootDirectory();
    else {
      QString name = hdr.stringData();
      if ( checkForDotDot( name, resp ) )
	return;

      QString fullname = composePath( name, backup );
      QDir dir( fullname );
      if ( dir.exists() )
	enterDirectory( hdr.stringData() );
      else {
	// hmm, directory does not exist. Check if we should create it
	if ( req.getFlags() & QObexObject::NoCreate ) {
	  resp.setCode( QObexObject::NotFound );
	  resp.addHeader( QObexHeader( QObexHeader::Description, "Directory does not exist" ) );
	} else {
	  dir.setPath( getCwd() );
	  if ( dir.mkdir( name ) ) {
	    enterDirectory( name );
	  } else {
	    resp.setCode( QObexObject::Forbidden );
	    resp.addHeader( QObexHeader( QObexHeader::Description, "Can not create directory" ) );
	  }
	}
      }
    }
  }
}

void QObexFBSServerOps::abort( const QObexObject& ) {
  myDebug(( "QObexFBSServerOps::abort( ... )" ));
  streamingError();
}

void QObexFBSServerOps::streamingError() {
  myDebug(( "QObexFBSServerOps::streamingError()" ));

  if ( mXmlBuf.isOpen() )
    mXmlBuf.close();
  mXmlBuf.setBuffer( QByteArray() );
  mFolderListingMode = false;

  if ( f.isOpen() )
    f.close();
}

bool QObexFBSServerOps::canHandle( const QByteArray& uuid ) {
  myDebug(( "QObexFBSServerOps::canHandle( ... )" ));
  return uuid == QObexUuidFBS;
}

QObexServerOps* QObexFBSServerOps::clone() {
  myDebug(( "QObexFBSServerOps::clone()" ));
  return new QObexFBSServerOps;
}

QObexAuthDigestChallenge::AuthInfo QObexFBSServerOps::serverAuthInfo() {
  myDebug(( "QObexFBSServerOps::serverAuthInfo( ... )" ));
  return QObexAuthDigestChallenge::AuthInfo( "serverRealm" );
}

QByteArray QObexFBSServerOps::serverSecret( const QString& ) {
  myDebug(( "QObexFBSServerOps::serverSecret( ... )" ));
  QByteArray secret;
  secret.duplicate( "fbsserver", 9 );
  return secret;
}

QObexAuthDigestResponse::AuthInfo QObexFBSServerOps::clientAuthInfo( const QString&, bool ) {
  myDebug(( "QObexFBSServerOps::authenticationRequired( ... )" ));
  QByteArray secret;
  secret.duplicate( "fbsserver", 9 );
  return QObexAuthDigestResponse::AuthInfo( secret, "clientUserid" );
}

bool QObexFBSServerOps::checkForDotDot( const QString& name, QObexObject& resp ) {
  myDebug(( "QObexFBSServerOps::checkForDotDot( %s, ... )", name.ascii() ));

  if ( name == ".." ||
       name.startsWith( "../" ) ||
       name.endsWith( "/.." ) ||
       name.contains( "/../" ) ) {
    resp.setCode( QObexObject::NotAcceptable );
    resp.addHeader( QObexHeader( QObexHeader::Description, "The path component .. is not allowed" ) );
    return true;
  }
  return false;
}

QByteArray QObexFBSServerOps::getObexFolderListing( const QString& name, bool*ok ) {
  QDomImplementation di;
  QDomDocument xmlDoc( di.createDocumentType( "folder-listing",
      QString::null, "obex-folder-listing.dtd" ) );
  QDomElement folderListingElement = xmlDoc.createElement( "folder-listing" );
  folderListingElement.setAttribute( "version", "1.0" );

  QDir dir( name );
  if ( dir.exists() ) {
    *ok = true;

    // stat the directory which is requested.
    // Writing to that will be the key to file deletion.
    QFileInfo dInfo( name );
    bool uDel = dInfo.permission( QFileInfo::WriteUser );
    bool gDel = dInfo.permission( QFileInfo::WriteGroup );
    bool oDel = dInfo.permission( QFileInfo::WriteOther );

    dir.setFilter( QDir::All | QDir::Hidden | QDir::System );
    dir.setSorting( QDir::Unsorted );
    QStringList entries = dir.entryList();
    QStringList::Iterator it = entries.begin();

    for ( it = entries.begin(); it != entries.end(); ++it ) {
      QString entName = *it;
      
      if ( (*it) == ".." ) {
	QDomElement element = xmlDoc.createElement( "parent-folder" );
	folderListingElement.appendChild( element );
      } else if ( (*it) != "." ) {
	QFileInfo fInfo( dir, *it );

	if ( fInfo.isDir() || fInfo.isFile() || fInfo.isSymLink() ) {
	  QDomElement element
	    = xmlDoc.createElement( fInfo.isDir() ? "folder" : "file" );
	  element.setAttribute( "name", *it );
	  element.setAttribute( "size", QString::number( fInfo.size() ) );
//          element.setAttribute( "type", "mimetype ... ???" );
	  element.setAttribute( "modified", fInfo.lastModified().toString("yyyyMMddThhmmssZ") );
	  element.setAttribute( "created", fInfo.created().toString("yyyyMMddThhmmssZ") );
	  element.setAttribute( "accessed", fInfo.lastRead().toString("yyyyMMddThhmmssZ") );
	  
	  QString perm;
	  if ( fInfo.permission( QFileInfo::ReadUser ) )
	    perm += "R";
	  if ( fInfo.permission( QFileInfo::WriteUser ) )
	    perm += "W";
	  if ( uDel )
	    perm += "D";
	  element.setAttribute( "user-perm", perm );
	  perm.truncate( 0 );
	  if ( fInfo.permission( QFileInfo::ReadGroup ) )
	    perm += "R";
	  if ( fInfo.permission( QFileInfo::WriteGroup ) )
	    perm += "W";
	  if ( gDel )
	    perm += "D";
	  element.setAttribute( "group-perm", perm );
	  perm.truncate( 0 );
	  if ( fInfo.permission( QFileInfo::ReadOther ) )
	    perm += "R";
	  if ( fInfo.permission( QFileInfo::WriteOther ) )
	    perm += "W";
	  if ( oDel )
	    perm += "D";
	  element.setAttribute( "other-perm", perm );
	  element.setAttribute( "owner", fInfo.owner() );
	  element.setAttribute( "group", fInfo.group() );
	  // FIXME ????
// 	    element.setAttribute( "xml:lang", QString( QTextCodec::locale() ) );
	  folderListingElement.appendChild( element );
	}
      }
    }
  }

  xmlDoc.appendChild( folderListingElement );
  
  QBuffer tmp;
  tmp.open( IO_WriteOnly );
  QTextStream stream( &tmp );
  stream.setEncoding( QTextStream::UnicodeUTF8 );
  stream << xmlDoc;
  tmp.close();
  
  return tmp.buffer();
}
