Skip to content

Commit

Permalink
Merge pull request #105 from brianjmiller/master
Browse files Browse the repository at this point in the history
Fix multiple question mark in query params handling in Utils.parseURL
  • Loading branch information
bscSCORM committed Mar 3, 2015
2 parents 292c710 + e779bb1 commit 19b1396
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 83 deletions.
13 changes: 11 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ module.exports = function(grunt) {
"src/About.js"
],
browserFileList = coreFileList.slice(),
nodeFileList = coreFileList.slice();
nodeFileList = coreFileList.slice(),
pkg,
bower;

browserFileList.push(
"src/Environment/Browser.js"
Expand All @@ -37,9 +39,16 @@ module.exports = function(grunt) {
"src/Environment/Node.js"
);

pkg = grunt.file.readJSON("package.json");
bower = grunt.file.readJSON("bower.json");

if (pkg.version !== bower.version) {
grunt.fail.fatal("package.json and bower.json versions do not match");
}

// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
pkg: pkg,

watch: {
files: ["src/**/*.js"],
Expand Down
58 changes: 41 additions & 17 deletions src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,26 +240,50 @@ TinCan client library
@private
*/
parseURL: function (url) {
var parts = String(url).split("?"),
pairs,
pair,
i,
params = {}
;
if (parts.length === 2) {
pairs = parts[1].split("&");
for (i = 0; i < pairs.length; i += 1) {
pair = pairs[i].split("=");
if (pair.length === 2 && pair[0]) {
params[pair[0]] = decodeURIComponent(pair[1]);
}
//
// see http://stackoverflow.com/a/21553982
// and http://stackoverflow.com/a/2880929
//
var reURLInformation,
match,
result,
paramMatch,
pl = /\+/g, // Regex for replacing addition symbol with a space
search = /([^&=]+)=?([^&]*)/g,
decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); };

reURLInformation = new RegExp(
[
"^(https?:)//", // protocol
"(([^:/?#]*)(?::([0-9]+))?)", // host (hostname and port)
"(/[^?#]*)", // pathname
"(\\?[^#]*|)", // search
"(#.*|)$" // hash
].join("")
);
match = url.match(reURLInformation);
result = {
protocol: match[1],
host: match[2],
hostname: match[3],
port: match[4],
pathname: match[5],
search: match[6],
hash: match[7],
params: {}
};

// 'path' is for backwards compatibility
result.path = result.protocol + "//" + result.host + result.pathname;

if (result.search !== "") {
// extra parens to let jshint know this is an expression
while ((paramMatch = search.exec(result.search.substring(1)))) {
result.params[decode(paramMatch[1])] = decode(paramMatch[2]);
}
}

return {
path: parts[0],
params: params
};
return result;
},

/**
Expand Down
89 changes: 53 additions & 36 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,42 +20,59 @@
</head>
<body>

<h2>Full Suite</h2>
<a href="complete.html">Complete Test</a>

<h2>Single Test Files</h2>
<ul>
<li><a href="single/TinCan.html">TinCan</a></li>
<li><a href="single/TinCan-async.html">TinCan Async</a></li>
<li><a href="single/TinCan-sync.html">TinCan Sync</a></li>
<li><a href="single/LRS.html">LRS</a></li>
<li><a href="single/LRS-browser.html">LRS (Browser Only)</a></li>
<li><a href="single/About.html">About</a></li>
<li><a href="single/State.html">State</a></li>
<li><a href="single/ActivityProfile.html">ActivityProfile</a></li>
<li><a href="single/AgentProfile.html">AgentProfile</a></li>
<li><a href="single/StatementsResult.html">StatementsResult</a></li>
<li><a href="single/Agent.html">Agent</a></li>
<li><a href="single/Group.html">Group</a></li>
<li><a href="single/AgentAccount.html">AgentAccount</a></li>
<li><a href="single/Activity.html">Activity</a></li>
<li><a href="single/ActivityDefinition.html">ActivityDefinition</a></li>
<li><a href="single/Context.html">Context</a></li>
<li><a href="single/ContextActivities.html">ContextActivities</a></li>
<li><a href="single/InteractionComponent.html">InteractionComponent</a></li>
<li><a href="single/Result.html">Result</a></li>
<li><a href="single/Score.html">Score</a></li>
<li><a href="single/Statement.html">Statement</a></li>
<li><a href="single/StatementRef.html">StatementRef</a></li>
<li><a href="single/SubStatement.html">SubStatement</a></li>
<li><a href="single/Verb.html">Verb</a></li>
<li><a href="single/Utils.html">Utils</a></li>
</ul>

<h2>Special Conditions</h2>
<ul>
<li><a href="single/offline.html">Offline / CORS Server Denied</a></li>
</ul>
<div style="display: inline-block; *display: inline; vertical-align: top; width: 30%;">
<h2>Full Suite</h2>
<a href="complete.html">Complete Test</a>

<h2>Single Test Files</h2>
<ul>
<li><a href="single/TinCan.html">TinCan</a></li>
<li><a href="single/TinCan-async.html">TinCan Async</a></li>
<li><a href="single/TinCan-sync.html">TinCan Sync</a></li>
<li><a href="single/LRS.html">LRS</a></li>
<li><a href="single/LRS-browser.html">LRS (Browser Only)</a></li>
<li><a href="single/About.html">About</a></li>
<li><a href="single/State.html">State</a></li>
<li><a href="single/ActivityProfile.html">ActivityProfile</a></li>
<li><a href="single/AgentProfile.html">AgentProfile</a></li>
<li><a href="single/StatementsResult.html">StatementsResult</a></li>
<li><a href="single/Agent.html">Agent</a></li>
<li><a href="single/Group.html">Group</a></li>
<li><a href="single/AgentAccount.html">AgentAccount</a></li>
<li><a href="single/Activity.html">Activity</a></li>
<li><a href="single/ActivityDefinition.html">ActivityDefinition</a></li>
<li><a href="single/Context.html">Context</a></li>
<li><a href="single/ContextActivities.html">ContextActivities</a></li>
<li><a href="single/InteractionComponent.html">InteractionComponent</a></li>
<li><a href="single/Result.html">Result</a></li>
<li><a href="single/Score.html">Score</a></li>
<li><a href="single/Statement.html">Statement</a></li>
<li><a href="single/StatementRef.html">StatementRef</a></li>
<li><a href="single/SubStatement.html">SubStatement</a></li>
<li><a href="single/Verb.html">Verb</a></li>
<li><a href="single/Utils.html">Utils</a></li>
</ul>

<h2>Special Conditions</h2>
<ul>
<li><a href="single/offline.html">Offline / CORS Server Denied</a></li>
</ul>
</div>
<div style="display: inline-block; *display: inline; vertical-align: top;">
<h2>Configuration</h2>
<pre id="config"></pre>
</div>

<script>
if (typeof module === "undefined") {
module = {};
}
</script>
<script src="config.js"></script>
<script>
var configEl = document.getElementById("config");
configEl.innerHTML = JSON.stringify(module.exports, null, 4);
</script>

</body>
</html>
62 changes: 59 additions & 3 deletions test/js/unit/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,80 @@
deepEqual(
result,
{
protocol: "http:",
host: "tincanapi.com:8080",
hostname: "tincanapi.com",
port: "8080",
pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test",
search: "",
hash: "",
params: {},
path: "http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test"
},
"return value: no params"
"return value: no params"
);

result = TinCan.Utils.parseURL("http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test?paramA=1&paramB=2");
deepEqual(
result,
{
protocol: "http:",
host: "tincanapi.com:8080",
hostname: "tincanapi.com",
port: "8080",
pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test",
search: "?paramA=1&paramB=2",
hash: "",
params: {
paramA: "1",
paramB: "2"
},
path: "http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test"
},
"return value: with params"
);
"return value: with params"
);

result = TinCan.Utils.parseURL("https://tincanapi.com/TinCanJS/Test/TinCan.Utils_parseURL/test?paramA=1&paramB=2&weirdParam=odd?secondQuestionMark#withHash");
deepEqual(
result,
{
protocol: "https:",
host: "tincanapi.com",
hostname: "tincanapi.com",
port: undefined,
pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test",
search: "?paramA=1&paramB=2&weirdParam=odd?secondQuestionMark",
hash: "#withHash",
params: {
paramA: "1",
paramB: "2",
weirdParam: "odd?secondQuestionMark"
},
path: "https://tincanapi.com/TinCanJS/Test/TinCan.Utils_parseURL/test"
},
"return value: with odd params, https no port, and hash"
);

result = TinCan.Utils.parseURL("http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test?paramA=1&paramB=2&paramC=%23isahashsymbol#theRealHash");
deepEqual(
result,
{
protocol: "http:",
host: "tincanapi.com:8080",
hostname: "tincanapi.com",
port: "8080",
pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test",
search: "?paramA=1&paramB=2&paramC=%23isahashsymbol",
hash: "#theRealHash",
params: {
paramA: "1",
paramB: "2",
paramC: "#isahashsymbol"
},
path: "http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test"
},
"return value: with params"
);
}
);
test(
Expand Down
71 changes: 46 additions & 25 deletions test/node-runner.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
var testRunner = require("qunit");
var testRunner = require("qunit"),
args = process.argv.slice(2),
tests,
isJSUnitFile = /^js\/unit\//,
isAbsoluteFile = /^\//;

if (args.length) {
tests = args;
}
else {
tests = [
"State.js",
"ActivityProfile.js",
"AgentProfile.js",
"StatementsResult.js",
"Agent.js",
"Group.js",
"Activity.js",
"ActivityDefinition.js",
"ContextActivities.js",
"Context.js",
"InteractionComponent.js",
"Result.js",
"Score.js",
"Statement.js",
"StatementRef.js",
"SubStatement.js",
"Verb.js",
"Utils.js",
"TinCan.js",
"TinCan-async.js",
"LRS.js",
"About.js"
];
}

for (i = 0; i < tests.length; i += 1) {
if (isJSUnitFile.test(tests[i])) {
tests[i] = __dirname + "/" + tests[i];
continue;
}
if (! isAbsoluteFile.test(tests[i])) {
tests[i] = __dirname + "/js/unit/" + tests[i];
}
}

testRunner.setup(
{
Expand Down Expand Up @@ -31,30 +75,7 @@ testRunner.run(
{ path: __dirname + "/config.js", namespace: "TinCanTestCfg" }
],
code: { path: __dirname + "/../build/tincan-node.js", namespace: "TinCan" },
tests: [
__dirname + "/js/unit/State.js"
, __dirname + "/js/unit/ActivityProfile.js"
, __dirname + "/js/unit/AgentProfile.js"
, __dirname + "/js/unit/StatementsResult.js"
, __dirname + "/js/unit/Agent.js"
, __dirname + "/js/unit/Group.js"
, __dirname + "/js/unit/Activity.js"
, __dirname + "/js/unit/ActivityDefinition.js"
, __dirname + "/js/unit/ContextActivities.js"
, __dirname + "/js/unit/Context.js"
, __dirname + "/js/unit/InteractionComponent.js"
, __dirname + "/js/unit/Result.js"
, __dirname + "/js/unit/Score.js"
, __dirname + "/js/unit/Statement.js"
, __dirname + "/js/unit/StatementRef.js"
, __dirname + "/js/unit/SubStatement.js"
, __dirname + "/js/unit/Verb.js"
, __dirname + "/js/unit/Utils.js"
, __dirname + "/js/unit/TinCan.js"
, __dirname + "/js/unit/TinCan-async.js"
, __dirname + "/js/unit/LRS.js"
, __dirname + "/js/unit/About.js"
]
tests: tests
},
function (err, report) {
if (err) {
Expand Down

0 comments on commit 19b1396

Please sign in to comment.