File: gzillahttp.c

package info (click to toggle)
gzilla 0.1.5-4
  • links: PTS
  • area: main
  • in suites: slink
  • size: 972 kB
  • ctags: 1,396
  • sloc: ansic: 15,102; sh: 173; makefile: 117; perl: 18
file content (378 lines) | stat: -rw-r--r-- 9,149 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
/*

 gzilla

 Copyright 1997 Raph Levien <raph@acm.org>

 This code is free for commercial and non-commercial use,
 modification, and redistribution, as long as the source code release,
 startup screen, or product packaging includes this copyright notice.

 */


/* todo: make error messages more descriptive. */

#include <unistd.h>

#include <errno.h>            /* for errno */
#include <string.h>           /* for strstr */

#include <gtk/gtk.h>

#include "gzillabytesink.h"
#include "gzillaurl.h"
#include "gzillasocket.h"
#include "version.h"

typedef struct _GzillaHttp GzillaHttp;

typedef enum {
  GZILLA_HTTP_INIT,
  GZILLA_HTTP_SOCKET_WAIT,
  GZILLA_HTTP_SENDING,
  GZILLA_HTTP_RECEIVING,
  GZILLA_HTTP_ERROR
} GzillaHttpState;

struct _GzillaHttp {
  gint tag;
  gint socket_tag;
  GzillaHttpState state;
  gint fd;
  gint wr_tag, rd_tag;
  char *query;
  gint query_size;
  gint query_index;
  gint status_id;
  GzillaByteSink *bytesink;
};

static gint num_http;
static gint num_http_max;
static GzillaHttp *http;

static gint http_tag = 0;

void
gzilla_http_init (void)
{
  num_http = 0;
  num_http_max = 16;
  http = g_new (GzillaHttp, num_http_max);
}


/* The input function for reading the reply back from the socket. This
   function gets called whenever the socket has bytes available for
   reading. Try reading a buffer and writing it out to the bytesink.
   If we reach eof, then close the fd and bytesink. */

static void
gzilla_http_input_func (gpointer data,
			gint source,
			GdkInputCondition condition)
{
  gint tag = (gint)data;
  gint i;
  char buf[8192];
  gint num_bytes;

  for (i = 0; i < num_http; i++)
    {
      if (http[i].tag == tag)
	break;
    }
  if (i < num_http)
    {
      num_bytes = read (http[i].fd, buf, sizeof (buf));
#ifdef VERBOSE
      g_print ("http %d read %d bytes\n", i, num_bytes);
#endif
      if (num_bytes < 0)
	{
	  if (errno == EINTR || errno == EAGAIN)
	    return;
	  num_bytes = 0;
	}
      if (num_bytes > 0)
	gzilla_bytesink_write (http[i].bytesink, buf, num_bytes);
      else
	{
#ifdef VERBOSE
	  g_print ("closing http %d fd %d\n", i, http[i].fd);
#endif
	  gtk_signal_disconnect (GTK_OBJECT (http[i].bytesink),
				 http[i].status_id);
	  gdk_input_remove (http[i].rd_tag);
	  close (http[i].fd);
	  gzilla_bytesink_close (http[i].bytesink);
	  http[i] = http[--num_http];
	}
    }
  else
    g_warning ("gzilla_http_input_function: trying to write into http that has already been removed\n");
}

/* The input function for writing the query out to the socket. This
   function gets called whenever the socket is ready for writing. Try
   writing the query string out to the socket. When all of the bytes
   have been written, remove this input function and install the input
   function for reading the response back. */

static void
gzilla_http_query_func (gpointer data,
			gint source,
			GdkInputCondition condition)
{
  gint tag = (gint)data;
  gint i;
  gint num_bytes;

  for (i = 0; i < num_http; i++)
    {
      if (http[i].tag == tag)
	break;
    }
  if (i < num_http)
    {
      num_bytes = write (http[i].fd,
			 http[i].query + http[i].query_index,
			 http[i].query_size - http[i].query_index);
      if (num_bytes < 0)
	{
	  /* todo: test for EAGAIN and EINTR */
	  num_bytes = 0;
	}
      http[i].query_index += num_bytes;
      if (http[i].query_index == http[i].query_size)
	{
	  gdk_input_remove (http[i].wr_tag);
	  http[i].wr_tag = 0;
	  g_free (http[i].query);
	  http[i].query = NULL;
#ifdef VERBOSE
	  g_print ("http %d sent query \n", i);
#endif
	  http[i].state = GZILLA_HTTP_RECEIVING;
	  http[i].rd_tag = 
	    gdk_input_add (http[i].fd,
			   GDK_INPUT_READ,
			   (GdkInputFunction) gzilla_http_input_func,
			   (void *)http[i].tag);
	}
    }
  else
    g_warning ("gzilla_http_query_function: trying to write query from http that has already been removed\n");
}

/* The callback for socket setup. This function gets called whenever
   the socket setup completes. Install the input handler for writing
   the query string to the socket. */

static void
gzilla_http_callback (gint fd,
		      void *callback_data)
{
  gint tag = (gint)callback_data;
  gint i;
  GzillaHttpState old_state;

  for (i = 0; i < num_http; i++)
    {
      if (http[i].tag == tag)
	break;
    }
  if (i < num_http)
    {
#ifdef VERBOSE
      g_print ("http %d got fd %d\n", i, fd);
#endif
      if (fd >= 0)
	{
	  http[i].fd = fd;
	  http[i].wr_tag =
	    gdk_input_add (fd,
			   GDK_INPUT_WRITE,
			   (GdkInputFunction) gzilla_http_query_func,
			   (void *)http[i].tag);
	  http[i].state = GZILLA_HTTP_SENDING;
	  gzilla_bytesink_status (http[i].bytesink,
				  GZILLA_STATUS_DIR_DOWN,
				  FALSE,
				  GZILLA_STATUS_MEANING_SHOW,
				  "Connected");
	}
      else
	{
	  gzilla_bytesink_status (http[i].bytesink,
				  GZILLA_STATUS_DIR_DOWN,
				  TRUE,
				  GZILLA_STATUS_MEANING_SHOW,
				  "Connect failed");
	  g_free (http[i].query);
	  old_state = http[i].state;
	  http[i].state = GZILLA_HTTP_ERROR;
	  if (old_state != GZILLA_HTTP_INIT)
	    http[i] = http[--num_http];
	}
    }
  else
    g_warning ("gzilla_http_callback: set up a socket for an http that has already been removed\n");
}

/* This function gets called when the bytesink we're writing into gets
   aborted. It shuts down the connection and deletes the http
   structure, freeing any resources that were taken. */

void
gzilla_http_status (GzillaByteSink *bytesink,
		    GzillaStatusDir dir,
		    gboolean abort,
		    GzillaStatusMeaning meaning,
		    const char *text,
		    void *data)
{
  gint tag = (gint)data;
  gint i;

#ifdef VERBOSE
  g_print ("gzilla_http_status: %d %d %d %s\n",
	   dir, abort, meaning, text);
#endif

  if (!abort)
    return;

  if (dir == GZILLA_STATUS_DIR_UP)
    {
      for (i = 0; i < num_http; i++)
	{
	  if (http[i].tag == tag)
	    break;
	}
      if (i < num_http)
	{
	  if (http[i].state == GZILLA_HTTP_SOCKET_WAIT)
	    {
	      gzilla_socket_abort (http[i].socket_tag);
	      g_free (http[i].query);
	    }
	  else if (http[i].state == GZILLA_HTTP_SENDING)
	    {
	      g_free (http[i].query);
	      close (http[i].fd);
	      gdk_input_remove (http[i].wr_tag);
	    }
	  else if (http[i].state == GZILLA_HTTP_RECEIVING)
	    {
	      close (http[i].fd);
	      gdk_input_remove (http[i].rd_tag);
	    }
	  else
	    g_warning ("gzilla_http_status: http state is inconsistent\n");
	  http[i] = http[--num_http];
	}
      else
	g_warning ("gzilla_http_status: trying to abort a nonexistent http\n");
    }
}

/* Create a new http connection for URL url, and asynchronously
   feed the bytes that come back to bytesink. */
void
gzilla_http_get (const char *url,
		 GzillaByteSink *bytesink,
		 void (*status_callback) (const char *status, void *data),
		 void *data)
{
  char hostname[256];
  int port;
  char *tail;
  char query[1024];
  gint tag;
  gint i;

  port = 80;

  /* hacked-in support for proxies, inspired by Olivier Aubert */
  if (getenv ("http_proxy") != NULL &&
      !(getenv ("no_proxy") != NULL &&
	strstr (url, getenv ("no_proxy")) != NULL))
    {
      gzilla_url_parse ((char *) getenv ("http_proxy"),
			hostname, sizeof(hostname), &port);
      tail = (char *)url;
    }
  else
    tail = gzilla_url_parse ((char *)url, hostname, sizeof(hostname), &port);

  if (tail != NULL)
    {
      if (num_http == num_http_max)
	{
	  num_http_max <<= 1;
	  http = g_realloc (http, num_http_max * sizeof(GzillaHttp));
	}
      tag = ++http_tag;
      i = num_http;
      num_http++;
      http[i].tag = tag;
      http[i].state = GZILLA_HTTP_INIT;
      http[i].fd = -1;
      http[i].bytesink = bytesink;
      /* todo: check size */
      sprintf (query,
	       "GET %s HTTP/1.0\r\n"
	       "User-Agent: gzilla %s\r\n"
	       "Host: %s:%d\r\n"
	       "\r\n",
	       tail,
	       GZILLA_VERSION,
	       hostname,
	       port);
      http[i].query = g_strdup (query);
      http[i].query_size = strlen (query);
      http[i].query_index = 0;
      http[i].rd_tag = 0;
      http[i].wr_tag = 0;
      http[i].socket_tag = gzilla_socket_new (hostname,
					      port,
					      gzilla_http_callback,
					      (void *)tag);
      if (http[i].state == GZILLA_HTTP_INIT)
	http[i].state = GZILLA_HTTP_SOCKET_WAIT;
      if (http[i].state == GZILLA_HTTP_ERROR)
	{
	  /* If error setting up socket, delete the http structure
	     immediately.

	     todo: shouldn't this send a signal down the bytesink, so
	     that the bytesink knows not to expect any more data? */
	  http[i] = http[--num_http];
	  gzilla_bytesink_status (bytesink,
				  GZILLA_STATUS_DIR_DOWN,
				  TRUE,
				  GZILLA_STATUS_MEANING_SHOW,
				  "Connect failed");
	}
      else
	{
	  /* hook up abort signal handler */
	  http[i].status_id =
	    gtk_signal_connect (GTK_OBJECT (bytesink),
				"status",
				(GtkSignalFunc) gzilla_http_status,
				(void *)tag);
	  if (port == 80)
	    sprintf (query, "Connecting to %s", hostname);
	  else
	    sprintf (query, "Connecting to %s:%d", hostname, port);
	  gzilla_bytesink_status (bytesink,
				  GZILLA_STATUS_DIR_DOWN,
				  FALSE,
				  GZILLA_STATUS_MEANING_SHOW,
				  query);
	}
    }
}