format.c (7148B)
1 #include "format.h" 2 3 #include <err.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <time.h> 7 8 void print_time(FILE* out, time_t time, int timezone_offset) { 9 time_t local_time = time + (timezone_offset * 60); 10 struct tm* time_in = gmtime(&local_time); 11 if (!time_in) { 12 return; 13 } 14 15 char formatted_time[32]; 16 if (!strftime(formatted_time, sizeof(formatted_time), "%a, %e %b %Y %H:%M:%S", 17 time_in)) { 18 err(1, "strftime"); 19 } 20 21 char timezone_sign = timezone_offset < 0 ? '-' : '+'; 22 int timezone_hours = (timezone_offset < 0 ? -1 : 1) * timezone_offset / 60; 23 int timezone_mins = (timezone_offset < 0 ? -1 : 1) * timezone_offset % 60; 24 char out_str[64]; 25 if (snprintf(out_str, sizeof(out_str), "%s %c%02d%02d", formatted_time, 26 timezone_sign, timezone_hours, timezone_mins) < 0) { 27 err(1, "snprintf"); 28 } 29 fprintf(out, "%s", out_str); 30 } 31 32 void print_time_z(FILE* out, time_t time) { 33 struct tm* time_in = gmtime(&time); 34 if (!time_in) { 35 return; 36 } 37 38 char formatted_time[32]; 39 if (!strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%dT%H:%M:%SZ", 40 time_in)) { 41 err(1, "strftime"); 42 } 43 fprintf(out, "%s", formatted_time); 44 } 45 46 void print_time_short(FILE* out, time_t time) { 47 struct tm* time_in = gmtime(&time); 48 if (!time_in) { 49 return; 50 } 51 52 char formatted_time[32]; 53 if (!strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %H:%M", 54 time_in)) { 55 err(1, "strftime"); 56 } 57 fprintf(out, "%s", formatted_time); 58 } 59 60 void print_percent_encoded(FILE* out, const char* str) { 61 static const char* hex_chars = "0123456789ABCDEF"; 62 63 size_t str_len = strlen(str); 64 for (size_t i = 0; i < str_len; i++) { 65 unsigned char uc = str[i]; 66 // NOTE: do not encode '/' for paths or ",-." 67 if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') || uc == '[' || 68 uc == ']') { 69 fprintf(out, "%%%c%c", hex_chars[(uc >> 4) & 0x0f], hex_chars[uc & 0x0f]); 70 } else { 71 fprintf(out, "%c", uc); 72 } 73 } 74 } 75 76 void print_xml_encoded(FILE* out, const char* str) { 77 print_xml_encoded_len(out, str, -1, true); 78 } 79 80 void print_xml_encoded_len(FILE* out, 81 const char* str, 82 ssize_t str_len, 83 bool output_crlf) { 84 size_t len = (str_len >= 0) ? (size_t)str_len : strlen(str); 85 for (size_t i = 0; i < len && str[i] != '\0'; i++) { 86 char c = str[i]; 87 switch (c) { 88 case '<': 89 fprintf(out, "<"); 90 break; 91 case '>': 92 fprintf(out, ">"); 93 break; 94 case '\'': 95 fprintf(out, "'"); 96 break; 97 case '&': 98 fprintf(out, "&"); 99 break; 100 case '"': 101 fprintf(out, """); 102 break; 103 case '\r': 104 case '\n': 105 if (output_crlf) { 106 fprintf(out, "%c", c); 107 } 108 break; 109 default: 110 fprintf(out, "%c", c); 111 } 112 } 113 } 114 115 void print_gopher_text(FILE* out, const char* str, bool output_lf) { 116 print_gopher_text_len(out, str, -1, output_lf); 117 } 118 119 void print_gopher_text_len(FILE* out, 120 const char* str, 121 ssize_t str_len, 122 bool output_lf) { 123 size_t len = (str_len >= 0) ? (size_t)str_len : strlen(str); 124 bool start_of_line = true; 125 for (size_t i = 0; i < len && str[i] != '\0'; i++) { 126 char c = str[i]; 127 if (output_lf && start_of_line && c == '[') { 128 fprintf(out, "[|"); 129 } else if (c == '\t') { 130 fprintf(out, " "); 131 } else if (c == '\r' || (!output_lf && c == '\n')) { 132 // Ignore. 133 } else { 134 fprintf(out, "%c", c); 135 } 136 start_of_line = (c == '\n'); 137 } 138 } 139 140 void print_gopher_link(FILE* out, const char* str) { 141 for (size_t i = 0; str[i] != '\0'; i++) { 142 char c = str[i]; 143 if (c == '|') { 144 fprintf(out, "\\|"); 145 } else if (c == '\t') { 146 fprintf(out, " "); 147 } else if (c == '\r' || c == '\n') { 148 // Ignore. 149 } else { 150 fprintf(out, "%c", c); 151 } 152 } 153 } 154 155 static size_t utf8_char_length(const char* str) { 156 unsigned char c = (unsigned char)*str; 157 if (c < 0x80) { 158 return 1; 159 } 160 if ((c & 0xe0) == 0xc0) { 161 return 2; 162 } 163 if ((c & 0xf0) == 0xe0) { 164 return 3; 165 } 166 if ((c & 0xf8) == 0xf0) { 167 return 4; 168 } 169 // Invalid UTF-8 sequence. Treat as single byte. 170 return 1; 171 } 172 173 static bool is_zwj(const char* str) { 174 return str[0] == '\xe2' && str[1] == '\x80' && str[2] == '\x8d'; 175 } 176 177 static bool is_variation_selector(const char* str) { 178 return str[0] == '\xef' && str[1] == '\xb8' && str[2] == '\x8f'; 179 } 180 181 static size_t get_char_width(wchar_t c) { 182 // ASCII characters in the range [0-31] and [127-255] are control characters 183 // or non-printable characters 184 if ((c >= 0 && c <= 31) || (c >= 127 && c <= 255)) { 185 return 1; 186 } 187 188 // Emoji range. 189 if (c >= 0x1F300 && c <= 0x1F6FF) { 190 return 2; 191 } 192 193 // Extended pictographic characters. 194 if (c >= 0x1F900 && c <= 0x1F9FF) { 195 return 2; 196 } 197 198 // Hiragana and Katakana ranges. 199 if ((c >= 0x3040 && c <= 0x309F) || (c >= 0x30A0 && c <= 0x30FF)) { 200 return 2; 201 } 202 203 // Kanji range. 204 if (c >= 0x4E00 && c <= 0x9FFF) { 205 return 2; 206 } 207 return 1; 208 } 209 210 // TODO(cbracken): There has to be a better way. 211 void print_gopher_link_padded(FILE* out, 212 const char* str, 213 size_t width, 214 char pad_char) { 215 size_t str_len = strlen(str); 216 size_t char_count = 0; 217 size_t last_char_width = 0; 218 size_t byte_count = 0; 219 while (byte_count < str_len) { 220 size_t pos = byte_count; 221 size_t char_length = utf8_char_length(&str[byte_count]); 222 byte_count += char_length; 223 if (char_count == width - 1 && byte_count < str_len) { 224 fprintf(out, "\u2026"); 225 last_char_width = 1; 226 char_count += last_char_width; 227 } else if (char_count < width) { 228 if (str[pos] == '|') { 229 fprintf(out, "\\|"); 230 char_count++; 231 last_char_width = 1; 232 } else if (str[pos] == '\t') { 233 for (size_t i = 0; i < 8 && char_count < width; i++) { 234 fprintf(out, " "); 235 char_count++; 236 } 237 last_char_width = 1; 238 } else if (str[pos] == '\r' || str[pos] == '\n') { 239 // Ignore. 240 last_char_width = 0; 241 } else if (char_length == 3 && is_zwj(&str[pos])) { 242 fprintf(out, "\xe2\x80\x8d"); 243 char_count -= last_char_width; 244 last_char_width = 0; 245 } else if (char_length == 3 && is_variation_selector(&str[pos])) { 246 fprintf(out, "\xef\xb8\x8f"); 247 char_count -= last_char_width; 248 last_char_width = 0; 249 } else { 250 for (size_t i = pos; i < byte_count; i++) { 251 fprintf(out, "%c", str[i]); 252 } 253 wchar_t wide_char; 254 if (mbtowc(&wide_char, &str[pos], MB_CUR_MAX) >= 0) { 255 last_char_width = get_char_width(wide_char); 256 } else { 257 last_char_width = 1; 258 } 259 char_count += last_char_width; 260 } 261 } 262 } 263 if (pad_char != '\0') { 264 while (char_count < width) { 265 fprintf(out, "%c", pad_char); 266 char_count++; 267 } 268 } 269 }