about summary refs log tree commit diff stats
path: root/src/tools/http_upload.c
blob: fcdd582a847f12bf5ba5a685859a8d0d1300365b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
language: c
install:
    - lsb_release -a
    - uname -a
    - sudo apt-get update
    - sudo apt-get -y install libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr2-dev libgpgme11-dev uuid-dev expect-dev tcl-dev
    - git clone git://github.com/boothj5/libmesode.git
    - cd libmesode
    - mkdir m4
    - ./bootstrap.sh
    - ./configure --prefix=/usr
    - make
    - sudo make install
    - cd ..
    - rm -rf libmesode
    - wget https://cmocka.org/files/1.0/cmocka-1.0.0.tar.xz
    - tar -xvf cmocka-1.0.0.tar.xz
    - cd cmocka-1.0.0
    - mkdir build
    - cd build
    - cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug ..
    - make
    - sudo make install
    - cd ../..
    - rm -rf cmocka-1.0.0
    - sudo apt-get install libmicrohttpd-dev
    - git clone git://github.com/boothj5/stabber.git
    - cd stabber
    - ./bootstrap.sh
    - ./configure --prefix=/usr
    - make
    - sudo make install
    - cd ..
    - rm -rf stabber
    - ./bootstrap.sh
script: ./configure && make && make check
' href='#n365'>365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
/*
 * http_upload.c
 * vim: expandtab:ts=4:sts=4:sw=4
 *
 * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
 *
 * This file is part of Profanity.
 *
 * Profanity 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.
 *
 * Profanity 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 Profanity.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link the code of portions of this program with the OpenSSL library under
 * certain conditions as described in each individual source file, and
 * distribute linked combinations including the two.
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception, you
 * may extend this exception to your version of the file(s), but you are not
 * obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version. If you delete this exception statement from all
 * source files in the program, then also delete it here.
 *
 */

#define _GNU_SOURCE 1

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <curl/curl.h>
#include <gio/gio.h>
#include <pthread.h>
#include <assert.h>

#include "profanity.h"
#include "event/client_events.h"
#include "tools/http_upload.h"
#include "config/preferences.h"
#include "ui/ui.h"
#include "ui/window.h"
#include "common.h"

#define FALLBACK_MIMETYPE           "application/octet-stream"
#define FALLBACK_CONTENTTYPE_HEADER "Content-Type: application/octet-stream"
#define FALLBACK_MSG                ""
#define FILE_HEADER_BYTES           512

struct curl_data_t
{
    char* buffer;
    size_t size;
};

GSList* upload_processes = NULL;

static int
_xferinfo(void* userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
    HTTPUpload* upload = (HTTPUpload*)userdata;

    pthread_mutex_lock(&lock);

    if (upload->cancel) {
        pthread_mutex_unlock(&lock);
        return 1;
    }

    if (upload->bytes_sent == ulnow) {
        pthread_mutex_unlock(&lock);
        return 0;
    } else {
        upload->bytes_sent = ulnow;
    }

    unsigned int ulperc = 0;
    if (ultotal != 0) {
        ulperc = (100 * ulnow) / ultotal;
    }

    char* msg;
    if (asprintf(&msg, "Uploading '%s': %d%%", upload->filename, ulperc) == -1) {
        msg = strdup(FALLBACK_MSG);
    }
    win_update_entry_message(upload->window, upload->put_url, msg);
    free(msg);

    pthread_mutex_unlock(&lock);

    return 0;
}

#if LIBCURL_VERSION_NUM < 0x072000
static int
_older_progress(void* p, double dltotal, double dlnow, double ultotal, double ulnow)
{
    return _xferinfo(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow);
}
#endif

static size_t
_data_callback(void* ptr, size_t size, size_t nmemb, void* data)
{
    size_t realsize = size * nmemb;
    struct curl_data_t* mem = (struct curl_data_t*)data;
    mem->buffer = realloc(mem->buffer, mem->size + realsize + 1);

    if (mem->buffer) {
        memcpy(&(mem->buffer[mem->size]), ptr, realsize);
        mem->size += realsize;
        mem->buffer[mem->size] = 0;
    }

    return realsize;
}

int
format_alt_url(char* original_url, char* new_scheme, char* new_fragment, char** new_url)
{
    int ret = 0;
    CURLU* h = curl_url();

    if ((ret = curl_url_set(h, CURLUPART_URL, original_url, 0)) != 0) {
        goto out;
    }

    if (new_scheme != NULL) {
        if ((ret = curl_url_set(h, CURLUPART_SCHEME, new_scheme, CURLU_NON_SUPPORT_SCHEME)) != 0) {
            goto out;
        }
    }

    if (new_fragment != NULL) {
        if ((ret = curl_url_set(h, CURLUPART_FRAGMENT, new_fragment, 0)) != 0) {
            goto out;
        }
    }

    ret = curl_url_get(h, CURLUPART_URL, new_url, 0);

out:
    curl_url_cleanup(h);
    return ret;
}

void*
http_file_put(void* userdata)
{
    HTTPUpload* upload = (HTTPUpload*)userdata;

    FILE* fh = NULL;

    char* err = NULL;
    char* content_type_header;

    CURL* curl;
    CURLcode res;

    upload->cancel = 0;
    upload->bytes_sent = 0;

    pthread_mutex_lock(&lock);
    char* msg;
    if (asprintf(&msg, "Uploading '%s': 0%%", upload->filename) == -1) {
        msg = strdup(FALLBACK_MSG);
    }
    win_print_http_transfer(upload->window, msg, upload->put_url);
    free(msg);

    char* cert_path = prefs_get_string(PREF_TLS_CERTPATH);
    pthread_mutex_unlock(&lock);

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, upload->put_url);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");

    struct curl_slist* headers = NULL;
    if (asprintf(&content_type_header, "Content-Type: %s", upload->mime_type) == -1) {
        content_type_header = strdup(FALLBACK_CONTENTTYPE_HEADER);
    }
    headers = curl_slist_append(headers, content_type_header);
    headers = curl_slist_append(headers, "Expect:");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

#if LIBCURL_VERSION_NUM >= 0x072000
    curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, _xferinfo);
    curl_easy_setopt(curl, CURLOPT_XFERINFODATA, upload);
#else
    curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, _older_progress);
    curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, upload);
#endif
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);

    struct curl_data_t output;
    output.buffer = NULL;
    output.size = 0;
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _data_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&output);

    curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity");

    fh = upload->filehandle;

    if (cert_path) {
        curl_easy_setopt(curl, CURLOPT_CAPATH, cert_path);
    }

    curl_easy_setopt(curl, CURLOPT_READDATA, fh);
    curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)(upload->filesize));
    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);

    if ((res = curl_easy_perform(curl)) != CURLE_OK) {
        err = strdup(curl_easy_strerror(res));
    } else {
        long http_code = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

        if (output.buffer) {
            output.buffer[output.size++] = '\0';
        }

        // XEP-0363 specifies 201 but prosody returns 200
        if (http_code != 200 && http_code != 201) {
            if (asprintf(&err, "Server returned %lu", http_code) == -1) {
                err = NULL;
            }
        }

#if 0
        printf("HTTP Status: %lu\n", http_code);
        printf("%s\n", output.buffer);
        printf("%lu bytes retrieved\n", (long)output.size);
#endif
    }

    curl_easy_cleanup(curl);
    curl_global_cleanup();
    curl_slist_free_all(headers);
    if (fh) {
        fclose(fh);
    }
    free(content_type_header);
    free(output.buffer);

    pthread_mutex_lock(&lock);
    g_free(cert_path);

    if (err) {
        char* msg;
        if (upload->cancel) {
            if (asprintf(&msg, "Uploading '%s' failed: Upload was canceled", upload->filename) == -1) {
                msg = strdup(FALLBACK_MSG);
            }
        } else {
            if (asprintf(&msg, "Uploading '%s' failed: %s", upload->filename, err) == -1) {
                msg = strdup(FALLBACK_MSG);
            }
            win_update_entry_message(upload->window, upload->put_url, msg);
        }
        cons_show_error(msg);
        free(msg);
        free(err);
    } else {
        if (!upload->cancel) {
            if (asprintf(&msg, "Uploading '%s': 100%%", upload->filename) == -1) {
                msg = strdup(FALLBACK_MSG);
            }
            win_update_entry_message(upload->window, upload->put_url, msg);
            win_mark_received(upload->window, upload->put_url);
            free(msg);

            char* url = NULL;
            if (format_alt_url(upload->get_url, upload->alt_scheme, upload->alt_fragment, &url) != 0) {
                char* msg;
                if (asprintf(&msg, "Uploading '%s' failed: Bad URL ('%s')", upload->filename, upload->get_url) == -1) {
                    msg = strdup(FALLBACK_MSG);
                }
                cons_show_error(msg);
                free(msg);
            } else {
                switch (upload->window->type) {
                case WIN_CHAT:
                {
                    ProfChatWin* chatwin = (ProfChatWin*)(upload->window);
                    assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
                    cl_ev_send_msg(chatwin, url, url);
                    break;
                }
                case WIN_PRIVATE:
                {
                    ProfPrivateWin* privatewin = (ProfPrivateWin*)(upload->window);
                    assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
                    cl_ev_send_priv_msg(privatewin, url, url);
                    break;
                }
                case WIN_MUC:
                {
                    ProfMucWin* mucwin = (ProfMucWin*)(upload->window);
                    assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
                    cl_ev_send_muc_msg(mucwin, url, url);
                    break;
                }
                default:
                    break;
                }

                curl_free(url);
            }
        }
    }

    upload_processes = g_slist_remove(upload_processes, upload);
    pthread_mutex_unlock(&lock);

    free(upload->filename);
    free(upload->mime_type);
    free(upload->get_url);
    free(upload->put_url);
    free(upload->alt_scheme);
    free(upload->alt_fragment);
    free(upload);

    return NULL;
}

char*
file_mime_type(const char* const filename)
{
    char* out_mime_type;
    char file_header[FILE_HEADER_BYTES];
    FILE* fh;
    if (!(fh = fopen(filename, "rb"))) {
        return strdup(FALLBACK_MIMETYPE);
    }
    size_t file_header_size = fread(file_header, 1, FILE_HEADER_BYTES, fh);
    fclose(fh);

    char* content_type = g_content_type_guess(filename, (unsigned char*)file_header, file_header_size, NULL);
    if (content_type != NULL) {
        char* mime_type = g_content_type_get_mime_type(content_type);
        out_mime_type = strdup(mime_type);
        g_free(mime_type);
    } else {
        return strdup(FALLBACK_MIMETYPE);
    }
    g_free(content_type);
    return out_mime_type;
}

off_t
file_size(int filedes)
{
    struct stat st;
    fstat(filedes, &st);
    return st.st_size;
}

void
http_upload_cancel_processes(ProfWin* window)
{
    GSList* upload_process = upload_processes;
    while (upload_process) {
        HTTPUpload* upload = upload_process->data;
        if (upload->window == window) {
            upload->cancel = 1;
            break;
        }
        upload_process = g_slist_next(upload_process);
    }
}

void
http_upload_add_upload(HTTPUpload* upload)
{
    upload_processes = g_slist_append(upload_processes, upload);
}