NeoMutt  2024-03-23-142-g2b2e76
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
browse.c
Go to the documentation of this file.
1
35#include "config.h"
36#include <limits.h>
37#include <stdbool.h>
38#include <stdio.h>
39#include <string.h>
40#include "private.h"
41#include "mutt/lib.h"
42#include "config/lib.h"
43#include "email/lib.h"
44#include "core/lib.h"
45#include "conn/lib.h"
46#include "gui/lib.h"
47#include "mutt.h"
48#include "lib.h"
49#include "browser/lib.h"
50#include "editor/lib.h"
51#include "history/lib.h"
52#include "adata.h"
53#include "mdata.h"
54#include "mutt_logging.h"
55#include "muttlib.h"
56
69static void add_folder(char delim, char *folder, bool noselect, bool noinferiors,
70 struct BrowserState *state, bool isparent)
71{
72 char tmp[PATH_MAX] = { 0 };
73 char relpath[PATH_MAX] = { 0 };
74 struct ConnAccount cac = { { 0 } };
75 char mailbox[1024] = { 0 };
76 struct FolderFile ff = { 0 };
77
78 if (imap_parse_path(state->folder, &cac, mailbox, sizeof(mailbox)))
79 return;
80
81 if (isparent)
82 {
83 /* render superiors as unix-standard ".." */
84 mutt_str_copy(relpath, "../", sizeof(relpath));
85 }
86 else if (mutt_str_startswith(folder, mailbox))
87 {
88 /* strip current folder from target, to render a relative path */
89 mutt_str_copy(relpath, folder + mutt_str_len(mailbox), sizeof(relpath));
90 }
91 else
92 {
93 mutt_str_copy(relpath, folder, sizeof(relpath));
94 }
95
96 /* apply filemask filter. This should really be done at menu setup rather
97 * than at scan, since it's so expensive to scan. But that's big changes
98 * to browser.c */
99 const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
100 if (!mutt_regex_match(c_mask, relpath))
101 {
102 return;
103 }
104
105 imap_qualify_path(tmp, sizeof(tmp), &cac, folder);
106 ff.name = mutt_str_dup(tmp);
107
108 /* mark desc with delim in browser if it can have subfolders */
109 if (!isparent && !noinferiors && (strlen(relpath) < sizeof(relpath) - 1))
110 {
111 relpath[strlen(relpath) + 1] = '\0';
112 relpath[strlen(relpath)] = delim;
113 }
114
115 ff.desc = mutt_str_dup(relpath);
116 ff.imap = true;
117
118 /* delimiter at the root is useless. */
119 if (folder[0] == '\0')
120 delim = '\0';
121 ff.delim = delim;
122 ff.selectable = !noselect;
123 ff.inferiors = !noinferiors;
124
125 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
127 struct MailboxNode *np = NULL;
128 STAILQ_FOREACH(np, &ml, entries)
129 {
130 if (mutt_str_equal(tmp, mailbox_path(np->mailbox)))
131 break;
132 }
133
134 if (np)
135 {
136 ff.has_mailbox = true;
137 ff.has_new_mail = np->mailbox->has_new;
138 ff.msg_count = np->mailbox->msg_count;
139 ff.msg_unread = np->mailbox->msg_unread;
140 }
142
143 ARRAY_ADD(&state->entry, ff);
144}
145
155static int browse_add_list_result(struct ImapAccountData *adata, const char *cmd,
156 struct BrowserState *bstate, bool isparent)
157{
158 struct ImapList list = { 0 };
159 int rc;
160 struct Url *url = url_parse(bstate->folder);
161 if (!url)
162 return -1;
163
164 imap_cmd_start(adata, cmd);
165 adata->cmdresult = &list;
166 do
167 {
168 list.name = NULL;
169 rc = imap_cmd_step(adata);
170
171 if ((rc == IMAP_RES_CONTINUE) && list.name)
172 {
173 /* Let a parent folder never be selectable for navigation */
174 if (isparent)
175 list.noselect = true;
176 /* prune current folder from output */
177 if (isparent || !mutt_str_startswith(url->path, list.name))
178 add_folder(list.delim, list.name, list.noselect, list.noinferiors, bstate, isparent);
179 }
180 } while (rc == IMAP_RES_CONTINUE);
181 adata->cmdresult = NULL;
182
183 url_free(&url);
184
185 return (rc == IMAP_RES_OK) ? 0 : -1;
186}
187
197int imap_browse(const char *path, struct BrowserState *state)
198{
199 struct ImapAccountData *adata = NULL;
200 struct ImapList list = { 0 };
201 struct ConnAccount cac = { { 0 } };
202 char buf[PATH_MAX + 16];
203 char mbox[PATH_MAX] = { 0 };
204 char munged_mbox[PATH_MAX] = { 0 };
205 const char *list_cmd = NULL;
206 int len;
207 int n;
208 char ctmp;
209 bool showparents = false;
210
211 if (imap_parse_path(path, &cac, buf, sizeof(buf)))
212 {
213 mutt_error(_("%s is an invalid IMAP path"), path);
214 return -1;
215 }
216
217 const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
218 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed", false, NULL);
219
220 // Pick first mailbox connected to the same server
221 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
223 struct MailboxNode *np = NULL;
224 STAILQ_FOREACH(np, &ml, entries)
225 {
226 adata = imap_adata_get(np->mailbox);
227 // Pick first mailbox connected on the same server
228 if (imap_account_match(&adata->conn->account, &cac))
229 break;
230 adata = NULL;
231 }
233 if (!adata)
234 goto fail;
235
236 const bool c_imap_list_subscribed = cs_subset_bool(NeoMutt->sub, "imap_list_subscribed");
237 if (c_imap_list_subscribed)
238 {
239 /* RFC3348 section 3 states LSUB is unreliable for hierarchy information.
240 * The newer LIST extensions are designed for this. */
242 list_cmd = "LIST (SUBSCRIBED RECURSIVEMATCH)";
243 else
244 list_cmd = "LSUB";
245 }
246 else
247 {
248 list_cmd = "LIST";
249 }
250
251 mutt_message(_("Getting folder list..."));
252
253 /* skip check for parents when at the root */
254 if (buf[0] == '\0')
255 {
256 mbox[0] = '\0';
257 n = 0;
258 }
259 else
260 {
261 imap_fix_path(adata->delim, buf, mbox, sizeof(mbox));
262 n = mutt_str_len(mbox);
263 }
264
265 if (n)
266 {
267 int rc;
268 mutt_debug(LL_DEBUG3, "mbox: %s\n", mbox);
269
270 /* if our target exists and has inferiors, enter it if we
271 * aren't already going to */
272 imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), mbox);
273 len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
275 snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
276 imap_cmd_start(adata, buf);
277 adata->cmdresult = &list;
278 do
279 {
280 list.name = 0;
281 rc = imap_cmd_step(adata);
282 if ((rc == IMAP_RES_CONTINUE) && list.name)
283 {
284 if (!list.noinferiors && list.name[0] &&
285 (imap_mxcmp(list.name, mbox) == 0) && (n < sizeof(mbox) - 1))
286 {
287 mbox[n++] = list.delim;
288 mbox[n] = '\0';
289 }
290 }
291 } while (rc == IMAP_RES_CONTINUE);
292 adata->cmdresult = NULL;
293
294 /* if we're descending a folder, mark it as current in browser_state */
295 if (mbox[n - 1] == list.delim)
296 {
297 showparents = true;
298 imap_qualify_path(buf, sizeof(buf), &cac, mbox);
299 state->folder = mutt_str_dup(buf);
300 n--;
301 }
302
303 /* Find superiors to list
304 * Note: UW-IMAP servers return folder + delimiter when asked to list
305 * folder + delimiter. Cyrus servers don't. So we ask for folder,
306 * and tack on delimiter ourselves.
307 * Further note: UW-IMAP servers return nothing when asked for
308 * NAMESPACES without delimiters at the end. Argh! */
309 for (n--; n >= 0 && mbox[n] != list.delim; n--)
310 ; // do nothing
311
312 if (n > 0) /* "aaaa/bbbb/" -> "aaaa" */
313 {
314 /* forget the check, it is too delicate (see above). Have we ever
315 * had the parent not exist? */
316 ctmp = mbox[n];
317 mbox[n] = '\0';
318
319 if (showparents)
320 {
321 mutt_debug(LL_DEBUG3, "adding parent %s\n", mbox);
322 add_folder(list.delim, mbox, true, false, state, true);
323 }
324
325 /* if our target isn't a folder, we are in our superior */
326 if (!state->folder)
327 {
328 /* store folder with delimiter */
329 mbox[n++] = ctmp;
330 ctmp = mbox[n];
331 mbox[n] = '\0';
332 imap_qualify_path(buf, sizeof(buf), &cac, mbox);
333 state->folder = mutt_str_dup(buf);
334 }
335 mbox[n] = ctmp;
336 }
337 else
338 {
339 /* "/bbbb/" -> add "/", "aaaa/" -> add "" */
340 char relpath[2] = { 0 };
341 /* folder may be "/" */
342 snprintf(relpath, sizeof(relpath), "%c", (n < 0) ? '\0' : adata->delim);
343 if (showparents)
344 add_folder(adata->delim, relpath, true, false, state, true);
345 if (!state->folder)
346 {
347 imap_qualify_path(buf, sizeof(buf), &cac, relpath);
348 state->folder = mutt_str_dup(buf);
349 }
350 }
351 }
352
353 /* no namespace, no folder: set folder to host only */
354 if (!state->folder)
355 {
356 imap_qualify_path(buf, sizeof(buf), &cac, NULL);
357 state->folder = mutt_str_dup(buf);
358 }
359
360 mutt_debug(LL_DEBUG3, "Quoting mailbox scan: %s -> ", mbox);
361 snprintf(buf, sizeof(buf), "%s%%", mbox);
362 imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), buf);
363 mutt_debug(LL_DEBUG3, "%s\n", munged_mbox);
364 len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
366 snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
367 if (browse_add_list_result(adata, buf, state, false))
368 goto fail;
369
370 if (ARRAY_EMPTY(&state->entry))
371 {
372 // L10N: (%s) is the name / path of the folder we were trying to browse
373 mutt_error(_("No such folder: %s"), path);
374 goto fail;
375 }
376
378
379 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
380 c_imap_check_subscribed, NULL);
381 return 0;
382
383fail:
384 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
385 c_imap_check_subscribed, NULL);
386 return -1;
387}
388
397int imap_mailbox_create(const char *path)
398{
399 struct ImapAccountData *adata = NULL;
400 struct ImapMboxData *mdata = NULL;
401 struct Buffer *name = buf_pool_get();
402 int rc = -1;
403
404 if (imap_adata_find(path, &adata, &mdata) < 0)
405 {
406 mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path);
407 goto done;
408 }
409
410 /* append a delimiter if necessary */
411 const size_t n = buf_strcpy(name, mdata->real_name);
412 if ((n != 0) && (name->data[n - 1] != adata->delim))
413 {
414 buf_addch(name, adata->delim);
415 }
416
417 struct FileCompletionData cdata = { false, NULL, NULL, NULL };
418 if (mw_get_field(_("Create mailbox: "), name, MUTT_COMP_NO_FLAGS, HC_MAILBOX,
419 &CompleteMailboxOps, &cdata) != 0)
420 {
421 goto done;
422 }
423
424 if (buf_is_empty(name))
425 {
426 mutt_error(_("Mailbox must have a name"));
427 goto done;
428 }
429
430 if (imap_create_mailbox(adata, buf_string(name)) < 0)
431 goto done;
432
433 imap_mdata_free((void *) &mdata);
434 mutt_message(_("Mailbox created"));
435 mutt_sleep(0);
436 rc = 0;
437
438done:
439 imap_mdata_free((void *) &mdata);
440 buf_pool_release(&name);
441 return rc;
442}
443
452int imap_mailbox_rename(const char *path)
453{
454 struct ImapAccountData *adata = NULL;
455 struct ImapMboxData *mdata = NULL;
456 struct Buffer *buf = NULL;
457 struct Buffer *newname = NULL;
458 int rc = -1;
459
460 if (imap_adata_find(path, &adata, &mdata) < 0)
461 {
462 mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path);
463 goto done;
464 }
465
466 if (mdata->real_name[0] == '\0')
467 {
468 mutt_error(_("Can't rename root folder"));
469 goto done;
470 }
471
472 buf = buf_pool_get();
473 newname = buf_pool_get();
474
475 buf_printf(buf, _("Rename mailbox %s to: "), mdata->name);
476 buf_strcpy(newname, mdata->name);
477
478 struct FileCompletionData cdata = { false, NULL, NULL, NULL };
480 &CompleteMailboxOps, &cdata) != 0)
481 {
482 goto done;
483 }
484
485 if (buf_is_empty(newname))
486 {
487 mutt_error(_("Mailbox must have a name"));
488 goto done;
489 }
490
491 imap_fix_path(adata->delim, buf_string(newname), buf->data, buf->dsize);
492
493 if (imap_rename_mailbox(adata, mdata->name, buf_string(buf)) < 0)
494 {
495 mutt_error(_("Rename failed: %s"), imap_get_qualifier(adata->buf));
496 goto done;
497 }
498
499 mutt_message(_("Mailbox renamed"));
500 mutt_sleep(0);
501 rc = 0;
502
503done:
504 imap_mdata_free((void *) &mdata);
505 buf_pool_release(&buf);
506 buf_pool_release(&newname);
507
508 return rc;
509}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:156
#define ARRAY_EMPTY(head)
Check if an array is empty.
Definition: array.h:74
const struct CompleteOps CompleteMailboxOps
Auto-Completion of Files / Mailboxes.
Definition: complete.c:162
Select a Mailbox from a list.
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:290
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:394
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
const struct Regex * cs_subset_regex(const struct ConfigSubset *sub, const char *name)
Get a regex config item by name.
Definition: helpers.c:218
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
Convenience wrapper for the config headers.
Connection Library.
Convenience wrapper for the core headers.
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:223
@ MUTT_IMAP
'IMAP' Mailbox type
Definition: mailbox.h:50
@ MUTT_MAILBOX_ANY
Match any Mailbox type.
Definition: mailbox.h:42
Edit a string.
Structs that make up an email.
int mw_get_field(const char *prompt, struct Buffer *buf, CompletionFlags complete, enum HistoryClass hclass, const struct CompleteOps *comp_api, void *cdata)
Ask the user for a string -.
Definition: window.c:274
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
void imap_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free() -.
Definition: mdata.c:39
Convenience wrapper for the gui headers.
Read/write command history from/to a file.
@ HC_MAILBOX
Mailboxes.
Definition: lib.h:57
struct ImapAccountData * imap_adata_get(struct Mailbox *m)
Get the Account data for this mailbox.
Definition: adata.c:123
int imap_browse(const char *path, struct BrowserState *state)
IMAP hook into the folder browser.
Definition: browse.c:197
static int browse_add_list_result(struct ImapAccountData *adata, const char *cmd, struct BrowserState *bstate, bool isparent)
Add entries to the folder browser.
Definition: browse.c:155
static void add_folder(char delim, char *folder, bool noselect, bool noinferiors, struct BrowserState *state, bool isparent)
Format and add an IMAP folder to the browser.
Definition: browse.c:69
int imap_mailbox_create(const char *path)
Create a new IMAP mailbox.
Definition: browse.c:397
int imap_mailbox_rename(const char *path)
Rename a mailbox.
Definition: browse.c:452
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
Given an IMAP command, send it to the server.
Definition: command.c:1118
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition: command.c:1132
int imap_parse_path(const char *path, struct ConnAccount *cac, char *mailbox, size_t mailboxlen)
Parse an IMAP mailbox name into ConnAccount, name.
Definition: util.c:474
int imap_mxcmp(const char *mx1, const char *mx2)
Compare mailbox names, giving priority to INBOX.
Definition: util.c:545
void imap_qualify_path(char *buf, size_t buflen, struct ConnAccount *conn_account, char *path)
Make an absolute IMAP folder target.
Definition: util.c:816
#define IMAP_RES_OK
<tag> OK ...
Definition: private.h:55
char * imap_fix_path(char delim, const char *mailbox, char *path, size_t plen)
Fix up the imap path.
Definition: util.c:679
#define IMAP_CAP_LIST_EXTENDED
RFC5258: IMAP4 LIST Command Extensions.
Definition: private.h:138
int imap_adata_find(const char *path, struct ImapAccountData **adata, struct ImapMboxData **mdata)
Find the Account data for this path.
Definition: util.c:73
bool imap_account_match(const struct ConnAccount *a1, const struct ConnAccount *a2)
Compare two Accounts.
Definition: util.c:1054
void imap_munge_mbox_name(bool unicode, char *dest, size_t dlen, const char *src)
Quote awkward characters in a mailbox name.
Definition: util.c:921
#define IMAP_RES_CONTINUE
* ...
Definition: private.h:56
char * imap_get_qualifier(char *buf)
Get the qualifier from a tagged response.
Definition: util.c:768
int imap_rename_mailbox(struct ImapAccountData *adata, char *oldname, const char *newname)
Rename a mailbox.
Definition: imap.c:479
int imap_create_mailbox(struct ImapAccountData *adata, const char *mailbox)
Create a new mailbox.
Definition: imap.c:437
@ LL_DEBUG3
Log at debug level 3.
Definition: logging2.h:45
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
bool mutt_regex_match(const struct Regex *regex, const char *str)
Shorthand to mutt_regex_capture()
Definition: regex.c:614
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:709
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:230
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:545
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:630
Many unsorted constants and some structs.
#define MUTT_COMP_NO_FLAGS
No flags are set.
Definition: mutt.h:56
#define PATH_MAX
Definition: mutt.h:42
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:74
NeoMutt Logging.
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:878
Some miscellaneous functions.
void neomutt_mailboxlist_clear(struct MailboxList *ml)
Free a Mailbox List.
Definition: neomutt.c:163
size_t neomutt_mailboxlist_get_all(struct MailboxList *head, struct NeoMutt *n, enum MailboxType type)
Get a List of all Mailboxes.
Definition: neomutt.c:186
Notmuch-specific Mailbox data.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
Pop-specific Account data.
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
GUI display the mailboxes in a side panel.
Key value store.
void * adata
Private data (for Mailbox backends)
Definition: account.h:42
State of the file/mailbox browser.
Definition: lib.h:144
char * folder
Folder name.
Definition: lib.h:147
struct BrowserEntryArray entry
Array of files / dirs / mailboxes.
Definition: lib.h:145
String manipulation buffer.
Definition: buffer.h:36
size_t dsize
Length of data.
Definition: buffer.h:39
char * data
Pointer to data.
Definition: buffer.h:37
Login details for a remote server.
Definition: connaccount.h:53
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:49
Input for the file completion function.
Definition: curs_lib.h:40
Browser entry representing a folder/dir.
Definition: lib.h:78
bool selectable
Folder can be selected.
Definition: lib.h:96
char delim
Path delimiter.
Definition: lib.h:93
bool imap
This is an IMAP folder.
Definition: lib.h:95
bool has_mailbox
This is a mailbox.
Definition: lib.h:98
char * name
Name of file/dir/mailbox.
Definition: lib.h:86
bool has_new_mail
true if mailbox has "new mail"
Definition: lib.h:89
char * desc
Description of mailbox.
Definition: lib.h:87
int msg_count
total number of messages
Definition: lib.h:90
bool inferiors
Folder has children.
Definition: lib.h:97
int msg_unread
number of unread messages
Definition: lib.h:91
IMAP-specific Account data -.
Definition: adata.h:40
char delim
Path delimiter.
Definition: adata.h:75
struct ImapList * cmdresult
Definition: adata.h:66
bool unicode
If true, we can send UTF-8, and the server will use UTF8 rather than mUTF7.
Definition: adata.h:62
ImapCapFlags capabilities
Capability flags.
Definition: adata.h:55
char * buf
Definition: adata.h:59
struct Connection * conn
Connection to IMAP server.
Definition: adata.h:41
Items in an IMAP browser.
Definition: private.h:149
bool noselect
Definition: private.h:152
bool noinferiors
Definition: private.h:153
char * name
Definition: private.h:150
char delim
Definition: private.h:151
IMAP-specific Mailbox data -.
Definition: mdata.h:40
char * real_name
Original Mailbox name, e.g.: INBOX can be just \0.
Definition: mdata.h:43
char * name
Mailbox name.
Definition: mdata.h:41
List of Mailboxes.
Definition: mailbox.h:166
struct Mailbox * mailbox
Mailbox in the list.
Definition: mailbox.h:167
bool has_new
Mailbox has new mail.
Definition: mailbox.h:85
int msg_count
Total number of messages.
Definition: mailbox.h:88
void * mdata
Driver specific data.
Definition: mailbox.h:132
int msg_unread
Number of unread messages.
Definition: mailbox.h:89
Container for Accounts, Notifications.
Definition: neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
Cached regular expression.
Definition: regex3.h:85
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:69
char * path
Path.
Definition: url.h:75
int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition: subset.c:297
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:239
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:124