/*
    This file is part of the KDE libraries

    Copyright (c) 2003 Oswald Buddenhagen <ossi@kde.org>

    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 Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <kshell.h>

#include <tqfile.h>

#include <stdlib.h>
#include <pwd.h>
#include <sys/types.h>

static int fromHex( TQChar c )
{
    if (c >= (QChar)'0' && c <= (QChar)'9')
        return c - (QChar)'0';
    else if (c >= (QChar)'A' && c <= (QChar)'F')
        return c - (QChar)'A' + 10;
    else if (c >= (QChar)'a' && c <= (QChar)'f')
        return c - (QChar)'a' + 10;
    return -1;
}

inline static bool isQuoteMeta( uint c )
{
#if 0 // it's not worth it, especially after seeing gcc's asm output ...
    static const uchar iqm[] = {
        0x00, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00
    }; // \'"$
    
    return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
#else
    return c == (int)'\\' || c == (int)'\'' || c == (int)'"' || c == (int)'$';
#endif
}

inline static bool isMeta( uint c )
{
    static const uchar iqm[] = {
        0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8,
        0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x38
    }; // \'"$`<>|;&(){}*?#
    
    return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
}

TQStringList KShell::splitArgs( const TQString &args, int flags, int *err )
{
    TQStringList ret;
    bool firstword = flags & AbortOnMeta;

    for (uint pos = 0; ; ) {
        TQChar c;
        do {
            if (pos >= args.length())
                goto okret;
            c = args.unicode()[pos++];
        } while (c.isSpace());
        TQString cret;
        if ((flags & TildeExpand) && c == (QChar)'~') {
            uint opos = pos;
            for (; ; pos++) {
                if (pos >= args.length())
                    break;
                c = args.unicode()[pos];
                if (c == (QChar)'/' || c.isSpace())
                    break;
                if (isQuoteMeta( c )) {
                    pos = opos;
                    c = (QChar)'~';
                    goto notilde;
                }
                if ((flags & AbortOnMeta) && isMeta( c ))
                    goto metaerr;
            }
            TQString ccret = homeDir( TQConstString( args.unicode() + opos, pos - opos ).string() );
            if (ccret.isEmpty()) {
                pos = opos;
                c = (QChar)'~';
                goto notilde;
            }
            if (pos >= args.length()) {
                ret += ccret;
                goto okret;
            }
            pos++;
            if (c.isSpace()) {
                ret += ccret;
                firstword = false;
                continue;
            }
            cret = ccret;
        }
        // before the notilde label, as a tilde does not match anyway
        if (firstword) {
            if (c == (QChar)'_' || (c >= (QChar)'A' && c <= (QChar)'Z') || (c >= (QChar)'a' && c <= (QChar)'z')) {
                uint pos2 = pos;
                TQChar cc;
                do
                  cc = args[pos2++];
                while (cc == (QChar)'_' || (cc >= (QChar)'A' && cc <= (QChar)'Z') ||
                       (cc >= (QChar)'a' && cc <= (QChar)'z') || (cc >= (QChar)'0' && cc <= (QChar)'9'));
                if (cc == (QChar)'=')
                    goto metaerr;
            }
        }
      notilde:
        do {
            if (c == (QChar)'\'') {
                uint spos = pos;
                do {
                    if (pos >= args.length())
                        goto quoteerr;
                    c = args.unicode()[pos++];
                } while (c != (QChar)'\'');
                cret += TQConstString( args.unicode() + spos, pos - spos - 1 ).string();
            } else if (c == (QChar)'"') {
                for (;;) {
                    if (pos >= args.length())
                        goto quoteerr;
                    c = args.unicode()[pos++];
                    if (c == (QChar)'"')
                        break;
                    if (c == (QChar)'\\') {
                        if (pos >= args.length())
                            goto quoteerr;
                        c = args.unicode()[pos++];
                        if (c != (QChar)'"' && c != (QChar)'\\' &&
                            !((flags & AbortOnMeta) && (c == (QChar)'$' || c == (QChar)'`')))
                            cret += (QChar)'\\';
                    } else if ((flags & AbortOnMeta) && (c == (QChar)'$' || c == (QChar)'`'))
                        goto metaerr;
                    cret += c;
                }
            } else if (c == (QChar)'$' && args[pos] == (QChar)'\'') {
                pos++;
                for (;;) {
                    if (pos >= args.length())
                        goto quoteerr;
                    c = args.unicode()[pos++];
                    if (c == (QChar)'\'')
                        break;
                    if (c == (QChar)'\\') {
                        if (pos >= args.length())
                            goto quoteerr;
                        c = args.unicode()[pos++];
                        switch (c) {
                        case 'a': cret += (QChar)'\a'; break;
                        case 'b': cret += (QChar)'\b'; break;
                        case 'e': cret += (QChar)'\033'; break;
                        case 'f': cret += (QChar)'\f'; break;
                        case 'n': cret += (QChar)'\n'; break;
                        case 'r': cret += (QChar)'\r'; break;
                        case 't': cret += (QChar)'\t'; break;
                        case '\\': cret += (QChar)'\\'; break;
                        case '\'': cret += (QChar)'\''; break;
                        case 'c': cret += args[pos++] & 31; break;
                        case 'x':
                          {
                            int hv = fromHex( args[pos] );
                            if (hv < 0) {
                                cret += "\\x";
                            } else {
                                int hhv = fromHex( args[++pos] );
                                if (hhv > 0) {
                                    hv = hv * 16 + hhv;
                                    pos++;
                                }
                                cret += TQChar( hv );
                            }
                            break;
                          }
                        default:
                            if (c >= (QChar)'0' && c <= (QChar)'7') {
                                int hv = c - '0';
                                for (int i = 0; i < 2; i++) {
                                    c = args[pos];
                                    if (c < (QChar)'0' || c > (QChar)'7')
                                        break;
                                    hv = hv * 8 + (c - '0');
                                    pos++;
                                }
                                cret += TQChar( hv );
                            } else {
                                cret += '\\';
                                cret += c;
                            }
                            break;
                        }
                    } else
                        cret += c;
                }
            } else {
                if (c == (QChar)'\\') {
                    if (pos >= args.length())
                        goto quoteerr;
                    c = args.unicode()[pos++];
                    if (!c.isSpace() &&
                        !((flags & AbortOnMeta) ? isMeta( c ) : isQuoteMeta( c )))
                        cret += '\\';
                } else if ((flags & AbortOnMeta) && isMeta( c ))
                    goto metaerr;
                cret += c;
            }
            if (pos >= args.length())
                break;
            c = args.unicode()[pos++];
        } while (!c.isSpace());
        ret += cret;
        firstword = false;
    }

  okret:
    if (err)
        *err = NoError;
    return ret;

  quoteerr:
   if (err)
       *err = BadQuoting;
   return TQStringList();

  metaerr:
   if (err)
       *err = FoundMeta;
   return TQStringList();
}

inline static bool isSpecial( uint c )
{
    static const uchar iqm[] = {
        0xff, 0xff, 0xff, 0xff, 0xdd, 0x07, 0x00, 0xd8,
        0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x38
    }; // 0-32 \'"$`<>|;&(){}*?#
    
    return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
}

TQString KShell::joinArgs( const TQStringList &args )
{
    TQChar q( '\'' );
    TQString ret;
    for (TQStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
        if (!ret.isEmpty())
            ret += ' ';
        if (!(*it).length())
            ret.append( q ).append( q );
        else {
            for (uint i = 0; i < (*it).length(); i++)
                if (isSpecial((*it).unicode()[i])) {
                    TQString tmp(*it);
                    tmp.replace( q, "'\\''" );
                    ret += q;
                    tmp += q;
                    ret += tmp;
                    goto ex;
                }
            ret += *it;
          ex: ;
        }
    }
    return ret;
}

TQString KShell::joinArgs( const char * const *args, int nargs )
{
    if (!args)
        return TQString::null; // well, TQString::empty, in fact. qt sucks ;)
    TQChar q( '\'' );
    TQString ret;
    for (const char * const *argp = args; nargs && *argp; argp++, nargs--) {
        if (!ret.isEmpty())
            ret += ' ';
        if (!**argp)
            ret.append( q ).append( q );
        else {
            TQString tmp( TQFile::decodeName( *argp ) );
            for (uint i = 0; i < tmp.length(); i++)
                if (isSpecial(tmp.unicode()[i])) {
                    tmp.replace( q, "'\\''" );
                    ret += q;
                    tmp += q;
                    ret += tmp;
                    goto ex;
                }
            ret += tmp;
          ex: ;
       }
    }
    return ret;
}

TQString KShell::joinArgsDQ( const TQStringList &args )
{
    TQChar q( '\'' ), sp( ' ' ), bs( '\\' );
    TQString ret;
    for (TQStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
        if (!ret.isEmpty())
            ret += sp;
        if (!(*it).length())
            ret.append( q ).append( q );
        else {
            for (uint i = 0; i < (*it).length(); i++)
                if (isSpecial((*it).unicode()[i])) {
                    ret.append( '$' ).append( q );
                    for (uint pos = 0; pos < (*it).length(); pos++) {
                        int c = (*it).unicode()[pos];
                        if (c < 32) {
                            ret += bs;
                            switch (c) {
                            case '\a': ret += 'a'; break;
                            case '\b': ret += 'b'; break;
                            case '\033': ret += 'e'; break;
                            case '\f': ret += 'f'; break;
                            case '\n': ret += 'n'; break;
                            case '\r': ret += 'r'; break;
                            case '\t': ret += 't'; break;
                            case '\034': ret += 'c'; ret += '|'; break;
                            default: ret += 'c'; ret += c + '@'; break;
                            }
                        } else {
                            if (c == '\'' || c == '\\')
                                ret += bs;
                            ret += c;
                        }
                    }
                    ret.append( q );
                    goto ex;
                }
            ret += *it;
          ex: ;
        }
    }
    return ret;
}

TQString KShell::tildeExpand( const TQString &fname )
{
    if (fname[0] == (QChar)'~') {
        int pos = fname.find( '/' );
        if (pos < 0)
            return homeDir( TQConstString( fname.unicode() + 1, fname.length() - 1 ).string() );
        TQString ret = homeDir( TQConstString( fname.unicode() + 1, pos - 1 ).string() );
        if (!ret.isNull())
            ret += TQConstString( fname.unicode() + pos, fname.length() - pos ).string();
        return ret;
    }
    return fname;
}

TQString KShell::homeDir( const TQString &user )
{
    if (user.isEmpty())
        return TQFile::decodeName( getenv( "HOME" ) );
    struct passwd *pw = getpwnam( TQFile::encodeName( user ).data() );
    if (!pw)
        return TQString::null;
    return TQFile::decodeName( pw->pw_dir );
}