Fix I/O in JsonConsumeWhitespace() and UtilStreamCopy().

These functions previously operated on the assumption that fgetc() would
block; however it will not block on HttpServer streams because those are
non-blocking. They now check error conditions properly before failing
prematurely.
This commit is contained in:
Jordan Bancino 2023-03-10 18:46:03 +00:00
parent fd12dee62e
commit 2d9b706f38
8 changed files with 76 additions and 20 deletions

View file

@ -15,6 +15,7 @@ Milestone: v0.3.0
[ ] TLS [ ] TLS
[ ] SOCKS [ ] SOCKS
[ ] Multi-output [ ] Multi-output
[ ] Move UtilStreamCopy()
[~] HTTP Client API [~] HTTP Client API
[ ] Document HttpParseHeaders() [ ] Document HttpParseHeaders()
[ ] HttpClient man page [ ] HttpClient man page

View file

@ -182,6 +182,9 @@ HttpRequestSend(HttpClientContext * context)
return 0; return 0;
} }
fflush(context->stream);
shutdown(fileno(context->stream), SHUT_WR);
lineLen = UtilGetLine(&line, &lineSize, context->stream); lineLen = UtilGetLine(&line, &lineSize, context->stream);
/* Line must contain at least "HTTP/x.x xxx" */ /* Line must contain at least "HTTP/x.x xxx" */

View file

@ -444,7 +444,7 @@ HttpServerWorkerThread(void *args)
* Every once in a while, we're too fast for the client. When this * Every once in a while, we're too fast for the client. When this
* happens, UtilGetLine() sets errno to EAGAIN. If we get * happens, UtilGetLine() sets errno to EAGAIN. If we get
* EAGAIN, then clear the error on the stream and try again * EAGAIN, then clear the error on the stream and try again
* after 1ms. This is typically more than enough time for the * after a few ms. This is typically more than enough time for the
* client to send data. */ * client to send data. */
firstRead = UtilServerTs(); firstRead = UtilServerTs();
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1 while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1
@ -453,7 +453,7 @@ HttpServerWorkerThread(void *args)
clearerr(fp); clearerr(fp);
/* If the server is stopped, or it's been a while, just /* If the server is stopped, or it's been a while, just
* give up. */ * give up so we aren't wasting a thread on this client. */
if (server->stop || (UtilServerTs() - firstRead) > 1000 * 30) if (server->stop || (UtilServerTs() - firstRead) > 1000 * 30)
{ {
goto finish; goto finish;

View file

@ -31,6 +31,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h>
struct JsonValue struct JsonValue
{ {
@ -705,8 +706,33 @@ static int
JsonConsumeWhitespace(JsonParserState * state) JsonConsumeWhitespace(JsonParserState * state)
{ {
int c; int c;
while (1)
{
c = fgetc(state->stream);
while (isspace(c = fgetc(state->stream))); if (feof(state->stream))
{
break;
}
if (ferror(state->stream))
{
if (errno == EAGAIN)
{
clearerr(state->stream);
continue;
}
else
{
break;
}
}
if (!isspace(c))
{
break;
}
}
return c; return c;
} }

View file

@ -246,7 +246,7 @@ UtilGetDelim(char **linePtr, size_t * n, int delim, FILE * stream)
while (1) while (1)
{ {
c = getc(stream); c = fgetc(stream);
if (ferror(stream) || (c == EOF && curPos == *linePtr)) if (ferror(stream) || (c == EOF && curPos == *linePtr))
{ {
@ -300,3 +300,38 @@ UtilGetLine(char **linePtr, size_t * n, FILE * stream)
{ {
return UtilGetDelim(linePtr, n, '\n', stream); return UtilGetDelim(linePtr, n, '\n', stream);
} }
size_t
UtilStreamCopy(FILE *in, FILE *out)
{
size_t bytes = 0;
int c;
while (1)
{
c = fgetc(in);
if (feof(in))
{
break;
}
if (ferror(in))
{
if (errno == EAGAIN)
{
clearerr(in);
continue;
}
else
{
break;
}
}
fputc(c, out);
bytes++;
}
return bytes;
}

View file

@ -50,4 +50,7 @@ extern ssize_t
extern ssize_t extern ssize_t
UtilGetLine(char **, size_t *, FILE *); UtilGetLine(char **, size_t *, FILE *);
extern size_t
UtilStreamCopy(FILE *, FILE *);
#endif /* TELODENDRIA_UTIL_H */ #endif /* TELODENDRIA_UTIL_H */

View file

@ -126,7 +126,7 @@ recipe_build() {
out=$(basename "$src" .c) out=$(basename "$src" .c)
out="build/tools/$out" out="build/tools/$out"
if [ $(mod_time "$src") -ge $(mod_time "$out") ]; then if [ $(mod_time "$src") -ge $(mod_time "$out") ] || [ $do_rebuild -eq 1 ]; then
echo "CC $(basename $out)" echo "CC $(basename $out)"
mkdir -p "$(dirname $out)" mkdir -p "$(dirname $out)"
if ! $CC $CFLAGS $LDFLAGS -Isrc/include -o "$out" $objs "$src"; then if ! $CC $CFLAGS $LDFLAGS -Isrc/include -o "$out" $objs "$src"; then

View file

@ -32,22 +32,10 @@
#include <HashMap.h> #include <HashMap.h>
#include <HttpClient.h> #include <HttpClient.h>
#include <Uri.h> #include <Uri.h>
#include <Util.h>
#define FLAG_HEADERS (1 << 0) #define FLAG_HEADERS (1 << 0)
/* TODO: Will eventually be provided by the Stream API */
static void
StreamCopy(FILE * in, FILE * out)
{
int c;
/* TODO: This should be buffered, not char-by-char */
while ((c = fgetc(in)) != EOF)
{
fputc(c, out);
}
}
static void static void
usage(char *prog) usage(char *prog)
{ {
@ -170,7 +158,7 @@ main(int argc, char **argv)
* from blocking if no pipe is provided */ * from blocking if no pipe is provided */
if (!isatty(fileno(stdin))) if (!isatty(fileno(stdin)))
{ {
StreamCopy(stdin, HttpClientStream(cx)); UtilStreamCopy(stdin, HttpClientStream(cx));
} }
res = HttpRequestSend(cx); res = HttpRequestSend(cx);
@ -197,7 +185,7 @@ main(int argc, char **argv)
printf("\n"); printf("\n");
} }
StreamCopy(HttpClientStream(cx), stdout); UtilStreamCopy(HttpClientStream(cx), stdout);
HttpClientContextFree(cx); HttpClientContextFree(cx);
UriFree(uri); UriFree(uri);