diff options
| -rw-r--r-- | src/pulsecore/protocol-http.c | 497 | 
1 files changed, 394 insertions, 103 deletions
| diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index f3b93819..08a70e50 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -42,20 +42,39 @@  /* Don't allow more than this many concurrent connections */  #define MAX_CONNECTIONS 10 -#define internal_server_error(c) http_message((c), 500, "Internal Server Error", NULL) -  #define URL_ROOT "/"  #define URL_CSS "/style"  #define URL_STATUS "/status" +#define URL_LISTEN "/listen" +#define URL_LISTEN_PREFIX "/listen/" + +#define MIME_HTML "text/html; charset=utf-8" +#define MIME_TEXT "text/plain; charset=utf-8" +#define MIME_CSS "text/css" + +#define HTML_HEADER(t)                                                  \ +    "<?xml version=\"1.0\"?>\n"                                         \ +    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \ +    "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"                   \ +    "        <head>\n"                                                  \ +    "                <title>"t"</title>\n"                              \ +    "                <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \ +    "        </head>\n"                                                 \ +    "        <body>\n" + +#define HTML_FOOTER                                                     \ +    "        </body>\n"                                                 \ +    "</html>\n" +enum state { +    STATE_REQUEST_LINE, +    STATE_MIME_HEADER, +    STATE_DATA +};  struct connection {      pa_http_protocol *protocol;      pa_ioline *line; -    enum { -        REQUEST_LINE, -        MIME_HEADER, -        DATA -    } state; +    enum state state;      char *url;      pa_module *module;  }; @@ -67,45 +86,242 @@ struct pa_http_protocol {      pa_idxset *connections;  }; -static void http_response(struct connection *c, int code, const char *msg, const char *mime) { -    char s[256]; + +static pa_bool_t is_mime_sample_spec(const pa_sample_spec *ss, const pa_channel_map *cm) { + +    pa_assert(pa_channel_map_compatible(cm, ss)); + +    switch (ss->format) { +        case PA_SAMPLE_S16BE: +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_U8: + +            if (ss->rate != 8000 && +                ss->rate != 11025 && +                ss->rate != 16000 && +                ss->rate != 22050 && +                ss->rate != 24000 && +                ss->rate != 32000 && +                ss->rate != 44100 && +                ss->rate != 48000) +                return FALSE; + +            if (ss->channels != 1 && +                ss->channels != 2) +                return FALSE; + +            if ((cm->channels == 1 && cm->map[0] != PA_CHANNEL_POSITION_MONO) || +                (cm->channels == 2 && (cm->map[0] != PA_CHANNEL_POSITION_LEFT || cm->map[1] != PA_CHANNEL_POSITION_RIGHT))) +                return FALSE; + +            return TRUE; + +        case PA_SAMPLE_ULAW: + +            if (ss->rate != 8000) +                return FALSE; + +            if (ss->channels != 1) +                return FALSE; + +            if (cm->map[0] != PA_CHANNEL_POSITION_MONO) +                return FALSE; + +            return TRUE; + +        default: +            return FALSE; +    } +} + +static void mimefy_sample_spec(pa_sample_spec *ss, pa_channel_map *cm) { + +    pa_assert(pa_channel_map_compatible(cm, ss)); + +    /* Turns the sample type passed in into the next 'better' one that +     * can be encoded for HTTP. If there is no 'better' one we pick +     * the 'best' one that is 'worse'. */ + +    if (ss->channels > 2) +        ss->channels = 2; + +    if (ss->rate > 44100) +        ss->rate = 48000; +    else if (ss->rate > 32000) +        ss->rate = 44100; +    else if (ss->rate > 24000) +        ss->rate = 32000; +    else if (ss->rate > 22050) +        ss->rate = 24000; +    else if (ss->rate > 16000) +        ss->rate = 22050; +    else if (ss->rate > 11025) +        ss->rate = 16000; +    else if (ss->rate > 8000) +        ss->rate = 11025; +    else +        ss->rate = 8000; + +    switch (ss->format) { +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_S24LE: +        case PA_SAMPLE_S24_32LE: +        case PA_SAMPLE_S24_32BE: +        case PA_SAMPLE_S32LE: +        case PA_SAMPLE_S32BE: +        case PA_SAMPLE_FLOAT32LE: +        case PA_SAMPLE_FLOAT32BE: +            ss->format = PA_SAMPLE_S24BE; +            break; + +        case PA_SAMPLE_S16BE: +        case PA_SAMPLE_S16LE: +            ss->format = PA_SAMPLE_S16BE; +            break; + +        case PA_SAMPLE_ULAW: +        case PA_SAMPLE_ALAW: + +            if (ss->rate == 8000 && ss->channels == 1) +                ss->format = PA_SAMPLE_ULAW; +            else +                ss->format = PA_SAMPLE_S16BE; +            break; + +        case PA_SAMPLE_U8: +            ss->format = PA_SAMPLE_U8; +            break; + +        case PA_SAMPLE_MAX: +        case PA_SAMPLE_INVALID: +            pa_assert_not_reached(); +    } + +    pa_channel_map_init_auto(cm, ss->channels, PA_CHANNEL_MAP_DEFAULT); + +    pa_assert(is_mime_sample_spec(ss, cm)); +} + +static char *sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) { +    pa_assert(pa_channel_map_compatible(cm, ss)); + +    if (!is_mime_sample_spec(ss, cm)) +        return NULL; + +    switch (ss->format) { + +        case PA_SAMPLE_S16BE: +        case PA_SAMPLE_S24BE: +        case PA_SAMPLE_U8: +            return pa_sprintf_malloc("audio/%s; rate=%u; channels=%u", +                                     ss->format == PA_SAMPLE_S16BE ? "L16" : +                                     (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"), +                                     ss->rate, ss->channels); + +        case PA_SAMPLE_ULAW: +            return pa_xstrdup("audio/basic"); + +        default: +            pa_assert_not_reached(); +    } + +    pa_assert(pa_sample_spec_valid(ss)); +} + +static char *mimefy_and_stringify_sample_spec(const pa_sample_spec *_ss, const pa_channel_map *_cm) { +    pa_sample_spec ss = *_ss; +    pa_channel_map cm = *_cm; + +    mimefy_sample_spec(&ss, &cm); + +    return sample_spec_to_mime_type(&ss, &cm); +} + +static char *escape_html(const char *t) { +    pa_strbuf *sb; +    const char *p, *e; + +    sb = pa_strbuf_new(); + +    for (e = p = t; *p; p++) { + +        if (*p == '>' || *p == '<' || *p == '&') { + +            if (p > e) { +                pa_strbuf_putsn(sb, e, p-e); +                e = p + 1; +            } + +            if (*p == '>') +                pa_strbuf_puts(sb, ">"); +            else if (*p == '<') +                pa_strbuf_puts(sb, "<"); +            else +                pa_strbuf_puts(sb, "&"); +        } +    } + +    if (p > e) +        pa_strbuf_putsn(sb, e, p-e); + +    return pa_strbuf_tostring_free(sb); +} + +static void http_response( +        struct connection *c, +        int code, +        const char *msg, +        const char *mime) { + +    char *s;      pa_assert(c);      pa_assert(msg);      pa_assert(mime); -    pa_snprintf(s, sizeof(s), -             "HTTP/1.0 %i %s\n" -             "Connection: close\n" -             "Content-Type: %s\n" -             "Cache-Control: no-cache\n" -             "Expires: 0\n" -             "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" -             "\n", code, msg, mime); - +    s = pa_sprintf_malloc( +            "HTTP/1.0 %i %s\n" +            "Connection: close\n" +            "Content-Type: %s\n" +            "Cache-Control: no-cache\n" +            "Expires: 0\n" +            "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" +            "\n", code, msg, mime);      pa_ioline_puts(c->line, s); +    pa_xfree(s);  } -static void http_message(struct connection *c, int code, const char *msg, const char *text) { -    char s[256]; +static void html_response( +        struct connection *c, +        int code, +        const char *msg, +        const char *text) { + +    char *s;      pa_assert(c); -    http_response(c, code, msg, "text/html"); +    http_response(c, code, msg, MIME_HTML);      if (!text)          text = msg; -    pa_snprintf(s, sizeof(s), -             "<?xml version=\"1.0\"?>\n" -             "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" -             "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>%s</title></head>\n" -             "<body>%s</body></html>\n", -             text, text); +    s = pa_sprintf_malloc( +            HTML_HEADER("%s") +            "%s" +            HTML_FOOTER, +            text, text);      pa_ioline_puts(c->line, s); +    pa_xfree(s); +      pa_ioline_defer_close(c->line);  } +static void internal_server_error(struct connection *c) { +    pa_assert(c); + +    html_response(c, 500, "Internal Server Error", NULL); +}  static void connection_unlink(struct connection *c) {      pa_assert(c); @@ -113,12 +329,153 @@ static void connection_unlink(struct connection *c) {      if (c->url)          pa_xfree(c->url); +    if (c->line) +        pa_ioline_unref(c->line); +      pa_idxset_remove_by_data(c->protocol->connections, c, NULL); -    pa_ioline_unref(c->line);      pa_xfree(c);  } +static void html_print_field(pa_ioline *line, const char *left, const char *right) { +    char *eleft, *eright; + +    eleft = escape_html(left); +    eright = escape_html(right); + +    pa_ioline_printf(line, +                     "<tr><td><b>%s</b></td>" +                     "<td>%s</td></tr>\n", eleft, eright); + +    pa_xfree(eleft); +    pa_xfree(eright); +} + +static void handle_url(struct connection *c) { +    pa_assert(c); + +    pa_log_debug("Request for %s", c->url); + +    if (pa_streq(c->url, URL_ROOT)) { +        char *t; + +        http_response(c, 200, "OK", MIME_HTML); + +        pa_ioline_puts(c->line, +                       HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION) +                       "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" +                       "<table>\n"); + +        t = pa_get_user_name_malloc(); +        html_print_field(c->line, "User Name:", t); +        pa_xfree(t); + +        t = pa_get_host_name_malloc(); +        html_print_field(c->line, "Host name:", t); +        pa_xfree(t); + +        t = pa_machine_id(); +        html_print_field(c->line, "Machine ID:", t); +        pa_xfree(t); + +        t = pa_uname_string(); +        html_print_field(c->line, "System:", t); +        pa_xfree(t); + +        t = pa_sprintf_malloc("%lu", (unsigned long) getpid()); +        html_print_field(c->line, "Process ID:", t); +        pa_xfree(t); + +        pa_ioline_puts(c->line, +                       "</table>\n" +                       "<p><a href=\"/status\">Show an extensive server status report</a></p>\n" +                       "<p><a href=\"/listen\">Monitor sinks and sources</a></p>\n" +                       HTML_FOOTER); + +        pa_ioline_defer_close(c->line); + +    } else if (pa_streq(c->url, URL_CSS)) { +        http_response(c, 200, "OK", MIME_CSS); + +        pa_ioline_puts(c->line, +                       "body { color: black; background-color: white; }\n" +                       "a:link, a:visited { color: #900000; }\n" +                       "div.news-date { font-size: 80%; font-style: italic; }\n" +                       "pre { background-color: #f0f0f0; padding: 0.4cm; }\n" +                       ".grey { color: #8f8f8f; font-size: 80%; }" +                       "table {  margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" +                       "td { padding-left:10px; padding-right:10px; }\n"); + +        pa_ioline_defer_close(c->line); + +    } else if (pa_streq(c->url, URL_STATUS)) { +        char *r; + +        http_response(c, 200, "OK", MIME_TEXT); +        r = pa_full_status_string(c->protocol->core); +        pa_ioline_puts(c->line, r); +        pa_xfree(r); + +        pa_ioline_defer_close(c->line); + +    } else if (pa_streq(c->url, URL_LISTEN)) { +        pa_source *source; +        pa_sink *sink; +        uint32_t idx; + +        http_response(c, 200, "OK", MIME_HTML); + +        pa_ioline_puts(c->line, +                       HTML_HEADER("Listen") +                       "<h2>Sinks</h2>\n" +                       "<p>\n"); + +        PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) { +            char *t, *m; + +            t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); +            m = mimefy_and_stringify_sample_spec(&sink->sample_spec, &sink->channel_map); + +            pa_ioline_printf(c->line, +                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n", +                             sink->monitor_source->name, m, t); + +            pa_xfree(t); +            pa_xfree(m); +        } + +        pa_ioline_puts(c->line, +                       "</p>\n" +                       "<h2>Sources</h2>\n" +                       "<p>\n"); + +        PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) { +            char *t, *m; + +            if (source->monitor_of) +                continue; + +            t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); +            m = mimefy_and_stringify_sample_spec(&source->sample_spec, &source->channel_map); + +            pa_ioline_printf(c->line, +                             "<a href=\"/listen/%s\" title=\"%s\">%s</a><br/>\n", +                             source->name, m, t); + +            pa_xfree(m); +            pa_xfree(t); + +        } + +        pa_ioline_puts(c->line, +                       "</p>\n" +                       HTML_FOOTER); + +        pa_ioline_defer_close(c->line); +    } else +        html_response(c, 404, "Not Found", NULL); +} +  static void line_callback(pa_ioline *line, const char *s, void *userdata) {      struct connection *c = userdata;      pa_assert(line); @@ -131,93 +488,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {      }      switch (c->state) { -        case REQUEST_LINE: { -            if (memcmp(s, "GET ", 4)) +        case STATE_REQUEST_LINE: { +            if (!pa_startswith(s, "GET "))                  goto fail;              s +=4;              c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?")); -            c->state = MIME_HEADER; +            c->state = STATE_MIME_HEADER;              break; -          } -        case MIME_HEADER: { +        case STATE_MIME_HEADER: {              /* Ignore MIME headers */              if (strcspn(s, " \r\n") != 0)                  break;              /* We're done */ -            c->state = DATA; - -            pa_log_info("request for %s", c->url); - -            if (!strcmp(c->url, URL_ROOT)) { -                char txt[256]; -                pa_sink *def_sink; -                pa_source *def_source; -                http_response(c, 200, "OK", "text/html"); - -                pa_ioline_puts(c->line, -                               "<?xml version=\"1.0\"?>\n" -                               "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" -                               "<html xmlns=\"http://www.w3.org/1999/xhtml\"><title>"PACKAGE_NAME" "PACKAGE_VERSION"</title>\n" -                               "<link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/></head><body>\n"); - -                pa_ioline_puts(c->line, -                               "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" -                               "<table>"); - -#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "<tr><td><b>%s</b></td><td>%s</td></tr>\n",(a),(b)) - -                PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt))); -                PRINTF_FIELD("Host name:", pa_get_host_name(txt, sizeof(txt))); -                PRINTF_FIELD("Default Sample Specification:", pa_sample_spec_snprint(txt, sizeof(txt), &c->protocol->core->default_sample_spec)); - -                def_sink = pa_namereg_get_default_sink(c->protocol->core); -                def_source = pa_namereg_get_default_source(c->protocol->core); - -                PRINTF_FIELD("Default Sink:", def_sink ? def_sink->name : "n/a"); -                PRINTF_FIELD("Default Source:", def_source ? def_source->name : "n/a"); - -                pa_ioline_puts(c->line, "</table>"); - -                pa_ioline_puts(c->line, "<p><a href=\"/status\">Click here</a> for an extensive server status report.</p>"); - -                pa_ioline_puts(c->line, "</body></html>\n"); - -                pa_ioline_defer_close(c->line); -            } else if (!strcmp(c->url, URL_CSS)) { -                http_response(c, 200, "OK", "text/css"); - -                pa_ioline_puts(c->line, -                               "body { color: black; background-color: white; margin: 0.5cm; }\n" -                               "a:link, a:visited { color: #900000; }\n" -                               "p { margin-left: 0.5cm; margin-right: 0.5cm; }\n" -                               "h1 { color: #00009F; }\n" -                               "h2 { color: #00009F; }\n" -                               "ul { margin-left: .5cm; }\n" -                               "ol { margin-left: .5cm; }\n" -                               "pre { margin-left: .5cm; background-color: #f0f0f0; padding: 0.4cm;}\n" -                               ".grey { color: #afafaf; }\n" -                               "table {  margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" -                               "td { padding-left:10px; padding-right:10px;  }\n"); - -                pa_ioline_defer_close(c->line); -            } else if (!strcmp(c->url, URL_STATUS)) { -                char *r; - -                http_response(c, 200, "OK", "text/plain"); -                r = pa_full_status_string(c->protocol->core); -                pa_ioline_puts(c->line, r); -                pa_xfree(r); - -                pa_ioline_defer_close(c->line); -            } else -                http_message(c, 404, "Not Found", NULL); +            c->state = STATE_DATA; +            handle_url(c);              break;          } @@ -247,7 +538,7 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *      c = pa_xnew(struct connection, 1);      c->protocol = p;      c->line = pa_ioline_new(io); -    c->state = REQUEST_LINE; +    c->state = STATE_REQUEST_LINE;      c->url = NULL;      c->module = m; @@ -258,12 +549,12 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *  void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {      struct connection *c; -    void *state = NULL; +    uint32_t idx;      pa_assert(p);      pa_assert(m); -    while ((c = pa_idxset_iterate(p->connections, &state, NULL))) +    PA_IDXSET_FOREACH(c, p->connections, idx)          if (c->module == m)              connection_unlink(c);  } | 
