Check-in by ben on 2025-11-09 08:04:54

 Scripts in the gophernicus CGI directory need to output network
 style EOL. Change \n to \n\r. Prefer printf over print. Add
 info() and item() convenience functions and use them. Fix
 parameter handling in wizard/step2

 INSERTED    DELETED
       29         31 src/account.m4
       17         17 src/cgi.awk
       64         62 src/details.m4
       15         12 src/download.m4
       24         24 src/list.m4
       19         17 src/lists.m4
       10          7 src/listsort.m4
       32         28 src/search.m4
        3          3 src/sort.m4
        1          1 src/sqlite.awk
       39         16 src/util.awk
       32         25 src/web.awk
       12          9 src/wizard/step1.m4
       15         11 src/wizard/step2.m4
       14          9 src/wizard/step3.m4
      326        272 TOTAL over 15 changed files

Index: src/account.m4
==================================================================
--- src/account.m4
+++ src/account.m4
@@ -12,12 +12,12 @@
incl(src/sqlite.awk)
incl(src/util.awk)
incl(src/web.awk)

function main(     acct, cmd, col, cols, count, descr, dir, email,
-     iaout, id, item_server, item_size, output, prefix, signature,
-     str, thumb, title, type, url)
+    iaout, id, item_server, item_size, out, sel, signature, str,
+    thumb, title, type, url)
{
    count = split(search, parts, "/")
    acct = parts[1]
    email = parts[2]

@@ -26,11 +26,11 @@
    if (length(str) > 0) {
        print str
        return
    }

-    output = cache_begin()
+    out = cache_begin()
    iaout = gettemp()

    url = api_endpoint "/metadata/" acct
    api_request(url, "GET", iaout)

@@ -75,48 +75,46 @@
        }
    }
    close(cmd)

    if (length(id) == 0) {
-        print_not_found(output, url)
+        print_not_found(out, url)
        cache_end()
        unlink(iaout)
        return
    }

-    print "Account: " acct >>output
+    info(out, "Account: " acct)
    if (length(thumb) > 0) {
        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
-        printf "IThumbnail\t%s/raw/%s\t%s\t%s\n", cgipath, url,
-            server, port >>output
+        sel = cgipath "/raw/" url
+        item(out, "I", "Thumbnail", sel, server, port)
    }
-    print_html(output, descr)
+    print_html(out, descr)

    if (length(email) > 0) {
-        prefix = "/sortaddeddate desc"
-        printf "1Uploads\t%s\tsearch?uploader:%s%s\t%s\t%s\n", cgipath,
-            email, prefix, server, port >>output
-    }
-
-    prefix = "/sortaddeddate desc"
-    printf "1Items\t%s/search?anyfield:%s%s\t%s\t%s\n", cgipath,
-        acct, prefix, server, port >>output
-
-    printf "1Lists\t%s/lists?%s\t%s\t%s\n", cgipath, acct, server,
-        port >>output
-
-    print "" >>output
-    printf "%-20s %s\n", "Identifier:", id >>output
-    if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size >>output
-    }
-    printf "%-20s %s\n", "Media Type:", type >>output
-
-    print "" >>output
-    printf "hAccount web page\tURL:%s/details?%s\t%s\t%s\n",
-        api_ssl_endpoint, uri_encode(id), server, port >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+        sel = cgipath "/search?uploader:" email "/sortaddeddate desc"
+        item(out, "1", "Uploads", sel, server, port)
+    }
+
+    sel = cgipath "/search?anyfield:" acct "/sortaddeddate desc"
+    item(out, "1", "Items", sel, server, port)
+
+    sel = cgipath "/lists?" acct
+    item(out, "1", "Lists", sel, server, port)
+
+    info(out, "")
+    info(out, sprintf("%-20s %s\r\n", "Identifier:", id))
+    if (item_size > 0) {
+        info(out, sprintf("%-20s %d\r\n", "Item Size:", item_size))
+    }
+    info(out, sprintf("%-20s %s\r\n", "Media Type:", type))
+
+    info(out, "")
+    sel = sprintf("URL:%s/details?%s", api_ssl_endpoint, uri_encode(id))
+    item(out, "h", "Account web page", sel, server, port)
+    item(out, "1", "PHAROS", docpath, server, port)

    cache_end()
    unlink(iaout)
    exit 0
}

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -1,21 +1,21 @@
function block_msg(ip, extra) {
-    print "Access Denied"
-    print "============="
-    print ""
-    print "This service only allows access from vetted client IP addresses."
-    print "The intent is to thwart abusive bots and crawlers."
-    print ""
-    print "To gain access:"
-    print ""
-    print "* Email your client IP address (" ip ") to:"
-    print "    " contact
-    print ""
-    print "* Include the word \"friendly\" in your message."
-    print ""
-    print "Service admin will reply when granted."
-    print extra
+    printf "Access Denied\r\n"
+    printf "=============\r\n"
+    printf "\r\n"
+    printf "This service only allows access from vetted client IP addresses.\r\n"
+    printf "The intent is to thwart abusive bots and crawlers.\r\n"
+    printf "\r\n"
+    printf "To gain access:\r\n"
+    printf "\r\n"
+    printf "* Email your client IP address (" ip ") to:\r\n"
+    printf "    %s\r\n", contact
+    printf "\r\n"
+    printf "* Include the word \"friendly\" in your message.\r\n"
+    printf "\r\n"
+    printf "Service admin will reply when granted.\r\n"
+    printf "%s\r\n", extra
    return
}

function cgi_init(     extra, ip, item) {
    ip = ENVIRON["REMOTE_ADDR"]
@@ -65,12 +65,12 @@
        }
        close(pass_list)
        if (blocked) {
            extra = ""
            if (topdir == "details") {
-                extra = "\nIn the meanwhile, see:\n\n" \
-                    sprintf("hWeb page\tURL:%s/details/%s\t%s\t%s\n",
+                extra = "\r\nIn the meanwhile, see:\r\n\r\n" \
+                    sprintf("hWeb page\tURL:%s/details/%s\t%s\t%s\r\n",
                    api_ssl_endpoint,
                    uri_encode(search),
                    server,
                    port)
            }

Index: src/details.m4
==================================================================
--- src/details.m4
+++ src/details.m4
@@ -13,11 +13,11 @@
incl(src/util.awk)
incl(src/web.awk)

function main(     add_date, col, cols, cmd, creator, descr, dir, i,
    iaout, id, item_id, item_server, item_size, label, language,
-    license, output, pub_date, scanner, signature, str, thumb,
+    license, out, pub_date, scanner, sel, signature, str, thumb,
    title, topic, topics, type, uploader_account, uploader_email,
    uploader_name, url)
{
    item_id = search

@@ -27,11 +27,11 @@
        print str
        return
    }

    iaout = gettemp()
-    output = cache_begin()
+    out = cache_begin()

    url = api_endpoint "/metadata/" item_id
    api_request(url, "GET", iaout)

    # format search results as a gopher directory (menu)
@@ -112,70 +112,71 @@
        }
    }
    close(cmd)

    if (length(id) == 0) {
-        print_not_found(output, url)
+        print_not_found(out, url)
        cache_end()
        unlink(iaout)
        return
    }

-    print shorten(title, 70) >>output
+    info(out, shorten(title, 70))
    if (creators == 1) {
        label = "by " shorten(creator[1], 70)
-        printf "1%s\t%ssearch?creator:(%s)\t%s\t%s\n", label,
-            cgipath, creator[1], server, port >>output
+        sel = sprintf("%s/search?creator:(%s)", cgipath, creator[1])
+        item(out, "1", label, sel, server, port)
    } else if (creators > 1) {
-        printf "\nby:\n" >>output
+        info(out, "")
+        info(out, "by:")
        for (i = 1; i <= creators; i++) {
            label = shorten(creator[i], 70)
-            printf "1%s\t%s/search?creator:(%s)\t%s\t%s\n", label,
-                cgipath, creator[i], server, port >>output
+            sel = sprintf("%s/search?creator:(%s)", cgipath, creator[i])
+            item(out, "1", label, sel, server, port)
        }
-        printf "\n" >>output
+        info(out, "")
    }
    if (length(thumb) > 0) {
        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
-        printf "IThumbnail\t%s/raw?%s\t%s\t%s\n", cgipath, url,
-            server, port >>output
+        sel = cgipath "/raw?" url
+        item(out, "I", "Thumbnail", sel, server, port)
    }

-    printf "1Download\t%s/download?%s\t%s\t%s\n", cgipath, item_id,
-        server, port >>output
-    printf "hWeb page\tURL:%s/details/%s\t%s\t%s\n",
-        api_ssl_endpoint, uri_encode(id), server, port >>output
-    print "" >>output
-
-    print_html(output, descr)
-
-    print "" >>output
+    sel = cgipath "/download?" item_id
+    item(out, "1", "Download", sel, server, port)
+    sel = "URL:" api_ssl_endpoint "/details/" uri_encode(id)
+    item(out, "h", "Web page", sel, server, port)
+    info(out, "")
+
+    print_html(out, descr)
+
+    info(out, "")
    if (length(add_date) > 0) {
-        printf "%-20s %s\n", "Date Added:", add_date >>output
+        info(out, sprintf("%-20s %s", "Date Added:", add_date))
    }
    if (pub_date != add_date) {
-        printf "%-20s %s\n", "Date Published:", pub_date >>output
+        info(out, sprintf("%-20s %s", "Date Published:", pub_date))
    }
-    printf "%-20s %s\n", "Identifier:", id >>output
+    info(out, sprintf("%-20s %s", "Identifier:", id))
    if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size >>output
+        info(out, sprintf("%-20s %d", "Item Size:", item_size))
    }
    if (length(language) > 0) {
-        printf "%-20s %s\n", "Language:", language >>output
+        info(out, sprintf("%-20s %s", "Language:", language))
    }
    if (length(license) > 0) {
-        printf "%-20s %s\n", "License:", license >>output
+        info(out, sprintf("%-20s %s", "License:", license))
    }
-    printf "%-20s %s\n", "Media Type:", type >>output
+    info(out, sprintf("%-20s %s", "Media Type:", type))

    if (topics > 0) {
-        print "" >>output
-        print "# Topics" >>output
+        info(out, "")
+        info(out, "# Topics")
        for (i = 1; i <= topics; i++) {
            label = shorten(topic[i], 40)
-            printf "1%s\t%s/search?subject:(%s)\t%s\t%s\n", label,
-                cgipath, topic[i], server, port >>output
+            sel = sprintf("%s/search?subject:(%s)", cgipath, topic[i])
+            item(out, "1", label, sel, server, port)
        }
    }

    # scrape uploader name from item web page HTML
    url = api_ssl_endpoint "/details/" item_id
@@ -190,40 +191,41 @@
        }
    }
    close(iaout)

    if (cols > 0) {
-        print "" >>output
-        print "# Collections" >>output
-        for (i = 1; i <= cols; i++) {
-            label = shorten(col[i], 40)
-            printf "1%s\t%s/search?collection:(%s)\t%s\t%s\n",
-                label, cgipath, col[i], server, port >>output
-        }
-    }
-
-    print "" >>output
-    print "# Uploaded by" >>output
-    if (length(uploader_account) > 0) {
-        label = shorten(uploader_account, 70)
-        printf "1%s\t%s/account?%s/%s\t%s\t%s\n", label, cgipath,
-            uploader_account, uploader_email, server, port >>output
-    } else if (length(uploader_email) > 0) {
-        label = shorten(uploader_email, 70)
-        printf "1%s\t%s/search?uploader:%s\t%s\t%s\n", label,
-            cgipath, uploader_email, server, port >>output
-    } else {
-        printf "%s\n", uploader_name >>output
-    }
-
-    print "" >>output
-    print "# Similar items" >>output
-    printf "1View similar items\t%s/search?similar:%s\t%s\t%s\n",
-        cgipath, item_id, server, port >>output
-
-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+        info(out, "")
+        info(out, "# Collections")
+        for (i = 1; i <= cols; i++) {
+            label = shorten(col[i], 40)
+            sel = sprintf("%s/search?collection:(%s)", cgipath, col[i])
+            item(out, "1", label, sel, server, port)
+        }
+    }
+
+    info(out, "")
+    info(out, "# Uploaded by")
+    if (length(uploader_account) > 0) {
+        label = shorten(uploader_account, 70)
+        sel = sprintf("%s/account?%s/%s", cgipath, uploader_account,
+            uploader_email)
+        item(out, "1", label, sel, server, port)
+    } else if (length(uploader_email) > 0) {
+        label = shorten(uploader_email, 70)
+        sel = cgipath "/search?uploader:" uploader_email
+        item(out, "1", label, sel, server, port)
+    } else {
+        info(out, uploader_name)
+    }
+
+    info(out, "")
+    info(out, "# Similar items")
+    sel = cgipath "/search?similar:" item_id
+    item(out, "1", "View similar items", sel, server, port)
+
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)

    cache_end()
    unlink(iaout)
    return
}

Index: src/download.m4
==================================================================
--- src/download.m4
+++ src/download.m4
@@ -9,12 +9,15 @@
incl(src/api.awk)
incl(src/cgi.awk)
incl(src/util.awk)

function main(     cmd, files, file_size, format, iaout, is_archive,
-      is_proxy, item_server, label, mtime, name, source, url)
+      is_proxy, item_server, label, mtime, name, out, sel, source,
+      url)
{
+    out = ""
+
    if (topdir == "download") {
        is_proxy = 1
    } else {
        # topdir == "direct"
        is_proxy = 0
@@ -64,32 +67,32 @@
            strftime("%Y-%m-%d %H:%M", mtime[i]),
            human_size(file_size[i]))
        url = sprintf("http://%s%s/%s", item_server, dir, name[i])
        if (is_proxy) {
            if (max_bin_size > 0 && file_size[i] > max_bin_size * size_mb) {
-                printf "h%s\tURL:%s\t%s\t%s\n", label, uri_encode(url),
-                    server, port
+                sel = "URL:" uri_encode(url)
+                item(out, "h", label, sel, server, port)
            } else {
-                printf "1%s\t%s/links?%s\t%s\t%s\n", label, cgipath,
-                    url, server, port
+                sel = cgipath "/links?" url
+                item(out, "1", label, sel, server, port)
            }
        } else {
-            printf "h%s\tURL:%s\t%s\t%s\n", label, uri_encode(url),
-                server, port
+            sel = "URL:" uri_encode(url)
+            item(out, "h", label, sel, server, port)
        }
        is_archive = detect_archive(url)
        if (is_archive) {
            url = sprintf("http://%s/view_archive.php?archive=%s/%s",
                item_server, dir, name[i])
-            printf "h%s (View Contents)\tURL:%s\t%s\t%s\n",
-                shorten_left(name[i], 40), uri_encode(url),
-                server, port
+            label = shorten_left(name[i], 40) " (View Contents)"
+            sel = "URL:" uri_encode(url)
+            item(out, "h", label, sel, server, port)
        }
    }

-    printf "1Downloads via http\t%s/direct?%s\t%s\t%s\n", cgipath,
-        search, server, port
+    sel = cgipath "/direct?" search
+    item(out, "1", "Downloads via http", sel, server, port)

    unlink(iaout)
    exit 0
}


Index: src/list.m4
==================================================================
--- src/list.m4
+++ src/list.m4
@@ -12,12 +12,12 @@
incl(src/sqlite.awk)
incl(src/util.awk)

function main(     acct, client_url, cmd, count, creator, iaout, id,
    is_private, items, label, list_id, name, name_slug, numfound,
-    order, order_name, order_names, order_param, output, page, pages,
-    prefix, rows, sort_param, signature, str, title, type, url,
+    order, order_name, order_names, order_param, out, page, pages,
+    rows, sel, sort_param, signature, str, title, type, url,
    user_query)
{
    order_names["creator"] = "creatorSorter"
    order_names["date"] = "date"
    order_names["title"] = "titleSorter"
@@ -57,11 +57,11 @@
    if (length(str) > 0) {
        print str
        return
    }

-    output = cache_begin()
+    out = cache_begin()
    iaout = gettemp()

    url = api_ssl_endpoint "/services/users/" acct "/lists/" list_id
    api_request(url, "GET", iaout)

@@ -131,14 +131,14 @@
        pages++
    }

    # format as a gopher directory (menu)

-    print acct "'s Lists" >>output
-    print "" >>output
-    printf "# List: %s, page %d of %d\n", name, page, pages >>output
-    print "" >>output
+    info(out, acct "'s Lists")
+    info(out, "")
+    info(out, sprintf("# List: %s, page %d of %d", name, page, pages))
+    info(out, "")

    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
    FS = "\t"
    count = 0
    creator = ""
@@ -170,12 +170,12 @@
                        shorten(title, 40), shorten(creator, 18))
                } else {
                    label = sprintf("[%s] %s", mediatype[type],
                        shorten(title, 58))
                }
-                printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath, id,
-                    server, port >>output
+                sel = cgipath "/details?" id
+                item(out, "1", label, sel, server, port)
                count++
            }
            creator = ""
            descr = ""
            id = ""
@@ -182,39 +182,39 @@
            type = ""
        }
    }
    close(cmd)

-    print "" >>output
+    info(out, "")

    # only show "page back" if the user is past page 1
    if (page > 1) {
-        prefix = sprintf("/page%d/rows%d/%s", page - 1, rows, sort_param)
-        printf "1[<<] Page %d\t%s/list?%s/%d%s\t%s\t%s\n",
-            page - 1, cgipath, acct, list_id, prefix, server,
-            port >>output
+        label = sprintf("[<<] Page %d", page - 1)
+        sel = sprintf("%s/list?%s/%d/page%d/rows%d/%s", cgipath, acct,
+            list_id, page - 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
    }

    # only show "next page" if the current page is completely full
    if (count == rows) {
-        prefix = sprintf("/page%d/rows%d/%s", page + 1, rows, sort_param)
-        printf "1[>>] Page %d\t%s/list?%s/%d%s\t%s\t%s\n",
-            page + 1, cgipath, acct, list_id, prefix, server,
-            port >>output
+        label = sprintf("[>>] Page %d", page + 1)
+        sel = sprintf("%s/list?%s/%d/page%d/rows%d/%s", cgipath,
+            acct, list_id, page + 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
    }

    # only show "sort" if there's more than one item to sort
    if (numfound > 1) {
-        printf "1[^v] Sort\t%s/listsort?%s/%d\t%s\t%s\n", cgipath,
-            acct, list_id, server, port >>output
+        sel = sprintf("%s/listsort?%s/%d", cgipath, acct, list_id)
+        item(out, "1", "[^v] Sort", sel, server, port)
    }

-    printf "1Account %s\t%s/account?%s\t%s\t%s\n", acct, cgipath,
-        acct, server, port >>output
+    sel = cgipath "/account?" acct
+    item(out, "1", "Account " acct, sel, server, port)

-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)

    cache_end()
    unlink(iaout)
    exit 0
}

Index: src/lists.m4
==================================================================
--- src/lists.m4
+++ src/lists.m4
@@ -10,22 +10,22 @@
incl(src/cache.awk)
incl(src/cgi.awk)
incl(src/sqlite.awk)
incl(src/util.awk)

-function main(     cmd, count, fields, iaout, i, id, is_private, item,
-    item_count, item_id, label, name, output, record, records,
-    signature, str, url)
+function main(     cmd, count, fields, iaout, i, id, is_private,
+    item, item_count, item_id, label, labelstr, name, out, record,
+    records, sel, signature, str, url)
{
    signature = sprintf("%s/lists", search)
    str = cache_init(signature)
    if (length(str) > 0) {
        print str
        return
    }

-    output = cache_begin()
+    out = cache_begin()
    iaout = gettemp()

    url = api_ssl_endpoint "/services/users/" search "/lists"
    api_request(url, "GET", iaout)

@@ -45,14 +45,15 @@

    while ((cmd | getline) > 0) {
        if ($1 == ".value[]" && $2 == "o") {
            # add information for previous list
            if (!is_private && length(name) > 0 && item_count > 0) {
-                label = shorten_left(name, 50)
-                item = sprintf("1%4d Items: %-50s\t%s/list?%s/%d\t%s\t%s",
-                    item_count, label, cgipath, search, id, server, port)
-                record = label "\v" id "\v" item
+                labelstr = shorten_left(name, 50)
+                label = sprintf("%4d Items: %-50s", item_count, labelstr)
+                sel = sprintf("%s/list?%s/%d", cgipath, search, id)
+                item = item_str("1", label, sel, server, port)
+                record = labelstr "\v" id "\v" item
                count++
                records[count] = record
            }
        } else if ($1 == ".value[].list_name" && $2 == "s") {
            name = $3
@@ -71,35 +72,36 @@
    }
    close(cmd)

    # add information for previous list
    if (!is_private && length(name) > 0 && item_count > 0) {
-        label = shorten_left(name, 50)
-        item = sprintf("1%4d Items: %-50s\t%s/list?%s/%d\t%s\t%s",
-            item_count, label, cgipath, search, id, server, port)
-        record = label "\v" id "\v" item
+        labelstr = shorten_left(name, 50)
+        label = sprintf("%4d Items: %-50s", item_count, labelstr)
+        sel = sprintf("%s/list?%s/%d", cgipath, search, id)
+        item = item_str("1", label, sel, server, port)
+        record = labelstr "\v" id "\v" item
        count++
        records[count] = record
    }

    # sort lists by label and id
    if (count > 0) {
        hsort(records, count)
    }

-    print search "'s Lists" >>output
-    print "" >>output
+    info(out, search "'s Lists")
+    info(out, "")

    for (i = 1; i <= count; i++) {
        record = records[i]
        split(record, fields, /\v/)
        item = fields[3]
-        print item >>output
+        printf "%s\r\n", item >>out
    }

-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)

    cache_end()
    unlink(iaout)
    return
}

Index: src/listsort.m4
==================================================================
--- src/listsort.m4
+++ src/listsort.m4
@@ -5,12 +5,15 @@
#
# Change list sort order

include(src/config.awk)
incl(src/cgi.awk)
+incl(src/util.awk)

-function main(     acct, i, lbl, list_id, opt) {
+function main(     acct, i, lbl, list_id, opt, out, sel) {
+    out = ""
+
    lbl[1] = "Relevance"
    opt[1] = ""
    lbl[2] = "Weekly views [^]"
    opt[2] = "week asc"
    lbl[3] = "Weekly views [v]"
@@ -30,19 +33,19 @@

    split(search, parts, "/")
    acct = parts[1]
    list_id = parts[2]

-    print "# Sort by"
-    print ""
+    info(out, "# Sort by")
    for (i = 1; i < 10; i++) {
        if (length(opt[i]) == 0) {
-            printf "1%s\t%s/list?%s/%d\t%s\t%s\n", lbl[i], cgipath,
-                acct, list_id, server, port
+            sel = sprintf("%s/list?%s/%d", cgipath, acct, list_id)
+            item(out, "1", lbl[i], sel, server, port)
        } else {
-            printf "1%s\t%s/list?%s/%d/sort%s\t%s\t%s\n", lbl[i],
-                cgipath, acct, list_id, opt[i], server, port
+            sel = sprintf("%s/list?%s/%d/sort%s", cgipath,
+                acct, list_id, opt[i])
+            item(out, "1", lbl[i], sel, server, port)
        }
    }
    exit 0
}


Index: src/search.m4
==================================================================
--- src/search.m4
+++ src/search.m4
@@ -8,15 +8,17 @@
include(src/config.awk)
incl(src/api.awk)
incl(src/cgi.awk)
incl(src/util.awk)

-function main(search,     cmd, count, creator, descr, field, fields, i,
-    iaout, id, item, items, jsout, label, numfound, order,
-    order_names, page, prefix, rows, searchstr, sort_param, str,
+function main(search,     cmd, count, creator, descr, field, fields,
+    i, iaout, id, item, items, jsout, label, numfound, order,
+    order_names, out, page, rows, searchstr, sel, sort_param, str,
    title, type, url)
{
+    out = ""
+
    order_names["addeddate"] = "addeddate"
    order_names["collection_size"] = "collection_size"
    order_names["createddate"] = "createddate"
    order_names["creator"] = "creatorSorter"
    order_names["date"] = "date"
@@ -148,25 +150,25 @@

    if (search ~ /^@/) {
        numfound++
    }
    if (numfound == 0) {
-        print "Your search did not match any items in the Archive."
-        print "Try different keywords or a more general search."
-        print ""
-        printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port
+        info(out, "Your search did not match any items in the Archive.")
+        info(out, "Try different keywords or a more general search.")
+        info(out, "")
+        item(out, "1", "PHAROS", docpath, server, port)
        unlink(jsout)
        unlink(iaout)
        return
    } else {
        pages = int(numfound / rows)
        if (numfound % rows != 0) {
            pages++
        }
-        printf "# %s search results, page %d of %d\n", numfound,
-            page, pages
-        print ""
+        info(out, sprintf("# %s search results, page %d of %d",
+            numfound, page, pages))
+        info(out, "")
    }

    # format search results as a gopher directory (menu)
    FS = "\t"
    creator = ""
@@ -175,12 +177,12 @@
    title = ""
    type = ""
    count = 0

    if (search ~ /^@/) {
-        printf "1Account %s\t%s/account?%s\t%s\t%s\n", search, cgipath,
-           search, server, port
+        sel = cgipath "/account?" search
+        item(out, "1", "Account " search, sel, server, port)
    }

    while ((getline <jsout) > 0) {
        if ($1 == ".response.docs[].creator" && $2 == "s") {
            creator = $3
@@ -200,53 +202,55 @@
            } else {
                label = sprintf("[%s] %s", mediatype[type],
                    shorten(title, 58))
            }
            if (type == "collection") {
-                printf "1%s\t%s/search?collection:(%s)\t%s\t%s\n",
-                    label, cgipath, id, server, port
+                sel = sprintf("%s/search?collection:(%s)", cgipath, id)
+                item(out, "1", label, sel, server, port)
            } else {
-                printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath,
-                   id, server, port
+                sel = cgipath "/details?" id
+                item(out, "1", label, sel, server, port)
            }
            creator = ""
            descr = ""
            id = ""
            type = ""
        }
    }
    close(jsout)

-    print ""
+    info(out, "")

    # only show "page back" if the user is past page 1
    if (page > 1) {
-        prefix = sprintf("/page%d/rows%d/%s", page - 1, rows, sort_param)
-        printf "1[<<] Page %d\t%s/search?%s%s\t%s\t%s\n",
-            page - 1, cgipath, search, prefix, server, port
+        label = sprintf("[<<] Page %d", page - 1)
+        sel = sprintf("%s/search?%s/page%d/rows%d/%s", cgipath,
+            search, page - 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
    }

    # only show "next page" if the current page is completely full
    if (count == rows) {
-        prefix = sprintf("/page%d/rows%d/%s", page + 1, rows, sort_param)
-        printf "1[>>] Page %d\t%s/search?%s%s\t%s\t%s\n",
-            page + 1, cgipath, search, prefix, server, port
+        label = sprintf("1[>>] Page %d", page + 1)
+        sel = sprintf("%s/search?%s/page%d/rows%d/%s", cgipath,
+            search, page + 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
    }

    # only show "sort" if there's more than one item to sort
    if (count > 1) {
-        printf "1[^v] Sort\t%s/sort?%s\t%s\t%s\n", cgipath,
-            search, server, port
+        sel = cgipath "/sort?" search
+        item(out, "1", "[^v] Sort", sel, server, port)
    }

    # only show "search within list" if there's multiple pages of results
    if (numfound > rows) {
-        printf "1[\\/] Filter results\t%s/wizard/step1?%s\t%s\t%s\n",
-            cgipath, search, server, port
+        sel = cgipath "/wizard/step1?" search
+        item(out, "1", "[\\/] Filter results", sel, server, port)
    }

-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port
+    item(out, "1", "PHAROS", docpath, server, port)

    unlink(jsout)
    unlink(iaout)
    exit 0
}

Index: src/sort.m4
==================================================================
--- src/sort.m4
+++ src/sort.m4
@@ -42,14 +42,14 @@
    lbl[16] = "Creator [v]"
    opt[16] = "creator desc"
    lbl[17] = "Random"
    opt[17] = "random asc"

-    print "# Sort by"
-    print ""
+    printf "# Sort by\r\n"
+    printf "\r\n"
    for (i = 1; i < 18; i++) {
-        printf "1%s\t%s/search?%s/sort%s\t%s\t%s\n",
+        printf "1%s\t%s/search?%s/sort%s\t%s\t%s\r\n",
            lbl[i], cgipath, search, opt[i], server, port
    }
    exit 0
}


Index: src/sqlite.awk
==================================================================
--- src/sqlite.awk
+++ src/sqlite.awk
@@ -7,11 +7,11 @@
    printf "%s;\n", query >>sqlcfg
    close(sqlcfg)
    cmd = sprintf("%s -batch -line -safe -init \"%s\" \"%s\" .quit 2>&1",
        cmd_sqlite, sqlcfg, db)
    while ((cmd | getline) > 0) {
-        retval = retval $0 "\n"
+        retval = retval $0 "\r\n"
    }
    close(cmd)
    unlink(sqlcfg)
    return retval
}

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -42,15 +42,15 @@
    while ((cmd_mktemp | getline) > 0) {
        retval = $0
    }
    result = close(cmd_mktemp)
    if (result != 0) {
-        print "Error: mktemp failed exit status: " result
+        info("", "Error: mktemp failed exit status: " result)
        exit
    }
    if (length(retval) == 0) {
-        print "Error: mktemp failed, no tmpfile"
+        info("", "Error: mktemp failed, no tmpfile")
        exit
    }
    return retval
}

@@ -110,30 +110,53 @@
        retval = sprintf("%dB", bytes)
    }
    return retval
}

-function print_not_found(output, url) {
-    print "Item cannot be found" >>output
-    print "" >>output
-    print "Items may be taken down for various reasons," >>output
-    print "including by decision of the uploader or" >>output
-    print "due to a violation of the Terms of Use." >>output
-    print "" >>output
-    printf "hMetadata\tURL:%s\t%s\t%s\n", url, server, port >>output
-    print "" >>output
+function info(out, str) {
+    if (length(out) == 0) {
+        printf "i%s\t\t\t\r\n", str
+    } else {
+        printf "i%s\t\t\t\r\n", str >>out
+    }
+    return
+}
+
+function item(out, type, label, sel, host, port,     line) {
+    line = item_str(type, label, sel, host, port)
+    if (length(out) == 0) {
+        printf "%s\r\n", line
+    } else {
+        printf "%s\r\n", line >>out
+    }
+    return
+}
+
+function item_str(type, label, sel, host, port) {
+    retval = sprintf("%s%s\t%s\t%s\t%s", type, label, sel, host, port)
+    return retval
+}
+
+function print_not_found(out, url) {
+    info(out, "Item cannot be found")
+    info(out, "")
+    info(out, "Items may be taken down for various reasons,")
+    info(out, "including by decision of the uploader or")
+    info(out, "due to a violation of the Terms of Use.")
+    info(out, "")
+    item(out, "h", "Metadata", "URL:" url, server, port)
+    info(out, "")
    url = api_ssl_endpoint "/about/terms.php"
-    printf "0Terms of Use\t%s/text?%s\t%s\t%s\n", cgipath,
-        url, server, port >>output
-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+    item(out, "0", "Terms of Use", cgipath "/text?" url, server, port)
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)
    return
}

function read_file(name,    retval) {
    while ((getline <name) > 0) {
-        retval = retval $0 "\n"
+        retval = retval $0 "\r\n"
    }
    close(name)
    return retval
}


Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -1,13 +1,16 @@
-function dump(search, type,     base, cmd, curlcfg, is_html, is_image,
-    label, limit, link, marker, parts, proto, relative, root, url)
+function dump(search, type,     base, cmd, curlcfg, is_html,
+    is_image, label, limit, link, marker, out, parts, proto,
+    relative, root, sel, url)
{
+    out = ""
+
    url = search
    gsub(/%3F/, "?", url)

    if (url !~ /^(http|https):\/\/[[:alnum:].-]+(:[0-9]+)*(\/[[:alnum:].,?@~=%%:\/+&_() -]*)*$/) {
-        printf "Error: Unacceptable URL \"%s\"\r\n", url
+        info(out, sprintf("Error: Unacceptable URL \"%s\"", url))
        return
    }

    if (type == TYPE_HEADERS || type == TYPE_RAW) {
        limit = max_bin_size
@@ -72,35 +75,34 @@
    # line numbers larger than the marker are referenced links

    marker = 999999

    if (type == TYPE_LINKS) {
+        sel = cgipath "/raw?" search
+
        is_html = detect_html(url)
        is_image = detect_image(url)
-        printf "9Binary download\t%s/raw?%s\t%s\t%s\n", cgipath,
-            search, server, port
+        item(out, "9", "Binary download", sel, server, port)
        if (is_image) {
-            printf "IImage view\t%s/raw?%s\t%s\t%s\n", cgipath,
-                search, server, port
+            item(out, "I", "Image view", sel, server, port)
        }
        if (is_html) {
            label = "Source"
        } else {
            label = "Text view"
        }
-        printf "0%s\t%s/raw?%s\t%s\t%s\n", label, cgipath, search,
-            server, port
+        item(out, "0", label, sel, server, port)
        if (is_html) {
            label = "HTML view"
        } else {
            label = "Strings"
        }
-        printf "0%s\t%s/text?%s\t%s\t%s\n", label, cgipath,
-            search, server, port
-        printf "0Headers\t%s/debug?%s\t%s\t%s\n", cgipath,
-            search, server, port
-        print ""
+        sel = cgipath "/text?" search
+        item(out, "0", label, sel, server, port)
+        sel = cgipath "/debug?" search
+        item(out, "0", "Headers", sel, server, port)
+        info(out, "")
    }

    while ((cmd | getline) > 0) {
        if (NR < marker) {
            if ($0 ~ /^References$/) {
@@ -118,11 +120,13 @@
    unlink(curlcfg)

    return
}

-function print_html(output, html,    cmd, marker, work) {
+function print_html(out, html,    cmd, marker, out, work) {
+    out = ""
+
    work = gettemp()
    gsub(/\\n/, "<br>", html)
    print html >work
    close(work)
    cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work,
@@ -133,13 +137,13 @@
        gsub(/\\t/, "        ")
        if (NR < marker) {
            if ($0 ~ /^References$/) {
                marker = NR
            }
-            print >>output
+            info(out, $0)
        } else {
-            print_ref_pharos(output, $0)
+            print_ref_pharos(out, $0)
        }
    }
    close(cmd)
    unlink(work)
    return
@@ -146,11 +150,15 @@
}

# Print the webdump references section, converting relative URLs
# to full URLs

-function print_ref_full(str, base, proto, root,     link, prefix, relative) {
+function print_ref_full(str, base, proto, root,     link, out,
+    prefix, relative)
+{
+    out = ""
+
    if (match(str, /^ [0-9]+\. /)) {
        prefix = substr(str, 0, RLENGTH)
        link = substr(str, RLENGTH + 1)
        # convert relative links to full URLs
        if (link !~ /^[a-z]+:/) {
@@ -162,22 +170,22 @@
                link = root relative
            } else {
                link = base "/" relative
            }
        }
-        print prefix link
+        info(out, prefix link)
    } else {
-        print str
+        info(out, str)
    }
    return
}


# Print the webdump references section, translating archive.org URLs to
# pharos URLs

-function print_ref_pharos(output, str,     id, label, link, prefix,
+function print_ref_pharos(out, str,     id, label, link, prefix,
    relative, token)
{
    if (match(str, /^ [0-9]+\. /)) {
        prefix = substr(str, 0, RLENGTH)
        link = substr(str, RLENGTH + 1)
@@ -190,17 +198,16 @@
                id = substr(id, 1, RSTART - 1)
            }
        }
        if (length(id) > 0) {
            label = prefix id
-            printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath,
-                id, server, port >>output
+            item(out, "1", label, cgipath "/details?" id, server, port)
        } else {
-             print str >>output
+            info(out, str)
        }
    } else {
-        print str >>output
+        info(out, str)
    }
    return
}

function web_init() {

Index: src/wizard/step1.m4
==================================================================
--- src/wizard/step1.m4
+++ src/wizard/step1.m4
@@ -5,12 +5,15 @@
#
# Select field to filter/search by

include(src/config.awk)
incl(src/cgi.awk)
+incl(src/util.awk)

-function main(     i, lbl, opt) {
+function main(     i, lbl, opt, out, sel) {
+    out = ""
+
    lbl[1]  = "Any field contains"
    opt[1]  = "anyfield"
    lbl[2]  = "Any field does not contain"
    opt[2]  = "-anyfield"
    lbl[3]  = "Title contains"
@@ -38,22 +41,22 @@
    lbl[14] = "Language contains"
    opt[14] = "language"
    lbl[15] = "Always available"
    opt[15] = "-access-restricted-item"

-    print "# Search wizard: Select field"
-    print ""
+    info(out, "# Search wizard: Select field")
+    info(out, "")
    for (i = 1; i < 16; i++) {
        if (opt[i] ~ /mediatype$/) {
-            printf "1%s\t%s/wizard/step2?%s/%s\t%s\t%]\n",
-                lbl[i], cgipath, search, opt[i], server, port
+            sel = sprintf("%s/wizard/step2?%s/%s", cgipath, search, opt[i])
+            item(out, "1", lbl[i], sel, server, port)
        } else if (lbl[i] == "Always available") {
-            printf "1%s\t%s/wizard/step3?%s/%s/true\t%s\t%s\n",
-                lbl[i], cgipath, search, opt[i], server, port
+            sel = sprintf("%s/wizard/step3?%s/%s", cgipath, search, opt[i])
+            item(out, "1", lbl[i], sel, server, port)
        } else {
-            printf "7%s\t%s/wizard/step3?%s/%s\t%s\t%s\n",
-                lbl[i], cgipath, search, opt[i], server, port
+            sel = sprintf("%s/wizard/step3?%s/%s", cgipath, search, opt[i])
+            item(out, "7", lbl[i], sel, server, port)
        }
    }
    exit 0
}


Index: src/wizard/step2.m4
==================================================================
--- src/wizard/step2.m4
+++ src/wizard/step2.m4
@@ -5,21 +5,25 @@
#
# Select mediatype to filter/search by

include(src/config.awk)
incl(src/cgi.awk)
+incl(src/util.awk)
+
+function main(     field, newsearch, out, searchstr, sel) {
+    out = ""

-function main(     field, newsearch, searchstr) {
-    field = parts[4]
-    searchstr = parts[5]
+    split(search, parts, "/")
+    searchstr = parts[1]
+    field = parts[2]

    if (field == "mediatype") {
-        print "# Mediatype is:"
+        info(out, "# Mediatype is:")
    } else {
-        print "# Mediatype does not contain:"
+        info(out, "# Mediatype does not contain:")
    }
-    print ""
+    info(out, "")

    lbl[1] = "audio"
    lbl[2] = "collection"
    lbl[3] = "data"
    lbl[4] = "etree"
@@ -32,20 +36,20 @@
        if (length(searchstr) == 0) {
            newsearch = sprintf("%s:(%s)", field, lbl[i])
        } else {
            newsearch = sprintf("%s AND %s:(%s)", searchstr, field, lbl[i])
        }
-        printf "1%s\t%s/search?%s\t%s\t%s\n", lbl[i], cgipath,
-            newsearch, server, port
+        sel = cgipath "/search?" newsearch
+        item(out, "1", lbl[i], sel, server, port)
    }
-    print "# Progress:"
-    print ""
-    print "* Field: Mediatype"
+    info(out, "# Progress:")
+    info(out, "")
+    info(out, "* Field: Mediatype")
    exit 0
}

BEGIN {
    config_init()

    cgi_init()
    main()
}

Index: src/wizard/step3.m4
==================================================================
--- src/wizard/step3.m4
+++ src/wizard/step3.m4
@@ -5,12 +5,17 @@
#
# Apply new search terms

include(src/config.awk)
incl(src/cgi.awk)
+incl(src/util.awk)

-function main(     field, label, newsearch, op, searchstr, value) {
+function main(     field, label, newsearch, op, out, searchstr,
+    sel, value)
+{
+    out = ""
+
    split(search, parts, "/")
    searchstr = parts[1]
    field = parts[2]
    value = searchreq

@@ -24,22 +29,22 @@
    if (length(searchstr) == 0) {
        newsearch = sprintf("%s:(%s)", field, value)
    } else {
        newsearch = sprintf("%s AND %s:(%s)", searchstr, field, value)
    }
-    print ""
-    printf "1Apply search criteria\t%s/search?%s\t%s\t%s\n",
-        cgipath, newsearch, server, port
-    print ""
-    print "# Progress:"
-    print ""
-    printf "* Field %s %s %s\n", label, op, value
-    printf "* New search: %s\n", newsearch
+    info(out, "")
+    sel = cgipath "/search?" newsearch
+    item(out, "1", "Apply search criteria", sel, server, port)
+    info(out, "")
+    info(out, "# Progress:")
+    info(out, "")
+    info(out, sprintf("* Field %s %s %s", label, op, value))
+    info(out, "* New search: " newsearch)
    exit 0
}

BEGIN {
    config_init()

    cgi_init()
    main()
}