From 7fa8593b0ab688e720c553c47a79065d6c653091 Mon Sep 17 00:00:00 2001 From: rr- Date: Sun, 4 Sep 2016 01:25:19 +0200 Subject: [PATCH] client/general: improve URL escaping Specifically, cater for /, + and % in URL components. --- INSTALL.md | 1 + client/js/api.js | 13 ++++++++++--- client/js/controllers/comments_controller.js | 2 +- client/js/controllers/post_controller.js | 4 ++-- client/js/controllers/post_list_controller.js | 2 +- client/js/controllers/tag_controller.js | 12 ++++++------ client/js/controllers/tag_list_controller.js | 2 +- client/js/controllers/user_list_controller.js | 2 +- client/js/models/post_list.js | 7 +++++-- client/js/models/snapshot_list.js | 2 +- client/js/models/tag_list.js | 2 +- client/js/models/user_list.js | 4 +++- client/js/router.js | 2 +- client/js/util/misc.js | 2 +- server/szurubooru/api/tag_api.py | 8 ++++---- 15 files changed, 39 insertions(+), 26 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 880d3bf8..bf758338 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -151,6 +151,7 @@ the one in the `config.yaml`, so that client knows how to access the backend! server { listen 80; server_name great.dude; + merge_slashes off; # to support post tags such as /// location ~ ^/api$ { return 302 /api/; diff --git a/client/js/api.js b/client/js/api.js index 016d82bb..ab93b9ad 100644 --- a/client/js/api.js +++ b/client/js/api.js @@ -63,12 +63,15 @@ class Api extends events.EventTarget { _process(url, requestFactory, data, files, options) { options = options || {}; - const fullUrl = this._getFullUrl(url); + const [fullUrl, query] = this._getFullUrl(url); return new Promise((resolve, reject) => { if (!options.noProgress) { nprogress.start(); } let req = requestFactory(fullUrl); + if (query) { + req.query(query); + } if (data) { req.attach('metadata', new Blob([JSON.stringify(data)])); } @@ -176,8 +179,12 @@ class Api extends events.EventTarget { } _getFullUrl(url) { - return (config.apiUrl + '/' + encodeURI(url)) - .replace(/([^:])\/+/g, '$1/'); + const fullUrl = + (config.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/'); + const matches = fullUrl.match(/^([^?]*)\??(.*)$/); + const baseUrl = matches[1]; + const request = matches[2]; + return [baseUrl, request]; } } diff --git a/client/js/controllers/comments_controller.js b/client/js/controllers/comments_controller.js index da760099..ea88a9cc 100644 --- a/client/js/controllers/comments_controller.js +++ b/client/js/controllers/comments_controller.js @@ -32,7 +32,7 @@ class CommentsController { }, requestPage: page => { 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 => { Object.assign(pageCtx, { diff --git a/client/js/controllers/post_controller.js b/client/js/controllers/post_controller.js index ec847444..7644e261 100644 --- a/client/js/controllers/post_controller.js +++ b/client/js/controllers/post_controller.js @@ -255,7 +255,7 @@ class PostController { } module.exports = router => { - router.enter('/post/:id/edit/:parameters?', + router.enter('/post/:id/edit/:parameters(.*)?', (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { // restore parameters from history state @@ -265,7 +265,7 @@ module.exports = router => { ctx.controller = new PostController(ctx.parameters.id, true, ctx); }); router.enter( - '/post/:id/:parameters?', + '/post/:id/:parameters(.*)?', (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { // restore parameters from history state diff --git a/client/js/controllers/post_list_controller.js b/client/js/controllers/post_list_controller.js index c1dfd066..a3eab90f 100644 --- a/client/js/controllers/post_list_controller.js +++ b/client/js/controllers/post_list_controller.js @@ -120,7 +120,7 @@ class PostListController { module.exports = router => { router.enter( - '/posts/:parameters?', + '/posts/:parameters(.*)?', (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { ctx.controller = new PostListController(ctx); }); }; diff --git a/client/js/controllers/tag_controller.js b/client/js/controllers/tag_controller.js index 74a6e4a1..3a824d4a 100644 --- a/client/js/controllers/tag_controller.js +++ b/client/js/controllers/tag_controller.js @@ -121,16 +121,16 @@ class TagController { } module.exports = router => { - router.enter('/tag/:name', (ctx, next) => { - ctx.controller = new TagController(ctx, 'summary'); - }); - router.enter('/tag/:name/edit', (ctx, next) => { + router.enter('/tag/:name(.+?)/edit', (ctx, next) => { 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'); }); - router.enter('/tag/:name/delete', (ctx, next) => { + router.enter('/tag/:name(.+?)/delete', (ctx, next) => { ctx.controller = new TagController(ctx, 'delete'); }); + router.enter('/tag/:name(.+)', (ctx, next) => { + ctx.controller = new TagController(ctx, 'summary'); + }); }; diff --git a/client/js/controllers/tag_list_controller.js b/client/js/controllers/tag_list_controller.js index 0f1f00ec..d61d8f71 100644 --- a/client/js/controllers/tag_list_controller.js +++ b/client/js/controllers/tag_list_controller.js @@ -75,7 +75,7 @@ class TagListController { module.exports = router => { router.enter( - '/tags/:parameters?', + '/tags/:parameters(.*)?', (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { ctx.controller = new TagListController(ctx); }); }; diff --git a/client/js/controllers/user_list_controller.js b/client/js/controllers/user_list_controller.js index 6e44ce8a..98e70031 100644 --- a/client/js/controllers/user_list_controller.js +++ b/client/js/controllers/user_list_controller.js @@ -69,7 +69,7 @@ class UserListController { module.exports = router => { router.enter( - '/users/:parameters?', + '/users/:parameters(.*)?', (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); }, (ctx, next) => { ctx.controller = new UserListController(ctx); }); }; diff --git a/client/js/models/post_list.js b/client/js/models/post_list.js index 451ce285..b03dd8e1 100644 --- a/client/js/models/post_list.js +++ b/client/js/models/post_list.js @@ -6,7 +6,10 @@ const Post = require('./post.js'); class PostList extends AbstractList { 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 => { return Promise.resolve(response); }).catch(response => { @@ -16,7 +19,7 @@ class PostList extends AbstractList { static search(text, page, pageSize, fields) { const url = - `/posts/?query=${text}` + + `/posts/?query=${encodeURIComponent(text)}` + `&page=${page}` + `&pageSize=${pageSize}` + `&fields=${fields.join(',')}`; diff --git a/client/js/models/snapshot_list.js b/client/js/models/snapshot_list.js index 628ee295..22348f4a 100644 --- a/client/js/models/snapshot_list.js +++ b/client/js/models/snapshot_list.js @@ -7,7 +7,7 @@ const Snapshot = require('./snapshot.js'); class SnapshotList extends AbstractList { static search(text, page, pageSize) { const url = - `/snapshots/?query=${text}` + + `/snapshots/?query=${encodeURIComponent(text)}` + `&page=${page}` + `&pageSize=${pageSize}`; return api.get(url).then(response => { diff --git a/client/js/models/tag_list.js b/client/js/models/tag_list.js index ec578bbf..268bcd80 100644 --- a/client/js/models/tag_list.js +++ b/client/js/models/tag_list.js @@ -7,7 +7,7 @@ const Tag = require('./tag.js'); class TagList extends AbstractList { static search(text, page, pageSize, fields) { const url = - `/tags/?query=${text}` + + `/tags/?query=${encodeURIComponent(text)}` + `&page=${page}` + `&pageSize=${pageSize}` + `&fields=${fields.join(',')}`; diff --git a/client/js/models/user_list.js b/client/js/models/user_list.js index c829afda..4ca430d5 100644 --- a/client/js/models/user_list.js +++ b/client/js/models/user_list.js @@ -6,7 +6,9 @@ const User = require('./user.js'); class UserList extends AbstractList { 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 Promise.resolve(Object.assign( {}, diff --git a/client/js/router.js b/client/js/router.js index f757e075..570fb3ef 100644 --- a/client/js/router.js +++ b/client/js/router.js @@ -75,7 +75,7 @@ class Route { const keys = this.keys; const qsIndex = path.indexOf('?'); const pathname = ~qsIndex ? path.slice(0, qsIndex) : path; - const m = this.regexp.exec(decodeURIComponent(pathname)); + const m = this.regexp.exec(pathname); if (!m) { return false; diff --git a/client/js/util/misc.js b/client/js/util/misc.js index c46f9793..83e286df 100644 --- a/client/js/util/misc.js +++ b/client/js/util/misc.js @@ -165,7 +165,7 @@ function formatUrlParameters(dict) { continue; } if (value) { - result.push(`${key}=${value}`); + result.push(`${key}=${encodeURIComponent(value)}`); } } return result.join(';'); diff --git a/server/szurubooru/api/tag_api.py b/server/szurubooru/api/tag_api.py index 81872de0..a69673ad 100644 --- a/server/szurubooru/api/tag_api.py +++ b/server/szurubooru/api/tag_api.py @@ -56,14 +56,14 @@ def create_tag(ctx, _params=None): return _serialize(ctx, tag) -@routes.get('/tag/(?P[^/]+)/?') +@routes.get('/tag/(?P.+)') def get_tag(ctx, params): auth.verify_privilege(ctx.user, 'tags:view') tag = tags.get_tag_by_name(params['tag_name']) return _serialize(ctx, tag) -@routes.put('/tag/(?P[^/]+)/?') +@routes.put('/tag/(?P.+)') def update_tag(ctx, params): tag = tags.get_tag_by_name(params['tag_name']) versions.verify_version(tag, ctx) @@ -97,7 +97,7 @@ def update_tag(ctx, params): return _serialize(ctx, tag) -@routes.delete('/tag/(?P[^/]+)/?') +@routes.delete('/tag/(?P.+)') def delete_tag(ctx, params): tag = tags.get_tag_by_name(params['tag_name']) versions.verify_version(tag, ctx) @@ -126,7 +126,7 @@ def merge_tags(ctx, _params=None): return _serialize(ctx, target_tag) -@routes.get('/tag-siblings/(?P[^/]+)/?') +@routes.get('/tag-siblings/(?P.+)') def get_tag_siblings(ctx, params): auth.verify_privilege(ctx.user, 'tags:view') tag = tags.get_tag_by_name(params['tag_name'])