#include #include #include #include #include #include #include #include #include // urlit tähän muotoon typedef struct _url_info { char *host; char *file; char *port; } url_info, *purl_info; // funktioiden esittelyt void help(char *me); char* create_req(url_info *u); int main(int argc, char *argv[]); int getti(char *arg_url); char* read_field(char *r, char *field); int parse_url(char *url, url_info *out); void free_url(url_info *url); // tulostetaan helppi void help(char *me) { printf("%s: missing URL\n" "eg. %s [URL]\n", me, me); return; } // palauttaa osoittimen HTTP-pyyntöön, joka tarvitsee // vapauttaa freellä char* create_req(url_info *url) { static char req_format[] = "GET %s HTTP/1.0\r\n" "Host: %s\r\n\r\n"; char *buf; int bufsz; bufsz = sizeof(req_format)-4 +strlen(url->host)+strlen(url->file); if((buf = malloc(bufsz))) { sprintf(buf, req_format, url->file, url->host); return buf; } return NULL; } int main(int argc, char *argv[]) { if(argc < 2) { help(argv[0]); return 0; } return getti(argv[1]); } int getti(char *arg_url) { // urlin parsiminen url_info url; parse_url(arg_url, &url); // luodaan http-pyyntö tälle urlille char *buf = create_req(&url); if(!buf) { printf("failed to parse url: %s", buf); return -1; } // getaddrinfo hakee hostin ip-osoitteen ja // valmistelee addrinfon yhdistämistä varten int ret; struct addrinfo hints, *servinfo; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; // IPv4 tai IPv6, kaikki käy hints.ai_socktype = SOCK_STREAM; // tcp ret = getaddrinfo(url.host, url.port, &hints, &servinfo); if(ret != 0) { printf("getaddrinfo: %s\n", gai_strerror(ret)); free_url(&url); return 0; } // servinfossa on nyt kaikkia hostin IP-osotteita vastaavat // addrinfot (linkitetyssä listassa), mutta selkeyden vuoksi // käytetään vain ensimmäistä // luodaan soketti int sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol); if(sock == -1) { perror("socket"); goto exit; } // yhdistäminen ret = connect(sock, servinfo->ai_addr, servinfo->ai_addrlen); if(ret == -1) { perror("connect"); goto exit; } // lähetetään http-pyyntö. // tarvitaan silmukka, koska send ei välttämättä // pysty lähettämään koko pakettia kerralla, vaikka // tämä onkin hyvin epätodennäköistä int i = 0, bufsz = strlen(buf); do { ret = send(sock, &buf[i], bufsz-i, 0); if(ret == -1) { perror("send"); close(sock); goto exit; } i += ret; } while(i < bufsz); free(buf); // vastaanotetaan dataa kunnes vastapää sulkee yhteyden... // ja sen pitäis sulkea, koska Connection: close // on oletus http/1.0:ssa buf = NULL; i = 0; bufsz = 4096; do { if(i >= bufsz || !buf) { bufsz *= 2; buf = realloc(buf, bufsz+1); } ret = recv(sock, &buf[i], bufsz-i, 0); if(ret == -1) { perror("recv"); close(sock); goto exit; } i += ret; } while(ret != 0); // kunnes vastapää sulkee yhteyden buf[i] = '\0'; close(sock); // sit parsitaan vastauksesta kaikki oleellinen int status, content_length; char *p; sscanf(buf, "HTTP/%*d.%*d %d", &status); //sscanf(buf, "Content-Length: %d", &content_length); // lasketaan sisällön pituus vaan vastaanotetuista tavuista, // eikä käytetä Content-Length kenttää (HTTP/1.1 ei lähetä sitä) if((p = strstr(buf, "\r\n\r\n"))) { content_length = &buf[i] - (p+2); // redirect? switch(status) { case 301: // Moved Permanently case 302: // Found case 303: // See Other case 307: // Temporary Redirect (HTTP/1.1) { char *new_url; new_url = read_field(buf, "Location"); printf("redirected to: %s\n", new_url); ret = getti(new_url); free(new_url); return ret; } default: break; } // tulostetaan otsakkeet, koska tehtävänanto p[3] = '\0'; printf("%s\n", buf); p[3] = '\n'; // tallennetaan sisältö tiedostoon if(content_length) { char *filename, fnbuf[128]; FILE *pfile; filename = strrchr(url.file, '/'); if(!filename || strlen(filename) == 1) { sprintf(fnbuf, "getti-%d.tmp", (int)time(0)); filename = fnbuf; } else filename += 1; if((pfile = fopen(filename, "w+"))) { fwrite(&p[4], 1, content_length, pfile); fclose(pfile); printf("content written to: %s\n", filename); } else { printf("fopen(%s, \"w+\") fails", filename); } } else { // ei saatu sisältöä, vois tehdä jotain fiksua... } } free(buf); exit: freeaddrinfo(servinfo); free_url(&url); return 0; } // etsii kentän arvon http-headerista // arvo on muotoa char* ja pitää palauttaa freellä char* read_field(char *r, char *field) { char *ret, *p, tmp; int sz, retsz; ret = NULL; // headerin koko if((p = strstr(r, "\r\n\r\n"))) { sz = p+4 - r; // varmistetaan ettei strstr segfaulttaa tmp = r[sz-1]; r[sz-1] = 0; if((p = strstr(r, field))) { p = strstr(p, " ")+1; retsz = strstr(p, "\r\n") - p; ret = malloc(retsz+1); memmove(ret, p, retsz); ret[retsz] = 0; } r[sz-1] = tmp; } return ret; } // palauttaa url_info-tietueen annetusta urlista int parse_url(char *url, url_info *out) { char *p, *p2; int hostsz, filesz; if(!url || !out) return 0; // urlin alusta turha http://-pois jos on p = url; if(!strncmp(p, "http://", 7)) p += 7; // hosti p2 = strchr(p, '/'); if(p2) hostsz = p2 - p; else hostsz = strlen(p); out->host = malloc(hostsz+1); strncpy(out->host, p, hostsz); out->host[hostsz] = 0; // tiedosto p += hostsz; filesz = strlen(p); if(filesz) { out->file = malloc(filesz+1); strcpy(out->file, p); } else out->file = strdup("/"); // portti if((p = strchr(out->host, ':'))) { out->port = strdup(p+1); *p = '\0'; // laiska... } else out->port = strdup("80"); return 1; } // vapauttaa url_info-tietuetta varten varatut resurssit void free_url(url_info *url) { if(url) { free(url->host); free(url->file); free(url->port); } return; }