+/* Set the request named NAME to VALUE. Specifically, this means that
+ a "NAME: VALUE\r\n" header line will be used in the request. If a
+ header with the same name previously existed in the request, its
+ value will be replaced by this one. A NULL value means do nothing.
+
+ RELEASE_POLICY determines whether NAME and VALUE should be released
+ (freed) with request_free. Allowed values are:
+
+ - rel_none - don't free NAME or VALUE
+ - rel_name - free NAME when done
+ - rel_value - free VALUE when done
+ - rel_both - free both NAME and VALUE when done
+
+ Setting release policy is useful when arguments come from different
+ sources. For example:
+
+ // Don't free literal strings!
+ request_set_header (req, "Pragma", "no-cache", rel_none);
+
+ // Don't free a global variable, we'll need it later.
+ request_set_header (req, "Referer", opt.referer, rel_none);
+
+ // Value freshly allocated, free it when done.
+ request_set_header (req, "Range",
+ aprintf ("bytes=%s-", number_to_static_string (hs->restval)),
+ rel_value);
+ */
+
+static void
+request_set_header (struct request *req, char *name, char *value,
+ enum rp release_policy)
+{
+ struct request_header *hdr;
+ int i;
+
+ if (!value)
+ {
+ /* A NULL value is a no-op; if freeing the name is requested,
+ free it now to avoid leaks. */
+ if (release_policy == rel_name || release_policy == rel_both)
+ xfree (name);
+ return;
+ }
+
+ for (i = 0; i < req->hcount; i++)
+ {
+ hdr = &req->headers[i];
+ if (0 == strcasecmp (name, hdr->name))
+ {
+ /* Replace existing header. */
+ release_header (hdr);
+ hdr->name = name;
+ hdr->value = value;
+ hdr->release_policy = release_policy;
+ return;
+ }
+ }
+
+ /* Install new header. */
+
+ if (req->hcount >= req->hcapacity)
+ {
+ req->hcapacity <<= 1;
+ req->headers = xrealloc (req->headers, req->hcapacity * sizeof (*hdr));
+ }
+ hdr = &req->headers[req->hcount++];
+ hdr->name = name;
+ hdr->value = value;
+ hdr->release_policy = release_policy;
+}
+
+/* Like request_set_header, but sets the whole header line, as
+ provided by the user using the `--header' option. For example,
+ request_set_user_header (req, "Foo: bar") works just like
+ request_set_header (req, "Foo", "bar"). */
+
+static void
+request_set_user_header (struct request *req, const char *header)
+{
+ char *name;
+ const char *p = strchr (header, ':');
+ if (!p)
+ return;
+ BOUNDED_TO_ALLOCA (header, p, name);
+ ++p;
+ while (ISSPACE (*p))
+ ++p;
+ request_set_header (req, xstrdup (name), (char *) p, rel_name);
+}
+
+/* Remove the header with specified name from REQ. Returns true if
+ the header was actually removed, false otherwise. */
+
+static bool
+request_remove_header (struct request *req, char *name)
+{
+ int i;
+ for (i = 0; i < req->hcount; i++)
+ {
+ struct request_header *hdr = &req->headers[i];
+ if (0 == strcasecmp (name, hdr->name))
+ {
+ release_header (hdr);
+ /* Move the remaining headers by one. */
+ if (i < req->hcount - 1)
+ memmove (hdr, hdr + 1, (req->hcount - i - 1) * sizeof (*hdr));
+ --req->hcount;
+ return true;
+ }
+ }
+ return false;
+}
+
+#define APPEND(p, str) do { \
+ int A_len = strlen (str); \
+ memcpy (p, str, A_len); \
+ p += A_len; \
+} while (0)
+
+/* Construct the request and write it to FD using fd_write. */
+
+static int
+request_send (const struct request *req, int fd)
+{
+ char *request_string, *p;
+ int i, size, write_error;
+
+ /* Count the request size. */
+ size = 0;
+
+ /* METHOD " " ARG " " "HTTP/1.0" "\r\n" */
+ size += strlen (req->method) + 1 + strlen (req->arg) + 1 + 8 + 2;
+
+ for (i = 0; i < req->hcount; i++)
+ {
+ struct request_header *hdr = &req->headers[i];
+ /* NAME ": " VALUE "\r\n" */
+ size += strlen (hdr->name) + 2 + strlen (hdr->value) + 2;
+ }
+
+ /* "\r\n\0" */
+ size += 3;
+
+ p = request_string = alloca_array (char, size);
+
+ /* Generate the request. */
+
+ APPEND (p, req->method); *p++ = ' ';
+ APPEND (p, req->arg); *p++ = ' ';
+ memcpy (p, "HTTP/1.0\r\n", 10); p += 10;
+
+ for (i = 0; i < req->hcount; i++)
+ {
+ struct request_header *hdr = &req->headers[i];
+ APPEND (p, hdr->name);
+ *p++ = ':', *p++ = ' ';
+ APPEND (p, hdr->value);
+ *p++ = '\r', *p++ = '\n';
+ }
+
+ *p++ = '\r', *p++ = '\n', *p++ = '\0';
+ assert (p - request_string == size);
+
+#undef APPEND
+
+ DEBUGP (("\n---request begin---\n%s---request end---\n", request_string));
+
+ /* Send the request to the server. */
+
+ write_error = fd_write (fd, request_string, size - 1, -1);
+ if (write_error < 0)
+ logprintf (LOG_VERBOSE, _("Failed writing HTTP request: %s.\n"),
+ strerror (errno));
+ return write_error;
+}
+
+/* Release the resources used by REQ. */
+
+static void
+request_free (struct request *req)
+{
+ int i;
+ xfree_null (req->arg);
+ for (i = 0; i < req->hcount; i++)
+ release_header (&req->headers[i]);
+ xfree_null (req->headers);
+ xfree (req);
+}
+
+/* Send the contents of FILE_NAME to SOCK. Make sure that exactly
+ PROMISED_SIZE bytes are sent over the wire -- if the file is
+ longer, read only that much; if the file is shorter, report an error. */
+
+static int
+post_file (int sock, const char *file_name, wgint promised_size)
+{
+ static char chunk[8192];
+ wgint written = 0;
+ int write_error;
+ FILE *fp;
+
+ DEBUGP (("[writing POST file %s ... ", file_name));
+
+ fp = fopen (file_name, "rb");
+ if (!fp)
+ return -1;
+ while (!feof (fp) && written < promised_size)
+ {
+ int towrite;
+ int length = fread (chunk, 1, sizeof (chunk), fp);
+ if (length == 0)
+ break;
+ towrite = MIN (promised_size - written, length);
+ write_error = fd_write (sock, chunk, towrite, -1);
+ if (write_error < 0)
+ {
+ fclose (fp);
+ return -1;
+ }
+ written += towrite;
+ }
+ fclose (fp);
+
+ /* If we've written less than was promised, report a (probably
+ nonsensical) error rather than break the promise. */
+ if (written < promised_size)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ assert (written == promised_size);
+ DEBUGP (("done]\n"));
+ return 0;
+}
+\f