📄 buffers.c
字号:
{
if (!string_len)
return (int)buf->datalen;
check();
while (string_len) {
size_t copy;
if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail))
buf_add_chunk_with_capacity(buf, string_len, 1);
copy = CHUNK_REMAINING_CAPACITY(buf->tail);
if (copy > string_len)
copy = string_len;
memcpy(CHUNK_WRITE_PTR(buf->tail), string, copy);
string_len -= copy;
string += copy;
buf->datalen += copy;
buf->tail->datalen += copy;
}
check();
tor_assert(buf->datalen < INT_MAX);
return (int)buf->datalen;
}
/** Helper: copy the first <b>string_len</b> bytes from <b>buf</b>
* onto <b>string</b>.
*/
static INLINE void
peek_from_buf(char *string, size_t string_len, const buf_t *buf)
{
chunk_t *chunk;
tor_assert(string);
/* make sure we don't ask for too much */
tor_assert(string_len <= buf->datalen);
/* assert_buf_ok(buf); */
chunk = buf->head;
while (string_len) {
size_t copy = string_len;
tor_assert(chunk);
if (chunk->datalen < copy)
copy = chunk->datalen;
memcpy(string, chunk->data, copy);
string_len -= copy;
string += copy;
chunk = chunk->next;
}
}
/** Remove <b>string_len</b> bytes from the front of <b>buf</b>, and store
* them into <b>string</b>. Return the new buffer size. <b>string_len</b>
* must be \<= the number of bytes on the buffer.
*/
int
fetch_from_buf(char *string, size_t string_len, buf_t *buf)
{
/* There must be string_len bytes in buf; write them onto string,
* then memmove buf back (that is, remove them from buf).
*
* Return the number of bytes still on the buffer. */
check();
peek_from_buf(string, string_len, buf);
buf_remove_from_front(buf, string_len);
check();
tor_assert(buf->datalen < INT_MAX);
return (int)buf->datalen;
}
/** Check <b>buf</b> for a variable-length cell according to the rules of link
* protocol version <b>linkproto</b>. If one is found, pull it off the buffer
* and assign a newly allocated var_cell_t to *<b>out</b>, and return 1.
* Return 0 if whatever is on the start of buf_t is not a variable-length
* cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start
* of a variable-length cell on <b>buf</b>, but the whole thing isn't there
* yet. */
int
fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto)
{
char hdr[VAR_CELL_HEADER_SIZE];
var_cell_t *result;
uint8_t command;
uint16_t length;
/* If linkproto is unknown (0) or v2 (2), variable-length cells work as
* implemented here. If it's 1, there are no variable-length cells. Tor
* does not support other versions right now, and so can't negotiate them.
*/
if (linkproto == 1)
return 0;
check();
*out = NULL;
if (buf->datalen < VAR_CELL_HEADER_SIZE)
return 0;
peek_from_buf(hdr, sizeof(hdr), buf);
command = *(uint8_t*)(hdr+2);
if (!(CELL_COMMAND_IS_VAR_LENGTH(command)))
return 0;
length = ntohs(get_uint16(hdr+3));
if (buf->datalen < (size_t)(VAR_CELL_HEADER_SIZE+length))
return 1;
result = var_cell_new(length);
result->command = command;
result->circ_id = ntohs(*(uint16_t*)hdr);
buf_remove_from_front(buf, VAR_CELL_HEADER_SIZE);
peek_from_buf(result->payload, length, buf);
buf_remove_from_front(buf, length);
check();
*out = result;
return 1;
}
/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to
* <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately.
* Return the number of bytes actually copied.
*/
int
move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen)
{
/* XXXX we can do way better here, but this doesn't turn up in any
* profiles. */
char b[4096];
size_t cp, len;
len = *buf_flushlen;
if (len > buf_in->datalen)
len = buf_in->datalen;
cp = len; /* Remember the number of bytes we intend to copy. */
tor_assert(cp < INT_MAX);
while (len) {
/* This isn't the most efficient implementation one could imagine, since
* it does two copies instead of 1, but I kinda doubt that this will be
* critical path. */
size_t n = len > sizeof(b) ? sizeof(b) : len;
fetch_from_buf(b, n, buf_in);
write_to_buf(b, n, buf_out);
len -= n;
}
*buf_flushlen -= cp;
return (int)cp;
}
/** Internal structure: represents a position in a buffer. */
typedef struct buf_pos_t {
const chunk_t *chunk; /**< Which chunk are we pointing to? */
int pos;/**< Which character inside the chunk's data are we pointing to? */
size_t chunk_pos; /**< Total length of all previous chunks. */
} buf_pos_t;
/** Initialize <b>out</b> to point to the first character of <b>buf</b>.*/
static void
buf_pos_init(const buf_t *buf, buf_pos_t *out)
{
out->chunk = buf->head;
out->pos = 0;
out->chunk_pos = 0;
}
/** Advance <b>out</b> to the first appearance of <b>ch</b> at the current
* position of <b>out</b>, or later. Return -1 if no instances are found;
* otherwise returns the absolute position of the character. */
static off_t
buf_find_pos_of_char(char ch, buf_pos_t *out)
{
const chunk_t *chunk;
int pos;
tor_assert(out);
if (out->chunk) {
if (out->chunk->datalen) {
/*XXXX020 remove this once the bug it detects is fixed. */
if (!(out->pos < (off_t)out->chunk->datalen)) {
log_warn(LD_BUG, "About to assert. %p, %d, %d, %p, %d.",
out, (int)out->pos,
(int)out->chunk_pos, out->chunk,
out->chunk?(int)out->chunk->datalen : (int)-1
);
}
tor_assert(out->pos < (off_t)out->chunk->datalen);
} else {
tor_assert(out->pos == 0);
}
}
pos = out->pos;
for (chunk = out->chunk; chunk; chunk = chunk->next) {
char *cp = memchr(chunk->data+pos, ch, chunk->datalen - pos);
if (cp) {
out->chunk = chunk;
tor_assert(cp - chunk->data < INT_MAX);
out->pos = (int)(cp - chunk->data);
return out->chunk_pos + out->pos;
} else {
out->chunk_pos += chunk->datalen;
pos = 0;
}
}
return -1;
}
/** Advance <b>pos</b> by a single character, if there are any more characters
* in the buffer. Returns 0 on sucess, -1 on failure. */
static INLINE int
buf_pos_inc(buf_pos_t *pos)
{
++pos->pos;
if (pos->pos == (off_t)pos->chunk->datalen) {
if (!pos->chunk->next)
return -1;
pos->chunk_pos += pos->chunk->datalen;
pos->chunk = pos->chunk->next;
pos->pos = 0;
}
return 0;
}
/** Return true iff the <b>n</b>-character string in <b>s</b> appears
* (verbatim) at <b>pos</b>. */
static int
buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n)
{
buf_pos_t p;
if (!n)
return 1;
memcpy(&p, pos, sizeof(p));
while (1) {
char ch = p.chunk->data[p.pos];
if (ch != *s)
return 0;
++s;
/* If we're out of characters that don't match, we match. Check this
* _before_ we test incrementing pos, in case we're at the end of the
* string. */
if (--n == 0)
return 1;
if (buf_pos_inc(&p)<0)
return 0;
}
}
/** Return the first position in <b>buf</b> at which the <b>n</b>-character
* string <b>s</b> occurs, or -1 if it does not occur. */
/*private*/ int
buf_find_string_offset(const buf_t *buf, const char *s, size_t n)
{
buf_pos_t pos;
buf_pos_init(buf, &pos);
while (buf_find_pos_of_char(*s, &pos) >= 0) {
if (buf_matches_at_pos(&pos, s, n)) {
tor_assert(pos.chunk_pos + pos.pos < INT_MAX);
return (int)(pos.chunk_pos + pos.pos);
} else {
if (buf_pos_inc(&pos)<0)
return -1;
}
}
return -1;
}
/** There is a (possibly incomplete) http statement on <b>buf</b>, of the
* form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain nuls.)
* If a) the headers include a Content-Length field and all bytes in
* the body are present, or b) there's no Content-Length field and
* all headers are present, then:
*
* - strdup headers into <b>*headers_out</b>, and nul-terminate it.
* - memdup body into <b>*body_out</b>, and nul-terminate it.
* - Then remove them from <b>buf</b>, and return 1.
*
* - If headers or body is NULL, discard that part of the buf.
* - If a headers or body doesn't fit in the arg, return -1.
* (We ensure that the headers or body don't exceed max len,
* _even if_ we're planning to discard them.)
* - If force_complete is true, then succeed even if not all of the
* content has arrived.
*
* Else, change nothing and return 0.
*/
int
fetch_from_buf_http(buf_t *buf,
char **headers_out, size_t max_headerlen,
char **body_out, size_t *body_used, size_t max_bodylen,
int force_complete)
{
char *headers, *p;
size_t headerlen, bodylen, contentlen;
int crlf_offset;
check();
if (!buf->head)
return 0;
crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4);
if (crlf_offset > (int)max_headerlen ||
(crlf_offset < 0 && buf->datalen > max_headerlen)) {
log_debug(LD_HTTP,"headers too long.");
return -1;
} else if (crlf_offset < 0) {
log_debug(LD_HTTP,"headers not all here yet.");
return 0;
}
/* Okay, we have a full header. Make sure it all appears in the first
* chunk. */
if ((int)buf->head->datalen < crlf_offset + 4)
buf_pullup(buf, crlf_offset+4, 0);
headerlen = crlf_offset + 4;
headers = buf->head->data;
bodylen = buf->datalen - headerlen;
log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen);
if (max_headerlen <= headerlen) {
log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.",
(int)headerlen, (int)max_headerlen-1);
return -1;
}
if (max_bodylen <= bodylen) {
log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.",
(int)bodylen, (int)max_bodylen-1);
return -1;
}
#define CONTENT_LENGTH "\r\nContent-Length: "
p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH);
if (p) {
int i;
i = atoi(p+strlen(CONTENT_LENGTH));
if (i < 0) {
log_warn(LD_PROTOCOL, "Content-Length is less than zero; it looks like "
"someone is trying to crash us.");
return -1;
}
contentlen = i;
/* if content-length is malformed, then our body length is 0. fine. */
log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen);
if (bodylen < contentlen) {
if (!force_complete) {
log_debug(LD_HTTP,"body not all here yet.");
return 0; /* not all there yet */
}
}
if (bodylen > contentlen) {
bodylen = contentlen;
log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen);
}
}
/* all happy. copy into the appropriate places, and return 1 */
if (headers_out) {
*headers_out = tor_malloc(headerlen+1);
fetch_from_buf(*headers_out, headerlen, buf);
(*headers_out)[headerlen] = 0; /* nul terminate it */
}
if (body_out) {
tor_assert(body_used);
*body_used = bodylen;
*body_out = tor_malloc(bodylen+1);
fetch_from_buf(*body_out, bodylen, buf);
(*body_out)[bodylen] = 0; /* nul terminate it */
}
check();
return 1;
}
/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one
* of the forms
* - socks4: "socksheader username\\0"
* - socks4a: "socksheader username\\0 destaddr\\0"
* - socks5 phase one: "version #methods methods"
* - socks5 phase two: "version command 0 addresstype..."
* If it's a complete and valid handshake, and destaddr fits in
* MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf,
* assign to <b>req</b>, and return 1.
*
* If it's invalid or too big, return -1.
*
* Else it's not all there yet, leave buf alone and return 0.
*
* If you want to specify the socks reply, write it into <b>req->reply</b>
* and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone.
*
* If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether
* the connection is possibly leaking DNS requests locally or not.
*
* If <b>safe_socks</b> is true, then reject unsafe socks protocols.
*
* If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are
* undefined.
*/
int
fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
int log_sockstype, int safe_socks)
{
unsigned int len;
char tmpbuf[INET_NTOA_BUF_LEN];
uint32_t destip;
uint8_t socksver;
enum {socks4, socks4a} socks4_prot = socks4a;
char *next, *startaddr;
struct in_addr in;
/* If the user connects with socks4 or the wrong variant of socks5,
* then log a warning to let him know that it might be unwise. */
static int have_warned_about_unsafe_socks = 0;
if (buf->datalen < 2) /* version and another byte */
return 0;
buf_pullup(buf, 128, 0);
tor_assert(buf->head && buf->head->datalen >= 2);
socksver = *buf->head->data;
switch (socksver) { /* which version of socks? */
case 5: /* socks5 */
if (req->socks_version != 5) { /* we need to negotiate a method */
unsigned char nummethods = (unsigned char)*(buf->head->data+1);
tor_assert(!req->socks_version);
if (buf->datalen < 2u+nummethods)
return 0;
buf_pullup(buf, 2u+nummethods, 0);
if (!nummethods || !memchr(buf->head->data+2, 0, nummethods)) {
log_warn(LD_APP,
"socks5: offered methods don't include 'no auth'. "
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -