-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuqimageclient.c
664 lines (622 loc) · 22 KB
/
uqimageclient.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
// Import
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <csse2310a4.h>
#include <ctype.h>
// Magic nums
#define MIN_ROTATION (-359)
#define MAX_ROTATION 359
#define MAX_SCALE 10000
#define BODY_INIT_BUFFER 400
#define ARG_MIN 6
#define ARG_MAX 4
#define OK_CODE 200
#define MAX_ARGS 3
// Structs
typedef struct {
char* portNumber;
char* rotateDegrees;
char* inFile;
char* outFile;
char* scaleArg[2];
char flipDirection;
} ClientConditions;
typedef struct {
char** argv;
int argc;
int currentArg;
} UsageCheckInfo;
typedef struct {
int degreesArg;
int inFileArg;
int outFileArg;
int scaleArg0;
int scaleArg1;
int flipDirectionArg;
} ArgumentPositions;
typedef struct {
char* request;
int numBytes;
char* header;
unsigned char* body;
} HTTPRequest;
typedef struct {
int numBytes;
unsigned char* serverBody;
int status;
char* statusExplanation;
} HTTPResponse;
typedef struct {
char* name;
char* value;
} HTTPHeader;
// Error codes
typedef enum {
USAGE_ERROR = 12,
READ_ERROR = 7,
WRITE_ERROR = 1,
CONNECTION_ERROR = 9,
COMMUNICATION_ERROR = 18,
RESPONSE_OUTPUT_ERROR = 13,
HTTP_NOT_OK_ERROR = 8,
DATA_ERROR = 2,
} ErrorCode;
// Error messages
const char* const errorUsage
= "Usage: uqimageclient portnumber [--in infilename] [--output "
"outputfilename] [--rotate degrees | --scale w h | --flip dirn]\n";
const char* const readError
= "uqimageclient: unable to open file \"%s\" for reading\n";
const char* const writeError
= "uqimageclient: unable to write to file \"%s\"\n";
const char* const connectionError
= "uqimageclient: cannot connect to port \"%s\"\n";
const char* const noDataError = "uqimageclient: no data read for input image\n";
const char* const communicationError
= "uqimageclient: communication error to server\n";
const char* const outputError = "uqimageclient: error while writing output\n";
// Error handlers
void error_handler_usage()
{
fprintf(stderr, errorUsage);
exit(USAGE_ERROR);
}
void error_handler_read(ClientConditions conditions)
{
fprintf(stderr, readError, conditions.inFile);
exit(READ_ERROR);
}
void error_handler_write(ClientConditions conditions)
{
fprintf(stderr, writeError, conditions.outFile);
exit(WRITE_ERROR);
}
void error_handler_connection(ClientConditions conditions)
{
fprintf(stderr, connectionError, conditions.portNumber);
exit(CONNECTION_ERROR);
}
void error_handler_no_data()
{
fprintf(stderr, noDataError);
exit(DATA_ERROR);
}
void error_handler_not_ok()
{
exit(HTTP_NOT_OK_ERROR);
}
void error_handler_communication()
{
fprintf(stderr, communicationError);
exit(COMMUNICATION_ERROR);
}
void error_handler_output()
{
fprintf(stderr, outputError);
exit(RESPONSE_OUTPUT_ERROR);
}
// Helper function for usage_check to check whether the rotate arg provided by
// the user is acceptable. Takes instance of ClientConditions struct to modify
// if argument is valid. Takes instance of UsageCheckInfo struct that contains
// information about state of usage_check (loop iteration, argv and argc).
// Returns ClientConditions struct. Calls usage error handler if args are
// invalid.
ClientConditions handle_rotate_arg(
ClientConditions conditions, UsageCheckInfo info)
{
int convertedDegrees;
if ((info.argc) > info.currentArg + 1) {
if (strlen(info.argv[info.currentArg + 1]) == 0) {
error_handler_usage();
}
for (int j = 1; j < (int)strlen(info.argv[info.currentArg + 1]); j++) {
if (info.argv[info.currentArg + 1][j] == '+'
|| info.argv[info.currentArg + 1][j] == '-') {
error_handler_usage();
}
}
for (int j = 0; j < (int)strlen(info.argv[info.currentArg + 1]); j++) {
if ((!isalpha(info.argv[info.currentArg + 1][j]))
&& (!isdigit(info.argv[info.currentArg + 1][j]))
&& info.argv[info.currentArg + 1][j] != '+'
&& info.argv[info.currentArg + 1][j] != '-') {
error_handler_usage();
}
}
convertedDegrees = atoi(info.argv[info.currentArg + 1]);
if (!convertedDegrees) {
error_handler_usage();
} else if (convertedDegrees > MAX_ROTATION
|| convertedDegrees < MIN_ROTATION) {
error_handler_usage();
}
} else {
error_handler_usage();
}
conditions.rotateDegrees = info.argv[info.currentArg + 1];
return conditions;
}
// Helper function for usage_check that determines whether the scale args
// provided by the user are valid. Checks if both arguments are present, if both
// args are numbers, and are within the set bounds. Takes instance of
// ClientConditions and UsageCheckInfo struct. If arguments are valid, will
// return modified ClientConditions struct, will call usage error handler if
// args are invalid.
ClientConditions handle_scale_arg(
ClientConditions conditions, UsageCheckInfo info)
{
if ((info.argc) > info.currentArg + 2) {
if ((strlen(info.argv[info.currentArg + 1])) == 0
|| (strlen(info.argv[info.currentArg + 2])) == 0) {
error_handler_usage();
}
for (int i = 1; i < MAX_ARGS; i++) {
for (int j = 1; j < (int)strlen(info.argv[info.currentArg + i]);
j++) {
if (info.argv[info.currentArg + i][j] == '+'
|| info.argv[info.currentArg + i][j] == '-') {
error_handler_usage();
}
}
// Check if it's a symbol that is not - or +
for (int j = 0; j < (int)strlen(info.argv[info.currentArg + i]);
j++) {
if ((!isalpha(info.argv[info.currentArg + i][j]))
&& (!isdigit(info.argv[info.currentArg + i][j]))
&& info.argv[info.currentArg + i][j] != '+'
&& info.argv[info.currentArg + i][j] != '-') {
error_handler_usage();
}
}
int convertedScaleArgument = atoi(info.argv[info.currentArg + i]);
if (!convertedScaleArgument) {
fflush(stdout);
error_handler_usage();
}
if (convertedScaleArgument < 1
|| convertedScaleArgument > MAX_SCALE) {
fflush(stdout);
error_handler_usage();
}
char* tempString = info.argv[info.currentArg + i];
conditions.scaleArg[i - 1] = tempString;
}
} else {
error_handler_usage();
}
return conditions;
}
// Helper function for usage_check to determine if the flip args provided by the
// user are valid. If arg is not 'h' or 'v', will call usage error handler.
// Modifies and returns modified ClientConditions struct if args are valid, will
// call error_handler_usage() otherwise. Takes ClientConditions and
// UsageCheckInfo structs.
ClientConditions handle_flip_arg(
ClientConditions conditions, UsageCheckInfo info)
{
if ((info.argc) > info.currentArg + 1) {
if ((strlen(info.argv[info.currentArg + 1]) == 0)) {
error_handler_usage();
}
if (strcmp(info.argv[info.currentArg + 1], "h")
&& (strcmp(info.argv[info.currentArg + 1], "v"))) {
error_handler_usage();
}
conditions.flipDirection = info.argv[info.currentArg + 1][0];
} else {
error_handler_usage();
}
return conditions;
}
// Helper function for usage_check to determine whether the in and output files
// specified by the user are valid. Validation process same for either file, so
// fileType is specified so correct file is stored in the ClientConditions
// struct. Takes ClientConditions struct and UsageCheckInfo struct. Returns
// modified ClientConditions struct. Calls error_handler _usage if arg(s)
// invalid.
ClientConditions handle_io_arg(
ClientConditions conditions, UsageCheckInfo info, int fileType)
{
if ((info.argc) > info.currentArg + 1) {
if (strlen(info.argv[info.currentArg + 1]) != 0) {
if (fileType) {
conditions.inFile = info.argv[info.currentArg + 1];
} else {
conditions.outFile = info.argv[info.currentArg + 1];
}
} else {
error_handler_usage();
}
} else {
error_handler_usage();
}
return conditions;
}
// Helper function for usage check to handle the port argument. Only need to
// check that it's non-empty. Takes port specified by user as arg as well as
// ClientConditions struct. Returns modified struct.
ClientConditions handle_port_arg(ClientConditions conditions, char* port)
{
if (strlen(port) == 0) {
error_handler_usage();
}
conditions.portNumber = port;
return conditions;
}
// Checks that the input that the user specified can be read from. Checks
// that the output file that the user specified can be written to. Takes
// ClientConditions struct (where filenames are stored) and returns nothing,
// but will call error handlers that will terminate the program if
// one or more of the files are unopenable.
void validity_check_io(ClientConditions conditions)
{
if (conditions.inFile) {
int readfd = open(conditions.inFile, O_RDONLY);
if (readfd < 0) {
error_handler_read(conditions);
}
close(readfd);
}
if (conditions.outFile) {
int writefd = open(conditions.outFile, O_RDWR | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR);
if (writefd < 0) {
error_handler_write(conditions);
}
close(writefd);
}
}
// Check if the arguments provided by user are valid by checking if argc > 0,
// checking argv by using various helper functions. Checks for duplicates and
// unexpected option arguments. Assigns values to the ClientConditions struct
// which is returned if no usage errors are detected.
ClientConditions check_usage(int argc, char** argv)
{
ClientConditions conditions = {0, 0, NULL, NULL, {0, 0}, 0};
UsageCheckInfo info = {NULL, 0, 0};
ArgumentPositions positions = {0, 0, 0, 0, 0, 0};
argv++;
argc--;
int argSet = 0;
int scaleFlag = 0;
info.argv = argv;
info.argc = argc;
// Increment argv by one and decrement argc by one to ignore filename
if (argc) {
for (int i = 0; i < argc; i++) { // If one of the positions of the args,
// don't check if its a valid argument
info.currentArg = i;
if ((i == positions.inFileArg || i == positions.degreesArg
|| i == positions.outFileArg || i == positions.scaleArg0
|| i == positions.flipDirectionArg
|| i == positions.scaleArg1)
&& i != 0) {
continue;
}
if (i == 0) {
conditions = handle_port_arg(conditions, argv[i]);
continue;
}
if (!strcmp("--in", argv[i]) && !conditions.inFile) {
conditions = handle_io_arg(conditions, info, 1);
positions.inFileArg = i + 1;
} else if (!strcmp("--output", argv[i]) && !conditions.outFile) {
conditions = handle_io_arg(conditions, info, 0);
positions.outFileArg = i + 1;
} else if (!strcmp("--rotate", argv[i])
&& !conditions.rotateDegrees) {
if (argSet) {
error_handler_usage();
}
conditions = handle_rotate_arg(conditions, info);
positions.degreesArg = i + 1;
argSet = 1;
} else if (!strcmp("--flip", argv[i])
&& !conditions.flipDirection) {
if (argSet) {
error_handler_usage();
}
conditions = handle_flip_arg(conditions, info);
positions.flipDirectionArg = i + 1;
argSet = 1;
} else if (!strcmp("--scale", argv[i]) && !scaleFlag) {
if (argSet) {
error_handler_usage();
}
// Set flags so we don't get repeated args
scaleFlag = 1;
conditions = handle_scale_arg(conditions, info);
positions.scaleArg0 = i + 1;
positions.scaleArg1 = i + 2;
argSet = 1;
} else {
error_handler_usage();
}
}
} else {
error_handler_usage();
}
return conditions;
}
// Attempt to resolve an address and then establish a connection to localhost
// with the provided port number. If we're unable to get the address or
// establish a connection to the server with the provided port no., then
// call the connection error handler. Takes ClientConditions struct to extract
// port number and returns socket file descriptor.
int establish_connection(ClientConditions conditions)
{
struct addrinfo* ai = 0;
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
int err;
if ((err = getaddrinfo("localhost", conditions.portNumber, &hints, &ai))) {
freeaddrinfo(ai);
error_handler_connection(conditions);
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(fd, ai->ai_addr, sizeof(struct sockaddr))) {
error_handler_connection(conditions);
}
return fd;
}
// Reads input from specified source. Will read file if --in specified by user,
// otherwise will read from stdin. Bytes are then fed into HTTPRequest struct
// where they are stored in an unsigned char* and ready to be packaged into
// HTTP request. Number of bytes retrieved from file is also counted
// and then recorded in the HTTPRequest struct. Returns HTTP struct,
// takes ClientConditions struct.
HTTPRequest handle_input(ClientConditions conditions)
{
HTTPRequest request = {NULL, 0, NULL, NULL};
request.numBytes = 0;
int bytesSent = 0;
int byte = 0;
int buffer = BODY_INIT_BUFFER;
unsigned char* httpBody = malloc(sizeof(unsigned char) * buffer);
if (conditions.inFile) {
FILE* fileInFd = fopen(conditions.inFile, "r");
while (!feof(fileInFd)) {
if (bytesSent == buffer - 2) {
buffer *= 2;
httpBody = realloc(httpBody, sizeof(unsigned char) * buffer);
}
byte = fgetc(fileInFd);
httpBody[bytesSent++] = (unsigned char)byte;
if (byte == EOF) {
bytesSent--;
}
}
fclose(fileInFd);
if (!bytesSent) {
error_handler_no_data();
}
// If infile not specified, read from stdin
} else {
while (!feof(stdin)) {
if (bytesSent == buffer - 1) {
buffer *= 2;
httpBody = realloc(httpBody, sizeof(unsigned char) * buffer);
}
byte = fgetc(stdin);
httpBody[bytesSent++] = (unsigned char)byte;
if (byte == EOF) {
bytesSent--;
}
}
if (!bytesSent) {
error_handler_no_data();
}
}
request.numBytes = bytesSent;
request.body = httpBody;
return request;
}
// Function responsible for assembling the flip argument that
// is to be packaged into the request. Returns char* in form
// of the final argument and takes an instance of the Client
// Conditions struct to extract the flip argument.
char* assemble_flip_arg(ClientConditions conditions)
{
char* flipArg = malloc(sizeof(char) * ARG_MIN + ARG_MAX);
strcpy(flipArg, "flip");
strcat(flipArg, ",");
char flipArg0[2];
flipArg0[0] = conditions.flipDirection;
flipArg0[1] = '\0';
strcat(flipArg, flipArg0);
return flipArg;
}
// Function responsible for assembling the scale argument
// that is to be packaged into the HTTP request. Returns char*
// in form of the final argument and takes an instance of the
// ClientConditions struct to extract scale arg.
char* assemble_scale_arg(ClientConditions conditions)
{
char* scaleArg = malloc(sizeof(char) * ARG_MIN + ARG_MAX);
strcpy(scaleArg, "scale");
strcat(scaleArg, ",");
strcat(scaleArg, conditions.scaleArg[0]);
strcat(scaleArg, ",");
strcat(scaleArg, conditions.scaleArg[1]);
return scaleArg;
}
// Function responsible for assembling the rotate arg that is to be
// packaged into the HTTP request. Returns char* in the form of the final
// argument and takes an instance of the ClientConditions struct to
// extract the assemble arg.
char* assemble_rotate_arg(ClientConditions conditions, int genericArg)
{
char* rotateArg = malloc(sizeof(char) * ARG_MIN + ARG_MAX);
strcpy(rotateArg, "rotate");
strcat(rotateArg, ",");
if (!genericArg) {
strcat(rotateArg, conditions.rotateDegrees);
} else {
strcat(rotateArg, "0");
}
return rotateArg;
}
// Assembles the address to be packaged into the request
// by taking the final versions of the assemble,
// flip and rotate args and combining them to
// create a valid address request. Returns char* in form of final
// address. Takes ClientConditions struct.
char* assemble_address(ClientConditions conditions)
{
char* rotateString;
char* scaleArg;
char* flipArg;
char* address = malloc(sizeof(char) * BODY_INIT_BUFFER);
strcpy(address, "/");
int rotateFlag = 0;
int scaleFlag = 0;
int flipFlag = 0;
if (conditions.rotateDegrees) {
rotateString = assemble_rotate_arg(conditions, 0);
rotateFlag = 1;
} else if (conditions.scaleArg[0]) {
scaleArg = assemble_scale_arg(conditions);
scaleFlag = 1;
} else if (conditions.flipDirection) {
flipArg = assemble_flip_arg(conditions);
flipFlag = 1;
}
if (rotateFlag) {
strcat(address, rotateString);
}
if (scaleFlag) {
strcat(address, scaleArg);
}
if (flipFlag) {
strcat(address, flipArg);
}
if (!flipFlag && !scaleFlag && !rotateFlag) {
char* rotateString = assemble_rotate_arg(conditions, 1);
strcat(address, rotateString);
}
return address;
}
// Responsible for receiving HTTP responses from the server and
// storing it into a HTTPResponse struct. Returns the struct,
// takes read end of the file descriptor and takes an instance
// of the HTTPResponse struct that is ready to be populated.
HTTPResponse handle_server_input(int fd2, HTTPResponse response)
{
unsigned char* body;
unsigned long len;
HttpHeader** headers;
int status;
char* statusExplanation;
FILE* from = fdopen(fd2, "r");
if (get_HTTP_response(
from, &status, &statusExplanation, &headers, &body, &len)) {
} else {
error_handler_communication();
}
fclose(from);
response.serverBody = body;
response.numBytes = (int)len;
response.status = status;
response.statusExplanation = statusExplanation;
return response;
}
// Takes response from server and responds appropriately depending
// on what was sent by the server. Returns nothing, takes read end
// of the file descriptor and an instance of the ClientConditions
// struct.
void http_receive_response(ClientConditions conditions, int fd2)
{
HTTPResponse response = {0, NULL, 0, NULL};
response = handle_server_input(fd2, response);
if (response.status == OK_CODE) {
if (conditions.outFile) {
size_t bytesWritten;
FILE* fileout = fopen(conditions.outFile, "w");
bytesWritten = fwrite(response.serverBody, sizeof(unsigned char),
response.numBytes, fileout);
if ((int)bytesWritten < response.numBytes) {
error_handler_output();
}
fclose(fileout);
} else {
size_t bytesWritten;
bytesWritten = fwrite(response.serverBody, sizeof(unsigned char),
response.numBytes, stdout);
if ((int)bytesWritten < response.numBytes) {
error_handler_output();
}
}
} else {
size_t bytesWritten;
bytesWritten = fwrite(response.serverBody, sizeof(unsigned char),
response.numBytes, stderr);
if ((int)bytesWritten < response.numBytes) {
error_handler_output();
}
error_handler_not_ok();
}
}
// Responsible for building a request to be sent to the server and then
// writing that request through the write end of the fd. Takes HTTPRequest
// struct, extracts elements of the struct and assembles a final
// request from the elements, sends request. Calls http_receive_response
// to receive response after request sent. Takes write end of the fd,
// ClientConditions struct and HTTPrequest request.
void http_request_constructor(
ClientConditions conditions, HTTPRequest request, int fd)
{
char contentLength[BODY_INIT_BUFFER];
sprintf(contentLength, "%d", request.numBytes);
int fd2 = dup(fd);
FILE* to = fdopen(fd, "w");
char* finalRequest
= malloc(sizeof(char) * request.numBytes + BODY_INIT_BUFFER);
strcpy(finalRequest, "POST ");
char* address = assemble_address(conditions);
strcat(finalRequest, address);
strcat(finalRequest, " ");
strcat(finalRequest, "HTTP/1.1\r\n");
strcat(finalRequest, "Content-Length: ");
strcat(finalRequest, contentLength);
strcat(finalRequest, "\r\n");
strcat(finalRequest, "\r\n");
fwrite(finalRequest, sizeof(char), strlen(finalRequest), to);
fwrite(request.body, sizeof(unsigned char), request.numBytes, to);
fclose(to);
http_receive_response(conditions, fd2);
}
int main(int argc, char** argv)
{
ClientConditions conditions = check_usage(argc, argv);
validity_check_io(conditions);
int fd = establish_connection(conditions);
HTTPRequest request = handle_input(conditions);
http_request_constructor(conditions, request, fd);
fflush(stdout);
}