/*JSON{ "type" : "staticmethod", "class" : "url", "name" : "parse", "generate" : "jswrap_url_parse", "params" : [ ["urlStr","JsVar","A URL to be parsed"], ["parseQuery","bool","Whether to parse the query string into an object not (default = false)"] ], "return" : ["JsVar","An object containing options for ```http.request``` or ```http.get```. Contains `method`, `host`, `path`, `pathname`, `search`, `port` and `query`"] } A utility function to split a URL into parts This is useful in web servers for instance when handling a request. For instance `url.parse("/a?b=c&d=e",true)` returns `{"method":"GET","host":"","path":"/a?b=c&d=e","pathname":"/a","search":"?b=c&d=e","port":80,"query":{"b":"c","d":"e"}}` */ JsVar *jswrap_url_parse(JsVar *url, bool parseQuery) { if (!jsvIsString(url)) return 0; JsVar *obj = jsvNewWithFlags(JSV_OBJECT); if (!obj) return 0; // out of memory // scan string to try and pick stuff out JsvStringIterator it; jsvStringIteratorNew(&it, url, 0); int slashes = 0; int colons = 0; int addrStart = -1; int portStart = -1; int pathStart = -1; int searchStart = -1; int charIdx = 0; int portNumber = 0; while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); if (ch == '/') { slashes++; if (pathStart<0) pathStart = charIdx; if (colons==1 && slashes==2 && addrStart<0) { addrStart = charIdx; pathStart = -1; searchStart = -1; } } if (ch == ':') { colons++; if (addrStart>=0 && pathStart<0) portStart = charIdx; } if (portStart>=0 && charIdx>portStart && pathStart<0 && ch >= '0' && ch <= '9') { portNumber = portNumber*10 + (ch-'0'); } if (ch == '?' && pathStart>=0) { searchStart = charIdx; } jsvStringIteratorNext(&it); charIdx++; } jsvStringIteratorFree(&it); // try and sort stuff out if (pathStart<0) pathStart = charIdx; if (pathStart<0) pathStart = charIdx; int addrEnd = (portStart>=0) ? portStart : pathStart; // pull out details if (addrStart>0) jsvObjectSetChildAndUnLock(obj, "protocol", jsvNewFromStringVar(url, 0, (size_t)addrStart-1)); jsvObjectSetChildAndUnLock(obj, "method", jsvNewFromString("GET")); jsvObjectSetChildAndUnLock(obj, "host", jsvNewFromStringVar(url, (size_t)(addrStart+1), (size_t)(addrEnd-(addrStart+1)))); JsVar *v; v = jsvNewFromStringVar(url, (size_t)pathStart, JSVAPPENDSTRINGVAR_MAXLENGTH); if (jsvGetStringLength(v)==0) jsvAppendString(v, "/"); jsvObjectSetChildAndUnLock(obj, "path", v); v = jsvNewFromStringVar(url, (size_t)pathStart, (size_t)((searchStart>=0)?(searchStart-pathStart):JSVAPPENDSTRINGVAR_MAXLENGTH)); if (jsvGetStringLength(v)==0) jsvAppendString(v, "/"); jsvObjectSetChildAndUnLock(obj, "pathname", v); jsvObjectSetChildAndUnLock(obj, "search", (searchStart>=0)?jsvNewFromStringVar(url, (size_t)searchStart, JSVAPPENDSTRINGVAR_MAXLENGTH):jsvNewNull()); jsvObjectSetChildAndUnLock(obj, "port", (portNumber<=0 || portNumber>65535) ? jsvNewWithFlags(JSV_NULL) : jsvNewFromInteger(portNumber)); JsVar *query = (searchStart>=0)?jsvNewFromStringVar(url, (size_t)(searchStart+1), JSVAPPENDSTRINGVAR_MAXLENGTH):jsvNewNull(); if (parseQuery && !jsvIsNull(query)) { JsVar *queryStr = query; jsvStringIteratorNew(&it, query, 0); query = jsvNewWithFlags(JSV_OBJECT); JsVar *key = jsvNewFromEmptyString(); JsVar *val = jsvNewFromEmptyString(); bool hadEquals = false; while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); if (ch=='&') { if (jsvGetStringLength(key)>0 || jsvGetStringLength(val)>0) { key = jsvAsArrayIndexAndUnLock(key); // make sure "0" gets made into 0 jsvMakeIntoVariableName(key, val); jsvAddName(query, key); jsvUnLock2(key, val); key = jsvNewFromEmptyString(); val = jsvNewFromEmptyString(); hadEquals = false; } } else if (!hadEquals && ch=='=') { hadEquals = true; } else { // decode percent escape chars if (ch=='%') { jsvStringIteratorNext(&it); ch = jsvStringIteratorGetChar(&it); jsvStringIteratorNext(&it); ch = (char)((chtod(ch)<<4) | chtod(jsvStringIteratorGetChar(&it))); } if (hadEquals) jsvAppendCharacter(val, ch); else jsvAppendCharacter(key, ch); } jsvStringIteratorNext(&it); charIdx++; } jsvStringIteratorFree(&it); jsvUnLock(queryStr); if (jsvGetStringLength(key)>0 || jsvGetStringLength(val)>0) { key = jsvAsArrayIndexAndUnLock(key); // make sure "0" gets made into 0 jsvMakeIntoVariableName(key, val); jsvAddName(query, key); } jsvUnLock2(key, val); } jsvObjectSetChildAndUnLock(obj, "query", query); return obj; }
/* Match one character. Doesn't modify txtIt, returns length of regexp char in *length */ bool matchcharacter(char *regexp, JsvStringIterator *txtIt, int *length, matchInfo *info) { *length = 1; char ch = jsvStringIteratorGetChar(txtIt); if (regexp[0]=='.') return true; if (regexp[0]=='[') { // Character set (any char inside '[]') info->rangeMatch = true; bool inverted = regexp[1]=='^'; if (inverted) (*length)++; bool matchAny = false; while (regexp[*length] && regexp[*length]!=']') { int matchLen; matchAny |= matchcharacter(®exp[*length], txtIt, &matchLen, info); (*length) += matchLen; } if (regexp[*length]==']') { (*length)++; } else { jsExceptionHere(JSET_ERROR, "Unfinished character set in RegEx"); return false; } info->rangeMatch = false; return matchAny != inverted; } char cH = regexp[0]; if (cH=='\\') { // escape character *length = 2; // fallback to the quoted character (e.g. /,-,? etc.) cH = regexp[1]; // missing quite a few here // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions if (cH=='d') return isNumeric(ch); if (cH=='D') return !isNumeric(ch); if (cH=='f') { cH=0x0C; goto haveCode; } if (cH=='n') { cH=0x0A; goto haveCode; } if (cH=='r') { cH=0x0D; goto haveCode; } if (cH=='s') return isWhitespace(ch); if (cH=='S') return !isWhitespace(ch); if (cH=='t') { cH=0x09; goto haveCode; } if (cH=='v') { cH=0x0B; goto haveCode; } if (cH=='w') return isNumeric(ch) || isAlpha(ch) || ch=='_'; if (cH=='W') return !(isNumeric(ch) || isAlpha(ch) || ch=='_'); if (cH>='0' && cH<='9') { cH-='0'; goto haveCode; } if (cH=='x' && regexp[2] && regexp[3]) { *length = 4; cH = (char)((chtod(regexp[2])<<4) | chtod(regexp[3])); goto haveCode; } } haveCode: if (info->rangeMatch && regexp[*length] == '-') { // Character set range start info->rangeFirstChar = cH; (*length)++; int matchLen; bool match = matchcharacter(®exp[*length], txtIt, &matchLen, info); (*length)+=matchLen; return match; } if (info->ignoreCase) { ch = jsvStringCharToLower(ch); cH = jsvStringCharToLower(cH); } if (info->rangeFirstChar != NO_RANGE) { // Character set range char cL = (char)info->rangeFirstChar; if (info->ignoreCase) { cL = jsvStringCharToLower(cL); } info->rangeFirstChar = NO_RANGE; return (ch >= cL && ch <= cH && cL < cH); } return cH==ch; }