Compare commits

..

18 commits

Author SHA1 Message Date
Daniel Eklöf
ca73ec71d5
selection: escape quotes in file names being DnD:ed on the command line
Closes #2363
2026-06-12 18:49:48 +02:00
Daniel Eklöf
8b047852f3
osc: kitty text-size: bail out if text is zero-length
This fixes a crash further down, where we try to lookup an existing
compose sequence.

Closes #2364
2026-06-12 18:21:51 +02:00
Daniel Eklöf
75e201608b
sixel: fix NULL deref when using a shared palette and gamma-correct blending
This fixes a copy-paste error where we read/modified/wrote the
private-palette instead of the shared palette.

Since the private palette is NULL in this case, that meant a crash.

Closes #2370
2026-06-12 18:09:02 +02:00
Daniel Eklöf
c9c448e611
sixel: clamp pan/pad to 5
Pad is always (in real life) 1, and no-one should be setting pan to
anything larger than 5 (the largest value possible to set with the
sixel init parameters). FWIW, no-one really uses anything but 1 here
either.

Clamping ensures we don't blow up allocations to something huge, or
worse, trigger an overflow that results in a pixel height of 0,
causing a division-by-zero.

Closes #2371
2026-06-12 18:04:18 +02:00
lumerue
35f30e6451
add ayu-dark 2026-06-12 17:47:34 +02:00
fhqh
7aa880654a
Import official Dracula (dark) / Alucard (light) theme
Some colors in dark theme have slightly different I assume more
correct values then previously
2026-06-12 17:45:59 +02:00
CismonX
66ec9fad88
csi: refactor CHT/CBT
Eliminate the outer loop, so that when moving the cursor multiple
tab stops, we no longer iterate the tab stop list all over again.

This also fixes a DoS flaw when passing a very large value as
CHT/CBT argument, which may hang the terminal.

Closes #2360
2026-06-12 17:41:08 +02:00
CismonX
382e9a31c5
selection: fix block selection direction update
This patch fixes a condition check in selection_update(), so that
during block selection, the selection area no longer gets incorrectly
updated when selecting back across the starting column.
2026-06-12 17:36:42 +02:00
Daniel Eklöf
d7742d0312
term: do not allow codepoint merging into grapheme clusters directly after a cursor move
That is, only do grapheme clustering when printing codepoints in
sequence, without any cursor movements in between.

Closes #2383
2026-06-12 15:54:45 +02:00
Campbell Barton
f66a020bba
Fix #2377: DECCRA: swapped row-bounds check dropping multi-row copies
`dst_bottom > dst_top` holds for every multi-row destination (rows
increase downward), so the guard rejected all multi-row copies. Flip to
`dst_top > dst_bottom`, mirroring the sibling column check and DEC STD 070.
2026-06-12 13:55:09 +02:00
Daniel Eklöf
8038adedf0
meson: require xkbcommon >= 1.6.0
We use XKB_KEYSYM_MAX, which was added in 1.6.0. In other words, this
requirement isn't really new, but now we've formalized it in
meson.build.

Closes #2379
2026-06-12 13:50:41 +02:00
CismonX
4bf60d0fbc
selection: do not copy empty text
Copy-on-select (configured with 'selection-target') may accidentally
clear the clipboard, if the user drags the mouse a little bit when
clicking inside a terminal window.

Now we only copy if there is actual text being selected.

Closes #2327
2026-05-22 11:49:46 +02:00
Daniel Eklöf
b18d8aa2f1
csi: DECCRA: clamp and verify destination rectangle coordinates
dst_right was already being clamped, but not dst_left.

In addition to that, reject the sequence if dst_left > dst_right, or
dst_bottom > dst_top.

This is already being done for the source rectangle, in
params_to_rectangular_area().

Closes #2352
2026-05-22 11:39:04 +02:00
Daniel Eklöf
2eaa7beba1
uri-parse: fix out-of-bounds read with malformed %-encoded content
If the input URI ends with a trailing '%' (or a trailing '%N'), we
read outside the provided buffer.

On NULL terminated input, this happened to work out since we'd
correctly detect an invalid %-sequence as soon as we read the NULL
terminator.

On input that is not NULL terminated, we're out of luck.

This patch fixes this by also checking we have enough input left to
even _try_ to read the %-digits.

Also add unit tests for this particular case.

Closes #2353
2026-05-22 11:00:48 +02:00
Daniel Eklöf
5335cec322
uri-parse: add a bunch of unit tests 2026-05-22 10:49:22 +02:00
Daniel Eklöf
f35e60577f
project: add .clangd, where we set -Wno-c2y-extensions
We make use of __COUNTER__, which recent versions of clang emit a
warning for (unless you compile with -std=c2y).

Our meson.build already handles this for the actual build, but if your
build is using gcc, -Wno-c2y-extensions isn't added to the build
rules (since gcc doesn't support the flag).
2026-05-17 15:03:56 +02:00
Daniel Eklöf
2d11b36a24
changelog: add new 'unreleased' section 2026-05-15 08:29:07 +02:00
Daniel Eklöf
ab1660ef62
Merge branch 'releases/1.27' 2026-05-15 08:27:39 +02:00
12 changed files with 373 additions and 50 deletions

5
.clangd Normal file
View file

@ -0,0 +1,5 @@
# -*- yaml -*-
CompileFlags:
Add:
- -Wno-c2y-extensions

View file

@ -1,5 +1,6 @@
# Changelog
* [Unreleased](#unreleased)
* [1.27.0](#1-27-0)
* [1.26.1](#1-26-1)
* [1.26.0](#1-26-0)
@ -69,6 +70,56 @@
* [1.2.0](#1-2-0)
## Unreleased
### Added
### Changed
* Do not allow codepoints to be merged into grapheme clusters directly
after a cursor move ([#2383][2383]).
* Dracula theme updated to latest official, and light theme alucard
added.
* Sixels: pan/pad clamped to 5 ([#2371][2371]).
[2383]: https://codeberg.org/dnkl/foot/issues/2383
[2371]: https://codeberg.org/dnkl/foot/issues/2371
### Deprecated
### Removed
### Fixed
* Out-of-bounds read when parsing URIs with malformed %-encoded
content ([#2353][2353]).
* DECCRA not clamping or verifying the destination rectangle
([#2352][2352]).
* Empty selection clearing the clipboard ([#2327][2327]).
* Require xkbcommon >= 1.6.0. This has been the case for a while, due
to our use of `XKB_KEYSYM_MAX`. Now it is formalized in
`meson.build` ([#2379][2379]).
* Block selection area incorrectly updated when selecting back
across the starting column.
* Passing a very large value as CHT/CBT argument hangs the terminal
([#2360][2360]).
* Sixel: crash when using a shared palette and gamma-correct blending
has been enabled, or foot is using 10-bit surface ([#2370][2370]).
* Kitty text-size protocol: fix crash when text is zero-length
([#2364][2364]).
* Escape quotes in file names being DnD:ed on the command line
([#2363][2363]).
[2353]: https://codeberg.org/dnkl/foot/issues/2353
[2352]: https://codeberg.org/dnkl/foot/issues/2352
[2327]: https://codeberg.org/dnkl/foot/issues/2327
[2379]: https://codeberg.org/dnkl/foot/issues/2379
[2360]: https://codeberg.org/dnkl/foot/issues/2360
[2370]: https://codeberg.org/dnkl/foot/issues/2370
[2364]: https://codeberg.org/dnkl/foot/issues/2364
### Security
### Contributors
## 1.27.0
### Added

56
csi.c
View file

@ -774,7 +774,7 @@ params_to_rectangular_area(const struct terminal *term, int first_idx,
int rel_bottom = vt_param_get(term, first_idx + 2, term->rows) - 1;
*right = min(vt_param_get(term, first_idx + 3, term->cols) - 1, term->cols - 1);
if (rel_top > rel_bottom || *left > *right)
if (unlikely(rel_top > rel_bottom || *left > *right))
return false;
*top = term_row_rel_to_abs(term, rel_top);
@ -1171,37 +1171,40 @@ csi_dispatch(struct terminal *term, uint8_t final)
case 'I': {
/* CHT - Tab Forward (param is number of tab stops to move through) */
for (int i = 0; i < vt_param_get(term, 0, 1); i++) {
int new_col = term->cols - 1;
tll_foreach(term->tab_stops, it) {
if (it->item > term->grid->cursor.point.col) {
new_col = it->item;
int count = vt_param_get(term, 0, 1);
int new_col = term->grid->cursor.point.col;
tll_foreach(term->tab_stops, it) {
if (it->item > new_col) {
if (--count < 0) {
break;
}
new_col = it->item;
}
xassert(new_col >= term->grid->cursor.point.col);
bool lcf = term->grid->cursor.lcf;
term_cursor_right(term, new_col - term->grid->cursor.point.col);
term->grid->cursor.lcf = lcf;
}
xassert(new_col >= term->grid->cursor.point.col);
bool lcf = term->grid->cursor.lcf;
term_cursor_right(term, new_col - term->grid->cursor.point.col);
term->grid->cursor.lcf = lcf;
break;
}
case 'Z':
case 'Z': {
/* CBT - Back tab (param is number of tab stops to move back through) */
for (int i = 0; i < vt_param_get(term, 0, 1); i++) {
int new_col = 0;
tll_rforeach(term->tab_stops, it) {
if (it->item < term->grid->cursor.point.col) {
new_col = it->item;
int count = vt_param_get(term, 0, 1);
int new_col = term->grid->cursor.point.col;
tll_rforeach(term->tab_stops, it) {
if (it->item < new_col) {
if (--count < 0) {
break;
}
new_col = it->item;
}
xassert(term->grid->cursor.point.col >= new_col);
term_cursor_left(term, term->grid->cursor.point.col - new_col);
}
xassert(term->grid->cursor.point.col >= new_col);
term_cursor_left(term, term->grid->cursor.point.col - new_col);
break;
}
case 'h':
case 'l': {
@ -2005,9 +2008,8 @@ csi_dispatch(struct terminal *term, uint8_t final)
}
int src_page = vt_param_get(term, 4, 1);
int dst_rel_top = vt_param_get(term, 5, 1) - 1;
int dst_left = vt_param_get(term, 6, 1) - 1;
int dst_left = min(vt_param_get(term, 6, 1) - 1, term->cols - 1);
int dst_page = vt_param_get(term, 7, 1);
if (unlikely(src_page != 1 || dst_page != 1)) {
@ -2021,6 +2023,18 @@ csi_dispatch(struct terminal *term, uint8_t final)
int dst_top = term_row_rel_to_abs(term, dst_rel_top);
int dst_bottom = term_row_rel_to_abs(term, dst_rel_bottom);
if (unlikely(dst_left > dst_right || dst_top > dst_bottom))
break;
/*
* src validated by params_to_rectangular_area()
* dst validated above
*/
xassert(src_bottom - src_top >= 0);
xassert(dst_bottom - dst_top >= 0);
xassert(src_right - src_left >= 0);
xassert(dst_right - dst_left >= 0);
/* Target area outside the screen is clipped */
const size_t row_count = min(src_bottom - src_top,
dst_bottom - dst_top) + 1;

View file

@ -151,7 +151,7 @@ wayland_protocols = dependency('wayland-protocols', version: '>=1.41',
default_options: ['tests=false'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
xkb = dependency('xkbcommon', version: '>=1.0.0')
xkb = dependency('xkbcommon', version: '>=1.6.0')
fontconfig = dependency('fontconfig')
utf8proc = dependency('libutf8proc', required: get_option('grapheme-clustering'))

3
osc.c
View file

@ -1149,6 +1149,9 @@ kitty_text_size(struct terminal *term, char *string)
*text = '\0';
text++;
if (text[0] == '\0')
return;
char32_t *wchars = ambstoc32(text);
if (wchars == NULL)
return;

View file

@ -1145,7 +1145,7 @@ selection_update(struct terminal *term, int col, int row)
struct coord *pivot_end = &term->selection.pivot.end;
if (term->selection.kind == SELECTION_BLOCK) {
if (new_end.col > pivot_start->col)
if (new_end.col >= pivot_start->col)
new_direction = SELECTION_RIGHT;
else
new_direction = SELECTION_LEFT;
@ -1948,6 +1948,9 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s
bool
text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
{
if (text == NULL || text[0] == '\0')
return false;
xassert(serial != 0);
struct wl_clipboard *clipboard = &seat->clipboard;
@ -2095,7 +2098,16 @@ decode_one_uri(struct clipboard_receive *ctx, char *uri, size_t len)
if (ctx->quote_paths)
ctx->cb("'", 1, ctx->user);
ctx->cb(path, strlen(path), ctx->user);
char *path_remaining = path;
for (char *next_quote = strchr(path_remaining, '\'');
next_quote != NULL;
path_remaining = next_quote + 1,
next_quote = strchr(path_remaining, '\''))
{
ctx->cb(path_remaining, next_quote - path_remaining, ctx->user);
ctx->cb("\\'", 2, ctx->user);
}
ctx->cb(path_remaining, strlen(path_remaining), ctx->user);
if (ctx->quote_paths)
ctx->cb("'", 1, ctx->user);
@ -2418,6 +2430,9 @@ selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t seri
bool
text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t serial)
{
if (text == NULL || text[0] == '\0')
return false;
if (term->wl->primary_selection_device_manager == NULL)
return false;

12
sixel.c
View file

@ -169,10 +169,10 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
if (term->sixel.linear_blending || term->sixel.use_10bit) {
for (size_t i = 0; i < active_palette_entries; i++) {
uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff;
term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b);
uint8_t r = (term->sixel.shared_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.shared_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.shared_palette[i] >> 0) & 0xff;
term->sixel.shared_palette[i] = color_decode_srgb(term, r, g, b);
}
}
} else {
@ -1888,8 +1888,8 @@ decgra(struct terminal *term, uint8_t c)
unsigned ph = nparams > 2 ? term->sixel.params[2] : 0;
unsigned pv = nparams > 3 ? term->sixel.params[3] : 0;
pan = pan > 0 ? pan : 1;
pad = pad > 0 ? pad : 1;
pan = pan > 0 ? min(pan, 5) : 1;
pad = pad > 0 ? min(pad, 5) : 1;
if (likely(term->sixel.image.width == 0 &&
term->sixel.image.height == 0))

View file

@ -4086,6 +4086,10 @@ term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disab
xassert(!grid->cursor.lcf);
grid->cursor.point.col = col;
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->vt.codepoint_merging_ok = true;
#endif
}
static void
@ -4126,6 +4130,9 @@ ascii_printer_fast(struct terminal *term, char32_t wc)
xassert(!grid->cursor.lcf);
grid->cursor.point.col = col;
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->vt.codepoint_merging_ok = true;
#endif
if (unlikely(row->extra != NULL)) {
grid_row_uri_range_erase(row, uri_start, uri_start);
@ -4210,7 +4217,11 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc)
{
int width = c32width(wc);
bool insert_mode_disable = false;
const bool grapheme_clustering = term->grapheme_shaping;
const bool grapheme_clustering = term->grapheme_shaping
#if defined(FOOT_GRAPHEME_CLUSTERING)
&& term->vt.codepoint_merging_ok
#endif
;
#if !defined(FOOT_GRAPHEME_CLUSTERING)
xassert(!grapheme_clustering);

View file

@ -264,6 +264,7 @@ struct vt {
char32_t last_printed;
#if defined(FOOT_GRAPHEME_CLUSTERING)
utf8proc_int32_t grapheme_state;
bool codepoint_merging_ok;
#endif
char32_t utf8;
struct {
@ -994,5 +995,6 @@ static inline void term_reset_grapheme_state(struct terminal *term)
{
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->vt.grapheme_state = 0;
term->vt.codepoint_merging_ok = false;
#endif
}

26
themes/ayu-dark Normal file
View file

@ -0,0 +1,26 @@
# -*- conf -*-
# theme: Ayu Dark
# upstream: https://github.com/ayu-theme/ayu-colors/blob/master/themes/dark.yaml
[colors-dark]
background=0d1017
foreground=bfbdb6
cursor=0d1017 ffb454
regular0=0a0000 # black
regular1=d75b63 # red
regular2=94c230 # green
regular3=e79e3a # yellow
regular4=40abe7 # blue
regular5=bc90e7 # magenta
regular6=7ecfb5 # cyan
regular7=ffffff # white
bright0=0a0000 # bright black
bright1=f07178 # bright red
bright2=aad94c # bright green
bright3=ffb454 # bright yellow
bright4=59c2ff # bright blue
bright5=d2a6ff # bright magenta
bright6=95e6cb # bright cyan
bright7=ffffff # bright white

View file

@ -1,23 +1,43 @@
# -*- conf -*-
# Dracula
# Dracula / Alucard
# Source: https://github.com/dracula/foot
[colors-dark]
cursor=282a36 f8f8f2
foreground=f8f8f2
background=282a36
regular0=000000 # black
regular1=ff5555 # red
regular2=50fa7b # green
regular3=f1fa8c # yellow
regular4=bd93f9 # blue
regular5=ff79c6 # magenta
regular6=8be9fd # cyan
regular7=bfbfbf # white
bright0=4d4d4d # bright black
bright1=ff6e67 # bright red
bright2=5af78e # bright green
bright3=f4f99d # bright yellow
bright4=caa9fa # bright blue
bright5=ff92d0 # bright magenta
bright6=9aedfe # bright cyan
bright7=e6e6e6 # bright white
regular0=21222c # black
regular1=ff5555 # red
regular2=50fa7b # green
regular3=f1fa8c # yellow
regular4=bd93f9 # blue
regular5=ff79c6 # magenta
regular6=8be9fd # cyan
regular7=f8f8f2 # white
bright0=6272a4 # bright black
bright1=ff6e6e # bright red
bright2=69ff94 # bright green
bright3=ffffa5 # bright yellow
bright4=d6acff # bright blue
bright5=ff92df # bright magenta
bright6=a4ffff # bright cyan
bright7=ffffff # bright white
[colors-light]
foreground=1f1f1f
background=fffbeb
regular0=fffbeb # white
regular1=cb3a2a # red
regular2=14710a # green
regular3=846e15 # yellow
regular4=644ac9 # blue
regular5=a3144d # magenta
regular6=036a96 # cyan
regular7=1f1f1f # white
bright0=6c664b # bright black
bright1=d74c3d # bright red
bright2=198d0c # bright green
bright3=9e841a # bright yellow
bright4=7862d0 # bright blue
bright5=bf185a # bright magenta
bright6=047fb4 # bright cyan
bright7=2c2b31 # bright white

178
uri.c
View file

@ -195,7 +195,9 @@ uri_parse(const char *uri, size_t len,
encoded_len -= prefix_len;
decoded_len += prefix_len;
if (hex2nibble(next[1]) <= 15 && hex2nibble(next[2]) <= 15) {
if (encoded_len >= 3 &&
hex2nibble(next[1]) <= 15 && hex2nibble(next[2]) <= 15)
{
*p++ = hex2nibble(next[1]) << 4 | hex2nibble(next[2]);
decoded_len++;
encoded_len -= 3;
@ -269,3 +271,177 @@ hostname_is_localhost(const char *hostname)
streq(hostname, "localhost") ||
streq(hostname, this_host)));
}
UNITTEST
{
/* Valid URI, all components */
const char uri[] = "http://john:wick@www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(streq(user, "john")); free(user);
xassert(streq(password, "wick")); free(password);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, all components. Using file scheme, so query+fragment is treated as part of the path */
const char uri[] = "file://john:wick@www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "file")); free(scheme);
xassert(streq(user, "john")); free(user);
xassert(streq(password, "wick")); free(password);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path?query1=abc&query2=def#fragment")); free(path);
xassert(query == NULL);
xassert(fragment == NULL);
}
UNITTEST
{
/* Test a valid URI containing all components, except password */
const char uri[] = "http://john@www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(streq(user, "john")); free(user);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, all components, except user+password */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path?query1=abc&query2=def#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, fragment, no query */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path#fragment";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(query == NULL);
xassert(streq(fragment, "fragment")); free(fragment);
}
UNITTEST
{
/* Valid URI, query, no fragment */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path?query1=abc&query2=def";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path")); free(path);
xassert(streq(query, "query1=abc&query2=def")); free(query);
xassert(fragment == NULL);
}
UNITTEST
{
/* Malformed URI, fragment before query (treated as part of path instead) */
const char uri[] = "http://www.foobar.com:80/this/is/the%20path#fragment?query1=abc&query2=def";
char *scheme, *user, *password, *host, *path, *query, *fragment;
uint16_t port = 0;
uri_parse(
uri, sizeof(uri) - 1, &scheme, &user, &password, &host, &port, &path,
&query, &fragment);
xassert(streq(scheme, "http")); free(scheme);
xassert(user == NULL);
xassert(password == NULL);
xassert(streq(host, "www.foobar.com")); free(host);
xassert(port == 80);
xassert(streq(path, "/this/is/the path#fragment?query1=abc&query2=def")); free(path);
xassert(query == NULL);
xassert(fragment == NULL);
}
UNITTEST
{
/* Malformed URI, trailing '%' */
const char uri[] = "file:///%ABNOT-PART-OF-INPUT";
char *path;
uri_parse(uri, 9, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL);
xassert(streq(path, "/%")); free(path);
}
UNITTEST
{
/* Malformed URI, trailing '%2' */
const char uri[] = "file:///%2ANOT-PART-OF-INPUT";
char *path;
uri_parse(uri, 10, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL);
xassert(streq(path, "/%2")); free(path);
}
UNITTEST
{
/* Malformed URI, trailing '%ag' */
const char uri[] = "file:///%ag";
char *path;
uri_parse(uri, sizeof(uri) - 1, NULL, NULL, NULL, NULL, NULL, &path, NULL, NULL);
xassert(streq(path, "/%ag")); free(path);
}