From bf44a072fb3b2773104bb2ac153c3e74d1e19cb5 Mon Sep 17 00:00:00 2001 From: thatcher Date: Mon, 26 Apr 2010 20:06:34 -0400 Subject: [PATCH] added basic cookie framework, needs work. added global JSON.parse (required for default cookie implementation). Finally found subtle, but big bug in default xhr implementation which can cause escape characters to be escaped twice. --- .project | 40 +++--- build.properties | 2 +- build.xml | 1 + src/console/__global__.js | 1 + src/console/json.js | 230 +++++++++++++++++++++++++++++++++++ src/html/cookie.js | 203 +++++++++++++++++-------------- src/platform/core/console.js | 19 +++ src/platform/core/html.js | 32 +++++ src/platform/core/xhr.js | 6 + src/platform/rhino/xhr.js | 124 +++++++++++++------ 10 files changed, 514 insertions(+), 144 deletions(-) create mode 100644 src/console/json.js diff --git a/.project b/.project index 3f1eef4f..e1330648 100644 --- a/.project +++ b/.project @@ -1,17 +1,23 @@ - - - env-js - - - - - - org.rubypeople.rdt.core.rubybuilder - - - - - - org.rubypeople.rdt.core.rubynature - - + + + env-js + + + + + + org.python.pydev.PyDevBuilder + + + + + org.rubypeople.rdt.core.rubybuilder + + + + + + org.rubypeople.rdt.core.rubynature + org.python.pydev.pythonNature + + diff --git a/build.properties b/build.properties index 9111e334..edb04482 100644 --- a/build.properties +++ b/build.properties @@ -2,7 +2,7 @@ PROJECT: env-js BUILD_MAJOR: 1 BUILD_MINOR: 2 -BUILD_ID: 0.11 +BUILD_ID: 12 BUILD_VERSION: ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_ID} BUILD: ${PROJECT}.${BUILD_VERSION} VERSION: ${BUILD} ${DSTAMP} diff --git a/build.xml b/build.xml index 249bcd1b..f1e242bb 100644 --- a/build.xml +++ b/build.xml @@ -196,6 +196,7 @@ + diff --git a/src/console/__global__.js b/src/console/__global__.js index 1b073730..d15bd8bc 100644 --- a/src/console/__global__.js +++ b/src/console/__global__.js @@ -3,4 +3,5 @@ * @author envjs team */ var Console, + JSON, console; diff --git a/src/console/json.js b/src/console/json.js new file mode 100644 index 00000000..ccab68d9 --- /dev/null +++ b/src/console/json.js @@ -0,0 +1,230 @@ +/* + http://www.JSON.org/json2.js + 2008-07-15 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ +JSON = function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + Date.prototype.toJSON = function (key) { + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + String.prototype.toJSON = function (key) { + return String(this); + }; + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + + escapeable.lastIndex = 0; + return escapeable.test(string) ? + '"' + string.replace(escapeable, function (a) { + var c = meta[a]; + if (typeof c === 'string') { + return c; + } + return '\\u' + ('0000' + + (+(a.charCodeAt(0))).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + + return String(value); + + case 'object': + + if (!value) { + return 'null'; + } + gap += indent; + partial = []; + + if (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length'))) { + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + + return { + stringify: function (value, replacer, space) { + + var i; + gap = ''; + indent = ''; + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + + } else if (typeof space === 'string') { + indent = space; + } + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + + return str('', {'': value}); + }, + + + parse: function (text, reviver) { + var j; + function walk(holder, key) { + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + ('0000' + + (+(a.charCodeAt(0))).toString(16)).slice(-4); + }); + } + + + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + + j = eval('(' + text + ')'); + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + + throw new SyntaxError('JSON.parse'); + } + }; +}(); diff --git a/src/html/cookie.js b/src/html/cookie.js index 45be5854..57e267c8 100644 --- a/src/html/cookie.js +++ b/src/html/cookie.js @@ -1,35 +1,39 @@ /* - * cookie.js + * cookie.js * Private internal helper class used to save/retreive cookies */ var Cookies = { - persistent:{ - //domain - key on domain name { - //path - key on path { - //name - key on name { - //value : cookie value - //other cookie properties - //} - //} - //} - //expire - provides a timestamp for expiring the cookie - //cookie - the cookie! - }, - temporary:{//transient is a reserved word :( - //like above - } -}; + persistent:{ + //domain - key on domain name { + //path - key on path { + //name - key on name { + //value : cookie value + //other cookie properties + //} + //} + //} + //expire - provides a timestamp for expiring the cookie + //cookie - the cookie! + }, + temporary:{//transient is a reserved word :( + //like above + } +}, __cookies__; //HTMLDocument cookie Cookies.set = function(doc, cookie){ - var i, + var i, index, name, value, properties = {}, attr, + attrs; + if(cookie) attrs = cookie.split(";"); + else + return; var domainValid = function(doc, value){ var i, @@ -45,67 +49,83 @@ Cookies.set = function(doc, cookie){ } return false; }; - //for now the strategy is to simply create a json object - //and post it to a file in the .cookies.js file. I hate parsing - //dates so I decided not to implement support for 'expires' - //(which is deprecated) and instead focus on the easier 'max-age' - //(which succeeds 'expires') - cookie = {};//keyword properties of the cookie - cookie['domain']=doc.domain; + //for now the strategy is to simply create a json object + //and post it to a file in the .cookies.js file. I hate parsing + //dates so I decided not to implement support for 'expires' + //(which is deprecated) and instead focus on the easier 'max-age' + //(which succeeds 'expires') + cookie = {};//keyword properties of the cookie + cookie['domain']=doc.domain; if(typeof(doc.location) == 'object'){ cookie['path'] = doc.location.pathname; }else{ cookie.path = '/'; } - for(i=0;i -1){ name = __trim__(attrs[i].slice(0,index)); value = __trim__(attrs[i].slice(index+1)); if(name=='max-age'){ //we'll have to set a timer to check these - //and garbage collect expired cookies - cookie[name] = parseInt(value, 10); - } else if(name=='domain'){ - if(domainValid(doc, value)){ - cookie['domain']=value; - } - } else if(name=='path'){ - //not sure of any special logic for path - cookie['path'] = value; - } else { - //its not a cookie keyword so store it in our array of properties - //and we'll serialize individually in a moment - properties[name] = value; - } - }else{ - if(attrs[i] == 'secure'){ + //and garbage collect expired cookies + cookie[name] = parseInt(value, 10); + } else if(name=='domain'){ + if(domainValid(doc, value)){ + cookie['domain']=value; + } + } else if(name=='path'){ + //not sure of any special logic for path + cookie['path'] = value; + } else { + //its not a cookie keyword so store it in our array of properties + //and we'll serialize individually in a moment + properties[name] = value; + } + }else{ + if(attrs[i] == 'secure'){ cookie[attrs[i]] = true; - } - } - } - if(!cookie['max-age']){ - //it's a transient cookie so it only lasts as long as - //the window.location remains the same - __mergeCookie__(Cookies.temporary, cookie, properties); - }else if(cookie['max-age']===0){ - //delete the cookies - //TODO - }else{ - //the cookie is persistent - __mergeCookie__(Cookies.persistent, cookie, properties); - __persistCookies__(); - } + } + } + } + if(!cookie['max-age']){ + //it's a transient cookie so it only lasts as long as + //the window.location remains the same + __mergeCookie__(Cookies.temporary, cookie, properties); + }else if(cookie['max-age']===0){ + //delete the cookies + //TODO + }else{ + //the cookie is persistent + __mergeCookie__(Cookies.persistent, cookie, properties); + __saveCookies__(doc); + } }; Cookies.get = function(doc){ - //The cookies that are returned must belong to the same domain - //and be at or below the current window.location.path. Also - //we must check to see if the cookie was set to 'secure' in which - //case we must check our current location.protocol to make sure it's - //https: - return __cookieString__(Cookies.temporary, doc) + - __cookieString__(Cookies.persistent, doc); + //The cookies that are returned must belong to the same domain + //and be at or below the current window.location.path. Also + //we must check to see if the cookie was set to 'secure' in which + //case we must check our current location.protocol to make sure it's + //https: + var persisted; + if(!__cookies__){ + try{ + __cookies__ = true; + persisted = __loadCookies__(doc); + if(persisted){ + __extend__(Cookies.persistent, persisted); + } + //console.log('set cookies for doc %s', doc.baseURI); + }catch(e){ + console.log('error loading cookies %s', e) + }; + } + var temporary = __cookieString__(Cookies.temporary, doc), + persistent = __cookieString__(Cookies.persistent, doc); + //console.log('temporary cookies: %s', temporary); + //console.log('persistent cookies: %s', persistent); + return temporary + persistent; }; function __cookieString__(cookies, doc) { @@ -115,11 +135,14 @@ function __cookieString__(cookies, doc) { name; for (domain in cookies) { // check if the cookie is in the current domain (if domain is set) + // console.log('cookie domain %s', domain); if (domain == "" || domain == doc.domain) { for (path in cookies[domain]) { + // console.log('cookie domain path %s', path); // make sure path is at or below the window location path if (path == "/" || doc.documentURI.indexOf(path) > 0) { for (name in cookies[domain][path]) { + // console.log('cookie domain path name %s', name); cookieString += name+"="+cookies[domain][path][name].value+";"; } @@ -131,39 +154,41 @@ function __cookieString__(cookies, doc) { }; function __mergeCookie__(target, cookie, properties){ - var name, now; - if(!target[cookie.domain]){ - target[cookie.domain] = {}; - } - if(!target[cookie.domain][cookie.path]){ - target[cookie.domain][cookie.path] = {}; - } - for(name in properties){ - now = new Date().getTime(); - target[cookie.domain][cookie.path][name] = { - value:properties[name], - "@env:secure":cookie.secure, - "@env:max-age":cookie['max-age'], - "@env:date-created":now, - "@env:expiration":now + cookie['max-age'] - }; - } + var name, now; + if(!target[cookie.domain]){ + target[cookie.domain] = {}; + } + if(!target[cookie.domain][cookie.path]){ + target[cookie.domain][cookie.path] = {}; + } + for(name in properties){ + now = new Date().getTime(); + target[cookie.domain][cookie.path][name] = { + value:properties[name], + "@env:secure":cookie.secure, + "@env:max-age":cookie['max-age'], + "@env:date-created":now, + "@env:expiration":now + cookie['max-age'] + }; + //console.log('cookie is %o',target[cookie.domain][cookie.path][name]); + } }; -function __persistCookies__(){ - //TODO - //I think it should be done via $env so it can be customized +function __saveCookies__(doc){ + //TODO + Envjs.saveCookies(Cookies.persistent); }; function __loadCookies__(){ - //TODO - //should also be configurable via $env + //TODO + //should also be configurable via $env try{ //TODO - load cookies - + return Envjs.loadCookies(); }catch(e){ //TODO - fail gracefully + console.log('%s', e); } }; - \ No newline at end of file + \ No newline at end of file diff --git a/src/platform/core/console.js b/src/platform/core/console.js index 9036356d..1ca0f5c0 100644 --- a/src/platform/core/console.js +++ b/src/platform/core/console.js @@ -19,3 +19,22 @@ Envjs.NONE = 3; * @param {Error} e */ Envjs.lineSource = function(e){}; + +/** + * @param {Object} js + * @param {Object} filter + * @param {Object} indentValue + */ +Envjs.js2json = function(js, filter, indentValue){ + return JSON.stringify(js, filter, indentValue||''); +}; + + +/** + * @param {Object} json + * @param {Object} filter + */ +Envjs.json2js = function(json, filter){ + return JSON.parse(json, filter); +}; + \ No newline at end of file diff --git a/src/platform/core/html.js b/src/platform/core/html.js index 7973e6b4..ec15e632 100644 --- a/src/platform/core/html.js +++ b/src/platform/core/html.js @@ -37,6 +37,38 @@ Envjs.loadInlineScript = function(script){ */ Envjs.eval = function(context, source, name){}; +/** + * Specifies the location of the cookie file + */ +Envjs.cookieFile = function(){ + return 'file://'+Envjs.tmpdir+'/.cookies'; +}; + +/** + * saves cookies to a local file + * @param {Object} htmldoc + */ +Envjs.saveCookies = function(cookies){ + var cookiejson = JSON.stringify(cookies,null,'\t'); + //console.log('persisting cookies %s', cookiejson); + Envjs.writeToFile(cookiejson, Envjs.cookieFile()); +}; + +/** + * loads cookies from a local file + * @param {Object} htmldoc + */ +Envjs.loadCookies = function(){ + var cookiejson = Envjs.readFromFile(Envjs.cookieFile()) + //console.log('loaded cookies : %s', cookiejson); + var js; + try{ + js = JSON.parse(cookiejson, null, '\t'); + }catch(e){ + console.log('failed to load cookies %s', e); + } + return js; +}; /** * Executes a script tag diff --git a/src/platform/core/xhr.js b/src/platform/core/xhr.js index ac505cde..3343a2fa 100644 --- a/src/platform/core/xhr.js +++ b/src/platform/core/xhr.js @@ -81,6 +81,12 @@ Envjs.writeToFile = function(text, url){}; */ Envjs.writeToTempFile = function(text, suffix){}; +/** + * Used to read the contents of a local file + * @param {Object} url + */ +Envjs.readFromFile = function(url){}; + /** * Used to delete a local file * @param {Object} url diff --git a/src/platform/rhino/xhr.js b/src/platform/rhino/xhr.js index 1e4e6141..4ebc2cc9 100644 --- a/src/platform/rhino/xhr.js +++ b/src/platform/rhino/xhr.js @@ -14,7 +14,7 @@ Envjs.getcwd = function() { Envjs.runAsync = function(fn, onInterupt){ ////Envjs.debug("running async"); var running = true, - run; + run; try{ run = Envjs.sync(function(){ @@ -64,6 +64,28 @@ Envjs.writeToTempFile = function(text, suffix){ }; +/** + * Used to read the contents of a local file + * @param {Object} url + */ +Envjs.readFromFile = function(url){ + var fileReader = new java.io.FileReader( + new java.io.File( + new java.net.URI( url ))); + + var stringwriter = new java.io.StringWriter(), + buffer = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 1024), + length; + + while ((length = fileReader.read(buffer, 0, 1024)) != -1) { + stringwriter.write(buffer, 0, length); + } + + stringwriter.close(); + return stringwriter.toString()+""; +}; + + /** * Used to delete a local file * @param {Object} url @@ -81,14 +103,32 @@ Envjs.deleteFile = function(url){ */ Envjs.connection = function(xhr, responseHandler, data){ var url = java.net.URL(xhr.url), - connection; + connection, + header, + outstream, + buffer, + length, + binary = false, + name, value, + contentEncoding, + instream, + responseXML, + i; if ( /^file\:/.test(url) ) { try{ - if ( xhr.method == "PUT" ) { - var text = data || "" ; - Envjs.writeToFile(text, url); + if ( "PUT" == xhr.method || "POST" == xhr.method ) { + data = data || "" ; + Envjs.writeToFile(data, url); + xhr.readyState = 4; + //could be improved, I just cant recall the correct http codes + xhr.status = 200; + xhr.statusText = ""; } else if ( xhr.method == "DELETE" ) { Envjs.deleteFile(url); + xhr.readyState = 4; + //could be improved, I just cant recall the correct http codes + xhr.status = 200; + xhr.statusText = ""; } else { connection = url.openConnection(); connection.connect(); @@ -125,7 +165,7 @@ Envjs.connection = function(xhr, responseHandler, data){ connection.setRequestMethod( xhr.method ); // Add headers to Java connection - for (var header in xhr.headers){ + for (header in xhr.headers){ connection.addRequestProperty(header+'', xhr.headers[header]+''); } @@ -134,18 +174,18 @@ Envjs.connection = function(xhr, responseHandler, data){ if(data instanceof Document){ if ( xhr.method == "PUT" || xhr.method == "POST" ) { connection.setDoOutput(true); - var outstream = connection.getOutputStream(), - xml = (new XMLSerializer()).serializeToString(data), - outbuffer = new java.lang.String(xml).getBytes('UTF-8'); - outstream.write(outbuffer, 0, outbuffer.length); + outstream = connection.getOutputStream(), + xml = (new XMLSerializer()).serializeToString(data); + buffer = new java.lang.String(xml).getBytes('UTF-8'); + outstream.write(buffer, 0, buffer.length); outstream.close(); } }else if(data.length&&data.length>0){ if ( xhr.method == "PUT" || xhr.method == "POST" ) { connection.setDoOutput(true); - var outstream = connection.getOutputStream(), - outbuffer = new java.lang.String(data).getBytes('UTF-8'); - outstream.write(outbuffer, 0, outbuffer.length); + voutstream = connection.getOutputStream(); + buffer = new java.lang.String(data).getBytes('UTF-8'); + outstream.write(buffer, 0, buffer.length); outstream.close(); } } @@ -157,13 +197,13 @@ Envjs.connection = function(xhr, responseHandler, data){ if(connection){ try{ - var respheadlength = connection.getHeaderFields().size(); + length = connection.getHeaderFields().size(); // Stick the response headers into responseHeaders - for (var i = 0; i < respheadlength; i++) { - var headerName = connection.getHeaderFieldKey(i); - var headerValue = connection.getHeaderField(i); - if (headerName) - xhr.responseHeaders[headerName+''] = headerValue+''; + for (i = 0; i < length; i++) { + name = connection.getHeaderFieldKey(i); + value = connection.getHeaderField(i); + if (name) + xhr.responseHeaders[name+''] = value+''; } }catch(e){ console.log('failed to load response headers \n%s',e); @@ -173,18 +213,25 @@ Envjs.connection = function(xhr, responseHandler, data){ xhr.status = parseInt(connection.responseCode,10) || undefined; xhr.statusText = connection.responseMessage || ""; - var contentEncoding = connection.getContentEncoding() || "utf-8", - baos = new java.io.ByteArrayOutputStream(), - buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 1024), - length, - stream = null, + contentEncoding = connection.getContentEncoding() || "utf-8"; + instream = null; responseXML = null; - + try{ - stream = (contentEncoding.equalsIgnoreCase("gzip") || - contentEncoding.equalsIgnoreCase("decompress") )? - new java.util.zip.GZIPInputStream(connection.getInputStream()) : - connection.getInputStream(); + //console.log('contentEncoding %s', contentEncoding); + if( contentEncoding.equalsIgnoreCase("gzip") || + contentEncoding.equalsIgnoreCase("decompress")){ + //zipped content + binary = true; + outstream = new java.io.ByteArrayOutputStream(); + buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 1024); + instream = new java.util.zip.GZIPInputStream(connection.getInputStream()) + }else{ + //this is a text file + outstream = new java.io.StringWriter(); + buffer = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 1024); + instream = new java.io.InputStreamReader(connection.getInputStream()); + } }catch(e){ if (connection.getResponseCode() == 404){ console.log('failed to open connection stream \n %s %s', @@ -193,18 +240,21 @@ Envjs.connection = function(xhr, responseHandler, data){ console.log('failed to open connection stream \n %s %s', e.toString(), e); } - stream = connection.getErrorStream(); + instream = connection.getErrorStream(); } - while ((length = stream.read(buffer)) != -1) { - baos.write(buffer, 0, length); + while ((length = instream.read(buffer, 0, 1024)) != -1) { + outstream.write(buffer, 0, length); } - baos.close(); - stream.close(); - - xhr.responseText = java.nio.charset.Charset.forName("UTF-8"). - decode(java.nio.ByteBuffer.wrap(baos.toByteArray())).toString()+""; + outstream.close(); + instream.close(); + + if(binary){ + xhr.responseText = new String(outstream.toByteArray(), 'UTF-8')+''; + }else{ + xhr.responseText = outstream.toString()+''; + } } if(responseHandler){