/* * Copyright (c) 2021-2024 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "browser.h" #include "settings.h" #include "util.h" enum { SETTING_STRING, SETTING_SHORT, SETTING_USHORT, SETTING_LONG, SETTING_PASSWORD, SETTING_BOOL }; enum { SETTING_FLAG_ALLOW_BLANK = (1 << 0) }; struct settings settings = { 0 }; struct setting { char name[32]; short type; long flags; long min; long max; char sdefault[32]; short ditl_id; unsigned long offset; unsigned long size; char password_storage[256]; }; void settings_load_defaults(void); void settings_find_prefs_folder(short *vrefnum, long *dirid); bool settings_edit(struct setting *defs, size_t defs_count, short dlog_id, bool use_defaults); void settings_load(void) { HParamBlockRec pb; struct settings tsettings; char fn[] = SETTINGS_FILENAME; char *res; short error, vrefnum, prefrefnum; long len, rlen, dirid; Handle h; GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid); memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.ioParam.ioPermssn = fsRdPerm; pb.fileParam.ioDirID = dirid; error = PBHOpen(&pb, false); if (error) { if (error != fnfErr) panic("Failed reading preferences file %s: %d", PtoCstr(fn), error); goto load_defaults; } rlen = len = sizeof(struct settings); FSRead(pb.ioParam.ioRefNum, &rlen, &tsettings); while (tsettings.version < SETTINGS_VERSION) { switch (tsettings.version) { default: FSClose(pb.ioParam.ioRefNum); if (ask("Unknown preferences version (%d), reset?", tsettings.version)) goto load_defaults; ExitToShell(); } tsettings.version++; } FSClose(pb.ioParam.ioRefNum); settings = tsettings; return; load_defaults: settings_load_defaults(); } void settings_load_defaults(void) { short n; memset(&settings, 0, sizeof(settings)); for (n = 0; default_bookmarks[n].name[0] != '\0'; n++) { strlcpy(settings.bookmark_names[n], default_bookmarks[n].name, sizeof(settings.bookmark_names[0])); strlcpy(settings.bookmark_uris[n], default_bookmarks[n].uri, sizeof(settings.bookmark_uris[0])); } settings.version = SETTINGS_VERSION; } void settings_save(struct settings *tsettings) { HParamBlockRec pb; char fn[] = SETTINGS_FILENAME; short error, vrefnum, prefrefnum; long dirid, len; Handle h; GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid); memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.fileParam.ioDirID = dirid; error = PBHCreate(&pb, false); if (error && error != dupFNErr) goto create_failed; if (!error) { memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.fileParam.ioDirID = dirid; if ((error = PBHGetFInfo(&pb, false))) goto create_failed; pb.fileParam.ioDirID = dirid; /* required after PBHGetFInfo */ pb.fileParam.ioFlFndrInfo.fdType = 'pref'; pb.fileParam.ioFlFndrInfo.fdCreator = SETTINGS_FILE_CREATOR; if ((error = PBHSetFInfo(&pb, false))) goto create_failed; memset(&pb, 0, sizeof(pb)); pb.ioParam.ioVRefNum = vrefnum; PBFlushVol(&pb, false); } memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.ioParam.ioPermssn = fsWrPerm; pb.fileParam.ioDirID = dirid; error = PBHOpen(&pb, false); if (error) goto create_failed; len = sizeof(struct settings); tsettings->version = SETTINGS_VERSION; FSWrite(pb.ioParam.ioRefNum, &len, tsettings); FSClose(pb.ioParam.ioRefNum); return; create_failed: panic("Failed creating preferences file %s: %d", PtoCstr(fn), error); } bool bookmarks_dialog(void) { Str255 txt; WindowPtr win; DialogPtr dlg; struct settings tsettings; struct URI *uri; short name_id, uri_id, itype, n, hit, bookmarkn; Handle ihandle; Rect irect, dlgrect; GetPort(&win); if ((dlg = GetNewDialog(BOOKMARKS_DLOG_ID, NULL, (WindowPtr)-1)) == NULL) { warn("Can't find DLOG %d", BOOKMARKS_DLOG_ID); return false; } center_in_screen(dlg->portRect.right - dlg->portRect.left, dlg->portRect.bottom - dlg->portRect.top, true, &dlgrect); MoveWindow(dlg, dlgrect.left, dlgrect.top, true); for (n = 0; n < BOOKMARKS_COUNT; n++) { if (settings.bookmark_uris[n][0] == '\0') continue; name_id = BOOKMARKS_NAME_ID_START + (n * 2); GetDItem(dlg, name_id, &itype, &ihandle, &irect); strlcpy((char *)&txt, settings.bookmark_names[n], sizeof(txt)); CtoPstr(txt); SetIText(ihandle, txt); uri_id = BOOKMARKS_URI_ID_START + (n * 2); GetDItem(dlg, uri_id, &itype, &ihandle, &irect); strlcpy((char *)&txt, settings.bookmark_uris[n], sizeof(txt)); CtoPstr(txt); SetIText(ihandle, txt); } tsettings = settings; ShowWindow(dlg); for (;;) { modal: ModalDialog(ModalDialogFilter, &hit); if (hit == cancel) break; if (hit != ok) continue; memset(&tsettings.bookmark_names, 0, sizeof(tsettings.bookmark_names)); memset(&tsettings.bookmark_uris, 0, sizeof(tsettings.bookmark_uris)); for (n = 0, bookmarkn = 0; n < BOOKMARKS_COUNT; n++) { uri_id = BOOKMARKS_URI_ID_START + (n * 2); GetDItem(dlg, uri_id, &itype, &ihandle, &irect); GetIText(ihandle, txt); if (txt[0] != '\0') { PtoCstr(txt); uri = parse_uri((char *)&txt); if (uri == NULL) { warn("Could not parse URI \"%s\"", txt); goto modal; } xfree(&uri); strlcpy(tsettings.bookmark_uris[bookmarkn], (char *)&txt, sizeof(tsettings.bookmark_uris[bookmarkn])); name_id = BOOKMARKS_NAME_ID_START + (n * 2); GetDItem(dlg, name_id, &itype, &ihandle, &irect); GetIText(ihandle, txt); PtoCstr(txt); strlcpy(tsettings.bookmark_names[bookmarkn], (char *)&txt, sizeof(tsettings.bookmark_names[bookmarkn])); bookmarkn++; } } settings_save(&tsettings); settings = tsettings; menu_load_bookmarks(); break; } DisposDialog(dlg); SetPort(win); } bool settings_edit(struct setting *defs, size_t defs_count, short dlog_id, bool use_defaults) { struct settings tsettings = settings; struct setting *s; DialogTHndl dlgh; DialogPtr dlg; Handle ihandle; Rect irect; size_t size, n, m, slen; long lval; bool bval, save; short hit, itype, ret; char *sdata, *strval; Str255 stmp; /* center dialog in screen */ dlgh = (DialogTHndl)xGetResource('DLOG', dlog_id); HLock(dlgh); center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left, (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top, true, &(**dlgh).boundsRect); HUnlock(dlgh); if ((dlg = GetNewDialog(dlog_id, nil, (WindowPtr)-1)) == NULL) panic("Can't find settings DLOG %d", dlog_id); for (n = 0; n < defs_count; n++) { s = &defs[n]; sdata = (char *)&tsettings + s->offset; GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect); switch (s->type) { case SETTING_PASSWORD: PasswordDialogFieldFilterSetup(s->ditl_id, (char *)s->password_storage, sizeof(s->password_storage)); strval = (use_defaults ? (char *)s->sdefault : sdata); size = strlen(strval); if (size >= sizeof(s->password_storage) - 1) size = 0; /* show a masked value */ stmp[0] = size; for (m = 1; m <= size; m++) stmp[m] = '¥'; SetIText(ihandle, stmp); /* but store the actual password */ memcpy(s->password_storage, strval, size); s->password_storage[size] = '\0'; break; case SETTING_STRING: strval = (use_defaults ? s->sdefault : sdata); memcpy(stmp, strval, sizeof(stmp)); stmp[sizeof(stmp) - 1] = '\0'; CtoPstr(stmp); SetIText(ihandle, stmp); break; case SETTING_SHORT: case SETTING_USHORT: case SETTING_LONG: if (use_defaults) { memcpy(stmp, s->sdefault, sizeof(stmp)); stmp[sizeof(stmp) - 1] = '\0'; } else if (s->type == SETTING_SHORT) { snprintf((char *)stmp, sizeof(stmp), "%d", BYTES_TO_SHORT(sdata[0], sdata[1])); } else if (s->type == SETTING_USHORT) { snprintf((char *)stmp, sizeof(stmp), "%u", BYTES_TO_SHORT(sdata[0], sdata[1])); } else if (s->type == SETTING_LONG) { snprintf((char *)stmp, sizeof(stmp), "%ld", BYTES_TO_LONG(sdata[0], sdata[1], sdata[2], sdata[3])); } CtoPstr(stmp); SetIText(ihandle, stmp); break; case SETTING_BOOL: SetCtlValue(ihandle, (use_defaults ? s->sdefault[0] : sdata[0]) == 1); break; default: panic("Unknown setting type %d", s->type); } } ret = false; ShowWindow(dlg); SelectWindow(dlg); get_input: ModalDialog(PasswordDialogFieldFilter, &hit); switch (hit) { case -1: goto settings_done; case OK: goto verify; default: GetDItem(dlg, hit, &itype, &ihandle, &irect); if (itype == (ctrlItem + chkCtrl)) SetCtlValue(ihandle, !GetCtlValue(ihandle)); goto get_input; } save = false; verify: for (n = 0; n < defs_count; n++) { s = &defs[n]; sdata = (char *)&tsettings + s->offset; GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect); if (s->type == SETTING_PASSWORD) { memcpy((char *)&stmp, s->password_storage, sizeof(stmp)); } else if (s->type == SETTING_BOOL) { snprintf((char *)&stmp, sizeof(stmp), "%d", GetCtlValue(ihandle)); } else { GetIText(ihandle, stmp); PtoCstr(stmp); } switch (s->type) { case SETTING_STRING: case SETTING_PASSWORD: slen = strlen((char *)stmp); if (slen == 0 && (s->flags & SETTING_FLAG_ALLOW_BLANK)) { /* ok */ } else { if (s->min && s->max && s->min == s->max && slen != s->min) { warn("%s must be %ld character%s", s->name, s->min, s->min == 1 ? "" : "s"); goto get_input; } if (s->min && slen < s->min) { warn("%s is too short (minimum %ld)", s->name, s->min); goto get_input; } if (s->max && slen > s->max) { warn("%s is too long (maximum %ld)", s->name, s->max); goto get_input; } } if (save) { memset(sdata, 0, s->size); strlcpy(sdata, (char *)stmp, s->size); } break; case SETTING_SHORT: case SETTING_USHORT: case SETTING_LONG: lval = atol((char *)stmp); if (lval < s->min) { if (s->type == SETTING_USHORT) warn("%s must be at least %ul", s->name, s->min); else warn("%s must be at least %ld", s->name, s->min); goto get_input; } if (lval > s->max) { if (s->type == SETTING_USHORT) warn("%s must be less than %ul", s->name, s->max); else warn("%s must be less than %ld", s->name, s->max); goto get_input; } if (save) { if (s->type == SETTING_LONG) { sdata[0] = (lval >> 24) & 0xff; sdata[1] = (lval >> 16) & 0xff; sdata[2] = (lval >> 8) & 0xff; sdata[3] = lval & 0xff; } else { sdata[0] = (lval >> 8) & 0xff; sdata[1] = lval & 0xff; } } break; case SETTING_BOOL: if (save) sdata[0] = (stmp[0] == '1'); break; } } if (!save) { save = true; goto verify; } /* all validated ok and fields written */ settings_save(&tsettings); settings = tsettings; ret = true; settings_done: PasswordDialogFieldFinish(); DisposeDialog(dlg); ReleaseResource(dlgh); return ret; }