Skip to content

Commit e76eab1

Browse files
authored
Require yes/no from user if wasm downloading > 10 MB of data (#156)
* Separate read buffer class instead of in response class * Store response in stream * Show prompt for large downloads
1 parent d7d2008 commit e76eab1

9 files changed

Lines changed: 170 additions & 68 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ set(GIT2CPP_SRC
103103
${GIT2CPP_SOURCE_DIR}/wasm/constants.hpp
104104
${GIT2CPP_SOURCE_DIR}/wasm/libgit2_internals.cpp
105105
${GIT2CPP_SOURCE_DIR}/wasm/libgit2_internals.hpp
106+
${GIT2CPP_SOURCE_DIR}/wasm/read_buffer.cpp
107+
${GIT2CPP_SOURCE_DIR}/wasm/read_buffer.hpp
106108
${GIT2CPP_SOURCE_DIR}/wasm/response.cpp
107109
${GIT2CPP_SOURCE_DIR}/wasm/response.hpp
108110
${GIT2CPP_SOURCE_DIR}/wasm/scope.cpp

src/wasm/read_buffer.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifdef EMSCRIPTEN
2+
3+
# include "read_buffer.hpp"
4+
5+
read_buffer_t::read_buffer_t(char* buffer, size_t buffer_size, size_t* bytes_read)
6+
: m_buffer(buffer)
7+
, m_buffer_size(buffer_size)
8+
, m_bytes_read(bytes_read)
9+
{
10+
}
11+
12+
#endif // EMSCRIPTEN

src/wasm/read_buffer.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
#ifdef EMSCRIPTEN
4+
5+
# include <stddef.h>
6+
7+
// Buffer used when reading remote data is created by libgit2 and passed to transport layer to use.
8+
// We only fill the buffer and otherwise return it unmodified.
9+
struct read_buffer_t
10+
{
11+
read_buffer_t(char* buffer, size_t buffer_size, size_t* bytes_read);
12+
13+
char* m_buffer;
14+
size_t m_buffer_size;
15+
size_t* m_bytes_read;
16+
};
17+
18+
#endif // EMSCRIPTEN

src/wasm/response.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
# include "../utils/common.hpp"
66
# include "libgit2_internals.hpp"
77

8-
wasm_http_response::wasm_http_response(char* buffer, size_t buffer_size, size_t* bytes_read)
9-
: m_buffer(buffer)
10-
, m_buffer_size(buffer_size)
11-
, m_bytes_read(bytes_read)
12-
, m_status(0)
8+
wasm_http_response::wasm_http_response()
9+
: m_status(0)
10+
, m_read_count(0)
11+
, m_total_bytes(0)
1312
{
14-
*m_bytes_read = 0;
1513
}
1614

1715
void wasm_http_response::add_header(const std::string& key, const std::string& value)
@@ -21,9 +19,10 @@ void wasm_http_response::add_header(const std::string& key, const std::string& v
2119

2220
void wasm_http_response::clear()
2321
{
24-
*m_bytes_read = 0;
2522
m_status = 0;
2623
m_status_text.clear();
24+
m_read_count = 0;
25+
m_total_bytes = 0;
2726
m_response_headers.clear();
2827
}
2928

src/wasm/response.hpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class wasm_http_response
1313
{
1414
public:
1515

16-
wasm_http_response(char* buffer, size_t buffer_size, size_t* bytes_read);
16+
wasm_http_response();
1717

1818
void add_header(const std::string& key, const std::string& value);
1919

@@ -29,11 +29,10 @@ class wasm_http_response
2929

3030
void set_git_error(std::string_view url) const;
3131

32-
char* m_buffer; // Not owned.
33-
size_t m_buffer_size;
34-
size_t* m_bytes_read; // Not owned.
35-
int32_t m_status; // Specific type corresponding to i32 in emscripten setValue call.
32+
int32_t m_status; // Specific type corresponding to i32 in emscripten setValue call.
3633
std::string m_status_text;
34+
unsigned int m_read_count;
35+
int64_t m_total_bytes; // Specific type corresponding to i64 in emscripten setValue call.
3736

3837
private:
3938

src/wasm/stream.cpp

Lines changed: 80 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
# include "../utils/common.hpp"
1111
# include "constants.hpp"
12+
# include "read_buffer.hpp"
1213
# include "response.hpp"
14+
# include "utils.hpp"
1315

1416
// Buffer size used in transport_smart, hardcoded in libgit2.
1517
# define EMFORGE_BUFSIZE 65536
@@ -157,8 +159,10 @@ EM_JS(
157159
(int request_index,
158160
char* buffer,
159161
size_t buffer_size,
162+
bool first_read, // Set subsequent arguments only if this is true.
160163
int32_t* status,
161164
const char** status_text,
165+
int64_t* total_bytes,
162166
const char** response_headers),
163167
{
164168
try
@@ -173,22 +177,28 @@ EM_JS(
173177
request.content = null;
174178
}
175179

176-
let bytes_read = 0;
180+
let byte_length = 0; // Total number of bytes that will be returned.
181+
let bytes_read = 0; // Number of bytes returned by this call.
177182
if (xhr.response && xhr.response.byteLength)
178183
{
179-
bytes_read = xhr.response.byteLength - request.result_buffer_pointer;
184+
byte_length = xhr.response.byteLength;
185+
bytes_read = byte_length - request.result_buffer_pointer;
180186
if (bytes_read > buffer_size)
181187
{
182188
bytes_read = buffer_size;
183189
}
184190
}
185191

186-
// Caller must delete the returned status_text and response_headers.
187-
// clang-format off
188-
setValue(status, xhr.status, 'i32*');
189-
setValue(status_text, stringToNewUTF8(xhr.statusText ?? ""), 'i8**');
190-
setValue(response_headers, stringToNewUTF8(xhr.getAllResponseHeaders() ?? ""), 'i8**');
191-
// clang-format on
192+
if (first_read)
193+
{
194+
// Caller must delete the returned status_text and response_headers.
195+
// clang-format off
196+
setValue(status, xhr.status, 'i32*');
197+
setValue(status_text, stringToNewUTF8(xhr.statusText ?? ""), 'i8**');
198+
setValue(total_bytes, byte_length, 'i64*');
199+
setValue(response_headers, stringToNewUTF8(xhr.getAllResponseHeaders() ?? ""), 'i8**');
200+
// clang-format on
201+
}
192202

193203
if (bytes_read > 0)
194204
{
@@ -308,7 +318,7 @@ static void delete_request(wasm_http_stream* stream)
308318
}
309319
}
310320

311-
static int read(wasm_http_stream* stream, wasm_http_response& response, bool is_read_response)
321+
static int read(wasm_http_stream* stream, read_buffer_t& read_buffer, bool is_read_response)
312322
{
313323
if (is_read_response)
314324
{
@@ -342,18 +352,23 @@ static int read(wasm_http_stream* stream, wasm_http_response& response, bool is_
342352
}
343353
}
344354

355+
auto& response = stream->m_response;
356+
bool first_read = response.m_read_count == 0;
345357
const char* status_text = nullptr;
346358
const char* response_headers = nullptr;
347359

348360
// Actual read.
349361
size_t bytes_read = js_read(
350362
stream->m_request_index,
351-
response.m_buffer,
352-
response.m_buffer_size,
363+
read_buffer.m_buffer,
364+
read_buffer.m_buffer_size,
365+
first_read,
353366
&response.m_status,
354367
&status_text,
368+
&response.m_total_bytes,
355369
&response_headers
356370
);
371+
stream->m_response.m_read_count++;
357372
if (bytes_read == static_cast<size_t>(-1))
358373
{
359374
convert_js_to_git_error(stream);
@@ -363,43 +378,52 @@ static int read(wasm_http_stream* stream, wasm_http_response& response, bool is_
363378
return -1;
364379
}
365380

366-
response.m_status_text = status_text;
367-
delete status_text; // Delete const char* allocated in JavaScript.
368-
369-
// Split single string with response headers separated by \r\n into individual headers.
370-
auto lines = split_input_at_newlines(response_headers);
371-
for (const auto& line : lines)
381+
if (first_read)
372382
{
373-
auto pos = line.find(":");
374-
if (pos == std::string::npos)
383+
response.m_status_text = status_text;
384+
delete status_text; // Delete const char* allocated in JavaScript.
385+
386+
// Split single string with response headers separated by \r\n into individual headers.
387+
auto lines = split_input_at_newlines(response_headers);
388+
for (const auto& line : lines)
375389
{
376-
// Skip invalid lines. Should this be an error condition?
377-
continue;
390+
auto pos = line.find(":");
391+
if (pos == std::string::npos)
392+
{
393+
// Skip invalid lines. Should this be an error condition?
394+
continue;
395+
}
396+
response.add_header(line.substr(0, pos), line.substr(pos + 1));
378397
}
379-
response.add_header(line.substr(0, pos), line.substr(pos + 1));
380-
}
381-
delete response_headers; // Delete const char* allocated in JavaScript.
398+
delete response_headers; // Delete const char* allocated in JavaScript.
382399

383-
// If successful, check expected response content-type is correct.
384-
if (response.m_status == GIT_HTTP_STATUS_OK)
385-
{
386-
auto expected_response_type = stream->m_service.m_response_type;
387-
if (!expected_response_type.empty()
388-
&& !response.has_header_matches("content-type", expected_response_type))
400+
// If successful, check expected response content-type is correct.
401+
if (response.m_status == GIT_HTTP_STATUS_OK)
389402
{
390-
// Not sure this should be checked at all, as CORS proxy may be doing something
391-
// with it.
392-
git_error_set(
393-
GIT_ERROR_HTTP,
394-
"expected response content-type header '%s' to request %s",
395-
expected_response_type.c_str(),
396-
stream->m_unconverted_url.c_str()
397-
);
403+
auto expected_response_type = stream->m_service.m_response_type;
404+
if (!expected_response_type.empty()
405+
&& !response.has_header_matches("content-type", expected_response_type))
406+
{
407+
// Not sure this should be checked at all, as CORS proxy may be doing something
408+
// with it.
409+
git_error_set(
410+
GIT_ERROR_HTTP,
411+
"expected response content-type header '%s' to request %s",
412+
expected_response_type.c_str(),
413+
stream->m_unconverted_url.c_str()
414+
);
415+
return -1;
416+
}
417+
}
418+
419+
if (!maybe_prompt_to_download(response.m_total_bytes))
420+
{
421+
git_error_set(GIT_ERROR_NONE, "Download aborted");
398422
return -1;
399423
}
400424
}
401425

402-
*response.m_bytes_read = bytes_read;
426+
*read_buffer.m_bytes_read = bytes_read;
403427
return 0;
404428
}
405429

@@ -427,7 +451,7 @@ static int write(wasm_http_stream* stream, const char* buffer, size_t buffer_siz
427451

428452
// C credential functions.
429453

430-
static int create_credential(wasm_http_stream* stream, const wasm_http_response& response)
454+
static int create_credential(wasm_http_stream* stream)
431455
{
432456
wasm_http_subtransport* subtransport = stream->m_subtransport;
433457

@@ -440,7 +464,7 @@ static int create_credential(wasm_http_stream* stream, const wasm_http_response&
440464
subtransport->m_authorization_header = "";
441465

442466
// Check that response headers show support for 'www-authenticate: Basic'.
443-
if (!response.has_header_starts_with("www-authenticate", "Basic"))
467+
if (!stream->m_response.has_header_starts_with("www-authenticate", "Basic"))
444468
{
445469
git_error_set(
446470
GIT_ERROR_HTTP,
@@ -541,36 +565,36 @@ void wasm_http_stream_free(git_smart_subtransport_stream* s)
541565
int wasm_http_stream_read(git_smart_subtransport_stream* s, char* buffer, size_t buffer_size, size_t* bytes_read)
542566
{
543567
wasm_http_stream* stream = reinterpret_cast<wasm_http_stream*>(s);
544-
wasm_http_response response(buffer, buffer_size, bytes_read);
568+
read_buffer_t read_buffer(buffer, buffer_size, bytes_read);
545569

546570
bool send = true;
547571
while (send)
548572
{
549-
if (read(stream, response, false) == static_cast<size_t>(-1))
573+
if (read(stream, read_buffer, false) == static_cast<size_t>(-1))
550574
{
551575
return -1; // git error already set.
552576
}
553577
send = false;
554578

555-
auto final_url_header = response.get_header("x-final-url");
579+
auto final_url_header = stream->m_response.get_header("x-final-url");
556580
if (final_url_header.has_value() && stream->ensure_final_url(final_url_header.value())
557-
&& response.m_status != GIT_HTTP_STATUS_OK)
581+
&& stream->m_response.m_status != GIT_HTTP_STATUS_OK)
558582
{
559583
// Resend only if status not OK, if OK next request will use updated URL.
560584
send = true;
561585
}
562586

563-
if (response.has_header("strict-transport-security") && stream->ensure_https()
564-
&& response.m_status != GIT_HTTP_STATUS_OK)
587+
if (stream->m_response.has_header("strict-transport-security") && stream->ensure_https()
588+
&& stream->m_response.m_status != GIT_HTTP_STATUS_OK)
565589
{
566590
// Resend only if status not OK, if OK next request will use https not http.
567591
send = true;
568592
}
569593

570-
if (response.m_status == GIT_HTTP_STATUS_UNAUTHORIZED)
594+
if (stream->m_response.m_status == GIT_HTTP_STATUS_UNAUTHORIZED)
571595
{
572596
// Request and create new credentials.
573-
if (create_credential(stream, response) < 0)
597+
if (create_credential(stream) < 0)
574598
{
575599
return -1; // git error already set.
576600
}
@@ -580,13 +604,13 @@ int wasm_http_stream_read(git_smart_subtransport_stream* s, char* buffer, size_t
580604
if (send)
581605
{
582606
delete_request(stream);
583-
response.clear();
607+
stream->m_response.clear();
584608
}
585609
}
586610

587-
if (response.m_status != GIT_HTTP_STATUS_OK)
611+
if (stream->m_response.m_status != GIT_HTTP_STATUS_OK)
588612
{
589-
response.set_git_error(stream->m_unconverted_url);
613+
stream->m_response.set_git_error(stream->m_unconverted_url);
590614
return -1;
591615
}
592616

@@ -597,15 +621,15 @@ int wasm_http_stream_read_response(git_smart_subtransport_stream* s, char* buffe
597621
{
598622
wasm_http_stream* stream = reinterpret_cast<wasm_http_stream*>(s);
599623

600-
wasm_http_response response(buffer, buffer_size, bytes_read);
601-
int error = read(stream, response, true);
624+
read_buffer_t read_buffer(buffer, buffer_size, bytes_read);
625+
int error = read(stream, read_buffer, true);
602626

603627
// May need similar handling of response status and headers as occurs in read() above, but so
604628
// far this has not been necessary.
605629

606-
if (error == 0 && response.m_status != GIT_HTTP_STATUS_OK)
630+
if (error == 0 && stream->m_response.m_status != GIT_HTTP_STATUS_OK)
607631
{
608-
response.set_git_error(stream->m_unconverted_url);
632+
stream->m_response.set_git_error(stream->m_unconverted_url);
609633
error = -1;
610634
}
611635

src/wasm/stream.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# include <git2/sys/transport.h>
66

77
# include "libgit2_internals.hpp"
8+
# include "response.hpp"
89
# include "subtransport.hpp"
910

1011
// A stream represents a single http/https request.
@@ -26,6 +27,7 @@ struct wasm_http_stream
2627
http_service m_service;
2728
std::string m_unconverted_url;
2829
int m_request_index;
30+
wasm_http_response m_response;
2931
};
3032

3133
void wasm_http_stream_free(git_smart_subtransport_stream* s);

0 commit comments

Comments
 (0)