int MatchInAlphaList(AlphaList *al, char *string) { Item *ip; int i = (int) *string; if (isalnum(i) || *string == '_') { for (ip = al->list[i]; ip != NULL; ip = ip->next) { if (FullTextMatch(string, ip->name)) { return true; } } } else { // We don't know what the correct hash is because the pattern in vague for (i = 0; i < CF_ALPHABETSIZE; i++) { for (ip = al->list[i]; ip != NULL; ip = ip->next) { if (FullTextMatch(string, ip->name)) { return true; } } } } return false; }
void DefaultVarPromise(Promise *pp) { char *regex = GetConstraintValue("if_match_regex", pp, CF_SCALAR); Rval rval; enum cfdatatype dt; Rlist *rp; bool okay = true; dt = GetVariable("this", pp->promiser, &rval); switch (dt) { case cf_str: case cf_int: case cf_real: if (regex && !FullTextMatch(regex,rval.item)) { return; } if (regex == NULL) { return; } break; case cf_slist: case cf_ilist: case cf_rlist: if (regex) { for (rp = (Rlist *) rval.item; rp != NULL; rp = rp->next) { if (FullTextMatch(regex,rp->item)) { okay = false; break; } } if (okay) { return; } } break; default: break; } DeleteScalar(pp->bundle, pp->promiser); ConvergeVarHashPromise(pp->bundle, pp, true); }
static int SelectOwnerMatch(char *path, struct stat *lstatptr, Rlist *crit) { AlphaList leafattrib; Rlist *rp; char ownerName[CF_BUFSIZE]; int gotOwner; InitAlphaList(&leafattrib); #ifndef MINGW // no uids on Windows char buffer[CF_SMALLBUF]; sprintf(buffer, "%jd", (uintmax_t) lstatptr->st_uid); PrependAlphaList(&leafattrib, buffer); #endif /* MINGW */ gotOwner = GetOwnerName(path, lstatptr, ownerName, sizeof(ownerName)); if (gotOwner) { PrependAlphaList(&leafattrib, ownerName); } else { PrependAlphaList(&leafattrib, "none"); } for (rp = crit; rp != NULL; rp = rp->next) { if (EvalFileResult((char *) rp->item, &leafattrib)) { CfDebug(" - ? Select owner match\n"); DeleteAlphaList(&leafattrib); return true; } if (gotOwner && FullTextMatch((char *) rp->item, ownerName)) { CfDebug(" - ? Select owner match\n"); DeleteAlphaList(&leafattrib); return true; } #ifndef MINGW if (FullTextMatch((char *) rp->item, buffer)) { CfDebug(" - ? Select owner match\n"); DeleteAlphaList(&leafattrib); return true; } #endif /* NOT MINGW */ } DeleteAlphaList(&leafattrib); return false; }
static int SelectOwnerMatch(EvalContext *ctx, char *path, struct stat *lstatptr, Rlist *crit) { Rlist *rp; char ownerName[CF_BUFSIZE]; int gotOwner; StringSet *leafattrib = StringSetNew(); #ifndef __MINGW32__ // no uids on Windows char buffer[CF_SMALLBUF]; snprintf(buffer, CF_SMALLBUF, "%jd", (uintmax_t) lstatptr->st_uid); StringSetAdd(leafattrib, xstrdup(buffer)); #endif /* __MINGW32__ */ gotOwner = GetOwnerName(path, lstatptr, ownerName, sizeof(ownerName)); if (gotOwner) { StringSetAdd(leafattrib, xstrdup(ownerName)); } else { StringSetAdd(leafattrib, xstrdup("none")); } for (rp = crit; rp != NULL; rp = rp->next) { if (EvalFileResult((char *) rp->item, leafattrib)) { Log(LOG_LEVEL_DEBUG, "Select owner match"); StringSetDestroy(leafattrib); return true; } if (gotOwner && (FullTextMatch(ctx, RlistScalarValue(rp), ownerName))) { Log(LOG_LEVEL_DEBUG, "Select owner match"); StringSetDestroy(leafattrib); return true; } #ifndef __MINGW32__ if (FullTextMatch(ctx, RlistScalarValue(rp), buffer)) { Log(LOG_LEVEL_DEBUG, "Select owner match"); StringSetDestroy(leafattrib); return true; } #endif /* !__MINGW32__ */ } StringSetDestroy(leafattrib); return false; }
static int SelectGroupMatch(struct stat *lstatptr, Rlist *crit) { AlphaList leafattrib; char buffer[CF_SMALLBUF]; struct group *gr; Rlist *rp; InitAlphaList(&leafattrib); sprintf(buffer, "%jd", (uintmax_t) lstatptr->st_gid); PrependAlphaList(&leafattrib, buffer); if ((gr = getgrgid(lstatptr->st_gid)) != NULL) { PrependAlphaList(&leafattrib, gr->gr_name); } else { PrependAlphaList(&leafattrib, "none"); } for (rp = crit; rp != NULL; rp = rp->next) { if (EvalFileResult((char *) rp->item, &leafattrib)) { CfDebug(" - ? Select group match\n"); DeleteAlphaList(&leafattrib); return true; } if (gr && FullTextMatch((char *) rp->item, gr->gr_name)) { CfDebug(" - ? Select owner match\n"); DeleteAlphaList(&leafattrib); return true; } if (FullTextMatch((char *) rp->item, buffer)) { CfDebug(" - ? Select owner match\n"); DeleteAlphaList(&leafattrib); return true; } } DeleteAlphaList(&leafattrib); return false; }
static int SelectIsSymLinkTo(char *filename,struct Rlist *crit) { #ifndef MINGW char buffer[CF_BUFSIZE]; struct Rlist *rp; for (rp = crit; rp != NULL; rp = rp->next) { memset(buffer,0,CF_BUFSIZE); if (readlink(filename,buffer,CF_BUFSIZE-1) == -1) { CfOut(cf_error,"readlink","Unable to read link %s in filter",filename); return false; } if (FullTextMatch(rp->item,buffer)) { return true; } } #endif /* NOT MINGW */ return false; }
int CheckParseClass(char *lval, char *s, const char *range) { char output[CF_BUFSIZE]; if (s == NULL) { return false; } CfDebug("\nCheckParseClass(%s => %s/%s)\n", lval, s, range); if (strlen(range) == 0) { return true; } if (FullTextMatch(range, s)) { return true; } snprintf(output, CF_BUFSIZE, "Class item on rhs of lval \'%s\' given as { %s } is out of bounds (should match %s)", lval, s, range); ReportError(output); return false; }
int IsMatchItemIn(EvalContext *ctx, Item *list, const char *item) /* Solve for possible regex/fuzzy models unified */ { Item *ptr; if ((item == NULL) || (strlen(item) == 0)) { return true; } for (ptr = list; ptr != NULL; ptr = ptr->next) { if (FuzzySetMatch(ptr->name, item) == 0) { return (true); } if (IsRegex(ptr->name)) { if (FullTextMatch(ctx, ptr->name, item)) { return (true); } } } return (false); }
int SelectLastItemMatching(EvalContext *ctx, const char *regexp, Item *begin, Item *end, Item **match, Item **prev) { Item *ip, *ip_last = NULL, *ip_prev = CF_UNDEFINED_ITEM; *match = CF_UNDEFINED_ITEM; *prev = CF_UNDEFINED_ITEM; for (ip = begin; ip != end; ip = ip->next) { if (ip->name == NULL) { continue; } if (FullTextMatch(ctx, regexp, ip->name)) { *prev = ip_prev; ip_last = ip; } ip_prev = ip; } if (ip_last) { *match = ip_last; return true; } return false; }
int SelectNextItemMatching(const char *regexp, Item *begin, Item *end, Item **match, Item **prev) { Item *ip_prev = CF_UNDEFINED_ITEM; *match = CF_UNDEFINED_ITEM; *prev = CF_UNDEFINED_ITEM; for (Item *ip = begin; ip != end; ip = ip->next) { if (ip->name == NULL) { continue; } if (FullTextMatch(regexp, ip->name)) { *match = ip; *prev = ip_prev; return true; } ip_prev = ip; } return false; }
static int SelectExecRegexMatch(char *filename, char *crit, char *prog) { char line[CF_BUFSIZE]; FILE *pp; char buf[CF_MAXVARSIZE]; // insert real value of $(this.promiser) in command ReplaceStr(prog, buf, sizeof(buf), "$(this.promiser)", filename); ReplaceStr(prog, buf, sizeof(buf), "${this.promiser}", filename); if ((pp = cf_popen(buf, "r")) == NULL) { CfOut(cf_error, "cf_popen", "Couldn't open pipe to command %s\n", buf); return false; } while (!feof(pp)) { line[0] = '\0'; CfReadLine(line, CF_BUFSIZE, pp); /* One buffer only */ if (FullTextMatch(crit, line)) { cf_pclose(pp); return true; } } cf_pclose(pp); return false; }
static int SelectGroupMatch(EvalContext *ctx, struct stat *lstatptr, Rlist *crit) { char buffer[CF_SMALLBUF]; struct group *gr; Rlist *rp; StringSet *leafattrib = StringSetNew(); snprintf(buffer, CF_SMALLBUF, "%jd", (uintmax_t) lstatptr->st_gid); StringSetAdd(leafattrib, xstrdup(buffer)); if ((gr = getgrgid(lstatptr->st_gid)) != NULL) { StringSetAdd(leafattrib, xstrdup(gr->gr_name)); } else { StringSetAdd(leafattrib, xstrdup("none")); } for (rp = crit; rp != NULL; rp = rp->next) { if (EvalFileResult((char *) rp->item, leafattrib)) { Log(LOG_LEVEL_DEBUG, "Select group match"); StringSetDestroy(leafattrib); return true; } if (gr && (FullTextMatch(ctx, (char *) rp->item, gr->gr_name))) { Log(LOG_LEVEL_DEBUG, "Select group match"); StringSetDestroy(leafattrib); return true; } if (FullTextMatch(ctx, (char *) rp->item, buffer)) { Log(LOG_LEVEL_DEBUG, "Select group match"); StringSetDestroy(leafattrib); return true; } } StringSetDestroy(leafattrib); return false; }
int MatchRegion(EvalContext *ctx, const char *chunk, const Item *begin, const Item *end, bool regex) /* Match a region in between the selection delimiters. It is called after SelectRegion. The end delimiter will be visible here so we have to check for it. Can handle multi-line chunks */ { const Item *ip = begin; char buf[CF_BUFSIZE]; int lines = 0; for (const char *sp = chunk; sp <= chunk + strlen(chunk); sp++) { memset(buf, 0, CF_BUFSIZE); sscanf(sp, "%[^\n]", buf); sp += strlen(buf); if (ip == NULL) { return false; } if (!regex && strcmp(buf, ip->name) != 0) { return false; } if (regex && !FullTextMatch(ctx, buf, ip->name)) { return false; } lines++; // We have to manually exclude the marked terminator if (ip == end) { return false; } // Now see if there is more if (ip->next) { ip = ip->next; } else // if the region runs out before the end { if (++sp <= chunk + strlen(chunk)) { return false; } break; } } return lines; }
void CFulEditCtrl::Colorize(const tstring& aLine, int begin) { CHARFORMAT2 cf; cf.cbSize = sizeof(CHARFORMAT2); ColorList *cList = HighlightManager::getInstance()->getList(); int end = GetTextLengthEx(GTL_NUMCHARS); SetSel(begin, end); //otroligt fulhack, måste lagas riktigt nån gång SetSelectionCharFormat(selFormat); logged = false; //compare the last line against all strings in the vector for(ColorIter i = cList->begin(); i != cList->end(); ++i) { ColorSettings* cs = &(*i); int pos; //set start position for find if( cs->getIncludeNick() ) { pos = 0; } else { pos = aLine.find(_T(">")); if(pos == tstring::npos) pos = aLine.find(_T("**")) + nick.length(); } //prepare the charformat cf.dwMask = CFM_BOLD | CFM_UNDERLINE | CFM_STRIKEOUT | CFM_ITALIC; cf.dwEffects = 0; if(cs->getBold()) cf.dwEffects |= CFE_BOLD; if(cs->getItalic()) cf.dwEffects |= CFE_ITALIC; if(cs->getUnderline()) cf.dwEffects |= CFE_UNDERLINE; if(cs->getStrikeout()) cf.dwEffects |= CFE_STRIKEOUT; if(cs->getHasBgColor()){ cf.dwMask |= CFM_BACKCOLOR; cf.crBackColor = cs->getBgColor(); } if(cs->getHasFgColor()){ cf.dwMask |= CFM_COLOR; cf.crTextColor = cs->getFgColor(); } while( pos != string::npos ){ if(cs->usingRegexp()) pos = RegExpMatch(cs, cf, aLine, begin); else pos = FullTextMatch(cs, cf, aLine, pos, begin); } matchedPopup = false; matchedSound = false; }//end for }//end Colorize
static int SelectPathRegexMatch(char *filename, char *crit) { if (FullTextMatch(crit, filename)) { return true; } return false; }
static int SelectNameRegexMatch(const char *filename, char *crit) { if (FullTextMatch(crit, ReadLastNode(filename))) { return true; } return false; }
static int CheckParseString(char *lval, char *s, const char *range) { char output[CF_BUFSIZE]; CfDebug("\nCheckParseString(%s => %s/%s)\n", lval, s, range); if (s == NULL) { return true; } if (strlen(range) == 0) { return true; } if (IsNakedVar(s, '@') || IsNakedVar(s, '$')) { CfDebug("Validation: Unable to verify variable expansion of %s at this stage\n", s); return false; } /* Deal with complex strings as special cases */ if (strcmp(lval, "mode") == 0 || strcmp(lval, "search_mode") == 0) { mode_t plus, minus; if (!ParseModeString(s, &plus, &minus)) { snprintf(output, CF_BUFSIZE, "Error parsing Unix permission string %s)", s); ReportError(output); return false; } } if (FullTextMatch(range, s)) { return true; } if (IsCf3VarString(s)) { CfDebug("Validation: Unable to verify syntax of %s due to variable expansion at this stage\n", s); } else { snprintf(output, CF_BUFSIZE, "Scalar item in %s => { %s } in rvalue is out of bounds (value should match pattern %s)", lval, s, range); ReportError(output); return false; } return true; }
SyntaxTypeMatch CheckParseContext(const char *context, const char *range) { if (strlen(range) == 0) { return SYNTAX_TYPE_MATCH_OK; } if (FullTextMatch(range, context)) { return SYNTAX_TYPE_MATCH_OK; } return SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE; }
static bool IgnoreInterface(char *name) { Rlist *rp; for (rp = IGNORE_INTERFACES; rp != NULL; rp=rp->next) { if (FullTextMatch(rp->item,name)) { CfOut(OUTPUT_LEVEL_VERBOSE, "", " -> Ignoring interface \"%s\" because it matches %s",name,CF_IGNORE_INTERFACES); return true; } } return false; }
static size_t StringSetMatchCount(StringSet *set, const char *regex) { size_t count = 0; StringSetIterator it = StringSetIteratorInit(set); const char *context = NULL; while ((context = SetIteratorNext(&it))) { // TODO: used FullTextMatch to avoid regressions, investigate whether StringMatch can be used if (FullTextMatch(regex, context)) { count++; } } return count; }
static bool SelectExecRegexMatch(EvalContext *ctx, char *filename, char *crit, char *prog) { // insert real value of $(this.promiser) in command char *buf_tmp = SearchAndReplace(prog, "$(this.promiser)", filename); char *buf = SearchAndReplace(buf_tmp, "${this.promiser}", filename); free(buf_tmp); FILE *pp = cf_popen(buf, "r", true); if (pp == NULL) { Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", buf, GetErrorStr()); free(buf); return false; } size_t line_size = CF_BUFSIZE; char *line = xmalloc(line_size); for (;;) { ssize_t res = CfReadLine(&line, &line_size, pp); if (res == -1) { if (!feof(pp)) { Log(LOG_LEVEL_ERR, "Error reading output from command '%s'. (fgets: %s)", buf, GetErrorStr()); } cf_pclose(pp); free(line); free(buf); return false; } if (FullTextMatch(ctx, crit, line)) { cf_pclose(pp); free(line); free(buf); return true; } } cf_pclose(pp); free(line); free(buf); return false; }
static SyntaxTypeMatch CheckParseString(const char *lval, const char *s, const char *range) { CfDebug("\nCheckParseString(%s => %s/%s)\n", lval, s, range); if (s == NULL) { return SYNTAX_TYPE_MATCH_OK; } if (strlen(range) == 0) { return SYNTAX_TYPE_MATCH_OK; } if (IsNakedVar(s, '@') || IsNakedVar(s, '$')) { return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; } /* Deal with complex strings as special cases */ if (strcmp(lval, "mode") == 0 || strcmp(lval, "search_mode") == 0) { mode_t plus, minus; if (!ParseModeString(s, &plus, &minus)) { return SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION; } } if (FullTextMatch(range, s)) { return SYNTAX_TYPE_MATCH_OK; } if (IsCf3VarString(s)) { return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; } else { return SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE; } return SYNTAX_TYPE_MATCH_OK; }
static void VerifyOccurrenceGroup(char *file, Promise *pp) { Attributes a = { {0} }; struct stat sb; char *sp, url[CF_BUFSIZE]; Rval retval; a = GetOccurrenceAttributes(pp); if (cfstat(file, &sb) == -1) { CfOut(cf_verbose, "", " !! File %s matched but could not be read", file); return; } if (a.path_root == NULL || a.web_root == NULL) { CfOut(cf_error, "", " !! No pathroot/webroot defined in representation"); PromiseRef(cf_error, pp); return; } Chop(a.path_root); DeleteSlash(a.path_root); sp = file + strlen(a.path_root) + 1; FullTextMatch(pp->promiser, sp); retval = ExpandPrivateRval("this", (Rval) {a.represents, CF_LIST}); DeleteScope("match"); if (strlen(a.web_root) > 0) { snprintf(url, CF_BUFSIZE - 1, "%s/%s", a.web_root, sp); } else { snprintf(url, CF_BUFSIZE - 1, "%s", sp); } AddOccurrence(&OCCURRENCES, url, retval.item, cfk_url, pp->classes); CfOut(cf_verbose, "", " -> File %s matched and being logged at %s", file, url); DeleteRlist((Rlist *) retval.item); }
static bool SelectExecRegexMatch(EvalContext *ctx, char *filename, char *crit, char *prog) { char line[CF_BUFSIZE]; FILE *pp; char buf[CF_MAXVARSIZE]; // insert real value of $(this.promiser) in command ReplaceStr(prog, buf, sizeof(buf), "$(this.promiser)", filename); ReplaceStr(prog, buf, sizeof(buf), "${this.promiser}", filename); if ((pp = cf_popen(buf, "r", true)) == NULL) { Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", buf, GetErrorStr()); return false; } for (;;) { ssize_t res = CfReadLine(line, CF_BUFSIZE, pp); if (res == -1) { Log(LOG_LEVEL_ERR, "Error reading output from command '%s'. (fgets: %s)", buf, GetErrorStr()); cf_pclose(pp); return false; } if (res == 0) { cf_pclose(pp); return false; } if (FullTextMatch(ctx, crit, line)) { cf_pclose(pp); return true; } } cf_pclose(pp); return false; }
static int SelectIsSymLinkTo(EvalContext *ctx, char *filename, Rlist *crit) { #ifndef __MINGW32__ char buffer[CF_BUFSIZE]; Rlist *rp; for (rp = crit; rp != NULL; rp = rp->next) { memset(buffer, 0, CF_BUFSIZE); struct stat statbuf; // Don't worry if this gives an error, that's handled above us. // We're calling lstat() here to avoid dereferencing the // symlink... and we only care if the inode is a directory. if (lstat(filename, &statbuf) == -1) { // Do nothing. } else if (S_ISDIR(statbuf.st_mode)) { Log(LOG_LEVEL_DEBUG, "Skipping readlink() on directory %s", filename); return false; } if (readlink(filename, buffer, CF_BUFSIZE - 1) == -1) { Log(LOG_LEVEL_ERR, "Unable to read link '%s' in filter. (readlink: %s)", filename, GetErrorStr()); return false; } if (FullTextMatch(ctx, rp->item, buffer)) { return true; } } #endif /* !__MINGW32__ */ return false; }
bool RlistIsInListOfRegex(EvalContext *ctx, const Rlist *list, const char *str) { if (str == NULL || list == NULL) { return false; } for (const Rlist *rp = list; rp != NULL; rp = rp->next) { if (rp->val.type != RVAL_TYPE_SCALAR) { continue; } if (FullTextMatch(ctx, RlistScalarValue(rp), str)) { return true; } } return false; }
bool RlistIsInListOfRegex(const Rlist *list, const char *str) { if (str == NULL || list == NULL) { return false; } for (const Rlist *rp = list; rp != NULL; rp = rp->next) { if (rp->type != RVAL_TYPE_SCALAR) { continue; } if (FullTextMatch(rp->item, str)) { return true; } } return false; }
static int SelectIsSymLinkTo(char *filename, Rlist *crit) { #ifndef __MINGW32__ char buffer[CF_BUFSIZE]; Rlist *rp; for (rp = crit; rp != NULL; rp = rp->next) { memset(buffer, 0, CF_BUFSIZE); if (readlink(filename, buffer, CF_BUFSIZE - 1) == -1) { CfOut(OUTPUT_LEVEL_ERROR, "readlink", "Unable to read link %s in filter", filename); return false; } if (FullTextMatch(rp->item, buffer)) { return true; } } #endif /* !__MINGW32__ */ return false; }
static int SelectProcRegexMatch(EvalContext *ctx, char *name1, char *name2, char *regex, char **colNames, char **line) { int i; if (regex == NULL) { return false; } if ((i = GetProcColumnIndex(name1, name2, colNames)) != -1) { if (FullTextMatch(ctx, regex, line[i])) { return true; } else { return false; } } return false; }
void VerifyClassPromise(EvalContext *ctx, Promise *pp, ARG_UNUSED void *param) { assert(param == NULL); Attributes a; a = GetClassContextAttributes(ctx, pp); if (!FullTextMatch("[a-zA-Z0-9_]+", pp->promiser)) { Log(LOG_LEVEL_VERBOSE, "Class identifier '%s' contains illegal characters - canonifying", pp->promiser); snprintf(pp->promiser, strlen(pp->promiser) + 1, "%s", CanonifyName(pp->promiser)); } if (a.context.nconstraints == 0) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "No constraints for class promise '%s'", pp->promiser); return; } if (a.context.nconstraints > 1) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Irreconcilable constraints in classes for '%s'", pp->promiser); return; } bool global_class; if (a.context.persistent > 0) /* Persistent classes are always global */ { global_class = true; } else if (a.context.scope == CONTEXT_SCOPE_NONE) { /* If there is no explicit scope, common bundles define global classes, other bundles define local classes */ if (strcmp(PromiseGetBundle(pp)->type, "common") == 0) { global_class = true; } else { global_class = false; } } else if (a.context.scope == CONTEXT_SCOPE_NAMESPACE) { global_class = true; } else if (a.context.scope == CONTEXT_SCOPE_BUNDLE) { global_class = false; } if (EvalClassExpression(ctx, a.context.expression, pp)) { if (!ValidClassName(pp->promiser)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Attempted to name a class '%s', which is an illegal class identifier", pp->promiser); } else { if (global_class) { Log(LOG_LEVEL_VERBOSE, "Adding global class '%s'", pp->promiser); EvalContextHeapAddSoft(ctx, pp->promiser, PromiseGetNamespace(pp)); } else { Log(LOG_LEVEL_VERBOSE, "Adding local bundle class '%s'", pp->promiser); EvalContextStackFrameAddSoft(ctx, pp->promiser); } if (a.context.persistent > 0) { Log(LOG_LEVEL_VERBOSE, "Adding persistent class '%s'. (%d minutes)", pp->promiser, a.context.persistent); EvalContextHeapPersistentSave(pp->promiser, PromiseGetNamespace(pp), a.context.persistent, CONTEXT_STATE_POLICY_RESET); } } } }