From 02aab33c688e1350c7e2ec4edeca1da8857b67de Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Jun 2018 14:50:13 +0100 Subject: [PATCH] Add E.lookupNoCase to allow searching case-insensitively for Object keys Also Add robustness test for String.toUpper/etc in case they're bound to non-strings Fix HTTP Chunked transfers when the server uses lowercase headers (fix #1458) --- ChangeLog | 2 ++ libs/network/socketserver.c | 10 +++++----- src/jsvar.c | 25 +++++++++++++++++++++++++ src/jsvar.h | 5 ++++- src/jswrap_espruino.c | 32 ++++++++++++++++++++++++++++++++ src/jswrap_espruino.h | 1 + src/jswrap_string.c | 4 +++- 7 files changed, 72 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index c895a9d042..bc983d7c7c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,8 @@ Switch to non-recursive StringExt copy (fix #1451) Fix rounding errors in fillPoly -> improve vector font rendering Fix issue that caused 'dump()' not to report variables/functions on Pixl.js + Add E.lookupNoCase to allow searching case-insensitively for Object keys + Fix HTTP Chunked transfers when the server uses lowercase headers (fix #1458) 1v99 : Increase jslMatch error buffer size to handle "UNFINISHED TEMPLATE LITERAL" string (#1426) nRF5x: Make FlashWrite cope with flash writes > 4k diff --git a/libs/network/socketserver.c b/libs/network/socketserver.c index f480375a1e..f1f2cbe892 100644 --- a/libs/network/socketserver.c +++ b/libs/network/socketserver.c @@ -164,11 +164,11 @@ bool httpParseHeaders(JsVar **receiveData, JsVar *objectForData, bool isServer) jsvStringIteratorFree(&it); // flag the req/response if Transfer-Encoding:chunked was set JsVarInt contentToReceive; - if (compareTransferEncodingAndUnlock(jsvObjectGetChild(vHeaders, "Transfer-Encoding", 0), "chunked")) { + if (compareTransferEncodingAndUnlock(jsvObjectGetChildI(vHeaders, "Transfer-Encoding"), "chunked")) { jsvObjectSetChildAndUnLock(objectForData, HTTP_NAME_CHUNKED, jsvNewFromBool(true)); contentToReceive = 1; } else { - contentToReceive = jsvGetIntegerAndUnLock(jsvObjectGetChild(vHeaders,"Content-Length",0)); + contentToReceive = jsvGetIntegerAndUnLock(jsvObjectGetChildI(vHeaders,"Content-Length")); } jsvObjectSetChildAndUnLock(objectForData, HTTP_NAME_RECEIVE_COUNT, jsvNewFromInteger(contentToReceive)); jsvUnLock(vHeaders); @@ -950,7 +950,7 @@ void clientRequestWrite(JsNetwork *net, JsVar *httpClientReqVar, JsVar *data, Js JsVar *headers = jsvObjectGetChild(options, "headers", 0); bool hasHostHeader = false; if (jsvIsObject(headers)) { - JsVar *hostHeader = jsvObjectGetChild(headers, "Host", 0); + JsVar *hostHeader = jsvObjectGetChildI(headers, "Host"); hasHostHeader = hostHeader!=0; jsvUnLock(hostHeader); httpAppendHeaders(sendData, headers); @@ -1018,7 +1018,7 @@ void clientRequestConnect(JsNetwork *net, JsVar *httpClientReqVar) { SocketType socketType = socketGetType(httpClientReqVar); - JsVar *options = jsvObjectGetChild(httpClientReqVar, HTTP_NAME_OPTIONS_VAR, false); + JsVar *options = jsvObjectGetChild(httpClientReqVar, HTTP_NAME_OPTIONS_VAR, 0); unsigned short port = (unsigned short)jsvGetIntegerAndUnLock(jsvObjectGetChild(options, "port", 0)); uint32_t host_addr = 0; @@ -1112,7 +1112,7 @@ void serverResponseWriteHead(JsVar *httpServerResponseVar, int statusCode, JsVar if (headers) { httpAppendHeaders(sendData, headers); // if Transfer-Encoding:chunked was set, subsequent writes need to 'chunk' the data that is sent - if (compareTransferEncodingAndUnlock(jsvObjectGetChild(headers, "Transfer-Encoding", 0), "chunked")) { + if (compareTransferEncodingAndUnlock(jsvObjectGetChildI(headers, "Transfer-Encoding"), "chunked")) { jsvObjectSetChildAndUnLock(httpServerResponseVar, HTTP_NAME_CHUNKED, jsvNewFromBool(true)); } } diff --git a/src/jsvar.c b/src/jsvar.c index 1e948dc4c1..bdd355b4ff 100644 --- a/src/jsvar.c +++ b/src/jsvar.c @@ -2611,6 +2611,24 @@ JsVar *jsvFindChildFromString(JsVar *parent, const char *name, bool addIfNotFoun return child; } +/// Find a child with a matching name using a case insensitive search +JsVar *jsvFindChildFromStringI(JsVar *parent, const char *name) { + assert(jsvHasChildren(parent)); + JsVarRef childref = jsvGetFirstChild(parent); + while (childref) { + // Don't Lock here, just use GetAddressOf - to try and speed up the finding + // TODO: We can do this now, but when/if we move to cacheing vars, it'll break + JsVar *child = jsvGetAddressOf(childref); + if (jsvHasCharacterData(child) && + jsvIsStringEqualOrStartsWithOffset(child, name, false, 0, true)) { + // found it! unlock parent but leave child locked + return jsvLockAgain(child); + } + childref = jsvGetNextSibling(child); + } + return 0; +} + /// See jsvIsNewChild - for fields that don't exist yet JsVar *jsvCreateNewChild(JsVar *parent, JsVar *index, JsVar *child) { JsVar *newChild = jsvAsName(index); @@ -2751,6 +2769,13 @@ JsVar *jsvObjectGetChild(JsVar *obj, const char *name, JsVarFlags createChild) { return child; } +/// Get the named child of an object using a case-insensitive search +JsVar *jsvObjectGetChildI(JsVar *obj, const char *name) { + if (!obj) return 0; + assert(jsvHasChildren(obj)); + return jsvSkipNameAndUnLock(jsvFindChildFromStringI(obj, name)); +} + /// Set the named child of an object, and return the child (so you can choose to unlock it if you want) JsVar *jsvObjectSetChild(JsVar *obj, const char *name, JsVar *child) { assert(jsvHasChildren(obj)); diff --git a/src/jsvar.h b/src/jsvar.h index 40ab84020d..c6dad093a1 100644 --- a/src/jsvar.h +++ b/src/jsvar.h @@ -643,7 +643,8 @@ JsVar *jsvAddNamedChild(JsVar *parent, JsVar *child, const char *name); // Add a JsVar *jsvSetNamedChild(JsVar *parent, JsVar *child, const char *name); // Add a child, and create a name for it. Returns a LOCKED name var. CHECKS FOR DUPLICATES JsVar *jsvSetValueOfName(JsVar *name, JsVar *src); // Set the value of a child created with jsvAddName,jsvAddNamedChild. Returns the UNLOCKED name argument JsVar *jsvFindChildFromString(JsVar *parent, const char *name, bool createIfNotFound); // Non-recursive finding of child with name. Returns a LOCKED var -JsVar *jsvFindChildFromVar(JsVar *parent, JsVar *childName, bool addIfNotFound); // Non-recursive finding of child with name. Returns a LOCKED var +JsVar *jsvFindChildFromStringI(JsVar *parent, const char *name); ///< Find a child with a matching name using a case insensitive search +JsVar *jsvFindChildFromVar(JsVar *parent, JsVar *childName, bool addIfNotFound); ///< Non-recursive finding of child with name. Returns a LOCKED var /// Remove a child - note that the child MUST ACTUALLY BE A CHILD! and should be a name, not a value. void jsvRemoveChild(JsVar *parent, JsVar *child); @@ -651,6 +652,8 @@ void jsvRemoveAllChildren(JsVar *parent); /// Get the named child of an object. If createChild!=0 then create the child JsVar *jsvObjectGetChild(JsVar *obj, const char *name, JsVarFlags createChild); +/// Get the named child of an object using a case-insensitive search +JsVar *jsvObjectGetChildI(JsVar *obj, const char *name); /// Set the named child of an object, and return the child (so you can choose to unlock it if you want) JsVar *jsvObjectSetChild(JsVar *obj, const char *name, JsVar *child); /// Set the named child of an object, and return the child (so you can choose to unlock it if you want) diff --git a/src/jswrap_espruino.c b/src/jswrap_espruino.c index 374574f168..af9ce073c3 100644 --- a/src/jswrap_espruino.c +++ b/src/jswrap_espruino.c @@ -1193,6 +1193,38 @@ void jswrap_espruino_mapInPlace(JsVar *from, JsVar *to, JsVar *map, JsVarInt bit jsvArrayBufferIteratorFree(&itTo); } +/*JSON{ + "type" : "staticmethod", + "class" : "E", + "name" : "lookupNoCase", + "generate" : "jswrap_espruino_lookupNoCase", + "params" : [ + ["haystack","JsVar","The Array/Object/Function to search"], + ["needle","JsVar","The key to search for"], + ["returnKey","bool","If true, return the key, else return the value itself"] + ], + "return" : ["JsVar","The value in the Object matching 'needle', or if `returnKey==true` the key's name - or undefined"] +} +Search in an Object, Array, or Function + */ +JsVar *jswrap_espruino_lookupNoCase(JsVar *haystack, JsVar *needle, bool returnKey) { + if (!jsvHasChildren(haystack)) return 0; + char needleBuf[64]; + if (jsvGetString(needle, needleBuf, sizeof(needleBuf))==sizeof(needleBuf)) { + jsExceptionHere(JSET_ERROR, "Search string is too long (>=%d chars)", sizeof(needleBuf)); + } + + if (returnKey) { + JsVar *key = jsvFindChildFromStringI(haystack, needleBuf); + if (key) { // convert name to a string + JsVar *n = jsvCopyNameOnly(key,false,false); + jsvUnLock(key); + return n; + } + return 0; + } else return jsvObjectGetChildI(haystack, needleBuf); +} + /*JSON{ "type" : "staticmethod", "ifndef" : "SAVE_ON_FLASH", diff --git a/src/jswrap_espruino.h b/src/jswrap_espruino.h index 8133465255..2e49078038 100644 --- a/src/jswrap_espruino.h +++ b/src/jswrap_espruino.h @@ -45,6 +45,7 @@ void jswrap_espruino_dumpFreeList(); JsVar *jswrap_espruino_getSizeOf(JsVar *v, int depth); JsVarInt jswrap_espruino_getAddressOf(JsVar *v, bool flatAddress); void jswrap_espruino_mapInPlace(JsVar *from, JsVar *to, JsVar *map, JsVarInt bits); +JsVar *jswrap_espruino_lookupNoCase(JsVar *haystack, JsVar *needle, bool returnKey); JsVar *jswrap_e_dumpStr(); JsVar *jswrap_espruino_HSBtoRGB(JsVarFloat hue, JsVarFloat sat, JsVarFloat bri, bool asArray); void jswrap_espruino_setPassword(JsVar *pwd); diff --git a/src/jswrap_string.c b/src/jswrap_string.c index d8ca8c84e8..b81117631e 100644 --- a/src/jswrap_string.c +++ b/src/jswrap_string.c @@ -558,9 +558,10 @@ JsVar *jswrap_string_split(JsVar *parent, JsVar *split) { JsVar *jswrap_string_toUpperLowerCase(JsVar *parent, bool upper) { JsVar *res = jsvNewFromEmptyString(); if (!res) return 0; // out of memory + JsVar *parentStr = jsvAsString(parent, true); JsvStringIterator itsrc, itdst; - jsvStringIteratorNew(&itsrc, parent, 0); + jsvStringIteratorNew(&itsrc, parentStr, 0); jsvStringIteratorNew(&itdst, res, 0); while (jsvStringIteratorHasChar(&itsrc)) { @@ -572,6 +573,7 @@ JsVar *jswrap_string_toUpperLowerCase(JsVar *parent, bool upper) { jsvStringIteratorFree(&itsrc); jsvStringIteratorFree(&itdst); + jsvUnLock(parentStr); return res; }