From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS3215 2.6.0.0/16 X-Spam-Status: No, score=-3.6 required=3.0 tests=AWL,BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,SPF_HELO_NONE, SPF_PASS shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from out1.vger.email (out1.vger.email [IPv6:2620:137:e000::1:20]) by dcvr.yhbt.net (Postfix) with ESMTP id 8F0551FC44 for ; Wed, 2 Nov 2022 22:10:23 +0000 (UTC) Authentication-Results: dcvr.yhbt.net; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="BCNzbONC"; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231388AbiKBWJo (ORCPT ); Wed, 2 Nov 2022 18:09:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53200 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229996AbiKBWJi (ORCPT ); Wed, 2 Nov 2022 18:09:38 -0400 Received: from mail-wr1-x434.google.com (mail-wr1-x434.google.com [IPv6:2a00:1450:4864:20::434]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1A14CBEC for ; Wed, 2 Nov 2022 15:09:36 -0700 (PDT) Received: by mail-wr1-x434.google.com with SMTP id cl5so192147wrb.9 for ; Wed, 02 Nov 2022 15:09:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:from:to:cc:subject:date :message-id:reply-to; bh=ZS+vJncVOAy2CYmx4D6/h8tcRsGlPjDDfC+97kQiBFE=; b=BCNzbONCL2huAn/CZ9m/BGn1qFX82VKYEdrH7PNnE4aT1SxxOHr05QDCwRX5t0jkKq P0zSXTkDCOXBcHubA2VaxnPsWTTllCQ9K/94S+5+hW1DilBsVO7Cd4c3fUBVTeUeggBq eTq8FHEUDa5aXMLDZLiL1G4uKtS0AcF4GNH1BH0WbQkMo7QF0G8YXruBJSoW4iWC4nEn gsH5/LJr0cc9AeOCNXAfIU4HoCPJeGfwKPy1qzYGPgXtqT0WMRq8lRAOG/SaSHATOMLg 53s2sV9X5zXZ5PxajwRCTOyDrhWTiPdKDgFVyOLLUsSNZdpo0+VyTqzXiQj6PLutQEdu 0Hdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ZS+vJncVOAy2CYmx4D6/h8tcRsGlPjDDfC+97kQiBFE=; b=mlXBosLr6eTSnjNeRWjymSh2kSDPxlD8Ic+Cz7bDo+yCDuFfZ/AMZ57/rCwez9fLpt qhgRUeVktr5DkF+iDURQxwAsDLT87zA95+IIVajDnKdcZsDrDGgSy0cAHDHJNSwDU5nq hStdhfaDPYBYbfMWlAW69jrD0Hx8Vdax9CB726TmjgU1yxk92KqqzTebu4l4IcpROybz sfepnU0qsvKp91HzJYXwStJRF6knQWj9zHvFRIcdS51AThBWxlsN7vKdaUO/LrhzyEy9 GK4EyHSzXvvatdSuoEWySuDKGz0W33AQkWIsv8l4O/69nbwbEudGRui80U2kDsgWyDoO DDyA== X-Gm-Message-State: ACrzQf0spdoLhRB4bHUxdzfc30LD/smWfFvkqKwuiATJWrStJkLesSE4 Mz0QYa2DQXuzohN03hYakb8hIDiW+mc= X-Google-Smtp-Source: AMsMyM4SVnFTrGONJ5vK5ji2YDjqb1JJvbuJOzg6NMilzb3sV4OLdGfbh7xj9Gfk95cyDLbQhN0v4Q== X-Received: by 2002:a5d:6d89:0:b0:236:7d7d:1e79 with SMTP id l9-20020a5d6d89000000b002367d7d1e79mr17282283wrs.673.1667426972973; Wed, 02 Nov 2022 15:09:32 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id h8-20020a05600c314800b003a1980d55c4sm3389900wmo.47.2022.11.02.15.09.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Nov 2022 15:09:32 -0700 (PDT) Message-Id: In-Reply-To: References: From: "Matthew John Cheetham via GitGitGadget" Date: Wed, 02 Nov 2022 22:09:19 +0000 Subject: [PATCH v3 01/11] http: read HTTP WWW-Authenticate response headers Fcc: Sent Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MIME-Version: 1.0 To: git@vger.kernel.org Cc: Derrick Stolee , Lessley Dennington , Matthew John Cheetham , M Hickford , Jeff Hostetler , Matthew John Cheetham , Matthew John Cheetham Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Matthew John Cheetham Read and store the HTTP WWW-Authenticate response headers made for a particular request. This will allow us to pass important authentication challenge information to credential helpers or others that would otherwise have been lost. According to RFC2616 Section 4.2 [1], header field names are not case-sensitive meaning when collecting multiple values for the same field name, we can just use the case of the first observed instance of each field name and no normalisation is required. libcurl only provides us with the ability to read all headers recieved for a particular request, including any intermediate redirect requests or proxies. The lines returned by libcurl include HTTP status lines delinating any intermediate requests such as "HTTP/1.1 200". We use these lines to reset the strvec of WWW-Authenticate header values as we encounter them in order to only capture the final response headers. The collection of all header values matching the WWW-Authenticate header is complicated by the fact that it is legal for header fields to be continued over multiple lines, but libcurl only gives us one line at a time. In the future [2] we may be able to leverage functions to read headers from libcurl itself, but as of today we must do this ourselves. [1] https://datatracker.ietf.org/doc/html/rfc2616#section-4.2 [2] https://daniel.haxx.se/blog/2022/03/22/a-headers-api-for-libcurl/ Signed-off-by: Matthew John Cheetham --- credential.c | 1 + credential.h | 15 ++++++++++ http.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/credential.c b/credential.c index f6389a50684..897b4679333 100644 --- a/credential.c +++ b/credential.c @@ -22,6 +22,7 @@ void credential_clear(struct credential *c) free(c->username); free(c->password); string_list_clear(&c->helpers, 0); + strvec_clear(&c->wwwauth_headers); credential_init(c); } diff --git a/credential.h b/credential.h index f430e77fea4..6f2e5bc610b 100644 --- a/credential.h +++ b/credential.h @@ -2,6 +2,7 @@ #define CREDENTIAL_H #include "string-list.h" +#include "strvec.h" /** * The credentials API provides an abstracted way of gathering username and @@ -115,6 +116,19 @@ struct credential { */ struct string_list helpers; + /** + * A `strvec` of WWW-Authenticate header values. Each string + * is the value of a WWW-Authenticate header in an HTTP response, + * in the order they were received in the response. + */ + struct strvec wwwauth_headers; + + /** + * Internal use only. Used to keep track of split header fields + * in order to fold multiple lines into one value. + */ + unsigned header_is_last_match:1; + unsigned approved:1, configured:1, quit:1, @@ -130,6 +144,7 @@ struct credential { #define CREDENTIAL_INIT { \ .helpers = STRING_LIST_INIT_DUP, \ + .wwwauth_headers = STRVEC_INIT, \ } /* Initialize a credential structure, setting all fields to empty. */ diff --git a/http.c b/http.c index 5d0502f51fd..03d43d352e7 100644 --- a/http.c +++ b/http.c @@ -183,6 +183,82 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) return nmemb; } +static size_t fwrite_wwwauth(char *ptr, size_t eltsize, size_t nmemb, void *p) +{ + size_t size = eltsize * nmemb; + struct strvec *values = &http_auth.wwwauth_headers; + struct strbuf buf = STRBUF_INIT; + const char *val; + const char *z = NULL; + + /* + * Header lines may not come NULL-terminated from libcurl so we must + * limit all scans to the maximum length of the header line, or leverage + * strbufs for all operations. + * + * In addition, it is possible that header values can be split over + * multiple lines as per RFC 2616 (even though this has since been + * deprecated in RFC 7230). A continuation header field value is + * identified as starting with a space or horizontal tab. + * + * The formal definition of a header field as given in RFC 2616 is: + * + * message-header = field-name ":" [ field-value ] + * field-name = token + * field-value = *( field-content | LWS ) + * field-content = + */ + + strbuf_add(&buf, ptr, size); + + /* Strip the CRLF that should be present at the end of each field */ + strbuf_trim_trailing_newline(&buf); + + /* Start of a new WWW-Authenticate header */ + if (skip_iprefix(buf.buf, "www-authenticate:", &val)) { + while (isspace(*val)) + val++; + + strvec_push(values, val); + http_auth.header_is_last_match = 1; + goto exit; + } + + /* + * This line could be a continuation of the previously matched header + * field. If this is the case then we should append this value to the + * end of the previously consumed value. + */ + if (http_auth.header_is_last_match && isspace(*buf.buf)) { + const char **v = values->v + values->nr - 1; + char *append = xstrfmt("%s%.*s", *v, (int)(size - 1), ptr + 1); + + free((void*)*v); + *v = append; + + goto exit; + } + + /* This is the start of a new header we don't care about */ + http_auth.header_is_last_match = 0; + + /* + * If this is a HTTP status line and not a header field, this signals + * a different HTTP response. libcurl writes all the output of all + * response headers of all responses, including redirects. + * We only care about the last HTTP request response's headers so clear + * the existing array. + */ + if (skip_iprefix(buf.buf, "http/", &z)) + strvec_clear(values); + +exit: + strbuf_release(&buf); + return size; +} + size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) { return nmemb; @@ -1829,6 +1905,8 @@ static int http_request(const char *url, fwrite_buffer); } + curl_easy_setopt(slot->curl, CURLOPT_HEADERFUNCTION, fwrite_wwwauth); + accept_language = http_get_accept_language_header(); if (accept_language) -- gitgitgadget