1/*
2 * Copyright (c) 2009-2014 Petri Lehtinen <petri@digip.org>
3 *
4 * Jansson is free software; you can redistribute it and/or modify
5 * it under the terms of the MIT license. See LICENSE for details.
6 */
7
8#include <stdlib.h>
9#include <string.h>
10
11#include <jansson.h>
12#include <curl/curl.h>
13
14#define BUFFER_SIZE  (256 * 1024)  /* 256 KB */
15
16#define URL_FORMAT   "https://api.github.com/repos/%s/%s/commits"
17#define URL_SIZE     256
18
19/* Return the offset of the first newline in text or the length of
20   text if there's no newline */
21static int newline_offset(const char *text)
22{
23    const char *newline = strchr(text, '\n');
24    if(!newline)
25        return strlen(text);
26    else
27        return (int)(newline - text);
28}
29
30struct write_result
31{
32    char *data;
33    int pos;
34};
35
36static size_t write_response(void *ptr, size_t size, size_t nmemb, void *stream)
37{
38    struct write_result *result = (struct write_result *)stream;
39
40    if(result->pos + size * nmemb >= BUFFER_SIZE - 1)
41    {
42        fprintf(stderr, "error: too small buffer\n");
43        return 0;
44    }
45
46    memcpy(result->data + result->pos, ptr, size * nmemb);
47    result->pos += size * nmemb;
48
49    return size * nmemb;
50}
51
52static char *request(const char *url)
53{
54    CURL *curl = NULL;
55    CURLcode status;
56    struct curl_slist *headers = NULL;
57    char *data = NULL;
58    long code;
59
60    curl_global_init(CURL_GLOBAL_ALL);
61    curl = curl_easy_init();
62    if(!curl)
63        goto error;
64
65    data = malloc(BUFFER_SIZE);
66    if(!data)
67        goto error;
68
69    struct write_result write_result = {
70        .data = data,
71        .pos = 0
72    };
73
74    curl_easy_setopt(curl, CURLOPT_URL, url);
75
76    /* GitHub commits API v3 requires a User-Agent header */
77    headers = curl_slist_append(headers, "User-Agent: Jansson-Tutorial");
78    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
79
80    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response);
81    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_result);
82
83    status = curl_easy_perform(curl);
84    if(status != 0)
85    {
86        fprintf(stderr, "error: unable to request data from %s:\n", url);
87        fprintf(stderr, "%s\n", curl_easy_strerror(status));
88        goto error;
89    }
90
91    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
92    if(code != 200)
93    {
94        fprintf(stderr, "error: server responded with code %ld\n", code);
95        goto error;
96    }
97
98    curl_easy_cleanup(curl);
99    curl_slist_free_all(headers);
100    curl_global_cleanup();
101
102    /* zero-terminate the result */
103    data[write_result.pos] = '\0';
104
105    return data;
106
107error:
108    if(data)
109        free(data);
110    if(curl)
111        curl_easy_cleanup(curl);
112    if(headers)
113        curl_slist_free_all(headers);
114    curl_global_cleanup();
115    return NULL;
116}
117
118int main(int argc, char *argv[])
119{
120    size_t i;
121    char *text;
122    char url[URL_SIZE];
123
124    json_t *root;
125    json_error_t error;
126
127    if(argc != 3)
128    {
129        fprintf(stderr, "usage: %s USER REPOSITORY\n\n", argv[0]);
130        fprintf(stderr, "List commits at USER's REPOSITORY.\n\n");
131        return 2;
132    }
133
134    snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);
135
136    text = request(url);
137    if(!text)
138        return 1;
139
140    root = json_loads(text, 0, &error);
141    free(text);
142
143    if(!root)
144    {
145        fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
146        return 1;
147    }
148
149    if(!json_is_array(root))
150    {
151        fprintf(stderr, "error: root is not an array\n");
152        json_decref(root);
153        return 1;
154    }
155
156    for(i = 0; i < json_array_size(root); i++)
157    {
158        json_t *data, *sha, *commit, *message;
159        const char *message_text;
160
161        data = json_array_get(root, i);
162        if(!json_is_object(data))
163        {
164            fprintf(stderr, "error: commit data %d is not an object\n", (int)(i + 1));
165            json_decref(root);
166            return 1;
167        }
168
169        sha = json_object_get(data, "sha");
170        if(!json_is_string(sha))
171        {
172            fprintf(stderr, "error: commit %d: sha is not a string\n", (int)(i + 1));
173            return 1;
174        }
175
176        commit = json_object_get(data, "commit");
177        if(!json_is_object(commit))
178        {
179            fprintf(stderr, "error: commit %d: commit is not an object\n", (int)(i + 1));
180            json_decref(root);
181            return 1;
182        }
183
184        message = json_object_get(commit, "message");
185        if(!json_is_string(message))
186        {
187            fprintf(stderr, "error: commit %d: message is not a string\n", (int)(i + 1));
188            json_decref(root);
189            return 1;
190        }
191
192        message_text = json_string_value(message);
193        printf("%.8s %.*s\n",
194               json_string_value(sha),
195               newline_offset(message_text),
196               message_text);
197    }
198
199    json_decref(root);
200    return 0;
201}
202