1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "setup.h"
24
25#ifndef CURL_DISABLE_GOPHER
26
27/* -- WIN32 approved -- */
28#include <stdio.h>
29#include <string.h>
30#include <stdarg.h>
31#include <stdlib.h>
32#include <ctype.h>
33
34#ifdef WIN32
35#include <time.h>
36#include <io.h>
37#else
38#ifdef HAVE_SYS_SOCKET_H
39#include <sys/socket.h>
40#endif
41#include <netinet/in.h>
42#ifdef HAVE_SYS_TIME_H
43#include <sys/time.h>
44#endif
45#ifdef HAVE_UNISTD_H
46#include <unistd.h>
47#endif
48#include <netdb.h>
49#ifdef HAVE_ARPA_INET_H
50#include <arpa/inet.h>
51#endif
52#ifdef HAVE_NET_IF_H
53#include <net/if.h>
54#endif
55#ifdef HAVE_SYS_IOCTL_H
56#include <sys/ioctl.h>
57#endif
58
59#ifdef HAVE_SYS_PARAM_H
60#include <sys/param.h>
61#endif
62
63#ifdef HAVE_SYS_SELECT_H
64#include <sys/select.h>
65#endif
66
67
68#endif
69
70#include "urldata.h"
71#include <curl/curl.h>
72#include "transfer.h"
73#include "sendf.h"
74
75#include "progress.h"
76#include "strequal.h"
77#include "gopher.h"
78#include "rawstr.h"
79#include "select.h"
80#include "url.h"
81#include "warnless.h"
82
83#define _MPRINTF_REPLACE /* use our functions only */
84#include <curl/mprintf.h>
85
86/* The last #include file should be: */
87#include "memdebug.h"
88
89
90/*
91 * Forward declarations.
92 */
93
94static CURLcode gopher_do(struct connectdata *conn, bool *done);
95
96/*
97 * Gopher protocol handler.
98 * This is also a nice simple template to build off for simple
99 * connect-command-download protocols.
100 */
101
102const struct Curl_handler Curl_handler_gopher = {
103  "GOPHER",                             /* scheme */
104  ZERO_NULL,                            /* setup_connection */
105  gopher_do,                            /* do_it */
106  ZERO_NULL,                            /* done */
107  ZERO_NULL,                            /* do_more */
108  ZERO_NULL,                            /* connect_it */
109  ZERO_NULL,                            /* connecting */
110  ZERO_NULL,                            /* doing */
111  ZERO_NULL,                            /* proto_getsock */
112  ZERO_NULL,                            /* doing_getsock */
113  ZERO_NULL,                            /* perform_getsock */
114  ZERO_NULL,                            /* disconnect */
115  ZERO_NULL,                            /* readwrite */
116  PORT_GOPHER,                          /* defport */
117  CURLPROTO_GOPHER,                     /* protocol */
118  PROTOPT_NONE                          /* flags */
119};
120
121static CURLcode gopher_do(struct connectdata *conn, bool *done)
122{
123  CURLcode result=CURLE_OK;
124  struct SessionHandle *data=conn->data;
125  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
126
127  curl_off_t *bytecount = &data->req.bytecount;
128  char *path = data->state.path;
129  char *sel;
130  char *sel_org = NULL;
131  ssize_t amount, k;
132
133  *done = TRUE; /* unconditionally */
134
135  /* Create selector. Degenerate cases: / and /1 => convert to "" */
136  if(strlen(path) <= 2)
137    sel = (char *)"";
138  else {
139    char *newp;
140    size_t j, i;
141    int len;
142
143    /* Otherwise, drop / and the first character (i.e., item type) ... */
144    newp = path;
145    newp+=2;
146
147    /* ... then turn ? into TAB for search servers, Veronica, etc. ... */
148    j = strlen(newp);
149    for(i=0; i<j; i++)
150      if(newp[i] == '?')
151        newp[i] = '\x09';
152
153    /* ... and finally unescape */
154    sel = curl_easy_unescape(data, newp, 0, &len);
155    if(!sel)
156      return CURLE_OUT_OF_MEMORY;
157    sel_org = sel;
158  }
159
160  /* We use Curl_write instead of Curl_sendf to make sure the entire buffer is
161     sent, which could be sizeable with long selectors. */
162  k = curlx_uztosz(strlen(sel));
163
164  for(;;) {
165    result = Curl_write(conn, sockfd, sel, k, &amount);
166    if(CURLE_OK == result) { /* Which may not have written it all! */
167      result = Curl_client_write(conn, CLIENTWRITE_HEADER, sel, amount);
168      if(result) {
169        Curl_safefree(sel_org);
170        return result;
171      }
172      k -= amount;
173      sel += amount;
174      if(k < 1)
175        break; /* but it did write it all */
176    }
177    else {
178      failf(data, "Failed sending Gopher request");
179      Curl_safefree(sel_org);
180      return result;
181    }
182    /* Don't busyloop. The entire loop thing is a work-around as it causes a
183       BLOCKING behavior which is a NO-NO. This function should rather be
184       split up in a do and a doing piece where the pieces that aren't
185       possible to send now will be sent in the doing function repeatedly
186       until the entire request is sent.
187
188       Wait a while for the socket to be writable. Note that this doesn't
189       acknowledge the timeout.
190    */
191    Curl_socket_ready(CURL_SOCKET_BAD, sockfd, 100);
192  }
193
194  Curl_safefree(sel_org);
195
196  /* We can use Curl_sendf to send the terminal \r\n relatively safely and
197     save allocing another string/doing another _write loop. */
198  result = Curl_sendf(sockfd, conn, "\r\n");
199  if(result != CURLE_OK) {
200    failf(data, "Failed sending Gopher request");
201    return result;
202  }
203  result = Curl_client_write(conn, CLIENTWRITE_HEADER, (char *)"\r\n", 2);
204  if(result)
205    return result;
206
207  Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount,
208                      -1, NULL); /* no upload */
209  return CURLE_OK;
210}
211#endif /*CURL_DISABLE_GOPHER*/
212