2828import gitlab .config
2929from gitlab .const import * # noqa
3030from gitlab .exceptions import * # noqa
31+ from gitlab import utils # noqa
3132
3233__title__ = 'python-gitlab'
3334__version__ = '1.5.1'
3940warnings .filterwarnings ('default' , category = DeprecationWarning ,
4041 module = '^gitlab' )
4142
43+ REDIRECT_MSG = ('python-gitlab detected an http to https redirection. You '
44+ 'must update your GitLab URL to use https:// to avoid issues.' )
45+
4246
4347def _sanitize (value ):
4448 if isinstance (value , dict ):
@@ -394,6 +398,26 @@ def _build_url(self, path):
394398 else :
395399 return '%s%s' % (self ._url , path )
396400
401+ def _check_redirects (self , result ):
402+ # Check the requests history to detect http to https redirections.
403+ # If the initial verb is POST, the next request will use a GET request,
404+ # leading to an unwanted behaviour.
405+ # If the initial verb is PUT, the data will not be send with the next
406+ # request.
407+ # If we detect a redirection to https with a POST or a PUT request, we
408+ # raise an exception with a useful error message.
409+ if result .history and self ._base_url .startswith ('http:' ):
410+ for item in result .history :
411+ if item .status_code not in (301 , 302 ):
412+ continue
413+ # GET methods can be redirected without issue
414+ if result .request .method == 'GET' :
415+ continue
416+ # Did we end-up with an https:// URL?
417+ location = item .headers .get ('Location' , None )
418+ if location and location .startswith ('https://' ):
419+ raise RedirectError (REDIRECT_MSG )
420+
397421 def http_request (self , verb , path , query_data = {}, post_data = None ,
398422 streamed = False , files = None , ** kwargs ):
399423 """Make an HTTP request to the Gitlab server.
@@ -417,27 +441,11 @@ def http_request(self, verb, path, query_data={}, post_data=None,
417441 GitlabHttpError: When the return code is not 2xx
418442 """
419443
420- def sanitized_url (url ):
421- parsed = six .moves .urllib .parse .urlparse (url )
422- new_path = parsed .path .replace ('.' , '%2E' )
423- return parsed ._replace (path = new_path ).geturl ()
424-
425444 url = self ._build_url (path )
426445
427- def copy_dict (dest , src ):
428- for k , v in src .items ():
429- if isinstance (v , dict ):
430- # Transform dict values in new attributes. For example:
431- # custom_attributes: {'foo', 'bar'} =>
432- # custom_attributes['foo']: 'bar'
433- for dict_k , dict_v in v .items ():
434- dest ['%s[%s]' % (k , dict_k )] = dict_v
435- else :
436- dest [k ] = v
437-
438446 params = {}
439- copy_dict (params , query_data )
440- copy_dict (params , kwargs )
447+ utils . copy_dict (params , query_data )
448+ utils . copy_dict (params , kwargs )
441449
442450 opts = self ._get_session_opts (content_type = 'application/json' )
443451
@@ -462,7 +470,7 @@ def copy_dict(dest, src):
462470 req = requests .Request (verb , url , json = json , data = data , params = params ,
463471 files = files , ** opts )
464472 prepped = self .session .prepare_request (req )
465- prepped .url = sanitized_url (prepped .url )
473+ prepped .url = utils . sanitized_url (prepped .url )
466474 settings = self .session .merge_environment_settings (
467475 prepped .url , {}, streamed , verify , None )
468476
@@ -472,6 +480,8 @@ def copy_dict(dest, src):
472480 while True :
473481 result = self .session .send (prepped , timeout = timeout , ** settings )
474482
483+ self ._check_redirects (result )
484+
475485 if 200 <= result .status_code < 300 :
476486 return result
477487
0 commit comments