1 #include "rc_url.h"
2 
3 #include "../rcheevos/rc_compat.h"
4 #include "../rhash/md5.h"
5 
6 #include <stdio.h>
7 #include <string.h>
8 
rc_url_encode(char * encoded,size_t len,const char * str)9 static int rc_url_encode(char* encoded, size_t len, const char* str) {
10   for (;;) {
11     switch (*str) {
12       case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j':
13       case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't':
14       case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
15       case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J':
16       case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T':
17       case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
18       case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
19       case '-': case '_': case '.': case '~':
20         if (len < 2)
21           return -1;
22 
23         *encoded++ = *str++;
24         --len;
25         break;
26 
27       case ' ':
28         if (len < 2)
29           return -1;
30 
31         *encoded++ = '+';
32         ++str;
33         --len;
34         break;
35 
36       default:
37         if (len < 4)
38           return -1;
39 
40         snprintf(encoded, len, "%%%02x", (unsigned char)*str);
41         encoded += 3;
42         ++str;
43         len -= 3;
44         break;
45 
46       case '\0':
47         *encoded = 0;
48         return 0;
49     }
50   }
51 }
52 
rc_url_award_cheevo(char * buffer,size_t size,const char * user_name,const char * login_token,unsigned cheevo_id,int hardcore,const char * game_hash)53 int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token,
54                         unsigned cheevo_id, int hardcore, const char* game_hash) {
55   char urle_user_name[64];
56   char urle_login_token[64];
57   int written;
58 
59   if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
60     return -1;
61   }
62 
63   if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
64     return -1;
65   }
66 
67   written = snprintf(
68     buffer,
69     size,
70     "http://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d",
71     urle_user_name,
72     urle_login_token,
73     cheevo_id,
74     hardcore ? 1 : 0
75   );
76 
77   if (game_hash && strlen(game_hash) == 32 && (size - (size_t)written) >= 35) {
78      written += snprintf(buffer + written, size - (size_t)written, "&m=%s", game_hash);
79   }
80 
81   return (size_t)written >= size ? -1 : 0;
82 }
83 
rc_url_submit_lboard(char * buffer,size_t size,const char * user_name,const char * login_token,unsigned lboard_id,int value)84 int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value) {
85   char urle_user_name[64];
86   char urle_login_token[64];
87   char signature[64];
88   unsigned char hash[16];
89   md5_state_t state;
90   int written;
91 
92   if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
93     return -1;
94   }
95 
96   if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
97     return -1;
98   }
99 
100   /* Evaluate the signature. */
101   snprintf(signature, sizeof(signature), "%u%s%d", lboard_id, user_name, value);
102   md5_init(&state);
103   md5_append(&state, (unsigned char*)signature, (int)strlen(signature));
104   md5_finish(&state, hash);
105 
106   written = snprintf(
107     buffer,
108     size,
109     "http://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
110     urle_user_name,
111     urle_login_token,
112     lboard_id,
113     value,
114     hash[ 0], hash[ 1], hash[ 2], hash[ 3], hash[ 4], hash[ 5], hash[ 6], hash[ 7],
115     hash[ 8], hash[ 9], hash[10], hash[11],hash[12], hash[13], hash[14], hash[15]
116   );
117 
118   return (size_t)written >= size ? -1 : 0;
119 }
120 
rc_url_get_gameid(char * buffer,size_t size,const char * hash)121 int rc_url_get_gameid(char* buffer, size_t size, const char* hash) {
122   int written = snprintf(
123     buffer,
124     size,
125     "http://retroachievements.org/dorequest.php?r=gameid&m=%s",
126     hash
127   );
128 
129   return (size_t)written >= size ? -1 : 0;
130 }
131 
rc_url_get_patch(char * buffer,size_t size,const char * user_name,const char * login_token,unsigned gameid)132 int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid) {
133   char urle_user_name[64];
134   char urle_login_token[64];
135   int written;
136 
137   if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
138     return -1;
139   }
140 
141   if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
142     return -1;
143   }
144 
145   written = snprintf(
146     buffer,
147     size,
148     "http://retroachievements.org/dorequest.php?r=patch&u=%s&t=%s&g=%u",
149     urle_user_name,
150     urle_login_token,
151     gameid
152   );
153 
154   return (size_t)written >= size ? -1 : 0;
155 }
156 
rc_url_get_badge_image(char * buffer,size_t size,const char * badge_name)157 int rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name) {
158   int written = snprintf(
159     buffer,
160     size,
161     "http://i.retroachievements.org/Badge/%s",
162     badge_name
163   );
164 
165   return (size_t)written >= size ? -1 : 0;
166 }
167 
rc_url_login_with_password(char * buffer,size_t size,const char * user_name,const char * password)168 int rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password) {
169   char urle_user_name[64];
170   char urle_password[256];
171   int written;
172 
173   if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
174     return -1;
175   }
176 
177   if (rc_url_encode(urle_password, sizeof(urle_password), password) != 0) {
178     return -1;
179   }
180 
181   written = snprintf(
182     buffer,
183     size,
184     "http://retroachievements.org/dorequest.php?r=login&u=%s&p=%s",
185     urle_user_name,
186     urle_password
187   );
188 
189   return (size_t)written >= size ? -1 : 0;
190 }
191 
rc_url_login_with_token(char * buffer,size_t size,const char * user_name,const char * login_token)192 int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token) {
193   char urle_user_name[64];
194   char urle_login_token[64];
195   int written;
196 
197   if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
198     return -1;
199   }
200 
201   if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
202     return -1;
203   }
204 
205   written = snprintf(
206     buffer,
207     size,
208     "http://retroachievements.org/dorequest.php?r=login&u=%s&t=%s",
209     urle_user_name,
210     urle_login_token
211   );
212 
213   return (size_t)written >= size ? -1 : 0;
214 }
215 
rc_url_get_unlock_list(char * buffer,size_t size,const char * user_name,const char * login_token,unsigned gameid,int hardcore)216 int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore) {
217   char urle_user_name[64];
218   char urle_login_token[64];
219   int written;
220 
221   if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
222     return -1;
223   }
224 
225   if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
226     return -1;
227   }
228 
229   written = snprintf(
230     buffer,
231     size,
232     "http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=%d",
233     urle_user_name,
234     urle_login_token,
235     gameid,
236     hardcore ? 1 : 0
237   );
238 
239   return (size_t)written >= size ? -1 : 0;
240 }
241 
rc_url_post_playing(char * buffer,size_t size,const char * user_name,const char * login_token,unsigned gameid)242 int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid) {
243   char urle_user_name[64];
244   char urle_login_token[64];
245   int written;
246 
247   if (rc_url_encode(urle_user_name, sizeof(urle_user_name), user_name) != 0) {
248     return -1;
249   }
250 
251   if (rc_url_encode(urle_login_token, sizeof(urle_login_token), login_token) != 0) {
252     return -1;
253   }
254 
255   written = snprintf(
256     buffer,
257     size,
258     "http://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u",
259     urle_user_name,
260     urle_login_token,
261     gameid
262   );
263 
264   return (size_t)written >= size ? -1 : 0;
265 }
266 
rc_url_append_param_equals(char * buffer,size_t buffer_size,size_t buffer_offset,const char * param)267 static int rc_url_append_param_equals(char* buffer, size_t buffer_size, size_t buffer_offset, const char* param)
268 {
269   int written = 0;
270   size_t param_len;
271 
272   if (buffer_offset >= buffer_size)
273     return -1;
274 
275   if (buffer_offset) {
276     buffer += buffer_offset;
277     buffer_size -= buffer_offset;
278 
279     if (buffer[-1] != '?') {
280       *buffer++ = '&';
281       buffer_size--;
282       written = 1;
283     }
284   }
285 
286   param_len = strlen(param);
287   if (param_len + 1 >= buffer_size)
288     return -1;
289   memcpy(buffer, param, param_len);
290   buffer[param_len] = '=';
291 
292   written += (int)param_len + 1;
293   return written + (int)buffer_offset;
294 }
295 
rc_url_append_unum(char * buffer,size_t buffer_size,size_t * buffer_offset,const char * param,unsigned value)296 static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value)
297 {
298   int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
299   if (written > 0) {
300     char num[16];
301     int chars = snprintf(num, sizeof(num), "%u", value);
302 
303     if (chars + written < (int)buffer_size)
304     {
305       memcpy(&buffer[written], num, chars + 1);
306       *buffer_offset = written + chars;
307       return 0;
308     }
309   }
310 
311   return -1;
312 }
313 
rc_url_append_str(char * buffer,size_t buffer_size,size_t * buffer_offset,const char * param,const char * value)314 static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value)
315 {
316   int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
317   if (written > 0)
318   {
319     buffer += written;
320     buffer_size -= written;
321 
322     if (rc_url_encode(buffer, buffer_size, value) == 0)
323     {
324       written += (int)strlen(buffer);
325       *buffer_offset = written;
326       return 0;
327     }
328   }
329 
330   return -1;
331 }
332 
rc_url_build_dorequest(char * url_buffer,size_t url_buffer_size,size_t * buffer_offset,const char * api,const char * user_name)333 static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset,
334    const char* api, const char* user_name)
335 {
336   const char* base_url = "http://retroachievements.org/dorequest.php";
337   size_t written = strlen(base_url);
338   int failure = 0;
339 
340   if (url_buffer_size < written + 1)
341     return -1;
342   memcpy(url_buffer, base_url, written);
343   url_buffer[written++] = '?';
344 
345   failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "r", api);
346   failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "u", user_name);
347 
348   *buffer_offset += written;
349   return failure;
350 }
351 
rc_url_ping(char * url_buffer,size_t url_buffer_size,char * post_buffer,size_t post_buffer_size,const char * user_name,const char * login_token,unsigned gameid,const char * rich_presence)352 int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size,
353                 const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence)
354 {
355   size_t written = 0;
356   int failure = rc_url_build_dorequest(url_buffer, url_buffer_size, &written, "ping", user_name);
357   failure |= rc_url_append_unum(url_buffer, url_buffer_size, &written, "g", gameid);
358 
359   written = 0;
360   failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "t", login_token);
361 
362   if (rich_presence && *rich_presence)
363     failure |= rc_url_append_str(post_buffer, post_buffer_size, &written, "m", rich_presence);
364 
365   if (failure) {
366     if (url_buffer_size)
367       url_buffer[0] = '\0';
368     if (post_buffer_size)
369       post_buffer[0] = '\0';
370   }
371 
372   return failure;
373 }
374