
/////////////////////////////////////////////////////////////////////////////
//
// Project:     SMB kioslave for KDE2
//
// File:        kio_smb_browse.cpp
//
// Abstract:    member function implementations for SMBSlave that deal with
//              SMB browsing
//
// Author(s):   Matthew Peterson <mpeterson@caldera.com>
//
//---------------------------------------------------------------------------
//
// Copyright (c) 2000  Caldera Systems, Inc.
//
// This program 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.1 of the License, or
// (at your option) any later version.
//
//     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 Lesser General Public License for more details.
//
//     You should have received a copy of the GNU General Public License
//     along with this program; see the file COPYING.  If not, please obtain
//     a copy from http://www.gnu.org/copyleft/gpl.html
//
/////////////////////////////////////////////////////////////////////////////

#include <config.h>
#include <pwd.h>
#include <grp.h>

#include <qtextcodec.h>

#include <kglobal.h>

#include "kio_smb.h"
#include "kio_smb_internal.h"

using namespace KIO;

int SMBSlave::cache_stat(const SMBUrl &url, struct stat* st )
{
    int result = smbc_stat( url.toSmbcUrl(), st);
    kdDebug(KIO_SMB) << "smbc_stat " << url << " " << errno << " " << result << endl;
    kdDebug(KIO_SMB) << "size " << (KIO::filesize_t)st->st_size << endl;
    return result;
}

//---------------------------------------------------------------------------
bool SMBSlave::browse_stat_path(const SMBUrl& _url, UDSEntry& udsentry, bool ignore_errors)
  // Returns: true on success, false on failure
{
    UDSAtom     udsatom;

    SMBUrl url = _url;

   if(cache_stat(url, &st) == 0)
   {
      if(!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode))
      {
         kdDebug(KIO_SMB)<<"SMBSlave::browse_stat_path mode: "<<st.st_mode<<endl;
         warning(i18n("%1:\n"
                      "Unknown file type, neither directory or file.").arg(url.prettyURL()));
         return false;
      }

      udsatom.m_uds  = KIO::UDS_FILE_TYPE;
      udsatom.m_long = st.st_mode & S_IFMT;
      udsentry.append(udsatom);

      udsatom.m_uds  = KIO::UDS_SIZE;
      udsatom.m_long = st.st_size;
      udsentry.append(udsatom);

      udsatom.m_uds  = KIO::UDS_USER;
      uid_t uid = st.st_uid;
      struct passwd *user = getpwuid( uid );
      if ( user )
          udsatom.m_str = user->pw_name;
      else
          udsatom.m_str = QString::number( uid );
      udsentry.append(udsatom);

      udsatom.m_uds  = KIO::UDS_GROUP;
      gid_t gid = st.st_gid;
      struct group *grp = getgrgid( gid );
      if ( grp )
          udsatom.m_str = grp->gr_name;
      else
          udsatom.m_str = QString::number( gid );
      udsentry.append(udsatom);

      udsatom.m_uds  = KIO::UDS_ACCESS;
      udsatom.m_long = st.st_mode & 07777;
      udsentry.append(udsatom);

      udsatom.m_uds  = UDS_MODIFICATION_TIME;
      udsatom.m_long = st.st_mtime;
      udsentry.append(udsatom);

      udsatom.m_uds  = UDS_ACCESS_TIME;
      udsatom.m_long = st.st_atime;
      udsentry.append(udsatom);

      udsatom.m_uds  = UDS_CREATION_TIME;
      udsatom.m_long = st.st_ctime;
      udsentry.append(udsatom);

   }
   else
   {
       if (!ignore_errors) {
           if (errno == EPERM || errno == EACCES)
               if (checkPassword(url)) {
                   redirection( url );
                   return false;
               }

           reportError(url);
       } else if (errno == ENOENT || errno == ENOTDIR) {
           warning(i18n("File does not exist: %1").arg(url.url()));
       }
       kdDebug(KIO_SMB) << "SMBSlave::browse_stat_path ERROR!!"<< endl;
       return false;
   }

   return true;
}

//===========================================================================
void SMBSlave::stat( const KURL& kurl )
{
    kdDebug(KIO_SMB) << "SMBSlave::stat on "<< kurl << endl;
    // make a valid URL
    KURL url = checkURL(kurl);

    // if URL is not valid we have to redirect to correct URL
    if (url != kurl)
    {
        kdDebug() << "redirection " << url << endl;
        redirection(url);
        finished();
        return;
    }

    m_current_url = url;

    UDSAtom     udsatom;
    UDSEntry    udsentry;
    // Set name
    udsatom.m_uds = KIO::UDS_NAME;
    udsatom.m_str = kurl.fileName();
    udsentry.append( udsatom );

    switch(m_current_url.getType())
    {
    case SMBURLTYPE_UNKNOWN:
        error(ERR_MALFORMED_URL,m_current_url.prettyURL());
        finished();
        return;

    case SMBURLTYPE_ENTIRE_NETWORK:
    case SMBURLTYPE_WORKGROUP_OR_SERVER:
        udsatom.m_uds = KIO::UDS_FILE_TYPE;
        udsatom.m_long = S_IFDIR;
        udsentry.append(udsatom);
        break;

    case SMBURLTYPE_SHARE_OR_PATH:
        if (browse_stat_path(m_current_url, udsentry, false))
            break;
        else {
            kdDebug(KIO_SMB) << "SMBSlave::stat ERROR!!"<< endl;
            finished();
            return;
        }
    default:
        kdDebug(KIO_SMB) << "SMBSlave::stat UNKNOWN " << url << endl;
        finished();
        return;
    }

    statEntry(udsentry);
    finished();
}

//===========================================================================
// TODO: complete checking
KURL SMBSlave::checkURL(const KURL& kurl) const
{
    kdDebug(KIO_SMB) << "checkURL " << kurl << endl;
    QString surl = kurl.url();
    if (surl.startsWith("smb:/")) {
        if (surl.length() == 5) // just the above
            return kurl; // unchanged

        if (surl.at(5) != '/') {
            surl = "smb://" + surl.mid(5);
            kdDebug(KIO_SMB) << "checkURL return1 " << surl << " " << KURL(surl) << endl;
            return KURL(surl);
        }
    }

    // smb:/ normaly have no userinfo
    // we must redirect ourself to remove the username and password
    if (surl.contains('@') && !surl.contains("smb://")) {
        KURL url(kurl);
        url.setPath("/"+kurl.url().right( kurl.url().length()-kurl.url().find('@') -1));
        QString userinfo = kurl.url().mid(5, kurl.url().find('@')-5);
        if(userinfo.contains(':'))  {
            url.setUser(userinfo.left(userinfo.find(':')));
            url.setPass(userinfo.right(userinfo.length()-userinfo.find(':')-1));
        } else {
            url.setUser(userinfo);
        }
        kdDebug(KIO_SMB) << "checkURL return2 " << url << endl;
        return url;
    }

    // no emtpy path
    KURL url(kurl);

    if (url.path().isEmpty())
        url.setPath("/");

    kdDebug(KIO_SMB) << "checkURL return3 " << url << endl;
    return url;
}

void SMBSlave::reportError(const SMBUrl &url)
{
    kdDebug(KIO_SMB) << "reportError " << url << " " << perror << endl;
    switch(errno)
    {
    case ENOENT:
        if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK)
            error( ERR_SLAVE_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall."));
        else
            error( ERR_DOES_NOT_EXIST, url.prettyURL());
        break;
#ifdef ENOMEDIUM
    case ENOMEDIUM:
        error( ERR_SLAVE_DEFINED,
               i18n( "No media in device for %1" ).arg( url.prettyURL() ) );
        break;
#endif
#ifdef EHOSTDOWN
    case EHOSTDOWN:
#endif
    case ECONNREFUSED:
        error(  ERR_SLAVE_DEFINED,
                i18n( "Could not connect to host for %1" ).arg( url.prettyURL() ) );
        break;
    case ENOTDIR:
        error( ERR_CANNOT_ENTER_DIRECTORY, url.prettyURL());
        break;
    case EFAULT:
    case EINVAL:
        error( ERR_DOES_NOT_EXIST, url.prettyURL());
        break;
    case EPERM:
    case EACCES:
        error( ERR_ACCESS_DENIED, url.prettyURL() );
        break;
    case EIO:
    case ENETUNREACH:
        if ( url.getType() == SMBURLTYPE_ENTIRE_NETWORK || url.getType() == SMBURLTYPE_WORKGROUP_OR_SERVER )
            error( ERR_SLAVE_DEFINED, i18n( "Error while connecting to server responsible for %1" ).arg( url.prettyURL() ) );
        else
            error( ERR_CONNECTION_BROKEN, url.prettyURL());
        break;
    case ENOMEM:
        error( ERR_OUT_OF_MEMORY, url.prettyURL() );
        break;
    case ENODEV:
        error( ERR_SLAVE_DEFINED, i18n("Share could not be found on given server"));
        break;
    case EBADF:
        error( ERR_INTERNAL, i18n("BAD File descriptor"));
        break;
    case ETIMEDOUT:
        error( ERR_SERVER_TIMEOUT, url.host() );
        break;
#ifdef ENOTUNIQ
    case ENOTUNIQ:
        error( ERR_SLAVE_DEFINED, i18n( "The given name could not be resolved to a unique server. "
                                        "Make sure your network is setup without any name conflicts "
                                        "between names used by Windows and by UNIX name resolution." ) );
        break;
#endif
    case 0: // success
	  error( ERR_INTERNAL, i18n("libsmbclient reported an error, but did not specify "
								"what the problem is. This might indicate a severe problem "
								"with your network - but also might indicate a problem with "
								"libsmbclient.\n"
								"If you want to help us, please provide a tcpdump of the "
								"network interface while you try to browse (be aware that "
								"it might contain private data, so do not post it if you are "
								"unsure about that - you can send it privately to the developers "
								"if they ask for it)") );
	  break;
    default:
        error( ERR_INTERNAL, i18n("Unknown error condition in stat: %1").arg(QString::fromLocal8Bit( strerror(errno))) );
    }
}

//===========================================================================
void SMBSlave::listDir( const KURL& kurl )
{
   kdDebug(KIO_SMB) << "SMBSlave::listDir on " << kurl << endl;

   // check (correct) URL
   KURL url = checkURL(kurl);
   // if URL is not valid we have to redirect to correct URL
   if (url != kurl)
   {
      redirection(url);
      finished();
      return;
   }

   m_current_url = kurl;

   int                 dirfd;
   struct smbc_dirent  *dirp = NULL;
   UDSEntry    udsentry;
   UDSAtom     atom;

   dirfd = smbc_opendir( m_current_url.toSmbcUrl() );
   kdDebug(KIO_SMB) << "SMBSlave::listDir open " << m_current_url.toSmbcUrl() << " " << m_current_url.getType() << " " << dirfd << endl;
   if(dirfd >= 0)
   {
       do {
           kdDebug(KIO_SMB) << "smbc_readdir " << endl;
           dirp = smbc_readdir(dirfd);
           if(dirp == 0)
               break;

           // Set name
           atom.m_uds = KIO::UDS_NAME;
           QString dirpName = QString::fromUtf8( dirp->name );
           // We cannot trust dirp->commentlen has it might be with or without the NUL character
           // See KDE bug #111430 and Samba bug #3030
           QString comment = QString::fromUtf8( dirp->comment );
           if ( dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_WORKGROUP ) {
               atom.m_str = dirpName.lower();
               atom.m_str.at( 0 ) = dirpName.at( 0 ).upper();
               if ( !comment.isEmpty() && dirp->smbc_type == SMBC_SERVER )
                   atom.m_str += " (" + comment + ")";
           } else
               atom.m_str = dirpName;

           kdDebug(KIO_SMB) << "dirp->name " <<  dirp->name  << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type << endl;

           udsentry.append( atom );
           if (atom.m_str.upper()=="IPC$" || atom.m_str=="." || atom.m_str == ".." ||
               atom.m_str.upper() == "ADMIN$" || atom.m_str.lower() == "printer$" || atom.m_str.lower() == "print$" )
           {
//            fprintf(stderr,"----------- hide: -%s-\n",dirp->name);
               // do nothing and hide the hidden shares
           }
           else if(dirp->smbc_type == SMBC_FILE)
           {
               // Set stat information
               m_current_url.addPath(dirpName);
               browse_stat_path(m_current_url, udsentry, true);
               m_current_url.cd("..");

               // Call base class to list entry
               listEntry(udsentry, false);
           }
           else if(dirp->smbc_type == SMBC_DIR)
           {
               m_current_url.addPath(dirpName);
               browse_stat_path(m_current_url, udsentry, true);
               m_current_url.cd("..");

               // Call base class to list entry
               listEntry(udsentry, false);
           }
           else if(dirp->smbc_type == SMBC_SERVER ||
                   dirp->smbc_type == SMBC_FILE_SHARE)
           {
               // Set type
               atom.m_uds = KIO::UDS_FILE_TYPE;
               atom.m_long = S_IFDIR;
               udsentry.append( atom );

               // Set permissions
               atom.m_uds  = KIO::UDS_ACCESS;
               atom.m_long = (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH);
               udsentry.append(atom);

               if (dirp->smbc_type == SMBC_SERVER) {
                   atom.m_uds = KIO::UDS_URL;
                   // QString workgroup = m_current_url.host().upper();
                   KURL u("smb:/");
                   u.setHost(dirpName);
                   atom.m_str = u.url();

                   // when libsmbclient knows
                   // atom.m_str = QString("smb://%1?WORKGROUP=%2").arg(dirpName).arg(workgroup.upper());
                   kdDebug(KIO_SMB) << "list item " << atom.m_str << endl;
                   udsentry.append(atom);

                   atom.m_uds = KIO::UDS_MIME_TYPE;
                   atom.m_str = QString::fromLatin1("application/x-smb-server");
                   udsentry.append(atom);
               }

               // Call base class to list entry
               listEntry(udsentry, false);
            }
           else if(dirp->smbc_type == SMBC_WORKGROUP)
           {
               // Set type
               atom.m_uds = KIO::UDS_FILE_TYPE;
               atom.m_long = S_IFDIR;
               udsentry.append( atom );

               // Set permissions
               atom.m_uds  = KIO::UDS_ACCESS;
               atom.m_long = (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH);
               udsentry.append(atom);

               atom.m_uds = KIO::UDS_MIME_TYPE;
               atom.m_str = QString::fromLatin1("application/x-smb-workgroup");
               udsentry.append(atom);

               atom.m_uds = KIO::UDS_URL;
               // QString workgroup = m_current_url.host().upper();
               KURL u("smb:/");
               u.setHost(dirpName);
               atom.m_str = u.url();
               udsentry.append(atom);

               // Call base class to list entry
               listEntry(udsentry, false);
           }
           else
           {
               kdDebug(KIO_SMB) << "SMBSlave::listDir SMBC_UNKNOWN :" << dirpName << endl;
               // TODO: we don't handle SMBC_IPC_SHARE, SMBC_PRINTER_SHARE
               //       SMBC_LINK, SMBC_COMMS_SHARE
               //SlaveBase::error(ERR_INTERNAL, TEXT_UNSUPPORTED_FILE_TYPE);
               // continue;
           }
           udsentry.clear();
       } while (dirp); // checked already in the head

       // clean up
       smbc_closedir(dirfd);
   }
   else
   {
       if (errno == EPERM || errno == EACCES)
           if (checkPassword(m_current_url)) {
               redirection( m_current_url );
               finished();
               return;
           }

       reportError(m_current_url);
       finished();
       return;
   }

   listEntry(udsentry, true);
   finished();
}

