Protocol-HTTP

 view release on metacpan or  search on metacpan

clib/src/panda/protocol/http/Request.cc  view on Meta::CPAN

string Request::_generate_boundary() noexcept {
    const constexpr size_t SZ = (string::MAX_SSO_CHARS / sizeof (int)) + (string::MAX_SSO_CHARS % sizeof (int) == 0 ? 0 : 1);
    const constexpr char alphabet[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    const constexpr size_t alphabet_sz = sizeof (alphabet) - 1;
    int dices[SZ];
    string r(40, '-');
    for(size_t i = 0; i <SZ; ++i) { dices[i] = std::rand(); }

    const char* random_bytes = (const char*)dices;
    for(size_t i = r.size() - 17; i < r.size(); ++i) {
        r[i] = alphabet[*random_bytes++ % alphabet_sz];
    }

    return r;
}

Request::Method Request::method() const noexcept {
    if (_method == Method::Unspecified) {
        bool use_post = (form && form.enc_type() == EncType::Multipart && (!form.empty() || (uri && !uri->query().empty()))) // complete form
                     || _form_streaming != FormStreaming::None;
        return use_post ? Method::Post : Method::Get;
    }
    return _method;
}

static inline bool _method_has_meaning_for_body (Request::Method method) {
    return method == Request::Method::Post || method == Request::Method::Put;
}

string Request::_http_header (SerializationContext& ctx) const {
    //part 1: precalc pieces
    auto eff_method  = method();
    bool body_method = _method_has_meaning_for_body(eff_method);
    auto out_meth    = _method_str(eff_method);
    auto eff_uri     = ctx.uri;

    auto out_reluri  = eff_uri ? eff_uri->relative() : string("/");

    auto tmp_http_ver = !ctx.http_version ? 11 : ctx.http_version;
    string out_content_length;
    bool calc_content_length
              = !ctx.chunked
            && (ctx.body->parts.size() || body_method)
            && !headers.has("Content-Length");
    if (calc_content_length) out_content_length = panda::to_string(ctx.body->length());

    size_t sz_host = 0;
    size_t sz_host_port = 0;
    if (!headers.has("Host") && eff_uri && eff_uri->host()) {
        // Host field builder
        // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host
        sz_host = eff_uri->host().length();
        auto& scheme = eff_uri->scheme();
        auto port = eff_uri->port();
        if ((!scheme) || (scheme == "http" && port != 80) || (scheme == "https" && port != 443)) {
            sz_host_port = 6;
        }
    }

    string out_accept_encoding;
    if (compression_prefs && compression_prefs != static_cast<compression::storage_t>(Compression::IDENTITY) && !headers.has("Accept-Encoding")) {
        string comp_pos, comp_neg;
        int index_pos = 0, index_neg = 0;
        compression::for_each(compression_prefs, [&](auto value, bool negation){
            const char* val = nullptr;
            switch (value) {
                case Compression::GZIP   : val = "gzip"; break;
                case Compression::BROTLI : val = "br";   break;
                default: return;
            }
            if (negation) {
                if (index_neg) { comp_neg += ", "; }
                comp_neg += val;
                comp_neg += ";q=0";
                ++index_neg;
            }  else {
                if (index_pos) { comp_pos += ", "; }
                comp_pos += val;
                ++index_pos;
            }
        });
        if (index_neg) {
            if (index_pos) { comp_pos += ", "; }
             comp_pos += comp_neg;
        }
        if (comp_pos) { out_accept_encoding = comp_pos; }
    }

    auto out_content_encoding = _content_encoding(ctx);
    size_t sz_cookies = 0;
    if (cookies.size()) {
        for (auto& f : cookies.fields) sz_cookies += f.name.length() + f.value.length() + 3; // 3 for ' ', '=' and ';' for each pair
    }

    // part 2: summarize pieces size
    size_t reserved = out_meth.length();
    reserved += out_reluri.length();
    reserved += 5 + 6 + 2 + 1;   /* http-version  + trailer */

    if (out_content_length)   reserved += 14 + 2 + out_content_length.length()   + 2;
    if (sz_host)              reserved += 4  + 2 + sz_host + sz_host_port        + 2;
    if (out_accept_encoding)  reserved += 15 + 2 + out_accept_encoding.length()  + 2;
    if (out_content_encoding) reserved += 16 + 2 + out_content_encoding.length() + 2;
    if (sz_cookies)           reserved += 6  + 2 + sz_cookies                    + 2;

    for (auto& h: ctx.handled_headers) { reserved += h.name.length() + 2 + h.value.length() + 2; }
    for (auto& h: headers)  {
        if (ctx.handled_headers.has(h.name)) continue;
        reserved += h.name.length() + 2 + h.value.length() + 2;
    }

    // part 3: write out pieces
    string s(reserved);
    s += out_meth;
    s += ' ';
    s += out_reluri;
    s += " HTTP/";
    if (tmp_http_ver == 11) s += "1.1\r\n";
    else                    s += "1.0\r\n";

    if (sz_host) {
        s += "Host: " ;
        s += eff_uri->host();
        if (sz_host_port) { s+= ":";  s += panda::to_string(eff_uri->port()); }

clib/src/panda/protocol/http/Request.cc  view on Meta::CPAN

        if (ctx.handled_headers.has(h.name)) continue;
        s += h.name; s += ": "; s += h.value; s+= "\r\n";
    }
    s += "\r\n";

    //assert((sz_cookies + headers.size()) ==  0);
    //assert(reserved >= s.length());

    return s;
}

std::vector<string> Request::to_vector () const {
    SerializationContext ctx;
    if (_form_streaming != FormStreaming::None && _form_streaming != FormStreaming::Started)
        throw "form streaming wasn't finished";

    bool form_streaming =  _form_streaming == FormStreaming::Started;
    /* it seems nobody supports muliptart + gzip + chunk */
    ctx.compression = !form_streaming ? compression.type : Compression::Type::IDENTITY;
    ctx.body        = &body;
    ctx.uri         = uri.get();
    ctx.chunked     = this->chunked || form_streaming;

    auto add_form_header = [&](auto& boundary) {
        string ct = "multipart/form-data; boundary=";
        ct += boundary;
        ctx.handled_headers.add("Content-Type", ct);
    };

    Body form_body;
    URI form_uri;
    if (form) {
        if (form.enc_type() == EncType::Multipart) {
            if (!form.empty() || (uri && !uri->query().empty())) {
                auto boundary = form_streaming ? _form_boundary : _generate_boundary();
                ctx.uri  = form.to_body(form_body, form_uri, uri, boundary);
                ctx.body = &form_body;
                add_form_header(boundary);
            } else if (form_streaming) {
                add_form_header(_form_boundary);
            }
        }
        else if((form.enc_type() == EncType::UrlEncoded) && !form.empty()) {
            form.to_uri(form_uri, uri);
            ctx.uri = &form_uri;
        }
    }
    else if (form_streaming) {
        add_form_header(_form_boundary);
    }
    return _to_vector(ctx, [&]() { return _compile_prepare(ctx); }, [&]() { return _http_header(ctx); });
}

bool Request::expects_continue () const {
    for (auto& val : headers.get_multi("Expect")) if (val == "100-continue") return true;
    return false;
}

std::uint8_t Request::allowed_compression (bool inverse) const noexcept {
    std::uint8_t result = 0;
    compression::for_each(compression_prefs, [&](auto value, bool negation){
        if (inverse == negation) {
            result |= value;
        }
    });
    return result;
}

static string form_trailer(const string& boundary) noexcept {
    auto sz = boundary.size() + 6;
    string r(sz);
    r += "--";
    r += boundary;
    r += "--\r\n";
    return r;
}

namespace tag {
    using uri = std::integral_constant<int, 0>;
    using form = std::integral_constant<int, 1>;
}

template<typename Tag> struct Helper;

template<> struct Helper<tag::form> {
    using Field =  Request::Form::value_type;

    struct PatrialField {
        string name;
        string mime_type;
        string filename;
        bool complete = false;
    };

    struct FullField: PatrialField {
        FullField(const string& name_, const string& filename_, const string& mime_type_, const string& value_):
            PatrialField{name_, mime_type_, filename_, true}, value{value_}{}
        string value;
    };

    static size_t buffer_size(const string &boundary, const Request::Form& container) noexcept {
        auto fields_count = container.size();
        size_t size = (
                boundary.length() + 4   /* "--" prefix and "\r\n" */
                + 37 + 2                /* Content-Disposition: form-data; name="" + \r\n */
            ) * fields_count  + 2;      /* -- */
        for(auto it : container) {
            size += it.second.value.length();
            auto& name = it.second.name;
            if (name) {
                size += name.length() + 14;   //; filename=""
            }
            auto& ct = it.second.content_type;
            if (ct) {
                size += ct.size() + 18; //Content-Type: xxx\r\n
            }
        }
        return size;
    }

    static void append_header(string& r, const string& header, const string& value) noexcept {



( run in 1.180 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )