File parsing and data management
The code sure must be streamlined. I didn't find any better way to review but to rearrange the code and put my notes as comments.

    // First, you test the character against the same condition twice.
    // A more direct way would be to do it once

    if (c == '#' || c == '[') {
        while (fgetc(f) != '\n')
            ;
        continue;
    }
    if (c == ' ' || c == '\n')
        continue;

    // Notice that here you still have `c` intact (so the comment about
    // NOW is misleading). Time to initialize the `url`:

    u = new_url();

    // Since you have the first character of the `u-&gt;path`, a correct
    // idiom is a `do {} while ();` loop:

    do {
        string_push_char(u-&gt;path, c);
        c = fgetc(f);
    } while(c != '\n' &amp;&amp; c != '[');

    // Here `c` is the reason of loop termination. Test it and proceed accordingly:

    if (c == '[') {
        while ((c = fgetc(f)) != ']') {
            string_push_char(u-&gt;title, c);
        }
        while(fgetc(f) != '\n')
            ;
    } else {
        string_copy(u-&gt;title, u-&gt;path);
    }


This almost it. The code parses the well-formed file, but will fall into an infinite loop if, for example, the title-closing bracket is missing.

As a side note, isspace(c) is strongly preferred to a manual testing.