/*! \file funceval2.cpp * \brief MUX function handlers. * * This file began as a place to put function handlers ported from other * MU* servers, but has also become home to miscellaneous new functions. * These handlers include side-effect functions, comsys / mail functions, * ansi functions, zone functions, encrypt / decrypt, random functions, * some text-formatting and list-munging functions, deprecated stack * functions, regexp functions, etc. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "ast.h" #include #include extern "C" { #include "color_ops.h" } #define PCRE2_CODE_UNIT_WIDTH 8 #include /* --------------------------------------------------------------------------- * fun_grab: a combination of extract() and match(), sortof. We grab the * single element that we match. * * grab(Test:1 Ack:2 Foof:3,*:2) => Ack:2 * grab(Test-1+Ack-2+Foof-3,*o*,+) => Foof-3 * Borrowed from PennMUSH 1.50 */ FUNCTION(fun_grab) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } // Walk the wordstring, until we find the word we want. // LBuf scList = LBuf_Src("fun_grab.list"); UTF8 *s = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep); do { UTF8 *r = split_token(&s, sep); mudstate.wild_invk_ctr = 0; if (quick_wild(fargs[1], r)) { safe_str(r, buff, bufc); return; } } while (s); } FUNCTION(fun_graball) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } SEP osep = sep; if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT)) { return; } bool bFirst = true; LBuf scList = LBuf_Src("fun_graball.list"); UTF8 *s = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep); do { UTF8 *r = split_token(&s, sep); mudstate.wild_invk_ctr = 0; if (quick_wild(fargs[1], r)) { if (!bFirst) { print_sep(osep, buff, bufc); } else { bFirst = false; } safe_str(r, buff, bufc); } } while (s); } /* --------------------------------------------------------------------------- * fun_scramble: randomizes the letters in a string. * Borrowed from PennMUSH 1.50 */ FUNCTION(fun_scramble) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); const unsigned char *p = reinterpret_cast(fargs[0]); size_t slen = strlen(reinterpret_cast(p)); size_t nClusters = co_cluster_count(p, slen); if (nClusters < 2) { safe_str(fargs[0], buff, bufc); return; } // #1110: heap-allocate index / cluster scratch (was LBUF-scale stack). // std::vector indices(nClusters); for (size_t i = 0; i < nClusters; i++) { indices[i] = static_cast(i); } for (size_t i = nClusters - 1; i > 0; i--) { size_t j = static_cast(RandomINT32(0, static_cast(i))); LBUF_OFFSET tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp; } // Output clusters in shuffled order (#2045). // // This used to call co_mid_cluster() once per cluster. Each of those // walks the whole string to find the requested index -- two // co_cluster_advance() calls, each stripping colour into a 32 KB buffer // and re-walking from the start -- so the cost was O(slen) per cluster // and O(slen^2) for the loop. scramble() is CA_PUBLIC, so at LBUF // length any player could occupy the single-threaded server for ~19s, // and max_cmdsecs/lag_limit cannot preempt a call already running. // // One pass builds the whole boundary table instead; emitting is then a // memcpy per cluster. co_cluster_offsets() reproduces co_mid_cluster()'s // ranges exactly, colour included, so the output is unchanged. // std::vector offsets(nClusters + 1); const size_t nFound = co_cluster_offsets(p, slen, offsets.data(), offsets.size()); // co_cluster_count() and co_cluster_offsets() walk the same way, so this // should not fire; clamp rather than trust it, since indices[] was built // from the first and is about to index the second. const size_t nEmit = (nFound < nClusters) ? nFound : nClusters; for (size_t i = 0; i < nEmit; i++) { const size_t iCluster = indices[i]; const size_t off = offsets[iCluster]; size_t cb = offsets[iCluster + 1] - off; size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (cb > nMax) cb = nMax; memcpy(*bufc, p + off, cb); *bufc += cb; } **bufc = '\0'; } /* --------------------------------------------------------------------------- * fun_shuffle: randomize order of words in a list. * Borrowed from PennMUSH 1.50 */ FUNCTION(fun_shuffle) { SEP sep; if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING)) { return; } SEP osep = sep; if (!OPTIONAL_DELIM(3, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT)) { return; } if (1 == sep.n && 1 == osep.n) { // Single-char delimiter: use co_words_count + co_extract. // size_t slen; const unsigned char *p = reinterpret_cast( trim_space_sep_n(fargs[0], sep, &slen)); unsigned char delim = static_cast(sep.str[0]); unsigned char out_delim = static_cast(osep.str[0]); size_t n = co_words_count(p, slen, delim); if (0 == n) { return; } // #1110: heap-allocate index / word scratch (was LBUF-scale stack). // std::vector indices(n); for (size_t i = 0; i < n; i++) { indices[i] = static_cast(i); } for (size_t i = n - 1; i > 0; i--) { size_t j = static_cast(RandomINT32(0, static_cast(i))); LBUF_OFFSET tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp; } // Locate every word ONCE, then emit by index (#2057). // // This used to call co_extract(p, slen, indices[i] + 1, 1, ...) per // word. co_extract addresses word i by scanning forward from the // start of the string counting delimiters, so it is O(slen) whichever // word is wanted -- and asking for every word in turn is O(slen^2). // At LBUF length one shuffle() occupied the single-threaded server for // ~3.4s, and shuffle() is CA_PUBLIC. // // Same defect and same repair as fun_scramble (#2045), which was // re-walking with co_mid_cluster; the multi-char branch below already // worked this way. co_split_words agrees with co_extract about what a // word is -- delimiter compression for space, exact for everything // else, colour codes staying with the word they precede -- and // tests/color_ops' split_extract_parity suite is what proves it rather // than assuming it. // std::vector wstarts(n), wends(n); size_t nFound = co_split_words(p, slen, &delim, 1, wstarts.data(), wends.data(), n); // indices[] was built from co_words_count and is about to index a // table built by co_split_words. tests/color_ops' split_extract_parity // suite asserts the two agree, so nFound == n and this clamp never // bites. // // Note what it does and does not do, because the two are easy to swap. // It bounds the LOOP COUNT, not the index. indices[] is a shuffled // permutation over [0, n), so its first nEmit entries are nEmit // DISTINCT indices -- not the indices [0, nFound). If the two // functions ever did disagree, w >= nFound would still be reached. // // What keeps that in bounds is the ALLOCATION: the tables are sized n // rather than nFound and are value-initialised, so an entry // co_split_words never wrote reads 0 - 0 and emits an empty word. // Size them to nFound -- the obvious tightening once someone notices // they are oversized in exactly the case this clamp is for -- and this // becomes an out-of-bounds read with the clamp still apparently in // place. const size_t nEmit = (nFound < n) ? nFound : n; bool bFirst = true; for (size_t i = 0; i < nEmit; i++) { if (!bFirst) { safe_chr(static_cast(out_delim), buff, bufc); } else { bFirst = false; } const size_t w = indices[i]; size_t nWord = wends[w] - wstarts[w]; size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nWord > nMax) nWord = nMax; memcpy(*bufc, p + wstarts[w], nWord); *bufc += nWord; } **bufc = '\0'; } else { // Multi-char delimiter: use co_split_words + Fisher-Yates. // #1110: heap-allocate word-boundary tables (was ~512 KiB stack). // const unsigned char *pData = reinterpret_cast(fargs[0]); size_t nLen = strlen(reinterpret_cast(fargs[0])); std::unique_ptr wstarts(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr std::unique_ptr wends(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr size_t nWords = co_split_words(pData, nLen, reinterpret_cast(sep.str), sep.n, wstarts.get(), wends.get(), LBUF_SIZE); /* Build index array and shuffle via Fisher-Yates. */ std::vector indices(nWords); for (size_t j = 0; j < nWords; j++) indices[j] = static_cast(j); for (size_t j = nWords; j > 1; j--) { LBUF_OFFSET pick = static_cast( RandomINT32(0, static_cast(j) - 1)); LBUF_OFFSET tmp = indices[j-1]; indices[j-1] = indices[pick]; indices[pick] = tmp; } bool bFirst = true; for (size_t j = 0; j < nWords; j++) { if (bFirst) { bFirst = false; } else { print_sep(osep, buff, bufc); } LBUF_OFFSET w = indices[j]; size_t nb = wends[w] - wstarts[w]; size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nb > nMax) nb = nMax; memcpy(*bufc, pData + wstarts[w], nb); *bufc += nb; } **bufc = '\0'; } } // pickrand -- choose a random item from a list. // FUNCTION(fun_pickrand) { SEP sep; if ( nfargs == 0 || fargs[0][0] == '\0' || !OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING)) { return; } size_t slen; const UTF8 *s = trim_space_sep_n(fargs[0], sep, &slen); if (0 == slen) { return; } if (1 == sep.n) { // Single-char delimiter: use co_words_count + co_extract. // const unsigned char *p = reinterpret_cast(s); unsigned char delim = static_cast(sep.str[0]); size_t n = co_words_count(p, slen, delim); if (0 < n) { size_t w = static_cast(RandomINT32(0, static_cast(n-1))); std::vector out(LBUF_SIZE); size_t nOut = co_extract(out.data(), p, slen, w + 1, 1, delim, delim); size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nOut > nMax) nOut = nMax; memcpy(*bufc, out.data(), nOut); *bufc += nOut; **bufc = '\0'; } } else { // Multi-char delimiter: use co_split_words. // #1110: heap-allocate word-boundary tables (was ~512 KiB stack). // const unsigned char *pData = reinterpret_cast(s); size_t nLen = slen; std::unique_ptr wstarts(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr std::unique_ptr wends(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr size_t nWords = co_split_words(pData, nLen, reinterpret_cast(sep.str), sep.n, wstarts.get(), wends.get(), LBUF_SIZE); if (nWords > 0) { LBUF_OFFSET w = static_cast( RandomINT32(0, static_cast(nWords) - 1)); size_t nb = wends[w] - wstarts[w]; size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nb > nMax) nb = nMax; memcpy(*bufc, pData + wstarts[w], nb); *bufc += nb; **bufc = '\0'; } } } // sortby() // typedef struct { UTF8 *buff;; dbref executor; dbref caller; dbref enactor; int aflags; } ucomp_context; static int u_comp(ucomp_context *pctx, const void *s1, const void *s2) { if ( mudstate.func_invk_ctr > mudconf.func_invk_lim || mudstate.func_nest_lev > mudconf.func_nest_lim || alarm_clock.alarmed) { return 0; } if ((pctx->aflags & AF_NOEVAL) || NoEval(pctx->executor)) { return 0; } const UTF8 *elems[2] = { T(s1), T(s2) }; LBuf tbuf = LBuf_Src("u_comp"); mux_strncpy(tbuf, pctx->buff, LBUF_SIZE-1); LBuf result = LBuf_Src("u_comp"); UTF8 *bp = result; // Use ast_exec (not mux_exec) to bypass the JIT compiler. // The JIT does not support dynamically-provided cargs — it would // use the outer caller's cargs instead of the comparison elements. // ast_exec(tbuf, LBUF_SIZE-1, result, &bp, pctx->executor, pctx->caller, pctx->enactor, AttrTrace(pctx->aflags, EV_STRIP_CURLY|EV_FCHECK|EV_EVAL), elems, 2); *bp = '\0'; int64_t n = mux_atoi64(result); return n; } inline int ucomp_bsearch(ucomp_context* pctx, void* arr[], int sz, void* ndl) { int l = 0; int r = sz; while (l < r) { int m = (l + r) >> 1; if (m == sz) { return sz; } if (u_comp(pctx, ndl, arr[m]) < 0) { r = m; } else { l = m + 1; } } return l; } static void mincomp_sort(ucomp_context* pctx, void* arr[], int sz) { if (sz <= 1) { return; } for (int i = 1; i < sz; i++) { void* t = arr[i]; int n = ucomp_bsearch(pctx, arr, i, t); for (int j = i; j > n; j--) { arr[j] = arr[j-1]; } arr[n] = t; } } FUNCTION(fun_sortby) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } SEP osep = sep; if (!OPTIONAL_DELIM(4, osep, DELIM_NULL|DELIM_CRLF|DELIM_STRING|DELIM_INIT)) { return; } UTF8 *atext; dbref thing; dbref aowner; int aflags; if (!parse_and_get_attrib(executor, fargs, &atext, &thing, &aowner, &aflags, buff, bufc)) { return; } ucomp_context ctx; ctx.buff = alloc_lbuf("fun_sortby.ctx"); mux_strncpy(ctx.buff, atext, LBUF_SIZE-1); ctx.executor = thing; ctx.caller = executor; ctx.enactor = enactor; ctx.aflags = aflags; LBuf list = LBuf_Src("fun_sortby"); list_copy_for_split(list, fargs[1]); std::unique_ptr ptrs(new UTF8*[LBUF_SIZE / 2]); // uninit (#2145): past-count reads are UB now, not nullptr const int nptrs = list2arr(ptrs.get(), LBUF_SIZE / 2, list, sep); if (nptrs > 1) { mincomp_sort(&ctx, reinterpret_cast(ptrs.get()), nptrs); } arr2list(ptrs.get(), nptrs, buff, bufc, osep); free_lbuf(ctx.buff); free_lbuf(atext); } // fun_last: Returns last word in a string. Borrowed from TinyMUSH 2.2. // FUNCTION(fun_last) { // If we are passed an empty arglist return a null string. // if (nfargs == 0) { return; } SEP sep; if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING)) { return; } if (1 == sep.n) { // Single-char delimiter: use co_last. // Trim leading/trailing delimiters for space (MUX compresses spaces). // size_t slen; const UTF8 *bp = trim_space_sep_n(fargs[0], sep, &slen); std::vector out(LBUF_SIZE); size_t nOut = co_last(out.data(), reinterpret_cast(bp), slen, static_cast(sep.str[0])); size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nOut > nMax) nOut = nMax; memcpy(*bufc, out.data(), nOut); *bufc += nOut; **bufc = '\0'; } else { // Multi-char delimiter: use co_split_words. // #1110: heap-allocate word-boundary tables (was ~512 KiB stack). // const unsigned char *pData = reinterpret_cast(fargs[0]); size_t nLen = strlen(reinterpret_cast(fargs[0])); std::unique_ptr ws(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr std::unique_ptr we(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr size_t nWords = co_split_words(pData, nLen, reinterpret_cast(sep.str), sep.n, ws.get(), we.get(), LBUF_SIZE); if (nWords > 0) { size_t nb = we[nWords-1] - ws[nWords-1]; size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nb > nMax) nb = nMax; memcpy(*bufc, pData + ws[nWords-1], nb); *bufc += nb; **bufc = '\0'; } } } /* * --------------------------------------------------------------------------- * fun_lrest: Returns all but the last word in a string. */ FUNCTION(fun_lrest) { // If we are passed an empty arglist return a null string. // if (nfargs == 0) { return; } SEP sep; if (!OPTIONAL_DELIM(2, sep, DELIM_DFLT|DELIM_STRING)) { return; } if (1 == sep.n) { // Single-char delimiter: use co_extract for words 1..N-1. // size_t slen; const unsigned char *p = reinterpret_cast( trim_space_sep_n(fargs[0], sep, &slen)); unsigned char delim = static_cast(sep.str[0]); size_t nWords = co_words_count(p, slen, delim); if (nWords > 1) { std::vector out(LBUF_SIZE); size_t nOut = co_extract(out.data(), p, slen, 1, nWords - 1, delim, delim); size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nOut > nMax) nOut = nMax; memcpy(*bufc, out.data(), nOut); *bufc += nOut; **bufc = '\0'; } } else { // Multi-char delimiter: use co_split_words. // #1110: heap-allocate word-boundary tables (was ~512 KiB stack). // const unsigned char *pData = reinterpret_cast(fargs[0]); size_t nLen = strlen(reinterpret_cast(fargs[0])); std::unique_ptr wstarts(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr std::unique_ptr wends(new size_t[LBUF_SIZE]); // uninit (#2145): past-count reads are UB now, not nullptr size_t nWords = co_split_words(pData, nLen, reinterpret_cast(sep.str), sep.n, wstarts.get(), wends.get(), LBUF_SIZE); if (nWords > 1) { size_t nb = wends[0] - wstarts[0]; size_t nMax = buff + (LBUF_SIZE-1) - *bufc; if (nb > nMax) nb = nMax; memcpy(*bufc, pData + wstarts[0], nb); *bufc += nb; for (size_t i = 1; i < nWords - 1; i++) { print_sep(sep, buff, bufc); nb = wends[i] - wstarts[i]; nMax = buff + (LBUF_SIZE-1) - *bufc; if (nb > nMax) nb = nMax; memcpy(*bufc, pData + wstarts[i], nb); *bufc += nb; } **bufc = '\0'; } } } // For an named object, or the executor, find the last created object // (optionally qualified by type). // FUNCTION(fun_lastcreate) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(ncargs); UNUSED_PARAMETER(cargs); // Determine the target by name, or use the executor if no name is given. // dbref target = executor; if ( 0 < nfargs && '\0' != fargs[0][0]) { target = match_thing_quiet(executor, fargs[0]); if (!Good_obj(target)) { safe_nomatch(buff, bufc); return; } // Verify that the executor has access to the named object. Notice // that an executor always has access to itself. // if ( !WizRoy(executor) && !Controls(executor, target)) { safe_noperm(buff, bufc); return; } } // If a type is given, qualify the result. // int iObjectPosition = 4; if ( 1 < nfargs && '\0' != fargs[1][0]) { switch (fargs[1][0]) { case 'R': case 'r': iObjectPosition = 0; break; case 'T': case 't': iObjectPosition = 1; break; case 'E': case 'e': iObjectPosition = 2; break; case 'P': case 'p': iObjectPosition = 3; break; } } int aowner; int aflags; // atr_get always allocates; free on every path (#1064). LBuf newobject_string = LBuf_Adopt(atr_get("fun_lastcreate.2998", target, A_NEWOBJS, &aowner, &aflags)); if ('\0' == newobject_string[0]) { safe_str(S_("#-1"), buff, bufc); return; } string_token st(newobject_string, T(" ")); int i; UTF8* ptr; for ( ptr = st.parse(), i = 0; nullptr != ptr && i < 5; ptr = st.parse(), i++) { if (i == iObjectPosition) { dbref jLastCreated = mux_atoi64(ptr); safe_tprintf_str(buff, bufc, T("#%d"), jLastCreated); break; } } } // Borrowed from TinyMUSH 2.2 // FUNCTION(fun_matchall) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } int wcount; UTF8 *r, *s, *old, tbuf[I32BUF_SIZE]; old = *bufc; // Check each word individually, returning the word number of all that // match. If none match, return 0. // wcount = 1; LBuf scList = LBuf_Src("fun_matchall.list"); s = trim_space_sep(list_copy_for_split(scList, fargs[0]), sep); do { r = split_token(&s, sep); mudstate.wild_invk_ctr = 0; if (quick_wild(fargs[1], r)) { mux_ltoa(wcount, tbuf); if (old != *bufc) { safe_chr(' ', buff, bufc); } safe_str(tbuf, buff, bufc); } wcount++; } while (s); if (*bufc == old) { safe_chr('0', buff, bufc); } } // --------------------------------------------------------------------------- // fun_ports: Returns a list of ports for a user. // Borrowed from TinyMUSH 2.2 // FUNCTION(fun_ports) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref target = lookup_player(executor, fargs[0], true); if (Good_obj(target)) { if (target == executor || Wizard(executor)) { if (Connected(target)) { make_portlist(executor, target, buff, bufc); } } else { safe_noperm(buff, bufc); } } else { safe_nomatch(buff, bufc); } } /* --------------------------------------------------------------------------- * fun_mix: Like map, but operates on up to ten lists simultaneously, passing * the elements as %0 - %10. * Borrowed from PennMUSH 1.50, upgraded by RhostMUSH. */ FUNCTION(fun_mix) { // Check to see if we have an appropriate number of arguments. // If there are more than three arguments, the last argument is // ALWAYS assumed to be a delimiter. // SEP sep; int lastn; if (nfargs < 4) { sep.n = 1; sep.str[0] = ' '; sep.str[1] = '\0'; lastn = nfargs - 1; } else if (!OPTIONAL_DELIM(nfargs, sep, DELIM_DFLT|DELIM_STRING)) { return; } else { lastn = nfargs - 2; } // Get the attribute. Check the permissions. // dbref thing; UTF8 *atext; dbref aowner; int aflags; if (!parse_and_get_attrib(executor, fargs, &atext, &thing, &aowner, &aflags, buff, bufc)) { return; } // Process the lists, one element at a time. // int i; int nwords = 0; UTF8 *cp[NUM_ENV_VARS]; FargVec lists(fargs + 1, lastn); for (i = 0; i < lastn; i++) { cp[i] = trim_space_sep(lists[i], sep); int twords = countwords(cp[i], sep); if (nwords < twords) { nwords = twords; } } const UTF8 *os[NUM_ENV_VARS]; bool bFirst = true; for ( int wc = 0; wc < nwords && mudstate.func_invk_ctr < mudconf.func_invk_lim && !alarm_clock.alarmed; wc++) { if (!bFirst) { print_sep(sep, buff, bufc); } else { bFirst = false; } for (i = 0; i < lastn; i++) { os[i] = split_token(&cp[i], sep); if (nullptr == os[i]) { os[i] = T(""); } } mux_exec(atext, LBUF_SIZE-1, buff, bufc, thing, executor, enactor, AttrTrace(aflags, EV_STRIP_CURLY|EV_FCHECK|EV_EVAL), os, lastn); } free_lbuf(atext); } /* --------------------------------------------------------------------------- * fun_step: A little like a fusion of iter() and mix(), it takes elements * of a list X at a time and passes them into a single function as %0, %1, * etc. step(,,,,) */ FUNCTION(fun_step) { int i; SEP isep; if (!OPTIONAL_DELIM(4, isep, DELIM_DFLT|DELIM_STRING)) { return; } SEP osep = isep; if (!OPTIONAL_DELIM(5, osep, DELIM_NULL|DELIM_CRLF|DELIM_INIT|DELIM_STRING)) { return; } int64_t step_size = mux_atoi64(fargs[2]); if ( step_size < 1 || NUM_ENV_VARS < step_size) { notify(executor, M_("Illegal step size.")); return; } // Get attribute. Check permissions. // UTF8 *atext; dbref thing; dbref aowner; int aflags; if (!parse_and_get_attrib(executor, fargs, &atext, &thing, &aowner, &aflags, buff, bufc)) { return; } LBuf scList = LBuf_Src("fun_step.list"); UTF8 *cp = trim_space_sep(list_copy_for_split(scList, fargs[1]), isep); const UTF8 *os[NUM_ENV_VARS]; bool bFirst = true; while ( cp && mudstate.func_invk_ctr < mudconf.func_invk_lim && !alarm_clock.alarmed) { if (!bFirst) { print_sep(osep, buff, bufc); } else { bFirst = false; } for (i = 0; cp && i < step_size; i++) { os[i] = split_token(&cp, isep); } // Evaluate the attribute as the object that owns it (executor = // thing), matching map()/mix()/foreach(); previously it ran as the // caller, so %!/me and permissions resolved to the wrong object. // mux_exec(atext, LBUF_SIZE-1, buff, bufc, thing, executor, enactor, AttrTrace(aflags, EV_STRIP_CURLY|EV_FCHECK|EV_EVAL), os, i); } free_lbuf(atext); } /* --------------------------------------------------------------------------- * fun_foreach: like map(), but it operates on a string, rather than on a list, * calling a user-defined function for each character in the string. * No delimiter is inserted between the results. * Borrowed from TinyMUSH 2.2 */ FUNCTION(fun_foreach) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); if ( nfargs != 2 && nfargs != 4) { safe_str(S_("#-1 FUNCTION (FOREACH) EXPECTS 2 OR 4 ARGUMENTS"), buff, bufc); return; } UTF8 *atext; dbref thing; dbref aowner; int aflags; if (!parse_and_get_attrib(executor, fargs, &atext, &thing, &aowner, &aflags, buff, bufc)) { return; } // Trim leading/trailing whitespace directly on the buffer. // const UTF8 *pStr = fargs[1]; while (mux_isspace(*pStr)) pStr++; size_t nStr = strlen(reinterpret_cast(pStr)); while (nStr > 0 && mux_isspace(pStr[nStr - 1])) nStr--; UTF8 cbuf[5] = {'\0', '\0', '\0', '\0', '\0'}; const UTF8 *bp = cbuf; size_t i = 0; if ( 4 == nfargs && '\0' != fargs[2][0] && '\0' != fargs[3][0]) { bool flag = false; UTF8 prev = '\0'; while ( i < nStr && mudstate.func_invk_ctr < mudconf.func_invk_lim && !alarm_clock.alarmed) { // Determine code point length from UTF-8 lead byte. // unsigned char ch = pStr[i]; size_t nBytes; if (ch < 0x80) nBytes = 1; else if (ch < 0xE0) nBytes = 2; else if (ch < 0xF0) nBytes = 3; else nBytes = 4; if (i + nBytes > nStr) break; memcpy(cbuf, pStr + i, nBytes); cbuf[nBytes] = '\0'; i += nBytes; if (flag) { if ( cbuf[0] == *fargs[3] && prev != '\\' && prev != '%') { flag = false; continue; } } else { if ( cbuf[0] == *fargs[2] && prev != '\\' && prev != '%') { flag = true; continue; } else { safe_copy_buf(cbuf, nBytes, buff, bufc); continue; } } mux_exec(atext, LBUF_SIZE-1, buff, bufc, thing, executor, enactor, AttrTrace(aflags, EV_STRIP_CURLY|EV_FCHECK|EV_EVAL), &bp, 1); prev = cbuf[0]; } } else { while ( i < nStr && mudstate.func_invk_ctr < mudconf.func_invk_lim && !alarm_clock.alarmed) { unsigned char ch = pStr[i]; size_t nBytes; if (ch < 0x80) nBytes = 1; else if (ch < 0xE0) nBytes = 2; else if (ch < 0xF0) nBytes = 3; else nBytes = 4; if (i + nBytes > nStr) break; memcpy(cbuf, pStr + i, nBytes); cbuf[nBytes] = '\0'; mux_exec(atext, LBUF_SIZE-1, buff, bufc, thing, executor, enactor, AttrTrace(aflags, EV_STRIP_CURLY|EV_FCHECK|EV_EVAL), &bp, 1); i += nBytes; } } free_lbuf(atext); } /* --------------------------------------------------------------------------- * fun_munge: combines two lists in an arbitrary manner. * Borrowed from TinyMUSH 2.2 * Hash table rewrite by Ian and Alierak. */ #if LBUF_SIZE < UINT16_MAX typedef uint16_t NHASH; #define ShiftHash(x) (x) >>= 16 #else typedef uint32_t NHASH; #define ShiftHash(x) #endif typedef struct munge_htab_rec { NHASH nHash; // partial hash value of this record's key LBUF_OFFSET iNext; // index of next record in this hash chain LBUF_OFFSET nKeyOffset; // offset of key string (incremented by 1), // zero indicates empty record. LBUF_OFFSET nValueOffset; // offset of value string } munge_htab_rec; FUNCTION(fun_munge) { SEP sep; if (!OPTIONAL_DELIM(4, sep, DELIM_DFLT|DELIM_STRING)) { return; } // Find our object and attribute. // UTF8 *atext; dbref thing; dbref aowner; int aflags; if (!parse_and_get_attrib(executor, fargs, &atext, &thing, &aowner, &aflags, buff, bufc)) { return; } // Prepare data structures for a hash table that will map // elements of list1 to corresponding elements of list2. // int nWords = countwords(fargs[1], sep); if (0 == nWords) { free_lbuf(atext); return; } std::vector htab(1 + 2 * nWords); std::vector tails(1 + nWords); int iNext = 1 + nWords; // first unused hash slot past starting area // Chop up the lists, converting them into a hash table that // maps elements of list1 to corresponding elements of list2. // The tokenizing walks and the offset-based lookups below both use // private copies of the two lists (#2136). // LBuf keys = LBuf_Src("fun_munge.keys"); UTF8 *p1 = trim_space_sep(list_copy_for_split(keys, fargs[1]), sep); LBuf vals = LBuf_Src("fun_munge.vals"); UTF8 *p2 = trim_space_sep(list_copy_for_split(vals, fargs[2]), sep); UTF8 *pKey, *pValue; for (pKey = split_token(&p1, sep), pValue = split_token(&p2, sep); nullptr != pKey && nullptr != pValue; pKey = split_token(&p1, sep), pValue = split_token(&p2, sep)) { uint32_t nHash = munge_hash(pKey); int nHashSlot = 1 + (nHash % nWords); ShiftHash(nHash); if (0 != tails[nHashSlot]) { // there is already a hash chain starting in this slot, // insert at the tail to preserve order. nHashSlot = tails[nHashSlot] = htab[tails[nHashSlot]].iNext = static_cast(iNext++); } else { tails[nHashSlot] = static_cast(nHashSlot); } htab[nHashSlot].nHash = static_cast(nHash); htab[nHashSlot].nKeyOffset = static_cast(1 + (pKey - keys.get())); htab[nHashSlot].nValueOffset = static_cast(pValue - vals.get()); } if ( nullptr != pKey || nullptr != pValue) { safe_str(S_("#-1 LISTS MUST BE OF EQUAL SIZE"), buff, bufc); free_lbuf(atext); return; } // Call the u-function with the first list as %0. // LBuf rlist = LBuf_Src("fun_munge"); UTF8 *bp; const UTF8 *uargs[2]; // The walks above tokenized private copies, so fargs[1] is still // pristine and can be %0 directly (this replaces #2157's copy). // bp = rlist; uargs[0] = fargs[1]; uargs[1] = sep.str; mux_exec(atext, LBUF_SIZE-1, rlist, &bp, executor, caller, enactor, AttrTrace(aflags, EV_STRIP_CURLY|EV_FCHECK|EV_EVAL), uargs, 2); *bp = '\0'; free_lbuf(atext); // Now that we have our result, put it back into array form. // Translate its elements according to the mappings in our hash table. // bool bFirst = true; bp = trim_space_sep(rlist, sep); if ('\0' != *bp) { UTF8 *result; for (result = split_token(&bp, sep); nullptr != result; result = split_token(&bp, sep)) { uint32_t nHash = munge_hash(result); int nHashSlot = 1 + (nHash % nWords); ShiftHash(nHash); while ( 0 != htab[nHashSlot].nKeyOffset && ( nHash != htab[nHashSlot].nHash || 0 != strcmp(reinterpret_cast(result), reinterpret_cast(keys.get() + htab[nHashSlot].nKeyOffset - 1)))) { nHashSlot = htab[nHashSlot].iNext; } if (0 != htab[nHashSlot].nKeyOffset) { if (!bFirst) { print_sep(sep, buff, bufc); } else { bFirst = false; } safe_str(vals.get() + htab[nHashSlot].nValueOffset, buff, bufc); // delete from the hash table memcpy(&htab[nHashSlot], &htab[htab[nHashSlot].iNext], sizeof(munge_htab_rec)); } } } } FUNCTION(fun_die) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); int64_t n = mux_atoi64(fargs[0]); int64_t die = mux_atoi64(fargs[1]); if ( n == 0 || die <= 0) { safe_chr('0', buff, bufc); return; } if ( n < 1 || LBUF_SIZE <= n) { safe_range(buff, bufc); return; } if ( 3 <= nfargs && isTRUE(mux_atoi64(fargs[2]))) { ITL pContext; ItemToList_Init(&pContext, buff, bufc); for (int count = 0; count < n; count++) { if (!ItemToList_AddInteger(&pContext, RandomINT32(1, die))) { break; } } ItemToList_Final(&pContext); return; } int total = 0; for (int count = 0; count < n; count++) { total += RandomINT32(1, die); } safe_ltoa(total, buff, bufc); } FUNCTION(fun_lrand) { SEP sep; if (!OPTIONAL_DELIM(4, sep, DELIM_NULL|DELIM_CRLF|DELIM_STRING)) { return; } int64_t n_times = mux_atoi64(fargs[2]); if (n_times < 1) { return; } if (n_times > LBUF_SIZE) { n_times = LBUF_SIZE; } int32_t iLower = mux_atoi64(fargs[0]); int32_t iUpper = mux_atoi64(fargs[1]); if (iLower <= iUpper) { for (int i = 0; i < n_times-1; i++) { int32_t val = RandomINT32(iLower, iUpper); safe_ltoa(val, buff, bufc); print_sep(sep, buff, bufc); } int32_t val = RandomINT32(iLower, iUpper); safe_ltoa(val, buff, bufc); } } // Borrowed from PennMUSH 1.50 // FUNCTION(fun_lit) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Just returns the argument, literally. // safe_str(fargs[0], buff, bufc); } FUNCTION(fun_dumping) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(fargs); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); #if !defined(HAVE_WORKING_FORK) safe_chr('0', buff, bufc); #else // HAVE_WORKING_FORK safe_bool(mudstate.dumping, buff, bufc); #endif // HAVE_WORKING_FORK } static size_t mux_Pack0(int64_t val, int iRadix, UTF8 symbols[], UTF8 *buf) { UTF8 *p = buf; // Handle sign. // if (val < 0) { *p++ = '-'; val = -val; } UTF8 *q = p; while (val > iRadix-1) { int64_t iDiv = val / iRadix; int64_t iTerm = val - iDiv * iRadix; val = iDiv; *p++ = symbols[iTerm]; } *p++ = symbols[val]; size_t nLength = p - buf; *p-- = '\0'; // The digits are in reverse order with a possible leading '-' // if the value was negative. q points to the first digit, // and p points to the last digit. // while (q < p) { // Swap characters are *p and *q // char temp = *p; *p = *q; *q = temp; // Move p and first digit towards the middle. // --p; ++q; // Stop when we reach or pass the middle. // } return nLength; } // The following table contains 64 symbols, so this supports -a- // radix-64 encoding. It is not however 'unix-to-unix' encoding. // All of the following characters are valid for an attribute // name, but not for the first character of an attribute name. // static UTF8 aRadix64[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@$"; // These sets are for compatibility with PennMUSH. // static UTF8 aRadixPenn36[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static UTF8 aRadixPenn64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; static bool mux_Unpack(const UTF8 *p, int64_t &val, int iRadixFrom, int iRadixTo, bool fPennBehavior) { if (10 == iRadixFrom) { val = mux_atoi64(p); return true; } bool fPlusSlash = false; bool fNegativeNumbers = true; UTF8 *symbols = aRadix64; if (fPennBehavior) { fNegativeNumbers = iRadixFrom < 63 && iRadixTo < 63; if (iRadixFrom <= 36) { symbols = aRadixPenn36; } else if (iRadixFrom < 64) { symbols = aRadixPenn64; } else { fPlusSlash = true; symbols = aRadixPenn64; } } // Build Table of valid characters. // UTF8 MatchTable[256]; memset(MatchTable, 0, sizeof(MatchTable)); for (int i = 0; i < iRadixFrom; i++) { MatchTable[static_cast(symbols[i])] = static_cast(i + 1); } if (fPlusSlash) { MatchTable[static_cast('+')] = static_cast(62 + 1); MatchTable[static_cast('/')] = static_cast(63 + 1); } // Leading whitespace // while (mux_isspace(*p)) { p++; } // Possible sign // int LeadingCharacter = '\0'; if (fNegativeNumbers) { LeadingCharacter = *p; if ( '-' == LeadingCharacter || '+' == LeadingCharacter) { p++; } } // Validate that string contains only characters from the subset of permitted characters. // int c; const UTF8 *q = p; while ( '\0' != *q && !mux_isspace(*q)) { c = *q; if (0 == MatchTable[static_cast(c)]) { return false; } q++; } // Verify trailing spaces. // while ('\0' != *q) { c = *q; if (!mux_isspace(c)) { return false; } q++; } // Convert symbols // val = 0; c = *p++; for (int iValue = MatchTable[static_cast(c)]; iValue; iValue = MatchTable[static_cast(c)]) { val = iRadixFrom * val + iValue - 1; c = *p++; } // Interpret sign // if ('-' == LeadingCharacter) { val = -val; } return true; } static void mux_Pack1(int64_t val, int iRadixTo, bool fPennBehavior, UTF8 *buff, UTF8 **bufc) { if (10 == iRadixTo) { safe_i64toa(val, buff, bufc); } else if ( val < 0 && 63 <= iRadixTo) { safe_str(S_("#-1 NEGATIVE NUMBER IS NOT REPRESENTABLE IN OUTPUT RADIX"), buff, bufc); } else { UTF8 *symbols = aRadix64; if (fPennBehavior) { if (iRadixTo <= 36) { symbols = aRadixPenn36; } else { symbols = aRadixPenn64; } } UTF8 TempBuffer[76]; // 1 '-', 63 binary digits, 1 '\0', 11 for safety. size_t nLength = mux_Pack0(val, iRadixTo, symbols, TempBuffer); safe_copy_buf(TempBuffer, nLength, buff, bufc); } } FUNCTION(fun_unpack) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Validate radix if present. // int iRadix = 64; if (2 <= nfargs) { if ( !is_integer(fargs[1], nullptr) || (iRadix = mux_atoi64(fargs[1])) < 2 || 64 < iRadix) { safe_str(S_("#-1 RADIX MUST BE A NUMBER BETWEEN 2 and 64"), buff, bufc); return; } } bool fPennBehavior = false; if (3 <= nfargs) { fPennBehavior = xlate(fargs[2]); } int64_t val; if (!mux_Unpack(fargs[0], val, iRadix, 10, fPennBehavior)) { safe_str(S_("#-1 NUMBER IS NOT VALID FOR INPUT RADIX"), buff, bufc); } else { safe_i64toa(val, buff, bufc); } } FUNCTION(fun_pack) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Validate the arguments are numeric. // if ( !is_integer(fargs[0], nullptr) || (2 <= nfargs && !is_integer(fargs[1], nullptr))) { safe_str(S_("#-1 ARGUMENTS MUST BE NUMBERS"), buff, bufc); return; } int64_t val = mux_atoi64(fargs[0]); // Validate that the radix is between 2 and 64. // int iRadix = 64; if (2 <= nfargs) { iRadix = mux_atoi64(fargs[1]); if ( iRadix < 2 || 64 < iRadix) { safe_str(S_("#-1 RADIX MUST BE A NUMBER BETWEEN 2 and 64"), buff, bufc); return; } } bool fPennBehavior = false; if (3 <= nfargs) { fPennBehavior = xlate(fargs[2]); } mux_Pack1(val, iRadix, fPennBehavior, buff, bufc); } FUNCTION(fun_baseconv) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Validate that input and output radix are integers. // if ( !is_integer(fargs[1], nullptr) || !is_integer(fargs[2], nullptr)) { safe_str(S_("#-1 ARGUMENTS MUST BE NUMBERS"), buff, bufc); return; } int64_t iRadixFrom = mux_atoi64(fargs[1]); if ( iRadixFrom < 2 || 64 < iRadixFrom) { safe_str(S_("#-1 INPUT RADIX MUST BE A NUMBER BETWEEN 2 and 64"), buff, bufc); return; } int64_t iRadixTo = mux_atoi64(fargs[2]); if ( iRadixTo < 2 || 64 < iRadixTo) { safe_str(S_("#-1 OUTPUT RADIX MUST BE A NUMBER BETWEEN 2 and 64"), buff, bufc); return; } int64_t val; if (!mux_Unpack(fargs[0], val, iRadixFrom, iRadixTo, true)) { safe_str(S_("#-1 NUMBER IS NOT VALID FOR INPUT RADIX"), buff, bufc); } else { mux_Pack1(val, iRadixTo, true, buff, bufc); } } FUNCTION(fun_strcat) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); int i; for (i = 0; i < nfargs; i++) { safe_str(fargs[i], buff, bufc); } } // grep() and grepi() code borrowed from PennMUSH 1.50 // static UTF8 *grep_util(dbref player, dbref thing, const UTF8 *pattern, const UTF8 *lookfor, size_t len, bool insensitive) { // Returns a list of attributes which match on // whose contents have . // olist_push(); find_wild_attrs(player, thing, pattern, false, false, false); BMH_State bmhs; if (insensitive) { BMH_PrepareI(&bmhs, len, lookfor); } else { BMH_Prepare(&bmhs, len, lookfor); } UTF8 *tbuf1 = alloc_lbuf("grep_util"); UTF8 *bp = tbuf1; dbref aowner; int aflags; for (int ca = olist_first(); ca != NOTHING && !alarm_clock.alarmed; ca = olist_next()) { size_t nText; UTF8 *attrib = atr_get_LEN(thing, ca, &aowner, &aflags, &nText); size_t i; bool bSucceeded; if (insensitive) { bSucceeded = BMH_ExecuteI(&bmhs, &i, len, lookfor, nText, attrib); } else { bSucceeded = BMH_Execute(&bmhs, &i, len, lookfor, nText, attrib); } if (bSucceeded) { if (bp != tbuf1) { safe_chr(' ', tbuf1, &bp); } ATTR *ap = atr_num(ca); const UTF8 *pName = T("(WARNING: Bad Attribute Number)"); if (ap) { pName = ap->name; } safe_str(pName, tbuf1, &bp); } free_lbuf(attrib); } *bp = '\0'; olist_pop(); return tbuf1; } static void grep_handler(UTF8 *buff, UTF8 **bufc, dbref executor, const UTF8 * const fargs[], bool bCaseInsens) { dbref it = match_thing_quiet(executor, fargs[0]); if (!Good_obj(it)) { safe_match_result(it, buff, bufc); return; } if (!Examinable(executor, it)) { safe_noperm(buff, bufc); return; } // Make sure there's an attribute and a pattern // if (!fargs[1] || !*fargs[1]) { safe_str(S_("#-1 NO SUCH ATTRIBUTE"), buff, bufc); return; } if (!fargs[2] || !*fargs[2]) { safe_str(S_("#-1 INVALID GREP PATTERN"), buff, bufc); return; } UTF8 *tp = grep_util(executor, it, fargs[1], fargs[2], strlen(reinterpret_cast(fargs[2])), bCaseInsens); safe_str(tp, buff, bufc); free_lbuf(tp); } FUNCTION(fun_grep) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); grep_handler(buff, bufc, executor, fargs, false); } FUNCTION(fun_grepi) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); grep_handler(buff, bufc, executor, fargs, true); } // regrep()/regrepi() — regex-based attribute value searching. // Like grep()/grepi() but uses PCRE2 regular expressions instead of // wildcard patterns. // static UTF8 *regrep_util(dbref player, dbref thing, const UTF8 *pattern, const UTF8 *lookfor, bool insensitive) { olist_push(); find_wild_attrs(player, thing, pattern, false, false, false); // Compile the regex. // int errcode; PCRE2_SIZE erroffset; uint32_t options = PCRE2_UTF; if (insensitive) options |= PCRE2_CASELESS; pcre2_code *re = pcre2_compile_8(lookfor, PCRE2_ZERO_TERMINATED, options, &errcode, &erroffset, nullptr); if (!re) { olist_pop(); UTF8 *tbuf1 = alloc_lbuf("regrep_util"); mux_strncpy(tbuf1, S_("#-1 REGEXP ERROR"), LBUF_SIZE-1); return tbuf1; } pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); // #1113: null-check match_data like regmatch/regrab/regedit. // if (!match_data) { pcre2_code_free(re); olist_pop(); UTF8 *tbuf1 = alloc_lbuf("regrep_util"); mux_strncpy(tbuf1, S_("#-1 REGEXP MATCH DATA ERROR"), LBUF_SIZE-1); return tbuf1; } UTF8 *tbuf1 = alloc_lbuf("regrep_util"); UTF8 *bp = tbuf1; dbref aowner; int aflags; for (int ca = olist_first(); ca != NOTHING && !alarm_clock.alarmed; ca = olist_next()) { size_t nText; UTF8 *attrib = atr_get_LEN(thing, ca, &aowner, &aflags, &nText); int matches = pcre2_match(re, attrib, nText, 0, 0, match_data, nullptr); if (matches >= 0) { if (bp != tbuf1) safe_chr(' ', tbuf1, &bp); ATTR *ap = atr_num(ca); const UTF8 *pName = T("(WARNING: Bad Attribute Number)"); if (ap) { pName = ap->name; } safe_str(pName, tbuf1, &bp); } free_lbuf(attrib); } *bp = '\0'; pcre2_match_data_free(match_data); pcre2_code_free(re); olist_pop(); return tbuf1; } static void regrep_handler(UTF8 *buff, UTF8 **bufc, dbref executor, const UTF8 * const fargs[], bool bCaseInsens) { dbref it = match_thing_quiet(executor, fargs[0]); if (!Good_obj(it)) { safe_match_result(it, buff, bufc); return; } if (!Examinable(executor, it)) { safe_noperm(buff, bufc); return; } if (!fargs[1] || !*fargs[1]) { safe_str(S_("#-1 NO SUCH ATTRIBUTE"), buff, bufc); return; } if (!fargs[2] || !*fargs[2]) { safe_str(S_("#-1 INVALID GREP PATTERN"), buff, bufc); return; } UTF8 *tp = regrep_util(executor, it, fargs[1], fargs[2], bCaseInsens); safe_str(tp, buff, bufc); free_lbuf(tp); } FUNCTION(fun_regrep) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); regrep_handler(buff, bufc, executor, fargs, false); } FUNCTION(fun_regrepi) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); regrep_handler(buff, bufc, executor, fargs, true); } // Borrowed from PennMUSH 1.50 // FUNCTION(fun_alphamax) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); const UTF8 *amax = fargs[0]; LBuf buf_max = LBuf_Src("fun_alphamax"); mux_strncpy(buf_max, strip_color(amax), LBUF_SIZE-1); for (int i = 1; i < nfargs; i++) { if (fargs[i] && strcmp(reinterpret_cast(buf_max.get()), reinterpret_cast(strip_color(fargs[i]))) < 0) { amax = fargs[i]; mux_strncpy(buf_max, strip_color(amax), LBUF_SIZE-1); } } safe_str(amax, buff, bufc); } // Borrowed from PennMUSH 1.50 // FUNCTION(fun_alphamin) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); const UTF8 *amin = fargs[0]; LBuf buf_min = LBuf_Src("fun_alphamin"); mux_strncpy(buf_min, strip_color(amin), LBUF_SIZE-1); for (int i = 1; i < nfargs; i++) { if (fargs[i] && strcmp(reinterpret_cast(buf_min.get()), reinterpret_cast(strip_color(fargs[i]))) > 0) { amin = fargs[i]; mux_strncpy(buf_min, strip_color(amin), LBUF_SIZE-1); } } safe_str(amin, buff, bufc); } // Borrowed from PennMUSH 1.50 // FUNCTION(fun_valid) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Checks to see if a given is valid as a parameter of // a given type (such as an object name) // size_t nValidName; bool bValid; if (!*fargs[0] || !*fargs[1]) { bValid = false; } else if (!mux_stricmp(fargs[0], T("attrname"))) { MakeCanonicalAttributeName(fargs[1], &nValidName, &bValid); } else if (!mux_stricmp(fargs[0], T("comalias"))) { MakeCanonicalComAlias(fargs[1], &nValidName, &bValid); } else if (!mux_stricmp(fargs[0], T("doing"))) { MakeCanonicalDoing(fargs[1], &nValidName, &bValid); } else if (!mux_stricmp(fargs[0], T("exitname"))) { MakeCanonicalExitName(fargs[1], &nValidName, &bValid); } else if (!mux_stricmp(fargs[0], T("malias"))) { MakeCanonicalMailAlias(fargs[1], &nValidName, &bValid); } else if (!mux_stricmp(fargs[0], T("maliasdesc"))) { size_t vw; MakeCanonicalMailAliasDesc(fargs[1], &nValidName, &bValid, &vw); } else if ( !mux_stricmp(fargs[0], T("name")) || !mux_stricmp(fargs[0], T("thingname"))) { MakeCanonicalObjectName(fargs[1], &nValidName, &bValid, mudconf.thing_name_charset); } else if (!mux_stricmp(fargs[0], T("roomname"))) { MakeCanonicalObjectName(fargs[1], &nValidName, &bValid, mudconf.room_name_charset); } else if (!mux_stricmp(fargs[0], T("password"))) { const UTF8 *msg; bValid = ok_password(fargs[1], &msg); } else if (!mux_stricmp(fargs[0], T("playername"))) { bValid = ValidatePlayerName(fargs[1]); } else { safe_nothing(buff, bufc); return; } safe_bool(bValid, buff, bufc); } // Borrowed from PennMUSH 1.50 // FUNCTION(fun_hastype) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref it = match_thing_quiet(executor, fargs[0]); if (!Good_obj(it)) { safe_match_result(it, buff, bufc); return; } // Match the full type name (help hastype(): ROOM, EXIT, PLAYER, // THING; anything else is #-1). This historically tested only // fargs[1][0], so any string starting with R/E/P/T passed as that // type, and the error arm fell through to safe_bool and emitted // "#-1 NO SUCH TYPE0" (#1168). bool bResult; if (!mux_stricmp(fargs[1], T("ROOM"))) { bResult = isRoom(it); } else if (!mux_stricmp(fargs[1], T("EXIT"))) { bResult = isExit(it); } else if (!mux_stricmp(fargs[1], T("PLAYER"))) { bResult = isPlayer(it); } else if (!mux_stricmp(fargs[1], T("THING"))) { bResult = isThing(it); } else { safe_str(S_("#-1 NO SUCH TYPE"), buff, bufc); return; } safe_bool(bResult, buff, bufc); } // Borrowed from PennMUSH 1.50 // FUNCTION(fun_lparent) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref it = match_thing_quiet(executor, fargs[0]); if (!Good_obj(it)) { safe_match_result(it, buff, bufc); return; } else if (!Examinable(executor, it)) { safe_noperm(buff, bufc); return; } ITL pContext; ItemToList_Init(&pContext, buff, bufc, '#'); if (!ItemToList_AddInteger(&pContext, it)) { ItemToList_Final(&pContext); return; } dbref par = Parent(it); int iNestLevel = 1; while ( Good_obj(par) && Examinable(executor, it) && iNestLevel < mudconf.parent_nest_lim) { if (!ItemToList_AddInteger(&pContext, par)) { break; } it = par; par = Parent(par); iNestLevel++; } ItemToList_Final(&pContext); } /* --------------------------------------------------------------------------- * fun_regmatch: Return 0 or 1 depending on whether or not a regular * expression matches a string. If a third argument is specified, dump * the results of a regexp pattern match into a set of arbitrary r()-registers. * * regmatch(string, pattern, list of registers) * If the number of matches exceeds the registers, those bits are tossed * out. * If -1 is specified as a register number, the matching bit is tossed. * Therefore, if the list is "-1 0 3 5", the regexp $0 is tossed, and * the regexp $1, $2, and $3 become r(0), r(3), and r(5), respectively. */ // `registers` is const since #2144 routed it through list2arr_nd — the // compiler now holds what the comment there only asserted (#2157, a // down-payment on #2136's const-fargs contract). static void real_regmatch(const UTF8 *search, const UTF8 *pattern, const UTF8 *registers, int nfargs, UTF8 *buff, UTF8 **bufc, bool cis) { if (alarm_clock.alarmed) { return; } PCRE2_SIZE erroffset; int errcode; // Compile the pattern pcre2_code *re = pcre2_compile_8( pattern, // pattern string PCRE2_ZERO_TERMINATED, // pattern is zero-terminated PCRE2_UTF|(cis ? PCRE2_CASELESS : 0), // options &errcode, // for error code &erroffset, // for error offset nullptr // use default compile context ); if (!re) { // Matching error - get the error message PCRE2_UCHAR errbuf[256]; pcre2_get_error_message(errcode, errbuf, sizeof(errbuf)); safe_str(S_("#-1 REGEXP ERROR "), buff, bufc); safe_str(reinterpret_cast(errbuf), buff, bufc); return; } // Create match data block for storing results pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); if (!match_data) { pcre2_code_free(re); safe_str(S_("#-1 REGEXP MATCH DATA ERROR"), buff, bufc); return; } // Do the match int matches = pcre2_match( re, // compiled pattern search, // subject string PCRE2_ZERO_TERMINATED, // length of subject string 0, // start offset in subject 0, // options match_data, // block for storing the result nullptr // use default match context ); safe_bool(matches > 0, buff, bufc); // If we don't have a third argument, we're done. if (nfargs != 3 || matches <= 0) { pcre2_match_data_free(match_data); pcre2_code_free(re); return; } // We need to parse the list of registers. Non-destructive (#2136): // `registers` is the caller's fargs[2], borrowed memory. const int NSUBEXP = 2 * MAX_GLOBAL_REGS; UTF8 *qregs[NSUBEXP]; SEP sep; sep.n = 1; memcpy(sep.str, " ", 2); LBuf scRegs = LBuf_Src("real_regmatch.regs"); int nqregs = list2arr_nd(qregs, NSUBEXP, registers, sep, scRegs); // Get ovector pointer for accessing capture groups PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); // Process each requested register for (int i = 0; i < nqregs; i++) { if ( !qregs[i] || !*qregs[i]) { continue; } int curq; if (IsSingleCharReg(qregs[i], curq)) { // Single-char register (0-9, a-z). // if (i < matches) { LBuf p = LBuf_Src("fun_regmatch"); // #1112: PCRE2 requires capacity on entry, not 0. // PCRE2_SIZE outlen = LBUF_SIZE - 1; int ret = pcre2_substring_copy_bynumber( match_data, i, p, &outlen); if (ret >= 0) { size_t n = static_cast(outlen); RegAssign(&mudstate.global_regs[curq], n, p); } else { // #1112: clear, do not no-op. RegAssign early-returns on // a null ptr (eval.cpp), so passing nullptr left the // register at its PREVIOUS value — invisible while every // capture was empty, but now that captures fill, an unset // group would leak whatever the caller had setq'd there. // The named-register branch below already uses T(""). // RegAssign(&mudstate.global_regs[curq], 0, T("")); } } else { // #1112: ditto — more registers listed than captured groups. // RegAssign(&mudstate.global_regs[curq], 0, T("")); } } else { // Named register. // size_t nName = strlen(reinterpret_cast(qregs[i])); if (IsValidNamedReg(qregs[i], nName)) { if (i < matches) { LBuf p = LBuf_Src("fun_regmatch"); // #1112: PCRE2 requires capacity on entry, not 0. // PCRE2_SIZE outlen = LBUF_SIZE - 1; int ret = pcre2_substring_copy_bynumber( match_data, i, p, &outlen); if (ret >= 0) { size_t n = static_cast(outlen); NamedRegAssign(mudstate.named_regs, qregs[i], nName, n, p); } else { NamedRegAssign(mudstate.named_regs, qregs[i], nName, 0, T("")); } } else { NamedRegAssign(mudstate.named_regs, qregs[i], nName, 0, T("")); } } } } pcre2_match_data_free(match_data); pcre2_code_free(re); } FUNCTION(fun_regmatch) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); real_regmatch(fargs[0], fargs[1], fargs[2], nfargs, buff, bufc, false); } FUNCTION(fun_regmatchi) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); real_regmatch(fargs[0], fargs[1], fargs[2], nfargs, buff, bufc, true); } /* --------------------------------------------------------------------------- * regrab(), regraball(). Like grab() and graball(), using a regular expression * instead of a wildcard pattern. The versions ending in i are case-insensitive. */ static void real_regrab(const UTF8 *search, const UTF8 *pattern, const SEP &sep, UTF8 *buff, UTF8 **bufc, bool cis, bool all) { if (alarm_clock.alarmed) { return; } PCRE2_SIZE erroffset; int errcode; // Compile the pattern pcre2_code *re = pcre2_compile_8( pattern, // pattern string PCRE2_ZERO_TERMINATED, // pattern is zero-terminated PCRE2_UTF|(cis ? PCRE2_CASELESS : 0), // options &errcode, // for error code &erroffset, // for error offset nullptr // use default compile context ); if (!re) { // Matching error - get the error message PCRE2_UCHAR errbuf[256]; pcre2_get_error_message(errcode, errbuf, sizeof(errbuf)); safe_str(S_("#-1 REGEXP ERROR "), buff, bufc); safe_str(reinterpret_cast(errbuf), buff, bufc); return; } // Create match data block for storing results pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); if (!match_data) { pcre2_code_free(re); safe_str(S_("#-1 REGEXP MATCH DATA ERROR"), buff, bufc); return; } // JIT compile if we're going to use the pattern multiple times if (all) { pcre2_jit_compile(re, PCRE2_JIT_COMPLETE); } bool first = true; LBuf scList = LBuf_Src("real_regrab.list"); UTF8 *s = trim_space_sep(list_copy_for_split(scList, search), sep); do { UTF8 *r = split_token(&s, sep); if (!alarm_clock.alarmed) { int rc = pcre2_match( re, // compiled pattern r, // subject string PCRE2_ZERO_TERMINATED, // length of subject string 0, // start offset in subject 0, // options match_data, // block for storing the result nullptr // use default match context ); if (rc >= 0) { if (first) { first = false; } else { print_sep(sep, buff, bufc); } safe_str(r, buff, bufc); if (!all) { break; } } } } while (s); pcre2_match_data_free(match_data); pcre2_code_free(re); } FUNCTION(fun_regrab) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } real_regrab(fargs[0], fargs[1], sep, buff, bufc, false, false); } FUNCTION(fun_regrabi) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } real_regrab(fargs[0], fargs[1], sep, buff, bufc, true, false); } FUNCTION(fun_regraball) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } real_regrab(fargs[0], fargs[1], sep, buff, bufc, false, true); } FUNCTION(fun_regraballi) { SEP sep; if (!OPTIONAL_DELIM(3, sep, DELIM_DFLT|DELIM_STRING)) { return; } real_regrab(fargs[0], fargs[1], sep, buff, bufc, true, true); } /* --------------------------------------------------------------------------- * regedit(), regediti(), regeditall(), regeditalli(). Regex-based * find-and-replace on strings, borrowing Penn's concept. Supports $0-$99 * for numbered capture groups and $ for named capture groups in the * replacement string. */ // regedit_substitute: Expand $N and $ references in a replacement // string using match data from a PCRE2 match. // static void regedit_substitute(const UTF8 *replacement, pcre2_match_data *match_data, UTF8 *buff, UTF8 **bufc) { const UTF8 *p = replacement; while (*p) { if ('$' == *p) { p++; if ('<' == *p) { // Named capture group: $ // p++; const UTF8 *namestart = p; while ( *p && '>' != *p) { p++; } if ('>' == *p) { size_t namelen = p - namestart; p++; // Build null-terminated name. // UTF8 namebuf[128]; if (namelen < sizeof(namebuf)) { memcpy(namebuf, namestart, namelen); namebuf[namelen] = '\0'; LBuf groupbuf = LBuf_Src("regsub_named"); PCRE2_SIZE outlen = LBUF_SIZE - 1; int ret = pcre2_substring_copy_byname( match_data, namebuf, groupbuf, &outlen ); if (ret >= 0) { safe_copy_buf(groupbuf, outlen, buff, bufc); } } } else { // Unterminated $<...>, output literally. // safe_chr('$', buff, bufc); safe_chr('<', buff, bufc); safe_str(namestart, buff, bufc); } } else if (mux_isdigit(*p)) { // Numbered capture group: $0 through $99 // int groupnum = *p - '0'; p++; if (mux_isdigit(*p)) { groupnum = groupnum * 10 + (*p - '0'); p++; } LBuf groupbuf = LBuf_Src("regsub_numbered"); PCRE2_SIZE outlen = LBUF_SIZE - 1; int ret = pcre2_substring_copy_bynumber( match_data, groupnum, groupbuf, &outlen ); if (ret >= 0) { safe_copy_buf(groupbuf, outlen, buff, bufc); } } else { // Lone $ not followed by digit or <, output literally. // safe_chr('$', buff, bufc); } } else { safe_chr(*p, buff, bufc); p++; } } } static void real_regedit(const UTF8 * const fargs[], int nfargs, UTF8 *buff, UTF8 **bufc, bool cis, bool all) { if (alarm_clock.alarmed) { return; } // Use two lbufs as ping-pong buffers for multi-pair processing. // UTF8 *inbuf = alloc_lbuf("regedit.in"); UTF8 *outbuf = alloc_lbuf("regedit.out"); mux_strncpy(inbuf, fargs[0], LBUF_SIZE - 1); for (int i = 1; i + 1 < nfargs; i += 2) { if (alarm_clock.alarmed) { break; } PCRE2_SIZE erroffset; int errcode; pcre2_code *re = pcre2_compile_8( fargs[i], PCRE2_ZERO_TERMINATED, PCRE2_UTF | (cis ? PCRE2_CASELESS : 0), &errcode, &erroffset, nullptr ); if (!re) { PCRE2_UCHAR errbuf[256]; pcre2_get_error_message(errcode, errbuf, sizeof(errbuf)); free_lbuf(inbuf); free_lbuf(outbuf); safe_str(S_("#-1 REGEXP ERROR "), buff, bufc); safe_str(reinterpret_cast(errbuf), buff, bufc); return; } pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); if (!match_data) { pcre2_code_free(re); free_lbuf(inbuf); free_lbuf(outbuf); safe_str(S_("#-1 REGEXP MATCH DATA ERROR"), buff, bufc); return; } if (all) { pcre2_jit_compile(re, PCRE2_JIT_COMPLETE); } UTF8 *outp = outbuf; PCRE2_SIZE pos = 0; PCRE2_SIZE inlen = strlen(reinterpret_cast(inbuf)); bool matched = false; while (pos <= inlen) { if (alarm_clock.alarmed) { break; } int rc = pcre2_match( re, inbuf, inlen, pos, 0, match_data, nullptr ); if (rc < 0) { // No more matches. Copy rest of input. // safe_copy_buf(inbuf + pos, inlen - pos, outbuf, &outp); break; } matched = true; PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); // Copy text before this match. // if (ovector[0] > pos) { safe_copy_buf(inbuf + pos, ovector[0] - pos, outbuf, &outp); } // Expand replacement string with capture group substitution. // regedit_substitute(fargs[i + 1], match_data, outbuf, &outp); if (!all) { // Copy remaining text after the first match. // safe_copy_buf(inbuf + ovector[1], inlen - ovector[1], outbuf, &outp); break; } // Advance past this match. Handle zero-length matches by // advancing one byte to avoid infinite loops. // if (ovector[1] == pos) { if (pos < inlen) { safe_chr(inbuf[pos], outbuf, &outp); } pos++; } else { pos = ovector[1]; } } *outp = '\0'; pcre2_match_data_free(match_data); pcre2_code_free(re); // Swap buffers for next pair. // UTF8 *tmp = inbuf; inbuf = outbuf; outbuf = tmp; } safe_str(inbuf, buff, bufc); free_lbuf(inbuf); free_lbuf(outbuf); } FUNCTION(fun_regedit) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); real_regedit(fargs, nfargs, buff, bufc, false, false); } FUNCTION(fun_regediti) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); real_regedit(fargs, nfargs, buff, bufc, true, false); } FUNCTION(fun_regeditall) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); real_regedit(fargs, nfargs, buff, bufc, false, true); } FUNCTION(fun_regeditalli) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); real_regedit(fargs, nfargs, buff, bufc, true, true); } /* --------------------------------------------------------------------------- * fun_translate: Takes a string and a second argument. If the second argument * is 0 or s, control characters are converted to spaces. If it's 1 or p, * they're converted to percent substitutions. */ FUNCTION(fun_translate) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nfargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); int ch = fargs[1][0]; bool type = (ch == 'p' || ch == '1'); safe_str(translate_string(fargs[0], type), buff, bufc); } // ------------------------------------------------------------------------- // fun_lrooms: Takes a dbref (room), an int (N), and an optional bool (B). // // MUX Syntax: lrooms( [,[,]]) // // Returns a list of rooms -levels deep from . If == 1, it will // return all room dbrefs between 0 and levels, while == 0 will // return only the room dbrefs on the Nth level. The default is to show all // rooms dbrefs between 0 and levels. // // Written by Marlek. Idea from RhostMUSH. // static void room_list ( dbref player, dbref enactor, dbref room, int maxlevels, bool showall ) { // BFS level-by-level traversal. 'current' holds rooms at depth d, // 'next' collects rooms at depth d+1. // std::vector current; std::vector next; current.push_back(room); for (int d = 0; d < maxlevels; d++) { for (size_t ci = 0; ci < current.size(); ci++) { dbref r = current[ci]; int lev; dbref parent; ITER_PARENTS(r, parent, lev) { if (!Has_exits(parent)) { continue; } int key = 0; if (Examinable(player, parent)) { key |= VE_LOC_XAM; } if (Dark(parent)) { key |= VE_LOC_DARK; } if (Dark(r)) { key |= VE_BASE_DARK; } dbref thing; DOLIST(thing, Exits(parent)) { dbref loc = Location(thing); // #1108: Unlinked exits leave Location == NOTHING (-1). // IsSet/Set no-op on out-of-range, but Examinable → Flags/ // Owner → db[-1] is an OOB read. Only walk rooms. // if ( !Good_obj(loc) || !isRoom(loc)) { continue; } if ( exit_visible(thing, player, key) && !mudstate.bfTraverse.IsSet(loc)) { mudstate.bfTraverse.Set(loc); if ( ( showall || d + 1 == maxlevels) && ( Examinable(player, loc) || Location(player) == loc || loc == enactor)) { mudstate.bfReport.Set(loc); } next.push_back(loc); } } } } current.swap(next); next.clear(); } } FUNCTION(fun_lrooms) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref room = match_thing_quiet(executor, fargs[0]); if (!Good_obj(room)) { safe_match_result(room, buff, bufc); return; } else if (!isRoom(room)) { safe_str(S_("#-1 FIRST ARGUMENT MUST BE A ROOM"), buff, bufc); return; } int N = 1; if (nfargs >= 2) { N = mux_atoi64(fargs[1]); if (N < 0) { safe_str(S_("#-1 SECOND ARGUMENT MUST BE A POSITIVE NUMBER"), buff, bufc); return; } else if (N > 50) { // Maybe this can be turned into a config parameter to prevent // misuse by putting in really large values. // safe_str(S_("#-1 SECOND ARGUMENT IS TOO LARGE"), buff, bufc); return; } } bool B = true; if (nfargs == 3) { B = xlate(fargs[2]); } mudstate.bfReport.Resize(mudstate.db_top-1); mudstate.bfTraverse.Resize(mudstate.db_top-1); mudstate.bfReport.ClearAll(); mudstate.bfTraverse.ClearAll(); mudstate.bfTraverse.Set(room); room_list(executor, enactor, room, N, B); mudstate.bfReport.Clear(room); ITL pContext; ItemToList_Init(&pContext, buff, bufc, '#'); dbref i; DO_WHOLE_DB(i) { if ( mudstate.bfReport.IsSet(i) && !ItemToList_AddInteger(&pContext, i)) { break; } } ItemToList_Final(&pContext); } // --------------------------------------------------------------------------- // fun_route: next-hop routing over NAVIGABLE rooms. // // route(, [, ]) // // Options (space-separated): distance, path, rebuild. // See docs/design-routing.md for the full specification. // --------------------------------------------------------------------------- #include "routing.h" FUNCTION(fun_route) { UNUSED_PARAMETER(fp); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref source = match_thing_quiet(executor, fargs[0]); if (!Good_obj(source)) { safe_match_result(source, buff, bufc); return; } dbref destination = match_thing_quiet(executor, fargs[1]); if (!Good_obj(destination)) { safe_match_result(destination, buff, bufc); return; } // Parse options (third argument, space-separated). // int options = 0; if (nfargs >= 3 && fargs[2] && *fargs[2]) { LBuf scOpts = LBuf_Src("fun_route.opts"); UTF8 *opts = trim_space_sep(list_copy_for_split(scOpts, fargs[2]), sepSpace); UTF8 *token; while (opts && *opts) { token = split_token(&opts, sepSpace); if (mux_stricmp(token, T("distance")) == 0) { options |= ROUTE_OPT_DISTANCE; } else if (mux_stricmp(token, T("path")) == 0) { options |= ROUTE_OPT_PATH; } else if (mux_stricmp(token, T("rebuild")) == 0) { options |= ROUTE_OPT_REBUILD; } else if (mux_stricmp(token, T("locked")) == 0) { options |= ROUTE_OPT_LOCKED; } else { safe_str(S_("#-2"), buff, bufc); return; } } } route_query(executor, source, destination, options, buff, bufc); }