http.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /*
  2. * http client for RT-Thread
  3. */
  4. #include "http.h"
  5. #include <dfs_posix.h>
  6. #include <lwip/sockets.h>
  7. #include <lwip/netdb.h>
  8. const char _http_get[] = "GET ";
  9. const char _http_host[] = "Host: ";
  10. const char _http_getend[] = " HTTP/1.0\r\n";
  11. const char _http_user_agent[] = "User-Agent: RT-Thread HTTP Agent\r\n";
  12. const char _http_endheader[] = "\r\n";
  13. const char _shoutcast_get[] = "GET %s HTTP/1.0\r\nHost: %s:%d\r\nUser-Agent: RT-Thread HTTP Agent\r\nIcy-MetaData: 1\r\nConnection: close\r\n\r\n";
  14. extern long int strtol(const char *nptr, char **endptr, int base);
  15. //
  16. // This function will parse the Content-Length header line and return the file size
  17. //
  18. int http_parse_content_length(char *mime_buf)
  19. {
  20. char *line;
  21. line = strstr(mime_buf, "CONTENT-LENGTH:");
  22. line += strlen("CONTENT-LENGTH:");
  23. // Advance past any whitepace characters
  24. while((*line == ' ') || (*line == '\t')) line++;
  25. return (int)strtol(line, RT_NULL, 10);
  26. }
  27. //
  28. // This function will parse the initial response header line and return 0 for a "200 OK",
  29. // or return the error code in the event of an error (such as 404 - not found)
  30. //
  31. int http_is_error_header(char *mime_buf)
  32. {
  33. char *line;
  34. int i;
  35. int code;
  36. line = strstr(mime_buf, "HTTP/1.");
  37. line += strlen("HTTP/1.");
  38. // Advance past minor protocol version number
  39. line++;
  40. // Advance past any whitespace characters
  41. while((*line == ' ') || (*line == '\t')) line++;
  42. // Terminate string after status code
  43. for(i = 0; ((line[i] != ' ') && (line[i] != '\t')); i++);
  44. line[i] = '\0';
  45. code = (int)strtol(line, RT_NULL, 10);
  46. if( code == 200 )
  47. return 0;
  48. else
  49. return code;
  50. }
  51. int shoutcast_is_error_header(char *mime_buf)
  52. {
  53. char *line;
  54. int i;
  55. int code;
  56. line = strstr(mime_buf, "ICY");
  57. line += strlen("ICY");
  58. // Advance past minor protocol version number
  59. line++;
  60. // Advance past any whitespace characters
  61. while((*line == ' ') || (*line == '\t')) line++;
  62. // Terminate string after status code
  63. for(i = 0; ((line[i] != ' ') && (line[i] != '\t')); i++);
  64. line[i] = '\0';
  65. code = (int)strtol(line, RT_NULL, 10);
  66. if( code == 200 )
  67. return 0;
  68. else
  69. return code;
  70. }
  71. //
  72. // When a request has been sent, we can expect mime headers to be
  73. // before the data. We need to read exactly to the end of the headers
  74. // and no more data. This readline reads a single char at a time.
  75. //
  76. int http_read_line( int socket, char * buffer, int size )
  77. {
  78. char * ptr = buffer;
  79. int count = 0;
  80. int rc;
  81. // Keep reading until we fill the buffer.
  82. while ( count < size )
  83. {
  84. rc = recv( socket, ptr, 1, 0 );
  85. if ( rc <= 0 ) return rc;
  86. if ((*ptr == '\n'))
  87. {
  88. ptr ++;
  89. count++;
  90. break;
  91. }
  92. // increment after check for cr. Don't want to count the cr.
  93. count++;
  94. ptr++;
  95. }
  96. // Terminate string
  97. *ptr = '\0';
  98. // return how many bytes read.
  99. return count;
  100. }
  101. //
  102. // Before we can connect we need to parse the server address and optional
  103. // port from the url provided. the format of "url" as passed to this function
  104. // is "//192.168.0.1:8080/blah.elf" where "192.168.0.1" can be either an IP
  105. // or a domain name and ":8080" is the optional port to connect to, default
  106. // port is 80.
  107. //
  108. // This function will return a filename string for use in GET
  109. // requests, and fill the structure pointed to by *server with the
  110. // correct values.
  111. //
  112. const char *http_resolve_address( struct sockaddr_in *server, const char * url, char *host_addr)
  113. {
  114. char *ptr;
  115. char port[6] = "80"; /* default port of 80(HTTP) */
  116. int i = 0, is_domain;
  117. struct hostent *hptr;
  118. /* strip http: */
  119. ptr = strchr(url, ':');
  120. if (ptr != NULL)
  121. {
  122. url = ptr + 1;
  123. }
  124. /* URL must start with double forward slashes. */
  125. if((url[0] != '/') || (url[1] != '/' )) return(NULL);
  126. url += 2; is_domain = 0;
  127. for(i = 0; ((url[i] != '\0') && (url[i] != '/')) && (i < 30); i++)
  128. {
  129. if((((host_addr[i] = url[i]) < '0') || (url[i] > '9')) && (url[i] != '.'))
  130. {
  131. if(url[i] == ':')
  132. {
  133. unsigned char w;
  134. /* allow specification of port in URL like http://www.server.net:8080/ */
  135. for(w = 0; ((w + i + 1) < 127) && (w < 5) && (url[w + i + 1] != '/') && (url[w + i + 1] != '\0'); w++)
  136. port[w] = url[w + i + 1];
  137. port[w] = '\0';
  138. rt_kprintf("HTTP: using port %s for connection\n", port);
  139. break;
  140. }
  141. else is_domain = 1;
  142. }
  143. }
  144. /* get host addr ok. */
  145. host_addr[i] = '\0';
  146. if (is_domain)
  147. {
  148. /* resolve the host name. */
  149. hptr = gethostbyname(host_addr);
  150. if(hptr == 0)
  151. {
  152. rt_kprintf("HTTP: failed to resolve domain '%s'\n", host_addr);
  153. return RT_NULL;
  154. }
  155. memcpy(&server->sin_addr, *hptr->h_addr_list, sizeof(server->sin_addr));
  156. }
  157. else
  158. {
  159. inet_aton(host_addr, (struct in_addr*)&(server->sin_addr));
  160. }
  161. /* set the port */
  162. server->sin_port = htons((int) strtol(port, NULL, 10));
  163. server->sin_family = AF_INET;
  164. while (*url != '/') url ++;
  165. return url;
  166. }
  167. //
  168. // This is the main HTTP client connect work. Makes the connection
  169. // and handles the protocol and reads the return headers. Needs
  170. // to leave the stream at the start of the real data.
  171. //
  172. static int http_connect(struct http_session* session,
  173. struct sockaddr_in * server, char *host_addr, const char * url)
  174. {
  175. int socket_handle;
  176. int peer_handle;
  177. int rc;
  178. char mimeBuffer[100];
  179. if((socket_handle = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP )) < 0)
  180. {
  181. rt_kprintf( "HTTP: SOCKET FAILED\n" );
  182. return -1;
  183. }
  184. peer_handle = connect( socket_handle, (struct sockaddr *) server, sizeof(*server));
  185. if ( peer_handle < 0 )
  186. {
  187. rt_kprintf( "HTTP: CONNECT FAILED %i\n", peer_handle );
  188. return -1;
  189. }
  190. // Needs more error checking here.....
  191. #if 0
  192. rc = send( peer_handle, _http_get, sizeof( _http_get ) - 1, 0 );
  193. rc = send( peer_handle, (void*) url, strlen( url ), 0 );
  194. rc = send( peer_handle, _http_getend, sizeof( _http_getend ) - 1, 0 );
  195. rc = send( peer_handle, _http_host, sizeof( _http_host ) - 1, 0 );
  196. rc = send( peer_handle, host_addr, strlen( host_addr ), 0 );
  197. rc = send( peer_handle, _http_endheader, sizeof( _http_endheader ) - 1, 0 ); // "\r\n"
  198. rc = send( peer_handle, _http_user_agent, sizeof( _http_user_agent ) - 1, 0 );
  199. rc = send( peer_handle, _http_endheader, sizeof( _http_endheader ) - 1, 0 );
  200. #else
  201. {
  202. rt_uint8_t *ptr, *buf;
  203. buf = rt_malloc (512);
  204. ptr = buf;
  205. rt_memcpy(ptr, _http_get, sizeof(_http_get) - 1);
  206. ptr += sizeof(_http_get) - 1;
  207. rt_memcpy(ptr, url, strlen(url));
  208. ptr += strlen(url);
  209. rt_memcpy(ptr, _http_getend, sizeof(_http_getend) - 1);
  210. ptr += sizeof(_http_getend) - 1;
  211. rt_memcpy(ptr, _http_host, sizeof(_http_host) - 1);
  212. ptr += sizeof(_http_host) - 1;
  213. rt_memcpy(ptr, host_addr, strlen(host_addr));
  214. ptr += strlen(host_addr);
  215. rt_memcpy(ptr, _http_endheader, sizeof(_http_endheader) - 1);
  216. ptr += sizeof(_http_endheader) - 1;
  217. rt_memcpy(ptr, _http_user_agent, sizeof(_http_user_agent) - 1);
  218. ptr += sizeof(_http_user_agent) - 1;
  219. rt_memcpy(ptr, _http_endheader, sizeof(_http_endheader) - 1);
  220. ptr += sizeof(_http_endheader) - 1;
  221. rc = send(peer_handle, buf,
  222. (rt_uint32_t)ptr - (rt_uint32_t)buf, 0);
  223. }
  224. #endif
  225. // We now need to read the header information
  226. while ( 1 )
  227. {
  228. int i;
  229. // read a line from the header information.
  230. rc = http_read_line( peer_handle, mimeBuffer, 100 );
  231. rt_kprintf(">> %s\n", mimeBuffer);
  232. if ( rc < 0 ) return rc;
  233. // End of headers is a blank line. exit.
  234. if (rc == 0) break;
  235. if ((rc == 2) && (mimeBuffer[0] == '\r')) break;
  236. // Convert mimeBuffer to upper case, so we can do string comps
  237. for(i = 0; i < strlen(mimeBuffer); i++)
  238. mimeBuffer[i] = toupper(mimeBuffer[i]);
  239. if(strstr(mimeBuffer, "HTTP/1.")) // First line of header, contains status code. Check for an error code
  240. {
  241. rc = http_is_error_header(mimeBuffer);
  242. if(rc)
  243. {
  244. rt_kprintf("HTTP: status code = %d!\n", rc);
  245. return -rc;
  246. }
  247. }
  248. if(strstr(mimeBuffer, "CONTENT-LENGTH:"))
  249. {
  250. session->size = http_parse_content_length(mimeBuffer);
  251. rt_kprintf("size = %d\n", session->size);
  252. }
  253. }
  254. // We've sent the request, and read the headers. SockHandle is
  255. // now at the start of the main data read for a file io read.
  256. return peer_handle;
  257. }
  258. struct http_session* http_session_open(char* url)
  259. {
  260. int peer_handle = 0;
  261. struct sockaddr_in server;
  262. const char *get_name;
  263. char host_addr[32];
  264. struct http_session* session;
  265. session = (struct http_session*) rt_malloc(sizeof(struct http_session));
  266. if(session == RT_NULL) return RT_NULL;
  267. session->size = 0;
  268. session->position = 0;
  269. /* Check valid IP address and URL */
  270. get_name = http_resolve_address(&server, url, &host_addr[0]);
  271. if(get_name == NULL)
  272. {
  273. rt_free(session);
  274. return RT_NULL;
  275. }
  276. // Now we connect and initiate the transfer by sending a
  277. // request header to the server, and receiving the response header
  278. if((peer_handle = http_connect(session, &server, host_addr, get_name)) < 0)
  279. {
  280. rt_kprintf("HTTP: failed to connect to '%s'!\n", host_addr);
  281. rt_free(session);
  282. return RT_NULL;
  283. }
  284. // http connect returns valid socket. Save in handle list.
  285. session->socket = peer_handle;
  286. /* open successfully */
  287. return session;
  288. }
  289. rt_size_t http_session_read(struct http_session* session, rt_uint8_t *buffer, rt_size_t length)
  290. {
  291. int bytesRead = 0;
  292. int totalRead = 0;
  293. int left = length;
  294. // Read until: there is an error, we've read "size" bytes or the remote
  295. // side has closed the connection.
  296. do
  297. {
  298. bytesRead = recv(session->socket, buffer + totalRead, left, 0);
  299. if(bytesRead <= 0) break;
  300. left -= bytesRead;
  301. totalRead += bytesRead;
  302. } while(left);
  303. return totalRead;
  304. }
  305. rt_off_t http_session_seek(struct http_session* session, rt_off_t offset, int mode)
  306. {
  307. switch(mode)
  308. {
  309. case SEEK_SET:
  310. session->position = offset;
  311. break;
  312. case SEEK_CUR:
  313. session->position += offset;
  314. break;
  315. case SEEK_END:
  316. session->position = session->size + offset;
  317. break;
  318. }
  319. return session->position;
  320. }
  321. int http_session_close(struct http_session* session)
  322. {
  323. lwip_close(session->socket);
  324. rt_free(session);
  325. return 0;
  326. }
  327. //
  328. // This is the main HTTP client connect work. Makes the connection
  329. // and handles the protocol and reads the return headers. Needs
  330. // to leave the stream at the start of the real data.
  331. //
  332. static int shoutcast_connect(struct shoutcast_session* session,
  333. struct sockaddr_in* server, char *host_addr, const char * url)
  334. {
  335. int socket_handle;
  336. int peer_handle;
  337. int rc;
  338. char mimeBuffer[100];
  339. if((socket_handle = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP )) < 0)
  340. {
  341. rt_kprintf( "ICY: SOCKET FAILED\n" );
  342. return -1;
  343. }
  344. peer_handle = connect( socket_handle, (struct sockaddr *) server, sizeof(*server));
  345. if ( peer_handle < 0 )
  346. {
  347. rt_kprintf( "ICY: CONNECT FAILED %i\n", peer_handle );
  348. return -1;
  349. }
  350. {
  351. rt_uint8_t *buf;
  352. rt_uint32_t length;
  353. buf = rt_malloc (512);
  354. length = rt_snprintf(buf, 512, _shoutcast_get, url, host_addr, server->sin_port);
  355. rc = send(peer_handle, buf, length, 0);
  356. rt_kprintf("SHOUTCAST request:\n%s", buf);
  357. /* release buffer */
  358. rt_free(buf);
  359. }
  360. /* read the header information */
  361. while ( 1 )
  362. {
  363. // read a line from the header information.
  364. rc = http_read_line(peer_handle, mimeBuffer, 100);
  365. rt_kprintf(">>%s", mimeBuffer);
  366. if ( rc < 0 ) return rc;
  367. // End of headers is a blank line. exit.
  368. if (rc == 0) break;
  369. if ((rc == 2) && (mimeBuffer[0] == '\r')) break;
  370. if(strstr(mimeBuffer, "ICY")) // First line of header, contains status code. Check for an error code
  371. {
  372. rc = shoutcast_is_error_header(mimeBuffer);
  373. if(rc)
  374. {
  375. rt_kprintf("ICY: status code = %d!\n", rc);
  376. return -rc;
  377. }
  378. }
  379. if (strstr(mimeBuffer, "HTTP/1."))
  380. {
  381. rc = http_is_error_header(mimeBuffer);
  382. if(rc)
  383. {
  384. rt_kprintf("HTTP: status code = %d!\n", rc);
  385. return -rc;
  386. }
  387. }
  388. if (strstr(mimeBuffer, "icy-name:"))
  389. {
  390. /* get name */
  391. char* name;
  392. name = mimeBuffer + strlen("icy-name:");
  393. session->station_name = rt_strdup(name);
  394. rt_kprintf("station name: %s\n", session->station_name);
  395. }
  396. if (strstr(mimeBuffer, "icy-br:"))
  397. {
  398. /* get bitrate */
  399. session->bitrate = strtol(mimeBuffer + strlen("icy-br:"), RT_NULL, 10);
  400. rt_kprintf("bitrate: %d\n", session->bitrate);
  401. }
  402. if (strstr(mimeBuffer, "icy-metaint:"))
  403. {
  404. /* get metaint */
  405. session->metaint = strtol(mimeBuffer + strlen("icy-metaint:"), RT_NULL, 10);
  406. rt_kprintf("metaint: %d\n", session->metaint);
  407. }
  408. if (strstr(mimeBuffer, "content-type:"))
  409. {
  410. /* check content-type */
  411. if (strstr(mimeBuffer, "content-type:audio/mpeg") == RT_NULL)
  412. {
  413. rt_kprintf("ICY content is not audio/mpeg.\n");
  414. return -1;
  415. }
  416. }
  417. if (strstr(mimeBuffer, "Content-Type:"))
  418. {
  419. /* check content-type */
  420. if (strstr(mimeBuffer, "Content-Type: audio/mpeg") == RT_NULL)
  421. {
  422. rt_kprintf("ICY content is not audio/mpeg.\n");
  423. return -1;
  424. }
  425. }
  426. }
  427. // We've sent the request, and read the headers. SockHandle is
  428. // now at the start of the main data read for a file io read.
  429. return peer_handle;
  430. }
  431. struct shoutcast_session* shoutcast_session_open(char* url)
  432. {
  433. int peer_handle = 0;
  434. struct sockaddr_in server;
  435. const char *get_name;
  436. char host_addr[32];
  437. struct shoutcast_session* session;
  438. session = (struct shoutcast_session*) rt_malloc(sizeof(struct shoutcast_session));
  439. if(session == RT_NULL) return RT_NULL;
  440. session->metaint = 0;
  441. session->current_meta_chunk = 0;
  442. session->bitrate = 0;
  443. session->station_name = RT_NULL;
  444. /* Check valid IP address and URL */
  445. get_name = http_resolve_address(&server, url, &host_addr[0]);
  446. if(get_name == NULL)
  447. {
  448. rt_free(session);
  449. return RT_NULL;
  450. }
  451. // Now we connect and initiate the transfer by sending a
  452. // request header to the server, and receiving the response header
  453. if((peer_handle = shoutcast_connect(session, &server, host_addr, get_name)) < 0)
  454. {
  455. rt_kprintf("SHOUTCAST: failed to connect to '%s'!\n", host_addr);
  456. if (session->station_name != RT_NULL)
  457. rt_free(session->station_name);
  458. rt_free(session);
  459. return RT_NULL;
  460. }
  461. // http connect returns valid socket. Save in handle list.
  462. session->socket = peer_handle;
  463. /* open successfully */
  464. return session;
  465. }
  466. rt_size_t shoutcast_session_read(struct shoutcast_session* session, rt_uint8_t *buffer, rt_size_t length)
  467. {
  468. int bytesRead = 0;
  469. int totalRead = 0;
  470. int left = length;
  471. // Read until: there is an error, we've read "size" bytes or the remote
  472. // side has closed the connection.
  473. do
  474. {
  475. bytesRead = recv(session->socket, buffer + totalRead, left, 0);
  476. if(bytesRead <= 0) break;
  477. left -= bytesRead;
  478. totalRead += bytesRead;
  479. } while(left);
  480. /* handle meta */
  481. if (session->current_meta_chunk + totalRead >= session->metaint)
  482. {
  483. int meta_length, next_chunk_length;
  484. // rt_kprintf("c: %d, total: %d\n", session->current_meta_chunk, totalRead);
  485. /* get the length of meta data */
  486. meta_length = buffer[session->metaint - session->current_meta_chunk] * 16;
  487. next_chunk_length = totalRead - (session->metaint - session->current_meta_chunk) -
  488. (meta_length + 1);
  489. // rt_kprintf("l: %d, n: %d\n", meta_length, next_chunk_length);
  490. /* skip meta data */
  491. memmove(&buffer[session->metaint - session->current_meta_chunk],
  492. &buffer[session->metaint - session->current_meta_chunk + meta_length + 1],
  493. next_chunk_length);
  494. /* set new current meta chunk */
  495. session->current_meta_chunk = next_chunk_length;
  496. totalRead = totalRead - (meta_length + 1);
  497. // rt_kprintf("total: %d\n", totalRead);
  498. }
  499. else
  500. {
  501. session->current_meta_chunk += totalRead;
  502. }
  503. return totalRead;
  504. }
  505. rt_off_t shoutcast_session_seek(struct shoutcast_session* session, rt_off_t offset, int mode)
  506. {
  507. /* not support seek yet */
  508. return 0;
  509. }
  510. int shoutcast_session_close(struct shoutcast_session* session)
  511. {
  512. lwip_close(session->socket);
  513. if (session->station_name != RT_NULL)
  514. rt_free(session->station_name);
  515. rt_free(session);
  516. return 0;
  517. }
  518. #include <finsh.h>
  519. void http_test(char* url)
  520. {
  521. struct http_session* session;
  522. char buffer[80];
  523. rt_size_t length;
  524. session = http_session_open(url);
  525. if (session == RT_NULL)
  526. {
  527. rt_kprintf("open http session failed\n");
  528. return;
  529. }
  530. do
  531. {
  532. rt_memset(buffer, 0, sizeof(buffer));
  533. length = http_session_read(session, buffer, sizeof(buffer));
  534. rt_kprintf(buffer);rt_kprintf("\n");
  535. } while (length > 0);
  536. http_session_close(session);
  537. }
  538. FINSH_FUNCTION_EXPORT(http_test, http client test);
  539. void shoutcast_test(char* url)
  540. {
  541. struct shoutcast_session* session;
  542. session = shoutcast_session_open(url);
  543. if (session == RT_NULL)
  544. {
  545. rt_kprintf("open shoutcast session failed\n");
  546. return;
  547. }
  548. shoutcast_session_close(session);
  549. }
  550. FINSH_FUNCTION_EXPORT(shoutcast_test, shoutcast client test);