/* Expand ${token} references in a path or string. */ static char *stemplateInner(cchar *str, void *keys, int json) { MprBuf *buf; cchar *value; char *src, *result, *cp, *tok; if (str) { if (schr(str, '$') == 0) { return sclone(str); } buf = mprCreateBuf(0, 0); for (src = (char*) str; *src; ) { if (*src == '$') { if (*++src == '{') { for (cp = ++src; *cp && *cp != '}'; cp++) ; tok = snclone(src, cp - src); } else { for (cp = src; *cp && (isalnum((uchar) *cp) || *cp == '_'); cp++) ; tok = snclone(src, cp - src); } if (json) { value = mprGetJson(keys, tok); } else { value = mprLookupKey(keys, tok); } if (value != 0) { mprPutStringToBuf(buf, value); if (src > str && src[-1] == '{') { src = cp + 1; } else { src = cp; } } else { mprPutCharToBuf(buf, '$'); if (src > str && src[-1] == '{') { mprPutCharToBuf(buf, '{'); } mprPutCharToBuf(buf, *src++); } } else { mprPutCharToBuf(buf, *src++); } } mprAddNullToBuf(buf); result = sclone(mprGetBufStart(buf)); } else { result = MPR->emptyString; } return result; }
static int srmatch(cchar *s, void *pattern, ...) { va_list ap; char **str; ssize len; int count, i, index, matches[64 * 2]; va_start(ap, pattern); count = pcre_exec(pattern, NULL, s, (int) slen(s), 0, 0, matches, sizeof(matches) / sizeof(int)); for (i = 0, str = 0; i < count; i++) { str = va_arg(ap, char**); if (str == NULL) { break; } index = i * 2; len = matches[index + 1] - matches[index]; *str = snclone(&s[matches[index]], len); } if (str) { while ((str = va_arg(ap, char**)) != 0) { *str = 0; } } va_end(ap); return count / 2; }
static void test_snclone(void) { char* clone = snclone("abc", 2); PCU_ASSERT_PTR_NOT_NULL(clone); PCU_ASSERT_STRING_EQUAL("ab", clone); free(clone); }
static void test_snclone_empty(void) { char* clone = snclone("", 0); PCU_ASSERT_PTR_NOT_NULL(clone); PCU_ASSERT_STRING_EQUAL("", clone); free(clone); }
/* String to list. This parses the string into space separated arguments. Single and double quotes are supported. This returns a stable list. */ PUBLIC MprList *stolist(cchar *src) { MprList *list; cchar *start; int quote; list = mprCreateList(0, MPR_LIST_STABLE); while (src && *src != '\0') { while (isspace((uchar) *src)) { src++; } if (*src == '\0') { break; } for (quote = 0, start = src; *src; src++) { if (*src == '\\') { src++; } else if (*src == '"' || *src == '\'') { if (*src == quote) { src++; break; } else if (quote == 0) { quote = *src; } } else if (isspace((uchar) *src) && !quote) { break; } } mprAddItem(list, snclone(start, src - start)); } return list; }
static void traceEvent(HttpConn *conn, int event, int arg) { HttpPacket *packet; if (event == HTTP_EVENT_READABLE) { packet = conn->readq->first; mprLog(3, "websock.c: read %s event, last %d", packet->type == WS_MSG_TEXT ? "text" : "binary", packet->last); mprLog(3, "websock.c: read: (start of data only) \"%s\"", snclone(mprGetBufStart(packet->content), 40)); } else if (event == HTTP_EVENT_APP_CLOSE) { mprLog(3, "websock.c: close event. Status status %d, orderly closed %d, reason %s", arg, httpWebSocketOrderlyClosed(conn), httpGetWebSocketCloseReason(conn)); } else if (event == HTTP_EVENT_ERROR) { mprLog(2, "websock.c: error event"); } }
static void echo_callback(HttpConn *conn, int event, int arg) { HttpPacket *packet; HttpWebSocket *ws; cchar *data; traceEvent(conn, event, arg); if (event == HTTP_EVENT_READABLE) { packet = httpGetPacket(conn->readq); assure(packet); /* Ignore precedding packets and just trace the last */ if (packet->last) { ws = conn->rx->webSocket; httpSend(conn, "{type: %d, last: %d, length: %d, data: \"%s\"}\n", packet->type, packet->last, ws->messageLength, snclone(mprGetBufStart(packet->content), 10)); } } }
/* Parse cached content of the form: headers \n\n data Set headers in the current requeset and return a reference to the data portion */ static cchar *setHeadersFromCache(HttpConn *conn, cchar *content) { cchar *data; char *header, *headers, *key, *value, *tok; if ((data = strstr(content, "\n\n")) == 0) { data = content; } else { headers = snclone(content, data - content); data += 2; for (header = stok(headers, "\n", &tok); header; header = stok(NULL, "\n", &tok)) { key = stok(header, ": ", &value); if (smatch(key, "X-Status")) { conn->tx->status = (int) stoi(value); } else { httpAddHeader(conn, key, value); } } } return data; }
static bool inRange(MprVersion *vp, cchar *expr) { char *cp, *ver, *op, *base, *pre, *all; cchar *high, *low; uint64 factor, min, max, num; while (isspace((uchar) *expr)) expr++; if (srmatch(expr, semExpr, &all, &op, &ver, NULL) <= 0) { mprLog("error", 5, "Bad version expression: %s", expr); return 0; } if (smatch(op, "~")) { /* ~VER Compatible with VER at the level of specificity given. ~1.2.3 == (>=1.2.3 <1.3.0) Compatible at the patch level ~1.2 == 1.2.x Compatible at the minor level ~1 == 1.x Compatible at the major level */ if (partCount(ver) == 3 && (cp = srchr(ver, '.')) != 0) { high = sjoin(snclone(ver, cp - ver), ".", MAX_VER_STR, NULL); if ((cp = schr(ver, '-')) != 0) { high = sjoin(high, cp, NULL); } return inRange(vp, sjoin(">=", ver, NULL)) && inRange(vp, sjoin("<", high, NULL)); } return inRange(vp, completeVersion(ver, "x")); } if (smatch(op, "^")) { /* ^VER Compatible with VER at the most significant level. ^0.2.3 == 0.2.3 <= VER < 0.3.0 ^1.2.3 == 1.2.3 <= VER < 2.0.0 So convert to a range */ high = ver; for (cp = ver, factor = VER_FACTOR * VER_FACTOR; *cp; cp++) { if (*cp == '.') { factor /= VER_FACTOR; } else if (isdigit((uchar) *cp) && *cp != '0') { num = (stoi(cp) + 1) * factor; high = numberToVersion(num); if ((cp = schr(ver, '-')) != 0) { high = sjoin(high, cp, NULL); } break; } } return inRange(vp, sjoin(">=", ver, NULL)) && inRange(vp, sjoin("<", high, NULL)); } ver = completeVersion(ver, "x"); if (srmatch(ver, semCriteria, &all, &base, &pre, NULL) <= 0) { mprLog("error", 5, "Cannot match version %s", ver); return 0; } if (vp->preVersion) { if (!pre) { return 0; } if (snumber(vp->preVersion)) { if (stoi(pre) < stoi(vp->preVersion)) { return 0; } } else { if (scmp(pre, vp->preVersion) < 0 && !smatch(pre, "-")) { return 0; } } } min = 0; max = MAX_VER * VER_FACTOR * VER_FACTOR; if (schr(ver, 'x')) { if (smatch(op, ">=")) { // 1.2.3 >= 2.x.x ... 1.2.3 >= 2.0.0 low = sreplace(ver, "x", "0"); min = versionToNumber(low); } else if (smatch(op, "<=")) { // 1.2.3 < 2.x.x ... 1.2.3 <2.MAX.MAX high = sreplace(ver, "x", MAX_VER_STR); max = versionToNumber(high); } else if (*op == '>') { // 1.2.3 > 2.x.x ... 1.2.3 > 2.0.0 low = sreplace(ver, "x", "0"); min = versionToNumber(low) + 1; } else if (*op == '<') { // 1.2.3 < 2.x.x ... 1.2.3 <2.MAX.MAX high = sreplace(ver, "x", MAX_VER_STR); max = versionToNumber(high) - 1; } else { low = sreplace(ver, "x", "0"); high = sreplace(ver, "x", MAX_VER_STR); return inRange(vp, sjoin(">=", low, NULL)) && inRange(vp, sjoin("<", high, NULL)); } } else if (smatch(op, ">=")) { min = versionToNumber(base); } else if (smatch(op, "<=")) { max = versionToNumber(base); } else if (*op == '>') { min = versionToNumber(base) + 1; } else if (*op == '<') { max = versionToNumber(base) - 1; } else { min = max = versionToNumber(base); } if (min <= vp->numberVersion && vp->numberVersion <= max) { return 1; } return 0; }
/* Create and initialize a URI. This accepts full URIs with schemes (http:) and partial URLs Support IPv4 and [IPv6]. Supported forms: SCHEME://[::]:PORT/URI SCHEME://HOST:PORT/URI [::]:PORT/URI :PORT/URI HOST:PORT/URI PORT/URI /URI URI NOTE: HOST/URI is not supported and requires a scheme prefix. This is because it is ambiguous with a relative uri path. Missing fields are null or zero. */ PUBLIC HttpUri *httpCreateUri(cchar *uri, int flags) { HttpUri *up; char *tok, *next; if ((up = mprAllocObj(HttpUri, manageUri)) == 0) { return 0; } tok = sclone(uri); /* [scheme://][hostname[:port]][/path[.ext]][#ref][?query] First trim query and then reference from the end */ if ((next = schr(tok, '?')) != 0) { *next++ = '\0'; up->query = sclone(next); } if ((next = schr(tok, '#')) != 0) { *next++ = '\0'; up->reference = sclone(next); } /* [scheme://][hostname[:port]][/path] */ if ((next = scontains(tok, "://")) != 0) { up->scheme = snclone(tok, (next - tok)); if (smatch(up->scheme, "http")) { if (flags & HTTP_COMPLETE_URI) { up->port = 80; } } else if (smatch(up->scheme, "ws")) { if (flags & HTTP_COMPLETE_URI) { up->port = 80; } up->webSockets = 1; } else if (smatch(up->scheme, "https")) { if (flags & HTTP_COMPLETE_URI) { up->port = 443; } up->secure = 1; } else if (smatch(up->scheme, "wss")) { if (flags & HTTP_COMPLETE_URI) { up->port = 443; } up->secure = 1; up->webSockets = 1; } tok = &next[3]; } /* [hostname[:port]][/path] */ if (*tok == '[' && ((next = strchr(tok, ']')) != 0)) { /* IPv6 [::]:port/uri */ up->host = snclone(&tok[1], (next - tok) - 1); tok = ++next; } else if (*tok && *tok != '/' && *tok != ':' && (up->scheme || strchr(tok, ':'))) { /* Supported forms: scheme://hostname hostname:port */ if ((next = spbrk(tok, ":/")) == 0) { next = &tok[slen(tok)]; } up->host = snclone(tok, next - tok); tok = next; } assert(tok); /* [:port][/path] */ if (*tok == ':') { up->port = atoi(++tok); if ((tok = schr(tok, '/')) == 0) { tok = ""; } } assert(tok); /* [/path] */ if (*tok) { up->path = sclone(tok); /* path[.ext[/extra]] */ if ((tok = srchr(up->path, '.')) != 0) { if (tok[1]) { if ((next = srchr(up->path, '/')) != 0) { if (next < tok) { up->ext = sclone(++tok); } } else { up->ext = sclone(++tok); } } } } if (flags & (HTTP_COMPLETE_URI | HTTP_COMPLETE_URI_PATH)) { if (up->path == 0 || *up->path == '\0') { up->path = sclone("/"); } } up->secure = smatch(up->scheme, "https") || smatch(up->scheme, "wss"); up->webSockets = (smatch(up->scheme, "ws") || smatch(up->scheme, "wss")); if (flags & HTTP_COMPLETE_URI) { if (!up->scheme) { up->scheme = sclone("http"); } if (!up->host) { up->host = sclone("localhost"); } if (!up->port) { up->port = up->secure ? 443 : 80; } } up->valid = httpValidUriChars(uri); return up; }
/* Create and initialize a URI. This accepts full URIs with schemes (http:) and partial URLs */ PUBLIC HttpUri *httpCreateUriFromParts(cchar *scheme, cchar *host, int port, cchar *path, cchar *reference, cchar *query, int flags) { HttpUri *up; char *cp, *tok; if ((up = mprAllocObj(HttpUri, manageUri)) == 0) { up->valid = 0; return 0; } if (!httpValidUriChars(scheme) || !httpValidUriChars(host) || !httpValidUriChars(path) || !httpValidUriChars(reference) || !httpValidUriChars(query)) { up->valid = 0; return up; } if (scheme) { up->scheme = sclone(scheme); up->secure = (smatch(up->scheme, "https") || smatch(up->scheme, "wss")); up->webSockets = (smatch(up->scheme, "ws") || smatch(up->scheme, "wss")); } else if (flags & HTTP_COMPLETE_URI) { up->scheme = "http"; } if (host) { if (*host == '[' && ((cp = strchr(host, ']')) != 0)) { up->host = snclone(&host[1], (cp - host) - 2); if ((cp = schr(++cp, ':')) && port == 0) { port = (int) stoi(++cp); } } else { up->host = sclone(host); if ((cp = schr(up->host, ':')) && port == 0) { port = (int) stoi(++cp); } } } else if (flags & HTTP_COMPLETE_URI) { up->host = sclone("localhost"); } if (port) { up->port = port; } if (path) { while (path[0] == '/' && path[1] == '/') { path++; } up->path = sclone(path); } if (flags & (HTTP_COMPLETE_URI | HTTP_COMPLETE_URI_PATH)) { if (up->path == 0 || *up->path == '\0') { up->path = sclone("/"); } } if (reference) { up->reference = sclone(reference); } if (query) { up->query = sclone(query); } if ((tok = srchr(up->path, '.')) != 0) { if ((cp = srchr(up->path, '/')) != 0) { if (cp <= tok) { up->ext = sclone(&tok[1]); } } else { up->ext = sclone(&tok[1]); } } up->valid = 1; return up; }
static void test_snclone_null(void) { char *clone = snclone(NULL, 1); PCU_ASSERT_PTR_NULL(clone); }
PUBLIC int espEmail(HttpConn *conn, cchar *to, cchar *from, cchar *subject, MprTime date, cchar *mime, cchar *message, MprList *files) { MprList *lines; MprCmd *cmd; cchar *body, *boundary, *contents, *encoded, *file; char *out, *err; ssize length; int i, next, status; if (!from || !*from) { from = "anonymous"; } if (!subject || !*subject) { subject = "Mail message"; } if (!mime || !*mime) { mime = "text/plain"; } if (!date) { date = mprGetTime(); } boundary = sjoin("esp.mail=", mprGetMD5("BOUNDARY"), NULL); lines = mprCreateList(0, 0); mprAddItem(lines, sfmt("To: %s", to)); mprAddItem(lines, sfmt("From: %s", from)); mprAddItem(lines, sfmt("Date: %s", mprFormatLocalTime(0, date))); mprAddItem(lines, sfmt("Subject: %s", subject)); mprAddItem(lines, "MIME-Version: 1.0"); mprAddItem(lines, sfmt("Content-Type: multipart/mixed; boundary=%s", boundary)); mprAddItem(lines, ""); boundary = sjoin("--", boundary, NULL); mprAddItem(lines, boundary); mprAddItem(lines, sfmt("Content-Type: %s", mime)); mprAddItem(lines, ""); mprAddItem(lines, ""); mprAddItem(lines, message); for (ITERATE_ITEMS(files, file, next)) { mprAddItem(lines, boundary); if ((mime = mprLookupMime(NULL, file)) == 0) { mime = "application/octet-stream"; } mprAddItem(lines, "Content-Transfer-Encoding: base64"); mprAddItem(lines, sfmt("Content-Disposition: inline; filename=\"%s\"", mprGetPathBase(file))); mprAddItem(lines, sfmt("Content-Type: %s; name=\"%s\"", mime, mprGetPathBase(file))); mprAddItem(lines, ""); contents = mprReadPathContents(file, &length); encoded = mprEncode64Block(contents, length); for (i = 0; i < length; i += 76) { mprAddItem(lines, snclone(&encoded[i], i + 76)); } } mprAddItem(lines, sfmt("%s--", boundary)); body = mprListToString(lines, "\n"); httpTraceContent(conn, "esp.email", "context", body, slen(body), 0); cmd = mprCreateCmd(conn->dispatcher); if (mprRunCmd(cmd, "sendmail -t", NULL, body, &out, &err, -1, 0) < 0) { mprDestroyCmd(cmd); return MPR_ERR_CANT_OPEN; } if (mprWaitForCmd(cmd, ME_ESP_EMAIL_TIMEOUT) < 0) { httpTrace(conn, "esp.email.error", "error", "msg=\"Timeout waiting for command to complete\", timeout=%d, command=\"%s\"", ME_ESP_EMAIL_TIMEOUT, cmd->argv[0]); mprDestroyCmd(cmd); return MPR_ERR_CANT_COMPLETE; } if ((status = mprGetCmdExitStatus(cmd)) != 0) { httpTrace(conn, "esp.email.error", "error", "msg=\"Sendmail failed\", status=%d, error=\"%s\"", status, err); mprDestroyCmd(cmd); return MPR_ERR_CANT_WRITE; } mprDestroyCmd(cmd); return 0; }