/*
    suPHP - (c)2002-2008 Sebastian Marsching <sebastian@marsching.com>

    This file is part of suPHP.

    suPHP 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.

    suPHP 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 suPHP; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "PathMatcher.hpp"
#include "Util.hpp"

using namespace suPHP;

suPHP::PathMatcher::PathMatcher(const UserInfo& user, const GroupInfo& group) {
    this->user = user;
    this->group = group;
}

bool suPHP::PathMatcher::matches(std::string pattern, std::string path) 
    throw (KeyNotFoundException, ParsingException) {
    std::string remainingPath = path;
    std::string remainingPattern = pattern;
    
    while (remainingPath.length() > 0 && remainingPattern.length() > 0) {
        bool escapeNext = false;
        for (int i = 0; i < remainingPattern.length(); i++) {
            char c = remainingPattern.at(i);
            if (escapeNext) {
                escapeNext = false;
                if (c == '\\' || c == '*' || c == '$') {
                    // Backslash was used as an escape character
                    if (remainingPath.at(i-1) == c) {
                        remainingPattern = remainingPattern.substr(i + 1);
                        remainingPath = remainingPath.substr(i);
                        break;
                    } else {
                        return false;
                    }
                } else {
                    if (remainingPath.at(i-1) == '\\') {
                        remainingPattern = remainingPattern.substr(i);
                        remainingPath = remainingPath.substr(i);
                        break;
                    } else {
                        return false;
                    }
                }
            } else {
                if (c == '\\') {
                    escapeNext = true;
                } else if (c == '*') {
                    remainingPattern = remainingPattern.substr(i + 1);
                    remainingPath = remainingPath.substr(i);
                    if (matches(remainingPattern, remainingPath)) {
                        return true;
                    }
                    std::string testPrefix;
                    for (int j = 0; j < remainingPath.length(); j++) {
                        char c2 = remainingPath.at(j);
                        if (c2 == '/') {
                            return false;
                        }
                        if (c2 == '\\' || c2 == '*' || c2 == '$') {
                            testPrefix += "\\";
                        }
                        testPrefix += c2;
                        if (matches(testPrefix + remainingPattern, 
                                    remainingPath)) {
                            return true;
                        }
                    }
                } else if (c == '$') {
                    if (remainingPattern.length() < i + 3) {
                        throw ParsingException("Incorrect use of $ in pattern \"" + pattern + "\".", __FILE__, __LINE__);
                    }
                    if (remainingPattern.at(i + 1) != '{') {
                        throw ParsingException("Incorrect use of $ in pattern \"" + pattern + "\".", __FILE__, __LINE__);
                    }
                    std::string::size_type closingBrace = remainingPattern.find('}', i);
                    if (closingBrace == std::string::npos) {
                        throw ParsingException("Incorrect use of $ in pattern \"" + pattern + "\".", __FILE__, __LINE__);
                    }
                    std::string varName = remainingPattern.substr(i + 2, closingBrace - i - 2);
                    remainingPattern = lookupVariable(varName) + remainingPattern.substr(closingBrace + 1);
                    break;
                } else {
                    if (i >= remainingPath.length() || c != remainingPath.at(i)) {
                        return false;
                    }
                    if (i == remainingPattern.length() - 1) {
                        if (c == '/' || (i + 1 < remainingPath.length() && remainingPath.at(i + 1) == '/')) {
                            // Path represents file in subdirectory
                            return true;
                        } else if (remainingPath.length() == remainingPattern.length()) {
                            // Exact match
                            return true;
                        } else {
                            return false;
                        }
                    }
                }
            }
        }
    }
    return false;
}

std::string suPHP::PathMatcher::lookupVariable(std::string str) 
    throw (KeyNotFoundException) {
    std::string rv;
    if (str == "USERNAME") {
        rv = user.getUsername();
    } else if (str == "UID") {
        rv = Util::intToStr(user.getUid());
    } else if (str == "HOME") {
        rv = user.getHomeDirectory();
    } else if (str == "GROUPNAME") {
        rv = group.getGroupname();
    } else if (str == "GID") {
        rv = Util::intToStr(group.getGid());
    } else {
        throw KeyNotFoundException("Key \"" + str + 
            "\" does not represent a valid variable name", __FILE__, __LINE__);
    }
    return rv;
}

std::string suPHP::PathMatcher::resolveVariables(std::string str) throw (KeyNotFoundException, ParsingException) {
    std::string out;
    bool escapeNext = false;
    for (int i = 0; i < str.length(); i++) {
        char c = str.at(i);
        if (escapeNext) {
            escapeNext = false;
            if (c == '\\' || c == '$') {
                // Backslash was used as an escape character
                out += c;
            } else {
                out += '\\';
                out += c;
            }
        } else {
            if (c == '\\') {
                escapeNext = true;
            } else if (c == '$') {
                if (str.length() < i + 3) {
                    throw ParsingException("Incorrect use of $ in string \"" + str + "\".", __FILE__, __LINE__);
                }
                if (str.at(i + 1) != '{') {
                    throw ParsingException("Incorrect use of $ in string \"" + str + "\".", __FILE__, __LINE__);
                }
                std::string::size_type closingBrace = str.find('}', i);
                if (closingBrace == std::string::npos) {
                    throw ParsingException("Incorrect use of $ in string \"" + str + "\".", __FILE__, __LINE__);
                }
                std::string varName = str.substr(i + 2, closingBrace - i - 2);
                out += lookupVariable(varName);
                i = closingBrace + 1;
            } else {
                out += c;
            }
        }
    }
    return out;
}
