2013-12-14 15:50:30 +01:00
|
|
|
# nghttp2 - HTTP/2.0 C Library
|
|
|
|
|
|
|
|
# Copyright (c) 2013 Tatsuhiro Tsujikawa
|
|
|
|
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
# a copy of this software and associated documentation files (the
|
|
|
|
# "Software"), to deal in the Software without restriction, including
|
|
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
# the following conditions:
|
|
|
|
|
|
|
|
# The above copyright notice and this permission notice shall be
|
|
|
|
# included in all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
cimport cnghttp2
|
|
|
|
|
|
|
|
from libc.stdlib cimport malloc, free
|
|
|
|
from libc.string cimport memcpy, memset
|
|
|
|
from libc.stdint cimport uint8_t, uint16_t, uint32_t, int32_t
|
|
|
|
|
|
|
|
HD_SIDE_REQUEST = cnghttp2.NGHTTP2_HD_SIDE_REQUEST
|
|
|
|
HD_SIDE_RESPONSE = cnghttp2.NGHTTP2_HD_SIDE_RESPONSE
|
|
|
|
|
|
|
|
HD_DEFLATE_HD_TABLE_BUFSIZE_MAX = 4096
|
|
|
|
|
2013-12-17 16:20:14 +01:00
|
|
|
HD_ENTRY_OVERHEAD = cnghttp2.NGHTTP2_HD_ENTRY_OVERHEAD
|
|
|
|
|
|
|
|
class HDTableEntry:
|
|
|
|
|
2013-12-21 15:02:48 +01:00
|
|
|
def __init__(self, name, namelen, value, valuelen, ref):
|
2013-12-17 16:20:14 +01:00
|
|
|
self.name = name
|
2013-12-21 15:02:48 +01:00
|
|
|
self.namelen = namelen
|
2013-12-17 16:20:14 +01:00
|
|
|
self.value = value
|
2013-12-21 15:02:48 +01:00
|
|
|
self.valuelen = valuelen
|
2013-12-17 16:20:14 +01:00
|
|
|
self.ref = ref
|
|
|
|
|
|
|
|
def space(self):
|
2013-12-21 15:02:48 +01:00
|
|
|
return self.namelen + self.valuelen + HD_ENTRY_OVERHEAD
|
2013-12-17 16:20:14 +01:00
|
|
|
|
2014-01-26 09:53:04 +01:00
|
|
|
cdef _change_table_size(cnghttp2.nghttp2_hd_context *ctx, hd_table_bufsize_max):
|
|
|
|
cdef int rv
|
|
|
|
rv = cnghttp2.nghttp2_hd_change_table_size(ctx, hd_table_bufsize_max)
|
|
|
|
if rv != 0:
|
|
|
|
raise Exception(_strerror(rv))
|
|
|
|
|
|
|
|
cdef _get_hd_table(cnghttp2.nghttp2_hd_context *ctx):
|
|
|
|
cdef int length = ctx.hd_table.len
|
|
|
|
cdef cnghttp2.nghttp2_hd_entry *entry
|
|
|
|
res = []
|
|
|
|
for i in range(length):
|
|
|
|
entry = cnghttp2.nghttp2_hd_table_get(ctx, i)
|
|
|
|
k = _get_pybytes(entry.nv.name, entry.nv.namelen)
|
|
|
|
v = _get_pybytes(entry.nv.value, entry.nv.valuelen)
|
|
|
|
res.append(HDTableEntry(k, entry.nv.namelen,
|
|
|
|
v, entry.nv.valuelen,
|
|
|
|
(entry.flags & cnghttp2.NGHTTP2_HD_FLAG_REFSET) != 0))
|
|
|
|
return res
|
2013-12-17 16:20:14 +01:00
|
|
|
|
2013-12-21 15:02:48 +01:00
|
|
|
cdef _get_pybytes(uint8_t *b, uint16_t blen):
|
|
|
|
# While the |blen| is positive, the |b| could be NULL. This is
|
|
|
|
# because deflater may deallocate the byte strings its local table
|
|
|
|
# space.
|
|
|
|
if b == NULL and blen > 0:
|
|
|
|
val = None
|
|
|
|
else:
|
|
|
|
val = b[:blen]
|
|
|
|
return val
|
|
|
|
|
2014-01-26 09:53:04 +01:00
|
|
|
cdef class HDDeflater:
|
2013-12-14 15:50:30 +01:00
|
|
|
'''Performs header compression. The header compression algorithm has
|
|
|
|
to know the header set to be compressed is request headers or
|
|
|
|
response headers. It is indicated by |side| parameter in the
|
|
|
|
constructor. The constructor also takes |hd_table_bufsize_max|
|
|
|
|
parameter, which limits the usage of header table in the given
|
|
|
|
amount of bytes. This is necessary because the header compressor
|
|
|
|
and decompressor has to share the same amount of header table and
|
|
|
|
the decompressor decides that number. The compressor may not want
|
|
|
|
to use all header table size because of limited memory
|
|
|
|
availability. In that case, the |hd_table_bufsize_max| can be used
|
|
|
|
to cap the upper limit of talbe size whatever the header table
|
|
|
|
size is chosen. The default value of |hd_table_bufsize_max| is
|
|
|
|
4096 bytes.
|
|
|
|
|
|
|
|
The following example shows how to compress request header sets:
|
|
|
|
|
|
|
|
import binascii, nghttp2
|
|
|
|
|
|
|
|
deflater = nghttp2.HDDeflater(nghttp2.HD_SIDE_REQUEST)
|
|
|
|
res = deflater.deflate([(b'foo', b'bar'),
|
|
|
|
(b'baz', b'buz')])
|
|
|
|
print(binascii.b2a_hex(res))
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
2014-01-26 09:53:04 +01:00
|
|
|
cdef cnghttp2.nghttp2_hd_deflater _deflater
|
|
|
|
|
2013-12-14 15:50:30 +01:00
|
|
|
def __cinit__(self, side,
|
|
|
|
hd_table_bufsize_max = HD_DEFLATE_HD_TABLE_BUFSIZE_MAX):
|
2014-01-26 09:53:04 +01:00
|
|
|
rv = cnghttp2.nghttp2_hd_deflate_init2(&self._deflater, side,
|
2013-12-14 15:50:30 +01:00
|
|
|
hd_table_bufsize_max)
|
|
|
|
if rv != 0:
|
|
|
|
raise Exception(_strerror(rv))
|
|
|
|
|
|
|
|
def __init__(self, side,
|
|
|
|
hd_table_bufsize_max = HD_DEFLATE_HD_TABLE_BUFSIZE_MAX):
|
2013-12-17 15:53:49 +01:00
|
|
|
super(HDDeflater, self).__init__()
|
2013-12-14 15:50:30 +01:00
|
|
|
|
|
|
|
def __dealloc__(self):
|
2014-01-26 09:53:04 +01:00
|
|
|
cnghttp2.nghttp2_hd_deflate_free(&self._deflater)
|
2013-12-14 15:50:30 +01:00
|
|
|
|
|
|
|
def deflate(self, headers):
|
|
|
|
'''Compresses the |headers|. The |headers| must be sequence of tuple
|
|
|
|
of name/value pair, which are sequence of bytes (not unicode
|
|
|
|
string).
|
|
|
|
|
|
|
|
This function returns the encoded header block in byte string.
|
|
|
|
An exception will be raised on error.
|
|
|
|
|
|
|
|
'''
|
|
|
|
cdef cnghttp2.nghttp2_nv *nva = <cnghttp2.nghttp2_nv*>\
|
|
|
|
malloc(sizeof(cnghttp2.nghttp2_nv)*\
|
|
|
|
len(headers))
|
|
|
|
cdef cnghttp2.nghttp2_nv *nvap = nva
|
|
|
|
for k, v in headers:
|
|
|
|
nvap[0].name = k
|
|
|
|
nvap[0].namelen = len(k)
|
|
|
|
nvap[0].value = v
|
|
|
|
nvap[0].valuelen = len(v)
|
|
|
|
nvap += 1
|
|
|
|
cdef uint8_t *out = NULL
|
|
|
|
cdef size_t outcap = 0
|
|
|
|
cdef ssize_t rv
|
2014-01-26 09:53:04 +01:00
|
|
|
rv = cnghttp2.nghttp2_hd_deflate_hd(&self._deflater, &out, &outcap,
|
2013-12-14 15:50:30 +01:00
|
|
|
0, nva, len(headers))
|
|
|
|
free(nva)
|
|
|
|
if rv < 0:
|
|
|
|
raise Exception(_strerror(rv))
|
|
|
|
cdef bytes res
|
|
|
|
try:
|
|
|
|
res = out[:rv]
|
|
|
|
finally:
|
|
|
|
cnghttp2.nghttp2_free(out)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def set_no_refset(self, no_refset):
|
|
|
|
'''Tells the compressor not to use reference set if |no_refset| is
|
|
|
|
nonzero. If |no_refset| is nonzero, on each invocation of
|
|
|
|
deflate(), compressor first emits index=0 to clear up
|
|
|
|
reference set.
|
|
|
|
|
|
|
|
'''
|
2014-01-26 09:53:04 +01:00
|
|
|
cnghttp2.nghttp2_hd_deflate_set_no_refset(&self._deflater, no_refset)
|
|
|
|
|
|
|
|
def change_table_size(self, hd_table_bufsize_max):
|
|
|
|
'''Changes header table size to |hd_table_bufsize_max| byte.
|
|
|
|
|
|
|
|
An exception will be raised on error.
|
|
|
|
|
|
|
|
'''
|
|
|
|
_change_table_size(&self._deflater.ctx, hd_table_bufsize_max)
|
|
|
|
|
|
|
|
def get_hd_table(self,):
|
|
|
|
'''Returns copy of current dynamic header table.'''
|
|
|
|
return _get_hd_table(&self._deflater.ctx)
|
2013-12-14 15:50:30 +01:00
|
|
|
|
2014-01-26 09:53:04 +01:00
|
|
|
cdef class HDInflater:
|
2013-12-14 15:50:30 +01:00
|
|
|
'''Performs header decompression.
|
|
|
|
|
|
|
|
The following example shows how to compress request header sets:
|
|
|
|
|
|
|
|
data = b'0082c5ad82bd0f000362617a0362757a'
|
|
|
|
inflater = nghttp2.HDInflater(nghttp2.HD_SIDE_REQUEST)
|
|
|
|
hdrs = inflater.inflate(data)
|
|
|
|
print(hdrs)
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
2014-01-26 09:53:04 +01:00
|
|
|
cdef cnghttp2.nghttp2_hd_inflater _inflater
|
|
|
|
|
2013-12-14 15:50:30 +01:00
|
|
|
def __cinit__(self, side):
|
2014-01-26 09:53:04 +01:00
|
|
|
rv = cnghttp2.nghttp2_hd_inflate_init(&self._inflater, side)
|
2013-12-14 15:50:30 +01:00
|
|
|
if rv != 0:
|
|
|
|
raise Exception(_strerror(rv))
|
|
|
|
|
|
|
|
def __init__(self, side):
|
2013-12-17 15:53:49 +01:00
|
|
|
super(HDInflater, self).__init__()
|
2013-12-14 15:50:30 +01:00
|
|
|
|
|
|
|
def __dealloc__(self):
|
2014-01-26 09:53:04 +01:00
|
|
|
cnghttp2.nghttp2_hd_inflate_free(&self._inflater)
|
2013-12-14 15:50:30 +01:00
|
|
|
|
|
|
|
def inflate(self, data):
|
|
|
|
'''Decompresses the compressed header block |data|. The |data| must be
|
|
|
|
byte string (not unicode string).
|
|
|
|
|
|
|
|
'''
|
2014-01-16 18:27:42 +01:00
|
|
|
cdef cnghttp2.nghttp2_nv nv
|
2014-01-26 09:53:04 +01:00
|
|
|
cdef int inflate_flags
|
2013-12-14 15:50:30 +01:00
|
|
|
cdef ssize_t rv
|
2014-01-16 18:27:42 +01:00
|
|
|
cdef uint8_t *buf = data
|
|
|
|
cdef size_t buflen = len(data)
|
|
|
|
res = []
|
|
|
|
while True:
|
2014-01-26 09:53:04 +01:00
|
|
|
inflate_flags = 0
|
|
|
|
rv = cnghttp2.nghttp2_hd_inflate_hd(&self._inflater, &nv,
|
|
|
|
&inflate_flags,
|
|
|
|
buf, buflen, 1)
|
2014-01-16 18:27:42 +01:00
|
|
|
if rv < 0:
|
|
|
|
raise Exception(_strerror(rv))
|
|
|
|
buf += rv
|
|
|
|
buflen -= rv
|
2014-01-26 09:53:04 +01:00
|
|
|
if inflate_flags & cnghttp2.NGHTTP2_HD_INFLATE_EMIT:
|
|
|
|
# may throw
|
|
|
|
res.append((nv.name[:nv.namelen], nv.value[:nv.valuelen]))
|
|
|
|
if inflate_flags & cnghttp2.NGHTTP2_HD_INFLATE_FINAL:
|
|
|
|
break
|
|
|
|
|
|
|
|
cnghttp2.nghttp2_hd_inflate_end_headers(&self._inflater)
|
2013-12-14 15:50:30 +01:00
|
|
|
return res
|
|
|
|
|
2014-01-26 09:53:04 +01:00
|
|
|
def change_table_size(self, hd_table_bufsize_max):
|
|
|
|
'''Changes header table size to |hd_table_bufsize_max| byte.
|
|
|
|
|
|
|
|
An exception will be raised on error.
|
|
|
|
|
|
|
|
'''
|
|
|
|
_change_table_size(&self._inflater.ctx, hd_table_bufsize_max)
|
|
|
|
|
|
|
|
def get_hd_table(self):
|
|
|
|
'''Returns copy of current dynamic header table.'''
|
|
|
|
return _get_hd_table(&self._inflater.ctx)
|
|
|
|
|
2013-12-14 15:50:30 +01:00
|
|
|
cdef _strerror(int liberror_code):
|
|
|
|
return cnghttp2.nghttp2_strerror(liberror_code).decode('utf-8')
|
2013-12-17 16:20:14 +01:00
|
|
|
|
|
|
|
def print_hd_table(hdtable):
|
|
|
|
'''Convenient function to print |hdtable| to the standard output. This
|
|
|
|
function does not work if header name/value cannot be decoded using
|
|
|
|
UTF-8 encoding.
|
|
|
|
|
|
|
|
s=N means the entry occupies N bytes in header table. if r=y, then
|
|
|
|
the entry is in the reference set.
|
|
|
|
|
|
|
|
'''
|
|
|
|
idx = 0
|
|
|
|
for entry in hdtable:
|
|
|
|
idx += 1
|
2013-12-21 15:02:48 +01:00
|
|
|
print('[{}] (s={}) (r={}) {}: {}'\
|
|
|
|
.format(idx, entry.space(),
|
|
|
|
'y' if entry.ref else 'n',
|
|
|
|
'**DEALLOCATED**' if entry.name is None else entry.name.decode('utf-8'),
|
|
|
|
'**DEALLOCATED**' if entry.value is None else entry.value.decode('utf-8')))
|