diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 1915cd325..73aa30191 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1,4 +1,4 @@ -HDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1051 $ $NHDT-Date: 1665130022 2022/10/07 08:07:02 $ +HDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1093 $ $NHDT-Date: 1672605786 2023/01/01 20:43:06 $ General Fixes and Modified Features ----------------------------------- @@ -1101,6 +1101,8 @@ eating garlic makes nearby monsters flee giants occasionally get a battle axe or a two-handed sword give gremlin the property it stole, if possible 'F'orcefighting with a war hammer has a small chance of breaking iron bars +player assigned name for monsters, specific objects, or object types could be + longer than what was intented to be allowed; for 'curses', much longer Fixes to 3.7.0-x Problems that Were Exposed Via git Repository diff --git a/src/do_name.c b/src/do_name.c index ba1b35257..670943d93 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 do_name.c $NHDT-Date: 1655663780 2022/06/19 18:36:20 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.254 $ */ +/* NetHack 3.7 do_name.c $NHDT-Date: 1672605786 2023/01/01 20:43:06 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.280 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Pasi Kallinen, 2018. */ /* NetHack may be freely redistributed. See license for details. */ @@ -19,6 +19,7 @@ static void gloc_filter_done(void); static boolean gather_locs_interesting(coordxy, coordxy, int); static void gather_locs(coord **, int *, int); static void truncate_to_map(int *, int *, schar, schar); +static char *name_from_player(char *, const char *, const char *); static void do_mgivenname(void); static boolean alreadynamed(struct monst *, char *, char *); static void do_oname(struct obj *); @@ -1180,6 +1181,34 @@ safe_oname(struct obj *obj) return ""; } +/* get a name for a monster or an object from player; + truncate if longer than PL_PSIZ, then return it */ +static char * +name_from_player( + char *outbuf, /* output buffer, assumed to be at least BUFSZ long; + * anything longer than PL_PSIZ will be truncated */ + const char *prompt, + const char *defres) /* only used if EDIT_GETLIN is enabled; only useful + * if windowport xxx's xxx_getlin() supports that */ +{ + outbuf[0] = '\0'; +#ifdef EDIT_GETLIN + if (defres && *defres) + Strcpy(outbuf, defres); /* default response from getlin() */ +#else + nhUse(defres); +#endif + getlin(prompt, outbuf); + if (!*outbuf || *outbuf == '\033') + return NULL; + + /* strip leading and trailing spaces, condense internal sequences */ + (void) mungspaces(outbuf); + if (strlen(outbuf) >= PL_PSIZ) + outbuf[PL_PSIZ - 1] = '\0'; + return outbuf; +} + /* historical note: this returns a monster pointer because it used to allocate a new bigger block of memory to hold the monster and its name */ struct monst * @@ -1290,17 +1319,10 @@ do_mgivenname(void) /* special case similar to the one in lookat() */ Sprintf(qbuf, "What do you want to call %s?", distant_monnam(mtmp, ARTICLE_THE, monnambuf)); - buf[0] = '\0'; -#ifdef EDIT_GETLIN - /* if there's an existing name, make it be the default answer */ - if (has_mgivenname(mtmp)) - Strcpy(buf, MGIVENNAME(mtmp)); -#endif - getlin(qbuf, buf); - if (!*buf || *buf == '\033') + /* use getlin() to get a name string from the player */ + if (!name_from_player(buf, qbuf, + has_mgivenname(mtmp) ? MGIVENNAME(mtmp) : NULL)) return; - /* strip leading and trailing spaces; unnames monster if all spaces */ - (void) mungspaces(buf); /* Unique monsters have their own specific names or titles. * Shopkeepers, temple priests and other minions use alternate @@ -1348,17 +1370,9 @@ do_oname(struct obj *obj) Sprintf(qbuf, "What do you want to name %s ", is_plural(obj) ? "these" : "this"); (void) safe_qbuf(qbuf, qbuf, "?", obj, xname, simpleonames, "item"); - buf[0] = '\0'; -#ifdef EDIT_GETLIN - /* if there's an existing name, make it be the default answer */ - if (has_oname(obj)) - Strcpy(buf, ONAME(obj)); -#endif - getlin(qbuf, buf); - if (!*buf || *buf == '\033') + /* use getlin() to get a name string from the player */ + if (!name_from_player(buf, qbuf, safe_oname(obj))) return; - /* strip leading and trailing spaces; unnames item if all spaces */ - (void) mungspaces(buf); /* * We don't violate illiteracy conduct here, although it is @@ -1701,14 +1715,8 @@ docall(struct obj *obj) docall_xname, simpleonames, "thing"); /* pointer to old name */ str1 = &(objects[obj->otyp].oc_uname); - buf[0] = '\0'; -#ifdef EDIT_GETLIN - /* if there's an existing name, make it be the default answer */ - if (*str1) - Strcpy(buf, *str1); -#endif - getlin(qbuf, buf); - if (!*buf || *buf == '\033') + /* use getlin() to get a name string from the player */ + if (!name_from_player(buf, qbuf, *str1)) return; /* clear old name */ diff --git a/src/invent.c b/src/invent.c index 6682d75f5..e144911c8 100644 --- a/src/invent.c +++ b/src/invent.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 invent.c $NHDT-Date: 1661240719 2022/08/23 07:45:19 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.424 $ */ +/* NetHack 3.7 invent.c $NHDT-Date: 1672827802 2023/01/04 10:23:22 $ $NHDT-Branch: naming-overflow-fix $:$NHDT-Revision: 1.439 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2567,17 +2567,21 @@ prinv(const char *prefix, struct obj *obj, long quan) DISABLE_WARNING_FORMAT_NONLITERAL char * -xprname(struct obj *obj, - const char *txt, /* text to print instead of obj */ - char let, /* inventory letter */ - boolean dot, /* append period; (dot && cost => Iu) */ - long cost, /* cost (for inventory of unpaid or expended items) */ - long quan) /* if non-0, print this quantity, not obj->quan */ +xprname( + struct obj *obj, + const char *txt, /* text to print instead of obj */ + char let, /* inventory letter */ + boolean dot, /* append period; (dot && cost => Iu) */ + long cost, /* cost (for inventory of unpaid or expended items) */ + long quan) /* if non-0, print this quantity, not obj->quan */ { static char li[BUFSZ]; + char suffix[80]; /* plenty of room for count and hallucinatory currency */ + int sfxlen, txtlen; /* signed int for %*s formatting */ + const char *fmt; boolean use_invlet = (flags.invlet_constant && let != CONTAINED_SYM && let != HANDS_SYM); - long savequan = 0; + long savequan = 0L; if (quan && obj) { savequan = obj->quan; @@ -2589,18 +2593,33 @@ xprname(struct obj *obj, * * Then obj == null and we are printing a total amount. * > Then the object is contained and doesn't have an inventory letter. */ - if (cost != 0 || let == '*') { + fmt = "%c - %.*s%s"; + if (!txt) + txt = doname(obj); + txtlen = (int) strlen(txt); + + if (cost != 0L || let == '*') { /* if dot is true, we're doing Iu, otherwise Ix */ - Sprintf(li, - iflags.menu_tab_sep ? "%c - %s\t%6ld %s" - : "%c - %-45s %6ld %s", - (dot && use_invlet ? obj->invlet : let), - (txt ? txt : doname(obj)), cost, currency(cost)); + if (dot && use_invlet) + let = obj->invlet; + Sprintf(suffix, "%c%6ld %.50s", iflags.menu_tab_sep ? '\t' : ' ', + cost, currency(cost)); + if (!iflags.menu_tab_sep) { + fmt = "%c - %-45.*s%s"; + if (txtlen < 45) + txtlen = 45; + } } else { /* ordinary inventory display or pickup message */ - Sprintf(li, "%c - %s%s", (use_invlet ? obj->invlet : let), - (txt ? txt : doname(obj)), (dot ? "." : "")); + if (use_invlet) + let = obj->invlet; + Strcpy(suffix, dot ? "." : ""); } + sfxlen = (int) strlen(suffix); + if (txtlen > BUFSZ - 1 - (4 + sfxlen)) /* 4: "c - " prefix */ + txtlen = BUFSZ - 1 - (4 + sfxlen); + Sprintf(li, fmt, let, txtlen, txt, suffix); + if (savequan) obj->quan = savequan; diff --git a/src/o_init.c b/src/o_init.c index 50526b837..f3da16d9c 100644 --- a/src/o_init.c +++ b/src/o_init.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 o_init.c $NHDT-Date: 1646950588 2022/03/10 22:16:28 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.56 $ */ +/* NetHack 3.7 o_init.c $NHDT-Date: 1672829455 2023/01/04 10:50:55 $ $NHDT-Branch: naming-overflow-fix $:$NHDT-Revision: 1.68 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -11,6 +11,7 @@ static void shuffle_all(void); static int QSORTCALLBACK discovered_cmp(const genericptr, const genericptr); static char *sortloot_descr(int, char *); static char *disco_typename(int); +static void disco_append_typename(char *, int); static char *oclass_to_name(char, char *); #ifdef TILES_IN_GLYPHMAP @@ -651,6 +652,30 @@ disco_typename(int otyp) return result; } +/* append typename(dis) to buf[], possibly truncating in the process */ +static void +disco_append_typename(char *buf, int dis) +{ + unsigned len = (unsigned) strlen(buf); + char *p, *typnm = disco_typename(dis); + + if (len + (unsigned) strlen(typnm) < BUFSZ) { + /* ordinary */ + Strcat(buf, typnm); + } else if ((p = strrchr(typnm, '(')) != 0 + && p > typnm && p[-1] == ' ' && strchr(p, ')') != 0) { + /* typename() returned "really long user-applied name (actual type)" + and we want to truncate from "really long user-applied name" while + keeping " (actual type)" intact */ + --p; /* back up to space in front of open paren */ + (void) strncat(buf, typnm, BUFSZ - 1 - (len + (unsigned) strlen(p))); + Strcat(buf, p); + } else { + /* unexpected; just truncate from end of typename */ + (void) strncat(buf, typnm, BUFSZ - 1 - len); + } +} + /* the #known command - show discovered object types */ int dodiscovered(void) /* free after Robert Viduya */ @@ -734,7 +759,7 @@ dodiscovered(void) /* free after Robert Viduya */ Strcpy(buf, objects[dis].oc_pre_discovered ? "* " : " "); if (lootsort) (void) sortloot_descr(dis, &buf[2]); - Strcat(buf, disco_typename(dis)); + disco_append_typename(buf, dis); if (!alphabetized && !lootsort) putstr(tmpwin, 0, buf); @@ -966,7 +991,7 @@ doclassdisco(void) Strcpy(buf, objects[dis].oc_pre_discovered ? "* " : " "); if (lootsort) (void) sortloot_descr(dis, &buf[2]); - Strcat(buf, disco_typename(dis)); + disco_append_typename(buf, dis); if (!alphabetized && !lootsort) putstr(tmpwin, 0, buf); diff --git a/src/objnam.c b/src/objnam.c index 960247ac3..7385b65c1 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 objnam.c $NHDT-Date: 1654557302 2022/06/06 23:15:02 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.368 $ */ +/* NetHack 3.7 objnam.c $NHDT-Date: 1672829441 2023/01/04 10:50:41 $ $NHDT-Branch: naming-overflow-fix $:$NHDT-Revision: 1.386 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -35,6 +35,7 @@ struct _readobjnam_data { static char *strprepend(char *, const char *); static char *nextobuf(void); static void releaseobuf(char *); +static void xcalled(char *, int, const char *, const char *); static char *xname_flags(struct obj *, unsigned); static char *minimal_xname(struct obj *); static void add_erosion_words(struct obj *, char *); @@ -187,6 +188,7 @@ obj_typename(int otyp) if (!actualn) actualn = (otyp > 0 && otyp < MAXOCLASSES) ? "generic" : "object?"; + buf[0] = '\0'; /* redundant */ switch (ocl->oc_class) { case COIN_CLASS: Strcpy(buf, "coin"); @@ -217,7 +219,7 @@ obj_typename(int otyp) else Strcpy(buf, "amulet"); if (un) - Sprintf(eos(buf), " called %s", un); + xcalled(buf, BUFSZ - (dn ? (int) strlen(dn) + 3 : 0), "", un); if (dn) Sprintf(eos(buf), " (%s)", dn); return buf; @@ -226,8 +228,8 @@ obj_typename(int otyp) Strcpy(buf, actualn); if (GemStone(otyp)) Strcat(buf, " stone"); - if (un) - Sprintf(eos(buf), " called %s", un); + if (un) /* 3: length of " (" + ")" which will enclose 'dn' */ + xcalled(buf, BUFSZ - (dn ? (int) strlen(dn) + 3 : 0), "", un); if (dn) Sprintf(eos(buf), " (%s)", dn); } else { @@ -236,7 +238,7 @@ obj_typename(int otyp) Strcat(buf, (ocl->oc_material == MINERAL) ? " stone" : " gem"); if (un) - Sprintf(eos(buf), " called %s", un); + xcalled(buf, BUFSZ, "", un); } return buf; } @@ -247,8 +249,8 @@ obj_typename(int otyp) else Sprintf(eos(buf), " of %s", actualn); } - if (un) - Sprintf(eos(buf), " called %s", un); + if (un) /* 3: length of " (" + ")" which will enclose 'dn' */ + xcalled(buf, BUFSZ - (dn ? (int) strlen(dn) + 3 : 0), "", un); if (dn) Sprintf(eos(buf), " (%s)", dn); return buf; @@ -498,6 +500,24 @@ reorder_fruit(boolean forward) } } +/* add " called " to end of buf, truncating if necessary */ +static void +xcalled( + char *buf, /* eos(obuf) or eos(&obuf[PREFIX]) */ + int siz, /* BUFSZ or BUFSZ-PREFIX */ + const char *pfx, /* usually class string, sometimes more specific */ + const char *sfx) /* user assigned type name */ +{ + int bufsiz = siz - 1 - (int) strlen(buf), + pfxlen = (int) (strlen(pfx) + sizeof " called " - sizeof ""); + + if (pfxlen > bufsiz) + panic("xcalled: not enough room for prefix (%d > %d)", + pfxlen, bufsiz); + + Sprintf(eos(buf), "%s called %.*s", pfx, bufsiz - pfxlen, sfx); +} + char * xname(struct obj* obj) { @@ -530,7 +550,7 @@ xname_flags( for those; pacify static analyzer without resorting to impossible() */ if (!actualn) actualn = (typ > 0 && typ < MAXOCLASSES) ? "generic" : "object?"; - /* As of 3.6.2: this used to be part of 'dn's initialization, but it + /* 3.6.2: this used to be part of 'dn's initialization, but it needs to come after possibly overriding 'actualn' */ if (!dn) dn = actualn; @@ -592,7 +612,7 @@ xname_flags( else if (nn) Strcpy(buf, actualn); else if (un) - Sprintf(buf, "amulet called %s", un); + xcalled(buf, BUFSZ - PREFIX, "amulet", un); else Sprintf(buf, "%s amulet", dn); break; @@ -611,11 +631,9 @@ xname_flags( Strcat(buf, dn); else if (nn) Strcat(buf, actualn); - else if (un) { - Strcat(buf, dn); - Strcat(buf, " called "); - Strcat(buf, un); - } else + else if (un) + xcalled(buf, BUFSZ - PREFIX, dn, un); + else Strcat(buf, dn); if (typ == FIGURINE && omndx != NON_PM) { @@ -650,7 +668,7 @@ xname_flags( if (nn) Strcat(buf, actualn); else if (un) - Sprintf(eos(buf), "%s called %s", armor_simple_name(obj), un); + xcalled(buf, BUFSZ - PREFIX, armor_simple_name(obj), un); else Strcat(buf, dn); break; @@ -750,8 +768,7 @@ xname_flags( } Strcat(buf, actualn); } else { - Strcat(buf, " called "); - Strcat(buf, un); + xcalled(buf, BUFSZ - PREFIX, "", un); } } else { Strcat(buf, dn); @@ -766,8 +783,7 @@ xname_flags( Strcat(buf, " of "); Strcat(buf, actualn); } else if (un) { - Strcat(buf, " called "); - Strcat(buf, un); + xcalled(buf, BUFSZ - PREFIX, "", un); } else if (ocl->oc_magic) { Strcat(buf, " labeled "); Strcat(buf, dn); @@ -782,7 +798,7 @@ xname_flags( else if (nn) Sprintf(buf, "wand of %s", actualn); else if (un) - Sprintf(buf, "wand called %s", un); + xcalled(buf, BUFSZ - PREFIX, "wand", un); else Sprintf(buf, "%s wand", dn); break; @@ -793,7 +809,7 @@ xname_flags( else if (nn) Strcpy(buf, actualn); else if (un) - Sprintf(buf, "novel called %s", un); + xcalled(buf, BUFSZ - PREFIX, "novel", un); else Sprintf(buf, "%s book", dn); break; @@ -805,7 +821,7 @@ xname_flags( Strcpy(buf, "spellbook of "); Strcat(buf, actualn); } else if (un) { - Sprintf(buf, "spellbook called %s", un); + xcalled(buf, BUFSZ - PREFIX, "spellbook", un); } else Sprintf(buf, "%s spellbook", dn); break; @@ -815,7 +831,7 @@ xname_flags( else if (nn) Sprintf(buf, "ring of %s", actualn); else if (un) - Sprintf(buf, "ring called %s", un); + xcalled(buf, BUFSZ - PREFIX, "ring", un); else Sprintf(buf, "%s ring", dn); break; @@ -826,7 +842,7 @@ xname_flags( Strcpy(buf, rock); } else if (!nn) { if (un) - Sprintf(buf, "%s called %s", rock, un); + xcalled(buf, BUFSZ - PREFIX, rock, un); else Sprintf(buf, "%s %s", dn, rock); } else { @@ -879,7 +895,8 @@ xname_flags( Strcat(buf, " named "); nameit: obufp = eos(buf); - Strcat(buf, ONAME(obj)); + (void) strncat(buf, ONAME(obj), + BUFSZ - 1 - PREFIX - (unsigned) strlen(buf)); /* downcase "The" in " named The ..." */ if (obj->oartifact && !strncmp(obufp, "The ", 4)) *obufp = lowc(*obufp); /* = 't'; */ @@ -1866,7 +1883,7 @@ just_an(char *outbuf, const char *str) } char * -an(const char* str) +an(const char *str) { char *buf = nextobuf(); @@ -1875,11 +1892,11 @@ an(const char* str) return strcpy(buf, "an []"); } (void) just_an(buf, str); - return strcat(buf, str); + return strncat(buf, str, BUFSZ - 1 - Strlen(buf)); } char * -An(const char* str) +An(const char *str) { char *tmp = an(str); @@ -1950,9 +1967,7 @@ the(const char* str) Strcpy(buf, "the "); else buf[0] = '\0'; - Strcat(buf, str); - - return buf; + return strncat(buf, str, BUFSZ - 1 - Strlen(buf)); } char * @@ -3846,10 +3861,12 @@ readobjnam_postparse1(struct _readobjnam_data *d) */ if ((d->p = strstri(d->bp, " named ")) != 0) { *d->p = 0; + /* note: if 'name' is too long, oname() will truncate it */ d->name = d->p + 7; } if ((d->p = strstri(d->bp, " called ")) != 0) { *d->p = 0; + /* note: if 'un' is too long, obj lookup just won't match anything */ d->un = d->p + 8; /* "helmet called telepathy" is not "helmet" (a specific type) * "shield called reflection" is not "shield" (a general type)