From 6a93d84a2a683d6a1e1c7eab903f4f12ddcf8f67 Mon Sep 17 00:00:00 2001 From: Thomas Jarosch Date: Fri, 4 Apr 2008 15:46:45 +0000 Subject: [PATCH] libi2ncommon: (tomj) merged common code from connd --- src/Makefile.am | 4 +- src/daemonfunc.cpp | 105 +++++++--- src/daemonfunc.hxx | 18 +- src/filefunc.cpp | 574 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/filefunc.hxx | 153 ++++++++++++++- src/stringfunc.cpp | 527 +++++++++++++++++++++++++++++++++++++++++++++++- src/stringfunc.hxx | 179 ++++++++++++++++- 7 files changed, 1459 insertions(+), 101 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 421cca4..1ddd7c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,8 +4,8 @@ INCLUDES = -I$(top_srcdir)/src @LIBGETTEXT_CFLAGS@ @LIBICONV_CFLAGS@ $(all_inclu # the library search path. lib_LTLIBRARIES = libi2ncommon.la -include_HEADERS = daemonfunc.hxx pidfile.hxx logread.hxx insocketstream.hxx oftmpstream.hxx pipestream.hxx filefunc.hxx stringfunc.hxx timefunc.hxx ipfunc.hxx ip_type.hxx -libi2ncommon_la_SOURCES = daemonfunc.cpp pidfile.cpp logread.cpp oftmpstream.cpp ipfunc.cpp timefunc.cpp filefunc.cpp stringfunc.cpp +include_HEADERS = userfunc.hxx daemonfunc.hxx pidfile.hxx logread.hxx insocketstream.hxx oftmpstream.hxx pipestream.hxx filefunc.hxx stringfunc.hxx timefunc.hxx ipfunc.hxx ip_type.hxx +libi2ncommon_la_SOURCES = userfunc.cpp daemonfunc.cpp pidfile.cpp logread.cpp oftmpstream.cpp ipfunc.cpp timefunc.cpp filefunc.cpp stringfunc.cpp # Note: If you specify a:b:c as the version in the next line, # the library that is made has version (a-c).c.b. In this diff --git a/src/daemonfunc.cpp b/src/daemonfunc.cpp index b1040b2..274c6a5 100644 --- a/src/daemonfunc.cpp +++ b/src/daemonfunc.cpp @@ -11,13 +11,20 @@ #include #include #include "daemonfunc.hxx" +#include "stringfunc.hxx" +#include "filefunc.hxx" + +namespace i2n +{ +namespace daemon +{ using namespace std; /** * Fork into the background. */ -void daemon::daemonize() +void daemonize() { int pid=fork(); @@ -34,43 +41,14 @@ void daemon::daemonize() } /** - * Lookup uid for given username - * @param username username to convert - * @return uid of the user - */ -uid_t daemon::lookup_uid(const std::string &username) -{ - struct passwd *user = getpwnam(username.c_str()); - if (user == NULL) { - throw runtime_error("user " + username + " not found"); - } - - return user->pw_uid; -} - -/** - * Lookup gid for given group - * @param group group to convert - * @return gid of the group - */ -gid_t daemon::lookup_gid(const std::string &group) -{ - struct group *my_group = getgrnam(group.c_str()); - if (my_group == NULL) { - throw runtime_error("group " + group + " not found"); - } - - return my_group->gr_gid; -} - -/** * Drop root privileges * @param username User to become. Don't change user if empty * @param group Group to become. Don't change group if empty */ -void daemon::drop_root_privileges(const std::string &username, +void drop_root_privileges(const std::string &username, const std::string &group) { +/* if (!group.empty()) { if (setgid(daemon::lookup_gid(group))) { throw runtime_error("Can't change to group " + group); @@ -82,4 +60,67 @@ void daemon::drop_root_privileges(const std::string &username, throw runtime_error("Can't change to user " + username); } } +*/ +} + +/** + * @brief determine the pids for a given program + * @param[in] name name (or full path) of the binary + * @param[out] result the pids associated with the name. + * @return @a true if the function performed without errors. + * + * Walk though the /proc/\'s and search for the name. + * + * @note Since this function uses /proc, it's system specific. Currently: + * Linux only! + * + * @todo check cmdline and stat in /proc/\ dir for the searched name. + */ +bool pid_of(const std::string& name, std::vector< pid_t >& result) +{ + std::vector< std::string > entries; + std::vector< pid_t > fuzz1_result; + std::vector< pid_t > fuzz2_result; + result.clear(); + if (!i2n::getDir("/proc", entries)) return false; + for(std::vector< std::string >::const_iterator it= entries.begin(); + it != entries.end(); + ++it) + { + pid_t pid; + if (! stringTo(*it, pid)) continue; + std::string base_path= std::string("/proc/") + *it; + std::string exe_path= base_path + "/exe"; + Stat stat(exe_path, false); + if (not stat or not stat.isLink()) continue; + std::string real_exe= readLink(exe_path); + if (real_exe == name) + { + result.push_back( pid ); + continue; + } + + std::string proc_stat= readFile( base_path + "/stat"); + if (proc_stat.empty()) continue; // process vanished + + //TODO some more fuzz tests here?! (cmdline, stat(us)) + + if (basename(real_exe) == name) + { + fuzz2_result.push_back(pid); + continue; + } + } + if (result.empty()) + { + result.swap(fuzz1_result); + } + if (result.empty()) + { + result.swap(fuzz2_result); + } + return true; +} // eo pidOf(const std::string&,std::vector< pid_t >&) + +} } diff --git a/src/daemonfunc.hxx b/src/daemonfunc.hxx index ba26433..a0e8011 100644 --- a/src/daemonfunc.hxx +++ b/src/daemonfunc.hxx @@ -8,18 +8,20 @@ #include #include +#include /// Collection of functions for daemons -class daemon +namespace i2n { -public: - static void daemonize(); +namespace daemon +{ + void daemonize(); - static uid_t lookup_uid(const std::string &username); - static gid_t lookup_gid(const std::string &group); + void drop_root_privileges(const std::string &username, + const std::string &group); - static void drop_root_privileges(const std::string &username, - const std::string &group); -}; + bool pid_of(const std::string& name, std::vector< pid_t >& result); +} +} #endif diff --git a/src/filefunc.cpp b/src/filefunc.cpp index f65db18..47e9838 100644 --- a/src/filefunc.cpp +++ b/src/filefunc.cpp @@ -6,7 +6,9 @@ email : info@intra2net.com ***************************************************************************/ +#include #include +#include #include #include #include @@ -19,21 +21,187 @@ #include #include -#include +#include +#include "filefunc.hxx" +#include "stringfunc.hxx" + +namespace i2n +{ using namespace std; -bool file_exists (const string &name) +/* +** implementation of Stat +*/ + +Stat::Stat() +: m_valid(false) { - struct stat statbuff; + clear(); +} // eo Stat::Stat() + + +Stat::Stat(const std::string& path, bool follow_links) +{ + stat(path,follow_links); +} // eo Stat::Stat(const std::string&,bool) + + +Stat::~Stat() +{ +} // eo Stat::~Stat() + + +/** + * @brief updates the internal data. + * + * In other words: stat()'s the file again. + */ +void Stat::recheck() +{ + if (! m_path.empty()) + { + stat(m_path, m_follow_links); + } +} // eo Stat::recheck() + + +/** + * @brief calls stat() or lstat() to get the information for the given path + * and stores that information. + * @param path the path which should be checked + * @param follow_links determine if (symbalic) links should be followed. + */ +void Stat::stat(const std::string& path, bool follow_links) +{ + clear(); + m_path= path; + m_follow_links= follow_links; + struct stat stat_info; + int res; + res = ( follow_links ? ::stat(path.c_str(), &stat_info) : ::lstat(path.c_str(), &stat_info) ); + if ( 0 == res ) + { + m_device = stat_info.st_dev; + m_inode = stat_info.st_ino; + m_mode = (stat_info.st_mode & ~(S_IFMT)); + m_num_links = stat_info.st_nlink; + m_uid = stat_info.st_uid; + m_gid = stat_info.st_gid; + m_device_type = stat_info.st_rdev; + m_size = stat_info.st_size; + m_atime = stat_info.st_atime; + m_mtime = stat_info.st_mtime; + m_ctime = stat_info.st_atime; + + m_is_link= S_ISLNK( stat_info.st_mode ); + m_is_regular= S_ISREG( stat_info.st_mode ); + m_is_directory= S_ISDIR( stat_info.st_mode ); + m_is_character_device= S_ISCHR( stat_info.st_mode ); + m_is_block_device= S_ISBLK( stat_info.st_mode ); + m_is_fifo= S_ISFIFO( stat_info.st_mode ); + m_is_socket= S_ISSOCK( stat_info.st_mode ); + } + m_valid = (0 == res); +} // eo Stat::stat(const std::string&,bool) - if (stat(name.c_str(), &statbuff) < 0) - return false; - else - return true; -} -long fsize (const string &name) +/** + * @brief clears the internal data. + */ +void Stat::clear() +{ + m_path.clear(); + m_valid= false; + + m_device = 0; + m_inode = 0; + m_mode = 0; + m_num_links = 0; + m_uid = 0; + m_gid = 0; + m_device_type = 0; + m_size = 0; + m_atime = 0; + m_mtime = 0; + m_ctime = 0; + + m_is_link= false; + m_is_regular= false; + m_is_directory= false; + m_is_character_device= false; + m_is_block_device= false; + m_is_fifo= false; + m_is_socket= false; +} // eo Stat::clear() + + +/** + * @brief checks if another instance describes the same file. + * @param rhs the other instance. + * @return @a true iff the other instance describes the same file. + * @note + * The "same file" means that the files are located on the same device and use the same inode. + * They might still have two different directory entries (different paths)! + */ +bool Stat::isSameAs(const Stat& rhs) +{ + return m_valid and rhs.m_valid + and ( m_device == rhs.m_device) + and ( m_inode == rhs.m_inode); +} // eo Stat::isSameAs(const Stat& rhs); + + +/** + * @brief checks if this and the other instance describe the same device. + * @param rhs the other instance. + * @return @a true if we and the other instance describe a device and the same device. + * + * "Same device" means that the devices have the same type and the same major and minor id. + */ +bool Stat::isSameDeviceAs(const Stat& rhs) +{ + return isDevice() and rhs.isDevice() + and ( m_is_block_device == rhs.m_is_block_device ) + and ( m_is_character_device == rhs.m_is_character_device ) + and ( m_device_type == rhs.m_device_type); +} // eo Stat::isSameDeviceAs(const Stat&) + +/** + * @brief check existence of a path. + * @param path path which should be tested. + * @return @a true iff path exists. + */ +bool path_exists(const std::string& path) +{ + struct stat stat_info; + int res = ::stat(path.c_str(), &stat_info); + if (res) return false; + return true; +} // eo path_exists(const std::string&) + +/** + * @brief check existence of a regular file. + * @param path path which should be tested. + * @return @a true if path exists and is a regular file (or a link pointing to a regular file). + * @note this checks for regular files; not for the pure exitsnace of a path; use pathExists() for that. + * @see pathExists + */ +bool file_exists(const std::string& path) +{ + struct stat stat_info; + int res = ::stat(path.c_str(), &stat_info); + if (res) return false; + return S_ISREG(stat_info.st_mode); +} // eo file_exists(const std::string&) + +// TODO: Use Stat class +/** + * Get size of file + * @param name filename to get size for + * @return file size or -1 if file does not exist + */ +long file_size (const string &name) { long iReturn = -1; @@ -50,54 +218,374 @@ long fsize (const string &name) return iReturn; } -string load_file(const string &name) +/** + * @brief tests the last modification time stamp of a path. + * @param path path which should be tested. + * @return the last modification time or 0 if the path doen't exist. + */ +time_t fileMTime(const std::string& path) { - FILE *f; - string s; + struct stat stat_info; + int res = ::stat(path.c_str(), &stat_info); + if (res) return 0; + return stat_info.st_mtime; +} // eo fileMTime(const std::string&) - if (name.find ("..") != string::npos) - return ("can't load file (..): " + name); - f=::fopen(name.c_str(),"rb"); - if (!f) +/** + * @brief reads the contents of a directory. + * @param path the path to the directory whose contents should be read. + * @param[out] result the resulting list of names. + * @param include_dot_names determines if dot-files should be included in the list. + * @return @a true if reading the directory was succesful, @a false on error. + */ +bool getDir( + const std::string& path, + std::vector< std::string >& result, + bool include_dot_names ) +{ + DIR* dir = ::opendir( path.c_str()); + if (!dir) + { + return false; + } + struct dirent *entry; + while ( NULL != (entry = ::readdir(dir)) ) { - return (""); + std::string name( entry->d_name ); + if (! include_dot_names && (name[0] == '.') ) + { + continue; + } + result.push_back( name ); } + ::closedir(dir); + return true; +} // eo getDir(const std::string&,std::vector< std::string >&,bool) + + +/** + * @brief reads the contents of a directory + * @param path the path to the directory whose contents should be read. + * @param include_dot_names determines if dot-files should be included in the list. + * @return the list of names (empty on error). + */ +std::vector< std::string > getDir(const std::string& path, bool include_dot_names ) +{ + std::vector< std::string > result; + getDir(path,result,include_dot_names); + return result; +} // eo getDir(const std::string&,bool) - ::fseek(f,0,SEEK_END); - int size=::ftell(f); - ::fseek(f,0,SEEK_SET); - char *c=new char[size+1]; - ::fread(c,1,size,f); - s.assign(c,size); - delete[] c; - ::fclose (f); - return s; -} -bool chown(const char* file,const char* owner, const char* group) +/** + * @brief removes a file from a filesystem. + * @param path path to the file. + * @return @a true iff the unlink was successful. + */ +bool unlink( const std::string& path ) { - struct passwd *p; - struct group *g; - uid_t uid; - gid_t gid; + int res = ::unlink( path.c_str() ); + return (res == 0); +} // eo unlink(const std::string&) + - p = getpwnam(owner); - if (p == NULL) - return false; - uid=p->pw_uid; - g = getgrnam(group); - if (g == NULL) +/** + * @brief creates a symbolic link named @a link_name to @a target. + * @param target the target the link should point to. + * @param link_name the name of the link. + * @param force if @a true, the (file or link) @a link_name is removed if it exists. + * @return @a true iff the symlink was successfully created. + */ +bool symlink(const std::string& target, const std::string& link_name, bool force) +{ + int res= -1; + if (target.empty() or link_name.empty() or target == link_name) + { + // no, we don't do this! return false; - gid=g->gr_gid; + } + std::string n_target; + if (target[0] == '/') // absolute target? + { + n_target= target; + } + else // relative target + { + // for stat'ing: prepend dir of link_name: + n_target= dirname(link_name)+"/"+ target; + } + Stat target_stat(n_target, false); + Stat link_name_stat(link_name, false); + if (target_stat.exists() && link_name_stat.exists()) + { + if (link_name_stat.isSameAs(target_stat) + or link_name_stat.isSameDeviceAs(target_stat) ) + { + return false; + } + //TODO: more consistency checks?! + } + if (link_name_stat.exists()) + { + // the link name already exists. + if (force) + { + // "force" as given, so try to remove the link_name: + unlink(link_name); + // update the stat: + link_name_stat.recheck(); + } + } + if (link_name_stat.exists()) + { + // well, if the link_name still exists; we cannot create that link: + errno = EEXIST; + return false; + } + res= ::symlink(target.c_str(), link_name.c_str()); + return (0 == res); +} // eo symlink(const std::string&,const std::string&,bool) + - if (!::chown(file,uid,gid)) - return true; + +/** + * @brief reads the target of a symbolic link. + * @param path path to the symbolic link + * @return the target of the link or an empty string on error. + */ +std::string readLink(const std::string& path) +{ + errno= 0; + Stat stat(path,false); + if (!stat || !stat.isLink()) + { + return std::string(); + } + int buffer_size= PATH_MAX+1 + 128; + boost::scoped_array buffer_ptr( new char[buffer_size] ); + int res= ::readlink( path.c_str(), buffer_ptr.get(), buffer_size-1 ); + if (res >= 0) + { + return std::string( buffer_ptr.get(), res ); + } + return std::string(); +} // eo readLink(const std::string&) + + + +/** + * @brief returns content of a file as string. + * + * A simple (q'n'd) function for retrieving content of a file as string.
+ * Also able to read special (but regular) files which don't provide a size when stat'ed + * (like files in the /proc filesystem). + * + * @param path path to the file. + * @return the content of the file as string (empty if file could be opened). + */ +std::string readFile(const std::string& path) +{ + Stat stat(path); + if (!stat.isReg()) + { + return std::string(); + } + std::ifstream f( path.c_str(), std::ios::in | std::ios::binary ); + std::string result; + if (f.is_open()) + { + // NOTE: there are cases where we don't know the correct size (/proc files...) + // therefore we only use the size for reserving space if we know it, but don't + // use it when reading the file! + if (stat.size() > 0) + { + // if we know the size, we reserve enough space. + result.reserve( stat.size() ); + } + char buffer[2048]; + while (f.good()) + { + f.read(buffer, sizeof(buffer)); + result.append(buffer, f.gcount()); + } + } + return result; +} // eo readFile + + +/** + * @brief writes a string to a file. + * @param path path to the file + * @param data the data which should be written into the file + * @return @a true if the data was written to the file. + * + * A simple (q'n'd) function for writing a string to a file. + */ +bool writeFile(const std::string& path, const std::string& data) +{ + std::ofstream f( path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); + if (f.good()) + { + f.write( data.data(), data.size() ); + return f.good(); + } else + { return false; -} + } +} // eo writeFile + + +/** + * @brief returns the filename part of a path (last component) + * @param path the path. + * @return the last component of the path. + */ +std::string basename(const std::string& path) +{ + std::string::size_type pos= path.rfind('/'); + if (pos != std::string::npos) + { + return path.substr(pos+1); + } + return path; +} // eo basename(const std::string&) + + +/** + * @brief returns the directory part of a path. + * @param path the path. + * @return the directory part of the path. + */ +std::string dirname(const std::string& path) +{ + std::string::size_type pos= path.rfind('/'); + if (pos != std::string::npos) + { + std::string result(path,0,pos); + if (result.empty()) + { + return "."; + } + return result; + } + return "."; +} // eo dirname(const std::string&) + + +/** + * @brief normalizes a path. + * + * This method removes empty and "." elements. + * It also resolves ".." parts by removing previous path elements if possible. + * Leading ".." elements are preserved when a relative path was given; else they are removed. + * Trailing slashes are removed. + * + * @param path the path which should be normalized. + * @return the normalized path. + */ + +std::string normalizePath(const std::string& path) +{ + if (path.empty()) + { + return std::string(); + } + // remember if the given path was absolute since this information vanishes when + // we split the path (since we split with omitting empty parts...) + bool is_absolute= (path[0]=='/'); + std::list< std::string > parts; + std::list< std::string > result_parts; + + splitString(path,parts,"/",true); + + for(std::list< std::string >::const_iterator it_parts= parts.begin(); + it_parts != parts.end(); + ++it_parts) + { + std::string part(*it_parts); //convenience.. + if (part == std::string(".") ) + { + // single dot is "current path"; ignore! + continue; + } + if (part == std::string("..") ) + { + // double dot is "one part back" + if (result_parts.empty()) + { + if (is_absolute) + { + // ignore since we cannot move behind / on absolute paths... + } + else + { + // on relative path, we need to store the "..": + result_parts.push_back(part); + } + } + else if (result_parts.back() == std::string("..") ) + { + // if last element was already "..", we need to store the new one again... + // (PS: no need for "absolute" check; this can only be the case on relative path) + result_parts.push_back(part); + } + else + { + // remove last element. + result_parts.pop_back(); + } + continue; + } + result_parts.push_back(part); + } + std::string result; + if (is_absolute) + { + result= "/"; + } + result+= joinString(result_parts,"/"); + return result; +} // eo normalizePath(const std::string&) + + +/** + * @brief changes the file(/path) mode. + * @param path the path to change the mode for. + * @param mode the new file mode. + * @return @a true iff the file mode was sucessfully changed. + */ +bool chmod(const std::string& path, int mode) +{ + int res= ::chmod(path.c_str(), mode); + return (res==0); +} // eo chmod(const std::string&,int) + + +/** + * @brief changed the owner of a file(/path) + * @param path the path to change the owner for. + * @param user the new file owner. + * @param group the new file group. + * @return @a true iff the file owner was succesfully changed. + * + * @note + * the validity of user and group within the system is not checked. + * This is intentional since this way we can use id's which are not assigned. + */ +bool chown(const std::string& path, const i2n::User& user, const i2n::Group& group) +{ + uid_t uid= user.uid; + if (uid<0) return false; + gid_t gid= group.gid; + if (gid<0) gid= user.gid; + if (gid<0) return false; + int res= ::chown( path.c_str(), uid, gid); + return (res==0); +} // eo chown(const std::string&,const User&,const Group&) /** * Recursive delete of files and directories @@ -162,3 +650,5 @@ bool recursive_delete(const std::string &path, std::string *error) return rtn; } + +} diff --git a/src/filefunc.hxx b/src/filefunc.hxx index 14c9ddb..0b62cbb 100644 --- a/src/filefunc.hxx +++ b/src/filefunc.hxx @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2004 by Gerd v. Egidy * + * Copyright (C) 2004-2008 by Intra2net AG * * info@intra2net.com * * * ***************************************************************************/ @@ -7,14 +7,157 @@ #ifndef __FILEFUNC_HXX #define __FILEFUNC_HXX -bool file_exists (const std::string &name); +#include "userfunc.hxx" -long fsize (const std::string &name); +namespace i2n +{ -std::string load_file (const std::string &name); +/** + * @brief helper class representing a file state. + * + * This basically wraps struct stat and provides some nicer access to the values. + */ +class Stat +{ + public: + Stat(); + Stat(const std::string& path, bool follow_links= true); + ~Stat(); -bool chown(const char* file,const char* owner, const char* group); + void recheck(); + + bool isValid() const { return m_valid; } + + bool exists() const { return m_valid; } + std::string path() const { return m_path; } + + dev_t device() const { return m_device; } + ino_t inode() const { return m_inode; } + mode_t mode() const { return m_mode; } + nlink_t nlink() const { return m_num_links; } + uid_t uid() const { return m_uid; } + gid_t gid() const { return m_gid; } + dev_t rdev() const { return m_device_type; } + off_t size() const { return m_size; } + time_t atime() const { return m_atime; } + time_t mtime() const { return m_mtime; } + time_t ctime() const { return m_ctime; } + + nlink_t numHardLinks() const { return m_num_links; } + dev_t deviceType() const { return m_device_type; } + time_t lastModifiedTime() const { return m_mtime; } + time_t createdTime() const { return m_ctime; } + + /* + ** unix style like type queries: + */ + + bool isLnk() const { return m_is_link; } + bool isReg() const { return m_is_regular; } + bool isDir() const { return m_is_directory; } + bool isChr() const { return m_is_character_device; } + bool isBlk() const { return m_is_block_device; } + bool isFifo() const { return m_is_fifo; } + bool isSock() const { return m_is_socket; } + + /* + ** readable style type queries: + */ + + bool isLink () const { return m_is_link; } + bool isRegular() const { return m_is_regular; } + bool isRegularFile() const { return m_is_regular; } + bool isDirectory() const { return m_is_directory; } + bool isCharacterDevice() const {return m_is_character_device; } + bool isBlockDevice() const { return m_is_block_device; } + bool isFIFO() const { return m_is_fifo; } + bool isSocket() const { return m_is_socket; } + + bool isDevice() const { return m_is_character_device or m_is_block_device; } + + + /* + ** "high level" queries + */ + + bool isSameAs(const Stat& rhs); + bool isSameDeviceAs(const Stat& rhs); + + /* + ** convenience methods + */ + + operator bool() const { return m_valid; } + + protected: + + void stat(const std::string& path, bool follow_links= true); + void clear(); + + protected: + std::string m_path; + bool m_follow_links; + + bool m_valid; + + dev_t m_device; + ino_t m_inode; + mode_t m_mode; + nlink_t m_num_links; + uid_t m_uid; + gid_t m_gid; + dev_t m_device_type; + off_t m_size; + time_t m_atime; + time_t m_mtime; + time_t m_ctime; + + bool m_is_link : 1; + bool m_is_regular : 1; + bool m_is_directory : 1; + bool m_is_character_device : 1; + bool m_is_block_device : 1; + bool m_is_fifo : 1; + bool m_is_socket : 1; +}; // eo class Stat + +/* +** misc tool functions +** (basically wrappers around system functions) +*/ + +bool path_exists(const std::string& path); +bool file_exists(const std::string& path); +long file_size (const std::string &name); + +time_t fileMTime(const std::string& path); + +bool getDir(const std::string& path, std::vector< std::string >& result, bool include_dot_names= false ); +std::vector< std::string > getDir(const std::string& path, bool include_dot_names= false ); + +bool unlink(const std::string& path); + +bool symlink(const std::string& target, const std::string& link_name, bool force= false); + +std::string readLink(const std::string& path); + + +std::string readFile(const std::string& path); + +bool writeFile(const std::string& path, const std::string& data); + + +std::string basename(const std::string& path); + +std::string dirname(const std::string& path); + +std::string normalizePath(const std::string& path); + +bool chmod(const std::string& path, int mode); +bool chown(const std::string& path, const i2n::User& user, const i2n::Group& group= i2n::Group()); bool recursive_delete(const std::string &path, std::string *error=NULL); +} + #endif diff --git a/src/stringfunc.cpp b/src/stringfunc.cpp index 8c67b97..7bf16f9 100644 --- a/src/stringfunc.cpp +++ b/src/stringfunc.cpp @@ -1,10 +1,9 @@ -/*************************************************************************** - escape.cpp - escaping of strings - ------------------- - begin : Sun Nov 14 1999 - copyright : (C) 1999 by Intra2net AG - email : info@intra2net.com - ***************************************************************************/ +/** @file + * + * (c) Copyright 2007-2008 by Intra2net AG + * + * info@intra2net.com + */ #include #include @@ -20,6 +19,520 @@ using namespace std; +namespace i2n { + + +namespace { + +const std::string hexDigitsLower("0123456789abcdef"); +const std::string hexDigitsUpper("0123456789ABCDEF"); + + +struct UpperFunc +{ + char operator() (char c) + { + return std::toupper(c); + } +}; // eo struct UpperFunc + + +struct LowerFunc +{ + char operator() (char c) + { + return std::tolower(c); + } +}; // eo struct LowerFunc + + +} // eo namespace + + + +/** + * default list of whitespaces (" \t\r\n"); + */ +const std::string whitespaces = " \t\r\n"; + +/** + * default list of lineendings ("\r\n"); + */ +const std::string lineends= "\r\n"; + + + +/** + * @brief checks if a string begins with a given prefix. + * @param[in,out] str the string which is tested + * @param prefix the prefix which should be tested for. + * @return @a true iff the prefix is not empty and the string begins with that prefix. + */ +bool hasPrefix(const std::string& str, const std::string& prefix) +{ + if (prefix.empty() || str.empty() || str.size() < prefix.size()) + { + return false; + } + return str.compare(0, prefix.size(), prefix) == 0; +} // eo hasPrefix(const std::string&,const std::string&) + + +/** + * @brief checks if a string ends with a given suffix. + * @param[in,out] str the string which is tested + * @param suffix the suffix which should be tested for. + * @return @a true iff the suffix is not empty and the string ends with that suffix. + */ +bool hasSuffix(const std::string& str, const std::string& suffix) +{ + if (suffix.empty() || str.empty() || str.size() < suffix.size()) + { + return false; + } + return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} // eo hasSuffix(const std::string&,const std::string&) + + +/** + * cut off characters from a given list from front and end of a string. + * @param[in,out] str the string which should be trimmed. + * @param charlist the list of characters to remove from beginning and end of string + * @return the result string. + */ +std::string trimMod(std::string& str, const std::string& charlist) +{ + // first: trim the beginning: + std::string::size_type pos= str.find_first_not_of(charlist); + if (pos == std::string::npos) + { + // whole string consists of charlist (or is already empty) + str.clear(); + return str; + } + else if (pos>0) + { + // str starts with charlist + str.erase(0,pos); + } + // now let's look at the tail: + pos= str.find_last_not_of(charlist)+1; // note: we already know there is at least one other char! + if ( pos < str.size() ) + { + str.erase(pos, str.size()-pos); + } + return str; +} // eo trimMod(std::string&,const std::string&) + + + +/** + * removes last character from a string when it is in a list of chars to be removed. + * @param[in,out] str the string. + * @param what the list of chars which will be tested for. + * @return the resulting string with last char removed (if applicable) + */ +std::string chompMod(std::string& str, const std::string& what) +{ + if (str.empty() || what.empty()) + { + return str; + } + if (what.find( str.at(str.size()-1) ) != std::string::npos) + { + str.erase( str.size() - 1); + } + return str; +} // eo chompMod(std::string&,const std::string&) + + +/** + * @brief converts a string to lower case. + * @param[in,out] str the string to modify. + * @return the string + */ +std::string lowerMod(std::string& str) +{ + std::transform( str.begin(), str.end(), str.begin(), LowerFunc() ); + return str; +} // eo lowerMod(std::string&) + + +/** + * @brief converts a string to upper case. + * @param[in,out] str the string to modify. + * @return the string + */ +std::string upperMod(std::string& str) +{ + std::transform( str.begin(), str.end(), str.begin(), UpperFunc() ); + return str; +} // eo upperMod(std::string&) + + + +/** + * cut off characters from a given list from front and end of a string. + * @param str the string which should be trimmed. + * @param charlist the list of characters to remove from beginning and end of string + * @return the result string. + */ +std::string trim(const std::string& str, const std::string& charlist) +{ + // first: trim the beginning: + std::string::size_type pos0= str.find_first_not_of(charlist); + if (pos0 == std::string::npos) + { + // whole string consists of charlist (or is already empty) + return std::string(); + } + // now let's look at the end: + std::string::size_type pos1= str.find_last_not_of(charlist); + return str.substr(pos0, pos1 - pos0 + 1); +} // eo trim(const std:.string&,const std::string&) + + +/** + * removes last character from a string when it is in a list of chars to be removed. + * @param str the string. + * @param what the list of chars which will be tested for. + * @return the resulting string with last char removed (if applicable) + */ +std::string chomp(const std::string& str, const std::string& what) +{ + if (str.empty() || what.empty()) + { + return str; + } + if (what.find( str.at(str.size()-1) ) != std::string::npos) + { + return str.substr(0, str.size()-1); + } + return str; +} // eo chomp(const std:.string&,const std::string&) + + +/** + * @brief returns a lower case version of a given string. + * @param str the string + * @return the lower case version of the string + */ +std::string lower(const std::string& str) +{ + std::string result(str); + return lowerMod(result); +} // eo lower(const std::string&) + + +/** + * @brief returns a upper case version of a given string. + * @param str the string + * @return the upper case version of the string + */ +std::string upper(const std::string& str) +{ + std::string result(str); + return upperMod(result); +} // eo upper(const std::string&) + + + +/** + * @brief removes a given suffix from a string. + * @param str the string. + * @param suffix the suffix which should be removed if the string ends with it. + * @return the string without the suffix. + * + * If the string ends with the suffix, it is removed. If the the string doesn't end + * with the suffix the original string is returned. + */ +std::string removeSuffix(const std::string& str, const std::string& suffix) +{ + if (hasSuffix(str,suffix)) + { + return str.substr(0, str.size()-suffix.size() ); + } + return str; +} // eo removeSuffix(const std::string&,const std::string&) + + + +/** + * @brief removes a given prefix from a string. + * @param str the string. + * @param prefix the prefix which should be removed if the string begins with it. + * @return the string without the prefix. + * + * If the string begins with the prefix, it is removed. If the the string doesn't begin + * with the prefix the original string is returned. + */ +std::string removePrefix(const std::string& str, const std::string& prefix) +{ + if (hasPrefix(str,prefix)) + { + return str.substr( prefix.size() ); + } + return str; +} // eo removePrefix(const std::string&,const std::string&) + + +/** + * split a string to key and value delimited by a given delimiter. + * The resulting key and value strings are trimmed (whitespaces removed at beginning and end). + * @param str the string which should be splitted. + * @param[out] key the resulting key + * @param[out] value the resulting value + * @param delimiter the delimiter between key and value; default is '='. + * @return @a true if the split was successful. + */ +bool pairSplit( + const std::string& str, + std::string& key, + std::string& value, + char delimiter) +{ + std::string::size_type pos = str.find(delimiter); + if (pos == std::string::npos) return false; + key= str.substr(0,pos); + value= str.substr(pos+1); + trimMod(key); + trimMod(value); + return true; +} // eo pairSplit(const std::string&,std::string&,std::string&,char) + + +/** + * splits a string by given delimiter + * + * @param[in] str the string which should be splitted. + * @param[out] result the list resulting from splitting @a str. + * @param[in] delimiter the delimiter (word/phrase) at which @a str should be splitted. + * @param[in] omit_empty should empty parts not be stored? + * @param[in] trim_list list of characters the parts should be trimmed by. + * (empty string results in no trim) + */ +void splitString( + const std::string& str, + std::list& result, + const std::string& delimiter, + bool omit_empty, + const std::string& trim_list +) +{ + std::string::size_type pos, last_pos=0; + bool delimiter_found= false; + while ( last_pos < str.size() && last_pos != std::string::npos) + { + pos= str.find(delimiter, last_pos); + std::string part; + if (pos == std::string::npos) + { + part= str.substr(last_pos); + delimiter_found= false; + } + else + { + part= str.substr(last_pos, pos-last_pos); + delimiter_found=true; + } + if (pos != std::string::npos) + { + last_pos= pos+ delimiter.size(); + } + else + { + last_pos= std::string::npos; + } + if (!trim_list.empty()) trimMod(part, trim_list); + if (omit_empty && part.empty()) continue; + result.push_back( part ); + } + // if the string ends with a delimiter we need to append an empty string if no omit_empty + // was given. + // (this way we keep the split result consistent to a join operation) + if (delimiter_found && !omit_empty) + { + result.push_back(""); + } +} // eo splitString(const std::string&,std::list< std::string >&,const std::string&,bool,const std::string&) + + +/** + * splits a string by a given delimiter + * @param str the string which should be splitted. + * @param delimiter delimiter the delimiter (word/phrase) at which @a str should be splitted. + * @param[in] omit_empty should empty parts not be stored? + * @param[in] trim_list list of characters the parts should be trimmed by. + * (empty string results in no trim) + * @return the list resulting from splitting @a str. + */ +std::list splitString( + const std::string& str, + const std::string& delimiter, + bool omit_empty, + const std::string& trim_list +) +{ + std::list result; + splitString(str, result, delimiter, omit_empty, trim_list); + return result; +} // eo splitString(const std::string&,const std::string&,bool,const std::string&) + + +/** + * @brief joins a list of strings into a single string. + * + * This funtion is (basically) the reverse operation of @a splitString. + * + * @param parts the list of strings. + * @param delimiter the delimiter which is inserted between the strings. + * @return the joined string. + */ +std::string joinString( + const std::list< std::string >& parts, + const std::string& delimiter +) +{ + std::string result; + if (! parts.empty()) + { + std::list< std::string >::const_iterator it= parts.begin(); + result = *it; + while( ++it != parts.end() ) + { + result+= delimiter; + result+= *it; + } + } + return result; +} // eo joinString(const std::list< std::string >&,const std::string&) + + + +/* +** conversions +*/ + + +/** + * @brief returns a hex string from a binary string. + * @param str the (binary) string + * @param upper_case_digits determine whether to use upper case characters for digits A-F. + * @return the string in hex notation. + */ +std::string binaryToHex( + const std::string& str, + bool upper_case_digits +) +{ + std::string result; + std::string hexDigits( upper_case_digits ? hexDigitsUpper : hexDigitsLower); + for(std::string::const_iterator it= str.begin(); + it != str.end(); + ++it) + { + result.push_back( hexDigits[ ((*it) >> 4) & 0x0f ] ); + result.push_back( hexDigits[ (*it) & 0x0f ] ); + } + return result; +} // eo binaryToHex(const std::string&,bool) + + +/** + * @brief converts a hex digit string to binary string. + * @param str hex digit string + * @return the binary string. + * + * The hex digit string may contains white spaces or colons which are treated + * as delimiters between hex digit groups. + * + * @todo rework the handling of half nibbles (consistency)! + */ +std::string hexToBinary( + const std::string& str +) +throw(std::runtime_error) +{ + std::string result; + char c= 0; + bool hasNibble= false; + bool lastWasWS= true; + for(std::string::const_iterator it= str.begin(); + it != str.end(); + ++it) + { + std::string::size_type p = hexDigitsLower.find( *it ); + if (p== std::string::npos) + { + p= hexDigitsUpper.find( *it ); + } + if (p == std::string::npos) + { + if ( ( whitespaces.find( *it ) != std::string::npos) // is it a whitespace? + or ( *it == ':') // or a colon? + ) + { + // we treat that as a valid delimiter: + if (hasNibble) + { + // 1 nibble before WS is treate as lower part: + result.push_back(c); + // reset state: + hasNibble= false; + } + lastWasWS= true; + continue; + } + } + if (p == std::string::npos ) + { + throw runtime_error("illegal character in hex digit string: " + str); + } + lastWasWS= false; + if (hasNibble) + { + c<<=4; + } + else + { + c=0; + } + c+= (p & 0x0f); + if (hasNibble) + { + //we already had a nibble, so a char is complete now: + result.push_back( c ); + hasNibble=false; + } + else + { + // this is the first nibble of a new char: + hasNibble=true; + } + } + if (hasNibble) + { + //well, there is one nibble left + // let's do some heuristics: + if (lastWasWS) + { + // if the preceeding character was a white space (or a colon) + // we treat the nibble as lower part: + //( this is consistent with shortened hex notations where leading zeros are not noted) + result.push_back( c ); + } + else + { + // if it was part of a hex digit chain, we treat it as UPPER part (!!) + result.push_back( c << 4 ); + } + } + return result; +} // eo hexToBinary(const std::string&) + + +} // eo namespace i2n + std::string iso_to_utf8(const std::string& isostring) { string result; diff --git a/src/stringfunc.hxx b/src/stringfunc.hxx index 196ae61..2802d36 100644 --- a/src/stringfunc.hxx +++ b/src/stringfunc.hxx @@ -1,13 +1,182 @@ -/*************************************************************************** - * Copyright (C) 2004 by Gerd v. Egidy * - * info@intra2net.com * - * * - ***************************************************************************/ +/** @file + * @brief collection of string tools (/ functions). + * + * contains a collection of miscellaneous functions for dealing with strings. + * + * some functions (like trim, lower case, upper case ...) are available in two versions: + * - a modifying one (suffix "Mod") which modifies the given string argument in place. + * - a non modifying one which take a constant string reference and returns a new string. + * . + * + * + * (c) Copyright 2007-2008 by Intra2net AG + * + * info@intra2net.com + */ #ifndef __STRINGFUNC_HXX #define __STRINGFUNC_HXX +#include #include +#include +#include + +namespace i2n { + +/* +** some useful constants: +*/ + +extern const std::string whitespaces; +extern const std::string lineends; + + +/* +** predicates: +*/ + + +bool hasPrefix(const std::string& str, const std::string& prefix); + +bool hasSuffix(const std::string& str, const std::string& suffix); + + +/* +** tool functions (modifying): +*/ + +std::string trimMod(std::string& str, const std::string& charlist = whitespaces); + +std::string chompMod(std::string& str, const std::string& what= lineends ); + +std::string lowerMod(std::string& str); + +std::string upperMod(std::string& str); + + +/* +** tool functions (not modifying): +*/ + +std::string trim(const std::string& str, const std::string& charlist = whitespaces); + +std::string chomp(const std::string& str, const std::string& what= lineends ); + +std::string lower(const std::string& str); + +std::string upper(const std::string& str); + + +std::string removeSuffix(const std::string& str, const std::string& suffix); + +std::string removePrefix(const std::string& str, const std::string& prefix); + + + +/* +** split and join: +*/ + + +bool pairSplit( + const std::string& str, + std::string& key, + std::string& value, + char delimiter = '='); + + +void splitString( + const std::string& str, + std::list< std::string >& result, + const std::string& delimiter= "\n", + bool omit_empty= false, + const std::string& trim_list= std::string() +); + +std::list< std::string > splitString( + const std::string& str, + const std::string& delimiter = "\n", + bool omit_empty= false, + const std::string& trim_list= std::string() +); + + +std::string joinString( + const std::list< std::string >& parts, + const std::string& delimiter = "\n" +); + + +/* +** conversions: +*/ + + +std::string binaryToHex(const std::string&str, bool upper_case_digits= false); + +std::string hexToBinary(const std::string& str) throw(std::runtime_error); + + + +/* +** "type conversions": +*/ + + +/** + * convert a datatype @a T to a string via string stream. + * + * @param s the string which should be converted to @a T. + * @return the value of type T. + */ +template< + class T +> +T stringTo(const std::string& s) +{ + std::istringstream istr(s); + T result; + istr >> result; + return result; +} // eo stringTo(const std::string&) + + +/** + * convert a datatype @a T to a string via string stream. + * + * @param s the string which should be converted to @a T. + * @param result the resulting value of type @a T. + * @return @a true iff the internal string stream was EOF after the conversion. + */ +template< + class T +> +bool stringTo(const std::string& s, T& result) +{ + std::istringstream istr(s); + istr >> result; + return istr.eof(); +} // eo stringTo(const std::string&) + + +/** + * convert a string to another datatype @a T via string stream. + * + * @param v the value (of type @a T) which should be converted to a string. + * @return the resulting string. + */ +template< + class T +> +std::string toString(const T& v) +{ + std::ostringstream ostr; + ostr << v; + return ostr.str(); +} // eo toString(const T&) + +} // eo namespace i2n std::string to_lower (const std::string &src); std::string to_upper (const std::string &src); -- 1.7.1