cors.c 6.86 KB
Newer Older
1 2 3 4 5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
Julien CROUZET's avatar
Julien CROUZET committed
6
 * Copyright 2017, Julien CROUZET <contact@juliencrouzet.fr>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 */

/**
 * Cors handling functions
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <ctype.h>

#include "cfgfile.h"
#include "client.h"
#include "logging.h"

#define CATMODULE "CORS"

static const char* cors_header_names[7] = {
Julien CROUZET's avatar
Julien CROUZET committed
27 28 29 30 31 32 33
    "access-control-allow-origin",
    "access-control-expose-headers",
    "access-control-max-age",
    "access-control-allow-credentials",
    "access-control-allow-methods",
    "access-control-allow-headers",
    NULL
34 35 36 37 38 39
};

static const char *icy_headers = "icy-br, icy-caps, icy-description, icy-genre, icy-metaint, icy-metadata-interval, icy-name, icy-pub, icy-public, icy-url";

static ice_config_cors_path_t* _find_matching_path(ice_config_cors_path_t  *paths, char *path)
{
Julien CROUZET's avatar
Julien CROUZET committed
40
    ice_config_cors_path_t *matching_path = paths;
41

Julien CROUZET's avatar
Julien CROUZET committed
42 43 44 45 46
    while(matching_path) {
        if (strncmp(matching_path->base, path, strlen(matching_path->base)) == 0) {
            return matching_path;
        }
        matching_path = matching_path->next;
47
    }
Julien CROUZET's avatar
Julien CROUZET committed
48
    return NULL;
49 50 51
}

static int _cors_valid_origin(ice_config_cors_path_t  *path, const char *origin) {
Julien CROUZET's avatar
Julien CROUZET committed
52 53 54 55 56 57 58 59 60 61 62
    if (path->forbidden) {
        for (int i = 0; path->forbidden[i]; i++) {
            if (strstr(origin, path->forbidden[i]) == origin) {
                ICECAST_LOG_DEBUG(
                    "Declared origin \"%s\" matches forbidden origin \"%s\", not sending CORS",
                    origin,
                    path->forbidden[i]
                );
                return 0;
            }
        }
63
    }
Julien CROUZET's avatar
Julien CROUZET committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
    if (path->allowed) {
        for (int i = 0; path->allowed[i]; i++) {
            if ((strlen(path->allowed[i]) == 1) && path->allowed[i][0] == '*') {
                ICECAST_LOG_DEBUG(
                    "All (\"*\") allowed origin for \"%s\", sending CORS",
                    origin
                );
                return 1;
            }
            if (strstr(origin, path->allowed[i]) == origin) {
                ICECAST_LOG_DEBUG(
                    "Declared origin \"%s\" matches allowed origin \"%s\", sending CORS",
                    origin,
                    path->allowed[i]
                );
                return 1;
            }
        }
82
    }
Julien CROUZET's avatar
Julien CROUZET committed
83 84 85 86 87
    ICECAST_LOG_DEBUG(
        "Declared origin \"%s\" does not matches any declared origin, not sending CORS",
        origin
    );
    return 0;
88 89 90 91 92 93 94
}

static void _add_header(char       **out,
                        size_t      *len,
                        const char  *header_name,
                        const char  *header_value)
{
Julien CROUZET's avatar
Julien CROUZET committed
95 96
    int   new_length;
    char *new_out;
97

Julien CROUZET's avatar
Julien CROUZET committed
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    if (!header_name || !header_value || !strlen(header_name)) {
        return;
    }
    new_length = strlen(header_name) + strlen(header_value) + 4;
    new_out = calloc(*len + new_length, sizeof(char));
    if (!new_out) {
        ICECAST_LOG_ERROR("Out of memory while setting CORS header.");
        return;
    }
    snprintf(new_out,
             *len + new_length, 
             "%s%s: %s\r\n",
             *out,
             header_name,
             header_value);
    free(*out);
    *len += new_length;
    *out = new_out;
116 117 118
}


Julien CROUZET's avatar
Julien CROUZET committed
119 120 121 122 123
/**
 * Removes an header by its name in current headers list.
 * Header removal is needed to remove any manually added headers
 * added while a forbidden rule is active.
 */
124 125 126 127
static void _remove_header(char                   **out,
                           size_t                  *len,
                           const char              *header_name)
{
Julien CROUZET's avatar
Julien CROUZET committed
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    int header_start[100];
    int header_end[100];
    int current_position = 0;
    int found_count = 0;
    char *new_out;

    if (!*len)
        return;
    while((current_position < (*len -1)) && found_count < 100) {
        char *substr = strcasestr((*out + current_position), header_name);
        char *substr_end;
        if (!substr) {
            break;
        }
        substr_end = strstr(substr, "\r\n");
        if (!substr_end) {
            return;
        }
        header_start[found_count] = substr - *out;
        header_end[found_count] = substr_end - *out + 2;
        current_position = header_end[found_count];
        found_count++;
150
    }
Julien CROUZET's avatar
Julien CROUZET committed
151 152
    if (!found_count) {
        return;
153
    }
Julien CROUZET's avatar
Julien CROUZET committed
154 155 156 157
    current_position = 0;
    new_out = calloc(*len + 1, sizeof(char));
    if (!new_out) {
        return;
158
    }
Julien CROUZET's avatar
Julien CROUZET committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
    free(*out);
    for (int i = 0; i < found_count; i++) {
        while (current_position < header_start[i]) {
            new_out[current_position] = *out[current_position];
        }
        current_position = header_end[i];
    }
    while (current_position < *len) {
        new_out[current_position] = 0;
        current_position++;
    }
    *out = new_out;
    for (int i = 0; i < found_count; i++) {
        *len -= header_end[i] - header_start[i];
    }
    return;
175 176 177 178 179 180 181 182
}


static void _add_cors(char                   **out,
                      size_t                  *len,
                      ice_config_cors_path_t  *path,
                      char                    *origin)
{
Julien CROUZET's avatar
Julien CROUZET committed
183 184 185 186 187 188 189 190 191 192 193
    _add_header(out, len, "Access-Control-Allow-Origin", origin);
    if (path->exposed_headers) {
        _add_header(out, len, "Access-Control-Expose-Headers", path->exposed_headers);
    } else {
        _add_header(out, len, "Access-Control-Expose-Headers", icy_headers);
    }
    _add_header(out, len, "Access-Control-Max-Age", "3600");
    _add_header(out, len, "Access-Control-Allow-Credentials", "true");
    _add_header(out, len, "Access-Control-Allow-Methods", "GET");
    _add_header(out, len, "Access-Control-Allow-Headers", "icy-metadata");
    return;
194 195 196
}

static void _remove_cors(char **out, size_t *len) {
Julien CROUZET's avatar
Julien CROUZET committed
197 198 199 200
    for(int i = 0; cors_header_names[i]; i++) {
        _remove_header(out, len, cors_header_names[i]);
    }
    return;
201 202 203 204 205 206 207
}

void cors_set_headers(char                   **out,
                      size_t                  *len,
                      ice_config_cors_path_t  *paths,
                      struct _client_tag      *client)
{
Julien CROUZET's avatar
Julien CROUZET committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    char *origin = NULL;
    char *path = (char *)client->parser->uri;
    ice_config_cors_path_t *matching_path;

    if (!paths)
        return;
    if (!(origin = (char *)httpp_getvar(client->parser, "origin")))
        return;
    if (!path)
        return;

    matching_path = _find_matching_path(paths, path);
    if (!matching_path) {
        ICECAST_LOG_DEBUG(
            "Requested path \"%s\" does not matches any declared CORS configured path",
            path
        );
        return;
    }
227 228

    ICECAST_LOG_DEBUG(
Julien CROUZET's avatar
Julien CROUZET committed
229 230 231
        "Requested path \"%s\" matches the \"%s\" declared CORS path",
        path,
        matching_path->base
232
    );
Julien CROUZET's avatar
Julien CROUZET committed
233 234 235 236 237 238 239 240 241

    _remove_cors(out, len);

    if (
        !matching_path->no_cors &&
        _cors_valid_origin(matching_path, origin)
    ) {
        _add_cors(out, len, matching_path, origin);
    }
242 243
    return;
}