client/general: improve URL escaping

Specifically, cater for /, + and % in URL components.
This commit is contained in:
rr- 2016-09-04 01:25:19 +02:00
parent a22fe306d1
commit 7fa8593b0a
15 changed files with 39 additions and 26 deletions

View file

@ -151,6 +151,7 @@ the one in the `config.yaml`, so that client knows how to access the backend!
server { server {
listen 80; listen 80;
server_name great.dude; server_name great.dude;
merge_slashes off; # to support post tags such as ///
location ~ ^/api$ { location ~ ^/api$ {
return 302 /api/; return 302 /api/;

View file

@ -63,12 +63,15 @@ class Api extends events.EventTarget {
_process(url, requestFactory, data, files, options) { _process(url, requestFactory, data, files, options) {
options = options || {}; options = options || {};
const fullUrl = this._getFullUrl(url); const [fullUrl, query] = this._getFullUrl(url);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!options.noProgress) { if (!options.noProgress) {
nprogress.start(); nprogress.start();
} }
let req = requestFactory(fullUrl); let req = requestFactory(fullUrl);
if (query) {
req.query(query);
}
if (data) { if (data) {
req.attach('metadata', new Blob([JSON.stringify(data)])); req.attach('metadata', new Blob([JSON.stringify(data)]));
} }
@ -176,8 +179,12 @@ class Api extends events.EventTarget {
} }
_getFullUrl(url) { _getFullUrl(url) {
return (config.apiUrl + '/' + encodeURI(url)) const fullUrl =
.replace(/([^:])\/+/g, '$1/'); (config.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/');
const matches = fullUrl.match(/^([^?]*)\??(.*)$/);
const baseUrl = matches[1];
const request = matches[2];
return [baseUrl, request];
} }
} }

View file

@ -32,7 +32,7 @@ class CommentsController {
}, },
requestPage: page => { requestPage: page => {
return PostList.search( return PostList.search(
'sort:comment-date+comment-count-min:1', page, 10, fields); 'sort:comment-date comment-count-min:1', page, 10, fields);
}, },
pageRenderer: pageCtx => { pageRenderer: pageCtx => {
Object.assign(pageCtx, { Object.assign(pageCtx, {

View file

@ -255,7 +255,7 @@ class PostController {
} }
module.exports = router => { module.exports = router => {
router.enter('/post/:id/edit/:parameters?', router.enter('/post/:id/edit/:parameters(.*)?',
(ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); },
(ctx, next) => { (ctx, next) => {
// restore parameters from history state // restore parameters from history state
@ -265,7 +265,7 @@ module.exports = router => {
ctx.controller = new PostController(ctx.parameters.id, true, ctx); ctx.controller = new PostController(ctx.parameters.id, true, ctx);
}); });
router.enter( router.enter(
'/post/:id/:parameters?', '/post/:id/:parameters(.*)?',
(ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); },
(ctx, next) => { (ctx, next) => {
// restore parameters from history state // restore parameters from history state

View file

@ -120,7 +120,7 @@ class PostListController {
module.exports = router => { module.exports = router => {
router.enter( router.enter(
'/posts/:parameters?', '/posts/:parameters(.*)?',
(ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); },
(ctx, next) => { ctx.controller = new PostListController(ctx); }); (ctx, next) => { ctx.controller = new PostListController(ctx); });
}; };

View file

@ -121,16 +121,16 @@ class TagController {
} }
module.exports = router => { module.exports = router => {
router.enter('/tag/:name', (ctx, next) => { router.enter('/tag/:name(.+?)/edit', (ctx, next) => {
ctx.controller = new TagController(ctx, 'summary');
});
router.enter('/tag/:name/edit', (ctx, next) => {
ctx.controller = new TagController(ctx, 'edit'); ctx.controller = new TagController(ctx, 'edit');
}); });
router.enter('/tag/:name/merge', (ctx, next) => { router.enter('/tag/:name(.+?)/merge', (ctx, next) => {
ctx.controller = new TagController(ctx, 'merge'); ctx.controller = new TagController(ctx, 'merge');
}); });
router.enter('/tag/:name/delete', (ctx, next) => { router.enter('/tag/:name(.+?)/delete', (ctx, next) => {
ctx.controller = new TagController(ctx, 'delete'); ctx.controller = new TagController(ctx, 'delete');
}); });
router.enter('/tag/:name(.+)', (ctx, next) => {
ctx.controller = new TagController(ctx, 'summary');
});
}; };

View file

@ -75,7 +75,7 @@ class TagListController {
module.exports = router => { module.exports = router => {
router.enter( router.enter(
'/tags/:parameters?', '/tags/:parameters(.*)?',
(ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); },
(ctx, next) => { ctx.controller = new TagListController(ctx); }); (ctx, next) => { ctx.controller = new TagListController(ctx); });
}; };

View file

@ -69,7 +69,7 @@ class UserListController {
module.exports = router => { module.exports = router => {
router.enter( router.enter(
'/users/:parameters?', '/users/:parameters(.*)?',
(ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); },
(ctx, next) => { ctx.controller = new UserListController(ctx); }); (ctx, next) => { ctx.controller = new UserListController(ctx); });
}; };

View file

@ -6,7 +6,10 @@ const Post = require('./post.js');
class PostList extends AbstractList { class PostList extends AbstractList {
static getAround(id, searchQuery) { static getAround(id, searchQuery) {
return api.get(`/post/${id}/around?fields=id&query=${searchQuery}`) const url =
`/post/${id}/around?fields=id` +
`&query=${encodeURIComponent(searchQuery)}`;
return api.get(url)
.then(response => { .then(response => {
return Promise.resolve(response); return Promise.resolve(response);
}).catch(response => { }).catch(response => {
@ -16,7 +19,7 @@ class PostList extends AbstractList {
static search(text, page, pageSize, fields) { static search(text, page, pageSize, fields) {
const url = const url =
`/posts/?query=${text}` + `/posts/?query=${encodeURIComponent(text)}` +
`&page=${page}` + `&page=${page}` +
`&pageSize=${pageSize}` + `&pageSize=${pageSize}` +
`&fields=${fields.join(',')}`; `&fields=${fields.join(',')}`;

View file

@ -7,7 +7,7 @@ const Snapshot = require('./snapshot.js');
class SnapshotList extends AbstractList { class SnapshotList extends AbstractList {
static search(text, page, pageSize) { static search(text, page, pageSize) {
const url = const url =
`/snapshots/?query=${text}` + `/snapshots/?query=${encodeURIComponent(text)}` +
`&page=${page}` + `&page=${page}` +
`&pageSize=${pageSize}`; `&pageSize=${pageSize}`;
return api.get(url).then(response => { return api.get(url).then(response => {

View file

@ -7,7 +7,7 @@ const Tag = require('./tag.js');
class TagList extends AbstractList { class TagList extends AbstractList {
static search(text, page, pageSize, fields) { static search(text, page, pageSize, fields) {
const url = const url =
`/tags/?query=${text}` + `/tags/?query=${encodeURIComponent(text)}` +
`&page=${page}` + `&page=${page}` +
`&pageSize=${pageSize}` + `&pageSize=${pageSize}` +
`&fields=${fields.join(',')}`; `&fields=${fields.join(',')}`;

View file

@ -6,7 +6,9 @@ const User = require('./user.js');
class UserList extends AbstractList { class UserList extends AbstractList {
static search(text, page) { static search(text, page) {
const url = `/users/?query=${text}&page=${page}&pageSize=30`; const url =
`/users/?query=${encodeURIComponent(text)}` +
`&page=${page}&pageSize=30`;
return api.get(url).then(response => { return api.get(url).then(response => {
return Promise.resolve(Object.assign( return Promise.resolve(Object.assign(
{}, {},

View file

@ -75,7 +75,7 @@ class Route {
const keys = this.keys; const keys = this.keys;
const qsIndex = path.indexOf('?'); const qsIndex = path.indexOf('?');
const pathname = ~qsIndex ? path.slice(0, qsIndex) : path; const pathname = ~qsIndex ? path.slice(0, qsIndex) : path;
const m = this.regexp.exec(decodeURIComponent(pathname)); const m = this.regexp.exec(pathname);
if (!m) { if (!m) {
return false; return false;

View file

@ -165,7 +165,7 @@ function formatUrlParameters(dict) {
continue; continue;
} }
if (value) { if (value) {
result.push(`${key}=${value}`); result.push(`${key}=${encodeURIComponent(value)}`);
} }
} }
return result.join(';'); return result.join(';');

View file

@ -56,14 +56,14 @@ def create_tag(ctx, _params=None):
return _serialize(ctx, tag) return _serialize(ctx, tag)
@routes.get('/tag/(?P<tag_name>[^/]+)/?') @routes.get('/tag/(?P<tag_name>.+)')
def get_tag(ctx, params): def get_tag(ctx, params):
auth.verify_privilege(ctx.user, 'tags:view') auth.verify_privilege(ctx.user, 'tags:view')
tag = tags.get_tag_by_name(params['tag_name']) tag = tags.get_tag_by_name(params['tag_name'])
return _serialize(ctx, tag) return _serialize(ctx, tag)
@routes.put('/tag/(?P<tag_name>[^/]+)/?') @routes.put('/tag/(?P<tag_name>.+)')
def update_tag(ctx, params): def update_tag(ctx, params):
tag = tags.get_tag_by_name(params['tag_name']) tag = tags.get_tag_by_name(params['tag_name'])
versions.verify_version(tag, ctx) versions.verify_version(tag, ctx)
@ -97,7 +97,7 @@ def update_tag(ctx, params):
return _serialize(ctx, tag) return _serialize(ctx, tag)
@routes.delete('/tag/(?P<tag_name>[^/]+)/?') @routes.delete('/tag/(?P<tag_name>.+)')
def delete_tag(ctx, params): def delete_tag(ctx, params):
tag = tags.get_tag_by_name(params['tag_name']) tag = tags.get_tag_by_name(params['tag_name'])
versions.verify_version(tag, ctx) versions.verify_version(tag, ctx)
@ -126,7 +126,7 @@ def merge_tags(ctx, _params=None):
return _serialize(ctx, target_tag) return _serialize(ctx, target_tag)
@routes.get('/tag-siblings/(?P<tag_name>[^/]+)/?') @routes.get('/tag-siblings/(?P<tag_name>.+)')
def get_tag_siblings(ctx, params): def get_tag_siblings(ctx, params):
auth.verify_privilege(ctx.user, 'tags:view') auth.verify_privilege(ctx.user, 'tags:view')
tag = tags.get_tag_by_name(params['tag_name']) tag = tags.get_tag_by_name(params['tag_name'])