static GIOStatus http_transfer_src_read(ZTransfer2 *s, ZStream *stream, gchar *buf, gsize count, gsize *bytes_read, GError **err) { HttpTransfer *self = Z_CAST(s, HttpTransfer); HttpProxy *owner = Z_CAST(s->owner, HttpProxy); GError *local_error = NULL; gsize br; GIOStatus res = G_IO_STATUS_NORMAL; if (self->src_read_state == HTTP_SR_INITIAL) self->src_read_state = HTTP_SR_PUSH_HEADERS; if (self->src_read_state >= HTTP_SR_PUSH_HEADERS && self->src_read_state <= HTTP_SR_PUSH_HEADERS_MAX) { if (self->push_mime_headers && self->stacked_preamble->len > 0) { gint move; *bytes_read = 0; move = MIN(count, self->stacked_preamble->len - self->stacked_preamble_ofs); memmove(buf, self->stacked_preamble->str + self->stacked_preamble_ofs, move); self->stacked_preamble_ofs += move; *bytes_read = move; if (self->stacked_preamble_ofs == self->stacked_preamble->len) { z_transfer2_set_proxy_out(s, FALSE); self->src_read_state = HTTP_SR_READ_INITIAL; } return G_IO_STATUS_NORMAL; } else { self->src_read_state = HTTP_SR_READ_INITIAL; } } *bytes_read = 0; if (self->src_chunked) { /* read as a chunked stream */ switch (self->src_read_state) { case HTTP_SR_READ_INITIAL: self->src_whole_length = 0; self->src_read_state = HTTP_SR_READ_CHUNK_LENGTH; z_stream_line_set_poll_partial(stream, FALSE); /* fallthrough */ case HTTP_SR_READ_CHUNK_LENGTH: { gchar line_buf[32], *line; gsize line_length; guint32 chunk_length; gchar *end; res = z_stream_line_get(stream, &line, &line_length, NULL); if (res == G_IO_STATUS_NORMAL) { /* a complete line was read, check if it is a valid chunk length */ if (line_length >= sizeof(line_buf) - 1) { /*LOG This message indicates that the chunk length line is too long. It is likely caused by a buggy client or server. */ z_proxy_log(self->super.owner, HTTP_VIOLATION, 1, "Chunk length line too long; line='%.*s'", (gint) line_length, line); res = G_IO_STATUS_ERROR; break; } /* we already checked that line_buf is large enough */ memcpy(line_buf, line, line_length); line_buf[line_length] = 0; chunk_length = strtoul(line_buf, &end, 16); if (end == line_buf) { /* hmm... invalid chunk length */ /*LOG This message indicates that the chunk length is invalid. It is likely caused by a buggy client or server. */ z_proxy_log(self->super.owner, HTTP_VIOLATION, 1, "Invalid chunk length; line='%s'", line_buf); res = G_IO_STATUS_ERROR; break; } /* * NOTE: the string pointed by end is NUL terminated, thus * we will not overflow our buffer */ while (*end == ' ') end++; if (*end == ';') { /* ignore and strip chunk extensions */ *end = 0; } if (*end) { /* hmm... invalid chunk length */ /*LOG This message indicates that the chunk length is invalid. It is likely caused by a buggy client or server. */ z_proxy_log(self->super.owner, HTTP_VIOLATION, 1, "Invalid chunk length; line='%s'", line_buf); res = G_IO_STATUS_ERROR; break; } if ((owner->max_chunk_length && chunk_length > owner->max_chunk_length) || (chunk_length & 0x80000000)) { /*LOG This message indicates that the length of the chunk is larger than allowed or is a negative number. Check the 'max_chunk_length' attribute. */ z_proxy_log(self->super.owner, HTTP_POLICY, 2, "Chunk too large; length='%d', max_chunk_length='%d'", chunk_length, owner->max_chunk_length); res = G_IO_STATUS_ERROR; break; } if (owner->max_body_length && (guint) self->src_whole_length + chunk_length > owner->max_body_length) { /* this chunk would be over body_length limit */ chunk_length = owner->max_body_length - self->src_whole_length; self->src_chunk_left = chunk_length; self->force_nonpersistent_mode = TRUE; self->src_chunk_truncated = TRUE; } self->src_chunk_left = chunk_length; self->src_last_chunk = chunk_length == 0; self->src_read_state = HTTP_SR_READ_CHUNK; z_stream_line_set_poll_partial(stream, TRUE); /* fall through */ } else break; } case HTTP_SR_READ_CHUNK: if (!self->src_last_chunk) { res = z_stream_read(stream, buf, MIN(self->src_chunk_left, count), &br, &local_error); if (res == G_IO_STATUS_NORMAL) { self->src_whole_length += br; self->src_chunk_left -= br; *bytes_read = br; } else if (res == G_IO_STATUS_EOF) { /* unexpected eof */ /*LOG This message indicates that Zorp unexpectedly got EOF during chunk encoded data transfer. It is likely a caused by a buggy client or server. */ z_proxy_log(self->super.owner, HTTP_VIOLATION, 1, "Unexpected EOF while dechunking stream;"); res = G_IO_STATUS_ERROR; break; } if (self->src_chunk_left == 0) { self->src_read_state = HTTP_SR_READ_FOOTER; z_stream_line_set_poll_partial(stream, FALSE); } break; } else { self->src_read_state = HTTP_SR_READ_FOOTER; z_stream_line_set_poll_partial(stream, FALSE); /* fallthrough */ } case HTTP_SR_READ_FOOTER: { gchar *line; gsize line_length; if (!self->src_chunk_truncated) { res = z_stream_line_get(stream, &line, &line_length, NULL); } else { res = G_IO_STATUS_EOF; } if (res == G_IO_STATUS_NORMAL) { if (line_length != 0) { /*LOG This message indicates that the chunk footer contains data. It is likely caused by a buggy client or server. */ z_proxy_log(self->super.owner, HTTP_VIOLATION, 1, "Chunk footer is not an empty line;"); res = G_IO_STATUS_ERROR; break; } if (self->src_last_chunk) { res = G_IO_STATUS_EOF; } else { self->src_read_state = HTTP_SR_READ_CHUNK_LENGTH; z_stream_line_set_poll_partial(stream, TRUE); /* come back later */ res = G_IO_STATUS_AGAIN; } break; } break; } } } else { /* copy until EOF or self->content_length bytes */ if (self->content_length == HTTP_LENGTH_NONE) { res = G_IO_STATUS_EOF; } else { if (self->src_read_state == HTTP_SR_INITIAL) { self->src_whole_length = 0; self->src_read_state = HTTP_SR_READ_ENTITY; } if (self->content_length == HTTP_LENGTH_UNKNOWN) { if (owner->max_body_length && self->src_whole_length + count >= owner->max_body_length) { count = owner->max_body_length - self->src_whole_length; } if (count == 0) { self->force_nonpersistent_mode = TRUE; res = G_IO_STATUS_EOF; } else res = z_stream_read(stream, buf, count, &br, &local_error); } else { /* for specified content-length, max_body_length has already been processed, and content_length contains the number of bytes to be transferred, but maximum max_body_length */ if (self->content_length >= 0 && (guint64) self->content_length == self->src_whole_length) res = G_IO_STATUS_EOF; else res = z_stream_read(stream, buf, MIN(count, self->content_length - self->src_whole_length), &br, &local_error); } if (res == G_IO_STATUS_NORMAL) { self->src_whole_length += br; *bytes_read = br; } } } if (local_error) g_propagate_error(err, local_error); return res; }
/** * http_ftp_fetch_response: * @self: HttpProxy instance * @status: returned FTP status code * @msg: returned FTP message * @msglen: size of the @msg buffer * * This function is called to fetch a response from the FTP server. Line * continuations are supported however only the first line will be returned * in @msg. **/ static gboolean http_ftp_fetch_response(HttpProxy *self, gint *status, gchar *msg, gsize msglen) { gchar *line; gsize length; gboolean continuation = TRUE, first = TRUE; gint mul, value, i; msg[0] = 0; while (continuation) { if (z_stream_line_get(self->super.endpoints[EP_SERVER], &line, &length, NULL) != G_IO_STATUS_NORMAL) return FALSE; if (length < 4) { /*LOG This message indicates that the response given by the FTP server was too short, not even the mandatory status code was included. */ z_proxy_log(self, HTTP_VIOLATION, 2, "Invalid FTP response, line too short; line='%.*s'", (gint)length, line); return FALSE; } value = 0; mul = 100; for (i = 0; i < 3; i++) { if (!isdigit(line[i])) { /*LOG This message indicates that the FTP server gave an invalid response, the status code returned by the server was not numeric. */ z_proxy_log(self, HTTP_VIOLATION, 2, "Invalid FTP response, response code not numeric; line='%.*s'", (gint)length, line); return FALSE; } value = value + mul * (line[i] - '0'); mul = mul / 10; } if (first) { gint copy = MIN(msglen-1, length - 4 + 1); *status = value; memcpy(msg, &line[4], copy); msg[copy] = 0; } else if (*status != value) { /*LOG This message indicates that the FTP server gave an invalid response as the status code changed from the one which was present on the first line. */ z_proxy_log(self, HTTP_VIOLATION, 2, "Invalid FTP response, continuation line contains different status code; ftp_status='%d', line='%.*s'", *status, (gint)length, line); return FALSE; } continuation = line[3] == '-'; } return TRUE; }
int main(void) { ZStream *input; int pair[2], status = 0; pid_t pid; gchar *line; gsize length; if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) { perror("socketpair()"); return 254; } pid = fork(); if (pid < 0) { perror("fork()"); return 254; } else if (pid != 0) { close(pair[1]); g_return_val_if_fail(write(pair[0], "1\n2\n3\n", 6) == 6, 253); g_return_val_if_fail(write(pair[0], "0123", 4) == 4, 253); g_return_val_if_fail(write(pair[0], "4567\n", 5) == 5, 253); g_return_val_if_fail(write(pair[0], "0123456789", 10) == 10, 253); g_return_val_if_fail(write(pair[0], "abc\nAabc\nabcdef\n", 16) == 16, 253); /* Because of truncate A will be eliminated */ g_return_val_if_fail(write(pair[0], "0123456789", 10) == 10, 253); g_return_val_if_fail(write(pair[0], "0123456789", 10) == 10, 253); g_return_val_if_fail(write(pair[0], "012345678\nAabcdef\n", 18) == 18, 253); /* Because of truncate A will be eliminated */ g_return_val_if_fail(write(pair[0], "012345678\nAabcdef", 17) == 17, 253); close(pair[0]); waitpid(pid, &status, 0); } else { gint rc; guint i; printf("%d\n", getpid()); sleep(1); close(pair[0]); input = z_stream_fd_new(pair[1], "sockpair input"); input = z_stream_line_new(input, 10, ZRL_EOL_NL | ZRL_TRUNCATE); i = 0; if (!z_stream_unget(input, "A", 1, NULL)) { printf("Error on unget\n"); _exit(1); } rc = z_stream_line_get(input, &line, &length, NULL); while (rc == G_IO_STATUS_NORMAL) { if (i >= (sizeof(expected_outputs) / sizeof(gchar *)) || line[0] != 'A' || strlen(expected_outputs[i]) != length - 1 || memcmp(expected_outputs[i], line + 1, length - 1) != 0) { printf("Error checking line: [%.*s] (length: %"G_GSIZE_FORMAT"), should be: %s\n", (int) length - 1, line + 1, length - 1, expected_outputs[i]); _exit(1); } else { printf("line ok: %.*s\n", (int) length, line); } if (!z_stream_unget(input, "A", 1, NULL)) { printf("Error on unget\n"); _exit(1); } rc = z_stream_line_get(input, &line, &length, NULL); i++; } if (i < (sizeof(expected_outputs) / sizeof(gchar *))) { printf("Missing output %u of %"G_GSIZE_FORMAT"\n", i, (sizeof(expected_outputs) / sizeof(gchar *))); _exit(1); } close(pair[1]); _exit(0); } return status >> 8; }