Skip to content

Commit

Permalink
Add E.lookupNoCase to allow searching case-insensitively for Object keys
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
gfwilliams committed Jun 6, 2018
1 parent 8da84b2 commit 02aab33
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 7 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions libs/network/socketserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
}
Expand Down
25 changes: 25 additions & 0 deletions src/jsvar.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down
5 changes: 4 additions & 1 deletion src/jsvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -643,14 +643,17 @@ 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);
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)
Expand Down
32 changes: 32 additions & 0 deletions src/jswrap_espruino.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/jswrap_espruino.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/jswrap_string.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -572,6 +573,7 @@ JsVar *jswrap_string_toUpperLowerCase(JsVar *parent, bool upper) {

jsvStringIteratorFree(&itsrc);
jsvStringIteratorFree(&itdst);
jsvUnLock(parentStr);

return res;
}
Expand Down

0 comments on commit 02aab33

Please sign in to comment.