//============================================================================= // // File : libkvifile.cpp // Creation date : Fri Nov 9 03:27:59 2001 GMT by Szymon Stefanek // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2001-2010 Szymon Stefanek (pragma at kvirc dot net) // Copyright (C) 2012 Elvio Basello (hellvis69 at gmail dot com) // // 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 // 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #include "KviModule.h" #include "KviFileUtils.h" #include "KviLocale.h" #include "KviMemory.h" #include "KviApplication.h" #include "KviOptions.h" #include "KviKvsArrayCast.h" #include "KviFile.h" #include #include #include #include #include #include #if !defined(COMPILE_ON_WINDOWS) && !defined(COMPILE_ON_MINGW) #include #endif #if defined(COMPILE_SSL_SUPPORT) // Apple deprecated openSSL since OS X 10.7: #ifdef DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER #undef DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER #define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER #endif #include #if OPENSSL_VERSION_NUMBER < 0x10100005L #define EVP_MD_CTX_new EVP_MD_CTX_create #define EVP_MD_CTX_free EVP_MD_CTX_destroy #endif #else // The fallback we can always use, but with very limited set of // functionality. #include #endif /* @doc: file.copy @type: command @title: file.copy @keyterms: copying files @short: Makes a copy of a file @syntax: file.copy [-o] @switches: !sw: -o | --overwrite Overwrites the file even if it already exists. @description: Makes a copy of the file to the path.[br][br] If the [-o] switch is used, any existing file is overwritten.[br][br] The directory containing the destination path must already exist: if you want to ensure this, use [cmd]file.mkdir[/cmd] first.[br][br] The paths ( and ) are adjusted according to the system that KVIrc is running on so you don't have to bother about portability: it [i]should[/i] be automatically guaranteed - just use a UNIX style path.[br] @seealso: [cmd]file.rename[/cmd], [fnc]$file.exists[/fnc] */ static bool file_kvs_cmd_copy(KviKvsModuleCommandCall * c) { QString szSrc, szDst; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("source", KVS_PT_NONEMPTYSTRING, 0, szSrc) KVSM_PARAMETER("destination", KVS_PT_NONEMPTYSTRING, 0, szDst) KVSM_PARAMETERS_END(c) if(szSrc.left(2) != "\\\\") KviFileUtils::adjustFilePath(szSrc); if(szDst.left(2) != "\\\\") KviFileUtils::adjustFilePath(szDst); if(KviFileUtils::fileExists(szDst) && !c->switches()->find('o', "overwrite")) { c->warning(__tr2qs("Destination file exists: no copy made")); return true; } if(!KviFileUtils::copyFile(szSrc, szDst)) { c->warning(__tr2qs("Failed to copy from '%Q' to '%Q'"), &szSrc, &szDst); c->warning(__tr2qs("Either the source doesn't exist or the destination can not be created")); } return true; } /* @doc: file.addimagepath @type: command @title: file.addimagepath @short: Adds a path to the image search path list @syntax: file.addimagepath @description: Adds to the image search path list.[br][br] KVIrc will include the path in its search for image files when a relative image file name is used.[br][br] Yes, this function is a bit misplaced... @seealso: [cmd]file.delimagepath[/cmd] */ static bool file_kvs_cmd_addimagepath(KviKvsModuleCommandCall * c) { QString szDst; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("path", KVS_PT_NONEMPTYSTRING, 0, szDst) KVSM_PARAMETERS_END(c) if(szDst.left(2) != "\\\\") KviFileUtils::adjustFilePath(szDst); //QStringList::Iterator it = KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths).find(szDst); int iIndex = KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths).indexOf(szDst); //if(it == KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths).end()) if(iIndex == -1) KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths) .append(szDst); return true; } /* @doc: file.delimagepath @type: command @title: file.delimagepath @short: Removes a path from the image search path list @syntax: file.delimagepath @description: Removes from the image search path list.[br][br] Yes, this function is a bit misplaced... @seealso: [cmd]file.addimagepath[/cmd] */ static bool file_kvs_cmd_delimagepath(KviKvsModuleCommandCall * c) { QString szDst; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("path", KVS_PT_NONEMPTYSTRING, 0, szDst) KVSM_PARAMETERS_END(c) if(szDst.left(2) != "\\\\") KviFileUtils::adjustFilePath(szDst); //QStringList::Iterator it = KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths).find(szDst); int iIndex = KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths).indexOf(szDst); //if(it == KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths).end()) if(iIndex != -1) KVI_OPTION_STRINGLIST(KviOption_stringlistImageSearchPaths) .removeAt(iIndex); return true; } /* @doc: file.write @type: command @title: file.write @keyterms: writing data to files @short: Writes a character data string to a file @syntax: file.write [-a] [-l] @switches: !sw: -a | --append If the file already exists, append the data instead of overwriting the original contents. !sw: -l | --local-8-bit Causes the file to be written in the local 8-bit character set instead of the default UTF-8. @description: Writes (which is a string of characters) to the file .[br] It does [b]not[/b] append a trailing LF character: if you want it you must explicitly specify it in the parameter.[br][br] [b]-a[/b] causes the command to append the to the file instead of overwriting the entire file.[br][br] The path is adjusted according to the system that KVIrc is running on so you don't have to bother about portability: it [i]should[/i] be automatically guaranteed - just use a UNIX style path.[br][br] The file is saved in UTF-8 unless the [b]-l[/b] switch is specified (in that case the local 8-bit encoding is used). Please note that UTF-8 is the only character set that preserves ALL the possible characters with the exception of the null terminator.[br][br] If you want to write binary data, take a look at [cmd]file.writeBytes[/cmd] instead. @seealso: [cmd]file.rename[/cmd], [fnc]$file.exists[/fnc] */ static bool file_kvs_cmd_write(KviKvsModuleCommandCall * c) { QString szFileName, szData; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETER("data", KVS_PT_NONEMPTYSTRING, 0, szData) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); bool bRet; if(c->switches()->find('l', "local-8-bit")) bRet = KviFileUtils::writeFileLocal8Bit(szFileName, szData, c->switches()->find('a', "append")); else bRet = KviFileUtils::writeFile(szFileName, szData, c->switches()->find('a', "append")); if(!bRet) c->warning(__tr2qs("Failed to write to file '%Q': the destination couldn't be opened"), &szFileName); return true; } /* @doc: file.writeBytes @type: command @title: file.writeBytes @keyterms: writing data to files @short: Writes binary data to a file @syntax: file.writeBytes [-a] @switches: !sw: -a | --append If the file already exists, append the data instead of overwriting the original contents. @description: Writes (which must be an array) to the file .[br][br] The array should either contain integers between 0 and 255, which will be written as bytes, or strings, which will be written in UTF-8 encoding.[br][br] -a causes the command to append the to the file instead of overwriting the entire file.[br][br] The path is adjusted according to the system that KVIrc is running on so you don't have to bother about portability: it [i]should[/i] be automatically guaranteed - just use a UNIX style path.[br] @examples: [example] for(%i=0;%i<256;%i++) %a[%i] = %i file.writeBytes "/home/pragma/test.bin" %a [/example] @seealso: [cmd]file.rename[/cmd], [fnc]$file.exists[/fnc], [fnc]$file.write[/fnc] */ static bool file_kvs_cmd_writeBytes(KviKvsModuleCommandCall * c) { QString szFileName; KviKvsArray * pArray = nullptr; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETER("dataArray", KVS_PT_ARRAY, 0, pArray) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); if(!pArray) return c->error(__tr2qs("Missing data array")); KviFile f(szFileName); if(!f.open(QFile::WriteOnly | (c->switches()->find('a', "append") ? QFile::Append : QFile::Truncate))) { c->warning(__tr2qs("Can't open file '%1' for writing").arg(szFileName)); return true; } QByteArray aBuffer; for(kvs_uint_t i = 0; i < pArray->size(); i++) { KviKvsVariant * pData = pArray->at(i); kvs_int_t iValue; if(pData->asInteger(iValue)) { aBuffer.append((char)iValue); } else { QString szValue; pData->asString(szValue); aBuffer.append(szValue.toUtf8()); } } if(!aBuffer.data()) return true; // nothing to do if(f.write(aBuffer.data(), aBuffer.length()) != ((unsigned int)(aBuffer.length()))) c->warning(__tr2qs("Error writing bytes to file '%1'").arg(szFileName)); return true; } /* @doc: file.rename @type: command @title: file.rename @keyterms: renaming files @short: Renames a file @syntax: file.rename @description: Renames a file from to . This command can also rename directories.[br][br] If already exists, this command fails.[br][br] The paths and are adjusted according to the system that KVIrc is running on so you don't have to bother about portability: it [i]should[/i] be automatically guaranteed - just use a UNIX style path.[br] @seealso: [cmd]file.copy[/cmd], [fnc]$file.exists[/fnc] */ static bool file_kvs_cmd_rename(KviKvsModuleCommandCall * c) { QString szOld, szNew; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("oldname", KVS_PT_NONEMPTYSTRING, 0, szOld) KVSM_PARAMETER("newname", KVS_PT_NONEMPTYSTRING, 0, szNew) KVSM_PARAMETERS_END(c) if(szOld.left(2) != "\\\\") KviFileUtils::adjustFilePath(szOld); if(szNew.left(2) != "\\\\") KviFileUtils::adjustFilePath(szNew); if(KviFileUtils::fileExists(szNew)) c->warning(__tr2qs("Destination file exists: file not renamed")); if(!KviFileUtils::renameFile(szOld, szNew)) c->warning(__tr2qs("Failed to rename '%Q' to '%Q'"), &szOld, &szNew); return true; } /* @doc: file.mkdir @type: command @title: file.mkdir @keyterms: creating directories @short: Creates a directory @syntax: file.mkdir [-q] [-e] @switches: !sw: -q | --quiet Don't complain if the directory cannot be made !sw: -e | --error Fail completely if the directory cannot be made (will stop execution of the script) @description: Creates .[br][br] The path is adjusted according to the system that KVIrc is running on so you don't have to bother about portability: it [i]should[/i] be automatically guaranteed. Just use an UNIX style path.[br] @seealso: [fnc]$file.exists[/fnc] */ static bool file_kvs_cmd_mkdir(KviKvsModuleCommandCall * c) { QString szDir; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("directory", KVS_PT_NONEMPTYSTRING, 0, szDir) KVSM_PARAMETERS_END(c) if(szDir.left(2) != "\\\\") KviFileUtils::adjustFilePath(szDir); if(!KviFileUtils::makeDir(szDir)) { if(!c->switches()->find('q', "quiet")) c->warning(__tr2qs("Failed to make the directory '%Q'"), &szDir); } return !c->switches()->find('e', "error"); } /* @doc: file.remove @type: command @title: file.remove @keyterms: removing files @short: Removes a file @syntax: file.remove [-q] @switches: !sw: -q | --quiet Suppresses any warning message (e.g. file doesn't exist) @description: Removes the file .[br][br] The path is adjusted according to the system that KVIrc is running on so you don't have to bother about portability: it [i]should[/i] be automatically guaranteed. Just use an UNIX style path.[br] @seealso: [fnc]$file.exists[/fnc] */ static bool file_kvs_cmd_remove(KviKvsModuleCommandCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); if(!KviFileUtils::removeFile(szFileName)) { if(!c->switches()->find('q', "quiet")) c->warning(__tr2qs("Failed to remove the file '%Q'"), &szFileName); } return true; } /* @doc: file.rmdir @type: command @title: file.rmdir @keyterms: removing directories @short: Removes a directory @syntax: file.rmdir [-q] @switches: !sw: -q | --quiet Suppresses any warning message (e.g. directory doesn't exist) @description: Removes the directory - the directory must be empty for this command to succeed.[br][br] The path is adjusted according to the system that KVIrc is running on so you don't have to bother about portability: it [i]should[/i] be automatically guaranteed. Just use a UNIX style path.[br] @seealso: [fnc]$file.exists[/fnc] [cmd]file.remove[/cmd] */ static bool file_kvs_cmd_rmdir(KviKvsModuleCommandCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); if(!KviFileUtils::removeDir(szFileName)) { if(!c->switches()->find('q', "quiet")) c->warning(__tr2qs("Failed to remove the directory '%Q'"), &szFileName); } return true; } /* @doc: file.exists @type: function @title: $file.exists @short: Check if a file or directory exists @syntax: $file.exists() @description: Returns true if the specified file or directory exists.[br][br] A UNIX style path should be given and is adjusted according to the system that KVIrc is running on.[br] @examples: [example] # Windows echo $file.exists(c:/windows/notepad.exe); # Linux, other UNIXs echo $file.exists(/etc/passwd); [/example] */ static bool file_kvs_fnc_exists(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_STRING, 0, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); QFileInfo f(szFileName); c->returnValue()->setBoolean(f.exists()); return true; } /* @doc: file.type @type: function @title: $file.type @short: Checks the type of a path @syntax: $file.type() @description: Returns [b]f[/b] if the points to a real file, [b]d[/b] if is the name of a directory or [b]l[/b] if it is a symbolic link.[br][br] The should be a UNIX style path and is adjusted according to the system that KVIrc is running on.[br] */ static bool file_kvs_fnc_type(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); QFileInfo f(szFileName); if(f.isFile()) c->returnValue()->setString("f"); else if(f.isDir()) c->returnValue()->setString("d"); else if(f.isSymLink()) c->returnValue()->setString("l"); return true; } /* @doc: file.size @type: function @title: $file.size @short: Returns the size of a file @syntax: $file.size() @description: Returns the size of the file pointed to by .[br][br] If the file does not exist, this function returns [b]0[/b].[br][br] should be a UNIX style file path and is adjusted according to the system that KVIrc is running on.[br] */ static bool file_kvs_fnc_size(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); QFileInfo f(szFileName); c->returnValue()->setInteger(f.size()); return true; } /* @doc: file.allSizes @type: function @title: $file.allSizes @short: Returns the size of each file in a specified directory. @syntax: $file.allSize() @description: Returns the size of each file in a specified directory as an array.[br][br] This function is not recursive: it won't return the size of files in subdirectories of . */ static bool file_kvs_fnc_allSizes(KviKvsModuleFunctionCall * c) { QString szDir; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("directory", KVS_PT_NONEMPTYSTRING, 0, szDir) KVSM_PARAMETERS_END(c) if(szDir.left(2) != "\\\\") KviFileUtils::adjustFilePath(szDir); QDir d(szDir); if(!d.exists()) { c->warning(__tr2qs("The specified directory doesn't exist '%Q'"), &szDir); return true; } QStringList sl; sl = d.entryList(QDir::Files); KviKvsArray * pArray = new KviKvsArray(); QString szFile; if(!sl.isEmpty()) { int iIdx = 0; for(const auto & it : sl) { szFile = szDir + it; QFileInfo f(szFile); pArray->set(iIdx, new KviKvsVariant((kvs_int_t)f.size())); iIdx++; } } c->returnValue()->setArray(pArray); return true; } /* @doc: file.fixpath @type: function @title: $file.fixpath @short: Converts file paths @syntax: $file.fixpath() @description: Returns the adjusted to match the current operating system path conventions.[br][br] This means that on UNIX, a path like [i]C:\folder\file.mp3[/i] will be returned as [i]/folder/file.mp3[/i], and vice-versa.[br][br] There is a minor problem with UNIX paths converted to the windows system: UNIX has no [i]drive[/i] concept, thus the UNIX paths do not contain a drive - KVIrc will always map the paths to the [i]default[/i] C: drive. This is a good reason to avoid using absolute hard-coded paths :).[br][br] Please note that you [b]don't need[/b] to call this function on paths that you pass to the other file.* functions: they are adjusted automatically.[br] @seealso: [fnc]$file.ps[/fnc] */ static bool file_kvs_fnc_fixpath(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_STRING, 0, szFileName) KVSM_PARAMETERS_END(c) KviFileUtils::adjustFilePath(szFileName); c->returnValue()->setString(szFileName); return true; } /* @doc: file.ps @type: function @title: $file.ps @short: Returns the path separator @syntax: $file.ps() @description: Returns the path separator for the current operating system.[br][br] On windows, [b]\[/b] is returned, on UNIX, [b]/[/b].[br] @seealso: [fnc]$file.fixpath[/fnc] */ static bool file_kvs_fnc_ps(KviKvsModuleFunctionCall * c) { c->returnValue()->setString(QString(QChar(KVI_PATH_SEPARATOR_CHAR))); return true; } /* @doc: file.ls @type: function @title: $file.ls @short: Returns a directory listing @syntax: $file.ls(,[[,]]) @description: Returns the listing of the specified directory as an array.[br][br] The should be given as a UNIX style path and is adjusted according to the system that KVIrc is running on.[br][br] may be a combination of the following characters:[br] [pre] [b]d:[/b] list directories [b]f:[/b] list files [b]l:[/b] list symbolic links [b]r:[/b] list readable files [b]w:[/b] list writable files [b]x:[/b] list executable files [b]h:[/b] list hidden files [b]s:[/b] list system files [b]n:[/b] sort files by name [b]t:[/b] sort files by file time [b]b:[/b] sort files by file size [b]z:[/b] put the directories first, then the files [b]k:[/b] invert sort order [b]i:[/b] case insensitive sort [/pre] If is empty, then a default of [b]dfrwxhs[/b] is set. If none of the [b]r[/b],[b]w[/b],[b]x[/b] flags are set then KVIrc sets all of them by default.[br][br] If is passed then it is interpreted as a wildcard string that must match the entries to be returned.[br] @examples: [example] %dir[]=$file.ls(/,"d") [cmd]foreach[/cmd](%f,%dir[])echo %f %dir[]=$file.ls(/usr/include,"f","t*.h"); [cmd]foreach[/cmd](%f,%dir[])echo %f %dir[]=$file.ls($file.homedir,"dfr"); [cmd]foreach[/cmd](%f,%dir[])echo %f [/example] */ static bool file_kvs_fnc_ls(KviKvsModuleFunctionCall * c) { QString szDir, szFlags, szFilter; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("directory", KVS_PT_NONEMPTYSTRING, 0, szDir) KVSM_PARAMETER("flags", KVS_PT_STRING, KVS_PF_OPTIONAL, szFlags) KVSM_PARAMETER("filter", KVS_PT_STRING, KVS_PF_OPTIONAL, szFilter) KVSM_PARAMETERS_END(c) if(szDir.left(2) != "\\\\") KviFileUtils::adjustFilePath(szDir); QDir d(szDir); if(!d.exists()) { c->warning(__tr2qs("The specified directory doesn't exist '%Q'"), &szDir); return true; } QFlags iFlags = nullptr; if(szFlags.isEmpty()) iFlags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::Readable | QDir::Writable | QDir::Executable | QDir::Hidden | QDir::System; else { if(szFlags.indexOf('d', Qt::CaseInsensitive) != -1) iFlags |= QDir::Dirs; if(szFlags.indexOf('f', Qt::CaseInsensitive) != -1) iFlags |= QDir::Files; if(szFlags.indexOf('l', Qt::CaseInsensitive) == -1) iFlags |= QDir::NoSymLinks; if(szFlags.indexOf('r', Qt::CaseInsensitive) != -1) iFlags |= QDir::Readable; if(szFlags.indexOf('w', Qt::CaseInsensitive) != -1) iFlags |= QDir::Writable; if(szFlags.indexOf('x', Qt::CaseInsensitive) != -1) iFlags |= QDir::Executable; if(szFlags.indexOf('h', Qt::CaseInsensitive) != -1) iFlags |= QDir::Hidden; if(szFlags.indexOf('s', Qt::CaseInsensitive) != -1) iFlags |= QDir::System; } QFlags iSort = nullptr; if(szFlags.isEmpty()) iSort = QDir::Unsorted; else { if(szFlags.indexOf('n', Qt::CaseInsensitive) != -1) iSort |= QDir::Name; if(szFlags.indexOf('t', Qt::CaseInsensitive) != -1) iSort |= QDir::Time; if(szFlags.indexOf('b', Qt::CaseInsensitive) != -1) iSort |= QDir::Size; if(szFlags.indexOf('z', Qt::CaseInsensitive) != -1) iSort |= QDir::DirsFirst; if(szFlags.indexOf('k', Qt::CaseInsensitive) != -1) iSort |= QDir::Reversed; if(szFlags.indexOf('i', Qt::CaseInsensitive) != -1) iSort |= QDir::IgnoreCase; } QStringList sl; if(!szFilter.isEmpty()) sl = d.entryList(QStringList(szFilter), iFlags, iSort); else sl = d.entryList(iFlags, iSort); KviKvsArray * pArray = new KviKvsArray(); if(!sl.isEmpty()) { int iIdx = 0; for(const auto & it : sl) { pArray->set(iIdx, new KviKvsVariant(it)); iIdx++; } } c->returnValue()->setArray(pArray); return true; } /* @doc: file.read @type: function @title: $file.read @short: Reads a text file @syntax: $file.read([,[,]]) @description: Reads at most bytes of the file pointed to by . If is not specified, KVIrc enforces a 1 MiB limit (so if you want to read a file that is larger than 1 MiB then you MUST specify the ).[br][br] The data read is returned as a string, so if the file contains binary data, expect strange results - if you want to read binary data (with null bytes inside) then take a look at [fnc]$file.readBytes[/fnc].[br][br] WARNING: Always check the size of the file you're going to read - it is not a good idea attempting to read a 700 MiB file with this function since it will probably hang your system and exhaust your virtual memory.[br][br] An empty string is returned if a serious error occurs.[br][br] is adjusted according to the Windows or UNIX system that KVIrc is running on.[br][br] If the single [b]l[/b] flag is present, the file is decoded with the local 8-bit character set, otherwise UTF-8 is used. @examples: [example] echo $file.read(/proc/cpuinfo) [/example] @seealso: [fnc]$file.readBytes[/fnc] */ static bool file_kvs_fnc_read(KviKvsModuleFunctionCall * c) { QString szFileName; kvs_uint_t uSize; QString szFlags; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETER("size", KVS_PT_UINT, KVS_PF_OPTIONAL, uSize) KVSM_PARAMETER("flags", KVS_PT_STRING, KVS_PF_OPTIONAL, szFlags) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); QFile f(szFileName); if(!f.open(QIODevice::ReadOnly)) { c->warning(__tr2qs("Can't open the file '%Q' for reading"), &szFileName); return true; } if(c->params()->count() < 2) uSize = 1024 * 1024; // 1 meg file default char * pcBuf = (char *)KviMemory::allocate(sizeof(char) * (uSize + 1)); unsigned int uReaded = 0; unsigned int uRetries = 0; while((uReaded < uSize) && (!f.atEnd())) { if(uRetries > 1000) { // ops KviMemory::free(pcBuf); c->warning(__tr2qs("Read error for file '%Q' (have been unable to read the requested size in 1000 retries)"), &szFileName); return true; } int iReadedNow = f.read(pcBuf + uReaded, uSize - uReaded); if(iReadedNow < 0) { KviMemory::free(pcBuf); c->warning(__tr2qs("Read error for file '%Q'"), &szFileName); return true; } uReaded += iReadedNow; uRetries++; } pcBuf[uReaded] = '\0'; if(szFlags.indexOf('l', Qt::CaseInsensitive) == -1) c->returnValue()->setString(QString::fromUtf8(pcBuf)); else c->returnValue()->setString(QString::fromLocal8Bit(pcBuf)); KviMemory::free(pcBuf); return true; } /* @doc: file.readBytes @type: function @title: $file.readBytes @short: Reads a binary file @syntax: $file.readBytes([,]) @description: Reads at most bytes of the file pointed to by and returns it as an array of integers (bytes). If is not specified, KVIrc enforces a 1 MiB limit (so if you want to read a file that is larger than 1 MiB then you MUST specify the ).[br][br] If you want simple text data then take a look at [fnc]$file.read[/fnc].[br][br] WARNING: Always check the size of the file you're going to read - it is not a good idea attempting to read a 700 MiB binary file with this function since it will probably hang your system and exhaust your virtual memory. An empty array (or just [i]nothing[/i]) is returned if a serious error occurs.[br][br] is adjusted according to the Windows or UNIX system that KVIrc is running on.[br] @examples: [example] echo $file.readBytes(/proc/cpuinfo) [/example] @seealso: [fnc]$file.read[/fnc], [cmd]file.writeBytes[/cmd] */ static bool file_kvs_fnc_readBytes(KviKvsModuleFunctionCall * c) { QString szFileName; kvs_uint_t uSize; QString szFlags; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETER("size", KVS_PT_UINT, KVS_PF_OPTIONAL, uSize) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); QFile f(szFileName); if(!f.open(QIODevice::ReadOnly)) { c->warning(__tr2qs("Can't open the file '%Q' for reading"), &szFileName); return true; } if(c->params()->count() < 2) uSize = 1024 * 1024; // 1 meg file default unsigned char * pcBuf = (unsigned char *)KviMemory::allocate(sizeof(char) * (uSize + 1)); unsigned int uReaded = 0; unsigned int uRetries = 0; while((uReaded < uSize) && (!f.atEnd())) { if(uRetries > 1000) { // ops KviMemory::free(pcBuf); c->warning(__tr2qs("Read error for file '%Q' (have been unable to read the requested size in 1000 retries)"), &szFileName); return true; } int iReadedNow = f.read((char *)(pcBuf + uReaded), uSize - uReaded); if(iReadedNow < 0) { KviMemory::free(pcBuf); c->warning(__tr2qs("Read error for file '%Q'"), &szFileName); return true; } uReaded += iReadedNow; uRetries++; } KviKvsArray * pArray = new KviKvsArray(); kvs_uint_t u = 0; unsigned char * p = pcBuf; while(u < uReaded) { pArray->set(u, new KviKvsVariant((kvs_int_t)(*p))); u++; p++; } c->returnValue()->setArray(pArray); KviMemory::free(pcBuf); return true; } /* @doc: file.readLines @type: function @title: $file.readLines @short: Reads lines of a text file @syntax: $file.readLines([,,[[,]]]) @description: Reads lines from the specified file and returns them as an array of strings. The lines are assumed to be separated by linefeed characters (which are [b]not[/b] returned). Eventual terminating carriage return and line feed characters at the end of the line are stripped.[br][br] If is specified, then all the lines with indexes lower than are discarded.[br][br] If is specified then at most this number of lines are returned, otherwise the entire file is returned.[br][br] WARNING: Always check the size of the file you're going to read - it is not a good idea attempting to read a 700 MiB file with this function since it will probably hang your system and exhaust your virtual memory.[br][br] is adjusted according to the Windows or UNIX system that KVIrc is running on.[br][br] If the single [b]l[/b] flag is present, the file is decoded with the local 8-bit character set, otherwise UTF-8 is used. @examples: [example] echo $file.readLines(/proc/cpuinfo) [/example] @seealso: [fnc]$file.read[/fnc], [cmd]file.writeLines[/cmd], [fnc]$lf[/fnc] */ static bool file_kvs_fnc_readLines(KviKvsModuleFunctionCall * c) { QString szFileName; QString szFlags; kvs_int_t iStartLine; kvs_int_t iCount; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETER("startline", KVS_PT_INT, KVS_PF_OPTIONAL, iStartLine) KVSM_PARAMETER("count", KVS_PT_INT, KVS_PF_OPTIONAL, iCount) KVSM_PARAMETER("flags", KVS_PT_STRING, KVS_PF_OPTIONAL, szFlags) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); QFile f(szFileName); if(!f.open(QIODevice::ReadOnly)) { c->warning(__tr2qs("Can't open the file '%Q' for reading"), &szFileName); return true; } if(c->params()->count() < 2) iStartLine = 0; if(c->params()->count() < 3) iCount = -1; bool bLocal8Bit = szFlags.indexOf('l', 0, Qt::CaseInsensitive) != -1; KviKvsArray * pArray = new KviKvsArray(); int iIndex = 0; QTextStream stream(&f); if(!bLocal8Bit) stream.setCodec(QTextCodec::codecForMib(106)); for(int i = 0; i < iStartLine; i++) stream.readLine(); if(iCount > 0) { while(iCount > 0) { QString szLine = stream.readLine(); if(szLine.isNull()) break; // EOF (atEnd() is unreliable on files in /proc) pArray->set(iIndex, new KviKvsVariant(szLine)); iIndex++; iCount--; } } else { for(;;) { QString szLine = stream.readLine(); if(szLine.isNull()) break; // EOF (atEnd() is unreliable on files in /proc) pArray->set(iIndex, new KviKvsVariant(szLine)); iIndex++; } } f.close(); c->returnValue()->setArray(pArray); return true; } /* @doc: file.writelines @type: command @title: file.writeLines @short: Writes an array of lines to a file @syntax: file.writeLines [-l] [-a] [-c] @switches: !sw: -l | --local-8-bit Causes the lines to be saved in local 8-bit character set instead of the default Unicode encoding. !sw: -a | --append If the file already exists, then the lines are appended to the end instead of overwriting the file. !sw: -c | --crlf The lines are separated by a carriage-return+line-feed character combination, compatible with Windows text mode files. For reference, this is the only way to make the file readable in Windows Notepad. Please note that this is broken design: do [b]not[/b] use it :) !sw: -n | --no-separator Do not separate the lines at all (either the separators are already inside the lines array or no line separation is desired at all). [b]-n[/b] takes precedence over [b]-c[/b]. !sw: -q | --quiet Don't complain if the file can't be opened: just fail silently @description: Writes the array of to the specified file, separated by a single linefeed character (see also [fnc]$lf[/fnc]) unless the [b]-c[/b] or [b]-n[/b] switches are used.[br][br] If the file already exists then it is overwritten with the new data unless the [b]-a[/b] switch is used.[br][br] The lines array is encoded in the UTF-8 character set unless the [b]-l[/b] switch is used. @seealso: [fnc]$file.readLines[/fnc] */ static bool file_kvs_cmd_writeLines(KviKvsModuleCommandCall * c) { QString szFileName, szFlags; KviKvsArrayCast a; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filename", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETER("lines", KVS_PT_ARRAYCAST, 0, a) KVSM_PARAMETERS_END(c) if(szFileName.left(2) != "\\\\") KviFileUtils::adjustFilePath(szFileName); KviFile f(szFileName); bool bAppend = c->switches()->find('a', "append"); bool bLocal8Bit = c->switches()->find('l', "local-8-bit"); bool bNoSeparator = c->switches()->find('n', "no-separator"); bool bAddCR = c->switches()->find('c', "crlf"); if(!f.open(QFile::WriteOnly | (bAppend ? QFile::Append : QFile::Truncate))) { if(!c->switches()->find('q', "quiet")) c->warning(__tr2qs("Can't open the file '%Q' for writing"), &szFileName); return true; } unsigned int u = 0; while(u < a.array()->size()) { KviKvsVariant * v = a.array()->at(u); QByteArray dat; if(v) { QString szDat; v->asString(szDat); dat = bLocal8Bit ? szDat.toLocal8Bit() : szDat.toUtf8(); if(!bNoSeparator) { if(bAddCR) dat += "\r\n"; else dat += '\n'; } f.write(dat.data(), dat.length()); } u++; } f.close(); return true; } /* @doc: file.localdir @type: function @title: $file.localdir @short: Get the KVIrc local directory @syntax: $file.localdir([relative_path:string]) @description: Returns the path to the KVIrc local data directory.[br][br] The KVIrc local data directory is always writable and contains the various subdirectories that KVIrc uses internally: audio, avatars, config, help, incoming, log, modules, msgcolors and pics.[br][br] If is passed, then it is appended to the local directory path.[br][br] The path is adjusted to contain single separators suitable for the platform that KVIrc is actually running on (thus you do not need to care about path separators in the , KVIrc will adjust them).[br] @examples: [example] echo KVIrc looks for pictures in $file.localdir(pics) echo panic.png would be translated to $file.localdir(pics/panic.png) [/example] */ static bool file_kvs_fnc_localdir(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("relative_path", KVS_PT_STRING, KVS_PF_OPTIONAL, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.isEmpty()) szFileName.append(KVI_PATH_SEPARATOR_CHAR); QString szPath; g_pApp->getLocalKvircDirectory(szPath, KviApplication::None, szFileName); KviFileUtils::adjustFilePath(szPath); c->returnValue()->setString(szPath); return true; } /* @doc: file.homedir @type: function @title: $file.homedir @short: Get the user's HOME directory @syntax: $file.homedir([relative_path:string]) @description: Returns the user's HOME directory path.[br][br] If is passed, then it is appended to the directory path.[br][br] The path is adjusted to contain single separators suitable for the platform that KVIrc is actually running on (thus you do not need to care about path separators in the , KVIrc will adjust them).[br] */ static bool file_kvs_fnc_homedir(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("relative_path", KVS_PT_STRING, KVS_PF_OPTIONAL, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.isEmpty()) szFileName.append(KVI_PATH_SEPARATOR_CHAR); QString szPath = QDir::homePath(); KviQString::ensureLastCharIs(szPath, KVI_PATH_SEPARATOR_CHAR); szPath.append(szFileName); KviFileUtils::adjustFilePath(szPath); c->returnValue()->setString(szPath); return true; } /* @doc: file.rootdir @type: function @title: $file.rootdir @short: Get the system root directory @syntax: $file.rootdir() $file.rootdir @description: Returns the system's root directory ('/' on UNIX and 'C:/' on Windows).[br][br] If is passed, then it is appended at the directory path.[br][br] The path is adjusted to contain single separators suitable for the platform that KVIrc is actually running on (thus you not need to care about path separators in the , KVIrc will adjust them).[br] */ static bool file_kvs_fnc_rootdir(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("relative_path", KVS_PT_STRING, KVS_PF_OPTIONAL, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.isEmpty()) szFileName.append(KVI_PATH_SEPARATOR_CHAR); QString szPath = QDir::rootPath(); KviQString::ensureLastCharIs(szPath, KVI_PATH_SEPARATOR_CHAR); szPath.append(szFileName); KviFileUtils::adjustFilePath(szPath); c->returnValue()->setString(szPath); return true; } /* @doc: file.cwd @type: function @title: $file.currentdir @short: Get the current directory @syntax: $file.currentdir([relative_path:string]) @description: Returns the current working directory.[br][br] If is passed, then it is appended to the directory path.[br][br] The path is adjusted to contain single separators suitable for the platform that KVIrc is actually running on (thus you do not need to care about path separators in the , KVIrc will adjust them).[br] */ static bool file_kvs_fnc_cwd(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("relative_path", KVS_PT_STRING, KVS_PF_OPTIONAL, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.isEmpty()) szFileName.append(KVI_PATH_SEPARATOR_CHAR); QString szPath = QDir::currentPath(); KviQString::ensureLastCharIs(szPath, KVI_PATH_SEPARATOR_CHAR); szPath.append(szFileName); KviFileUtils::adjustFilePath(szPath); c->returnValue()->setString(szPath); return true; } /* @doc: file.globaldir @type: function @title: $file.globaldir @short: Get the KVIrc global directory @syntax: $file.globaldir() $file.globaldir @description: Returns the path to the KVIrc global data directory.[br][br] The KVIrc global data directory is always readable (but usually not writable) and contains the various subdirectories that KVIrc uses internally: audio, avatars, config, help, incoming, log, modules, msgcolors and pics.[br][br] If is passed, then it is appended to the directory path.[br][br] The path is adjusted to contain single separators suitable for the platform that KVIrc is actually running on (thus you do not need to care about path separators in the , KVIrc will adjust them).[br] @examples: [example] echo KVIrc looks for pictures in $file.globaldir(pics) echo panic.png would be translated to $file.globaldir(pics/panic.png) [/example] */ static bool file_kvs_fnc_globaldir(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("relative_path", KVS_PT_STRING, KVS_PF_OPTIONAL, szFileName) KVSM_PARAMETERS_END(c) if(szFileName.isEmpty()) szFileName.append(KVI_PATH_SEPARATOR_CHAR); QString szPath; g_pApp->getGlobalKvircDirectory(szPath, KviApplication::None, szFileName); KviFileUtils::adjustFilePath(szPath); c->returnValue()->setString(szPath); return true; } /* @doc: file.extractpath @type: function @title: $file.extractpath @short: Extract the parent directory path from a file path @syntax: $file.extractpath() @description: Returns the parent directory path part of the translated to match the current platform filesystem conventions.[br][br] The path will [b]not[/b] contain a trailing path separator.[br][br] For example, if is /usr/arch/mp3/Carisma_SuonoDelSilenzio.mp3 then this function will return /usr/arch/mp3 on UNIX and C:\usr\arch\mp3 on Windows. @seealso: [fnc]$file.extractFileName[/fnc] */ static bool file_kvs_fnc_extractpath(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filepath", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETERS_END(c) c->returnValue()->setString(QFileInfo(szFileName).absolutePath()); return true; } /* @doc: file.extractfilename @type: function @title: $file.extractfilename @short: Extract the filename from a file path @syntax: $file.extractfilename() @description: Returns the filename part of the filepath translated to match the current platform filesystem conventions.[br][br] For example, if is /usr/arch/mp3/Carisma_SuonoDelSilenzio.mp3 then this function will return Carisma_SuonoDelSilenzio.mp3 @seealso: [fnc]$file.extractfilename[/fnc] */ static bool file_kvs_fnc_extractfilename(KviKvsModuleFunctionCall * c) { QString szFileName; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("filepath", KVS_PT_NONEMPTYSTRING, 0, szFileName) KVSM_PARAMETERS_END(c) KviFileUtils::extractFileName(szFileName); KviQString::cutToLast(szFileName, KVI_PATH_SEPARATOR_CHAR); c->returnValue()->setString(szFileName); return true; } /* @doc: file.diskspace @type: function @title: $file.diskspace @short: Return the available diskspace. @syntax: $file.diskspace([dirpath:string]) @description: Returns a hash containing the [b]freespace[/b] and [b]totalspace[/b] keys, measured in bytes.[br][br] Without the optional dirpath parameter, the current device's disk space will be returned (i.e. the disk KVIrc is running from).[br] */ static bool file_kvs_fnc_diskSpace(KviKvsModuleFunctionCall * c) { QString szPath; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("dir_path", KVS_PT_STRING, KVS_PF_OPTIONAL, szPath) KVSM_PARAMETERS_END(c) if(szPath.isEmpty()) szPath = "."; long long int fTotal; long long int fFree; // this for win #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW) ULARGE_INTEGER free, total; if (GetDiskFreeSpaceEx(szPath.toStdWString().c_str(), nullptr, &total, &free) == 0) { c->warning(__tr2qs("An error occurred retrieving the amount of free space in '%Q'"), &szPath); return true; } fFree = free.QuadPart; fTotal = total.QuadPart; #else // this one for linux and macos auto pcPath = szPath.toUtf8(); struct statvfs stFileSystem; if (statvfs(pcPath.data(), &stFileSystem) == -1) { c->warning(__tr2qs("An error occurred retrieving the amount of free space in '%Q'"), &szPath); return true; } fFree = stFileSystem.f_bavail * stFileSystem.f_bsize; fTotal = stFileSystem.f_blocks * stFileSystem.f_bsize; #endif KviKvsHash * pHash = new KviKvsHash(); pHash->set("freespace", new KviKvsVariant((kvs_int_t)fFree)); pHash->set("totalspace", new KviKvsVariant((kvs_int_t)fTotal)); c->returnValue()->setHash(pHash); return true; } /* @doc: file.digest @type: function @title: $file.digest @short: Returns a digest/hash string of the given file contents @syntax: $file.digest([,]) @description: Calculates a digest for the file identified by the given string using the algorithm specified.[br][br] Currently supported: md5 (default), md4, md2, sha1, mdc2, ripemd160 and dss1. Requires OpenSSL support, but offers a minimal set of hashes in any case. */ static bool file_kvs_fnc_digest(KviKvsModuleFunctionCall * c) { QString szFile, szAlgo, szResult; KVSM_PARAMETERS_BEGIN(c) KVSM_PARAMETER("data", KVS_PT_NONEMPTYSTRING, 0, szFile) KVSM_PARAMETER("algorithm", KVS_PT_STRING, KVS_PF_OPTIONAL, szAlgo) KVSM_PARAMETERS_END(c) KviFileUtils::adjustFilePath(szFile); QFile file(szFile); if(!file.open(QIODevice::ReadOnly)) { c->warning(__tr2qs("Can't open the file '%Q' for reading"), &szFile); return true; } QByteArray content = file.readAll(); if(content.isEmpty()) { c->warning(__tr2qs("Can't read data from file")); return true; } #if defined(COMPILE_SSL_SUPPORT) if(szAlgo.isEmpty()) szAlgo = "md5"; EVP_MD_CTX *mdctx; const EVP_MD * pMD; unsigned char ucMDValue[EVP_MAX_MD_SIZE]; unsigned int uMDLen, u; char cBuffer[3]; OpenSSL_add_all_digests(); pMD = EVP_get_digestbyname(szAlgo.toUtf8().data()); if(!pMD) { c->warning(__tr2qs("'%Q' algorithm is not supported"), &szAlgo); return true; } mdctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(mdctx, pMD, nullptr); EVP_DigestUpdate(mdctx, content.constData(), content.size()); EVP_DigestFinal_ex(mdctx, ucMDValue, &uMDLen); EVP_MD_CTX_free(mdctx); for(u = 0; u < uMDLen; u++) { #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW) _snprintf(cBuffer, 3, "%02x", ucMDValue[u]); #else snprintf(cBuffer, 3, "%02x", ucMDValue[u]); #endif szResult.append(cBuffer); } #else // fall back to QCryptographicHash QCryptographicHash::Algorithm qAlgo; if(szAlgo.toLower() == "sha1") { qAlgo = QCryptographicHash::Sha1; } else if(szAlgo.toLower() == "md4") { qAlgo = QCryptographicHash::Md4; } else if(szAlgo.toLower() == "md5") { qAlgo = QCryptographicHash::Md5; } else { c->warning(__tr2qs("KVIrc is compiled without Crypto++ or SSL support. $file.digest supports only MD4, MD5 and SHA1.")); return true; } szResult.append(QCryptographicHash::hash(content, qAlgo).toHex()); #endif c->returnValue()->setString(szResult); return true; } /* @doc: file.time @type: function @title: $file.time @short: Returns the datetime of a file in unixtime format @syntax: $file.time([,