1414# KIND, either express or implied. See the License for the
1515# specific language governing permissions and limitations
1616# under the License.
17+ import logging
18+ from typing import Optional
19+
1720from flask import flash , request , Response
1821from flask_appbuilder import expose
1922from flask_appbuilder .security .decorators import has_access_api
2427from superset .typing import FlaskResponse
2528from superset .views .base import BaseSupersetView
2629
30+ logger = logging .getLogger (__name__ )
31+
2732
2833class R (BaseSupersetView ): # pylint: disable=invalid-name
2934
3035 """used for short urls"""
3136
37+ @staticmethod
38+ def _validate_url (url : Optional [str ] = None ) -> bool :
39+ if url and (
40+ url .startswith ("//superset/dashboard/" )
41+ or url .startswith ("//superset/explore/" )
42+ ):
43+ return True
44+ return False
45+
3246 @event_logger .log_this
3347 @expose ("/<int:url_id>" )
3448 def index (self , url_id : int ) -> FlaskResponse : # pylint: disable=no-self-use
@@ -38,8 +52,9 @@ def index(self, url_id: int) -> FlaskResponse: # pylint: disable=no-self-use
3852 if url .url .startswith (explore_url ):
3953 explore_url += f"r={ url_id } "
4054 return redirect (explore_url [1 :])
41-
42- return redirect (url .url [1 :])
55+ if self ._validate_url (url .url ):
56+ return redirect (url .url [1 :])
57+ return redirect ("/" )
4358
4459 flash ("URL to nowhere..." , "danger" )
4560 return redirect ("/" )
@@ -49,6 +64,9 @@ def index(self, url_id: int) -> FlaskResponse: # pylint: disable=no-self-use
4964 @expose ("/shortner/" , methods = ["POST" ])
5065 def shortner (self ) -> FlaskResponse : # pylint: disable=no-self-use
5166 url = request .form .get ("data" )
67+ if not self ._validate_url (url ):
68+ logger .warning ("Invalid URL: %s" , url )
69+ return Response (f"Invalid URL: { url } " , 400 )
5270 obj = models .Url (url = url )
5371 db .session .add (obj )
5472 db .session .commit ()
0 commit comments