/* Map options to an attribute string. Remove all internal control specific options and transparently handle URI link options. WARNING: this returns a non-cloned reference and relies on no GC yield until the returned value is used or cloned. This is done as an optimization to reduce memeory allocations. */ static cchar *map(HttpConn *conn, MprHash *options) { Esp *esp; EspReq *req; MprHash *params; MprKey *kp; MprBuf *buf; cchar *value; char *pstr; if (options == 0 || mprGetHashLength(options) == 0) { return MPR->emptyString; } req = conn->data; if (httpGetOption(options, EDATA("refresh"), 0) && !httpGetOption(options, "id", 0)) { httpAddOption(options, "id", sfmt("id_%d", req->lastDomID++)); } esp = MPR->espService; buf = mprCreateBuf(-1, -1); for (kp = 0; (kp = mprGetNextKey(options, kp)) != 0; ) { if (kp->type != MPR_JSON_OBJ && kp->type != MPR_JSON_ARRAY && !mprLookupKey(esp->internalOptions, kp->key)) { mprPutCharToBuf(buf, ' '); value = kp->data; /* Support link template resolution for these options */ if (smatch(kp->key, EDATA("click")) || smatch(kp->key, EDATA("remote")) || smatch(kp->key, EDATA("refresh"))) { value = httpUriEx(conn, value, options); if ((params = httpGetOptionHash(options, "params")) != 0) { pstr = (char*) ""; for (kp = 0; (kp = mprGetNextKey(params, kp)) != 0; ) { pstr = sjoin(pstr, mprUriEncode(kp->key, MPR_ENCODE_URI_COMPONENT), "=", mprUriEncode(kp->data, MPR_ENCODE_URI_COMPONENT), "&", NULL); } if (pstr[0]) { /* Trim last "&" */ pstr[strlen(pstr) - 1] = '\0'; } mprPutToBuf(buf, "%s-params='%s", params); } } mprPutStringToBuf(buf, kp->key); mprPutStringToBuf(buf, "='"); mprPutStringToBuf(buf, value); mprPutCharToBuf(buf, '\''); } } mprAddNullToBuf(buf); return mprGetBufStart(buf); }
/* Return the (proposed) request headers function getRequestHeaders(): Object */ static EjsPot *http_getRequestHeaders(Ejs *ejs, EjsHttp *hp, int argc, EjsObj **argv) { MprKey *kp; HttpConn *conn; EjsPot *headers; conn = hp->conn; headers = ejsCreateEmptyPot(ejs); for (kp = 0; conn->tx && (kp = mprGetNextKey(conn->tx->headers, kp)) != 0; ) { ejsSetPropertyByName(ejs, headers, EN(kp->key), ejsCreateStringFromAsc(ejs, kp->data)); } return headers; }
/* radio("priority", "{low: 0, med: 1, high: 2}", NULL) radio("priority", "{low: 0, med: 1, high: 2}", "{value:'2'}") */ PUBLIC void espRadio(HttpConn *conn, cchar *field, cchar *choicesString, cchar *optionsString) { MprKey *kp; MprHash *choices, *options; cchar *value, *checked; choices = httpGetOptions(choicesString); options = httpGetOptions(optionsString); value = getValue(conn, field, options); for (kp = 0; (kp = mprGetNextKey(choices, kp)) != 0; ) { checked = (smatch(kp->data, value)) ? " checked" : ""; espRender(conn, "%s <input type='radio' name='%s' value='%s'%s%s />\r\n", spascal(kp->key), field, kp->data, checked, map(conn, options)); } }
static void testIterateHash(MprTestGroup *gp) { MprHash *table; MprKey *sp; char name[80], address[80]; cchar *where; int count, i, check[HASH_COUNT]; table = mprCreateHash(HASH_COUNT, 0); memset(check, 0, sizeof(check)); /* Fill the table */ for (i = 0; i < HASH_COUNT; i++) { mprSprintf(name, sizeof(name), "Bit longer name.%d", i); mprSprintf(address, sizeof(address), "%d Park Ave", i); sp = mprAddKey(table, name, sclone(address)); assert(sp != 0); } assert(mprGetHashLength(table) == HASH_COUNT); /* Check data entered into the table */ sp = mprGetFirstKey(table); count = 0; while (sp) { assert(sp != 0); where = sp->data; assert(isdigit((int) where[0]) != 0); i = atoi(where); check[i] = 1; sp = mprGetNextKey(table, sp); count++; } assert(count == HASH_COUNT); count = 0; for (i = 0; i < HASH_COUNT; i++) { if (check[i]) { count++; } } assert(count == HASH_COUNT); }
PUBLIC void espRenderFlash(HttpConn *conn, cchar *kinds) { EspReq *req; MprKey *kp; cchar *msg; req = conn->data; if (kinds == 0 || req->flash == 0 || mprGetHashLength(req->flash) == 0) { return; } for (kp = 0; (kp = mprGetNextKey(req->flash, kp)) != 0; ) { msg = kp->data; if (strstr(kinds, kp->key) || strstr(kinds, "all")) { espRender(conn, "<span class='flash-%s'>%s</span>", kp->key, msg); } } }
// MOB - rethink name espGetFlash cchar *espGetFlashMessage(HttpConn *conn, cchar *kind) { EspReq *req; MprKey *kp; cchar *msg; req = conn->data; if (kind == 0 || req->flash == 0 || mprGetHashLength(req->flash) == 0) { return 0; } for (kp = 0; (kp = mprGetNextKey(req->flash, kp)) != 0; ) { msg = kp->data; if (smatch(kind, kp->key) || smatch(kind, "all")) { return msg; } } return 0; }
PUBLIC cchar *espGetFeedback(HttpConn *conn, cchar *kind) { EspReq *req; MprKey *kp; cchar *msg; req = conn->reqData; if (kind == 0 || req == 0 || req->feedback == 0 || mprGetHashLength(req->feedback) == 0) { return 0; } for (kp = 0; (kp = mprGetNextKey(req->feedback, kp)) != 0; ) { msg = kp->data; if (smatch(kind, kp->key) || smatch(kind, "all")) { return msg; } } return 0; }
/* function get headers(): Object */ static EjsPot *http_headers(Ejs *ejs, EjsHttp *hp, int argc, EjsObj **argv) { MprHash *hash; MprKey *kp; EjsPot *results; int i; if (!waitForResponseHeaders(hp)) { return 0; } results = ejsCreateEmptyPot(ejs); hash = httpGetHeaderHash(hp->conn); if (hash == 0) { return results; } for (i = 0, kp = mprGetFirstKey(hash); kp; kp = mprGetNextKey(hash, kp), i++) { ejsSetPropertyByName(ejs, results, EN(kp->key), ejsCreateStringFromAsc(ejs, kp->data)); } return results; }
/* This will be enabled when caching is enabled for the route and there is no acceptable cache data to use. OR - manual caching has been enabled. */ static void outgoingCacheFilterService(HttpQueue *q) { HttpPacket *packet, *data; HttpConn *conn; HttpTx *tx; MprKey *kp; cchar *cachedData; ssize size; int foundDataPacket; conn = q->conn; tx = conn->tx; foundDataPacket = 0; cachedData = 0; if (tx->status < 200 || tx->status > 299) { tx->cacheBuffer = 0; } /* This routine will save cached responses to tx->cacheBuffer. It will also send cached data if the X-SendCache header is present. Normal caching is done by cacheHandler */ if (mprLookupKey(conn->tx->headers, "X-SendCache") != 0) { if (fetchCachedResponse(conn)) { mprLog(3, "cacheFilter: write cached content for '%s'", conn->rx->uri); cachedData = setHeadersFromCache(conn, tx->cachedContent); tx->length = slen(cachedData); } } for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if (!httpWillNextQueueAcceptPacket(q, packet)) { httpPutBackPacket(q, packet); return; } if (packet->flags & HTTP_PACKET_HEADER) { if (!cachedData && tx->cacheBuffer) { /* Add defined headers to the start of the cache buffer. Separate with a double newline. */ mprPutFmtToBuf(tx->cacheBuffer, "X-Status: %d\n", tx->status); for (kp = 0; (kp = mprGetNextKey(tx->headers, kp)) != 0; ) { mprPutFmtToBuf(tx->cacheBuffer, "%s: %s\n", kp->key, kp->data); } mprPutCharToBuf(tx->cacheBuffer, '\n'); } } else if (packet->flags & HTTP_PACKET_DATA) { if (cachedData) { /* Using X-SendCache. Replace the data with the cached response. */ mprFlushBuf(packet->content); mprPutBlockToBuf(packet->content, cachedData, (ssize) tx->length); } else if (tx->cacheBuffer) { /* Save the response packet to the cache buffer. Will write below in saveCachedResponse. */ size = mprGetBufLength(packet->content); if ((tx->cacheBufferLength + size) < conn->limits->cacheItemSize) { mprPutBlockToBuf(tx->cacheBuffer, mprGetBufStart(packet->content), mprGetBufLength(packet->content)); tx->cacheBufferLength += size; } else { tx->cacheBuffer = 0; mprLog(3, "cacheFilter: Item too big to cache %d bytes, limit %d", tx->cacheBufferLength + size, conn->limits->cacheItemSize); } } foundDataPacket = 1; } else if (packet->flags & HTTP_PACKET_END) { if (cachedData && !foundDataPacket) { /* Using X-SendCache but there was no data packet to replace. So do the write here */ data = httpCreateDataPacket((ssize) tx->length); mprPutBlockToBuf(data->content, cachedData, (ssize) tx->length); httpPutPacketToNext(q, data); } else if (tx->cacheBuffer) { /* Save the cache buffer to the cache store */ saveCachedResponse(conn); } } httpPutPacketToNext(q, packet); } }
/* If the program has a UNIX style "#!/program" string at the start of the file that program will be selected and the original program will be passed as the first arg to that program with argv[] appended after that. If the program is not found, this routine supports a safe intelligent search for the command. If all else fails, we just return in program the fileName we were passed in. script will be set if we are modifying the program to run and we have extracted the name of the file to run as a script. */ static void findExecutable(HttpConn *conn, char **program, char **script, char **bangScript, cchar *fileName) { HttpRx *rx; HttpTx *tx; HttpRoute *route; MprKey *kp; MprFile *file; cchar *actionProgram, *ext, *cmdShell, *cp, *start, *path; char buf[ME_MAX_FNAME + 1]; rx = conn->rx; tx = conn->tx; route = rx->route; *bangScript = 0; *script = 0; *program = 0; path = 0; actionProgram = mprGetMimeProgram(rx->route->mimeTypes, rx->mimeType); ext = tx->ext; /* If not found, go looking for the fileName with the extensions defined in appweb.conf. NOTE: we don't use PATH deliberately!!! */ if (access(fileName, X_OK) < 0) { for (kp = 0; (kp = mprGetNextKey(route->extensions, kp)) != 0; ) { path = sjoin(fileName, ".", kp->key, NULL); if (access(path, X_OK) == 0) { break; } path = 0; } if (kp) { ext = kp->key; } else { path = fileName; } } else { path = fileName; } assert(path && *path); #if ME_WIN_LIKE if (ext && (strcmp(ext, ".bat") == 0 || strcmp(ext, ".cmd") == 0)) { /* Let a mime action override COMSPEC */ if (actionProgram) { cmdShell = actionProgram; } else { cmdShell = getenv("COMSPEC"); } if (cmdShell == 0) { cmdShell = "cmd.exe"; } *script = sclone(path); *program = sclone(cmdShell); return; } #endif if (actionProgram) { *program = sclone(actionProgram); } else if ((file = mprOpenFile(path, O_RDONLY, 0)) != 0) { if (mprReadFile(file, buf, ME_MAX_FNAME) > 0) { mprCloseFile(file); buf[ME_MAX_FNAME] = '\0'; if (buf[0] == '#' && buf[1] == '!') { cp = start = &buf[2]; cmdShell = ssplit(&buf[2], "\r\n", NULL); if (!mprIsPathAbs(cmdShell)) { /* If we cannot access the command shell and the command is not an absolute path, look in the same directory as the script. */ if (mprPathExists(cmdShell, X_OK)) { cmdShell = mprJoinPath(mprGetPathDir(path), cmdShell); } } *program = sclone(cmdShell); *bangScript = sclone(path); return; } } else { mprCloseFile(file); } } if (actionProgram) { *program = sclone(actionProgram); *bangScript = sclone(path); } else { *program = sclone(path); } return; }
static void pruneCache(MprCache *cache, MprEvent *event) { MprTime when, factor; MprKey *kp; CacheItem *item; ssize excessKeys; if (!cache) { cache = shared; if (!cache) { return; } } if (event) { when = mprGetTime(); } else { /* Expire all items by setting event to NULL */ when = MAXINT64; } if (mprTryLock(cache->mutex)) { /* Check for expired items */ for (kp = 0; (kp = mprGetNextKey(cache->store, kp)) != 0; ) { item = (CacheItem*) kp->data; mprLog(6, "Cache: \"%s\" lifespan %d, expires in %d secs", item->key, item->lifespan / 1000, (item->expires - when) / 1000); if (item->expires && item->expires <= when) { mprLog(5, "Cache prune expired key %s", kp->key); removeItem(cache, item); } } mprAssert(cache->usedMem >= 0); /* If too many keys or too much memory used, prune keys that expire soonest. */ if (cache->maxKeys < MAXSSIZE || cache->maxMem < MAXSSIZE) { /* Look for those expiring in the next 5 minutes, then 20 mins, then 80 ... */ excessKeys = mprGetHashLength(cache->store) - cache->maxKeys; factor = 5 * 60 * MPR_TICKS_PER_SEC; when += factor; while (excessKeys > 0 || cache->usedMem > cache->maxMem) { for (kp = 0; (kp = mprGetNextKey(cache->store, kp)) != 0; ) { item = (CacheItem*) kp->data; if (item->expires && item->expires <= when) { mprLog(5, "Cache too big execess keys %Ld, mem %Ld, prune key %s", excessKeys, (cache->maxMem - cache->usedMem), kp->key); removeItem(cache, item); } } factor *= 4; when += factor; } } mprAssert(cache->usedMem >= 0); if (mprGetHashLength(cache->store) == 0) { if (event) { mprRemoveEvent(event); cache->timer = 0; } } unlock(cache); } }
PUBLIC void httpWriteHeaders(HttpQueue *q, HttpPacket *packet) { Http *http; HttpConn *conn; HttpTx *tx; HttpUri *parsedUri; MprKey *kp; MprBuf *buf; int level; assert(packet->flags == HTTP_PACKET_HEADER); conn = q->conn; http = conn->http; tx = conn->tx; buf = packet->content; if (tx->flags & HTTP_TX_HEADERS_CREATED) { return; } tx->flags |= HTTP_TX_HEADERS_CREATED; tx->responded = 1; if (conn->headersCallback) { /* Must be before headers below */ (conn->headersCallback)(conn->headersCallbackArg); } if (tx->flags & HTTP_TX_USE_OWN_HEADERS && !conn->error) { conn->keepAliveCount = 0; return; } setHeaders(conn, packet); if (conn->endpoint) { mprPutStringToBuf(buf, conn->protocol); mprPutCharToBuf(buf, ' '); mprPutIntToBuf(buf, tx->status); mprPutCharToBuf(buf, ' '); mprPutStringToBuf(buf, httpLookupStatus(http, tx->status)); } else { mprPutStringToBuf(buf, tx->method); mprPutCharToBuf(buf, ' '); parsedUri = tx->parsedUri; if (http->proxyHost && *http->proxyHost) { if (parsedUri->query && *parsedUri->query) { mprPutToBuf(buf, "http://%s:%d%s?%s %s", http->proxyHost, http->proxyPort, parsedUri->path, parsedUri->query, conn->protocol); } else { mprPutToBuf(buf, "http://%s:%d%s %s", http->proxyHost, http->proxyPort, parsedUri->path, conn->protocol); } } else { if (parsedUri->query && *parsedUri->query) { mprPutToBuf(buf, "%s?%s %s", parsedUri->path, parsedUri->query, conn->protocol); } else { mprPutStringToBuf(buf, parsedUri->path); mprPutCharToBuf(buf, ' '); mprPutStringToBuf(buf, conn->protocol); } } } if ((level = httpShouldTrace(conn, HTTP_TRACE_TX, HTTP_TRACE_FIRST, tx->ext)) >= mprGetLogLevel(tx)) { mprAddNullToBuf(buf); mprLog(level, " %s", mprGetBufStart(buf)); } mprPutStringToBuf(buf, "\r\n"); /* Output headers */ kp = mprGetFirstKey(conn->tx->headers); while (kp) { mprPutStringToBuf(packet->content, kp->key); mprPutStringToBuf(packet->content, ": "); if (kp->data) { mprPutStringToBuf(packet->content, kp->data); } mprPutStringToBuf(packet->content, "\r\n"); kp = mprGetNextKey(conn->tx->headers, kp); } /* By omitting the "\r\n" delimiter after the headers, chunks can emit "\r\nSize\r\n" as a single chunk delimiter */ if (tx->length >= 0 || tx->chunkSize <= 0) { mprPutStringToBuf(buf, "\r\n"); } if (tx->altBody) { /* Error responses are emitted here */ mprPutStringToBuf(buf, tx->altBody); httpDiscardQueueData(tx->queue[HTTP_QUEUE_TX]->nextQ, 0); } tx->headerSize = mprGetBufLength(buf); tx->flags |= HTTP_TX_HEADERS_CREATED; q->count += httpGetPacketLength(packet); }