CDC/ECM driver for mbed, based on USBDevice by mbed-official. Uses PicoTCP to access Ethernet USB device. License: GPLv2
Fork of USB_Ethernet by
pico_http_client.c
00001 /********************************************************************* 00002 PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. 00003 See LICENSE and COPYING for usage. 00004 00005 Author: Andrei Carp <andrei.carp@tass.be> 00006 *********************************************************************/ 00007 #include <string.h> 00008 #include <stdint.h> 00009 #include "pico_tree.h" 00010 #include "pico_config.h" 00011 #include "pico_socket.h" 00012 #include "pico_tcp.h" 00013 #include "pico_dns_client.h" 00014 #include "pico_http_client.h" 00015 #include "pico_ipv4.h" 00016 #include "pico_stack.h" 00017 00018 /* 00019 * This is the size of the following header 00020 * 00021 * GET <resource> HTTP/1.1<CRLF> 00022 * Host: <host>:<port><CRLF> 00023 * User-Agent: picoTCP<CRLF> 00024 * Connection: close<CRLF> 00025 * <CRLF> 00026 * 00027 * where <resource>,<host> and <port> will be added later. 00028 */ 00029 00030 #ifdef PICO_SUPPORT_HTTP_CLIENT 00031 00032 #define HTTP_GET_BASIC_SIZE 63u 00033 #define HTTP_HEADER_LINE_SIZE 50u 00034 #define RESPONSE_INDEX 9u 00035 00036 #define HTTP_CHUNK_ERROR 0xFFFFFFFFu 00037 00038 #ifdef dbg 00039 #undef dbg 00040 #define dbg(...) do{}while(0); 00041 #endif 00042 00043 #define consumeChar(c) (pico_socket_read(client->sck,&c,1u)) 00044 #define isLocation(line) (memcmp(line,"Location",8u) == 0) 00045 #define isContentLength(line) (memcmp(line,"Content-Length",14u) == 0u) 00046 #define isTransferEncoding(line) (memcmp(line,"Transfer-Encoding",17u) == 0u) 00047 #define isChunked(line) (memcmp(line," chunked",8u) == 0u) 00048 #define isNotHTTPv1(line) (memcmp(line,"HTTP/1.",7u)) 00049 #define is_hex_digit(x) ( ('0' <= x && x <= '9') || ('a' <= x && x <= 'f') ) 00050 #define hex_digit_to_dec(x) ( ('0' <= x && x <= '9') ? x-'0' : ( ('a' <= x && x <= 'f') ? x-'a' + 10 : -1) ) 00051 00052 struct pico_http_client 00053 { 00054 uint16_t connectionID; 00055 uint8_t state; 00056 struct pico_socket * sck; 00057 void (*wakeup)(uint16_t ev, uint16_t conn); 00058 struct pico_ip4 ip; 00059 struct pico_http_uri * uriKey; 00060 struct pico_http_header * header; 00061 }; 00062 00063 // HTTP Client internal states 00064 #define HTTP_READING_HEADER 0 00065 #define HTTP_READING_BODY 1 00066 #define HTTP_READING_CHUNK_VALUE 2 00067 #define HTTP_READING_CHUNK_TRAIL 3 00068 00069 00070 static int compareClients(void * ka, void * kb) 00071 { 00072 return ((struct pico_http_client *)ka)->connectionID - ((struct pico_http_client *)kb)->connectionID; 00073 } 00074 00075 PICO_TREE_DECLARE(pico_client_list,compareClients); 00076 00077 // Local functions 00078 int parseHeaderFromServer(struct pico_http_client * client, struct pico_http_header * header); 00079 int readChunkLine(struct pico_http_client * client); 00080 00081 void tcpCallback(uint16_t ev, struct pico_socket *s) 00082 { 00083 00084 struct pico_http_client * client = NULL; 00085 struct pico_tree_node * index; 00086 00087 // find httpClient 00088 pico_tree_foreach(index,&pico_client_list) 00089 { 00090 if( ((struct pico_http_client *)index->keyValue)->sck == s ) 00091 { 00092 client = (struct pico_http_client *)index->keyValue; 00093 break; 00094 } 00095 } 00096 00097 if(!client) 00098 { 00099 dbg("Client not found...Something went wrong !\n"); 00100 return; 00101 } 00102 00103 if(ev & PICO_SOCK_EV_CONN) 00104 client->wakeup(EV_HTTP_CON,client->connectionID); 00105 00106 if(ev & PICO_SOCK_EV_RD) 00107 { 00108 00109 // read the header, if not read 00110 if(client->state == HTTP_READING_HEADER) 00111 { 00112 // wait for header 00113 client->header = pico_zalloc(sizeof(struct pico_http_header)); 00114 if(!client->header) 00115 { 00116 pico_err = PICO_ERR_ENOMEM; 00117 return; 00118 } 00119 00120 // wait for header 00121 if(parseHeaderFromServer(client,client->header) < 0) 00122 { 00123 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00124 } 00125 else 00126 { 00127 // call wakeup 00128 if(client->header->responseCode != HTTP_CONTINUE) 00129 { 00130 client->wakeup( 00131 client->header->responseCode == HTTP_OK ? 00132 EV_HTTP_REQ | EV_HTTP_BODY : // data comes for sure only when 200 is received 00133 EV_HTTP_REQ 00134 ,client->connectionID); 00135 } 00136 } 00137 } 00138 else 00139 { 00140 // just let the user know that data has arrived, if chunked data comes, will be treated in the 00141 // read api. 00142 client->wakeup(EV_HTTP_BODY,client->connectionID); 00143 } 00144 } 00145 00146 if(ev & PICO_SOCK_EV_ERR) 00147 { 00148 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00149 } 00150 00151 if( (ev & PICO_SOCK_EV_CLOSE) || (ev & PICO_SOCK_EV_FIN) ) 00152 { 00153 client->wakeup(EV_HTTP_CLOSE,client->connectionID); 00154 } 00155 00156 } 00157 00158 // used for getting a response from DNS servers 00159 static void dnsCallback(char *ip, void * ptr) 00160 { 00161 struct pico_http_client * client = (struct pico_http_client *)ptr; 00162 00163 if(!client) 00164 { 00165 dbg("Who made the request ?!\n"); 00166 return; 00167 } 00168 00169 if(ip) 00170 { 00171 client->wakeup(EV_HTTP_DNS,client->connectionID); 00172 00173 // add the ip address to the client, and start a tcp connection socket 00174 pico_string_to_ipv4(ip,&client->ip.addr); 00175 pico_free(ip); 00176 client->sck = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcpCallback); 00177 if(!client->sck) 00178 { 00179 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00180 return; 00181 } 00182 00183 if(pico_socket_connect(client->sck,&client->ip,short_be(client->uriKey->port)) < 0) 00184 { 00185 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00186 return; 00187 } 00188 00189 } 00190 else 00191 { 00192 // wakeup client and let know error occured 00193 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00194 00195 // close the client (free used heap) 00196 pico_http_client_close(client->connectionID); 00197 } 00198 } 00199 00200 /* 00201 * API used for opening a new HTTP Client. 00202 * 00203 * The accepted uri's are [http://]hostname[:port]/resource 00204 * no relative uri's are accepted. 00205 * 00206 * The function returns a connection ID >= 0 if successful 00207 * -1 if an error occured. 00208 */ 00209 int pico_http_client_open(char * uri, void (*wakeup)(uint16_t ev, uint16_t conn)) 00210 { 00211 struct pico_http_client * client; 00212 00213 if(!wakeup) 00214 { 00215 pico_err = PICO_ERR_EINVAL; 00216 return HTTP_RETURN_ERROR; 00217 } 00218 00219 client = pico_zalloc(sizeof(struct pico_http_client)); 00220 if(!client) 00221 { 00222 // memory error 00223 pico_err = PICO_ERR_ENOMEM; 00224 return HTTP_RETURN_ERROR; 00225 } 00226 00227 client->wakeup = wakeup; 00228 client->connectionID = (uint16_t)pico_rand() & 0x7FFFu; // negative values mean error, still not good generation 00229 00230 client->uriKey = pico_zalloc(sizeof(struct pico_http_uri)); 00231 00232 if(!client->uriKey) 00233 { 00234 pico_err = PICO_ERR_ENOMEM; 00235 pico_free(client); 00236 return HTTP_RETURN_ERROR; 00237 } 00238 00239 pico_processURI(uri,client->uriKey); 00240 00241 if(pico_tree_insert(&pico_client_list,client)) 00242 { 00243 // already in 00244 pico_err = PICO_ERR_EEXIST; 00245 pico_free(client->uriKey); 00246 pico_free(client); 00247 return HTTP_RETURN_ERROR; 00248 } 00249 00250 // dns query 00251 dbg("Querying : %s \n",client->uriKey->host); 00252 pico_dns_client_getaddr(client->uriKey->host, dnsCallback,client); 00253 00254 // return the connection ID 00255 return client->connectionID; 00256 } 00257 00258 /* 00259 * API for sending a header to the client. 00260 * 00261 * if hdr == HTTP_HEADER_RAW , then the parameter header 00262 * is sent as it is to client. 00263 * 00264 * if hdr == HTTP_HEADER_DEFAULT, then the parameter header 00265 * is ignored and the library will build the response header 00266 * based on the uri passed when opening the client. 00267 * 00268 */ 00269 int pico_http_client_sendHeader(uint16_t conn, char * header, int hdr) 00270 { 00271 struct pico_http_client search = {.connectionID = conn}; 00272 struct pico_http_client * http = pico_tree_findKey(&pico_client_list,&search); 00273 int length ; 00274 if(!http) 00275 { 00276 dbg("Client not found !\n"); 00277 return HTTP_RETURN_ERROR; 00278 } 00279 00280 // the api gives the possibility to the user to build the GET header 00281 // based on the uri passed when opening the client, less headache for the user 00282 if(hdr == HTTP_HEADER_DEFAULT) 00283 { 00284 header = pico_http_client_buildHeader(http->uriKey); 00285 00286 if(!header) 00287 { 00288 pico_err = PICO_ERR_ENOMEM; 00289 return HTTP_RETURN_ERROR; 00290 } 00291 } 00292 00293 length = pico_socket_write(http->sck,(void *)header,strlen(header)+1); 00294 00295 if(hdr == HTTP_HEADER_DEFAULT) 00296 pico_free(header); 00297 00298 return length; 00299 } 00300 00301 /* 00302 * API for reading received data. 00303 * 00304 * This api hides from the user if the transfer-encoding 00305 * was chunked or a full length was provided, in case of 00306 * a chunked transfer encoding will "de-chunk" the data 00307 * and pass it to the user. 00308 */ 00309 int pico_http_client_readData(uint16_t conn, char * data, uint16_t size) 00310 { 00311 struct pico_http_client dummy = {.connectionID = conn}; 00312 struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); 00313 00314 if(!client) 00315 { 00316 dbg("Wrong connection id !\n"); 00317 pico_err = PICO_ERR_EINVAL; 00318 return HTTP_RETURN_ERROR; 00319 } 00320 00321 // for the moment just read the data, do not care if it's chunked or not 00322 if(client->header->transferCoding == HTTP_TRANSFER_FULL) 00323 return pico_socket_read(client->sck,(void *)data,size); 00324 else 00325 { 00326 int lenRead = 0; 00327 00328 // read the chunk line 00329 if(readChunkLine(client) == HTTP_RETURN_ERROR) 00330 { 00331 dbg("Probably the chunk is malformed or parsed wrong...\n"); 00332 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00333 return HTTP_RETURN_ERROR; 00334 } 00335 00336 // nothing to read, no use to try 00337 if(client->state != HTTP_READING_BODY) 00338 { 00339 pico_err = PICO_ERR_EAGAIN; 00340 return HTTP_RETURN_OK; 00341 } 00342 00343 // check if we need more than one chunk 00344 if(size >= client->header->contentLengthOrChunk) 00345 { 00346 // read the rest of the chunk, if chunk is done, proceed to the next chunk 00347 while(lenRead <= size) 00348 { 00349 int tmpLenRead = 0; 00350 00351 if(client->state == HTTP_READING_BODY) 00352 { 00353 00354 // if needed truncate the data 00355 tmpLenRead = pico_socket_read(client->sck,data + lenRead, 00356 client->header->contentLengthOrChunk < size-lenRead ? client->header->contentLengthOrChunk : size-lenRead); 00357 00358 if(tmpLenRead > 0) 00359 { 00360 client->header->contentLengthOrChunk -= tmpLenRead; 00361 } 00362 else if(tmpLenRead < 0) 00363 { 00364 // error on reading 00365 dbg(">>> Error returned pico_socket_read\n"); 00366 pico_err = PICO_ERR_EBUSY; 00367 // return how much data was read until now 00368 return lenRead; 00369 } 00370 } 00371 00372 lenRead += tmpLenRead; 00373 if(readChunkLine(client) == HTTP_RETURN_ERROR) 00374 { 00375 dbg("Probably the chunk is malformed or parsed wrong...\n"); 00376 client->wakeup(EV_HTTP_ERROR,client->connectionID); 00377 return HTTP_RETURN_ERROR; 00378 } 00379 00380 if(client->state != HTTP_READING_BODY || !tmpLenRead) break; 00381 00382 } 00383 } 00384 else 00385 { 00386 // read the data from the chunk 00387 lenRead = pico_socket_read(client->sck,(void *)data,size); 00388 00389 if(lenRead) 00390 client->header->contentLengthOrChunk -= lenRead; 00391 } 00392 00393 return lenRead; 00394 } 00395 } 00396 00397 /* 00398 * API for reading received data. 00399 * 00400 * Reads out the header struct received from server. 00401 */ 00402 struct pico_http_header * pico_http_client_readHeader(uint16_t conn) 00403 { 00404 struct pico_http_client dummy = {.connectionID = conn}; 00405 struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); 00406 00407 if(client) 00408 { 00409 return client->header; 00410 } 00411 else 00412 { 00413 // not found 00414 dbg("Wrong connection id !\n"); 00415 pico_err = PICO_ERR_EINVAL; 00416 return NULL; 00417 } 00418 } 00419 00420 /* 00421 * API for reading received data. 00422 * 00423 * Reads out the uri struct after was processed. 00424 */ 00425 struct pico_http_uri * pico_http_client_readUriData(uint16_t conn) 00426 { 00427 struct pico_http_client dummy = {.connectionID = conn}; 00428 struct pico_http_client * client = pico_tree_findKey(&pico_client_list,&dummy); 00429 // 00430 if(client) 00431 return client->uriKey; 00432 else 00433 { 00434 // not found 00435 dbg("Wrong connection id !\n"); 00436 pico_err = PICO_ERR_EINVAL; 00437 return NULL; 00438 } 00439 } 00440 00441 /* 00442 * API for reading received data. 00443 * 00444 * Close the client. 00445 */ 00446 int pico_http_client_close(uint16_t conn) 00447 { 00448 struct pico_http_client * toBeRemoved = NULL; 00449 struct pico_http_client dummy = {}; 00450 dummy.connectionID = conn; 00451 00452 dbg("Closing the client...\n"); 00453 toBeRemoved = pico_tree_delete(&pico_client_list,&dummy); 00454 if(!toBeRemoved) 00455 { 00456 dbg("Warning ! Element not found ..."); 00457 return HTTP_RETURN_ERROR; 00458 } 00459 00460 // close socket 00461 if(toBeRemoved->sck) 00462 pico_socket_close(toBeRemoved->sck); 00463 00464 00465 if(toBeRemoved->header) 00466 { 00467 // free space used 00468 if(toBeRemoved->header->location) 00469 pico_free(toBeRemoved->header->location); 00470 00471 pico_free(toBeRemoved->header); 00472 } 00473 00474 if(toBeRemoved->uriKey) 00475 { 00476 if(toBeRemoved->uriKey->host) 00477 pico_free(toBeRemoved->uriKey->host); 00478 00479 if(toBeRemoved->uriKey->resource) 00480 pico_free(toBeRemoved->uriKey->resource); 00481 pico_free(toBeRemoved->uriKey); 00482 } 00483 pico_free(toBeRemoved); 00484 00485 return 0; 00486 } 00487 00488 /* 00489 * API for reading received data. 00490 * 00491 * Builds a GET header based on the fields on the uri. 00492 */ 00493 char * pico_http_client_buildHeader(const struct pico_http_uri * uriData) 00494 { 00495 char * header; 00496 char port[6u]; // 6 = max length of a uint16 + \0 00497 00498 uint16_t headerSize = HTTP_GET_BASIC_SIZE; 00499 00500 if(!uriData->host || !uriData->resource || !uriData->port) 00501 { 00502 pico_err = PICO_ERR_EINVAL; 00503 return NULL; 00504 } 00505 00506 // 00507 headerSize += strlen(uriData->host) + strlen(uriData->resource) + pico_itoa(uriData->port,port) + 4u; // 3 = size(CRLF + \0) 00508 header = pico_zalloc(headerSize); 00509 00510 if(!header) 00511 { 00512 // not enought memory 00513 pico_err = PICO_ERR_ENOMEM; 00514 return NULL; 00515 } 00516 00517 // build the actual header 00518 strcpy(header,"GET "); 00519 strcat(header,uriData->resource); 00520 strcat(header," HTTP/1.1\r\n"); 00521 strcat(header,"Host: "); 00522 strcat(header,uriData->host); 00523 strcat(header,":"); 00524 strcat(header,port); 00525 strcat(header,"\r\n"); 00526 strcat(header,"User-Agent: picoTCP\r\nConnection: close\r\n\r\n"); //? 00527 00528 return header; 00529 } 00530 00531 int parseHeaderFromServer(struct pico_http_client * client, struct pico_http_header * header) 00532 { 00533 char line[HTTP_HEADER_LINE_SIZE]; 00534 char c; 00535 int index = 0; 00536 00537 // read the first line of the header 00538 while(consumeChar(c)>0 && c!='\r') 00539 { 00540 if(index < HTTP_HEADER_LINE_SIZE) // truncate if too long 00541 line[index++] = c; 00542 } 00543 00544 consumeChar(c); // consume \n 00545 00546 // check the integrity of the response 00547 // make sure we have enough characters to include the response code 00548 // make sure the server response starts with HTTP/1. 00549 if(index < RESPONSE_INDEX+2 || isNotHTTPv1(line)) 00550 { 00551 // wrong format of the the response 00552 pico_err = PICO_ERR_EINVAL; 00553 return HTTP_RETURN_ERROR; 00554 } 00555 00556 // extract response code 00557 header->responseCode = (line[RESPONSE_INDEX] - '0') * 100u + 00558 (line[RESPONSE_INDEX+1] - '0') * 10u + 00559 (line[RESPONSE_INDEX+2] - '0'); 00560 00561 00562 if(header->responseCode/100u > 5u) 00563 { 00564 // invalid response type 00565 header->responseCode = 0; 00566 return HTTP_RETURN_ERROR; 00567 } 00568 00569 dbg("Server response : %d \n",header->responseCode); 00570 00571 // parse the rest of the header 00572 while(consumeChar(c)>0) 00573 { 00574 if(c==':') 00575 { 00576 // check for interesting fields 00577 00578 // Location: 00579 if(isLocation(line)) 00580 { 00581 index = 0; 00582 while(consumeChar(c)>0 && c!='\r') 00583 { 00584 line[index++] = c; 00585 } 00586 00587 // allocate space for the field 00588 header->location = pico_zalloc(index+1u); 00589 if(!header->location) 00590 { 00591 pico_err = PICO_ERR_ENOMEM; 00592 return HTTP_RETURN_ERROR; 00593 } 00594 00595 memcpy(header->location,line,index); 00596 00597 }// Content-Length: 00598 else if(isContentLength(line)) 00599 { 00600 header->contentLengthOrChunk = 0u; 00601 header->transferCoding = HTTP_TRANSFER_FULL; 00602 // consume the first space 00603 consumeChar(c); 00604 while(consumeChar(c)>0 && c!='\r') 00605 { 00606 header->contentLengthOrChunk = header->contentLengthOrChunk*10u + (c-'0'); 00607 } 00608 00609 }// Transfer-Encoding: chunked 00610 else if(isTransferEncoding(line)) 00611 { 00612 index = 0; 00613 while(consumeChar(c)>0 && c!='\r') 00614 { 00615 line[index++] = c; 00616 } 00617 00618 if(isChunked(line)) 00619 { 00620 header->contentLengthOrChunk = 0u; 00621 header->transferCoding = HTTP_TRANSFER_CHUNKED; 00622 } 00623 00624 }// just ignore the line 00625 else 00626 { 00627 while(consumeChar(c)>0 && c!='\r'); 00628 } 00629 00630 // consume the next one 00631 consumeChar(c); 00632 // reset the index 00633 index = 0u; 00634 } 00635 else if(c=='\r' && !index) 00636 { 00637 // consume the \n 00638 consumeChar(c); 00639 break; 00640 } 00641 else 00642 { 00643 line[index++] = c; 00644 } 00645 } 00646 00647 if(header->transferCoding == HTTP_TRANSFER_CHUNKED) 00648 { 00649 // read the first chunk 00650 header->contentLengthOrChunk = 0; 00651 00652 client->state = HTTP_READING_CHUNK_VALUE; 00653 readChunkLine(client); 00654 00655 } 00656 else 00657 client->state = HTTP_READING_BODY; 00658 00659 dbg("End of header\n"); 00660 return HTTP_RETURN_OK; 00661 00662 } 00663 00664 // an async read of the chunk part, since in theory a chunk can be split in 2 packets 00665 int readChunkLine(struct pico_http_client * client) 00666 { 00667 char c = 0; 00668 00669 if(client->header->contentLengthOrChunk==0 && client->state == HTTP_READING_BODY) 00670 { 00671 client->state = HTTP_READING_CHUNK_VALUE; 00672 } 00673 00674 if(client->state == HTTP_READING_CHUNK_VALUE) 00675 { 00676 while(consumeChar(c)>0 && c!='\r' && c!=';') 00677 { 00678 if(is_hex_digit(c)) 00679 client->header->contentLengthOrChunk = (client->header->contentLengthOrChunk << 4u) + hex_digit_to_dec(c); 00680 else 00681 { 00682 pico_err = PICO_ERR_EINVAL; 00683 // something went wrong 00684 return HTTP_RETURN_ERROR; 00685 } 00686 } 00687 00688 if(c=='\r' || c==';') client->state = HTTP_READING_CHUNK_TRAIL; 00689 } 00690 00691 if(client->state == HTTP_READING_CHUNK_TRAIL) 00692 { 00693 00694 while(consumeChar(c)>0 && c!='\n'); 00695 00696 if(c=='\n') client->state = HTTP_READING_BODY; 00697 } 00698 00699 return HTTP_RETURN_OK; 00700 } 00701 #endif
Generated on Wed Jul 13 2022 02:20:45 by 1.7.2