8000 Adds support for the Content-Encoding header by aminroosta · Pull Request #256 · nicolasff/webdis · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Adds support for the Content-Encoding header #256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 54 additions & 13 deletions src/cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,19 +311,22 @@
const char *uri, size_t uri_len, formatting_fun *f_format) {

const char *ext;
int ext_len = -1;
const char *suffix;
int ext_len = 0;
int suffix_len = 0;
int parsed_uri_len = uri_len;
unsigned int i;
int found = 0; /* did we match it to a predefined format? */
int found = 0; /* did we match the extension and/or the suffix to a predefined format? */

/* those are the available reply formats */
struct reply_format {
/* those are the available content-type formats */
struct content_type_format {
const char *s;
size_t sz;
formatting_fun f;
const char *ct;
};
struct reply_format funs[] = {
{.s = "json", .sz = 4, .f = json_reply, .ct = "application/json"},
struct content_type_format funs[] = {
{.s = "json", .sz = 4, . f = json_reply, .ct = "application/json"},
{.s = "raw", .sz = 3, .f = raw_reply, .ct = "binary/octet-stream"},

#ifdef MSGPACK
Expand All @@ -341,26 +344,55 @@
{.s = "jpeg", .sz = 4, .f = custom_type_reply, .ct = "image/jpeg"},

{.s = "js", .sz = 2, .f = json_reply, .ct = "application/javascript"},
{.s = "css", .sz = 3, .f = custom_type_reply, .ct = "text/css"},
{.s = "css", .sz = 3, .f = custom_type_reply, .ct = "text/css"}
};

/* those are the available content-encoding formats */
struct content_encoding_format {
const char *s;
size_t sz;
formatting_fun f;
};
struct content_encoding_format sfxs[] = {
{.s = "gzip", .sz = 4, . f = custom_type_reply},
{.s = "br", .sz = 2, . f = custom_type_reply},
{.s = "zstd", .sz = 4, . f = custom_type_reply}
};

/* default */
*f_format = json_reply;

/* find extension */
/* find extension and/or suffix */
for(ext = uri + uri_len - 1; ext != uri && *ext != '/'; --ext) {
if(*ext == '.') {
ext++;
ext_len = uri + uri_len - ext;
suffix = ext + 1;
suffix_len = uri + uri_len - suffix;

for(ext = ext - 1; ext != uri && *ext != '/'; --ext) {

Check notice

Code scanning / CodeQL

For loop variable changed in body Note

Loop counters should not be modified in the body of the
loop
.

Check notice

Code scanning / CodeQL

For loop variable changed in body Note

Loop counters should not be modified in the body of the
loop
.
if(*ext == '.') {
ext++;

Check notice

Code scanning / CodeQL

For loop variable changed in body Note

Loop counters should not be modified in the body of the
loop
.
Loop counters should not be modified in the body of the
loop
.
ext_len = suffix - ext - 1;
break;
}

}
break;
}
}
if(!ext_len) return uri_len; /* nothing found */
if(!suffix_len) return uri_len; /* nothing found */

if(ext_len) { /* both extension and suffix are found, as in 'key.ext.suffix' */
parsed_uri_len = uri_len - ext_len - 1 - suffix_len - 1;
} else {
ext = suffix;
ext_len = suffix_len;

parsed_uri_len = uri_len - ext_len - 1;
}

/* find function for the given extension */
for(i = 0; i < sizeof(funs)/sizeof(funs[0]); ++i) {
if(ext_len == (int)funs[i].sz && strncmp(ext, funs[i].s, ext_len) == 0) {

if(cmd->mime_free) free(cmd->mime);
cmd->mime = (char*)funs[i].ct;
cmd->mime_free = 0;
Expand All @@ -376,8 +408,17 @@
/* /!\ we don't copy cmd->mime, this is done soon after in cmd_setup */
}

/* override function if suffix matches a known content-encoding */
for(i = 0; i < sizeof(sfxs)/sizeof(sfxs[0]); ++i) {
if(suffix_len == (int)sfxs[i].sz && strncmp(suffix, sfxs[i].s, suffix_len) == 0) {
cmd->content_encoding = (char *)sfxs[i].s;
*f_format = sfxs[i].f;
found = 1;
}
}

if(found) {
return uri_len - ext_len - 1;
return parsed_uri_len;
} else {
/* no matching format, use default output with the full argument, extension included. */
return uri_len;
Expand Down
2 changes: 2 additions & 0 deletions src/cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ struct cmd {
char *mime; /* forced output content-type */
int mime_free; /* need to free mime buffer */

char *content_encoding; /* forced output content-encoding */

char *filename; /* content-disposition attachment */

char *if_none_match; /* used with ETags */
Expand Down
4 changes: 4 additions & 0 deletions src/formats/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ format_send_reply(struct cmd *cmd, const char *p, size_t sz, const char *content

int free_cmd = 1;
const char *ct = cmd->mime?cmd->mime:content_type;
const char *ce = cmd->content_encoding;
struct http_response *resp;

if(cmd->is_websocket) {
Expand Down Expand Up @@ -113,6 +114,9 @@ format_send_reply(struct cmd *cmd, const char *p, size_t sz, const char *content
}
http_response_set_header(resp, "Content-Type", ct, HEADER_COPY_VALUE);
http_response_set_header(resp, "ETag", etag, HEADER_COPY_VALUE);
if(ce) {
http_response_set_header(resp, "Content-Encoding", ce, HEADER_COPY_VALUE);
}
http_response_set_body(resp, p, sz);
}
resp->http_version = cmd->http_version;
Expand Down
30 changes: 29 additions & 1 deletion tests/basic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/python3
import urllib.request, urllib.error, urllib.parse, unittest, json, hashlib, threading, uuid, time
import urllib.request, urllib.error, urllib.parse, unittest, json, hashlib, threading, uuid, time, gzip
from functools import wraps
try:
import msgpack
Expand All @@ -19,6 +19,10 @@ def query(self, url, data = None, headers={}):
r = urllib.request.Request(self.wrap(url), data, headers)
return urllib.request.urlopen(r)

def put(self, url, data):
r = urllib.request.Request(self.wrap(url), data=data, method='PUT')
return urllib.request.urlopen(r)

class TestBasics(TestWebdis):

def test_crossdomain(self):
Expand Down Expand Up @@ -72,6 +76,30 @@ def test_list(self):
self.assertTrue(f.getheader('ETag') == '"622e51f547a480bef7cf5452fb7782db"')
self.assertTrue(f.read() == b'{"LRANGE":["abc","def"]}')

def test_encoding_with_extension_and_suffix(self):
"success type (+OK)"
self.query('DEL/world')
# NOTE: the gzip implementation in python generates OS dependent headers.
# > gzip.compress(('{"user_id": 1234}').encode('utf-8'), mtime=0)
gzipped_data = b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xabV*-N-\x8a\xcfLQ\xb2R0426\xa9\x05\x00\x07\xb91\xf2\x11\x00\x00\x00'
self.put('SET/world', gzipped_data)
f = self.query('GET/world.json.gzip')
self.assertTrue(f.getheader('Content-Type') == 'application/json')
self.assertTrue(f.getheader('Content-Encoding') == 'gzip')
self.assertTrue(f.getheader('ETag') == '"8c50e25769b3ee8892d466d536a6ce2f"')
self.assertTrue(gzip.decompress(f.read()) == b'{"user_id": 1234}')

def test_encoding_with_suffix(self):
"success type (+OK)"
self.query('DEL/world')
gzipped_data = b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xabV*-N-\x8a\xcfLQ\xb2R0426\xa9\x05\x00\x07\xb91\xf2\x11\x00\x00\x00'
self.put('SET/world', gzipped_data)
f = self.query('GET/world.gzip?type=text/plain')
self.assertTrue(f.getheader('Content-Type') == 'text/plain')
self.assertTrue(f.getheader('Content-Encoding') == 'gzip')
self.assertTrue(f.getheader('ETag') == '"8c50e25769b3ee8892d466d536a6ce2f"')
self.assertTrue(gzip.decompress(f.read()) == b'{"user_id": 1234}')

def test_error(self):
"error return type"
f = self.query('UNKNOWN/COMMAND')
Expand Down
Loading
0