jkhsjdhjs

GIF Downloader

Jul 13th, 2016
93
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 13.23 KB | None | 0 0
  1. /* aktiviere aktuelle (2008-ext9) POSIX extensions, z.b. getline, getaddrinfo */
  2. #define _POSIX_C_SOURCE 200809L
  3.  
  4. #include <sys/types.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <unistd.h>
  8. #include <string.h>
  9. #include <sys/socket.h>
  10. #include <netdb.h>
  11. #include <fcntl.h>
  12. #include <arpa/inet.h>
  13. #include <netinet/in.h>
  14.  
  15. #include <stdbool.h>
  16.  
  17. #include "cJSON.h"
  18.  
  19. /* compilieren mit: gcc gif.c cJSON.c -pedantic -std=c11 -Wextra -Wall -lm -o gif_download */
  20.  
  21.  
  22.  
  23. /**
  24.  * Verbindet einen Socket zu dem angegebenen Host auf Port 80
  25.  *
  26.  * @param host Der Host mit dem sich verbunden werden soll
  27.  * @return Falls erfolgreich den verbundenen Socket, bei Fehler einen negativen Wert
  28.  */
  29. int connect_socket(char* host) {
  30.     /*****   TODO:    ****/
  31.     int sock;
  32.     struct addrinfo hints, *servinfo;
  33.     // Folgende Seite kann sehr hilfreich sein: http://www.beej.us/guide/bgnet/output/html/singlepage/bgnet.html#syscalls
  34.     // Namensauflösung z.B. mit getaddrinfo
  35.     memset(&hints, 0, sizeof hints);
  36.     hints.ai_socktype = SOCK_STREAM;
  37.     if(getaddrinfo(host, "http", &hints, &servinfo) != 0) {
  38.         fprintf(stderr, "Fehler bei der Namensauflösung!\n");
  39.         freeaddrinfo(servinfo);
  40.         return -1;
  41.     }
  42.     // Socket erzeugen, wir brauchen einen TCP socket!
  43.     if((sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) <= -1) {
  44.         fprintf(stderr, "Fehler beim Erstellen des Sockets!\n");
  45.         freeaddrinfo(servinfo);
  46.         return -1;
  47.     }
  48.     // Socket verbinden, connect ist das Stichwort!
  49.     if(connect(sock, servinfo->ai_addr, servinfo->ai_addrlen) <= -1) {
  50.         fprintf(stderr, "Fehler beim Aufbauen der Verbindung!\n");
  51.         freeaddrinfo(servinfo);
  52.         return -1;
  53.     }
  54.     // Fehler behandeln und entsprechende Fehlermeldungen auf stderr ausgeben
  55.     freeaddrinfo(servinfo);
  56.     return sock;
  57. }
  58.  
  59. /**
  60.  * Schreibt Daten in eine Datei
  61.  *
  62.  * @param filefd Filepointer auf mit fopen geöffnete Datei
  63.  * @param bytes Pointer auf bytes die geschrieben werden sollen
  64.  * @param Anzahl an bytes die zu schreiben sind
  65.  * @return Negativ bei Fehler, sonst positiv
  66.  */
  67. int write_bytes_to_file(FILE* filefd, char* bytes, size_t num_bytes) {
  68.    
  69.     int bytes_written = 0;
  70.     size_t total_written = 0;
  71.    
  72.     while(total_written != num_bytes) {
  73.         bytes_written = fwrite(bytes+total_written, 1, num_bytes-total_written, filefd);
  74.         if (bytes_written == 0) {
  75.             return -1;
  76.         }
  77.         total_written += bytes_written;
  78.     }
  79.     fflush(filefd);
  80.    
  81.     return 1;
  82. }
  83.  
  84.  
  85. /**
  86.  * Führt einen HTTP/1.0 GET request aus und speichert den Body in die angegebene Datei
  87.  *
  88.  * @param host Host bei dem Heruntergeladen werden soll
  89.  * @param request Der GET Request
  90.  * @param file_name Dateiname wo der HTTP Body hingeschrieben werden soll
  91.  * @return Negativ bei Fehler, sonst positiv
  92.  */
  93. int download_to_file(char* host, char* request, char* file_name) {
  94.     // Der Socket
  95.     int socketfd;
  96.     // Der Filepointer zur Datei wo wir schreiben wollen
  97.     FILE* filefd;
  98.     // Das muster für unseren request, die %s müssen wir noch durch die Parameter ersetzten
  99.     char* http_request_pattern = "GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n";
  100.     // 2^10 bytes für den request sollten reichen
  101.     char request_buffer[1 << 10];
  102.     // 2^16 byte ist unser receive buffer, wir lesen also immer 65kB in chunks
  103.     char receive_buffer[1 << 16];
  104.     // Wie groß ist etwas?
  105.     ssize_t num_bytes;
  106.     // könnte nützlich sein um nachzuhalten ob wir schon den Header empfangen haben
  107.     bool header_consumed = false;
  108.    
  109.     // erzeuge den Request anhand des Patterns
  110.     num_bytes = snprintf(request_buffer, sizeof(request_buffer), http_request_pattern, request, host);
  111.     if (num_bytes >= (ssize_t)sizeof(request_buffer)) {
  112.         fprintf(stderr, "Der request ist zu lang\n");
  113.         return -1;
  114.     }
  115.     // Verbinde den socket mit connect_socket und prüfe ob dies Erfolgreich war
  116.     // Im Fehlerfall, gib einen Fehler auf stderr aus und verlass die Funktion sauber und ordentlich
  117.     if((socketfd = connect_socket(host)) < 0) {
  118.         fprintf(stderr, "Fehler beim Verbindungsaufbau");
  119.         close(socketfd);
  120.         return -1;
  121.     }
  122.    
  123.     /****** TODO: Sende nun (z.B. mit send) den request an den Server ****/
  124.     // Im Fehlerfall, gib einen Fehler auf stderr aus und verlass die Funktion sauber und ordentlich
  125.     if(send(socketfd, request_buffer, strlen(request_buffer), 0) <= -1) {
  126.         fprintf(stderr, "Fehler beim Senden der Daten!\n");
  127.         close(socketfd);
  128.         return -1;
  129.     }
  130.    
  131.  
  132.    
  133.     // Dies öffnet die Datei zum schreiben!
  134.     filefd = fopen(file_name, "w+");
  135.     if(filefd == NULL) {
  136.         perror("Fehler beim Datei öffnen");
  137.         close(socketfd);
  138.         return -1;
  139.     }
  140.    
  141.    
  142.     /**
  143.      * Eine HTTP response besteht aus einem Header und einem Body
  144.      *
  145.      * Header und Body werden durch die Zeichenfolge \r\n\r\n voneinander getrennt
  146.      * Da wir einen HTTP/1.0 request gemacht haben, bekommen wir einen sogenannte
  147.      * End-Of-Stream response. Wir wissen vorher nicht genau wie groß die Antwort ist.
  148.      * aber der Server wird die Verbindung schließen sobald er alle Daten verschickt hat
  149.      *
  150.      * Wenn der Server die Verbindung schließt, returned recv das es 0 byte empfangen hat
  151.      * So können wir also das End-Of-Stream feststellen.
  152.      *
  153.      *
  154.      * Wir interessieren uns hier nur für den Body, den Header wollen wir ignorieren
  155.      * Wir müssen ihn aber auf jedenfall empfangen, aber wir können ihn getrost ignorieren
  156.      *
  157.      * Das ganze ist etwas unsauber aber um die komplexität der Aufgabe gering zu halten machen wir das so.
  158.      */
  159.     while(1) {
  160.         /**** TODO: lese Daten vom socket in den receive_buffer, z.b. mit recv ***/
  161.         // pass auf das du nicht mehr ließt als du speicher hast!
  162.         int r;
  163.         char *ptr;
  164.         if((r = recv(socketfd, receive_buffer, sizeof receive_buffer, 0)) <= -1) {
  165.             fclose(filefd);
  166.             close(socketfd);
  167.             fprintf(stderr, "Fehler beim Empfangen der Daten!\n");
  168.             return -1;
  169.         }
  170.         if(r >= 1) {
  171.             if(header_consumed) {
  172.                 if(write_bytes_to_file(filefd, receive_buffer, r) <= -1) {
  173.                     fclose(filefd);
  174.                     close(socketfd);
  175.                     fprintf(stderr, "Fehler beim Schreiben der Daten in die Datei!");
  176.                     return -1;
  177.                 }
  178.             }
  179.             else if((ptr = strstr(receive_buffer, "\r\n\r\n")) != NULL) {
  180.                 header_consumed = true;
  181.                 ptr += 4;
  182.                 if(write_bytes_to_file(filefd, ptr, r - (ptr - &*receive_buffer)) <= -1) {
  183.                     fclose(filefd);
  184.                     close(socketfd);
  185.                     fprintf(stderr, "Fehler beim Schreiben der Daten in die Datei!");
  186.                     return -1;
  187.                 }
  188.             }
  189.         }
  190.         else if(r == 0) {
  191.             break;
  192.         }
  193.         /**** TODO: wieviele byte wurden empfangen? Sind wir schon am Ende? Liegt ein Fehler vor? Müssen wir die Endlosschleife verlassen? ***/
  194.         // Im Fehlerfall, gib einen Fehler auf stderr aus und verlass die Funktion sauber und ordentlich!
  195.        
  196.        
  197.        
  198.         /* TODO: haben wir schon den Header empfangen?
  199.          *
  200.          * ja? -> Dann schreib die empfangen bytes mittels write_bytes_to_file in die Datei
  201.          *
  202.          * nein? -> dann prüf ob wir das Ende des Headers in den aktuellen Daten haben
  203.          *          Vergiss nicht alles nach dem Header in die Datei zu schreiben
  204.          *
  205.          * Achtung: Prüfe ob das Schreiben in die Datei erfolgreich war, wenn nicht räum hinter dir auf und verlass die Funktion ordentlich!
  206.          */
  207.        
  208.        
  209.     }
  210.    
  211.     // schließ die Datei
  212.     fclose(filefd);
  213.    
  214.     // schließ den socket!
  215.     close(socketfd);
  216.    
  217.    
  218.     return 1;
  219. }
  220.  
  221.  
  222. /**
  223.  * Sucht die "original" URL in der 1. Zeile der Datei
  224.  *
  225.  * @param file_name  Datei mit dem giphy json result
  226.  * @param url Pointer auf Pointer auf einen String, Pointer hinter Pointer sollte auf Null zeigen und muss später mit free freigeben werden (speicher wird angelegt!)
  227.  * @return Negativ bei Fehler, sonst positiv
  228.  */
  229. int find_url(char* file_name, char** url) {
  230.     FILE* file;
  231.    
  232.     char* buffer = NULL;
  233.     ssize_t line_len;
  234.     size_t linecap = 0;
  235.     file = fopen(file_name, "r");
  236.    
  237.    
  238.     // Lade 1. Zeile aus der Datei
  239.     line_len = getline(&buffer, &linecap, file);
  240.    
  241.     if(line_len < 0) {
  242.         fclose(file);
  243.         free(buffer);
  244.         perror("Error beim lesen einer Zeile");
  245.         return -1;
  246.     }
  247.    
  248.     // parse das Json und such die URL
  249.     cJSON* root = cJSON_Parse(buffer);
  250.     cJSON* data = cJSON_GetObjectItem(root, "data");
  251.     cJSON* first = cJSON_GetArrayItem(data, 0);
  252.     cJSON* images = cJSON_GetObjectItem(first, "images");
  253.     cJSON* original = cJSON_GetObjectItem(images, "original");
  254.     cJSON* jurl = cJSON_GetObjectItem(original, "url");
  255.    
  256.     // hat irgendwas davon nicht geklappt?
  257.     if(jurl == NULL) {
  258.         cJSON_Delete(root);
  259.         free(buffer);
  260.         fclose(file);
  261.         return -1;
  262.     }
  263.    
  264.     // wie lang ist die URL?
  265.     int len = strlen(jurl->valuestring)+1;
  266.     // leg speicher an
  267.     *url = malloc(len);
  268.     // haben wir genug speicher?
  269.     if (*url == NULL) {
  270.         cJSON_Delete(root);
  271.         free(buffer);
  272.         fclose(file);
  273.         return -1;
  274.     }
  275.     // erstmal alles 0en
  276.     memset(*url, 0, len);
  277.     // Jetzt die URL kopieren
  278.     memcpy(*url, jurl->valuestring, len-1);
  279.    
  280.     // das json objekt wieder freigeben
  281.     cJSON_Delete(root);
  282.     //die von getline allozierten Daten wieder freigeben
  283.     free(buffer);
  284.     // Datei zu machen
  285.     fclose(file);
  286.    
  287.     // das hat wohl alles geklappt
  288.     return 1;
  289. }
  290.  
  291. /**
  292.  * Fragt den Nutzer nach einer Eingabe und ersetzt Leerzeichen durch '+'
  293.  *
  294.  * @param input Pointer auf einen Pointer auf einen String, wenn Pointer hinter Pointer auf NULL zeigt wird Speicher angelegt, Achtung muss wieder freigegeben werden
  295.  * @return Negativ bei Fehler, sonst positiv
  296.  */
  297. int get_search_term_from_input(char** input) {
  298.     char* user_query = "Wonach soll ich suchen: ";
  299.    
  300.     size_t linecap = 0;
  301.     ssize_t linelen;
  302.    
  303.     fwrite(user_query, 1, strlen(user_query), stdout);
  304.     fflush(stdout);
  305.    
  306.     // ließ eine Zeile von stdin
  307.     if((linelen = getline(input, &linecap, stdin)) < 0) {
  308.         return -1;
  309.     }
  310.    
  311.     // loesche das \n vom ENTER drücken des Users und terminiere den string
  312.     // linelen ist laenge ohne null termination also genau das \n, achtung 0 indiziert !
  313.     (*input)[linelen-1] = '\0';
  314.    
  315.     // pos zeigt auf den Anfang des Strings
  316.     char* pos = *input;
  317.    
  318.     /**** TODO: ersetzte alle Leerzeichen durch ein '+' ****/
  319.     for(; *pos != '\0'; pos++) {
  320.         if(*pos != ' ') continue;
  321.         *pos = '+';
  322.     }
  323.    
  324.     return 1;
  325. }
  326.  
  327.  
  328.  
  329. int main() {
  330.     char* JSON_URL = "/tmp/giphy_json";
  331.     // Darauf muessen wir eine Anfrage stellen, %s wird noch durch die Suchanfrage ersetzt
  332.     char* query_template = "/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=1&q=%s";
  333.     char* query;
  334.     char* url = NULL;
  335.    
  336.     if(get_search_term_from_input(&url) < 0){
  337.         printf("Error bei Benutzereingabe\n");
  338.         return -1;
  339.     }
  340.    
  341.     query = malloc(strlen(query_template) + strlen(url));
  342.    
  343.     if(query == NULL) {
  344.         fprintf(stderr, "Nicht genug Speicherplatz\n");
  345.         free(url);
  346.         return -1;
  347.     }
  348.     // Da ist genug Platz, Schreibe des Ergebnis des Formatstrings in query (also %s ersetzten durch die Usereingabe)
  349.     sprintf(query, query_template, url);
  350.     // jetzt brauchen wir URL nicht mehr
  351.     free(url);
  352.     // aber wir verwenden es gleich nochmal... also wieder auf NULL
  353.     url = NULL;
  354.    
  355.     // download the api request t
  356.     if(download_to_file("api.giphy.com", query, JSON_URL) < 0) {
  357.         free(query);
  358.         fprintf(stderr, "Error downloading response\n");
  359.         return -1;
  360.     }
  361.    
  362.     if(find_url(JSON_URL, &url) < 0) {
  363.         free(url);
  364.         fprintf(stderr, "No results\n");
  365.         return -1;
  366.     }
  367.    
  368.     // finde teilstrings in der URL
  369.     // dateiname = letzer / + 1
  370.     char* filename = strrchr(url, '/')+1;
  371.     // host anfang = 1. / + 2 (e.g. HTTP://)
  372.     char* host_start = strchr(url, '/')+2;
  373.     // request ist alles nach dem host, also 1. / nach host_start
  374.     char* request = strchr(host_start, '/');
  375.    
  376.     // wir brauchen den host null terminiert, sonst lesen string Funktionen bis zum Ende
  377.     char* host = malloc(request - host_start + 1);
  378.     memset(host, 0, request - host_start + 1);
  379.     memcpy(host, host_start, request - host_start);
  380.    
  381.    
  382.     if(download_to_file(host, request, filename) < 0) {
  383.         fprintf(stderr, "Error downloading gif\n");
  384.         free(host);
  385.         free(url);
  386.         return -1;
  387.     };
  388.    
  389.     free(host);
  390.     free(url);
  391.     return 0;
  392. }
Advertisement
Add Comment
Please, Sign In to add comment