Merge pull request #4575 from minrk/encode_paths

make sure to encode URL components for API requests
This commit is contained in:
Min RK 2013-12-19 09:49:16 -08:00
commit 8be6c4b617
9 changed files with 182 additions and 64 deletions

View File

@ -419,6 +419,19 @@ IPython.utils = (function (IPython) {
} }
return url; return url;
}; };
var encode_uri_components = function (uri) {
// encode just the components of a multi-segment uri,
// leaving '/' separators
return uri.split('/').map(encodeURIComponent).join('/');
}
var url_join_encode = function () {
// join a sequence of url components with '/',
// encoding each component with encodeURIComponent
return encode_uri_components(url_path_join.apply(null, arguments));
};
var splitext = function (filename) { var splitext = function (filename) {
@ -458,6 +471,8 @@ IPython.utils = (function (IPython) {
autoLinkUrls : autoLinkUrls, autoLinkUrls : autoLinkUrls,
points_to_pixels : points_to_pixels, points_to_pixels : points_to_pixels,
url_path_join : url_path_join, url_path_join : url_path_join,
url_join_encode : url_join_encode,
encode_uri_components : encode_uri_components,
splitext : splitext, splitext : splitext,
always_new : always_new, always_new : always_new,
browser : browser browser : browser

View File

@ -77,7 +77,7 @@ var IPython = (function (IPython) {
IPython.notebook.new_notebook(); IPython.notebook.new_notebook();
}); });
this.element.find('#open_notebook').click(function () { this.element.find('#open_notebook').click(function () {
window.open(utils.url_path_join( window.open(utils.url_join_encode(
that.baseProjectUrl(), that.baseProjectUrl(),
'tree', 'tree',
that.notebookPath() that.notebookPath()
@ -93,7 +93,7 @@ var IPython = (function (IPython) {
IPython.notebook.save_notebook({async : false}); IPython.notebook.save_notebook({async : false});
} }
var url = utils.url_path_join( var url = utils.url_join_encode(
that.baseProjectUrl(), that.baseProjectUrl(),
'files', 'files',
that.notebookPath(), that.notebookPath(),

View File

@ -79,15 +79,11 @@ var IPython = (function (IPython) {
}; };
Notebook.prototype.notebookName = function() { Notebook.prototype.notebookName = function() {
var name = $('body').data('notebookName'); return $('body').data('notebookName');
name = decodeURIComponent(name);
return name;
}; };
Notebook.prototype.notebookPath = function() { Notebook.prototype.notebookPath = function() {
var path = $('body').data('notebookPath'); return $('body').data('notebookPath');
path = decodeURIComponent(path);
return path
}; };
/** /**
@ -1689,10 +1685,10 @@ var IPython = (function (IPython) {
} }
} }
$([IPython.events]).trigger('notebook_saving.Notebook'); $([IPython.events]).trigger('notebook_saving.Notebook');
var url = utils.url_path_join( var url = utils.url_join_encode(
this.baseProjectUrl(), this._baseProjectUrl,
'api/notebooks', 'api/notebooks',
this.notebookPath(), this.notebook_path,
this.notebook_name this.notebook_name
); );
$.ajax(url, settings); $.ajax(url, settings);
@ -1743,15 +1739,15 @@ var IPython = (function (IPython) {
* @method save_notebook_error * @method save_notebook_error
* @param {jqXHR} xhr jQuery Ajax object * @param {jqXHR} xhr jQuery Ajax object
* @param {String} status Description of response status * @param {String} status Description of response status
* @param {String} error_msg HTTP error message * @param {String} error HTTP error message
*/ */
Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) { Notebook.prototype.save_notebook_error = function (xhr, status, error) {
$([IPython.events]).trigger('notebook_save_failed.Notebook'); $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
}; };
Notebook.prototype.new_notebook = function(){ Notebook.prototype.new_notebook = function(){
var path = this.notebookPath(); var path = this.notebook_path;
var base_project_url = this.baseProjectUrl(); var base_project_url = this._baseProjectUrl;
var settings = { var settings = {
processData : false, processData : false,
cache : false, cache : false,
@ -1761,7 +1757,7 @@ var IPython = (function (IPython) {
success : function (data, status, xhr){ success : function (data, status, xhr){
var notebook_name = data.name; var notebook_name = data.name;
window.open( window.open(
utils.url_path_join( utils.url_join_encode(
base_project_url, base_project_url,
'notebooks', 'notebooks',
path, path,
@ -1771,7 +1767,7 @@ var IPython = (function (IPython) {
); );
} }
}; };
var url = utils.url_path_join( var url = utils.url_join_encode(
base_project_url, base_project_url,
'api/notebooks', 'api/notebooks',
path path
@ -1781,8 +1777,8 @@ var IPython = (function (IPython) {
Notebook.prototype.copy_notebook = function(){ Notebook.prototype.copy_notebook = function(){
var path = this.notebookPath(); var path = this.notebook_path;
var base_project_url = this.baseProjectUrl(); var base_project_url = this._baseProjectUrl;
var settings = { var settings = {
processData : false, processData : false,
cache : false, cache : false,
@ -1791,7 +1787,7 @@ var IPython = (function (IPython) {
data : JSON.stringify({copy_from : this.notebook_name}), data : JSON.stringify({copy_from : this.notebook_name}),
async : false, async : false,
success : function (data, status, xhr) { success : function (data, status, xhr) {
window.open(utils.url_path_join( window.open(utils.url_join_encode(
base_project_url, base_project_url,
'notebooks', 'notebooks',
data.path, data.path,
@ -1799,7 +1795,7 @@ var IPython = (function (IPython) {
), '_blank'); ), '_blank');
} }
}; };
var url = utils.url_path_join( var url = utils.url_join_encode(
base_project_url, base_project_url,
'api/notebooks', 'api/notebooks',
path path
@ -1821,10 +1817,10 @@ var IPython = (function (IPython) {
error : $.proxy(that.rename_error, this) error : $.proxy(that.rename_error, this)
}; };
$([IPython.events]).trigger('rename_notebook.Notebook', data); $([IPython.events]).trigger('rename_notebook.Notebook', data);
var url = utils.url_path_join( var url = utils.url_join_encode(
this.baseProjectUrl(), this._baseProjectUrl,
'api/notebooks', 'api/notebooks',
this.notebookPath(), this.notebook_path,
this.notebook_name this.notebook_name
); );
$.ajax(url, settings); $.ajax(url, settings);
@ -1832,19 +1828,20 @@ var IPython = (function (IPython) {
Notebook.prototype.rename_success = function (json, status, xhr) { Notebook.prototype.rename_success = function (json, status, xhr) {
this.notebook_name = json.name this.notebook_name = json.name;
var name = this.notebook_name var name = this.notebook_name;
var path = json.path var path = json.path;
this.session.rename_notebook(name, path); this.session.rename_notebook(name, path);
$([IPython.events]).trigger('notebook_renamed.Notebook', json); $([IPython.events]).trigger('notebook_renamed.Notebook', json);
} }
Notebook.prototype.rename_error = function (json, status, xhr) { Notebook.prototype.rename_error = function (xhr, status, error) {
var that = this; var that = this;
var dialog = $('<div/>').append( var dialog = $('<div/>').append(
$("<p/>").addClass("rename-message") $("<p/>").addClass("rename-message")
.html('This notebook name already exists.') .html('This notebook name already exists.')
) )
$([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
IPython.dialog.modal({ IPython.dialog.modal({
title: "Notebook Rename Error!", title: "Notebook Rename Error!",
body: dialog, body: dialog,
@ -1889,10 +1886,10 @@ var IPython = (function (IPython) {
error : $.proxy(this.load_notebook_error,this), error : $.proxy(this.load_notebook_error,this),
}; };
$([IPython.events]).trigger('notebook_loading.Notebook'); $([IPython.events]).trigger('notebook_loading.Notebook');
var url = utils.url_path_join( var url = utils.url_join_encode(
this._baseProjectUrl, this._baseProjectUrl,
'api/notebooks', 'api/notebooks',
this.notebookPath(), this.notebook_path,
this.notebook_name this.notebook_name
); );
$.ajax(url, settings); $.ajax(url, settings);
@ -1974,12 +1971,13 @@ var IPython = (function (IPython) {
* *
* @method load_notebook_error * @method load_notebook_error
* @param {jqXHR} xhr jQuery Ajax object * @param {jqXHR} xhr jQuery Ajax object
* @param {String} textStatus Description of response status * @param {String} status Description of response status
* @param {String} errorThrow HTTP error message * @param {String} error HTTP error message
*/ */
Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) { Notebook.prototype.load_notebook_error = function (xhr, status, error) {
$([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
if (xhr.status === 400) { if (xhr.status === 400) {
var msg = errorThrow; var msg = error;
} else if (xhr.status === 500) { } else if (xhr.status === 500) {
var msg = "An unknown error occurred while loading this notebook. " + var msg = "An unknown error occurred while loading this notebook. " +
"This version can load notebook formats " + "This version can load notebook formats " +
@ -2034,10 +2032,10 @@ var IPython = (function (IPython) {
* @method list_checkpoints * @method list_checkpoints
*/ */
Notebook.prototype.list_checkpoints = function () { Notebook.prototype.list_checkpoints = function () {
var url = utils.url_path_join( var url = utils.url_join_encode(
this.baseProjectUrl(), this._baseProjectUrl,
'api/notebooks', 'api/notebooks',
this.notebookPath(), this.notebook_path,
this.notebook_name, this.notebook_name,
'checkpoints' 'checkpoints'
); );
@ -2085,8 +2083,8 @@ var IPython = (function (IPython) {
* @method create_checkpoint * @method create_checkpoint
*/ */
Notebook.prototype.create_checkpoint = function () { Notebook.prototype.create_checkpoint = function () {
var url = utils.url_path_join( var url = utils.url_join_encode(
this.baseProjectUrl(), this._baseProjectUrl,
'api/notebooks', 'api/notebooks',
this.notebookPath(), this.notebookPath(),
this.notebook_name, this.notebook_name,
@ -2172,8 +2170,8 @@ var IPython = (function (IPython) {
*/ */
Notebook.prototype.restore_checkpoint = function (checkpoint) { Notebook.prototype.restore_checkpoint = function (checkpoint) {
$([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint); $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
var url = utils.url_path_join( var url = utils.url_join_encode(
this.baseProjectUrl(), this._baseProjectUrl,
'api/notebooks', 'api/notebooks',
this.notebookPath(), this.notebookPath(),
this.notebook_name, this.notebook_name,
@ -2220,8 +2218,8 @@ var IPython = (function (IPython) {
*/ */
Notebook.prototype.delete_checkpoint = function (checkpoint) { Notebook.prototype.delete_checkpoint = function (checkpoint) {
$([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint); $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
var url = utils.url_path_join( var url = utils.url_join_encode(
this.baseProjectUrl(), this._baseProjectUrl,
'api/notebooks', 'api/notebooks',
this.notebookPath(), this.notebookPath(),
this.notebook_name, this.notebook_name,

View File

@ -128,8 +128,8 @@ var IPython = (function (IPython) {
SaveWidget.prototype.update_address_bar = function(){ SaveWidget.prototype.update_address_bar = function(){
var nbname = IPython.notebook.notebook_name; var nbname = IPython.notebook.notebook_name;
var path = IPython.notebook.notebookPath(); var path = IPython.notebook.notebookPath();
var state = {path : utils.url_path_join(path,nbname)}; var state = {path : utils.url_join_encode(path, nbname)};
window.history.replaceState(state, "", utils.url_path_join( window.history.replaceState(state, "", utils.url_join_encode(
"/notebooks", "/notebooks",
path, path,
nbname) nbname)

View File

@ -113,7 +113,7 @@ var IPython = (function (IPython) {
$([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
if (this.running) { if (this.running) {
this.stop_channels(); this.stop_channels();
var url = utils.url_path_join(this.kernel_url, "restart"); var url = utils.url_join_encode(this.kernel_url, "restart");
$.post(url, $.post(url,
$.proxy(this._kernel_started, this), $.proxy(this._kernel_started, this),
'json' 'json'
@ -133,7 +133,7 @@ var IPython = (function (IPython) {
ws_url = prot + location.host + ws_url; ws_url = prot + location.host + ws_url;
} }
this.ws_url = ws_url; this.ws_url = ws_url;
this.kernel_url = utils.url_path_join(this.base_url, this.kernel_id); this.kernel_url = utils.url_join_encode(this.base_url, this.kernel_id);
this.start_channels(); this.start_channels();
}; };

View File

@ -44,7 +44,7 @@ var IPython = (function (IPython) {
} }
}, },
}; };
var url = utils.url_path_join(this._baseProjectUrl, 'api/sessions'); var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions');
$.ajax(url, settings); $.ajax(url, settings);
}; };
@ -64,7 +64,7 @@ var IPython = (function (IPython) {
data: JSON.stringify(model), data: JSON.stringify(model),
dataType : "json", dataType : "json",
}; };
var url = utils.url_path_join(this._baseProjectUrl, 'api/sessions', this.id); var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions', this.id);
$.ajax(url, settings); $.ajax(url, settings);
}; };
@ -76,7 +76,7 @@ var IPython = (function (IPython) {
dataType : "json", dataType : "json",
}; };
this.kernel.running = false; this.kernel.running = false;
var url = utils.url_path_join(this._baseProjectUrl, 'api/sessions', this.id); var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions', this.id);
$.ajax(url, settings); $.ajax(url, settings);
}; };

View File

@ -51,7 +51,7 @@ var IPython = (function (IPython) {
dataType : "json", dataType : "json",
success : $.proxy(this.load_list_success, this) success : $.proxy(this.load_list_success, this)
}; };
var url = utils.url_path_join(this.baseProjectUrl(), 'clusters'); var url = utils.url_join_encode(this.baseProjectUrl(), 'clusters');
$.ajax(url, settings); $.ajax(url, settings);
}; };
@ -137,7 +137,7 @@ var IPython = (function (IPython) {
} }
}; };
status_col.html('starting'); status_col.html('starting');
var url = utils.url_path_join( var url = utils.url_join_encode(
that.baseProjectUrl(), that.baseProjectUrl(),
'clusters', 'clusters',
that.data.profile, that.data.profile,
@ -179,7 +179,7 @@ var IPython = (function (IPython) {
} }
}; };
status_col.html('stopping'); status_col.html('stopping');
var url = utils.url_path_join( var url = utils.url_join_encode(
that.baseProjectUrl(), that.baseProjectUrl(),
'clusters', 'clusters',
that.data.profile, that.data.profile,

View File

@ -147,7 +147,7 @@ var IPython = (function (IPython) {
},this) },this)
}; };
var url = utils.url_path_join( var url = utils.url_join_encode(
this.baseProjectUrl(), this.baseProjectUrl(),
'api', 'api',
'notebooks', 'notebooks',
@ -177,7 +177,7 @@ var IPython = (function (IPython) {
var nbname = utils.splitext(name)[0]; var nbname = utils.splitext(name)[0];
var item = this.new_notebook_item(i); var item = this.new_notebook_item(i);
this.add_link(path, nbname, item); this.add_link(path, nbname, item);
name = utils.url_path_join(this.notebookPath(), name); name = utils.url_path_join(path, name);
if(this.sessions[name] === undefined){ if(this.sessions[name] === undefined){
this.add_delete_button(item); this.add_delete_button(item);
} else { } else {
@ -214,10 +214,10 @@ var IPython = (function (IPython) {
item.find(".item_name").text(nbname); item.find(".item_name").text(nbname);
item.find("a.item_link") item.find("a.item_link")
.attr('href', .attr('href',
utils.url_path_join( utils.url_join_encode(
this.baseProjectUrl(), this.baseProjectUrl(),
"notebooks", "notebooks",
this.notebookPath(), path,
nbname + ".ipynb" nbname + ".ipynb"
) )
).attr('target','_blank'); ).attr('target','_blank');
@ -254,7 +254,7 @@ var IPython = (function (IPython) {
that.load_sessions(); that.load_sessions();
} }
}; };
var url = utils.url_path_join( var url = utils.url_join_encode(
that.baseProjectUrl(), that.baseProjectUrl(),
'api/sessions', 'api/sessions',
session session
@ -294,11 +294,11 @@ var IPython = (function (IPython) {
parent_item.remove(); parent_item.remove();
} }
}; };
var url = utils.url_path_join( var url = utils.url_join_encode(
notebooklist.baseProjectUrl(), notebooklist.baseProjectUrl(),
'api/notebooks', 'api/notebooks',
notebooklist.notebookPath(), notebooklist.notebookPath(),
nbname + '.ipynb' nbname + '.ipynb'
); );
$.ajax(url, settings); $.ajax(url, settings);
} }
@ -339,7 +339,7 @@ var IPython = (function (IPython) {
} }
}; };
var url = utils.url_path_join( var url = utils.url_join_encode(
that.baseProjectUrl(), that.baseProjectUrl(),
'api/notebooks', 'api/notebooks',
that.notebookPath(), that.notebookPath(),
@ -373,7 +373,7 @@ var IPython = (function (IPython) {
success : function (data, status, xhr) { success : function (data, status, xhr) {
var notebook_name = data.name; var notebook_name = data.name;
window.open( window.open(
utils.url_path_join( utils.url_join_encode(
base_project_url, base_project_url,
'notebooks', 'notebooks',
path, path,
@ -382,7 +382,7 @@ var IPython = (function (IPython) {
); );
} }
}; };
var url = utils.url_path_join( var url = utils.url_join_encode(
base_project_url, base_project_url,
'api/notebooks', 'api/notebooks',
path path

View File

@ -0,0 +1,105 @@
//
// Test saving a notebook with escaped characters
//
casper.notebook_test(function () {
// don't use unicode with ambiguous composed/decomposed normalization
// because the filesystem may use a different normalization than literals.
// This causes no actual problems, but will break string comparison.
var nbname = "has#hash and space and unicø∂e.ipynb";
this.evaluate(function (nbname) {
IPython.notebook.notebook_name = nbname;
IPython._save_success = IPython._save_failed = false;
$([IPython.events]).on('notebook_saved.Notebook', function () {
IPython._save_success = true;
});
$([IPython.events]).on('notebook_save_failed.Notebook',
function (event, xhr, status, error) {
IPython._save_failed = "save failed with " + xhr.status + xhr.responseText;
});
IPython.notebook.save_notebook();
}, {nbname:nbname});
this.waitFor(function () {
return this.evaluate(function(){
return IPython._save_failed || IPython._save_success;
});
});
this.then(function(){
var success_failure = this.evaluate(function(){
return [IPython._save_success, IPython._save_failed];
});
this.test.assertEquals(success_failure[1], false, "Save did not fail");
this.test.assertEquals(success_failure[0], true, "Save OK");
var current_name = this.evaluate(function(){
return IPython.notebook.notebook_name;
});
this.test.assertEquals(current_name, nbname, "Save with complicated name");
});
this.thenEvaluate(function(){
$([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
IPython._checkpoint_created = true;
});
IPython._checkpoint_created = false;
IPython.notebook.save_checkpoint();
});
this.waitFor(function () {
return this.evaluate(function(){
return IPython._checkpoint_created;
});
});
this.then(function(){
var checkpoints = this.evaluate(function(){
return IPython.notebook.checkpoints;
});
this.test.assertEquals(checkpoints.length, 1, "checkpoints OK");
});
this.then(function(){
var baseUrl = this.get_notebook_server();
this.open(baseUrl);
});
this.waitForSelector('.list_item');
this.then(function(){
var notebook_url = this.evaluate(function(nbname){
var escaped_name = encodeURIComponent(nbname);
var return_this_thing;
$("a.item_link").map(function (i,a) {
if (a.href.indexOf(escaped_name) >= 0) {
return_this_thing = a.href;
return;
}
});
return return_this_thing;
}, {nbname:nbname});
this.test.assertEquals(notebook_url == null, false, "Escaped URL in notebook list");
// open the notebook
this.open(notebook_url);
});
// wait for the notebook
this.waitForSelector("#notebook");
this.waitFor(function(){
return this.evaluate(function(){
return IPython.notebook || false;
});
});
this.then(function(){
// check that the notebook name is correct
var notebook_name = this.evaluate(function(){
return IPython.notebook.notebook_name;
});
this.test.assertEquals(notebook_name, nbname, "Notebook name is correct");
});
});