greenhouse

greenhouse

forge suite

git clone [email protected]:greenhouse.git

feat(petal): harden against untrusted input and bound work

commit322994003fbbd7f9aa86c1ba1a1f9968eef1c3ad
parent4fdd089732dd628cdabaa2ae8fbc54a20b528d6a
authorlostduk <[email protected]>
date2026-10-14 19:31 +0000
petal/config.def.h3+
petal/petal.c331++++++++++++++++++++--------------------

2 files changed, -164/+170

petal/config.def.h

@@ -7,6 +7,9 @@ static const char *service = "git"; static const int loglimit  = 100; static const int maxdepth  = 8; static const int barwidth  = 40;+static const int maxblob   = 512 * 1024;+static const int maxdiff   = 10000;+static const int maxcpu    = 10;  static const Service services[] = { 	{ "git",   "https://git.lostduk.com/" },

petal/petal.c

@@ -7,14 +7,15 @@ #include <stdlib.h> #include <string.h> #include <time.h>+#include <sys/resource.h> #include <sys/stat.h> #include <git2.h>  /* macros */ #define LENGTH(X) (sizeof(X) / sizeof((X)[0]))-#define HTML      "text/html; charset=utf-8" #define HOUR      3600L #define DAY       (24 * HOUR)+#define MAXTIME   253402300799LL  /* enums */ typedef struct {@@ -24,16 +25,16 @@ typedef struct {  /* function declarations */ static int bprintf(char *buf, size_t len, const char *fmt, ...);-static int commitdiff(git_diff **diff, git_commit *commit);+static int commitdiff(git_diff **diff, git_commit *commit, int renames); static void commitpage(const char *id); static const char *envor(const char *name, const char *fallback); static void filepage(const char *path, const git_tree_entry *e); static int findref(char *spec, char **rest);-static int headcommit(git_commit **commit, git_repository *r); static void httpheader(int status, const char *type); static void indexpage(void); static void indexrow(const char *name); static int isrepo(const char *name);+static int loadcommit(const char *id); static int loadref(git_reference *r); static void logpage(void); static void logrow(const git_oid *oid);@@ -43,14 +44,13 @@ static int openrepo(git_repository **r, const char *name); static void pagebegin(int status, const char *pagetitle); static void pageend(void); static void printage(const git_time *t);-static void printdiff(git_diff *diff);+static void printdiff(git_diff *diff, git_patch **patches); static void printname(const char *name); static void printpath(const char *path);-static void printstat(git_diff *diff);+static void printstat(git_diff *diff, git_patch **patches); static void printtime(const git_time *t, const char *fmt); static void printurl(const char *page, const char *r, const char *path); static void rawpage(const char *path);-static void readdesc(const char *name, char *buf, size_t len); static void readfile(const char *name, const char *file, char *buf,                      size_t len); static void refrows(const char *glob);@@ -62,9 +62,8 @@ static void treerow(const git_tree_entry *e, const char *path); static void treetable(const git_tree *tree, const char *path); static void urlencode(const char *s); static int validname(const char *s);+static int validtime(const git_time *t); static void walk(const char *rel, int depth);-static void walkent(const char *dir, const char *rel, const char *e,-                    int depth); static void xmlencode(const char *s); static void xmlencoden(const char *s, size_t len); @@ -75,6 +74,7 @@ static const char refshead[] = static const char *base; static const char *reponame; static git_repository *repo;+static git_odb *odb; static char ref[PATH_MAX]; static git_oid tip; static long ofs;@@ -97,7 +97,7 @@ bprintf(char *buf, size_t len, const char *fmt, ...) }  int-commitdiff(git_diff **diff, git_commit *commit)+commitdiff(git_diff **diff, git_commit *commit, int renames) { 	git_commit *parent = NULL; 	git_tree *tree = NULL, *ptree = NULL;@@ -122,7 +122,8 @@ commitdiff(git_diff **diff, git_commit *commit) 	} 	if ((err = git_diff_tree_to_tree(diff, repo, ptree, tree, &opts)) < 0) 		goto end;-	err = git_diff_find_similar(*diff, &fopts);+	if (renames)+		err = git_diff_find_similar(*diff, &fopts); end: 	if (err < 0) { 		git_diff_free(*diff);@@ -137,20 +138,17 @@ end: void commitpage(const char *id) {-	char spec[128];-	git_object *obj; 	git_commit *c; 	git_diff *diff;+	git_patch **patches = NULL; 	const git_signature *a; 	const char *body, *pid;-	unsigned int i;+	size_t i, n = 0; -	if (bprintf(spec, sizeof(spec), "%s^{commit}", id) < 0-	|| git_revparse_single(&obj, repo, spec) < 0) {+	if (loadcommit(id) < 0 || git_commit_lookup(&c, repo, &tip) < 0) { 		notfound(); 		return; 	}-	c = (git_commit *)obj; 	a = git_commit_author(c);  	pagebegin(200, git_commit_summary(c));@@ -177,11 +175,23 @@ commitpage(const char *id) 	xmlencode(a->email); 	fputs("&gt;</td></tr>\n<tr><td>date</td><td>", stdout); 	printtime(&a->when, "%Y-%m-%d %H:%M");-	printf(" %c%02d%02d</td></tr>\n</table>\n", a->when.sign,-		abs(a->when.offset) / 60, abs(a->when.offset) % 60);-	if (commitdiff(&diff, c) >= 0) {-		printstat(diff);-		printdiff(diff);+	if (validtime(&a->when))+		printf(" %c%02d%02d", a->when.sign, abs(a->when.offset) / 60,+			abs(a->when.offset) % 60);+	fputs("</td></tr>\n</table>\n", stdout);+	if (commitdiff(&diff, c, 1) >= 0) {+		n = git_diff_num_deltas(diff);+		patches = calloc(n + 1, sizeof(*patches));+	}+	if (patches) {+		for (i = 0; i < n; i++)+			if (git_patch_from_diff(&patches[i], diff, i) < 0)+				patches[i] = NULL;+		printstat(diff, patches);+		printdiff(diff, patches);+		for (i = 0; i < n; i++)+			git_patch_free(patches[i]);+		free(patches); 	} 	pageend(); 	git_diff_free(diff);@@ -200,34 +210,38 @@ envor(const char *name, const char *fallback) void filepage(const char *path, const git_tree_entry *e) {-	git_blob *blob;-	const char *s, *nl;-	size_t len, i, l, n, lines;-	int binary;+	git_blob *blob = NULL;+	git_object_t type;+	const char *s = NULL, *nl;+	size_t len, i, l, n, lines = 0; -	if (git_blob_lookup(&blob, repo, git_tree_entry_id(e)) < 0) {+	if (git_odb_read_header(&len, &type, odb, git_tree_entry_id(e)) < 0+	|| (len <= (size_t)maxblob+	&& git_blob_lookup(&blob, repo, git_tree_entry_id(e)) < 0)) { 		notfound(); 		return; 	}-	s = git_blob_rawcontent(blob);-	len = (size_t)git_blob_rawsize(blob);-	binary = git_blob_is_binary(blob);-	for (i = 0, lines = 0; i < len; i++)-		if (s[i] == '\n')+	if (blob && !git_blob_is_binary(blob)) {+		s = git_blob_rawcontent(blob);+		for (i = 0; i < len; i++)+			if (s[i] == '\n')+				lines++;+		if (len && s[len - 1] != '\n') 			lines++;-	if (len && s[len - 1] != '\n')-		lines++;+	}  	pagebegin(200, reponame); 	repoheader("tree"); 	printpath(path); 	fputs("<span class=\"info\">", stdout);-	if (!binary)+	if (s) 		printf("%zu lines &middot; ", lines); 	printf("%zuB &middot; <a href=\"", len); 	printurl("raw", ref, path); 	fputs("\">raw</a></span></p>\n", stdout);-	if (binary) {+	if (!blob) {+		fputs("<p>File too large.</p>\n", stdout);+	} else if (!s) { 		fputs("<p>Binary file.</p>\n", stdout); 	} else { 		fputs("<pre class=\"blob\">", stdout);@@ -252,8 +266,8 @@ findref(char *spec, char **rest) 	char *cut = NULL, *p;  	for (;;) {-		if (git_reference_dwim(&r, repo, spec) >= 0-		&& loadref(r) >= 0) {+		if ((git_reference_dwim(&r, repo, spec) >= 0+		&& loadref(r) >= 0) || loadcommit(spec) >= 0) { 			*rest = cut ? cut + 1 : spec + strlen(spec); 			return 0; 		}@@ -269,24 +283,15 @@ findref(char *spec, char **rest) 	} } -int-headcommit(git_commit **commit, git_repository *r)-{-	git_object *obj;--	if (git_revparse_single(&obj, r, "HEAD^{commit}") < 0)-		return -1;-	*commit = (git_commit *)obj;-	return 0;-}- void httpheader(int status, const char *type) { 	if (status == 404) 		fputs("Status: 404 Not Found\r\n", stdout); 	printf("Content-Type: %s\r\n", type);-	fputs("X-Content-Type-Options: nosniff\r\n\r\n", stdout);+	fputs("Content-Security-Policy: default-src 'none'; "+		"style-src 'self'; img-src 'self'\r\n"+		"X-Content-Type-Options: nosniff\r\n\r\n", stdout); }  void@@ -307,9 +312,9 @@ indexrow(const char *name) { 	char desc[256], owner[256]; 	git_repository *r = NULL;-	git_commit *commit = NULL;+	git_object *obj = NULL; -	readdesc(name, desc, sizeof(desc));+	readfile(name, "description", desc, sizeof(desc)); 	readfile(name, "owner", owner, sizeof(owner)); 	fputs("<tr><td class=\"name\"><a href=\"", stdout); 	xmlencode(base);@@ -322,12 +327,12 @@ indexrow(const char *name) 	fputs("</td><td class=\"owner sm\">", stdout); 	xmlencode(owner); 	fputs("</td>", stdout);-	if (openrepo(&r, name) >= 0 && headcommit(&commit, r) >= 0)-		printage(&git_commit_committer(commit)->when);-	else-		fputs("<td class=\"r age a4\">&mdash;</td>", stdout);+	if (openrepo(&r, name) >= 0+	&& git_revparse_single(&obj, r, "HEAD^{commit}") < 0)+		obj = NULL;+	printage(obj ? &git_commit_committer((git_commit *)obj)->when : NULL); 	fputs("</tr>\n", stdout);-	git_commit_free(commit);+	git_object_free(obj); 	git_repository_free(r); } @@ -339,11 +344,28 @@ isrepo(const char *name)  	if (!validname(name)) 		return 0;-	if (bprintf(path, sizeof(path), "%s/%s.git", root, name) < 0)-		return 0;-	if (stat(path, &st) < 0)+	if (bprintf(path, sizeof(path), "%s/%s.git/git-daemon-export-ok",+	    root, name) < 0) 		return 0;-	return S_ISDIR(st.st_mode);+	return stat(path, &st) >= 0;+}++int+loadcommit(const char *id)+{+	git_oid oid;+	git_commit *c;+	size_t len;++	len = strlen(id);+	if (len < 4 || len > GIT_OID_SHA1_HEXSIZE+	|| git_oid_fromstrn(&oid, id, len) < 0+	|| git_commit_lookup_prefix(&c, repo, &oid, len) < 0)+		return -1;+	git_oid_cpy(&tip, git_commit_id(c));+	git_oid_tostr(ref, sizeof(ref), &tip);+	git_commit_free(c);+	return 0; }  int@@ -397,16 +419,12 @@ logpage(void) 			if (ofs > loglimit) 				printf("?ofs=%ld", ofs - loglimit); 			fputs("\">&larr; newer</a>", stdout);-		} else {-			fputs("<span>&larr; newer</span>", stdout); 		} 		printf("<span>%ld&ndash;%ld</span>", ofs + 1, ofs + n); 		if (more) { 			fputs("<a href=\"", stdout); 			printurl("log", ref, NULL); 			printf("?ofs=%ld\">older &rarr;</a>", ofs + n);-		} else {-			fputs("<span>older &rarr;</span>", stdout); 		} 		fputs("</nav>\n", stdout); 	}@@ -435,7 +453,7 @@ logrow(const git_oid *oid) 	xmlencode(git_commit_summary(commit)); 	fputs("</a></td><td class=\"sm\">", stdout); 	xmlencode(author->name);-	if (commitdiff(&diff, commit) >= 0)+	if (commitdiff(&diff, commit, 0) >= 0) 		git_diff_get_stats(&stats, diff); 	if (stats) 		printf("</td><td class=\"r num sm\">%zu</td>"@@ -487,7 +505,7 @@ pagebegin(int status, const char *pagetitle) { 	size_t i; -	httpheader(status, HTML);+	httpheader(status, "text/html; charset=utf-8"); 	fputs("<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n" 		"<meta name=\"viewport\" " 		"content=\"width=device-width, initial-scale=1\">\n<title>",@@ -543,6 +561,10 @@ printage(const git_time *t) 	long d, n; 	size_t i, level; +	if (!t || !validtime(t)) {+		fputs("<td class=\"r age a4\">&mdash;</td>", stdout);+		return;+	} 	if ((d = (long)(time(NULL) - (time_t)t->time)) < 0) 		d = 0; 	for (i = 0; d >= upto[i]; i++)@@ -557,18 +579,17 @@ printage(const git_time *t) }  void-printdiff(git_diff *diff)+printdiff(git_diff *diff, git_patch **patches) {-	git_patch *patch; 	const git_diff_delta *delta; 	const git_diff_hunk *hunk; 	const git_diff_line *line;-	size_t i, h, l, nhunks, nlines, len;+	git_patch *p;+	size_t i, h, l, nlines, len, total = 0;  	for (i = 0; i < git_diff_num_deltas(diff); i++) {-		if (git_patch_from_diff(&patch, diff, i) < 0)-			continue;-		delta = git_patch_get_delta(patch);+		delta = git_diff_get_delta(diff, i);+		p = patches[i]; 		printf("<h3 id=\"f%zu\">", i); 		if (strcmp(delta->old_file.path, delta->new_file.path)) { 			xmlencode(delta->old_file.path);@@ -578,9 +599,8 @@ printdiff(git_diff *diff) 		fputs("</h3>\n<pre class=\"diff\">", stdout); 		if (delta->flags & GIT_DIFF_FLAG_BINARY) 			fputs("<span>Binary files differ.</span>", stdout);-		nhunks = git_patch_num_hunks(patch);-		for (h = 0; h < nhunks; h++) {-			if (git_patch_get_hunk(&hunk, &nlines, patch, h) < 0)+		for (h = 0; p && h < git_patch_num_hunks(p); h++) {+			if (git_patch_get_hunk(&hunk, &nlines, p, h) < 0) 				break; 			len = hunk->header_len; 			while (len && hunk->header[len - 1] == '\n')@@ -589,8 +609,10 @@ printdiff(git_diff *diff) 			xmlencoden(hunk->header, len); 			fputs("</span>", stdout); 			for (l = 0; l < nlines; l++) {-				if (git_patch_get_line_in_hunk(&line, patch,-				    h, l) < 0)+				if (total++ >= (size_t)maxdiff)+					goto cut;+				if (git_patch_get_line_in_hunk(&line, p, h,+				    l) < 0) 					break; 				if (line->origin == GIT_DIFF_LINE_ADDITION) 					fputs("<span class=\"i\">+", stdout);@@ -609,8 +631,10 @@ printdiff(git_diff *diff) 			} 		} 		fputs("</pre>\n", stdout);-		git_patch_free(patch); 	}+	return;+cut:+	fputs("</pre>\n<p>Diff truncated.</p>\n", stdout); }  void@@ -662,36 +686,33 @@ printpath(const char *path) }  void-printstat(git_diff *diff)+printstat(git_diff *diff, git_patch **patches) {-	git_patch *patch;-	size_t i, j, n, add, del, max = 0, tadd = 0, tdel = 0;-	size_t *st;+	size_t i, j, n, add, del, w, max = 0, tadd = 0, tdel = 0;  	n = git_diff_num_deltas(diff);-	if (!(st = calloc(n * 2 + 1, sizeof(*st))))-		return; 	for (i = 0; i < n; i++) {-		if (git_patch_from_diff(&patch, diff, i) < 0)-			continue;-		git_patch_line_stats(NULL, &st[i * 2], &st[i * 2 + 1], patch);-		git_patch_free(patch);-		if (st[i * 2] + st[i * 2 + 1] > max)-			max = st[i * 2] + st[i * 2 + 1];+		add = del = 0;+		if (patches[i])+			git_patch_line_stats(NULL, &add, &del, patches[i]);+		if (add + del > max)+			max = add + del; 	} 	fputs("<table class=\"stat\">\n", stdout); 	for (i = 0; i < n; i++) {-		add = st[i * 2];-		del = st[i * 2 + 1];+		add = del = 0;+		if (patches[i])+			git_patch_line_stats(NULL, &add, &del, patches[i]); 		tadd += add; 		tdel += del; 		printf("<tr><td><a href=\"#f%zu\">", i); 		xmlencode(git_diff_get_delta(diff, i)->new_file.path); 		printf("</a></td><td class=\"r num\">%zu</td>" 			"<td class=\"bar\"><span class=\"ins\">", add + del);-		if (max > (size_t)barwidth) {-			add = (add * barwidth + max - 1) / max;-			del = (del * barwidth + max - 1) / max;+		if (add + del && max > (size_t)barwidth) {+			w = ((add + del) * barwidth + max - 1) / max;+			add = (add * w + (add + del) / 2) / (add + del);+			del = w - add; 		} 		for (j = 0; j < add; j++) 			putchar('+');@@ -704,7 +725,6 @@ printstat(git_diff *diff) 		"<span class=\"del\">-%zu</span>/" 		"<span class=\"ins\">+%zu</span></p>\n", 		n, n == 1 ? "" : "s", tdel, tadd);-	free(st); }  void@@ -714,6 +734,8 @@ printtime(const git_time *t, const char *fmt) 	time_t secs; 	char buf[32]; +	if (!validtime(t))+		return; 	secs = (time_t)t->time + (time_t)t->offset * 60; 	if (!gmtime_r(&secs, &tm)) 		return;@@ -766,14 +788,6 @@ end: }  void-readdesc(const char *name, char *buf, size_t len)-{-	readfile(name, "description", buf, len);-	if (!strncmp(buf, "Unnamed repository", 18))-		buf[0] = '\0';-}--void readfile(const char *name, const char *file, char *buf, size_t len) { 	char path[PATH_MAX];@@ -787,6 +801,8 @@ readfile(const char *name, const char *file, char *buf, size_t len) 	if (fgets(buf, (int)len, fp)) 		buf[strcspn(buf, "\r\n")] = '\0'; 	fclose(fp);+	if (!strncmp(buf, "Unnamed repository", 18))+		buf[0] = '\0'; }  void@@ -841,7 +857,7 @@ repoheader(const char *tab) 	char desc[256]; 	size_t i; -	readdesc(reponame, desc, sizeof(desc));+	readfile(reponame, "description", desc, sizeof(desc)); 	fputs("<header>\n<h1>", stdout); 	printname(reponame); 	fputs("</h1>\n", stdout);@@ -894,9 +910,9 @@ route(char *path) 		*cut = '\0'; 		rest = cut + 1; 	}-	if (openrepo(&repo, path) < 0) {+	if (openrepo(&repo, path) < 0 || git_repository_odb(&odb, repo) < 0) { 		notfound();-		return;+		goto end; 	} 	reponame = path; 	if (git_repository_head(&r, repo) >= 0)@@ -929,6 +945,8 @@ route(char *path) 	} else { 		notfound(); 	}+end:+	git_odb_free(odb); 	git_repository_free(repo); } @@ -977,8 +995,8 @@ treerow(const git_tree_entry *e, const char *path) { 	char child[PATH_MAX]; 	const char *name;-	git_object_t type;-	git_blob *blob;+	git_object_t type, otype;+	size_t len;  	name = git_tree_entry_name(e); 	type = git_tree_entry_type(e);@@ -998,44 +1016,29 @@ treerow(const git_tree_entry *e, const char *path) 		fputs(type == GIT_OBJECT_TREE ? "/</a>" : "</a>", stdout); 	} 	fputs("</td><td class=\"r num\">", stdout);-	if (type == GIT_OBJECT_BLOB-	&& git_blob_lookup(&blob, repo, git_tree_entry_id(e)) >= 0) {-		printf("%lluB", (unsigned long long)git_blob_rawsize(blob));-		git_blob_free(blob);-	}+	if (type == GIT_OBJECT_BLOB && git_odb_read_header(&len, &otype, odb,+	    git_tree_entry_id(e)) >= 0)+		printf("%zuB", len); 	fputs("</td></tr>\n", stdout); }  void treetable(const git_tree *tree, const char *path) {-	char parent[PATH_MAX]; 	const git_tree_entry *e;-	const char *p; 	size_t i, n;-	int pass, isdir;+	int pass;  	fputs("<table class=\"tree\">\n<thead><tr><th class=\"sm\">Mode</th>" 		"<th>Name</th><th class=\"r\">Size</th></tr></thead>\n" 		"<tbody>\n", stdout);-	if (*path) {-		p = strrchr(path, '/');-		if (bprintf(parent, sizeof(parent), "%.*s",-		    p ? (int)(p - path) : 0, path) < 0)-			return;-		fputs("<tr><td class=\"mode sm\">d---------</td>"-			"<td class=\"dirent\"><a href=\"", stdout);-		printurl("tree", ref, parent);-		fputs("\">../</a></td><td></td></tr>\n", stdout);-	} 	n = git_tree_entrycount(tree); 	for (pass = 0; pass < 2; pass++) { 		for (i = 0; i < n; i++) { 			e = git_tree_entry_byindex(tree, i);-			isdir = git_tree_entry_type(e) == GIT_OBJECT_TREE;-			if (isdir == pass)-				continue;-			treerow(e, path);+			if ((git_tree_entry_type(e) == GIT_OBJECT_TREE)+			    != pass)+				treerow(e, path); 		} 	} 	fputs("</tbody>\n</table>\n", stdout);@@ -1077,49 +1080,46 @@ validname(const char *s) 	return !start; } -void-walk(const char *rel, int depth)+int+validtime(const git_time *t) {-	char dir[PATH_MAX];-	struct dirent **ents;-	int i, n;--	if (depth > maxdepth)-		return;-	if (bprintf(dir, sizeof(dir), "%s/%s", root, rel) < 0)-		return;-	if ((n = scandir(dir, &ents, NULL, alphasort)) < 0)-		return;-	for (i = 0; i < n; i++) {-		walkent(dir, rel, ents[i]->d_name, depth);-		free(ents[i]);-	}-	free(ents);+	return t->time >= 0 && t->time <= MAXTIME+		&& t->offset >= -1440 && t->offset <= 1440; }  void-walkent(const char *dir, const char *rel, const char *e, int depth)+walk(const char *rel, int depth) { 	char path[PATH_MAX], name[PATH_MAX];+	struct dirent **ents; 	struct stat st;+	const char *e; 	size_t len;+	int i, n; -	if (e[0] == '.')-		return;-	if (bprintf(path, sizeof(path), "%s/%s", dir, e) < 0)-		return;-	if (lstat(path, &st) < 0 || !S_ISDIR(st.st_mode))-		return;-	if (bprintf(name, sizeof(name), "%s%s%s", rel, *rel ? "/" : "", e) < 0)-		return;-	len = strlen(e);-	if (len <= 4 || strcmp(e + len - 4, ".git")) {-		walk(name, depth + 1);+	if (depth > maxdepth+	|| bprintf(path, sizeof(path), "%s/%s", root, rel) < 0+	|| (n = scandir(path, &ents, NULL, alphasort)) < 0) 		return;+	for (i = 0; i < n; i++) {+		e = ents[i]->d_name;+		len = strlen(e);+		if (e[0] != '.'+		&& bprintf(name, sizeof(name), "%s%s%s", rel, *rel ? "/" : "",+		    e) >= 0+		&& bprintf(path, sizeof(path), "%s/%s", root, name) >= 0+		&& lstat(path, &st) >= 0 && S_ISDIR(st.st_mode)) {+			if (len > 4 && !strcmp(e + len - 4, ".git")) {+				name[strlen(name) - 4] = '\0';+				if (isrepo(name))+					indexrow(name);+			} else {+				walk(name, depth + 1);+			}+		}+		free(ents[i]); 	}-	name[strlen(name) - 4] = '\0';-	if (validname(name))-		indexrow(name);+	free(ents); }  void@@ -1148,9 +1148,12 @@ xmlencoden(const char *s, size_t len) int main(void) {-	char path[PATH_MAX];+	char path[1024]; 	const char *q;+	struct rlimit rl; +	rl.rlim_cur = rl.rlim_max = maxcpu;+	setrlimit(RLIMIT_CPU, &rl); 	root = envor("PETAL_ROOT", root); 	css = envor("PETAL_CSS", css); 	icon = envor("PETAL_ICON", icon);