Skip to content

Commit

Permalink
Fixing issues with setting headers on py2
Browse files Browse the repository at this point in the history
  • Loading branch information
Valloric committed Feb 19, 2016
1 parent 7a105f5 commit 6899eae
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 7 deletions.
43 changes: 43 additions & 0 deletions ycmd/bottle_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright (C) 2016 ycmd contributors.
#
# This file is part of ycmd.
#
# ycmd is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ycmd is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ycmd. If not, see <http://www.gnu.org/licenses/>.

from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import * # noqa

from future.utils import PY2
from ycmd.utils import ToBytes, ToUnicode
import bottle


# Bottle.py is stupid when it comes to bytes vs unicode so we have to carefully
# conform to its stupidity when setting headers.
# Bottle docs state that the response.headers dict-like object stores keys and
# values as bytes on py2 and unicode on py3. What it _actually_ does is store
# keys in this variable state while values are always unicode (on both py2 and
# py3).
# Both the documented and actual behavior are dumb and cause needless problems.
# Bottle should just consistently store unicode objects on both Python versions,
# making life easier for codebases that work across versions, thus preventing
# tracebacks in the depths of WSGI server frameworks.
def SetResponseHeader( name, value ):
name = ToBytes( name ) if PY2 else ToUnicode( name )
bottle.response.set_header( name, ToUnicode( value ) )
5 changes: 3 additions & 2 deletions ycmd/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@
import bottle
import http.client
import traceback
from bottle import request, response
from bottle import request
from . import server_state
from ycmd import user_options_store
from ycmd.responses import BuildExceptionResponse, BuildCompletionResponse
from ycmd import hmac_plugin
from ycmd import extra_conf_store
from ycmd.request_wrap import RequestWrap
from ycmd.bottle_utils import SetResponseHeader


# num bytes for the request body buffer; request.json only works if the request
Expand Down Expand Up @@ -228,7 +229,7 @@ def ErrorHandler( httperror ):


def _JsonResponse( data ):
response.set_header( 'Content-Type', 'application/json' )
SetResponseHeader( 'Content-Type', 'application/json' )
return json.dumps( data, default = _UniversalSerialize )


Expand Down
10 changes: 6 additions & 4 deletions ycmd/hmac_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
import http.client
from urllib.parse import urlparse
from base64 import b64decode, b64encode
from bottle import request, response, abort
from bottle import request, abort
from ycmd import hmac_utils
from ycmd.utils import ToBytes, ToUnicode
from ycmd.utils import ToBytes
from ycmd.bottle_utils import SetResponseHeader

_HMAC_HEADER = 'x-ycm-hmac'
_HOST_HEADER = 'host'
Expand Down Expand Up @@ -91,5 +92,6 @@ def RequestAuthenticated( method, path, body, hmac_secret ):


def SetHmacHeader( body, hmac_secret ):
response.headers[ _HMAC_HEADER ] = ToUnicode( b64encode(
hmac_utils.CreateHmac( ToBytes( body ), ToBytes( hmac_secret ) ) ) )
value = b64encode( hmac_utils.CreateHmac( ToBytes( body ),
ToBytes( hmac_secret ) ) )
SetResponseHeader( _HMAC_HEADER, value )
55 changes: 55 additions & 0 deletions ycmd/tests/bottle_utils_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (C) 2016 ycmd contributors.
#
# This file is part of ycmd.
#
# ycmd is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ycmd is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ycmd. If not, see <http://www.gnu.org/licenses/>.

# Intentionally not importing unicode_literals!
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import * # noqa

from future.utils import PY2
from nose.tools import eq_
from mock import patch, call
from ycmd import bottle_utils
import bottle


if PY2:
@patch( 'bottle.response' )
def SetResponseHeader_Py2CorrectTypesWithStr_test( *args ):
bottle_utils.SetResponseHeader( 'foo', 'bar' )
eq_( bottle.response.set_header.call_args, call( 'foo', u'bar' ) )


@patch( 'bottle.response' )
def SetResponseHeader_Py2CorrectTypesWithUnicode_test( *args ):
bottle_utils.SetResponseHeader( u'foo', u'bar' )
eq_( bottle.response.set_header.call_args, call( 'foo', u'bar' ) )

else:
@patch( 'bottle.response' )
def SetResponseHeader_Py3CorrectTypesWithBytes_test( *args ):
bottle_utils.SetResponseHeader( b'foo', b'bar' )
eq_( bottle.response.set_header.call_args, call( u'foo', u'bar' ) )


@patch( 'bottle.response' )
def SetResponseHeader_Py3CorrectTypesWithUnicode_test( *args ):
bottle_utils.SetResponseHeader( u'foo', u'bar' )
eq_( bottle.response.set_header.call_args, call( u'foo', u'bar' ) )
2 changes: 1 addition & 1 deletion ycmd/tests/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from future import standard_library
standard_library.install_aliases()
from builtins import * # noqa
from future.utils import PY2, native

from future.utils import PY2, native
from hamcrest import raises, assert_that, calling
from mock import patch, call
from nose.tools import eq_, ok_
Expand Down

0 comments on commit 6899eae

Please sign in to comment.