You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

images.c 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. /*
  2. * Copyright 2024 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "config.h"
  17. #include "images.h"
  18. #include "task.h"
  19. #include "message.h"
  20. #include "libserver/html/html.h"
  21. #define msg_debug_images(...) rspamd_conditional_debug_fast(NULL, NULL, \
  22. rspamd_images_log_id, "images", task->task_pool->tag.uid, \
  23. G_STRFUNC, \
  24. __VA_ARGS__)
  25. INIT_LOG_MODULE(images)
  26. #ifdef USABLE_GD
  27. #include "gd.h"
  28. #include "hash.h"
  29. #include <math.h>
  30. #define RSPAMD_NORMALIZED_DIM 64
  31. static rspamd_lru_hash_t *images_hash = NULL;
  32. #endif
  33. static const uint8_t png_signature[] = {137, 80, 78, 71, 13, 10, 26, 10};
  34. static const uint8_t jpg_sig1[] = {0xff, 0xd8};
  35. static const uint8_t jpg_sig_jfif[] = {0xff, 0xe0};
  36. static const uint8_t jpg_sig_exif[] = {0xff, 0xe1};
  37. static const uint8_t gif_signature[] = {'G', 'I', 'F', '8'};
  38. static const uint8_t bmp_signature[] = {'B', 'M'};
  39. static bool process_image(struct rspamd_task *task, struct rspamd_mime_part *part);
  40. bool rspamd_images_process_mime_part_maybe(struct rspamd_task *task,
  41. struct rspamd_mime_part *part)
  42. {
  43. if (part->part_type == RSPAMD_MIME_PART_UNDEFINED) {
  44. if (part->detected_type &&
  45. strcmp(part->detected_type, "image") == 0 &&
  46. part->parsed_data.len > 0) {
  47. return process_image(task, part);
  48. }
  49. }
  50. return false;
  51. }
  52. void rspamd_images_process(struct rspamd_task *task)
  53. {
  54. unsigned int i;
  55. struct rspamd_mime_part *part;
  56. PTR_ARRAY_FOREACH(MESSAGE_FIELD(task, parts), i, part)
  57. {
  58. rspamd_images_process_mime_part_maybe(task, part);
  59. }
  60. }
  61. static enum rspamd_image_type
  62. detect_image_type(rspamd_ftok_t *data)
  63. {
  64. if (data->len > sizeof(png_signature) / sizeof(png_signature[0])) {
  65. if (memcmp(data->begin, png_signature, sizeof(png_signature)) == 0) {
  66. return IMAGE_TYPE_PNG;
  67. }
  68. }
  69. if (data->len > 10) {
  70. if (memcmp(data->begin, jpg_sig1, sizeof(jpg_sig1)) == 0) {
  71. if (memcmp(data->begin + 2, jpg_sig_jfif, sizeof(jpg_sig_jfif)) == 0 ||
  72. memcmp(data->begin + 2, jpg_sig_exif, sizeof(jpg_sig_exif)) == 0) {
  73. return IMAGE_TYPE_JPG;
  74. }
  75. }
  76. }
  77. if (data->len > sizeof(gif_signature) / sizeof(gif_signature[0])) {
  78. if (memcmp(data->begin, gif_signature, sizeof(gif_signature)) == 0) {
  79. return IMAGE_TYPE_GIF;
  80. }
  81. }
  82. if (data->len > sizeof(bmp_signature) / sizeof(bmp_signature[0])) {
  83. if (memcmp(data->begin, bmp_signature, sizeof(bmp_signature)) == 0) {
  84. return IMAGE_TYPE_BMP;
  85. }
  86. }
  87. return IMAGE_TYPE_UNKNOWN;
  88. }
  89. static struct rspamd_image *
  90. process_png_image(rspamd_mempool_t *pool, rspamd_ftok_t *data)
  91. {
  92. struct rspamd_image *img;
  93. uint32_t t;
  94. const uint8_t *p;
  95. if (data->len < 24) {
  96. msg_info_pool("bad png detected (maybe striped)");
  97. return NULL;
  98. }
  99. /* In png we should find iHDR section and get data from it */
  100. /* Skip signature and read header section */
  101. p = data->begin + 12;
  102. if (memcmp(p, "IHDR", 4) != 0) {
  103. msg_info_pool("png doesn't begins with IHDR section");
  104. return NULL;
  105. }
  106. img = rspamd_mempool_alloc0(pool, sizeof(struct rspamd_image));
  107. img->type = IMAGE_TYPE_PNG;
  108. img->data = data;
  109. p += 4;
  110. memcpy(&t, p, sizeof(uint32_t));
  111. img->width = ntohl(t);
  112. p += 4;
  113. memcpy(&t, p, sizeof(uint32_t));
  114. img->height = ntohl(t);
  115. return img;
  116. }
  117. static struct rspamd_image *
  118. process_jpg_image(rspamd_mempool_t *pool, rspamd_ftok_t *data)
  119. {
  120. const uint8_t *p, *end;
  121. uint16_t h, w;
  122. struct rspamd_image *img;
  123. img = rspamd_mempool_alloc0(pool, sizeof(struct rspamd_image));
  124. img->type = IMAGE_TYPE_JPG;
  125. img->data = data;
  126. p = data->begin;
  127. end = p + data->len - 8;
  128. p += 2;
  129. while (p < end) {
  130. if (p[0] == 0xFF && p[1] != 0xFF) {
  131. unsigned int len = p[2] * 256 + p[3];
  132. p++;
  133. if (*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3 ||
  134. *p == 0xc9 || *p == 0xca || *p == 0xcb) {
  135. memcpy(&h, p + 4, sizeof(uint16_t));
  136. h = p[4] * 0xff + p[5];
  137. img->height = h;
  138. w = p[6] * 0xff + p[7];
  139. img->width = w;
  140. return img;
  141. }
  142. p += len;
  143. }
  144. else {
  145. p++;
  146. }
  147. }
  148. return NULL;
  149. }
  150. static struct rspamd_image *
  151. process_gif_image(rspamd_mempool_t *pool, rspamd_ftok_t *data)
  152. {
  153. struct rspamd_image *img;
  154. const uint8_t *p;
  155. uint16_t t;
  156. if (data->len < 10) {
  157. msg_info_pool("bad gif detected (maybe striped)");
  158. return NULL;
  159. }
  160. img = rspamd_mempool_alloc0(pool, sizeof(struct rspamd_image));
  161. img->type = IMAGE_TYPE_GIF;
  162. img->data = data;
  163. p = data->begin + 6;
  164. memcpy(&t, p, sizeof(uint16_t));
  165. img->width = GUINT16_FROM_LE(t);
  166. memcpy(&t, p + 2, sizeof(uint16_t));
  167. img->height = GUINT16_FROM_LE(t);
  168. return img;
  169. }
  170. static struct rspamd_image *
  171. process_bmp_image(rspamd_mempool_t *pool, rspamd_ftok_t *data)
  172. {
  173. struct rspamd_image *img;
  174. int32_t t;
  175. const uint8_t *p;
  176. if (data->len < 28) {
  177. msg_info_pool("bad bmp detected (maybe striped)");
  178. return NULL;
  179. }
  180. img = rspamd_mempool_alloc0(pool, sizeof(struct rspamd_image));
  181. img->type = IMAGE_TYPE_BMP;
  182. img->data = data;
  183. p = data->begin + 18;
  184. memcpy(&t, p, sizeof(uint32_t));
  185. img->width = GUINT32_FROM_LE(t);
  186. memcpy(&t, p + 4, sizeof(int32_t));
  187. img->height = GUINT32_FROM_LE(t);
  188. return img;
  189. }
  190. #ifdef USABLE_GD
  191. /*
  192. * DCT from Emil Mikulic.
  193. * http://unix4lyfe.org/dct/
  194. */
  195. static void
  196. rspamd_image_dct_block(int pixels[8][8], double *out)
  197. {
  198. int i;
  199. int rows[8][8];
  200. static const int c1 = 1004 /* cos(pi/16) << 10 */,
  201. s1 = 200 /* sin(pi/16) */,
  202. c3 = 851 /* cos(3pi/16) << 10 */,
  203. s3 = 569 /* sin(3pi/16) << 10 */,
  204. r2c6 = 554 /* sqrt(2)*cos(6pi/16) << 10 */,
  205. r2s6 = 1337 /* sqrt(2)*sin(6pi/16) << 10 */,
  206. r2 = 181; /* sqrt(2) << 7*/
  207. int x0, x1, x2, x3, x4, x5, x6, x7, x8;
  208. /* transform rows */
  209. for (i = 0; i < 8; i++) {
  210. x0 = pixels[0][i];
  211. x1 = pixels[1][i];
  212. x2 = pixels[2][i];
  213. x3 = pixels[3][i];
  214. x4 = pixels[4][i];
  215. x5 = pixels[5][i];
  216. x6 = pixels[6][i];
  217. x7 = pixels[7][i];
  218. /* Stage 1 */
  219. x8 = x7 + x0;
  220. x0 -= x7;
  221. x7 = x1 + x6;
  222. x1 -= x6;
  223. x6 = x2 + x5;
  224. x2 -= x5;
  225. x5 = x3 + x4;
  226. x3 -= x4;
  227. /* Stage 2 */
  228. x4 = x8 + x5;
  229. x8 -= x5;
  230. x5 = x7 + x6;
  231. x7 -= x6;
  232. x6 = c1 * (x1 + x2);
  233. x2 = (-s1 - c1) * x2 + x6;
  234. x1 = (s1 - c1) * x1 + x6;
  235. x6 = c3 * (x0 + x3);
  236. x3 = (-s3 - c3) * x3 + x6;
  237. x0 = (s3 - c3) * x0 + x6;
  238. /* Stage 3 */
  239. x6 = x4 + x5;
  240. x4 -= x5;
  241. x5 = r2c6 * (x7 + x8);
  242. x7 = (-r2s6 - r2c6) * x7 + x5;
  243. x8 = (r2s6 - r2c6) * x8 + x5;
  244. x5 = x0 + x2;
  245. x0 -= x2;
  246. x2 = x3 + x1;
  247. x3 -= x1;
  248. /* Stage 4 and output */
  249. rows[i][0] = x6;
  250. rows[i][4] = x4;
  251. rows[i][2] = x8 >> 10;
  252. rows[i][6] = x7 >> 10;
  253. rows[i][7] = (x2 - x5) >> 10;
  254. rows[i][1] = (x2 + x5) >> 10;
  255. rows[i][3] = (x3 * r2) >> 17;
  256. rows[i][5] = (x0 * r2) >> 17;
  257. }
  258. /* transform columns */
  259. for (i = 0; i < 8; i++) {
  260. x0 = rows[0][i];
  261. x1 = rows[1][i];
  262. x2 = rows[2][i];
  263. x3 = rows[3][i];
  264. x4 = rows[4][i];
  265. x5 = rows[5][i];
  266. x6 = rows[6][i];
  267. x7 = rows[7][i];
  268. /* Stage 1 */
  269. x8 = x7 + x0;
  270. x0 -= x7;
  271. x7 = x1 + x6;
  272. x1 -= x6;
  273. x6 = x2 + x5;
  274. x2 -= x5;
  275. x5 = x3 + x4;
  276. x3 -= x4;
  277. /* Stage 2 */
  278. x4 = x8 + x5;
  279. x8 -= x5;
  280. x5 = x7 + x6;
  281. x7 -= x6;
  282. x6 = c1 * (x1 + x2);
  283. x2 = (-s1 - c1) * x2 + x6;
  284. x1 = (s1 - c1) * x1 + x6;
  285. x6 = c3 * (x0 + x3);
  286. x3 = (-s3 - c3) * x3 + x6;
  287. x0 = (s3 - c3) * x0 + x6;
  288. /* Stage 3 */
  289. x6 = x4 + x5;
  290. x4 -= x5;
  291. x5 = r2c6 * (x7 + x8);
  292. x7 = (-r2s6 - r2c6) * x7 + x5;
  293. x8 = (r2s6 - r2c6) * x8 + x5;
  294. x5 = x0 + x2;
  295. x0 -= x2;
  296. x2 = x3 + x1;
  297. x3 -= x1;
  298. /* Stage 4 and output */
  299. out[i * 8] = (double) ((x6 + 16) >> 3);
  300. out[i * 8 + 1] = (double) ((x4 + 16) >> 3);
  301. out[i * 8 + 2] = (double) ((x8 + 16384) >> 13);
  302. out[i * 8 + 3] = (double) ((x7 + 16384) >> 13);
  303. out[i * 8 + 4] = (double) ((x2 - x5 + 16384) >> 13);
  304. out[i * 8 + 5] = (double) ((x2 + x5 + 16384) >> 13);
  305. out[i * 8 + 6] = (double) (((x3 >> 8) * r2 + 8192) >> 12);
  306. out[i * 8 + 7] = (double) (((x0 >> 8) * r2 + 8192) >> 12);
  307. }
  308. }
  309. struct rspamd_image_cache_entry {
  310. unsigned char digest[64];
  311. unsigned char dct[RSPAMD_DCT_LEN / NBBY];
  312. };
  313. static void
  314. rspamd_image_cache_entry_dtor(gpointer p)
  315. {
  316. struct rspamd_image_cache_entry *entry = p;
  317. g_free(entry);
  318. }
  319. static uint32_t
  320. rspamd_image_dct_hash(gconstpointer p)
  321. {
  322. return rspamd_cryptobox_fast_hash(p, rspamd_cryptobox_HASHBYTES,
  323. rspamd_hash_seed());
  324. }
  325. static gboolean
  326. rspamd_image_dct_equal(gconstpointer a, gconstpointer b)
  327. {
  328. return memcmp(a, b, rspamd_cryptobox_HASHBYTES) == 0;
  329. }
  330. static void
  331. rspamd_image_create_cache(struct rspamd_config *cfg)
  332. {
  333. images_hash = rspamd_lru_hash_new_full(cfg->images_cache_size, NULL,
  334. rspamd_image_cache_entry_dtor,
  335. rspamd_image_dct_hash, rspamd_image_dct_equal);
  336. }
  337. static gboolean
  338. rspamd_image_check_hash(struct rspamd_task *task, struct rspamd_image *img)
  339. {
  340. struct rspamd_image_cache_entry *found;
  341. if (images_hash == NULL) {
  342. rspamd_image_create_cache(task->cfg);
  343. }
  344. found = rspamd_lru_hash_lookup(images_hash, img->parent->digest,
  345. task->tv.tv_sec);
  346. if (found) {
  347. /* We need to decompress */
  348. img->dct = g_malloc(RSPAMD_DCT_LEN / NBBY);
  349. rspamd_mempool_add_destructor(task->task_pool, g_free,
  350. img->dct);
  351. /* Copy as found could be destroyed by LRU */
  352. memcpy(img->dct, found->dct, RSPAMD_DCT_LEN / NBBY);
  353. img->is_normalized = TRUE;
  354. return TRUE;
  355. }
  356. return FALSE;
  357. }
  358. static void
  359. rspamd_image_save_hash(struct rspamd_task *task, struct rspamd_image *img)
  360. {
  361. struct rspamd_image_cache_entry *found;
  362. if (img->is_normalized) {
  363. found = rspamd_lru_hash_lookup(images_hash, img->parent->digest,
  364. task->tv.tv_sec);
  365. if (!found) {
  366. found = g_malloc0(sizeof(*found));
  367. memcpy(found->dct, img->dct, RSPAMD_DCT_LEN / NBBY);
  368. memcpy(found->digest, img->parent->digest, sizeof(found->digest));
  369. rspamd_lru_hash_insert(images_hash, found->digest, found,
  370. task->tv.tv_sec, 0);
  371. }
  372. }
  373. }
  374. #endif
  375. void rspamd_image_normalize(struct rspamd_task *task, struct rspamd_image *img)
  376. {
  377. #ifdef USABLE_GD
  378. gdImagePtr src = NULL, dst = NULL;
  379. unsigned int i, j, k, l;
  380. double *dct;
  381. if (img->data->len == 0 || img->data->len > G_MAXINT32) {
  382. return;
  383. }
  384. if (img->height <= RSPAMD_NORMALIZED_DIM ||
  385. img->width <= RSPAMD_NORMALIZED_DIM) {
  386. return;
  387. }
  388. if (img->data->len > task->cfg->max_pic_size) {
  389. return;
  390. }
  391. if (rspamd_image_check_hash(task, img)) {
  392. return;
  393. }
  394. switch (img->type) {
  395. case IMAGE_TYPE_JPG:
  396. src = gdImageCreateFromJpegPtr(img->data->len, (void *) img->data->begin);
  397. break;
  398. case IMAGE_TYPE_PNG:
  399. src = gdImageCreateFromPngPtr(img->data->len, (void *) img->data->begin);
  400. break;
  401. case IMAGE_TYPE_GIF:
  402. src = gdImageCreateFromGifPtr(img->data->len, (void *) img->data->begin);
  403. break;
  404. case IMAGE_TYPE_BMP:
  405. src = gdImageCreateFromBmpPtr(img->data->len, (void *) img->data->begin);
  406. break;
  407. default:
  408. return;
  409. }
  410. if (src == NULL) {
  411. msg_info_task("cannot load image of type %s from %T",
  412. rspamd_image_type_str(img->type), img->filename);
  413. }
  414. else {
  415. gdImageSetInterpolationMethod(src, GD_BILINEAR_FIXED);
  416. dst = gdImageScale(src, RSPAMD_NORMALIZED_DIM, RSPAMD_NORMALIZED_DIM);
  417. gdImageGrayScale(dst);
  418. gdImageDestroy(src);
  419. img->is_normalized = TRUE;
  420. dct = g_malloc0(sizeof(double) * RSPAMD_DCT_LEN);
  421. img->dct = g_malloc0(RSPAMD_DCT_LEN / NBBY);
  422. rspamd_mempool_add_destructor(task->task_pool, g_free,
  423. img->dct);
  424. /*
  425. * Split message into blocks:
  426. *
  427. * ****
  428. * ****
  429. *
  430. * Get sum of saturation values, and set bit if sum is > avg
  431. * Then go further
  432. *
  433. * ****
  434. * ****
  435. *
  436. * and repeat this algorithm.
  437. *
  438. * So on each iteration we move by 16 pixels and calculate 2 elements of
  439. * signature
  440. */
  441. for (i = 0; i < RSPAMD_NORMALIZED_DIM; i += 8) {
  442. for (j = 0; j < RSPAMD_NORMALIZED_DIM; j += 8) {
  443. int p[8][8];
  444. for (k = 0; k < 8; k++) {
  445. p[k][0] = gdImageGetPixel(dst, i + k, j);
  446. p[k][1] = gdImageGetPixel(dst, i + k, j + 1);
  447. p[k][2] = gdImageGetPixel(dst, i + k, j + 2);
  448. p[k][3] = gdImageGetPixel(dst, i + k, j + 3);
  449. p[k][4] = gdImageGetPixel(dst, i + k, j + 4);
  450. p[k][5] = gdImageGetPixel(dst, i + k, j + 5);
  451. p[k][6] = gdImageGetPixel(dst, i + k, j + 6);
  452. p[k][7] = gdImageGetPixel(dst, i + k, j + 7);
  453. }
  454. rspamd_image_dct_block(p,
  455. dct + i * RSPAMD_NORMALIZED_DIM + j);
  456. double avg = 0.0;
  457. for (k = 0; k < 8; k++) {
  458. for (l = 0; l < 8; l++) {
  459. double x = *(dct +
  460. i * RSPAMD_NORMALIZED_DIM + j + k * 8 + l);
  461. avg += (x - avg) / (double) (k * 8 + l + 1);
  462. }
  463. }
  464. for (k = 0; k < 8; k++) {
  465. for (l = 0; l < 8; l++) {
  466. unsigned int idx = i * RSPAMD_NORMALIZED_DIM + j + k * 8 + l;
  467. if (dct[idx] >= avg) {
  468. setbit(img->dct, idx);
  469. }
  470. }
  471. }
  472. }
  473. }
  474. gdImageDestroy(dst);
  475. g_free(dct);
  476. rspamd_image_save_hash(task, img);
  477. }
  478. #endif
  479. }
  480. struct rspamd_image *
  481. rspamd_maybe_process_image(rspamd_mempool_t *pool,
  482. rspamd_ftok_t *data)
  483. {
  484. enum rspamd_image_type type;
  485. struct rspamd_image *img = NULL;
  486. if ((type = detect_image_type(data)) != IMAGE_TYPE_UNKNOWN) {
  487. switch (type) {
  488. case IMAGE_TYPE_PNG:
  489. img = process_png_image(pool, data);
  490. break;
  491. case IMAGE_TYPE_JPG:
  492. img = process_jpg_image(pool, data);
  493. break;
  494. case IMAGE_TYPE_GIF:
  495. img = process_gif_image(pool, data);
  496. break;
  497. case IMAGE_TYPE_BMP:
  498. img = process_bmp_image(pool, data);
  499. break;
  500. default:
  501. img = NULL;
  502. break;
  503. }
  504. }
  505. return img;
  506. }
  507. static bool
  508. process_image(struct rspamd_task *task, struct rspamd_mime_part *part)
  509. {
  510. struct rspamd_image *img;
  511. img = rspamd_maybe_process_image(task->task_pool, &part->parsed_data);
  512. if (img != NULL) {
  513. msg_debug_images("detected %s image of size %ud x %ud",
  514. rspamd_image_type_str(img->type),
  515. img->width, img->height);
  516. if (part->cd) {
  517. img->filename = &part->cd->filename;
  518. }
  519. img->parent = part;
  520. part->part_type = RSPAMD_MIME_PART_IMAGE;
  521. part->specific.img = img;
  522. return true;
  523. }
  524. return false;
  525. }
  526. const char *
  527. rspamd_image_type_str(enum rspamd_image_type type)
  528. {
  529. switch (type) {
  530. case IMAGE_TYPE_PNG:
  531. return "PNG";
  532. break;
  533. case IMAGE_TYPE_JPG:
  534. return "JPEG";
  535. break;
  536. case IMAGE_TYPE_GIF:
  537. return "GIF";
  538. break;
  539. case IMAGE_TYPE_BMP:
  540. return "BMP";
  541. break;
  542. default:
  543. break;
  544. }
  545. return "unknown";
  546. }
  547. static void
  548. rspamd_image_process_part(struct rspamd_task *task, struct rspamd_mime_part *part)
  549. {
  550. struct rspamd_mime_header *rh;
  551. struct rspamd_mime_text_part *tp;
  552. struct html_image *himg;
  553. const char *cid;
  554. unsigned int cid_len, i;
  555. struct rspamd_image *img;
  556. img = (struct rspamd_image *) part->specific.img;
  557. if (img) {
  558. /* Check Content-Id */
  559. rh = rspamd_message_get_header_from_hash(part->raw_headers,
  560. "Content-Id", FALSE);
  561. if (rh) {
  562. cid = rh->decoded;
  563. if (*cid == '<') {
  564. cid++;
  565. }
  566. cid_len = strlen(cid);
  567. if (cid_len > 0) {
  568. if (cid[cid_len - 1] == '>') {
  569. cid_len--;
  570. }
  571. PTR_ARRAY_FOREACH(MESSAGE_FIELD(task, text_parts), i, tp)
  572. {
  573. if (IS_TEXT_PART_HTML(tp) && tp->html != NULL) {
  574. himg = rspamd_html_find_embedded_image(tp->html, cid, cid_len);
  575. if (himg != NULL) {
  576. img->html_image = himg;
  577. himg->embedded_image = img;
  578. msg_debug_images("found linked image by cid: <%s>",
  579. cid);
  580. if (himg->height == 0) {
  581. himg->height = img->height;
  582. }
  583. if (himg->width == 0) {
  584. himg->width = img->width;
  585. }
  586. }
  587. }
  588. }
  589. }
  590. }
  591. }
  592. }
  593. void rspamd_images_link(struct rspamd_task *task)
  594. {
  595. struct rspamd_mime_part *part;
  596. unsigned int i;
  597. PTR_ARRAY_FOREACH(MESSAGE_FIELD(task, parts), i, part)
  598. {
  599. if (part->part_type == RSPAMD_MIME_PART_IMAGE) {
  600. rspamd_image_process_part(task, part);
  601. }
  602. }
  603. }