vil_stream_url.cxx
Go to the documentation of this file.
1 // This is core/vil/vil_stream_url.cxx
2 //:
3 // \file
4 // \author fsm
5 
6 #include <cstdio>
7 #include <cstring>
8 #include <cstdlib>
9 #include <string>
10 #include <iostream>
11 #include <fstream>
12 #include "vil_stream_url.h"
13 #include <vil/vil_stream_core.h>
14 
15 #include <cassert>
16 #undef sprintf
17 #ifdef _MSC_VER
18 # include <vcl_msvc_warnings.h>
19 #endif
20 
21 #if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
22 
23 # include <unistd.h> // read(), write(), close()
24 # include <netdb.h> // gethostbyname(), sockaddr_in()
25 # include <sys/socket.h>
26 # include <netinet/in.h> // htons()
27 # define SOCKET int
28 #elif defined (_WIN32) && !defined(__CYGWIN__)
29 # include <winsock2.h>
30 #endif
31 
32 
33 static const
34 char base64_encoding[]=
35 {
36  'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
37  'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
38  'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
39  'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
40 };
41 
42 static char out_buf[4];
43 
44 static const char * encode_triplet(const char data[3], unsigned n)
45 {
46  assert (n>0 && n <4);
47  out_buf[0] = base64_encoding[(data[0] & 0xFC) >> 2];
48 
49  if (n==1)
50  {
51  out_buf[2] = out_buf[3] = '=';
52  return out_buf;
53  }
54 
55  out_buf[1] = base64_encoding[
56  ((data[0] & 0x3) << 4) + ((data[1] & 0xf0)>>4)];
57  out_buf[2] = base64_encoding[
58  ((data[1] & 0xf) << 2) + ((data[2] & 0xc0)>>6)];
59 
60  if (n==2)
61  {
62  out_buf[3] = '=';
63  return out_buf;
64  }
65 
66  out_buf[3] = base64_encoding[ (data[2] & 0x3f) ];
67  return out_buf;
68 }
69 
70 //=======================================================================
71 
72 static std::string encode_base64(const std::string& in)
73 {
74  std::string out;
75  unsigned int i = 0, line_octets = 0;
76  const auto l = (unsigned int)(in.size());
77  char data[3];
78  while (i < l)
79  {
80  data[0] = in[i++];
81  data[1] = data[2] = 0;
82 
83  if (i == l)
84  {
85  out.append(encode_triplet(data,1),4);
86  return out;
87  }
88 
89  data[1] = in[i++];
90 
91  if (i == l)
92  {
93  out.append(encode_triplet(data,2),4);
94  return out;
95  }
96 
97  data[2] = in[i++];
98 
99  out.append(encode_triplet(data,3),4);
100 
101  if (line_octets >= 68/4) // print carriage return
102  {
103  out.append("\r\n",2);
104  line_octets = 0;
105  }
106  else
107  ++line_octets;
108  }
109 
110  return out;
111 }
112 
113 
115  : u_(nullptr)
116 {
117  if (std::strncmp(url, "http://", 7) != 0)
118  return; // doesn't look like a URL to me....
119 
120  char const *p = url+7;
121  while (*p && *p!='/')
122  ++p;
123 
124  // split URL into auth, host, path and port number.
125  std::string host = std::string(url+7, p);
126  std::string path = (*p) ? p+1 : "";
127  std::string auth;
128  int port = 80; // default
129 
130  // authentication
131  for (unsigned int i=0; i<host.size(); ++i)
132  if (host[i] == '@') {
133  auth = std::string(host.c_str(), host.c_str()+i);
134  host = std::string(host.c_str()+i+1, host.c_str() + host.size());
135  break;
136  }
137 
138  // port?
139  if (host.size() > 0)
140  for (auto i=(unsigned int)(host.size()-1); i>0; --i)
141  if (host[i] == ':') {
142  port = std::atoi(host.c_str() + i + 1);
143  host = std::string(host.c_str(), host.c_str() + i);
144  break;
145  }
146 
147  // do character translation
148  for (unsigned k =0; k < path.size(); ++k)
149  if (path[k] == ' ')
150  path.replace(k, 1, "%20");
151  else if (path[k] == '%')
152  path.replace(k, 1, "%25");
153 
154  // so far so good.
155 #ifdef DEBUG
156  std::cerr << "auth = \'" << auth << "\'\n"
157  << "host = \'" << host << "\'\n"
158  << "path = \'" << path << "\'\n"
159  << "port = " << port << std::endl;
160 #endif
161 
162 #if defined(_WIN32) && !defined(__CYGWIN__)
163  static int called_WSAStartup;
164  if (called_WSAStartup==0)
165  {
166  WORD wVersionRequested;
167  WSADATA wsaData;
168 
169  wVersionRequested = MAKEWORD( 2, 2 );
170 
171  /* int err = */ WSAStartup( wVersionRequested, &wsaData );
172  }
173 #endif
174 
175  // create socket endpoint.
176  SOCKET tcp_socket = socket(PF_INET, // IPv4 protocols.
177  SOCK_STREAM, // two-way, reliable, connection-based stream socket.
178  PF_UNSPEC); // protocol number.
179 
180 #if defined(_WIN32) && !defined(__CYGWIN__)
181  if (tcp_socket == INVALID_SOCKET) {
182  std::cerr << __FILE__ ": failed to create socket.\n";
183 # ifndef NDEBUG
184  std::cerr << "error code : " << WSAGetLastError() << std::endl;
185 # endif
186  return;
187  }
188 #else
189  if (tcp_socket < 0)
190  std::cerr << __FILE__ ": failed to create socket.\n";
191 #endif
192 
193 #ifdef DEBUG
194  std::cerr << __FILE__ ": tcp_sockect = " << tcp_socket << std::endl;
195 #endif
196 
197  // get network address of server.
198  hostent *hp = gethostbyname(host.c_str());
199  if (! hp) {
200  std::cerr << __FILE__ ": failed to lookup host\n";
201 #if defined(_WIN32) && !defined(__CYGWIN__)
202  closesocket(tcp_socket);
203 #else
204  close(tcp_socket);
205 #endif
206  return;
207  }
208 
209  // make socket address.
210  sockaddr_in my_addr;
211  my_addr.sin_family = AF_INET;
212  my_addr.sin_port = htons(port); // convert port number to network byte order..
213  std::memcpy(&my_addr.sin_addr, hp->h_addr_list[0], hp->h_length);
214 
215  // connect to server.
216  if (connect(tcp_socket , (sockaddr *) &my_addr, sizeof my_addr) < 0) {
217  std::cerr << __FILE__ ": failed to connect to host\n";
218  //perror(__FILE__);
219 #if defined(_WIN32) && !defined(__CYGWIN__)
220  closesocket(tcp_socket);
221 #else
222  close(tcp_socket);
223 #endif
224  return;
225  }
226 
227  // buffer for data transfers over socket.
228 
229  char buffer[4096];
230 
231  // send HTTP 1.1 request.
232  std::snprintf(buffer, 4090, "GET /%s / HTTP/1.1\r\n", path.c_str());
233  if (auth != "")
234  std::snprintf(buffer+std::strlen(buffer), 4090-std::strlen(buffer),
235  "Authorization: Basic %s\n", encode_base64(auth).c_str());
236 
237  if (std::snprintf(buffer+std::strlen(buffer), 4090-std::strlen(buffer), "\r\n") < 0)
238  {
239  std::cerr << "ERROR: vil_stream_url buffer overflow.";
240  std::abort();
241  }
242 
243 #if defined(_WIN32) && !defined(__CYGWIN__)
244  if (send(tcp_socket, buffer, (int)std::strlen(buffer), 0) < 0)
245  {
246  std::cerr << __FILE__ ": error sending HTTP request\n";
247  closesocket(tcp_socket);
248  return;
249  }
250 #else
251  if (::write(tcp_socket, buffer, std::strlen(buffer)) < 0)
252  {
253  std::cerr << __FILE__ ": error sending HTTP request\n";
254  close(tcp_socket);
255  return;
256  }
257 #endif
258 
259 
260 // std::ofstream test2("/test2.jpg", std::ios::binary);
261 
262  // read from socket into memory.
263  u_ = new vil_stream_core;
264  u_->ref();
265  {
266  unsigned entity_marker = 0; // count end of header CR and LFs
267  vil_streampos n;
268 #if defined(_WIN32) && !defined(__CYGWIN__)
269  while ((n = recv(tcp_socket, buffer, sizeof buffer,0 )) > 0L)
270 #else
271  while ((n = ::read(tcp_socket, buffer, sizeof buffer)) > 0L)
272 #endif
273  {
274  // search for the CRLFCRLF sequence that marks the end
275  // of the http response header
276  assert (entity_marker < 5);
277  if (entity_marker==4)
278  {
279  u_->write(buffer, n);
280 // test2.write(buffer, n);
281  }
282  else
283  {
284  for (vil_streampos i=0; i<n; ++i)
285  {
286  if ((entity_marker==2||entity_marker==0) && buffer[i]=='\r') entity_marker++;
287  else if (entity_marker==1 && buffer[i]=='\n') entity_marker++;
288  else if (entity_marker==3 && buffer[i]=='\n')
289  {
290  entity_marker++;
291  u_->write(buffer+i+1, n-i-1);
292 // test2.write(buffer+i+1, n-i-1);
293  break;
294  }
295  else entity_marker=0;
296  }
297  }
298  }
299  }
300 
301 #if 0 // useful for figuring out where the error is
302  char btest[4096];
303  std::ofstream test("/test.jpg", std::ios::binary);
304  u_->seek(0L);
305  while (vil_streampos bn = u_->read(btest, 4096L))
306  test.write(btest, bn);
307  test.close();
308 #endif
309 
310 
311  // close connection to server.
312 #if defined(_WIN32) && !defined(__CYGWIN__)
313  closesocket(tcp_socket);
314 #else
315  close(tcp_socket);
316 #endif
317 }
318 
320 {
321  if (u_) {
322  u_->unref();
323  u_ = nullptr;
324  }
325 }
vil_stream_url(vil_stream_url const &)
virtual vil_streampos write(void const *buf, vil_streampos n)=0
Write n bytes from buf. Returns number of bytes written.
vil_streampos read(void *buf, vil_streampos n) override
Read n bytes into buf. Returns number of bytes read.
virtual void seek(vil_streampos position)=0
Goto file pointer.
virtual vil_streampos read(void *buf, vil_streampos n)=0
Read n bytes into buf. Returns number of bytes read.
open an URL
vil_stream * u_
void ref()
up/down the reference count.
Definition: vil_stream.h:45
void unref()
Definition: vil_stream.cxx:31
~vil_stream_url() override
An in-core vil_stream implementation.
vxl_int_32 vil_streampos
Definition: vil_stream.h:16
An in-core vil_stream implementation.
vil_streampos write(void const *buf, vil_streampos n) override
Write n bytes from buf. Returns number of bytes written.