/* * scriptpp.cpp * * This file is a part of NSIS. * * Copyright (C) 1999-2021 Nullsoft and Contributors * * Licensed under the zlib/libpng license (the "License"); * you may not use this file except in compliance with the License. * * Licence details can be found in the file COPYING. * * This software is provided 'as-is', without any express or implied * warranty. */ #include "Platform.h" #include #include #include "tstring.h" #include "lineparse.h" #include #include "tokens.h" #include "build.h" #include "utf.h" #include "util.h" #include "BinInterop.h" #include "dirreader.h" #include // for assert(3) #include #include #include "boost/scoped_ptr.hpp" using namespace std; #ifdef _WIN32 # include // for chdir #else # include // for stat and umask # include // for mode_t # include // for O_RDONLY # include # include // for mkstemp #endif #define MAX_INCLUDEDEPTH 10 #define PRINTHELPEX(cmdname) { print_help((cmdname)); return PS_ERROR; } #define PRINTHELP() PRINTHELPEX(line.gettoken_str(0)) static void PREPROCESSONLY_BEGINCOMMENT() { extern FILE *g_output; _ftprintf(g_output, _T("!if 0 /*\n")); } static void PREPROCESSONLY_ENDCOMMENT() { extern FILE *g_output; _ftprintf(g_output, _T("*/\n!endif\n")); } static UINT read_line_helper(NStreamLineReader&lr, TCHAR*buf, UINT cch) { // Helper function for reading lines from text files. buf MUST be valid and cch MUST be > 1! // Returns 0 on error or the number of characters read including the first \n, \r or \0. // When it returns 0, buf[0] is 0 for EOF and NStream::ERR_* for errors. UINT lrr = lr.ReadLine(buf, cch), eof = 0; if (NStream::OK != lrr) { ++eof; if (!lr.IsEOF()) { buf[0] = (TCHAR) lrr; return 0; } } const bool unicode = lr.IsUnicode(); for(cch = 0;; ++cch) if (!buf[cch] || NStream::IsNewline(buf[cch], unicode)) break; if (cch) eof = 0; // Read something, postpone EOF return ++cch - eof; } #ifdef NSIS_SUPPORT_STANDARD_PREDEFINES TCHAR *CEXEBuild::set_file_predefine(const TCHAR *filename) { TCHAR *oldfileinfo = NULL; TCHAR *oldfilename = definedlist.find(_T("__FILE__")); TCHAR *oldfiledir = definedlist.find(_T("__FILEDIR__")); if (oldfilename && oldfiledir) { oldfileinfo = new TCHAR[_tcslen(oldfilename)+1+_tcslen(oldfiledir)+1]; _tcscpy(oldfileinfo, oldfilename); _tcscat(oldfileinfo, _T("|")); _tcscat(oldfileinfo, oldfiledir); definedlist.del(_T("__FILE__")); definedlist.del(_T("__FILEDIR__")); } const TCHAR *p = _tcsrchr(filename,_T('\\')), *p2 = _tcsrchr(filename,_T('/')); if (p2 > p) p = p2; if (p) p++; else p = filename; definedlist.add(_T("__FILE__"),p); TCHAR dir[260]; // BUGBUG: MAX_PATH outside #ifdef _WIN32, should be PATH/NAME_MAX on POSIX? #ifdef _WIN32 LPTSTR lpFilePart; GetFullPathName(filename, COUNTOF(dir), dir, &lpFilePart); PathRemoveFileSpec(dir); #else if (p == filename) _tcscpy(dir, _T(".")); else my_strncpy(dir, filename, p-filename+!0); #endif definedlist.add(_T("__FILEDIR__"),dir); return oldfileinfo; } void CEXEBuild::restore_file_predefine(TCHAR *oldfilename) { definedlist.del(_T("__FILEDIR__")); definedlist.del(_T("__FILE__")); if (oldfilename) { TCHAR *oldfiledir = _tcschr(oldfilename, _T('|')); definedlist.add(_T("__FILEDIR__"),oldfiledir+1); *oldfiledir = '\0'; definedlist.add(_T("__FILE__"),oldfilename); delete[] oldfilename; } } TCHAR *CEXEBuild::set_timestamp_predefine(const TCHAR *filename) { TCHAR *oldtimestamp = definedlist.find(_T("__TIMESTAMP__")); if (oldtimestamp) { oldtimestamp = _tcsdup(oldtimestamp); definedlist.del(_T("__TIMESTAMP__")); } #ifdef _WIN32 TCHAR datebuf[128] = _T(""), timebuf[128] = _T(""), timestampbuf[256]; WIN32_FIND_DATA fd; FILETIME floctime; SYSTEMTIME stime; HANDLE hSearch = FindFirstFile(filename, &fd); if (hSearch != INVALID_HANDLE_VALUE) { FindClose(hSearch); FileTimeToLocalFileTime(&fd.ftLastWriteTime, &floctime); FileTimeToSystemTime(&floctime, &stime); GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &stime, NULL, datebuf, COUNTOF(datebuf)); GetTimeFormat(LOCALE_USER_DEFAULT, 0, &stime, NULL, timebuf, COUNTOF(timebuf)); wsprintf(timestampbuf,_T("%") NPRIs _T(" %") NPRIs,datebuf,timebuf); definedlist.add(_T("__TIMESTAMP__"),timestampbuf); } #else struct stat st; if (!_tstat(filename, &st)) definedlist.add(_T("__TIMESTAMP__"),PosixBug_CtoTString(ctime(&st.st_mtime))); #endif return oldtimestamp; } void CEXEBuild::restore_timestamp_predefine(TCHAR *oldtimestamp) { definedlist.del(_T("__TIMESTAMP__")); if (oldtimestamp) { definedlist.add(_T("__TIMESTAMP__"),oldtimestamp); free(oldtimestamp); } } TCHAR *CEXEBuild::set_line_predefine(int linecnt, BOOL is_macro) { TCHAR* linebuf = NULL; MANAGE_WITH(linebuf, free); TCHAR temp[128] = _T(""); _stprintf(temp,_T("%d"),linecnt); TCHAR *oldline = definedlist.find(_T("__LINE__")); if (oldline) { oldline = _tcsdup(oldline); definedlist.del(_T("__LINE__")); } if (is_macro && oldline) { linebuf = (TCHAR *)malloc((_tcslen(oldline)+_tcslen(temp)+2)*sizeof(TCHAR)); _stprintf(linebuf,_T("%") NPRIs _T(".%") NPRIs,oldline,temp); } else { linebuf = _tcsdup(temp); } definedlist.add(_T("__LINE__"),linebuf); return oldline; } void CEXEBuild::restore_line_predefine(TCHAR *oldline) { definedlist.del(_T("__LINE__")); if (oldline) { definedlist.add(_T("__LINE__"),oldline); free(oldline); } } void CEXEBuild::set_date_time_predefines() { time_t etime; struct tm *ltime; TCHAR datebuf[128], timebuf[128]; time(&etime); ltime = localtime(&etime); #ifdef _WIN32 SYSTEMTIME st; st.wYear = ltime->tm_year+1900, st.wMonth = ltime->tm_mon + 1, st.wDay = ltime->tm_mday; st.wHour = ltime->tm_hour, st.wMinute = ltime->tm_min, st.wSecond = ltime->tm_sec; st.wMilliseconds = 0; GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, datebuf, sizeof(datebuf)); definedlist.add(_T("__DATE__"), datebuf); GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, timebuf, sizeof(timebuf)); definedlist.add(_T("__TIME__"), timebuf); #else my_strftime(datebuf, sizeof(datebuf), _T("%x"), ltime); definedlist.add(_T("__DATE__"), datebuf); my_strftime(timebuf, sizeof(timebuf), _T("%X"), ltime); definedlist.add(_T("__TIME__"), timebuf); #endif } void CEXEBuild::del_date_time_predefines() { definedlist.del(_T("__DATE__")); definedlist.del(_T("__TIME__")); } #endif TCHAR* CEXEBuild::GetMacro(const TCHAR *macroname, TCHAR**macroend /*= 0*/) { TCHAR *t = (TCHAR*)m_macros.get(), *mbeg, *mbufbeg = t; size_t cbAll = m_macros.getlen(); for (; t && *t; ++t) { mbeg = t; if ((size_t)t - (size_t)mbufbeg >= cbAll) break; const bool foundit = !_tcsicmp(mbeg, macroname); t += _tcslen(t) + 1; // advance over macro name // advance over parameters while (*t) t += _tcslen(t) + 1; t++; // Separator between parameters and data // advance over data while (*t) t += _tcslen(t) + 1; if (foundit) { if (macroend) *macroend = ++t; return mbeg; } } return 0; } int CEXEBuild::pp_macro(LineParser&line) { const TCHAR*const macroname = line.gettoken_str(1), *tokstr; if (!macroname[0]) PRINTHELP() if (MacroExists(macroname)) { ERROR_MSG(_T("!macro: macro named \"%") NPRIs _T("\" already exists!\n"), macroname); return PS_ERROR; } m_macros.add(macroname, (int)(_tcslen(macroname)+1)*sizeof(TCHAR)); for (int pc=2; pc < line.getnumtokens(); pc++) { if (!(tokstr = line.gettoken_str(pc))[0]) { ERROR_MSG(_T("!macro: macro parameter %d is empty, not valid!\n"), pc-1); return PS_ERROR; } for (int a = 2; a < pc; a++) { if (!_tcsicmp(tokstr, line.gettoken_str(a))) { ERROR_MSG(_T("!macro: macro parameter named %") NPRIs _T(" is used multiple times!\n"), tokstr); return PS_ERROR; } } m_macros.add(tokstr, (int)(_tcslen(tokstr)+1)*sizeof(TCHAR)); } m_macros.add(_T(""), sizeof(_T(""))); // Separator between parameters and data for (;;) { TCHAR *str = m_templinebuf, *p = str; UINT lrres = curlinereader->ReadLine(str, MAX_LINELENGTH); if (NStream::OK != lrres) { if (curlinereader->IsEOF()) { if (!str[0]) { ERROR_MSG(_T("!macro \"%") NPRIs _T("\": unterminated (no !macroend found in file)!\n"), macroname); return PS_ERROR; } } else { ERROR_MSG(curlinereader->GetErrorMessage(lrres).c_str()); return PS_ERROR; } } //SCRIPT_MSG(_T("%") NPRIs _T("%") NPRIs, str, str[_tcslen(str)-1] == _T('\n') ? _T("") : _T("\n")); // remove trailing whitespace while (*p) p++; if (p > str) p--; while (p >= str && (*p == _T('\r') || *p == _T('\n') || *p == _T(' ') || *p == _T('\t'))) p--; *++p = 0; LineParser l2(false); if (!l2.parse(str)) { if (!_tcsicmp(l2.gettoken_str(0), _T("!macroend"))) { linecnt++; break; } if (!_tcsicmp(l2.gettoken_str(0), _T("!macro"))) { ERROR_MSG(_T("Error: can't define a macro inside a macro!\n")); return PS_ERROR; } } if (str[0]) m_macros.add(str, (int)(_tcslen(str)+1)*sizeof(TCHAR)); else m_macros.add(_T(" "), sizeof(_T(" "))); linecnt++; } m_macros.add(_T(""), sizeof(_T(""))); // End of data return PS_OK; } int CEXEBuild::pp_macroundef(LineParser&line) { const TCHAR*const mname = line.gettoken_str(1); if (!mname[0]) PRINTHELP() TCHAR *mend, *mbeg = GetMacro(mname, &mend); if (!mbeg) { ERROR_MSG(_T("!macroundef: \"%") NPRIs _T("\" does not exist!\n"), mname); return PS_ERROR; } TCHAR *mbufb = (TCHAR*)m_macros.get(); const size_t mcb = ((mend)-mbeg)*sizeof(TCHAR), mbufcb = m_macros.getlen(); memmove(mbeg, mend, mbufcb-(((mbeg-mbufb)*sizeof(TCHAR))+mcb)); m_macros.resize(truncate_cast(int,(size_t)(mbufcb-mcb))); SCRIPT_MSG(_T("!macroundef: %") NPRIs _T("\n"), mname); return PS_OK; } int CEXEBuild::pp_insertmacro(LineParser&line) { static unsigned char g_insertmacrorecursion = 0; const TCHAR*const macroname = line.gettoken_str(1); if (!macroname[0]) PRINTHELP() TCHAR *t = GetMacro(macroname), *m = (TCHAR *)m_macros.get(); SCRIPT_MSG(_T("!insertmacro: %") NPRIs _T("\n"), macroname); if (!t) { ERROR_MSG(_T("!insertmacro: macro named \"%") NPRIs _T("\" not found!\n"), macroname); return PS_ERROR; } t+=_tcslen(t)+1; GrowBuf l_define_names; DefineList l_define_saves; int npr = 0; // advance over params while (*t) { TCHAR *v = definedlist.find(t); if (v) { l_define_saves.add(t,v); definedlist.del(t); } l_define_names.add(t, (int)(_tcslen(t)+1)*sizeof(TCHAR)); definedlist.add(t, line.gettoken_str(npr+2)); npr++; t += _tcslen(t)+1; } l_define_names.add(_T(""), sizeof(_T(""))); t++; if (npr != line.getnumtokens()-2) { ERROR_MSG(_T("!insertmacro: macro \"%") NPRIs _T("\" requires %d parameter(s), passed %d!\n"), macroname, npr,line.getnumtokens()-2); return PS_ERROR; } if (++g_insertmacrorecursion > MAX_MACRORECURSION) { ERROR_MSG(_T("!insertmacro: insert depth is limited to %u macros!\n"), MAX_MACRORECURSION); return PS_ERROR; } const bool oldparserinsidecomment = inside_comment; inside_comment = false; // "!insertmacro foo /*" does not mean that the macro body is a comment TCHAR str[1024]; wsprintf(str, _T("macro:%") NPRIs, macroname); const TCHAR *oldmacroname = m_currentmacroname; m_currentmacroname = macroname; definedlist.set(_T("__MACRO__"), m_currentmacroname); int lp = 0; while (*t) { lp++; if (_tcscmp(t, _T(" "))) { int ret = process_oneline(t, str, lp); if (ret != PS_OK) { ERROR_MSG(_T("Error in macro %") NPRIs _T(" on macroline %d\n"), macroname, lp); return ret; } } { // fix t if process_oneline changed m_macros TCHAR *nm = (TCHAR*)m_macros.get(); if (nm != m) t += nm - m, m = nm; } t += _tcslen(t)+1; } { TCHAR *p = (TCHAR*)l_define_names.get(); while (*p) { definedlist.del(p); TCHAR *v; if ((v = l_define_saves.find(p))) definedlist.add(p,v); p += _tcslen(p)+1; } } definedlist.del(_T("__MACRO__")); m_currentmacroname = oldmacroname; if (oldmacroname) definedlist.add(_T("__MACRO__"), oldmacroname); inside_comment = oldparserinsidecomment; --g_insertmacrorecursion; SCRIPT_MSG(_T("!insertmacro: end of %") NPRIs _T("\n"), macroname); return PS_OK; } int CEXEBuild::pp_tempfile(LineParser&line) { TCHAR *symbol = line.gettoken_str(1); TCHAR *tfpath = create_tempfile_path(); if (!tfpath) { ERROR_MSG(_T("!tempfile: Unable to create temporary file!\n")); return PS_ERROR; } int symexisted = definedlist.add(symbol, tfpath); free(tfpath); if (symexisted) { ERROR_MSG(_T("!tempfile: \"%") NPRIs _T("\" already defined!\n"), symbol); return PS_ERROR; } return PS_OK; } int CEXEBuild::pp_delfile(LineParser&line) { UINT fatal = true, a = 1, matchcount = 0; const TCHAR *fc = line.gettoken_str(a); if (line.getnumtokens()==3) { if (!_tcsicmp(fc,_T("/nonfatal"))) fatal = 0, fc = line.gettoken_str(++a); else PRINTHELP(); } SCRIPT_MSG(_T("!delfile: \"%") NPRIs _T("\"\n"), fc); const TCHAR *fmt = _T("!delfile: \"%") NPRIs _T("\" couldn't be deleted.\n"); tstring dir = get_dir_name(fc), spec = get_file_name(fc); tstring basedir = dir + PLATFORM_PATH_SEPARATOR_STR; if (dir == spec) dir = _T("."), basedir = _T(""); // no path, just file name boost::scoped_ptr dr( new_dir_reader() ); dr->read(dir); // BUGBUG: PATH_CONVERT? dir_reader::iterator files_itr = dr->files().begin(); for (; files_itr != dr->files().end(); files_itr++) { if (!dir_reader::matches(*files_itr, spec)) continue; ++matchcount; tstring file = basedir + *files_itr; // BUGBUG: PATH_CONVERT? fc = file.c_str(); if (-1 == _tunlink(fc)) { if (fatal) return (ERROR_MSG(fmt, fc), PS_ERROR); else warning_fl(DW_PP_DELFILE_DELERROR, fmt, fc); } else SCRIPT_MSG(_T("!delfile: deleted \"%") NPRIs _T("\"\n"), fc); } if (!matchcount) { if (fatal) return (ERROR_MSG(fmt, fc), PS_ERROR); else warning_fl(DW_PP_DELFILE_NOMATCH, fmt, fc); } return PS_OK; } int CEXEBuild::pp_appendfile(LineParser&line) { WORD tok = 0, cp = 0, forceEnc = false, rawnl = false; bool bom = false; TCHAR *param, buf[9+!0]; for (;;) { param = line.gettoken_str(++tok); my_strncpy(buf, param, COUNTOF(buf)); if (!_tcsicmp(param,_T("/RawNL"))) ++rawnl; else if (!_tcsicmp(buf,_T("/CharSet="))) { ++forceEnc, cp = GetEncodingFromString(param+9, bom); if (NStreamEncoding::UNKNOWN == cp) { ERROR_MSG(_T("!appendfile: Invalid parameter \"%") NPRIs _T("\"!\n"), param); return PS_ERROR; } } else break; } if (line.getnumtokens() != 2 + tok) { PRINTHELP(); return PS_ERROR; } param = line.gettoken_str(tok); NOStream ostrm; if (!ostrm.CreateFileForAppending(param, NStreamEncoding::ACP)) { ERROR_MSG(_T("!appendfile: \"%") NPRIs _T("\" couldn't be opened.\n"), param); return PS_ERROR; } if (ostrm.IsUnicode()) bom = false; if (forceEnc) ostrm.StreamEncoding().SetCodepage(cp); const TCHAR *const text = line.gettoken_str(++tok); bool succ = bom ? ostrm.WriteBOM(ostrm.StreamEncoding()) : true; if (!succ || (rawnl ? !ostrm.WriteString(text) : !ostrm.WritePlatformNLString(text))) { ERROR_MSG(_T("!appendfile: error writing to \"%") NPRIs _T("\".\n"), param); return PS_ERROR; } SCRIPT_MSG(_T("!appendfile: \"%") NPRIs _T("\" \"%") NPRIs _T("\"\n"), param, text); return PS_OK; } enum { PPGVHF_VALID = 0x01, PPGVHF_NOERRORS = 0x02, PPGVHF_PACKED = 0x04, PPGVHF_TLB = 0x08 }; int CEXEBuild::pp_getversionhelper(const TCHAR *cmdname, const TCHAR *path, const TCHAR *basesymname, DWORD high, DWORD low, DWORD flags) { TCHAR *symbuf = m_templinebuf; DWORD tlb = (flags & PPGVHF_TLB), noerrors = (flags & PPGVHF_NOERRORS); FILE *pF = tlb ? MSTLB_fopen(path) : FOPEN(path, ("rb")); if (pF) fclose(pF); bool vnum = pF && (flags & PPGVHF_VALID); // LibraryLocal users want to detect "file not found" vs "no version info" if (!vnum) high = low = 0; DWORD vals[] = { high >> 16, high & 0xffff, low >> 16, low & 0xffff }, count = 4; if (tlb) count = 2, vals[0] = high, vals[1] = low, vals[2] = vals[3] = 0; if (!pF) { if (noerrors) return PS_OK; ERROR_MSG(_T("%") NPRIs _T(": error reading version info from \"%") NPRIs _T("\"\n"), cmdname, path); return PS_ERROR; } if (flags & PPGVHF_PACKED) { SCRIPT_MSG(_T("%") NPRIs _T(": %") NPRIs _T(" (%lu.%lu)->(%") NPRIs _T(")\n"), cmdname, path, high, low, basesymname); _stprintf(symbuf,_T("%") NPRIs _T("HIGH"), basesymname), vnum ? definedlist.set_ui32(symbuf, high) : definedlist.set(symbuf, _T("")); _stprintf(symbuf,_T("%") NPRIs _T("LOW"), basesymname), vnum ? definedlist.set_ui32(symbuf, low) : definedlist.set(symbuf, _T("")); } else { SCRIPT_MSG(_T("%") NPRIs _T(": %") NPRIs _T(" (%u.%u.%u.%u)->(%") NPRIs _T("<1..%d>)\n"), cmdname, path, vals[0], vals[1], vals[2], vals[3], basesymname, (int) count); for (UINT i = 0; i < count; ++i) { _stprintf(symbuf,_T("%") NPRIs _T("%u"), basesymname, i+1); vnum ? definedlist.set_ui32(symbuf, vals[i]) : definedlist.set(symbuf, _T("")); } } return PS_OK; } int CEXEBuild::pp_getversion(int which_token, LineParser&line) { const bool tlb = TOK_P_GETTLBVERSION == which_token; const TCHAR *cmdname = tlb ? _T("!gettlbversion") : _T("!getdllversion"), *path; DWORD ti = 1, flags = tlb ? PPGVHF_TLB : 0, low, high, prod = 0; for (;; ++ti) { if (!_tcsicmp(line.gettoken_str(ti), _T("/noerrors"))) flags |= PPGVHF_NOERRORS; else if (!_tcsicmp(line.gettoken_str(ti), _T("/packed"))) flags |= PPGVHF_PACKED; else if (!_tcsicmp(line.gettoken_str(ti), _T("/productversion"))) ++prod; else break; } if ((tlb ? GetTLBVersion : GetDLLVersion)(path = line.gettoken_str(ti), high, low, !!prod)) flags |= PPGVHF_VALID; return pp_getversionhelper(cmdname, path, line.gettoken_str(ti+1), high, low, flags); } int CEXEBuild::pp_searchreplacestring(LineParser&line) { int ignoreCase = !_tcsicmp(line.gettoken_str(1), _T("/ignorecase")); if (line.getnumtokens() != 5+ignoreCase) PRINTHELP() TCHAR *define = line.gettoken_str(1+ignoreCase); TCHAR *src = line.gettoken_str(2+ignoreCase); TCHAR *search = line.gettoken_str(3+ignoreCase); TCHAR *replace = line.gettoken_str(4+ignoreCase); int searchlen = (int)_tcslen(search), replacelen = (int)_tcslen(replace); if (!searchlen) { ERROR_MSG(_T("!searchreplace: search string must not be empty for search/replace!\n")); return PS_ERROR; } GrowBuf valout; while (*src) { if (ignoreCase ? _tcsnicmp(src, search, searchlen) : _tcsncmp(src, search, searchlen)) valout.add(src++, sizeof(TCHAR)); else { valout.add(replace, sizeof(TCHAR)*replacelen); src += searchlen; } } valout.add(_T(""),sizeof(TCHAR)); definedlist.del(define); // allow changing variables since we'll often use this in series if (definedlist.add(define, (TCHAR*)valout.get())) { ERROR_MSG(_T("!searchreplace: error defining \"%") NPRIs _T("\"!\n"), define); return PS_ERROR; } SCRIPT_MSG(_T("!searchreplace: \"%") NPRIs _T("\"=\"%") NPRIs _T("\"\n"), define,(TCHAR*)valout.get()); return S_OK; } int CEXEBuild::pp_searchparsestring(LineParser&line) { bool ignCase = false, noErrors = false, isFile = false; int parmOffs = 1; while (parmOffs < line.getnumtokens()) { if (!_tcsicmp(line.gettoken_str(parmOffs), _T("/ignorecase"))) { ignCase = true; parmOffs++; } else if (!_tcsicmp(line.gettoken_str(parmOffs), _T("/noerrors"))) { noErrors = true; parmOffs++; } else if (!_tcsicmp(line.gettoken_str(parmOffs), _T("/file"))) { isFile = true; parmOffs++; } else break; } if (parmOffs+3 > line.getnumtokens()) { ERROR_MSG(_T("!searchparse: not enough parameters\n")); return PS_ERROR; } const TCHAR *source_string = line.gettoken_str(parmOffs++); DefineList *list = 0; if (isFile) { const TCHAR *const filename = source_string; NIStream filestrm; if (!filestrm.OpenFileForReading(filename)) { ERROR_MSG(_T("!searchparse /file: error opening \"%") NPRIs _T("\"\n"), filename); return PS_ERROR; } UINT req_parm = (line.getnumtokens() - parmOffs)/2, fail_parm = 0, linnum = 0; NStreamLineReader lr(filestrm); GrowBuf tmpstr; TCHAR *str = m_templinebuf; for (;;) { tmpstr.resize(0); for (;;) { ++linnum; UINT cch = read_line_helper(lr, str, MAX_LINELENGTH); if (!cch) { if (*str) { tstring lrmsg = lr.GetErrorMessage((UINT)*str, filename, linnum); ERROR_MSG(_T("!searchparse: %") NPRIs, lrmsg.c_str()); return PS_ERROR; } break; // EOF } str[--cch] = _T('\0'); // remove newline const bool endSlash = (cch && _T('\\') == str[cch-1]); if (endSlash) --cch; // don't include the slash character if (tmpstr.getlen() || endSlash) tmpstr.add(str,cch*sizeof(TCHAR)); // if we have valid contents and not ending on slash, then done if (!endSlash && (str[0] || tmpstr.getlen())) break; } if (!str[0] && !tmpstr.getlen()) break; // reached eof TCHAR *thisline = str; if (tmpstr.getlen()) { tmpstr.add(_T("\0"),sizeof(TCHAR)); thisline = (TCHAR*)tmpstr.get(); } UINT linefailparm; DefineList *tlist = searchParseString(thisline, line, parmOffs, ignCase, true, &linefailparm); if (linefailparm > fail_parm) fail_parm = linefailparm; if (tlist && tlist->getnum()) { if (!list || tlist->getnum() > list->getnum()) { delete list; list = tlist, tlist = 0; if ((unsigned)list->getnum() >= req_parm) { fail_parm = -1; // success break; // we found all the tokens, stop parsing the file } } } delete tlist; // parse line } if ((UINT)-1 != fail_parm && !noErrors) { const TCHAR *msgprefix = !fail_parm ? _T("starting ") : _T(""); TCHAR *p = line.gettoken_str(parmOffs + (fail_parm*2)); ERROR_MSG(_T("!searchparse: %") NPRIs _T("string \"%") NPRIs _T("\" not found in file!\n"), msgprefix, p ? p : _T("(null)")); return PS_ERROR; } } else { list = searchParseString(source_string, line, parmOffs, ignCase, noErrors); if (!list && !noErrors) return PS_ERROR; } if (list) // if we got our list, merge them defines in { for (int i=0; i < list->getnum(); i++) { TCHAR *def = list->getname(i), *val = list->getvalue(i); if (def && val) definedlist.set(def,val); } } delete list; return PS_OK; } DefineList *CEXEBuild::searchParseString(const TCHAR *source_string, LineParser&line, int parmOffs, bool ignCase, bool noErrors, UINT*failParam) { const bool allowEmptyFirstTok = true; if (failParam) *failParam = 0; DefineList *ret = NULL; const TCHAR *defout = 0, *src_start = 0, *tok; int toklen = 0, maxlen; for (;;) { tok = line.gettoken_str(parmOffs++); const bool lasttoken = parmOffs > line.getnumtokens(); if (!*tok) tok = 0, maxlen = -1; // No more tokens to search for, save the rest of the string else { toklen = (int) _tcslen(tok); while (*source_string && (ignCase?_tcsnicmp(source_string, tok, toklen):_tcsncmp(source_string, tok, toklen))) source_string++; maxlen = (int)(source_string - src_start); // Length of previous string } if (defout && defout[0]) // We now know the start and length of the previous string, add it to the list { if (!ret) ret = new DefineList(); if (maxlen < 0) ret->add(defout, src_start); else ret->addn(defout, maxlen, src_start); } if (!tok && lasttoken) break; if (!*source_string || (allowEmptyFirstTok ? false : !tok)) // We did not find the requested token! { if (failParam) *failParam = ret ? ret->getnum() : 0; if (noErrors) break; // Caller is OK with a incomplete list of matched strings const TCHAR *msgprefix = src_start ? _T("") : _T("starting "); ERROR_MSG(_T("!searchparse: %") NPRIs _T("string \"%") NPRIs _T("\" not found, aborted search!\n"), msgprefix, tok ? tok : _T("(null)")); delete ret; return NULL; } defout = line.gettoken_str(parmOffs++), src_start = source_string += toklen; } return ret; } int CEXEBuild::pp_verbose(LineParser&line) { for(int argi = 1; argi < line.getnumtokens(); ++argi) { int k = line.gettoken_enum(argi, _T("push\0pop\0")), v, convsucc; if (k < 0) // not push/pop, just set the level { v = line.gettoken_int(argi, &convsucc); if (!convsucc || v < 0 || v > 4 ) { // < 2.47 would reset level to 0 without warning! ERROR_MSG(_T("!verbose: Invalid verbose level\n")); return PS_ERROR; } } else { if (k) // pop { int l = verbose_stack.getlen(); if (l) { v= ((int*)verbose_stack.get())[(l/sizeof(int))-1]; verbose_stack.resize(l-sizeof(int)); } else { warning_fl(DW_PP_VERBOSE_POP_EMPTY_STACK, _T("!verbose: Pop failed, stack is empty")); continue; // Pop failed, should still process the next parameter } } else // push { v = get_verbosity(); verbose_stack.add(&v,sizeof(int)); continue; } } set_verbosity(v); } return PS_OK; } int CEXEBuild::pp_define(LineParser&line) { const TCHAR *cmdnam = line.gettoken_str(0), *define = line.gettoken_str(1); GrowBuf file_buf; TCHAR datebuf[256], mathbuf[256], *value; int dupemode = 0; if (!_tcsicmp(define, _T("/ifndef"))) dupemode = 1; else if (!_tcsicmp(define, _T("/redef"))) dupemode = 2; if (dupemode != 0) { line.eattoken(); define = line.gettoken_str(1); if (dupemode == 1 && definedlist.find(define)) return PS_OK; } if (!_tcsicmp(define, _T("/date")) || !_tcsicmp(define, _T("/utcdate"))) { if (line.getnumtokens() != 4) PRINTHELPEX(cmdnam) const TCHAR *date_type = define; time_t rawtime; time(&rawtime); define = line.gettoken_str(2), value = line.gettoken_str(3); if (!_tcsicmp(date_type, _T("/utcdate"))) rawtime = mktime(gmtime(&rawtime)); datebuf[0] = 0; size_t s = _tcsftime(datebuf, COUNTOF(datebuf), value, localtime(&rawtime)); if (s == 0) datebuf[0] = _T('\0'); else datebuf[max(s, COUNTOF(datebuf)-1)] = _T('\0'); value = datebuf; } else if (!_tcsicmp(define, _T("/file")) || !_tcsicmp(define, _T("/file_noerr"))) { if (line.getnumtokens() != 4) PRINTHELPEX(cmdnam) const TCHAR *const filename = line.gettoken_str(3), *const swit = define; NIStream filestrm; if (!filestrm.OpenFileForReading(filename)) { if (!swit[5]) { // "/file" vs "/file_noerr" ERROR_MSG(_T("!define /file: file not found (\"%") NPRIs _T("\")\n"), filename); return PS_ERROR; } } else { NStreamLineReader lr(filestrm); TCHAR *str = m_templinebuf; for (UINT linnum = 0;;) { ++linnum; UINT cch = read_line_helper(lr, str, MAX_LINELENGTH); if (!cch) { if (*str) { tstring lrmsg = lr.GetErrorMessage((UINT)*str, filename, linnum); ERROR_MSG(_T("!define %") NPRIs _T(": %") NPRIs, swit, lrmsg.c_str()); return PS_ERROR; } break; // EOF } str[--cch] = _T('\0'); // Remove \r or \n, we always append \n if (file_buf.getlen()) file_buf.add(_T("\n"), sizeof(TCHAR)); file_buf.add(str, cch*sizeof(TCHAR)); } } define = line.gettoken_str(2); file_buf.add(_T("\0"), sizeof(TCHAR)); value = (TCHAR *)file_buf.get(); } else if (!_tcsicmp(define, _T("/math"))) { int value1, value2, tc = line.getnumtokens(), onlyval1 = 0; TCHAR *mathop; if (tc != 5 && tc != 6) badmathsyntax: PRINTHELPEX(cmdnam) define = line.gettoken_str(2); value1 = line.gettoken_int(3); mathop = line.gettoken_str(4); value2 = line.gettoken_int(5); value = mathbuf; if (!_tcscmp(mathop,_T("+"))) { _stprintf(value, _T("%d"), value1 + value2); } else if (!_tcscmp(mathop, _T("-"))) { _stprintf(value, _T("%d"), value1 - value2); } else if (!_tcscmp(mathop, _T("*"))) { _stprintf(value, _T("%d"), value1 * value2); } else if (!_tcscmp(mathop, _T("&"))) { _stprintf(value, _T("%d"), value1 & value2); } else if (!_tcscmp(mathop, _T("|"))) { _stprintf(value, _T("%d"), value1 | value2); } else if (!_tcscmp(mathop, _T("^"))) { _stprintf(value, _T("%d"), value1 ^ value2); } else if (!_tcscmp(mathop, _T("~"))) { _stprintf(value, _T("%d"), ~ value1), ++onlyval1; } else if (!_tcscmp(mathop, _T("!"))) { _stprintf(value, _T("%d"), ! value1), ++onlyval1; } else if (!_tcscmp(mathop, _T("&&"))) { _stprintf(value, _T("%d"), value1 && value2); } else if (!_tcscmp(mathop, _T("||"))) { _stprintf(value, _T("%d"), value1 || value2); } else if (!_tcscmp(mathop, _T("<<")) || !_tcscmp(mathop, _T("<<<")) ) { _stprintf(value, _T("%d"), value1 << value2); } else if (!_tcscmp(mathop, _T(">>"))) { _stprintf(value, _T("%d"), (signed int)value1 >> (signed int)value2); } else if (!_tcscmp(mathop, _T(">>>"))) { _stprintf(value, _T("%u"), (unsigned int)value1 >> (unsigned int)value2); } else if (!_tcscmp(mathop, _T("/"))) { if (value2 == 0) { ERROR_MSG(_T("!define /math: division by zero! (\"%i %") NPRIs _T(" %i\")\n"), value1, mathop, value2); return PS_ERROR; } _stprintf(value, _T("%d"), value1 / value2); } else if (!_tcscmp(mathop, _T("%"))) { if (value2 == 0) { ERROR_MSG(_T("!define /math: division by zero! (\"%i %") NPRIs _T(" %i\")\n"),value1,mathop,value2); return PS_ERROR; } _stprintf(value, _T("%d"), value1 % value2); } else goto badmathsyntax; if (tc + onlyval1 != 6) goto badmathsyntax; } else if (!_tcsicmp(define, _T("/intfmt"))) { if (line.getnumtokens() != 5) PRINTHELPEX(cmdnam) define = line.gettoken_str(2); _stprintf(value = mathbuf, line.gettoken_str(3), line.gettoken_int(4)); } else { if (line.getnumtokens() >= 4) PRINTHELPEX(cmdnam) value = line.gettoken_str(2); } if (dupemode == 2) definedlist.del(define); if (definedlist.add(define, value)) { ERROR_MSG(_T("!define: \"%") NPRIs _T("\" already defined!\n"), define); return PS_ERROR; } SCRIPT_MSG(_T("!define: \"%") NPRIs _T("\"=\"%") NPRIs _T("\"\n"), define, value); return PS_OK; } int CEXEBuild::pp_undef(LineParser&line) { UINT noerr = false, stopswitch = false, handled = 0; for (int ti = 1; ti < line.getnumtokens(); ++ti) { const TCHAR *name = line.gettoken_str(ti); if (!stopswitch && !_tcsicmp(name, _T("/noerrors"))) { ++noerr; continue; } stopswitch = ++handled; if (definedlist.del(name) && !noerr) warning_fl(DW_PP_UNDEF_UNDEFINED, _T("!undef: \"%") NPRIs _T("\" not defined!"), name); else SCRIPT_MSG(_T("!undef: \"%") NPRIs _T("\"\n"), name); } if (!handled) { PRINTHELP(); return PS_ERROR; } return PS_OK; } int CEXEBuild::pp_packhdr(LineParser&line) { unsigned int bufOf = false; TCHAR *packname = line.gettoken_str(1); PATH_CONVERT(packname); if (!strtrycpy(build_packname, packname, COUNTOF(build_packname))) ++bufOf; if (!strtrycpy(build_packcmd, line.gettoken_str(2), COUNTOF(build_packcmd))) ++bufOf; SCRIPT_MSG(_T("!packhdr: filename=\"%") NPRIs _T("\", command=\"%") NPRIs _T("\"\n"), build_packname, build_packcmd); return bufOf ? PS_ERROR : PS_OK; } template void slist_append(T&list, T&item) { T prev; for (prev = list; prev && prev->next;) prev = prev->next; (prev ? prev->next : list) = item; } int CEXEBuild::pp_finalize(int which_token, LineParser&line) { #ifndef NSIS_CONFIG_UNINSTALL_SUPPORT if (which_token == TOK_P_UNINSTFINALIZE) { ERROR_MSG(_T("Error: %") NPRIs _T(" specified, %") NPRIns _T(" not defined.\n"), line.gettoken_str(0), "NSIS_CONFIG_UNINSTALL_SUPPORT"); return PS_ERROR; } #endif TCHAR* cmdstr = line.gettoken_str(1); int validparams = false; postbuild_cmd *newcmd = postbuild_cmd::make(cmdstr, line.gettoken_enum(2, _T("<\0>\0<>\0=\0ignore\0")), line.gettoken_int(3, &validparams)); if (line.getnumtokens() == 1+1) newcmd->cmpop = 4, validparams = true; // Just a command, ignore the exit code if (newcmd->cmpop == -1 || !validparams) PRINTHELP(); slist_append(which_token == TOK_P_UNINSTFINALIZE ? postubuild_cmds : postbuild_cmds, newcmd); SCRIPT_MSG(_T("!%") NPRIns _T("finalize: \"%") NPRIs _T("\"\n"), which_token == TOK_P_UNINSTFINALIZE ? "uninst" : "", cmdstr); return PS_OK; } int CEXEBuild::pp_execute(int which_token, LineParser&line) { const TCHAR *cmdname=get_commandtoken_name(which_token); const TCHAR *exec=line.gettoken_str(1), *define=0; TCHAR buf[33]; int comp=line.gettoken_enum(2,_T("<\0>\0<>\0=\0ignore\0")); int validparams=true, ret=-1, cmpv=0, forceutf8=0; switch(line.getnumtokens()-1) { case 1: comp=4; break; case 2: comp=5, validparams=!!*(define=line.gettoken_str(2)); break; case 3: cmpv=line.gettoken_int(3,&validparams); break; default: forceutf8=comp=-1; } if (!validparams || comp == -1) PRINTHELP() tstring compile; if (TOK_P_MAKENSIS == which_token) { extern const TCHAR *g_argv0; compile=_T("\""), compile+=get_executable_path(g_argv0), compile+= _T("\""); compile+= _T(" ") OPT_STR _T("v"), wsprintf(buf,_T("%d"),get_verbosity()), compile+=buf; #if defined(_WIN32) && defined(_UNICODE) // POSIX does not support -OUTPUTCHARSET compile+= _T(" ") OPT_STR _T("OCS UTF8"), forceutf8++; // Force UTF-8 and disable batch-file workaround in RunChildProcessRedirected #endif if (*exec) compile+= _T(" "), compile+=exec; exec=compile.c_str(); } SCRIPT_MSG(_T("%") NPRIs _T(": \"%") NPRIs _T("\"\n"),cmdname,exec); if (preprocessonly) PREPROCESSONLY_BEGINCOMMENT(); #ifdef _WIN32 if (TOK_P_SYSTEMEXEC != which_token) ret=RunChildProcessRedirected(exec, forceutf8 ? true : false); else #endif //~ _WIN32 ret=sane_system(exec), (void)forceutf8; // forceutf8 is not used on POSIX if (comp == 5) { definedlist.set_si32(define,ret); } else if (!check_external_exitcode(ret,comp,cmpv)) { ERROR_MSG(_T("%") NPRIs _T(": returned %d, aborting\n"),cmdname,ret); return PS_ERROR; } if (preprocessonly) PREPROCESSONLY_ENDCOMMENT(); SCRIPT_MSG(_T("%") NPRIs _T(": returned %d\n"),cmdname,ret); return PS_OK; } int CEXEBuild::pp_addincludedir(LineParser&line) { TCHAR *f = line.gettoken_str(1); PATH_CONVERT(f); include_dirs.add(f, 0); return PS_OK; } int CEXEBuild::includeScript(const TCHAR *file, NStreamEncoding&enc) { NIStream incstrm; const bool openok = incstrm.OpenFileForReading(file, enc); if (NStreamEncoding::AUTO == enc.GetCodepage() && build_unicode && !enc.IsUnicodeCodepage(enc.GetPlatformDefaultCodepage()) && enc.GetPlatformDefaultCodepage() == incstrm.StreamEncoding().GetCodepage() ) { incstrm.StreamEncoding().SetCodepage(NStreamEncoding::UTF8); // !include defaults to UTF-8 after "Unicode true" } enc = incstrm.StreamEncoding(); TCHAR bufcpdisp[20]; incstrm.StreamEncoding().GetCPDisplayName(bufcpdisp); SCRIPT_MSG(_T("!include: \"%") NPRIs _T("\" (%") NPRIs _T(")\n"), file, bufcpdisp); if (!openok) { ERROR_MSG(_T("!include: could not open file: \"%") NPRIs _T("\"\n"), file); return PS_ERROR; } if (build_include_depth >= MAX_INCLUDEDEPTH) { ERROR_MSG(_T("!include: too many levels of includes (%d max).\n"),MAX_INCLUDEDEPTH); return PS_ERROR; } build_include_depth++; const int last_linecnt = linecnt; const TCHAR *last_filename = curfilename; curfilename = file, linecnt = 0; NStreamLineReader linereader(incstrm); NStreamLineReader *last_linereader = curlinereader; curlinereader = &linereader; #ifdef NSIS_SUPPORT_STANDARD_PREDEFINES TCHAR *oldfilename = set_file_predefine(curfilename); TCHAR *oldtimestamp = set_timestamp_predefine(curfilename); #endif int r = parseScript(); const int errline = linecnt; #ifdef NSIS_SUPPORT_STANDARD_PREDEFINES restore_file_predefine(oldfilename); restore_timestamp_predefine(oldtimestamp); #endif curfilename = last_filename, linecnt = last_linecnt; curlinereader = last_linereader; build_include_depth--; if (r != PS_EOF && r != PS_OK) { ERROR_MSG(_T("!include: error in script: \"%") NPRIs _T("\" on line %d\n"), file, errline); return PS_ERROR; } SCRIPT_MSG(_T("!include: closed: \"%") NPRIs _T("\"\n"), file); return PS_OK; } int CEXEBuild::pp_include(LineParser&line) { bool required = true; NStreamEncoding enc(NStreamEncoding::AUTO); TCHAR *f; unsigned int toks = line.getnumtokens() - 1, included = 0; for(unsigned int tok = 0; toks;) { f = line.gettoken_str(++tok); if (tok >= toks) break; if (!_tcsicmp(f,_T("/nonfatal"))) required = false; TCHAR buf[9+1]; my_strncpy(buf, f, COUNTOF(buf)); if (!_tcsicmp(buf,_T("/charset="))) { WORD cp = GetEncodingFromString(f+9); if (NStreamEncoding::UNKNOWN == cp) toks = 0; enc.SafeSetCodepage(cp); } } if (!toks || !*f) PRINTHELP(); TCHAR *fc = my_convert(f); tstring dir = get_dir_name(fc), spec = get_file_name(fc), basedir = dir; my_convert_free(fc); path_append_separator(basedir); if (dir == spec) basedir = _T(""), dir = _T("."); // no path, just file name // search working directory boost::scoped_ptr dr( new_dir_reader() ); dr->read(dir); for (dir_reader::iterator files_itr = dr->files().begin(); files_itr != dr->files().end(); files_itr++) { if (!dir_reader::matches(*files_itr, spec)) continue; tstring incfile = basedir + *files_itr; if (includeScript(incfile.c_str(), enc) != PS_OK) return PS_ERROR; else included++; } if (included) return PS_OK; // search include dirs TCHAR *incdir = include_dirs.get(); int incdirs = include_dirs.getnum(); for (int i = 0; i < incdirs; i++, incdir += _tcslen(incdir) + 1) { tstring curincdir(incdir), incfile; if (_T(".") != dir) path_append(curincdir, dir); boost::scoped_ptr dr( new_dir_reader() ); dr->read(curincdir); for (dir_reader::iterator incdir_itr = dr->files().begin(); incdir_itr != dr->files().end(); incdir_itr++) { if (!dir_reader::matches(*incdir_itr, spec)) continue; path_append(incfile = curincdir, *incdir_itr); if (includeScript(incfile.c_str(), enc) != PS_OK) return PS_ERROR; else included++; } if (included) return PS_OK; } if (!included) // nothing found? { if (required) { ERROR_MSG(_T("!include: could not find: \"%") NPRIs _T("\"\n"), f); return PS_ERROR; } else warning_fl(DW_INCLUDE_NONFATAL_NOT_FOUND, _T("!include: could not find: \"%") NPRIs _T("\""), f); } return PS_OK; } int CEXEBuild::pp_cd(LineParser&line) { const TCHAR *dir = line.gettoken_str(1); if (!dir[0] || _tchdir(dir)) { ERROR_MSG(_T("!cd: error changing to: \"%") NPRIs _T("\"\n"), dir); return PS_ERROR; } return PS_OK; }