Release 2017.1
[cascardo/rnetclient.git] / decfile.c
index ebffc9f..3856c2b 100644 (file)
--- a/decfile.c
+++ b/decfile.c
@@ -1,5 +1,6 @@
 /*
- *  Copyright (C) 2012-2013  Thadeu Lima de Souza Cascardo <cascardo@minaslivre.org>
+ *  Copyright (C) 2012-2014  Thadeu Lima de Souza Cascardo <cascardo@minaslivre.org>
+ *  Copyright (C) 2014  Alexandre Oliva <lxoliva@fsfla.org>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
+#include <gcrypt.h>
+#include "pmhash.h"
+#include "rnet_message.h"
+
+#ifndef MAX
+#define MAX(a,b) (a >= b) ? a : b
+#endif
 
 struct rnet_decfile {
        char *filename;
        FILE *file;
        char **lines;
        int lines_len;
+       struct pmhash *header;
+       struct rnet_message *message;
 };
 
 /*
@@ -51,7 +61,7 @@ static int append_line(struct rnet_decfile *decfile, char *line)
        return 0;
 out:
        decfile->lines_len -= 1;
-       return -1;
+       return -ENOMEM;
 }
 
 static void decfile_release_lines(struct rnet_decfile *decfile)
@@ -63,6 +73,280 @@ static void decfile_release_lines(struct rnet_decfile *decfile)
        decfile->lines = NULL;
 }
 
+static char * get_header(struct rnet_decfile *decfile);
+static int decfile_parse_file(struct rnet_decfile *decfile);
+
+#define parse(field, sz) \
+       r = -ENOMEM; \
+       val = malloc(sz + 1); \
+       if (!val) \
+               goto out_val; \
+       val[sz] = 0; \
+       memcpy(val, p, sz); \
+       p += sz; \
+       key = strdup(field); \
+       if (!key) \
+               goto out_key; \
+       if (pmhash_add(&hash, key, val)) \
+               goto out_add;
+
+static int decfile_parse_header(struct rnet_decfile *decfile)
+{
+       char *buffer;
+       int r;
+       int exerc;
+       char *p;
+       char *tail;
+       char *key;
+       char *val;
+       struct pmhash *hash;
+
+       p = buffer = get_header(decfile);
+       if (!buffer)
+               return -EINVAL;
+
+       hash = decfile->header;
+
+       /* Common header fields. Most of these are used by rnet_encode. */
+       parse("sistema", 8);
+       parse("exerc", 4);
+       parse("ano", 4);
+       parse("codigo_recnet", 4);
+       parse("in_ret", 1);
+       parse("cpf", 11);
+       parse("filler", 3);
+       parse("tipo_ni", 1);
+       parse("nr_versao", 3);
+       parse("nome", 60);
+       parse("uf", 2);
+       parse("hash", 10);
+
+       /* Assert the size of the common header matches the expectation. */
+       if (p - buffer != RNET_HEADER_HEAD_COMMON) {
+               fprintf(stderr, "RNET_HEADER_HEAD_COMMON in decfile.h needs to be adjusted to %ti\n", p - buffer);
+               goto out_val;
+       }
+
+       /* Retrieve exerc from the hash table. */
+       exerc = atoi(pmhash_get(hash, "exerc"));
+
+       /* Assert exerc is no less than the minimum supported version */
+       if (exerc < 2013) {
+               fprintf(stderr, "This software does not support declarations older than 2013.\n");
+               goto out_val;
+       }
+
+       /* Check for tested versions. */
+       if (exerc > 2016) {
+               fprintf(stderr, "Unknown file version, but proceeding anyway.\n");
+               return 0;
+       }
+
+       /* These fields exist at least since 2013. */
+       parse("in_cert", 1);
+       parse("dt_nasc", 8);
+       parse("in_comp", 1);
+       parse("in_res", 1);
+       parse("in_gerada", 1);
+       parse("nr_recibo_anterior", 10);
+       parse("in_pgd", 1);
+       parse("so", 14);
+       parse("versao_so", 7);
+       parse("jvm", 9);
+       parse("nr_recibo", 10);
+       parse("municipio", 4);
+       parse("conjuge", 11);
+       parse("obrig", 1);
+       parse("impdevido", 13);
+       parse("nr_recibo", 10);
+       parse("in_seg", 1);
+       parse("imppago", 2);
+       parse("impant", 1);
+       parse("mudend", 1);
+       parse("cep", 8);
+       parse("debito", 1);
+       parse("banco", 3);
+       parse("agencia", 4);
+       parse("filler", 1);
+       parse("data_julgado", 8);
+       parse("imppagar", 13);
+       parse("tribfonte", 1);
+       parse("cpfrra", 11);
+       parse("trib_rra", 1);
+       parse("cpf_rra2", 11);
+       parse("trib_3rra", 1);
+       parse("cpf_rra3", 11);
+
+       /* Fields added in 2014. */
+       if (exerc >= 2014) {
+               parse("trib_4rra", 1);
+               parse("cpf_rra4", 11);
+       }
+
+       /* These fields exist at least since 2013. */
+       parse("vr_doacao", 13);
+       parse("cnpj1", 14);
+       parse("cnpj2", 14);
+       parse("cnpj3", 14);
+       parse("cnpj4", 14);
+       parse("cpf_dep1", 11);
+       parse("dnas_dep1", 8);
+       parse("cpf_dep2", 11);
+       parse("dnas_dep2", 8);
+       parse("cpf_dep3", 11);
+       parse("dnas_dep3", 8);
+       parse("cpf_dep4", 11);
+       parse("dnas_dep4", 8);
+       parse("cpf_dep5", 11);
+       parse("dnas_dep5", 8);
+       parse("cpf_dep6", 11);
+       parse("dnas_dep6", 8);
+       parse("cnpj_med1", 14);
+       parse("cnpj_med2", 14);
+       parse("cpf_alim", 11);
+       parse("cpf_invent", 11);
+       parse("municipio", 40);
+       parse("contribuinte", 60);
+
+       /* The contents of this field until 2014 (cpf_empregada) were moved to
+        * the end of the header in 2015 (cpfdomestic@). This field has then
+        * been converted into a filler field. */
+       if (exerc <= 2014) {
+               parse("cpf_empregada", 11);
+       } else {
+               parse("filler", 11);
+       }
+
+       /* These fields exist at least since 2013. */
+       parse("mac", 12);
+       parse("data_nao_residente", 8);
+       parse("cpf_procurador", 11);
+       parse("obrigatoriedade", 3);
+       parse("rendtrib", 13);
+       parse("cnpj_prev", 14);
+       parse("cnpj_prev2", 14);
+       parse("vr_totisentos", 13);
+       parse("vr_totexclusivo", 13);
+       parse("vr_totpagamentos", 13);
+
+       /* End of header in 2013. */
+
+       /* Fields added in 2014. */
+       if (exerc >= 2014) {
+               parse("nr_conta", 13);
+               parse("nr_dv_conta", 2);
+               parse("in_dv_conta", 1);
+       }
+
+       /* End of header in 2014. */
+
+       /* Fields added in 2015. */
+       if (exerc >= 2015) {
+               parse("codnaturezaocup", 2);
+               parse("cpfdomestic@", 11);
+               parse("nitdomestic@", 11);
+               parse("cpfdomestic@2", 11);
+               parse("nitdomestic@2", 11);
+               parse("cpfdomestic@3", 11);
+               parse("nitdomestic@3", 11);
+               parse("deciniciada", 1);
+               parse("utilpgd", 1);
+               parse("utilapp", 1);
+               parse("utilonline", 1);
+               parse("utilrascunho", 1);
+               parse("utilprepreenchida", 1);
+               parse("utilfontes", 1);
+               parse("utilplanosaude", 1);
+               parse("utilrecuperar", 1);
+               parse("dectransmitida", 1);
+       }
+
+       /* End of header in 2015. */
+
+       /* Fields added in 2016. */
+       if (exerc >= 2016) {
+               parse("dedutivelmaior1", 14);
+               parse("dedutivelmaior2", 14);
+               parse("dedutivelmaior3", 14);
+               parse("dedutivelmaior4", 14);
+               parse("dedutivelmaior5", 14);
+               parse("dedutivelmaior6", 14);
+               parse("funprespmaior", 14);
+       }
+
+       /* End of header in 2016. */
+
+       /* Tail fields, which exist at least since 2013. */
+       tail = p;
+       parse("versaotestpgd", 3);
+       parse("controle", 10);
+
+       if (*p++ != '\r') {
+               fprintf(stderr,
+                       "missing CR at the %tith header character\n",
+                       p - buffer);
+               goto out_val;
+       } else if (*p++ != '\n') {
+               fprintf(stderr,
+                       "missing LF at the %tith header character\n",
+                       p - buffer);
+               goto out_val;
+       } else if (*p != 0) {
+               fprintf(stderr,
+                       "missing NUL at the %tith header character\n",
+                       p - buffer);
+               goto out_val;
+       } else if (p - tail != RNET_HEADER_TAIL_COMMON) {
+               fprintf(stderr, "RNET_HEADER_TAIL_COMMON in decfile.h needs to be adjusted to %ti\n", p - tail);
+               goto out_val;
+       }
+
+       /* Verify header size */
+       switch (exerc) {
+               case 2013:
+                       if (p - buffer != RNET_HEADER_SIZE_2013) {
+                               fprintf(stderr, "RNET_HEADER_SIZE_2013 in decfile.h needs to be adjusted to %ti,\nor parse_header in decfile.c needs updating\n", p - buffer);
+                               goto out_val;
+                       }
+                       break;
+               case 2014:
+                       if (p - buffer != RNET_HEADER_SIZE_2014) {
+                               fprintf(stderr, "RNET_HEADER_SIZE_2014 in decfile.h needs to be adjusted to %ti,\nor parse_header in decfile.c needs updating\n", p - buffer);
+                               goto out_val;
+                       }
+                       break;
+               case 2015:
+                       if (p - buffer != RNET_HEADER_SIZE_2015) {
+                               fprintf(stderr, "RNET_HEADER_SIZE_2015 in decfile.h needs to be adjusted to %ti,\nor parse_header in decfile.c needs updating\n", p - buffer);
+                               goto out_val;
+                       }
+                       break;
+               case 2016:
+                       if (p - buffer != RNET_HEADER_SIZE_2016) {
+                               fprintf(stderr, "RNET_HEADER_SIZE_2016 in decfile.h needs to be adjusted to %ti,\nor parse_header in decfile.c needs updating\n", p - buffer);
+                               goto out_val;
+                       }
+                       break;
+               default:
+                       /* This case should never be reached even for later
+                          years, because later years should not be parsed
+                          further than the common header. */
+                       fprintf(stderr, "Error while processing header. Unrecognized version\n");
+                       goto out_val;
+                       break;
+       }
+
+       return 0;
+out_add:
+       free(key);
+out_key:
+       free(val);
+out_val:
+       return r;
+}
+
+#undef parse
+
 static int decfile_parse(struct rnet_decfile *decfile)
 {
        char *buffer = NULL;
@@ -77,18 +361,26 @@ static int decfile_parse(struct rnet_decfile *decfile)
                buffer = NULL;
                len = 0;
        }
-       return 0;
+       if (!(r = decfile_parse_header(decfile)) && !(r = decfile_parse_file(decfile)))
+               return 0;
 out:
        decfile_release_lines(decfile);
-       return -1;
+       return r;
 }
 
 struct rnet_decfile * rnet_decfile_open(char *filename)
 {
        struct rnet_decfile *decfile;
+       int r = -ENOMEM;
        decfile = malloc(sizeof(*decfile));
        if (!decfile)
                return NULL;
+       decfile->header = pmhash_new();
+       if (!decfile->header)
+               goto out_header;
+       decfile->message = rnet_message_new();
+       if (!decfile->message)
+               goto out_message;
        decfile->filename = strdup(filename);
        if (!decfile->filename)
                goto out_filename;
@@ -97,7 +389,7 @@ struct rnet_decfile * rnet_decfile_open(char *filename)
                goto out_file;
        decfile->lines_len = 0;
        decfile->lines = NULL;
-       if (decfile_parse(decfile))
+       if ((r = decfile_parse(decfile)))
                goto out_parse;
        return decfile;
 out_parse:
@@ -105,7 +397,12 @@ out_parse:
 out_file:
        free(decfile->filename);
 out_filename:
+       rnet_message_del(decfile->message);
+out_message:
+       pmhash_del(decfile->header);
+out_header:
        free(decfile);
+       errno = -r;
        return NULL;
 }
 
@@ -116,3 +413,94 @@ void rnet_decfile_close(struct rnet_decfile *decfile)
        free(decfile->filename);
        free(decfile);
 }
+
+static char * get_header(struct rnet_decfile *decfile)
+{
+       int i;
+       for (i = 0; i < decfile->lines_len; i++) {
+               if (!strncmp(decfile->lines[i], "IRPF", 4)) {
+                       return decfile->lines[i];
+               }
+       }
+       return NULL;
+}
+
+char *rnet_decfile_get_header_field(struct rnet_decfile *decfile, char *field)
+{
+       return pmhash_get(decfile->header, field);
+}
+
+/* returns true if register is declaration and not a receipt */
+static int decfile_reg_is_dec(char *line)
+{
+       if (line[0] >= '0' && line[0] <= '9' &&
+           line[1] >= '0' && line[1] <= '9')
+               return 1;
+       if (!strncmp(line, "IRPF    ", 8))
+               return 1;
+       if (!strncmp(line, "T9", 2))
+               return 1;
+       return 0;
+}
+
+/* strip a register from its control number and append it to message */
+static int append_stripped_reg_ctrl(struct rnet_message **message, char *line)
+{
+       size_t len;
+       struct rnet_message *msg = *message;
+       int growth;
+       if (!decfile_reg_is_dec(line))
+               return 0;
+       len = strlen(line);
+       if (len < 12)
+               return -EINVAL;
+       growth = msg->len + len - 10 - msg->alen;
+       if (growth > 0) {
+               if (rnet_message_expand(message, growth))
+                       return -ENOMEM;
+               msg = *message;
+       }
+       memcpy(&msg->buffer[msg->len], line, len - 12);
+       msg->buffer[msg->len + len - 12] = '\r';
+       msg->buffer[msg->len + len - 11] = '\n';
+       msg->len += len - 10;
+       return 0;
+}
+
+static int decfile_parse_file(struct rnet_decfile *decfile)
+{
+       int i;
+       int r;
+       for (i = 0; i < decfile->lines_len; i++) {
+               r = append_stripped_reg_ctrl(&decfile->message,
+                                               decfile->lines[i]);
+               if (r)
+                       return r;
+       }
+       return 0;
+}
+
+struct rnet_message * rnet_decfile_get_file(struct rnet_decfile *decfile)
+{
+       return decfile->message;
+}
+
+char * rnet_decfile_get_file_hash(struct rnet_decfile *decfile)
+{
+       char *hash;
+       size_t len;
+       if (gcry_md_test_algo(GCRY_MD_MD5))
+               return NULL;
+       len = gcry_md_get_algo_dlen(GCRY_MD_MD5);
+       hash = malloc(len);
+       if (!hash)
+               return NULL;
+       gcry_md_hash_buffer(GCRY_MD_MD5, hash, decfile->message->buffer,
+                                       decfile->message->len);
+       return hash;
+}
+
+char * rnet_decfile_get_header(struct rnet_decfile *decfile)
+{
+       return get_header(decfile);
+}