📄 http.c
字号:
/* HTTP support. Copyright (C) 2005 Free Software Foundation, Inc.This file is part of GNU Wget.GNU Wget is free software; you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation; either version 2 of the License, or (at your option) any later version.GNU Wget is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with Wget; if not, write to the Free SoftwareFoundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.In addition, as a special exception, the Free Software Foundationgives permission to link the code of its release of Wget with theOpenSSL project's "OpenSSL" library (or with modified versions of itthat use the same license as the "OpenSSL" library), and distributethe linked executables. You must obey the GNU General Public Licensein all respects for all of the code used other than "OpenSSL". If youmodify this file, you may extend this exception to your version of thefile, but you are not obligated to do so. If you do not wish to doso, delete this exception statement from your version. */#include <config.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#ifdef HAVE_STRING_H# include <string.h>#else# include <strings.h>#endif#ifdef HAVE_UNISTD_H# include <unistd.h>#endif#include <assert.h>#include <errno.h>#if TIME_WITH_SYS_TIME# include <sys/time.h># include <time.h>#else# if HAVE_SYS_TIME_H# include <sys/time.h># else# include <time.h># endif#endif#ifndef errnoextern int errno;#endif#include "wget.h"#include "utils.h"#include "url.h"#include "host.h"#include "retr.h"#include "connect.h"#include "netrc.h"#ifdef HAVE_SSL# include "ssl.h"#endif#ifdef ENABLE_NTLM# include "http-ntlm.h"#endif#include "cookies.h"#ifdef ENABLE_DIGEST# include "gen-md5.h"#endif#include "convert.h"extern char *version_string;extern SUM_SIZE_INT total_downloaded_bytes;extern FILE *output_stream;extern int output_stream_regular;#ifndef MIN# define MIN(x, y) ((x) > (y) ? (y) : (x))#endifstatic int cookies_loaded_p;static struct cookie_jar *wget_cookie_jar;#define TEXTHTML_S "text/html"#define TEXTXHTML_S "application/xhtml+xml"/* Some status code validation macros: */#define H_20X(x) (((x) >= 200) && ((x) < 300))#define H_PARTIAL(x) ((x) == HTTP_STATUS_PARTIAL_CONTENTS)#define H_REDIRECTED(x) ((x) == HTTP_STATUS_MOVED_PERMANENTLY \ || (x) == HTTP_STATUS_MOVED_TEMPORARILY \ || (x) == HTTP_STATUS_SEE_OTHER \ || (x) == HTTP_STATUS_TEMPORARY_REDIRECT)/* HTTP/1.0 status codes from RFC1945, provided for reference. *//* Successful 2xx. */#define HTTP_STATUS_OK 200#define HTTP_STATUS_CREATED 201#define HTTP_STATUS_ACCEPTED 202#define HTTP_STATUS_NO_CONTENT 204#define HTTP_STATUS_PARTIAL_CONTENTS 206/* Redirection 3xx. */#define HTTP_STATUS_MULTIPLE_CHOICES 300#define HTTP_STATUS_MOVED_PERMANENTLY 301#define HTTP_STATUS_MOVED_TEMPORARILY 302#define HTTP_STATUS_SEE_OTHER 303 /* from HTTP/1.1 */#define HTTP_STATUS_NOT_MODIFIED 304#define HTTP_STATUS_TEMPORARY_REDIRECT 307 /* from HTTP/1.1 *//* Client error 4xx. */#define HTTP_STATUS_BAD_REQUEST 400#define HTTP_STATUS_UNAUTHORIZED 401#define HTTP_STATUS_FORBIDDEN 403#define HTTP_STATUS_NOT_FOUND 404#define HTTP_STATUS_RANGE_NOT_SATISFIABLE 416/* Server errors 5xx. */#define HTTP_STATUS_INTERNAL 500#define HTTP_STATUS_NOT_IMPLEMENTED 501#define HTTP_STATUS_BAD_GATEWAY 502#define HTTP_STATUS_UNAVAILABLE 503enum rp { rel_none, rel_name, rel_value, rel_both};struct request { const char *method; char *arg; struct request_header { char *name, *value; enum rp release_policy; } *headers; int hcount, hcapacity;};/* Create a new, empty request. At least request_set_method must be called before the request can be used. */static struct request *request_new (void){ struct request *req = xnew0 (struct request); req->hcapacity = 8; req->headers = xnew_array (struct request_header, req->hcapacity); return req;}/* Set the request's method and its arguments. METH should be a literal string (or it should outlive the request) because it will not be freed. ARG will be freed by request_free. */static voidrequest_set_method (struct request *req, const char *meth, char *arg){ req->method = meth; req->arg = arg;}/* Return the method string passed with the last call to request_set_method. */static const char *request_method (const struct request *req){ return req->method;}/* Free one header according to the release policy specified with request_set_header. */static voidrelease_header (struct request_header *hdr){ switch (hdr->release_policy) { case rel_none: break; case rel_name: xfree (hdr->name); break; case rel_value: xfree (hdr->value); break; case rel_both: xfree (hdr->name); xfree (hdr->value); break; }}/* 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 voidrequest_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 voidrequest_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 1 if the header was actually removed, 0 otherwise. */static intrequest_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 1; } } return 0;}#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 intrequest_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.0); 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 voidrequest_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 intpost_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.0); 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;}static const char *response_head_terminator (const char *hunk, int oldlen, int peeklen){ const char *start, *end; /* If at first peek, verify whether HUNK starts with "HTTP". If not, this is a HTTP/0.9 request and we must bail out without reading anything. */ if (oldlen == 0 && 0 != memcmp (hunk, "HTTP", MIN (peeklen, 4))) return hunk; if (oldlen < 4) start = hunk; else
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -