GF_Err afra_box_read(GF_Box *s, GF_BitStream *bs)
{
	unsigned int i;
	GF_AdobeFragRandomAccessBox *ptr = (GF_AdobeFragRandomAccessBox *)s;

	ISOM_DECREASE_SIZE(ptr, 9)
	ptr->long_ids = gf_bs_read_int(bs, 1);
	ptr->long_offsets = gf_bs_read_int(bs, 1);
	ptr->global_entries = gf_bs_read_int(bs, 1);
	ptr->reserved = gf_bs_read_int(bs, 5);
	ptr->time_scale = gf_bs_read_u32(bs);

	ptr->entry_count = gf_bs_read_u32(bs);
	if (ptr->size < ptr->entry_count * (ptr->long_offsets ? 16 : 12))
		return GF_ISOM_INVALID_FILE;

	for (i=0; i<ptr->entry_count; i++) {
		GF_AfraEntry *ae = gf_malloc(sizeof(GF_AfraEntry));
		if (!ae) return GF_OUT_OF_MEM;

		ISOM_DECREASE_SIZE(ptr, 8)
		ae->time = gf_bs_read_u64(bs);
		if (ptr->long_offsets) {
			ISOM_DECREASE_SIZE(ptr, 8)
			ae->offset = gf_bs_read_u64(bs);
		} else {
			ISOM_DECREASE_SIZE(ptr, 4)
			ae->offset = gf_bs_read_u32(bs);
		}

		gf_list_insert(ptr->local_access_entries, ae, i);
	}

	if (ptr->global_entries) {
		ISOM_DECREASE_SIZE(ptr, 4)
		ptr->global_entry_count = gf_bs_read_u32(bs);
		for (i=0; i<ptr->global_entry_count; i++) {
			GF_GlobalAfraEntry *ae = gf_malloc(sizeof(GF_GlobalAfraEntry));
			if (!ae) return GF_OUT_OF_MEM;
			ISOM_DECREASE_SIZE(ptr, 8)
			ae->time = gf_bs_read_u64(bs);
			if (ptr->long_ids) {
				ISOM_DECREASE_SIZE(ptr, 8)
				ae->segment = gf_bs_read_u32(bs);
				ae->fragment = gf_bs_read_u32(bs);
			} else {
				ISOM_DECREASE_SIZE(ptr, 4)
				ae->segment = gf_bs_read_u16(bs);
				ae->fragment = gf_bs_read_u16(bs);
			}
			if (ptr->long_offsets) {
				ISOM_DECREASE_SIZE(ptr, 16)
				ae->afra_offset = gf_bs_read_u64(bs);
				ae->offset_from_afra = gf_bs_read_u64(bs);
			} else {
				ISOM_DECREASE_SIZE(ptr, 8)
				ae->afra_offset = gf_bs_read_u32(bs);
				ae->offset_from_afra = gf_bs_read_u32(bs);
			}

			gf_list_insert(ptr->global_access_entries, ae, i);
		}
	}

	return GF_OK;
}


GF_Err abst_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	unsigned int i;
	GF_AdobeBootstrapInfoBox *ptr = (GF_AdobeBootstrapInfoBox *)s;

	e = gf_isom_full_box_write(s, bs);
	if (e) return e;

	gf_bs_write_u32(bs, ptr->bootstrapinfo_version);
	gf_bs_write_int(bs, ptr->profile, 2);
	gf_bs_write_int(bs, ptr->live, 1);
	gf_bs_write_int(bs, ptr->update, 1);
	gf_bs_write_int(bs, ptr->reserved, 4);
	gf_bs_write_u32(bs, ptr->time_scale);
	gf_bs_write_u64(bs, ptr->current_media_time);
	gf_bs_write_u64(bs, ptr->smpte_time_code_offset);
	if (ptr->movie_identifier)
		gf_bs_write_data(bs, ptr->movie_identifier, (u32)strlen(ptr->movie_identifier) + 1);
	else
		gf_bs_write_u8(bs, 0);

	gf_bs_write_u8(bs, ptr->server_entry_count);
	for (i=0; i<ptr->server_entry_count; i++) {
		char *str = (char*)gf_list_get(ptr->server_entry_table, i);
		gf_bs_write_data(bs, str, (u32)strlen(str) + 1);
	}

	gf_bs_write_u8(bs, ptr->quality_entry_count);
	for (i=0; i<ptr->quality_entry_count; i++) {
		char *str = (char*)gf_list_get(ptr->quality_entry_table, i);
		gf_bs_write_data(bs, str, (u32)strlen(str) + 1);
	}

	if (ptr->drm_data)
		gf_bs_write_data(bs, ptr->drm_data, (u32)strlen(ptr->drm_data) + 1);
	else
		gf_bs_write_u8(bs, 0);

	if (ptr->meta_data)
		gf_bs_write_data(bs, ptr->meta_data, (u32)strlen(ptr->meta_data) + 1);
	else
		gf_bs_write_u8(bs, 0);

	gf_bs_write_u8(bs, ptr->segment_run_table_count);
	for (i=0; i<ptr->segment_run_table_count; i++) {
		e = gf_isom_box_write((GF_Box *)gf_list_get(ptr->segment_run_table_entries, i), bs);
		if (e) return e;
	}

	gf_bs_write_u8(bs, ptr->fragment_run_table_count);
	for (i=0; i<ptr->fragment_run_table_count; i++) {
		e = gf_isom_box_write((GF_Box *)gf_list_get(ptr->fragment_run_table_entries, i), bs);
		if (e) return e;
	}

	return GF_OK;
}

The abst_box_size function in GPAC 1.0.1 allows attackers to cause a denial of service (NULL pointer dereference) via a crafted file in the MP4Box command.


GF_Box *asrt_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_AdobeSegmentRunTableBox, GF_ISOM_BOX_TYPE_ASRT);
	tmp->quality_segment_url_modifiers = gf_list_new();
	tmp->segment_run_entry_table = gf_list_new();
	return (GF_Box *)tmp;
}


GF_Box *afrt_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_AdobeFragmentRunTableBox, GF_ISOM_BOX_TYPE_AFRT);
	tmp->quality_segment_url_modifiers = gf_list_new();
	tmp->fragment_run_entry_table = gf_list_new();
	return (GF_Box *)tmp;
}


GF_Err afra_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	unsigned int i;
	GF_AdobeFragRandomAccessBox *ptr = (GF_AdobeFragRandomAccessBox *)s;

	e = gf_isom_full_box_write(s, bs);
	if (e) return e;

	gf_bs_write_int(bs, ptr->long_ids, 1);
	gf_bs_write_int(bs, ptr->long_offsets, 1);
	gf_bs_write_int(bs, ptr->global_entries, 1);
	gf_bs_write_int(bs, 0, 5);
	gf_bs_write_u32(bs, ptr->time_scale);

	gf_bs_write_u32(bs, ptr->entry_count);
	for (i=0; i<ptr->entry_count; i++) {
		GF_AfraEntry *ae = (GF_AfraEntry *)gf_list_get(ptr->local_access_entries, i);
		gf_bs_write_u64(bs, ae->time);
		if (ptr->long_offsets)
			gf_bs_write_u64(bs, ae->offset);
		else
			gf_bs_write_u32(bs, (u32)ae->offset);
	}

	if (ptr->global_entries) {
		gf_bs_write_u32(bs, ptr->global_entry_count);
		for (i=0; i<ptr->global_entry_count; i++) {
			GF_GlobalAfraEntry *gae = (GF_GlobalAfraEntry *)gf_list_get(ptr->global_access_entries, i);
			gf_bs_write_u64(bs, gae->time);
			if (ptr->long_ids) {
				gf_bs_write_u32(bs, gae->segment);
				gf_bs_write_u32(bs, gae->fragment);
			} else {
				gf_bs_write_u16(bs, (u16)gae->segment);
				gf_bs_write_u16(bs, (u16)gae->fragment);
			}
			if (ptr->long_offsets) {
				gf_bs_write_u64(bs, gae->afra_offset);
				gf_bs_write_u64(bs, gae->offset_from_afra);
			} else {
				gf_bs_write_u32(bs, (u32)gae->afra_offset);
				gf_bs_write_u32(bs, (u32)gae->offset_from_afra);
			}
		}
	}

	return GF_OK;
}


GF_Err afra_box_size(GF_Box *s)
{
	GF_AdobeFragRandomAccessBox *ptr = (GF_AdobeFragRandomAccessBox *)s;

	s->size += 9
	           + ptr->entry_count * (ptr->long_offsets ? 16 : 12)
	           + (ptr->global_entries ? 4 + ptr->global_entry_count * (4 + (ptr->long_offsets ? 16 : 8) + (ptr->long_ids ? 8 : 4)) : 0);

	return GF_OK;
}


GF_Err asrt_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	unsigned int i;
	GF_AdobeSegmentRunTableBox *ptr = (GF_AdobeSegmentRunTableBox *)s;

	e = gf_isom_full_box_write(s, bs);
	if (e) return e;

	gf_bs_write_u8(bs, ptr->quality_entry_count);
	for (i=0; i<ptr->quality_entry_count; i++) {
		char *str = (char*)gf_list_get(ptr->quality_segment_url_modifiers, i);
		gf_bs_write_data(bs, str, (u32)strlen(str) + 1);
	}

	gf_bs_write_u32(bs, ptr->segment_run_entry_count);
	for (i=0; i<ptr->segment_run_entry_count; i++) {
		GF_AdobeSegmentRunEntry *sre = (GF_AdobeSegmentRunEntry *)gf_list_get(ptr->segment_run_entry_table, i);
		gf_bs_write_u32(bs, sre->first_segment);
		gf_bs_write_u32(bs, sre->fragment_per_segment);
	}

	return GF_OK;
}


void abst_box_del(GF_Box *s)
{
	GF_AdobeBootstrapInfoBox *ptr = (GF_AdobeBootstrapInfoBox *)s;
	if (ptr == NULL) return;

	if (ptr->movie_identifier)
		gf_free(ptr->movie_identifier);
	if (ptr->drm_data)
		gf_free(ptr->drm_data);
	if (ptr->meta_data)
		gf_free(ptr->meta_data);

	while (gf_list_count(ptr->server_entry_table)) {
		gf_free(gf_list_get(ptr->server_entry_table, 0));
		gf_list_rem(ptr->server_entry_table, 0);
	}
	gf_list_del(ptr->server_entry_table);

	while (gf_list_count(ptr->quality_entry_table)) {
		gf_free(gf_list_get(ptr->quality_entry_table, 0));
		gf_list_rem(ptr->quality_entry_table, 0);
	}
	gf_list_del(ptr->quality_entry_table);

	while (gf_list_count(ptr->segment_run_table_entries)) {
		gf_isom_box_del((GF_Box *)gf_list_get(ptr->segment_run_table_entries, 0));
		gf_list_rem(ptr->segment_run_table_entries, 0);
	}
	gf_list_del(ptr->segment_run_table_entries);

	while (gf_list_count(ptr->fragment_run_table_entries)) {
		gf_isom_box_del((GF_Box *)gf_list_get(ptr->fragment_run_table_entries, 0));
		gf_list_rem(ptr->fragment_run_table_entries, 0);
	}
	gf_list_del(ptr->fragment_run_table_entries);

	gf_free(ptr);
}


GF_Box *abst_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_AdobeBootstrapInfoBox, GF_ISOM_BOX_TYPE_ABST);
	tmp->server_entry_table = gf_list_new();
	tmp->quality_entry_table = gf_list_new();
	tmp->segment_run_table_entries = gf_list_new();
	tmp->fragment_run_table_entries = gf_list_new();
	return (GF_Box *)tmp;
}


void afrt_box_del(GF_Box *s)
{
	GF_AdobeFragmentRunTableBox *ptr = (GF_AdobeFragmentRunTableBox *)s;
	if (ptr == NULL) return;

	while (gf_list_count(ptr->quality_segment_url_modifiers)) {
		gf_free(gf_list_get(ptr->quality_segment_url_modifiers, 0));
		gf_list_rem(ptr->quality_segment_url_modifiers, 0);
	}
	gf_list_del(ptr->quality_segment_url_modifiers);

	while (gf_list_count(ptr->fragment_run_entry_table)) {
		gf_free(gf_list_get(ptr->fragment_run_entry_table, 0));
		gf_list_rem(ptr->fragment_run_entry_table, 0);
	}
	gf_list_del(ptr->fragment_run_entry_table);

	gf_free(ptr);
}


Prevent fts5 tokenizer unicode61 from considering '\0' to be a token characters, even if other characters of class "Cc" are.

FossilOrigin-Name: b7b7bde9b7a03665e3691c6d51118965f216d2dfb1617f138b9f9e60e418ed2f

int sqlite3Fts5UnicodeCategory(u32 iCode) {
  int iRes = -1;
  int iHi;
  int iLo;
  int ret;
  u16 iKey;

  if( iCode>=(1<<20) ){
    return 0;
  }
  iLo = aFts5UnicodeBlock[(iCode>>16)];
  iHi = aFts5UnicodeBlock[1+(iCode>>16)];
  iKey = (iCode & 0xFFFF);
  while( iHi>iLo ){
    int iTest = (iHi + iLo) / 2;
    assert( iTest>=iLo && iTest<iHi );
    if( iKey>=aFts5UnicodeMap[iTest] ){
      iRes = iTest;
      iLo = iTest+1;
    }else{
      iHi = iTest;
    }
  }

  if( iRes<0 ) return 0;
  if( iKey>=(aFts5UnicodeMap[iRes]+(aFts5UnicodeData[iRes]>>5)) ) return 0;
  ret = aFts5UnicodeData[iRes] & 0x1F;
  if( ret!=30 ) return ret;
  return ((iKey - aFts5UnicodeMap[iRes]) & 0x01) ? 5 : 9;
}

Rejected reason: DO NOT USE THIS CANDIDATE NUMBER. ConsultIDs: none. Reason: This candidate was withdrawn by its CNA. Further investigation showed that it was not a security issue. Notes: none.


int sqlite3Fts5UnicodeFold(int c, int eRemoveDiacritic){
  /* Each entry in the following array defines a rule for folding a range
  ** of codepoints to lower case. The rule applies to a range of nRange
  ** codepoints starting at codepoint iCode.
  **
  ** If the least significant bit in flags is clear, then the rule applies
  ** to all nRange codepoints (i.e. all nRange codepoints are upper case and
  ** need to be folded). Or, if it is set, then the rule only applies to
  ** every second codepoint in the range, starting with codepoint C.
  **
  ** The 7 most significant bits in flags are an index into the aiOff[]
  ** array. If a specific codepoint C does require folding, then its lower
  ** case equivalent is ((C + aiOff[flags>>1]) & 0xFFFF).
  **
  ** The contents of this array are generated by parsing the CaseFolding.txt
  ** file distributed as part of the "Unicode Character Database". See
  ** http://www.unicode.org for details.
  */
  static const struct TableEntry {
    unsigned short iCode;
    unsigned char flags;
    unsigned char nRange;
  } aEntry[] = {
    {65, 14, 26},          {181, 64, 1},          {192, 14, 23},
    {216, 14, 7},          {256, 1, 48},          {306, 1, 6},
    {313, 1, 16},          {330, 1, 46},          {376, 116, 1},
    {377, 1, 6},           {383, 104, 1},         {385, 50, 1},
    {386, 1, 4},           {390, 44, 1},          {391, 0, 1},
    {393, 42, 2},          {395, 0, 1},           {398, 32, 1},
    {399, 38, 1},          {400, 40, 1},          {401, 0, 1},
    {403, 42, 1},          {404, 46, 1},          {406, 52, 1},
    {407, 48, 1},          {408, 0, 1},           {412, 52, 1},
    {413, 54, 1},          {415, 56, 1},          {416, 1, 6},
    {422, 60, 1},          {423, 0, 1},           {425, 60, 1},
    {428, 0, 1},           {430, 60, 1},          {431, 0, 1},
    {433, 58, 2},          {435, 1, 4},           {439, 62, 1},
    {440, 0, 1},           {444, 0, 1},           {452, 2, 1},
    {453, 0, 1},           {455, 2, 1},           {456, 0, 1},
    {458, 2, 1},           {459, 1, 18},          {478, 1, 18},
    {497, 2, 1},           {498, 1, 4},           {502, 122, 1},
    {503, 134, 1},         {504, 1, 40},          {544, 110, 1},
    {546, 1, 18},          {570, 70, 1},          {571, 0, 1},
    {573, 108, 1},         {574, 68, 1},          {577, 0, 1},
    {579, 106, 1},         {580, 28, 1},          {581, 30, 1},
    {582, 1, 10},          {837, 36, 1},          {880, 1, 4},
    {886, 0, 1},           {902, 18, 1},          {904, 16, 3},
    {908, 26, 1},          {910, 24, 2},          {913, 14, 17},
    {931, 14, 9},          {962, 0, 1},           {975, 4, 1},
    {976, 140, 1},         {977, 142, 1},         {981, 146, 1},
    {982, 144, 1},         {984, 1, 24},          {1008, 136, 1},
    {1009, 138, 1},        {1012, 130, 1},        {1013, 128, 1},
    {1015, 0, 1},          {1017, 152, 1},        {1018, 0, 1},
    {1021, 110, 3},        {1024, 34, 16},        {1040, 14, 32},
    {1120, 1, 34},         {1162, 1, 54},         {1216, 6, 1},
    {1217, 1, 14},         {1232, 1, 88},         {1329, 22, 38},
    {4256, 66, 38},        {4295, 66, 1},         {4301, 66, 1},
    {7680, 1, 150},        {7835, 132, 1},        {7838, 96, 1},
    {7840, 1, 96},         {7944, 150, 8},        {7960, 150, 6},
    {7976, 150, 8},        {7992, 150, 8},        {8008, 150, 6},
    {8025, 151, 8},        {8040, 150, 8},        {8072, 150, 8},
    {8088, 150, 8},        {8104, 150, 8},        {8120, 150, 2},
    {8122, 126, 2},        {8124, 148, 1},        {8126, 100, 1},
    {8136, 124, 4},        {8140, 148, 1},        {8152, 150, 2},
    {8154, 120, 2},        {8168, 150, 2},        {8170, 118, 2},
    {8172, 152, 1},        {8184, 112, 2},        {8186, 114, 2},
    {8188, 148, 1},        {8486, 98, 1},         {8490, 92, 1},
    {8491, 94, 1},         {8498, 12, 1},         {8544, 8, 16},
    {8579, 0, 1},          {9398, 10, 26},        {11264, 22, 47},
    {11360, 0, 1},         {11362, 88, 1},        {11363, 102, 1},
    {11364, 90, 1},        {11367, 1, 6},         {11373, 84, 1},
    {11374, 86, 1},        {11375, 80, 1},        {11376, 82, 1},
    {11378, 0, 1},         {11381, 0, 1},         {11390, 78, 2},
    {11392, 1, 100},       {11499, 1, 4},         {11506, 0, 1},
    {42560, 1, 46},        {42624, 1, 24},        {42786, 1, 14},
    {42802, 1, 62},        {42873, 1, 4},         {42877, 76, 1},
    {42878, 1, 10},        {42891, 0, 1},         {42893, 74, 1},
    {42896, 1, 4},         {42912, 1, 10},        {42922, 72, 1},
    {65313, 14, 26},
  };
  static const unsigned short aiOff[] = {
   1,     2,     8,     15,    16,    26,    28,    32,
   37,    38,    40,    48,    63,    64,    69,    71,
   79,    80,    116,   202,   203,   205,   206,   207,
   209,   210,   211,   213,   214,   217,   218,   219,
   775,   7264,  10792, 10795, 23228, 23256, 30204, 54721,
   54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274,
   57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406,
   65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462,
   65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511,
   65514, 65521, 65527, 65528, 65529,
  };

  int ret = c;

  assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 );

  if( c<128 ){
    if( c>='A' && c<='Z' ) ret = c + ('a' - 'A');
  }else if( c<65536 ){
    const struct TableEntry *p;
    int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;
    int iLo = 0;
    int iRes = -1;

    assert( c>aEntry[0].iCode );
    while( iHi>=iLo ){
      int iTest = (iHi + iLo) / 2;
      int cmp = (c - aEntry[iTest].iCode);
      if( cmp>=0 ){
        iRes = iTest;
        iLo = iTest+1;
      }else{
        iHi = iTest-1;
      }
    }

    assert( iRes>=0 && c>=aEntry[iRes].iCode );
    p = &aEntry[iRes];
    if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){
      ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF;
      assert( ret>0 );
    }

    if( eRemoveDiacritic ){
      ret = fts5_remove_diacritic(ret, eRemoveDiacritic==2);
    }
  }

  else if( c>=66560 && c<66600 ){
    ret = c + 40;
  }

  return ret;
}


void sqlite3Fts5UnicodeAscii(u8 *aArray, u8 *aAscii){
  int i = 0;
  int iTbl = 0;
  while( i<128 ){
    int bToken = aArray[ aFts5UnicodeData[iTbl] & 0x1F ];
    int n = (aFts5UnicodeData[iTbl] >> 5) + i;
    for(; i<128 && i<n; i++){
      aAscii[i] = (u8)bToken;
    }
    iTbl++;
  }
  aAscii[0] = 0;                  /* 0x00 is never a token character */
}


int sqlite3Fts5UnicodeIsdiacritic(int c){
  unsigned int mask0 = 0x08029FDF;
  unsigned int mask1 = 0x000361F8;
  if( c<768 || c>817 ) return 0;
  return (c < 768+32) ?
      (mask0 & ((unsigned int)1 << (c-768))) :
      (mask1 & ((unsigned int)1 << (c-768-32)));
}


int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){
  aArray[0] = 1;
  switch( zCat[0] ){
    case 'C':
          switch( zCat[1] ){
            case 'c': aArray[1] = 1; break;
            case 'f': aArray[2] = 1; break;
            case 'n': aArray[3] = 1; break;
            case 's': aArray[4] = 1; break;
            case 'o': aArray[31] = 1; break;
            case '*':
              aArray[1] = 1;
              aArray[2] = 1;
              aArray[3] = 1;
              aArray[4] = 1;
              aArray[31] = 1;
              break;
            default: return 1;          }
          break;

    case 'L':
          switch( zCat[1] ){
            case 'l': aArray[5] = 1; break;
            case 'm': aArray[6] = 1; break;
            case 'o': aArray[7] = 1; break;
            case 't': aArray[8] = 1; break;
            case 'u': aArray[9] = 1; break;
            case 'C': aArray[30] = 1; break;
            case '*':
              aArray[5] = 1;
              aArray[6] = 1;
              aArray[7] = 1;
              aArray[8] = 1;
              aArray[9] = 1;
              aArray[30] = 1;
              break;
            default: return 1;          }
          break;

    case 'M':
          switch( zCat[1] ){
            case 'c': aArray[10] = 1; break;
            case 'e': aArray[11] = 1; break;
            case 'n': aArray[12] = 1; break;
            case '*':
              aArray[10] = 1;
              aArray[11] = 1;
              aArray[12] = 1;
              break;
            default: return 1;          }
          break;

    case 'N':
          switch( zCat[1] ){
            case 'd': aArray[13] = 1; break;
            case 'l': aArray[14] = 1; break;
            case 'o': aArray[15] = 1; break;
            case '*':
              aArray[13] = 1;
              aArray[14] = 1;
              aArray[15] = 1;
              break;
            default: return 1;          }
          break;

    case 'P':
          switch( zCat[1] ){
            case 'c': aArray[16] = 1; break;
            case 'd': aArray[17] = 1; break;
            case 'e': aArray[18] = 1; break;
            case 'f': aArray[19] = 1; break;
            case 'i': aArray[20] = 1; break;
            case 'o': aArray[21] = 1; break;
            case 's': aArray[22] = 1; break;
            case '*':
              aArray[16] = 1;
              aArray[17] = 1;
              aArray[18] = 1;
              aArray[19] = 1;
              aArray[20] = 1;
              aArray[21] = 1;
              aArray[22] = 1;
              break;
            default: return 1;          }
          break;

    case 'S':
          switch( zCat[1] ){
            case 'c': aArray[23] = 1; break;
            case 'k': aArray[24] = 1; break;
            case 'm': aArray[25] = 1; break;
            case 'o': aArray[26] = 1; break;
            case '*':
              aArray[23] = 1;
              aArray[24] = 1;
              aArray[25] = 1;
              aArray[26] = 1;
              break;
            default: return 1;          }
          break;

    case 'Z':
          switch( zCat[1] ){
            case 'l': aArray[27] = 1; break;
            case 'p': aArray[28] = 1; break;
            case 's': aArray[29] = 1; break;
            case '*':
              aArray[27] = 1;
              aArray[28] = 1;
              aArray[29] = 1;
              break;
            default: return 1;          }
          break;

  }
  return 0;
}


CVE-2022-32744 s4:kdc: Don't allow HDB keytab iteration

A fallback in krb5_rd_req_ctx() means that Samba's kpasswd service will
try many inappropriate keys to decrypt the ticket supplied to it. For
example, it will accept a ticket encrypted with the Administrator's key,
when it should rather accept only tickets encrypted with the krbtgt's
key (and not an RODC krbtgt). To fix this, declare the HDB keytab using
the HDBGET ops, which do not support iteration.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15074

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andreas Schneider <asn@samba.org>

static NTSTATUS kdc_check_generic_kerberos(struct irpc_message *msg,
				 struct kdc_check_generic_kerberos *r)
{
	struct PAC_Validate pac_validate;
	DATA_BLOB srv_sig;
	struct PAC_SIGNATURE_DATA kdc_sig;
	struct kdc_server *kdc = talloc_get_type(msg->private_data, struct kdc_server);
	krb5_kdc_configuration *kdc_config =
		(krb5_kdc_configuration *)kdc->private_data;
	enum ndr_err_code ndr_err;
	int ret;
	hdb_entry ent;
	krb5_principal principal;

	/* There is no reply to this request */
	r->out.generic_reply = data_blob(NULL, 0);

	ndr_err = ndr_pull_struct_blob(&r->in.generic_request, msg, &pac_validate,
				       (ndr_pull_flags_fn_t)ndr_pull_PAC_Validate);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (pac_validate.MessageType != NETLOGON_GENERIC_KRB5_PAC_VALIDATE) {
		/* We don't implement any other message types - such as certificate validation - yet */
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (pac_validate.ChecksumAndSignature.length != (pac_validate.ChecksumLength + pac_validate.SignatureLength)
	    || pac_validate.ChecksumAndSignature.length < pac_validate.ChecksumLength
	    || pac_validate.ChecksumAndSignature.length < pac_validate.SignatureLength ) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	srv_sig = data_blob_const(pac_validate.ChecksumAndSignature.data,
				  pac_validate.ChecksumLength);

	ret = krb5_make_principal(kdc->smb_krb5_context->krb5_context, &principal,
				  lpcfg_realm(kdc->task->lp_ctx),
				  "krbtgt", lpcfg_realm(kdc->task->lp_ctx),
				  NULL);

	if (ret != 0) {
		return NT_STATUS_NO_MEMORY;
	}

	ret = kdc_config->db[0]->hdb_fetch_kvno(kdc->smb_krb5_context->krb5_context,
						 kdc_config->db[0],
						 principal,
						 HDB_F_GET_KRBTGT | HDB_F_DECRYPT,
						 0,
						 &ent);

	if (ret != 0) {
		hdb_free_entry(kdc->smb_krb5_context->krb5_context, kdc_config->db[0], &ent);
		krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);

		return NT_STATUS_LOGON_FAILURE;
	}

	kdc_sig.type = pac_validate.SignatureType;
	kdc_sig.signature = data_blob_const(&pac_validate.ChecksumAndSignature.data[pac_validate.ChecksumLength],
					    pac_validate.SignatureLength);

	ret = kdc_check_pac(kdc->smb_krb5_context->krb5_context, srv_sig, &kdc_sig, &ent);

	hdb_free_entry(kdc->smb_krb5_context->krb5_context, kdc_config->db[0], &ent);
	krb5_free_principal(kdc->smb_krb5_context->krb5_context, principal);

	if (ret != 0) {
		return NT_STATUS_LOGON_FAILURE;
	}

	return NT_STATUS_OK;
}


static NTSTATUS kdc_startup_interfaces(struct kdc_server *kdc,
				       struct loadparm_context *lp_ctx,
				       struct interface *ifaces,
				       const struct model_ops *model_ops)
{
	int num_interfaces;
	TALLOC_CTX *tmp_ctx = talloc_new(kdc);
	NTSTATUS status;
	int i;
	uint16_t kdc_port = lpcfg_krb5_port(lp_ctx);
	uint16_t kpasswd_port = lpcfg_kpasswd_port(lp_ctx);
	bool done_wildcard = false;

	num_interfaces = iface_list_count(ifaces);

	/* if we are allowing incoming packets from any address, then
	   we need to bind to the wildcard address */
	if (!lpcfg_bind_interfaces_only(lp_ctx)) {
		size_t num_binds = 0;
		char **wcard = iface_list_wildcard(kdc);
		NT_STATUS_HAVE_NO_MEMORY(wcard);
		for (i=0; wcard[i]; i++) {
			if (kdc_port) {
				status = kdc_add_socket(kdc, model_ops,
							"kdc", wcard[i], kdc_port,
							kdc_process, false);
				if (NT_STATUS_IS_OK(status)) {
					num_binds++;
				}
			}

			if (kpasswd_port) {
				status = kdc_add_socket(kdc, model_ops,
							"kpasswd", wcard[i], kpasswd_port,
							kpasswd_process, false);
				if (NT_STATUS_IS_OK(status)) {
					num_binds++;
				}
			}
		}
		talloc_free(wcard);
		if (num_binds == 0) {
			return NT_STATUS_INVALID_PARAMETER_MIX;
		}
		done_wildcard = true;
	}

	for (i=0; i<num_interfaces; i++) {
		const char *address = talloc_strdup(tmp_ctx, iface_list_n_ip(ifaces, i));

		if (kdc_port) {
			status = kdc_add_socket(kdc, model_ops,
						"kdc", address, kdc_port,
						kdc_process, done_wildcard);
			NT_STATUS_NOT_OK_RETURN(status);
		}

		if (kpasswd_port) {
			status = kdc_add_socket(kdc, model_ops,
						"kpasswd", address, kpasswd_port,
						kpasswd_process, done_wildcard);
			NT_STATUS_NOT_OK_RETURN(status);
		}
	}

	talloc_free(tmp_ctx);

	return NT_STATUS_OK;
}


NTSTATUS server_service_kdc_init(TALLOC_CTX *ctx)
{
	static const struct service_details details = {
		.inhibit_fork_on_accept = true,
		.inhibit_pre_fork = false,
		.task_init = kdc_task_init,
		.post_fork = kdc_post_fork
	};
	return register_server_service(ctx, "kdc", &details);
}


static kdc_code kdc_process(struct kdc_server *kdc,
			    TALLOC_CTX *mem_ctx,
			    DATA_BLOB *input,
			    DATA_BLOB *reply,
			    struct tsocket_address *peer_addr,
			    struct tsocket_address *my_addr,
			    int datagram_reply)
{
	int ret;
	char *pa;
	struct sockaddr_storage ss;
	krb5_data k5_reply;
	krb5_kdc_configuration *kdc_config =
		(krb5_kdc_configuration *)kdc->private_data;

	krb5_data_zero(&k5_reply);

	krb5_kdc_update_time(NULL);

	ret = tsocket_address_bsd_sockaddr(peer_addr, (struct sockaddr *) &ss,
				sizeof(struct sockaddr_storage));
	if (ret < 0) {
		return KDC_ERROR;
	}
	pa = tsocket_address_string(peer_addr, mem_ctx);
	if (pa == NULL) {
		return KDC_ERROR;
	}

	DBG_DEBUG("Received KDC packet of length %lu from %s\n",
		  (long)input->length - 4, pa);

	ret = krb5_kdc_process_krb5_request(kdc->smb_krb5_context->krb5_context,
					    kdc_config,
					    input->data, input->length,
					    &k5_reply,
					    pa,
					    (struct sockaddr *) &ss,
					    datagram_reply);
	if (ret == -1) {
		*reply = data_blob(NULL, 0);
		return KDC_ERROR;
	}

	if (ret == HDB_ERR_NOT_FOUND_HERE) {
		*reply = data_blob(NULL, 0);
		return KDC_PROXY_REQUEST;
	}

	if (k5_reply.length) {
		*reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
		krb5_data_free(&k5_reply);
	} else {
		*reply = data_blob(NULL, 0);
	}
	return KDC_OK;
}


static NTSTATUS kdc_task_init(struct task_server *task)
{
	struct kdc_server *kdc;
	NTSTATUS status;
	struct interface *ifaces;

	switch (lpcfg_server_role(task->lp_ctx)) {
	case ROLE_STANDALONE:
		task_server_terminate(task, "kdc: no KDC required in standalone configuration", false);
		return NT_STATUS_INVALID_DOMAIN_ROLE;
	case ROLE_DOMAIN_MEMBER:
		task_server_terminate(task, "kdc: no KDC required in member server configuration", false);
		return NT_STATUS_INVALID_DOMAIN_ROLE;
	case ROLE_DOMAIN_PDC:
	case ROLE_DOMAIN_BDC:
	case ROLE_IPA_DC:
		task_server_terminate(
		    task, "Cannot start KDC as a 'classic Samba' DC", false);
		return NT_STATUS_INVALID_DOMAIN_ROLE;
	case ROLE_ACTIVE_DIRECTORY_DC:
		/* Yes, we want a KDC */
		break;
	}

	load_interface_list(task, task->lp_ctx, &ifaces);

	if (iface_list_count(ifaces) == 0) {
		task_server_terminate(task, "kdc: no network interfaces configured", false);
		return NT_STATUS_UNSUCCESSFUL;
	}

	task_server_set_title(task, "task[kdc]");

	kdc = talloc_zero(task, struct kdc_server);
	if (kdc == NULL) {
		task_server_terminate(task, "kdc: out of memory", true);
		return NT_STATUS_NO_MEMORY;
	}

	kdc->task = task;
	task->private_data = kdc;

	/* start listening on the configured network interfaces */
	status = kdc_startup_interfaces(kdc, task->lp_ctx, ifaces,
					task->model_ops);
	if (!NT_STATUS_IS_OK(status)) {
		task_server_terminate(task, "kdc failed to setup interfaces", true);
		return status;
	}

	return NT_STATUS_OK;
}


Check untrusted queue is in outside enclave

PiperOrigin-RevId: 333370935
Change-Id: Ic3f15d5db1302d95c7cb199b44172474fecb81ca

void enc_untrusted_enable_waiting(int32_t *const queue) {
  enc_untrusted_wait_queue_set_value(queue, kWaitQueueEnabled);
}

An arbitrary memory overwrite vulnerability in Asylo versions up to 0.6.0 allows an attacker to make a host call to enc_untrusted_create_wait_queue that uses a pointer queue that relies on UntrustedLocalMemcpy, which fails to validate where the pointer is located. This allows an attacker to write memory values from within the enclave. We recommend upgrading past commit a37fb6a0e7daf30134dbbf357c9a518a1026aa02


int32_t *enc_untrusted_create_wait_queue() {
  MessageWriter input;
  MessageReader output;
  input.Push<uint64_t>(sizeof(int32_t));
  const auto status = NonSystemCallDispatcher(
      ::asylo::host_call::kLocalLifetimeAllocHandler, &input, &output);
  CheckStatusAndParamCount(status, output, "enc_untrusted_create_wait_queue",
                           2);
  int32_t *queue = reinterpret_cast<int32_t *>(output.next<uintptr_t>());
  if (!TrustedPrimitives::IsOutsideEnclave(queue, sizeof(int32_t))) {
    TrustedPrimitives::BestEffortAbort(
        "enc_untrusted_create_wait_queue: queue should be in untrusted memory");
  }
  int klinux_errno = output.next<int>();
  if (queue == nullptr) {
    errno = FromkLinuxErrorNumber(klinux_errno);
  }
  enc_untrusted_disable_waiting(queue);
  return queue;
}


void enc_untrusted_disable_waiting(int32_t *const queue) {
  enc_untrusted_wait_queue_set_value(queue, kWaitQueueDisabled);
}


int enc_untrusted_sys_futex_wake(int32_t *futex, int32_t num) {
  if (!TrustedPrimitives::IsOutsideEnclave(futex, sizeof(int32_t))) {
    TrustedPrimitives::BestEffortAbort(
        "enc_untrusted_sys_futex_wake: futex word should be in untrusted "
        "local memory.");
  }

  MessageWriter input;
  MessageReader output;
  input.Push<uint64_t>(reinterpret_cast<uint64_t>(futex));
  input.Push<int32_t>(num);
  const auto status = NonSystemCallDispatcher(
      ::asylo::host_call::kSysFutexWakeHandler, &input, &output);
  CheckStatusAndParamCount(status, output, "enc_untrusted_sys_futex_wake", 2);
  int result = output.next<int>();
  int klinux_errno = output.next<int>();
  if (result == -1) {
    errno = FromkLinuxErrorNumber(klinux_errno);
  }
  return result;
}


void enc_untrusted_thread_wait_value(int32_t *const queue, int32_t value,
                                     uint64_t timeout_microsec) {
  enc_untrusted_sys_futex_wait(queue, value, timeout_microsec);
}


void enc_untrusted_notify(int32_t *const queue, int32_t num_threads) {
  enc_untrusted_sys_futex_wake(queue, num_threads);
}


void enc_untrusted_thread_wait(int32_t *const queue,
                               uint64_t timeout_microsec) {
  enc_untrusted_thread_wait_value(queue, kWaitQueueEnabled, timeout_microsec);
}


void enc_untrusted_destroy_wait_queue(int32_t *const queue) {
  // This is a no op on purpose. Wait queue pointers are now
  // registered to be freed on enclave exit.
}


int enc_untrusted_sys_futex_wait(int32_t *futex, int32_t expected,
                                 int64_t timeout_microsec) {
  if (!TrustedPrimitives::IsOutsideEnclave(futex, sizeof(int32_t))) {
    TrustedPrimitives::BestEffortAbort(
        "enc_untrusted_sys_futex_wait: futex word should be in untrusted "
        "local memory.");
  }

  MessageWriter input;
  MessageReader output;
  input.Push<uint64_t>(reinterpret_cast<uint64_t>(futex));
  input.Push<int32_t>(expected);
  input.Push<int64_t>(timeout_microsec);
  const auto status = NonSystemCallDispatcher(
      ::asylo::host_call::kSysFutexWaitHandler, &input, &output);
  CheckStatusAndParamCount(status, output, "enc_untrusted_sys_futex_wait", 2);
  int result = output.next<int>();
  int klinux_errno = output.next<int>();

  // If FUTEX_WAIT successfully causes the thread to be suspended in the kernel,
  // it returns a zero when the caller is woken up. Otherwise, it returns the
  // appropriate errno.
  if (result != 0) {
    errno = FromkLinuxErrorNumber(klinux_errno);
  }
  return result;
}


void enc_untrusted_wait_queue_set_value(int32_t *const queue, int32_t value) {
  TrustedPrimitives::UntrustedLocalMemcpy(queue, &value, sizeof(int32_t));
}


Fix echo-message for *status

Close #1705

void CClient::Disconnected() {
    DEBUG(GetSockName() << " == Disconnected()");
    CIRCNetwork* pNetwork = m_pNetwork;
    SetNetwork(nullptr, false, false);

    if (m_pUser) {
        NETWORKMODULECALL(OnClientDisconnect(), m_pUser, pNetwork, this,
                          NOTHING);
    }
}

ZNC 1.8.0 up to 1.8.1-rc1 allows authenticated users to trigger an application crash (with a NULL pointer dereference) if echo-message is not enabled and there is no network.


void CClient::StatusCTCP(const CString& sLine) {
    CString sCommand = sLine.Token(0);

    if (sCommand.Equals("PING")) {
        PutStatusNotice("\001PING " + sLine.Token(1, true) + "\001");
    } else if (sCommand.Equals("VERSION")) {
        PutStatusNotice("\001VERSION " + CZNC::GetTag() + "\001");
    }
}


TEST_F(ZNCTest, Encoding) {
    auto znc = Run();
    auto ircd = ConnectIRCd();
    auto client = LoginClient();
    ircd.Write(":server 001 nick :hello");
    // legacy
    ircd.Write(":n!u@h PRIVMSG nick :Hello\xE6world");
    client.ReadUntil("Hello\xE6world");
    client.Write("PRIVMSG *controlpanel :SetNetwork Encoding $me $net UTF-8");
    client.ReadUntil("Encoding = UTF-8");
    ircd.Write(":n!u@h PRIVMSG nick :Hello\xE6world");
    client.ReadUntil("Hello\xEF\xBF\xBDworld");
    client.Write(
        "PRIVMSG *controlpanel :SetNetwork Encoding $me $net ^CP-1251");
    client.ReadUntil("Encoding = ^CP-1251");
    ircd.Write(":n!u@h PRIVMSG nick :Hello\xE6world");
    client.ReadUntil("Hello\xD0\xB6world");
    ircd.Write(":n!u@h PRIVMSG nick :Hello\xD0\xB6world");
    client.ReadUntil("Hello\xD0\xB6world");
}


void CClient::PutModule(const CString& sModule, const CString& sLine) {
    if (!m_pUser) {
        return;
    }

    DEBUG("(" << GetFullName()
              << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() +
                     ((sModule.empty()) ? "status" : sModule) +
                     "!znc@znc.in PRIVMSG " << GetNick() << " :" << sLine
              << "]");

    VCString vsLines;
    sLine.Split("\n", vsLines);
    for (const CString& s : vsLines) {
        Write(":" + m_pUser->GetStatusPrefix() +
              ((sModule.empty()) ? "status" : sModule) +
              "!znc@znc.in PRIVMSG " + GetNick() + " :" + s + "\r\n");
    }
}


void CClient::PutStatusNotice(const CString& sLine) {
    PutModNotice("status", sLine);
}


bool CClient::OnNoticeMessage(CNoticeMessage& Message) {
    CString sTargets = Message.GetTarget();

    VCString vTargets;
    sTargets.Split(",", vTargets, false);

    for (CString& sTarget : vTargets) {
        Message.SetTarget(sTarget);
        if (m_pNetwork) {
            // May be nullptr.
            Message.SetChan(m_pNetwork->FindChan(sTarget));
        }

        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
            if (!sTarget.Equals("status")) {
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
                        OnModNotice(Message.GetText()));
            }
            continue;
        }

        bool bContinue = false;
        NETWORKMODULECALL(OnUserNoticeMessage(Message), m_pUser, m_pNetwork,
                          this, &bContinue);
        if (bContinue) continue;

        if (!GetIRCSock()) {
            // Some lagmeters do a NOTICE to their own nick, ignore those.
            if (!sTarget.Equals(m_sNick))
                PutStatus(
                    t_f("Your notice to {1} got lost, you are not connected to "
                        "IRC!")(Message.GetTarget()));
            continue;
        }

        if (m_pNetwork) {
            AddBuffer(Message);
            EchoMessage(Message);
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
                                    CMessage::ExcludeTags));
        }
    }

    return true;
}


void CAuthBase::Invalidate() { m_pSock = nullptr; }


void CClient::PutModNotice(const CString& sModule, const CString& sLine) {
    if (!m_pUser) {
        return;
    }

    DEBUG("(" << GetFullName()
              << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() +
                     ((sModule.empty()) ? "status" : sModule) +
                     "!znc@znc.in NOTICE " << GetNick() << " :" << sLine
              << "]");
    Write(":" + m_pUser->GetStatusPrefix() +
          ((sModule.empty()) ? "status" : sModule) + "!znc@znc.in NOTICE " +
          GetNick() + " :" + sLine + "\r\n");
}


void CClient::Timeout() { PutClient("ERROR :" + t_s("Closing link: Timeout")); }


void CClient::ParseUser(const CString& sAuthLine) {
    // user[@identifier][/network]

    const size_t uSlash = sAuthLine.rfind("/");
    if (uSlash != CString::npos) {
        m_sNetwork = sAuthLine.substr(uSlash + 1);

        ParseIdentifier(sAuthLine.substr(0, uSlash));
    } else {
        ParseIdentifier(sAuthLine);
    }
}


TEST_F(ZNCTest, AwayNotify) {
    auto znc = Run();
    auto ircd = ConnectIRCd();
    auto client = ConnectClient();
    client.Write("CAP LS");
    client.Write("PASS :hunter2");
    client.Write("NICK nick");
    client.Write("USER user/test x x :x");
    QByteArray cap_ls;
    client.ReadUntilAndGet(" LS :", cap_ls);
    ASSERT_THAT(cap_ls.toStdString(),
                AllOf(HasSubstr("cap-notify"), Not(HasSubstr("away-notify"))));
    client.Write("CAP REQ :cap-notify");
    client.ReadUntil("ACK :cap-notify");
    client.Write("CAP END");
    client.ReadUntil(" 001 ");
    ircd.ReadUntil("USER");
    ircd.Write("CAP user LS :away-notify");
    ircd.ReadUntil("CAP REQ :away-notify");
    ircd.Write("CAP user ACK :away-notify");
    ircd.ReadUntil("CAP END");
    ircd.Write(":server 001 user :welcome");
    client.ReadUntil("CAP user NEW :away-notify");
    client.Write("CAP REQ :away-notify");
    client.ReadUntil("ACK :away-notify");
    ircd.Write(":x!y@z AWAY :reason");
    client.ReadUntil(":x!y@z AWAY :reason");
    ircd.Close();
    client.ReadUntil("DEL :away-notify");
}


unsigned int CClient::PutStatus(const CTable& table) {
    unsigned int idx = 0;
    CString sLine;
    while (table.GetLine(idx++, sLine)) PutStatus(sLine);
    return idx - 1;
}


void CClient::AuthUser() {
    if (!m_bGotNick || !m_bGotUser || !m_bGotPass || m_bInCap || IsAttached())
        return;

    m_spAuth = std::make_shared<CClientAuth>(this, m_sUser, m_sPass);

    CZNC::Get().AuthUser(m_spAuth);
}


bool CClient::OnPongMessage(CMessage& Message) {
    // Block PONGs, we already responded to the pings
    return true;
}


TEST_F(ZNCTest, InvalidConfigInChan) {
    QFile conf(m_dir.path() + "/configs/znc.conf");
    ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text));
    QTextStream out(&conf);
    out << R"(
        <User foo>
            <Network bar>
                <Chan #baz>
                    Invalid = Line
                </Chan>
            </Network>
        </User>
    )";
    out.flush();
    auto znc = Run();
    znc->ShouldFinishItself(1);
}


void CClient::ParsePass(const CString& sAuthLine) {
    // [user[@identifier][/network]:]password

    const size_t uColon = sAuthLine.find(":");
    if (uColon != CString::npos) {
        m_sPass = sAuthLine.substr(uColon + 1);

        ParseUser(sAuthLine.substr(0, uColon));
    } else {
        m_sPass = sAuthLine;
    }
}


void CClient::RespondCap(const CString& sResponse) {
    PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse);
}


void CClient::ClearServerDependentCaps() {
    if (HasCapNotify() && !m_ssServerDependentCaps.empty()) {
        CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(),
                                          m_ssServerDependentCaps.end());
        PutClient(":irc.znc.in CAP " + GetNick() + " DEL :" + sCaps);

        for (const CString& sCap : m_ssServerDependentCaps) {
            const auto& it = m_mCoreCaps.find(sCap);
            if (m_mCoreCaps.end() != it) {
                const auto& handler = std::get<1>(it->second);
                handler(false);
            }
        }
    }

    m_ssServerDependentCaps.clear();
}


bool CClient::OnOtherMessage(CMessage& Message) {
    const CString& sCommand = Message.GetCommand();

    if (sCommand.Equals("ZNC")) {
        CString sTarget = Message.GetParam(0);
        CString sModCommand;

        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
            sModCommand = Message.GetParamsColon(1);
        } else {
            sTarget = "status";
            sModCommand = Message.GetParamsColon(0);
        }

        if (sTarget.Equals("status")) {
            if (sModCommand.empty())
                PutStatus(t_s("Hello. How may I help you?"));
            else
                UserCommand(sModCommand);
        } else {
            if (sModCommand.empty())
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
                        PutModule(t_s("Hello. How may I help you?")))
            else
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
                        OnModCommand(sModCommand))
        }
        return true;
    } else if (sCommand.Equals("ATTACH")) {
        if (!m_pNetwork) {
            return true;
        }

        CString sPatterns = Message.GetParamsColon(0);

        if (sPatterns.empty()) {
            PutStatusNotice(t_s("Usage: /attach <#chans>"));
            return true;
        }

        set<CChan*> sChans = MatchChans(sPatterns);
        unsigned int uAttachedChans = AttachChans(sChans);

        PutStatusNotice(t_p("There was {1} channel matching [{2}]",
                            "There were {1} channels matching [{2}]",
                            sChans.size())(sChans.size(), sPatterns));
        PutStatusNotice(t_p("Attached {1} channel", "Attached {1} channels",
                            uAttachedChans)(uAttachedChans));

        return true;
    } else if (sCommand.Equals("DETACH")) {
        if (!m_pNetwork) {
            return true;
        }

        CString sPatterns = Message.GetParamsColon(0);

        if (sPatterns.empty()) {
            PutStatusNotice(t_s("Usage: /detach <#chans>"));
            return true;
        }

        set<CChan*> sChans = MatchChans(sPatterns);
        unsigned int uDetached = DetachChans(sChans);

        PutStatusNotice(t_p("There was {1} channel matching [{2}]",
                            "There were {1} channels matching [{2}]",
                            sChans.size())(sChans.size(), sPatterns));
        PutStatusNotice(t_p("Detached {1} channel", "Detached {1} channels",
                            uDetached)(uDetached));

        return true;
    } else if (sCommand.Equals("PROTOCTL")) {
        for (const CString& sParam : Message.GetParams()) {
            if (sParam == "NAMESX") {
                m_bNamesx = true;
            } else if (sParam == "UHNAMES") {
                m_bUHNames = true;
            }
        }
        return true;  // If the server understands it, we already enabled namesx
                      // / uhnames
    }

    return false;
}


bool CClient::OnModeMessage(CModeMessage& Message) {
    CString sTarget = Message.GetTarget();

    if (m_pNetwork && m_pNetwork->IsChan(sTarget) && !Message.HasModes()) {
        // If we are on that channel and already received a
        // /mode reply from the server, we can answer this
        // request ourself.

        CChan* pChan = m_pNetwork->FindChan(sTarget);
        if (pChan && pChan->IsOn() && !pChan->GetModeString().empty()) {
            PutClient(":" + m_pNetwork->GetIRCServer() + " 324 " + GetNick() +
                      " " + sTarget + " " + pChan->GetModeString());
            if (pChan->GetCreationDate() > 0) {
                PutClient(":" + m_pNetwork->GetIRCServer() + " 329 " +
                          GetNick() + " " + sTarget + " " +
                          CString(pChan->GetCreationDate()));
            }
            return true;
        }
    }

    return false;
}


void CClient::ReadLine(const CString& sData) {
    CLanguageScope user_lang(GetUser() ? GetUser()->GetLanguage() : "");
    CString sLine = sData;

    sLine.Replace("\n", "");
    sLine.Replace("\r", "");

    DEBUG("(" << GetFullName() << ") CLI -> ZNC ["
        << CDebug::Filter(sLine) << "]");

    bool bReturn = false;
    if (IsAttached()) {
        NETWORKMODULECALL(OnUserRaw(sLine), m_pUser, m_pNetwork, this,
                          &bReturn);
    } else {
        GLOBALMODULECALL(OnUnknownUserRaw(this, sLine), &bReturn);
    }
    if (bReturn) return;

    CMessage Message(sLine);
    Message.SetClient(this);

    if (IsAttached()) {
        NETWORKMODULECALL(OnUserRawMessage(Message), m_pUser, m_pNetwork, this,
                          &bReturn);
    } else {
        GLOBALMODULECALL(OnUnknownUserRawMessage(Message), &bReturn);
    }
    if (bReturn) return;

    CString sCommand = Message.GetCommand();

    if (!IsAttached()) {
        // The following commands happen before authentication with ZNC
        if (sCommand.Equals("PASS")) {
            m_bGotPass = true;

            CString sAuthLine = Message.GetParam(0);
            ParsePass(sAuthLine);

            AuthUser();
            // Don't forward this msg.  ZNC has already registered us.
            return;
        } else if (sCommand.Equals("NICK")) {
            CString sNick = Message.GetParam(0);

            m_sNick = sNick;
            m_bGotNick = true;

            AuthUser();
            // Don't forward this msg.  ZNC will handle nick changes until auth
            // is complete
            return;
        } else if (sCommand.Equals("USER")) {
            CString sAuthLine = Message.GetParam(0);

            if (m_sUser.empty() && !sAuthLine.empty()) {
                ParseUser(sAuthLine);
            }

            m_bGotUser = true;
            if (m_bGotPass) {
                AuthUser();
            } else if (!m_bInCap) {
                SendRequiredPasswordNotice();
            }

            // Don't forward this msg.  ZNC has already registered us.
            return;
        }
    }

    if (Message.GetType() == CMessage::Type::Capability) {
        HandleCap(Message);

        // Don't let the client talk to the server directly about CAP,
        // we don't want anything enabled that ZNC does not support.
        return;
    }

    if (!m_pUser) {
        // Only CAP, NICK, USER and PASS are allowed before login
        return;
    }

    switch (Message.GetType()) {
        case CMessage::Type::Action:
            bReturn = OnActionMessage(Message);
            break;
        case CMessage::Type::CTCP:
            bReturn = OnCTCPMessage(Message);
            break;
        case CMessage::Type::Join:
            bReturn = OnJoinMessage(Message);
            break;
        case CMessage::Type::Mode:
            bReturn = OnModeMessage(Message);
            break;
        case CMessage::Type::Notice:
            bReturn = OnNoticeMessage(Message);
            break;
        case CMessage::Type::Part:
            bReturn = OnPartMessage(Message);
            break;
        case CMessage::Type::Ping:
            bReturn = OnPingMessage(Message);
            break;
        case CMessage::Type::Pong:
            bReturn = OnPongMessage(Message);
            break;
        case CMessage::Type::Quit:
            bReturn = OnQuitMessage(Message);
            break;
        case CMessage::Type::Text:
            bReturn = OnTextMessage(Message);
            break;
        case CMessage::Type::Topic:
            bReturn = OnTopicMessage(Message);
            break;
        default:
            bReturn = OnOtherMessage(Message);
            break;
    }

    if (bReturn) return;

    PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags));
}


void CClient::Connected() { DEBUG(GetSockName() << " == Connected();"); }


bool CClient::IsValidIdentifier(const CString& sIdentifier) {
    // ^[-\w]+$

    if (sIdentifier.empty()) {
        return false;
    }

    const char* p = sIdentifier.c_str();
    while (*p) {
        if (*p != '_' && *p != '-' && !isalnum(*p)) {
            return false;
        }

        p++;
    }

    return true;
}


TEST_F(ZNCTest, JoinKey) {
    QFile conf(m_dir.path() + "/configs/znc.conf");
    ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text));
    QTextStream(&conf) << "ServerThrottle = 1\n";
    auto znc = Run();

    auto ircd = ConnectIRCd();
    auto client = LoginClient();
    ircd.Write(":server 001 nick :Hello");
    client.Write("JOIN #znc secret");
    ircd.ReadUntil("JOIN #znc secret");
    ircd.Write(":nick JOIN :#znc");
    client.ReadUntil("JOIN :#znc");
    ircd.Close();

    ircd = ConnectIRCd();
    ircd.Write(":server 001 nick :Hello");
    ircd.ReadUntil("JOIN #znc secret");
}


const CIRCSock* CClient::GetIRCSock() const {
    if (m_pNetwork) {
        return m_pNetwork->GetIRCSock();
    }

    return nullptr;
}


bool CClient::OnQuitMessage(CQuitMessage& Message) {
    bool bReturn = false;
    NETWORKMODULECALL(OnUserQuitMessage(Message), m_pUser, m_pNetwork, this,
                      &bReturn);
    if (!bReturn) {
        Close(Csock::CLT_AFTERWRITE);  // Treat a client quit as a detach
    }
    // Don't forward this msg.  We don't want the client getting us
    // disconnected.
    return true;
}


CIRCSock* CClient::GetIRCSock() {
    if (m_pNetwork) {
        return m_pNetwork->GetIRCSock();
    }

    return nullptr;
}


void CClient::ParseIdentifier(const CString& sAuthLine) {
    // user[@identifier]

    const size_t uAt = sAuthLine.rfind("@");
    if (uAt != CString::npos) {
        const CString sId = sAuthLine.substr(uAt + 1);

        if (IsValidIdentifier(sId)) {
            m_sIdentifier = sId;
            m_sUser = sAuthLine.substr(0, uAt);
        } else {
            m_sUser = sAuthLine;
        }
    } else {
        m_sUser = sAuthLine;
    }
}


bool CClient::OnCTCPMessage(CCTCPMessage& Message) {
    CString sTargets = Message.GetTarget();

    VCString vTargets;
    sTargets.Split(",", vTargets, false);

    if (Message.IsReply()) {
        CString sCTCP = Message.GetText();
        if (sCTCP.Token(0) == "VERSION") {
            // There are 2 different scenarios:
            //
            // a) CTCP reply for VERSION is not set.
            // 1. ZNC receives CTCP VERSION from someone
            // 2. ZNC forwards CTCP VERSION to client
            // 3. Client replies with something
            // 4. ZNC adds itself to the reply
            // 5. ZNC sends the modified reply to whoever asked
            //
            // b) CTCP reply for VERSION is set.
            // 1. ZNC receives CTCP VERSION from someone
            // 2. ZNC replies with the configured reply (or just drops it if
            //    empty), without forwarding anything to client
            // 3. Client does not see any CTCP request, and does not reply
            //
            // So, if user doesn't want "via ZNC" in CTCP VERSION reply, they
            // can set custom reply.
            //
            // See more bikeshedding at github issues #820 and #1012
            Message.SetText(sCTCP + " via " + CZNC::GetTag(false));
        }
    }

    for (CString& sTarget : vTargets) {
        Message.SetTarget(sTarget);
        if (m_pNetwork) {
            // May be nullptr.
            Message.SetChan(m_pNetwork->FindChan(sTarget));
        }

        bool bContinue = false;
        if (Message.IsReply()) {
            NETWORKMODULECALL(OnUserCTCPReplyMessage(Message), m_pUser,
                              m_pNetwork, this, &bContinue);
        } else {
            NETWORKMODULECALL(OnUserCTCPMessage(Message), m_pUser, m_pNetwork,
                              this, &bContinue);
        }
        if (bContinue) continue;

        if (!GetIRCSock()) {
            // Some lagmeters do a NOTICE to their own nick, ignore those.
            if (!sTarget.Equals(m_sNick))
                PutStatus(t_f(
                    "Your CTCP to {1} got lost, you are not connected to IRC!")(
                    Message.GetTarget()));
            continue;
        }

        if (m_pNetwork) {
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
                                    CMessage::ExcludeTags));
        }
    }

    return true;
}


CString CClient::GetNickMask() const {
    if (GetIRCSock() && GetIRCSock()->IsAuthed()) {
        return GetIRCSock()->GetNickMask();
    }

    CString sHost =
        m_pNetwork ? m_pNetwork->GetBindHost() : m_pUser->GetBindHost();
    if (sHost.empty()) {
        sHost = "irc.znc.in";
    }

    return GetNick() + "!" +
           (m_pNetwork ? m_pNetwork->GetIdent() : m_pUser->GetIdent()) + "@" +
           sHost;
}


bool CClient::OnPingMessage(CMessage& Message) {
    // All PONGs are generated by ZNC. We will still forward this to
    // the ircd, but all PONGs from irc will be blocked.
    if (!Message.GetParams().empty())
        PutClient(":irc.znc.in PONG irc.znc.in " + Message.GetParamsColon(0));
    else
        PutClient(":irc.znc.in PONG irc.znc.in");
    return false;
}


void CClient::SendRequiredPasswordNotice() {
    PutClient(":irc.znc.in 464 " + GetNick() + " :Password required");
    PutClient(
        ":irc.znc.in NOTICE " + GetNick() + " :*** "
        "You need to send your password. "
        "Configure your client to send a server password.");
    PutClient(
        ":irc.znc.in NOTICE " + GetNick() + " :*** "
        "To connect now, you can use /quote PASS <username>:<password>, "
        "or /quote PASS <username>/<network>:<password> to connect to a "
        "specific network.");
}


TEST_F(ZNCTest, Connect) {
    auto znc = Run();

    auto ircd = ConnectIRCd();
    ircd.ReadUntil("CAP LS");

    auto client = ConnectClient();
    client.Write("PASS :hunter2");
    client.Write("NICK nick");
    client.Write("USER user/test x x :x");
    client.ReadUntil("Welcome");
    client.Close();

    client = ConnectClient();
    client.Write("PASS :user:hunter2");
    client.Write("NICK nick");
    client.Write("USER u x x x");
    client.ReadUntil("Welcome");
    client.Close();

    client = ConnectClient();
    client.Write("NICK nick");
    client.Write("USER user x x x");
    client.ReadUntil("Configure your client to send a server password");
    client.Close();

    ircd.Write(":server 001 nick :Hello");
    ircd.ReadUntil("WHO");
}


void CClient::SetNick(const CString& s) { m_sNick = s; }


void CClient::HandleCap(const CMessage& Message) {
    CString sSubCmd = Message.GetParam(0);

    if (sSubCmd.Equals("LS")) {
        SCString ssOfferCaps;
        for (const auto& it : m_mCoreCaps) {
            bool bServerDependent = std::get<0>(it.second);
            if (!bServerDependent ||
                m_ssServerDependentCaps.count(it.first) > 0)
                ssOfferCaps.insert(it.first);
        }
        GLOBALMODULECALL(OnClientCapLs(this, ssOfferCaps), NOTHING);
        CString sRes =
            CString(" ").Join(ssOfferCaps.begin(), ssOfferCaps.end());
        RespondCap("LS :" + sRes);
        m_bInCap = true;
        if (Message.GetParam(1).ToInt() >= 302) {
            m_bCapNotify = true;
        }
    } else if (sSubCmd.Equals("END")) {
        m_bInCap = false;
        if (!IsAttached()) {
            if (!m_pUser && m_bGotUser && !m_bGotPass) {
                SendRequiredPasswordNotice();
            } else {
                AuthUser();
            }
        }
    } else if (sSubCmd.Equals("REQ")) {
        VCString vsTokens;
        Message.GetParam(1).Split(" ", vsTokens, false);

        for (const CString& sToken : vsTokens) {
            bool bVal = true;
            CString sCap = sToken;
            if (sCap.TrimPrefix("-")) bVal = false;

            bool bAccepted = false;
            const auto& it = m_mCoreCaps.find(sCap);
            if (m_mCoreCaps.end() != it) {
                bool bServerDependent = std::get<0>(it->second);
                bAccepted = !bServerDependent ||
                            m_ssServerDependentCaps.count(sCap) > 0;
            }
            GLOBALMODULECALL(IsClientCapSupported(this, sCap, bVal),
                             &bAccepted);

            if (!bAccepted) {
                // Some unsupported capability is requested
                RespondCap("NAK :" + Message.GetParam(1));
                return;
            }
        }

        // All is fine, we support what was requested
        for (const CString& sToken : vsTokens) {
            bool bVal = true;
            CString sCap = sToken;
            if (sCap.TrimPrefix("-")) bVal = false;

            auto handler_it = m_mCoreCaps.find(sCap);
            if (m_mCoreCaps.end() != handler_it) {
                const auto& handler = std::get<1>(handler_it->second);
                handler(bVal);
            }
            GLOBALMODULECALL(OnClientCapRequest(this, sCap, bVal), NOTHING);

            if (bVal) {
                m_ssAcceptedCaps.insert(sCap);
            } else {
                m_ssAcceptedCaps.erase(sCap);
            }
        }

        RespondCap("ACK :" + Message.GetParam(1));
    } else if (sSubCmd.Equals("LIST")) {
        CString sList =
            CString(" ").Join(m_ssAcceptedCaps.begin(), m_ssAcceptedCaps.end());
        RespondCap("LIST :" + sList);
    } else {
        PutClient(":irc.znc.in 410 " + GetNick() + " " + sSubCmd +
                  " :Invalid CAP subcommand");
    }
}


const vector<CClient*>& CClient::GetClients() const {
    if (m_pNetwork) {
        return m_pNetwork->GetClients();
    }

    return m_pUser->GetUserClients();
}


void CAuthBase::AcceptLogin(CUser& User) {
    if (m_pSock) {
        AcceptedLogin(User);
        Invalidate();
    }
}


TEST_F(ZNCTest, FixFixOfCVE20149403) {
    auto znc = Run();
    auto ircd = ConnectIRCd();
    ircd.Write(":server 001 nick :Hello");
    ircd.Write(":nick JOIN @#znc");
    ircd.ReadUntil("MODE @#znc");
    ircd.Write(":server 005 nick STATUSMSG=@ :supports");
    ircd.Write(":server PING :12345");
    ircd.ReadUntil("PONG 12345");

    QNetworkRequest request;
    request.setRawHeader("Authorization",
                         "Basic " + QByteArray("user:hunter2").toBase64());
    request.setUrl(QUrl("http://127.0.0.1:12345/mods/global/webadmin/addchan"));
    auto reply = HttpPost(request, {
                                       {"user", "user"},
                                       {"network", "test"},
                                       {"submitted", "1"},
                                       {"name", "@#znc"},
                                       {"enabled", "1"},
                                   });
    EXPECT_THAT(reply->readAll().toStdString(),
                HasSubstr("Could not add channel [@#znc]"));
}


void CClient::AddBuffer(const T& Message) {
    const CString sTarget = Message.GetTarget();

    T Format;
    Format.Clone(Message);
    Format.SetNick(CNick(_NAMEDFMT(GetNickMask())));
    Format.SetTarget(_NAMEDFMT(sTarget));
    Format.SetText("{text}");

    CChan* pChan = m_pNetwork->FindChan(sTarget);
    if (pChan) {
        if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline()) {
            pChan->AddBuffer(Format, Message.GetText());
        }
    } else if (Message.GetType() != CMessage::Type::Notice) {
        if (!m_pUser->AutoClearQueryBuffer() || !m_pNetwork->IsUserOnline()) {
            CQuery* pQuery = m_pNetwork->AddQuery(sTarget);
            if (pQuery) {
                pQuery->AddBuffer(Format, Message.GetText());
            }
        }
    }
}


void CClient::PutClient(const CString& sLine) {
    PutClient(CMessage(sLine));
}


TEST_F(ZNCTest, Channel) {
    auto znc = Run();
    auto ircd = ConnectIRCd();

    auto client = LoginClient();
    client.ReadUntil("Welcome");
    client.Write("JOIN #znc");
    client.Close();

    ircd.Write(":server 001 nick :Hello");
    ircd.ReadUntil("JOIN #znc");
    ircd.Write(":nick JOIN :#znc");
    ircd.Write(":server 353 nick #znc :nick");
    ircd.Write(":server 366 nick #znc :End of /NAMES list");
    ircd.Write(":server PING :1");
    ircd.ReadUntil("PONG 1");

    client = LoginClient();
    client.ReadUntil(":nick JOIN :#znc");
}


void CClient::SetNetwork(CIRCNetwork* pNetwork, bool bDisconnect,
                         bool bReconnect) {
    if (m_pNetwork) {
        m_pNetwork->ClientDisconnected(this);

        if (bDisconnect) {
            ClearServerDependentCaps();
            // Tell the client they are no longer in these channels.
            const vector<CChan*>& vChans = m_pNetwork->GetChans();
            for (const CChan* pChan : vChans) {
                if (!(pChan->IsDetached())) {
                    PutClient(":" + m_pNetwork->GetIRCNick().GetNickMask() +
                              " PART " + pChan->GetName());
                }
            }
        }
    } else if (m_pUser) {
        m_pUser->UserDisconnected(this);
    }

    m_pNetwork = pNetwork;

    if (bReconnect) {
        if (m_pNetwork) {
            m_pNetwork->ClientConnected(this);
        } else if (m_pUser) {
            m_pUser->UserConnected(this);
        }
    }
}


bool CClient::PutClientRaw(const CString& sLine) {
    CString sCopy = sLine;
    bool bReturn = false;
    NETWORKMODULECALL(OnSendToClient(sCopy, *this), m_pUser, m_pNetwork, this,
                      &bReturn);
    if (bReturn) return false;

    DEBUG("(" << GetFullName() << ") ZNC -> CLI ["
        << CDebug::Filter(sCopy) << "]");
    Write(sCopy + "\r\n");
    return true;
}


void CClient::PutIRC(const CString& sLine) {
    if (m_pNetwork) {
        m_pNetwork->PutIRC(sLine);
    }
}


bool CClient::SendMotd() {
    const VCString& vsMotd = CZNC::Get().GetMotd();

    if (!vsMotd.size()) {
        return false;
    }

    for (const CString& sLine : vsMotd) {
        if (m_pNetwork) {
            PutStatusNotice(m_pNetwork->ExpandString(sLine));
        } else {
            PutStatusNotice(m_pUser->ExpandString(sLine));
        }
    }

    return true;
}


CString CAuthBase::GetRemoteIP() const {
    if (m_pSock) return m_pSock->GetRemoteIP();
    return "";
}


void CAuthBase::RefuseLogin(const CString& sReason) {
    if (!m_pSock) return;

    CUser* pUser = CZNC::Get().FindUser(GetUsername());

    // If the username is valid, notify that user that someone tried to
    // login. Use sReason because there are other reasons than "wrong
    // password" for a login to be rejected (e.g. fail2ban).
    if (pUser) {
        pUser->PutStatusNotice(t_f(
            "A client from {1} attempted to login as you, but was rejected: "
            "{2}")(GetRemoteIP(), sReason));
    }

    GLOBALMODULECALL(OnFailedLogin(GetUsername(), GetRemoteIP()), NOTHING);
    RefusedLogin(sReason);
    Invalidate();
}


bool CClient::OnTextMessage(CTextMessage& Message) {
    CString sTargets = Message.GetTarget();

    VCString vTargets;
    sTargets.Split(",", vTargets, false);

    for (CString& sTarget : vTargets) {
        Message.SetTarget(sTarget);
        if (m_pNetwork) {
            // May be nullptr.
            Message.SetChan(m_pNetwork->FindChan(sTarget));
        }

        if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) {
            EchoMessage(Message);

            if (sTarget.Equals("status")) {
                CString sMsg = Message.GetText();
                UserCommand(sMsg);
            } else {
                CALLMOD(sTarget, this, m_pUser, m_pNetwork,
                        OnModCommand(Message.GetText()));
            }
            continue;
        }

        bool bContinue = false;
        NETWORKMODULECALL(OnUserTextMessage(Message), m_pUser, m_pNetwork, this,
                          &bContinue);
        if (bContinue) continue;

        if (!GetIRCSock()) {
            // Some lagmeters do a PRIVMSG to their own nick, ignore those.
            if (!sTarget.Equals(m_sNick))
                PutStatus(
                    t_f("Your message to {1} got lost, you are not connected "
                        "to IRC!")(Message.GetTarget()));
            continue;
        }

        if (m_pNetwork) {
            AddBuffer(Message);
            EchoMessage(Message);
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
                                    CMessage::ExcludeTags));
        }
    }

    return true;
}


TEST(Config, AlreadyExists) {
    QTemporaryDir dir;
    WriteConfig(dir.path());
    Process p(ZNC_BIN_DIR "/znc", QStringList() << "--debug"
                                                << "--datadir" << dir.path()
                                                << "--makeconf");
    p.ReadUntil("already exists");
    p.CanDie();
}


TEST_F(ZNCTest, FixCVE20149403) {
    auto znc = Run();
    auto ircd = ConnectIRCd();
    ircd.Write(":server 001 nick :Hello");
    ircd.Write(":server 005 nick CHANTYPES=# :supports");
    ircd.Write(":server PING :1");
    ircd.ReadUntil("PONG 1");

    QNetworkRequest request;
    request.setRawHeader("Authorization",
                         "Basic " + QByteArray("user:hunter2").toBase64());
    request.setUrl(QUrl("http://127.0.0.1:12345/mods/global/webadmin/addchan"));
    HttpPost(request, {
                          {"user", "user"},
                          {"network", "test"},
                          {"submitted", "1"},
                          {"name", "znc"},
                          {"enabled", "1"},
                      });
    EXPECT_THAT(HttpPost(request,
                         {
                             {"user", "user"},
                             {"network", "test"},
                             {"submitted", "1"},
                             {"name", "znc"},
                             {"enabled", "1"},
                         })
                    ->readAll()
                    .toStdString(),
                HasSubstr("Channel [#znc] already exists"));
}


CString CClient::GetFullName() const {
    if (!m_pUser) return GetRemoteIP();
    CString sFullName = m_pUser->GetUsername();
    if (!m_sIdentifier.empty()) sFullName += "@" + m_sIdentifier;
    if (m_pNetwork) sFullName += "/" + m_pNetwork->GetName();
    return sFullName;
}


void CClient::SetTagSupport(const CString& sTag, bool bState) {
    if (bState) {
        m_ssSupportedTags.insert(sTag);
    } else {
        m_ssSupportedTags.erase(sTag);
    }
}


void CClient::ConnectionRefused() {
    DEBUG(GetSockName() << " == ConnectionRefused()");
}


void CClient::NotifyServerDependentCaps(const SCString& ssCaps) {
    for (const CString& sCap : ssCaps) {
        const auto& it = m_mCoreCaps.find(sCap);
        if (m_mCoreCaps.end() != it) {
            bool bServerDependent = std::get<0>(it->second);
            if (bServerDependent) {
                m_ssServerDependentCaps.insert(sCap);
            }
        }
    }

    if (HasCapNotify() && !m_ssServerDependentCaps.empty()) {
        CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(),
                                          m_ssServerDependentCaps.end());
        PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + sCaps);
    }
}


CClient::~CClient() {
    if (m_spAuth) {
        CClientAuth* pAuth = (CClientAuth*)&(*m_spAuth);
        pAuth->Invalidate();
    }
    if (m_pUser != nullptr) {
        m_pUser->AddBytesRead(GetBytesRead());
        m_pUser->AddBytesWritten(GetBytesWritten());
    }
}


unsigned int CClient::DetachChans(const std::set<CChan*>& sChans) {
    unsigned int uDetached = 0;
    for (CChan* pChan : sChans) {
        if (pChan->IsDetached()) continue;
        uDetached++;
        pChan->DetachUser();
    }
    return uDetached;
}


void CClient::BouncedOff() {
    PutStatusNotice(
        t_s("You are being disconnected because another user just "
            "authenticated as you."));
    Close(Csock::CLT_AFTERWRITE);
}


bool CClient::OnJoinMessage(CJoinMessage& Message) {
    CString sChans = Message.GetTarget();
    CString sKeys = Message.GetKey();

    VCString vsChans;
    sChans.Split(",", vsChans, false);
    sChans.clear();

    VCString vsKeys;
    sKeys.Split(",", vsKeys, true);
    sKeys.clear();

    for (unsigned int a = 0; a < vsChans.size(); a++) {
        Message.SetTarget(vsChans[a]);
        Message.SetKey((a < vsKeys.size()) ? vsKeys[a] : "");
        if (m_pNetwork) {
            // May be nullptr.
            Message.SetChan(m_pNetwork->FindChan(vsChans[a]));
        }
        bool bContinue = false;
        NETWORKMODULECALL(OnUserJoinMessage(Message), m_pUser, m_pNetwork, this,
                          &bContinue);
        if (bContinue) continue;

        CString sChannel = Message.GetTarget();
        CString sKey = Message.GetKey();

        if (m_pNetwork) {
            CChan* pChan = m_pNetwork->FindChan(sChannel);
            if (pChan) {
                if (pChan->IsDetached())
                    pChan->AttachUser(this);
                else
                    pChan->JoinUser(sKey);
                continue;
            } else if (!sChannel.empty()) {
                pChan = new CChan(sChannel, m_pNetwork, false);
                if (m_pNetwork->AddChan(pChan)) {
                    pChan->SetKey(sKey);
                }
            }
        }

        if (!sChannel.empty()) {
            sChans += (sChans.empty()) ? sChannel : CString("," + sChannel);

            if (!vsKeys.empty()) {
                sKeys += (sKeys.empty()) ? sKey : CString("," + sKey);
            }
        }
    }

    Message.SetTarget(sChans);
    Message.SetKey(sKeys);

    return sChans.empty();
}


void CClientAuth::AcceptedLogin(CUser& User) {
    if (m_pClient) {
        m_pClient->AcceptLogin(User);
    }
}


void CClient::PutStatus(const CString& sLine) { PutModule("status", sLine); }


bool CClient::OnPartMessage(CPartMessage& Message) {
    CString sChans = Message.GetTarget();

    VCString vsChans;
    sChans.Split(",", vsChans, false);
    sChans.clear();

    for (CString& sChan : vsChans) {
        bool bContinue = false;
        Message.SetTarget(sChan);
        if (m_pNetwork) {
            // May be nullptr.
            Message.SetChan(m_pNetwork->FindChan(sChan));
        }
        NETWORKMODULECALL(OnUserPartMessage(Message), m_pUser, m_pNetwork, this,
                          &bContinue);
        if (bContinue) continue;

        sChan = Message.GetTarget();

        CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChan) : nullptr;

        if (pChan && !pChan->IsOn()) {
            PutStatusNotice(t_f("Removing channel {1}")(sChan));
            m_pNetwork->DelChan(sChan);
        } else {
            sChans += (sChans.empty()) ? sChan : CString("," + sChan);
        }
    }

    if (sChans.empty()) {
        return true;
    }

    Message.SetTarget(sChans);

    return false;
}


set<CChan*> CClient::MatchChans(const CString& sPatterns) const {
    VCString vsPatterns;
    sPatterns.Replace_n(",", " ")
        .Split(" ", vsPatterns, false, "", "", true, true);

    set<CChan*> sChans;
    for (const CString& sPattern : vsPatterns) {
        vector<CChan*> vChans = m_pNetwork->FindChans(sPattern);
        sChans.insert(vChans.begin(), vChans.end());
    }
    return sChans;
}


void CClient::AcceptLogin(CUser& User) {
    m_sPass = "";
    m_pUser = &User;

    // Set our proper timeout and set back our proper timeout mode
    // (constructor set a different timeout and mode)
    SetTimeout(User.GetNoTrafficTimeout(), TMO_READ);

    SetSockName("USR::" + m_pUser->GetUsername());
    SetEncoding(m_pUser->GetClientEncoding());

    if (!m_sNetwork.empty()) {
        m_pNetwork = m_pUser->FindNetwork(m_sNetwork);
        if (!m_pNetwork) {
            PutStatus(t_f("Network {1} doesn't exist.")(m_sNetwork));
        }
    } else if (!m_pUser->GetNetworks().empty()) {
        // If a user didn't supply a network, and they have a network called
        // "default" then automatically use this network.
        m_pNetwork = m_pUser->FindNetwork("default");
        // If no "default" network, try "user" network. It's for compatibility
        // with early network stuff in ZNC, which converted old configs to
        // "user" network.
        if (!m_pNetwork) m_pNetwork = m_pUser->FindNetwork("user");
        // Otherwise, just try any network of the user.
        if (!m_pNetwork) m_pNetwork = *m_pUser->GetNetworks().begin();
        if (m_pNetwork && m_pUser->GetNetworks().size() > 1) {
            PutStatusNotice(
                t_s("You have several networks configured, but no network was "
                    "specified for the connection."));
            PutStatusNotice(
                t_f("Selecting network {1}. To see list of all configured "
                    "networks, use /znc ListNetworks")(m_pNetwork->GetName()));
            PutStatusNotice(t_f(
                "If you want to choose another network, use /znc JumpNetwork "
                "<network>, or connect to ZNC with username {1}/<network> "
                "(instead of just {1})")(m_pUser->GetUsername()));
        }
    } else {
        PutStatusNotice(
            t_s("You have no networks configured. Use /znc AddNetwork "
                "<network> to add one."));
    }

    SetNetwork(m_pNetwork, false);

    SendMotd();

    NETWORKMODULECALL(OnClientLogin(), m_pUser, m_pNetwork, this, NOTHING);
}


bool CClient::OnTopicMessage(CTopicMessage& Message) {
    bool bReturn = false;
    CString sChan = Message.GetTarget();
    CString sTopic = Message.GetTopic();
    if (m_pNetwork) {
        // May be nullptr.
        Message.SetChan(m_pNetwork->FindChan(sChan));
    }

    if (!sTopic.empty()) {
        NETWORKMODULECALL(OnUserTopicMessage(Message), m_pUser, m_pNetwork,
                          this, &bReturn);
    } else {
        NETWORKMODULECALL(OnUserTopicRequest(sChan), m_pUser, m_pNetwork, this,
                          &bReturn);
        Message.SetTarget(sChan);
    }

    return bReturn;
}


CString CClient::GetNick(bool bAllowIRCNick) const {
    CString sRet;

    const CIRCSock* pSock = GetIRCSock();
    if (bAllowIRCNick && pSock && pSock->IsAuthed()) {
        sRet = pSock->GetNick();
    }

    return (sRet.empty()) ? m_sNick : sRet;
}


CClientAuth::CClientAuth(CClient* pClient, const CString& sUsername,
                         const CString& sPassword)
    : CAuthBase(sUsername, sPassword, pClient), m_pClient(pClient) {}


void CClient::ReachedMaxBuffer() {
    DEBUG(GetSockName() << " == ReachedMaxBuffer()");
    if (IsAttached()) {
        PutClient("ERROR :" + t_s("Closing link: Too long raw line"));
    }
    Close();
}


TEST_F(ZNCTest, BuildMod) {
    auto znc = Run();
    auto ircd = ConnectIRCd();
    auto client = LoginClient();
    QTemporaryDir srcd;
    QDir srcdir(srcd.path());
    QFile file(srcdir.filePath("testmod.cpp"));
    ASSERT_TRUE(file.open(QIODevice::WriteOnly | QIODevice::Text));
    QTextStream out(&file);
    out << R"(
        #include <znc/Modules.h>
        class TestModule : public CModule {
          public:
            MODCONSTRUCTOR(TestModule) {}
            void OnModCommand(const CString& sLine) override {
                PutModule("Lorem ipsum");
            }
        };
        MODULEDEFS(TestModule, "Test")
    )";
    file.close();
    QDir dir(m_dir.path());
    EXPECT_TRUE(dir.mkdir("modules"));
    EXPECT_TRUE(dir.cd("modules"));
    {
        Process p(ZNC_BIN_DIR "/znc-buildmod",
                  QStringList() << srcdir.filePath("file-not-found.cpp"),
                  [&](QProcess* proc) {
                      proc->setWorkingDirectory(dir.absolutePath());
                      proc->setProcessChannelMode(QProcess::ForwardedChannels);
                  });
        p.ShouldFinishItself(1);
        p.ShouldFinishInSec(300);
    }
    {
        Process p(ZNC_BIN_DIR "/znc-buildmod",
                  QStringList() << srcdir.filePath("testmod.cpp"),
                  [&](QProcess* proc) {
                      proc->setWorkingDirectory(dir.absolutePath());
                      proc->setProcessChannelMode(QProcess::ForwardedChannels);
                  });
        p.ShouldFinishItself();
        p.ShouldFinishInSec(300);
    }
    client.Write("znc loadmod testmod");
    client.Write("PRIVMSG *testmod :hi");
    client.ReadUntil("Lorem ipsum");
}


void CClient::RefuseLogin(const CString& sReason) {
    PutStatus("Bad username and/or password.");
    PutClient(":irc.znc.in 464 " + GetNick() + " :" + sReason);
    Close(Csock::CLT_AFTERWRITE);
}


bool CClient::OnActionMessage(CActionMessage& Message) {
    CString sTargets = Message.GetTarget();

    VCString vTargets;
    sTargets.Split(",", vTargets, false);

    for (CString& sTarget : vTargets) {
        Message.SetTarget(sTarget);
        if (m_pNetwork) {
            // May be nullptr.
            Message.SetChan(m_pNetwork->FindChan(sTarget));
        }

        bool bContinue = false;
        NETWORKMODULECALL(OnUserActionMessage(Message), m_pUser, m_pNetwork,
                          this, &bContinue);
        if (bContinue) continue;

        if (m_pNetwork) {
            AddBuffer(Message);
            EchoMessage(Message);
            PutIRC(Message.ToString(CMessage::ExcludePrefix |
                                    CMessage::ExcludeTags));
        }
    }

    return true;
}


unsigned int CClient::AttachChans(const std::set<CChan*>& sChans) {
    unsigned int uAttached = 0;
    for (CChan* pChan : sChans) {
        if (!pChan->IsDetached()) continue;
        uAttached++;
        pChan->AttachUser();
    }
    return uAttached;
}


bool CClient::PutClient(const CMessage& Message) {
    if (!m_bAwayNotify && Message.GetType() == CMessage::Type::Away) {
        return false;
    } else if (!m_bAccountNotify &&
               Message.GetType() == CMessage::Type::Account) {
        return false;
    }

    CMessage Msg(Message);

    const CIRCSock* pIRCSock = GetIRCSock();
    if (pIRCSock) {
        if (Msg.GetType() == CMessage::Type::Numeric) {
            unsigned int uCode = Msg.As<CNumericMessage>().GetCode();

            if (uCode == 352) {  // RPL_WHOREPLY
                if (!m_bNamesx && pIRCSock->HasNamesx()) {
                    // The server has NAMESX, but the client doesn't, so we need
                    // to remove extra prefixes
                    CString sNick = Msg.GetParam(6);
                    if (sNick.size() > 1 && pIRCSock->IsPermChar(sNick[1])) {
                        CString sNewNick = sNick;
                        size_t pos =
                            sNick.find_first_not_of(pIRCSock->GetPerms());
                        if (pos >= 2 && pos != CString::npos) {
                            sNewNick = sNick[0] + sNick.substr(pos);
                        }
                        Msg.SetParam(6, sNewNick);
                    }
                }
            } else if (uCode == 353) {  // RPL_NAMES
                if ((!m_bNamesx && pIRCSock->HasNamesx()) ||
                    (!m_bUHNames && pIRCSock->HasUHNames())) {
                    // The server has either UHNAMES or NAMESX, but the client
                    // is missing either or both
                    CString sNicks = Msg.GetParam(3);
                    VCString vsNicks;
                    sNicks.Split(" ", vsNicks, false);

                    for (CString& sNick : vsNicks) {
                        if (sNick.empty()) break;

                        if (!m_bNamesx && pIRCSock->HasNamesx() &&
                            pIRCSock->IsPermChar(sNick[0])) {
                            // The server has NAMESX, but the client doesn't, so
                            // we just use the first perm char
                            size_t pos =
                                sNick.find_first_not_of(pIRCSock->GetPerms());
                            if (pos >= 2 && pos != CString::npos) {
                                sNick = sNick[0] + sNick.substr(pos);
                            }
                        }

                        if (!m_bUHNames && pIRCSock->HasUHNames()) {
                            // The server has UHNAMES, but the client doesn't,
                            // so we strip away ident and host
                            sNick = sNick.Token(0, false, "!");
                        }
                    }

                    Msg.SetParam(
                        3, CString(" ").Join(vsNicks.begin(), vsNicks.end()));
                }
            }
        } else if (Msg.GetType() == CMessage::Type::Join) {
            if (!m_bExtendedJoin && pIRCSock->HasExtendedJoin()) {
                Msg.SetParams({Msg.As<CJoinMessage>().GetTarget()});
            }
        }
    }

    MCString mssTags;

    for (const auto& it : Msg.GetTags()) {
        if (IsTagEnabled(it.first)) {
            mssTags[it.first] = it.second;
        }
    }

    if (HasServerTime()) {
        // If the server didn't set the time tag, manually set it
        mssTags.emplace("time", CUtils::FormatServerTime(Msg.GetTime()));
    }

    Msg.SetTags(mssTags);
    Msg.SetClient(this);
    Msg.SetNetwork(m_pNetwork);

    bool bReturn = false;
    NETWORKMODULECALL(OnSendToClientMessage(Msg), m_pUser, m_pNetwork, this,
                      &bReturn);
    if (bReturn) return false;

    return PutClientRaw(Msg.ToString());
}


TEST_F(ZNCTest, HTTP) {
    auto znc = Run();
    auto ircd = ConnectIRCd();
    auto reply = HttpGet(QNetworkRequest(QUrl("http://127.0.0.1:12345/")));
    EXPECT_THAT(reply->rawHeader("Server").toStdString(), HasSubstr("ZNC"));
}


void CClientAuth::RefusedLogin(const CString& sReason) {
    if (m_pClient) {
        m_pClient->RefuseLogin(sReason);
    }
}


GF_Err Media_GetESD(GF_MediaBox *mdia, u32 sampleDescIndex, GF_ESD **out_esd, Bool true_desc_only)
{
	u32 type;
	GF_ESD *esd;
	GF_MPEGSampleEntryBox *entry = NULL;
	GF_ESDBox *ESDa;
	GF_ProtectionSchemeInfoBox *sinf;
	GF_SampleDescriptionBox *stsd = mdia->information->sampleTable->SampleDescription;

	*out_esd = NULL;
	if (!stsd || !stsd->child_boxes || !sampleDescIndex || (sampleDescIndex > gf_list_count(stsd->child_boxes)) )
		return GF_BAD_PARAM;

	esd = NULL;
	entry = (GF_MPEGSampleEntryBox*)gf_list_get(stsd->child_boxes, sampleDescIndex - 1);
	if (! entry) return GF_ISOM_INVALID_MEDIA;

	*out_esd = NULL;
	ESDa = NULL;
	type = entry->type;
	switch (type) {
	case GF_ISOM_BOX_TYPE_ENCV:
	case GF_ISOM_BOX_TYPE_ENCA:
	case GF_ISOM_BOX_TYPE_ENCS:
	case GF_ISOM_BOX_TYPE_ENCF:
	case GF_ISOM_BOX_TYPE_ENCM:
	case GF_ISOM_BOX_TYPE_ENCT:
		sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF);
		if (sinf && sinf->original_format) {
			type = sinf->original_format->data_format;
		}
		break;
	case GF_ISOM_BOX_TYPE_RESV:
		sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_RINF);
		if (sinf && sinf->original_format) {
			type = sinf->original_format->data_format;
		}
		break;
	}

	switch (type) {
	case GF_ISOM_BOX_TYPE_MP4V:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO)
			return GF_ISOM_INVALID_MEDIA;
		ESDa = ((GF_MPEGVisualSampleEntryBox*)entry)->esd;
		if (ESDa) esd = (GF_ESD *) ESDa->desc;
		/*avc1 encrypted*/
		else esd = ((GF_MPEGVisualSampleEntryBox*) entry)->emul_esd;
		break;
	case GF_ISOM_BOX_TYPE_AVC1:
	case GF_ISOM_BOX_TYPE_AVC2:
	case GF_ISOM_BOX_TYPE_AVC3:
	case GF_ISOM_BOX_TYPE_AVC4:
	case GF_ISOM_BOX_TYPE_HVC1:
	case GF_ISOM_BOX_TYPE_HEV1:
	case GF_ISOM_BOX_TYPE_HVC2:
	case GF_ISOM_BOX_TYPE_HEV2:
	case GF_ISOM_BOX_TYPE_HVT1:
	case GF_ISOM_BOX_TYPE_264B:
	case GF_ISOM_BOX_TYPE_265B:
	case GF_ISOM_BOX_TYPE_DVHE:
	case GF_ISOM_BOX_TYPE_VVC1:
	case GF_ISOM_BOX_TYPE_VVI1:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO)
			return GF_ISOM_INVALID_MEDIA;
		esd = ((GF_MPEGVisualSampleEntryBox*) entry)->emul_esd;
		break;
	case GF_ISOM_BOX_TYPE_SVC1:
	case GF_ISOM_BOX_TYPE_MVC1:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO)
			return GF_ISOM_INVALID_MEDIA;
		if ((mdia->mediaTrack->extractor_mode & 0x0000FFFF) != GF_ISOM_NALU_EXTRACT_INSPECT)
			AVC_RewriteESDescriptorEx((GF_MPEGVisualSampleEntryBox*) entry, mdia);
		else
			AVC_RewriteESDescriptorEx((GF_MPEGVisualSampleEntryBox*) entry, NULL);
		esd = ((GF_MPEGVisualSampleEntryBox*) entry)->emul_esd;
		break;
	case GF_ISOM_BOX_TYPE_LHE1:
	case GF_ISOM_BOX_TYPE_LHV1:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO)
			return GF_ISOM_INVALID_MEDIA;
		if ((mdia->mediaTrack->extractor_mode & 0x0000FFFF) != GF_ISOM_NALU_EXTRACT_INSPECT)
			HEVC_RewriteESDescriptorEx((GF_MPEGVisualSampleEntryBox*) entry, mdia);
		else
			HEVC_RewriteESDescriptorEx((GF_MPEGVisualSampleEntryBox*) entry, NULL);
		esd = ((GF_MPEGVisualSampleEntryBox*) entry)->emul_esd;
		break;
	case GF_ISOM_BOX_TYPE_AV01:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO)
			return GF_ISOM_INVALID_MEDIA;
		AV1_RewriteESDescriptorEx((GF_MPEGVisualSampleEntryBox*)entry, mdia);
		esd = ((GF_MPEGVisualSampleEntryBox*)entry)->emul_esd;
		break;
	case GF_ISOM_BOX_TYPE_VP08:
	case GF_ISOM_BOX_TYPE_VP09:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO)
			return GF_ISOM_INVALID_MEDIA;
		VP9_RewriteESDescriptorEx((GF_MPEGVisualSampleEntryBox*)entry, mdia);
		esd = ((GF_MPEGVisualSampleEntryBox*)entry)->emul_esd;
		break;
	case GF_ISOM_BOX_TYPE_MP4A:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO)
			return GF_ISOM_INVALID_MEDIA;
        {
            GF_MPEGAudioSampleEntryBox *ase = (GF_MPEGAudioSampleEntryBox*)entry;
            ESDa = ase->esd;
            if (ESDa) {
				esd = (GF_ESD *) ESDa->desc;
            } else if (!true_desc_only) {
				Bool make_mp4a = GF_FALSE;
				sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF);

				if (sinf && sinf->original_format) {
					if (sinf->original_format->data_format==GF_ISOM_BOX_TYPE_MP4A) {
						make_mp4a = GF_TRUE;
					}
				} else {
					// Assuming that if no ESD is provided the stream is Basic MPEG-4 AAC LC
					make_mp4a = GF_TRUE;
				}
				if (make_mp4a) {
					GF_M4ADecSpecInfo aacinfo;
					memset(&aacinfo, 0, sizeof(GF_M4ADecSpecInfo));
					aacinfo.nb_chan = ase->channel_count;
					aacinfo.base_object_type = GF_M4A_AAC_LC;
					aacinfo.base_sr = ase->samplerate_hi;
					*out_esd = gf_odf_desc_esd_new(0);
					(*out_esd)->decoderConfig->streamType = GF_STREAM_AUDIO;
					(*out_esd)->decoderConfig->objectTypeIndication = GF_CODECID_AAC_MPEG4;
					gf_m4a_write_config(&aacinfo, &(*out_esd)->decoderConfig->decoderSpecificInfo->data, &(*out_esd)->decoderConfig->decoderSpecificInfo->dataLength);
				}
            }
        }
		break;
	case GF_ISOM_BOX_TYPE_MP4S:
		if (entry->internal_type==GF_ISOM_SAMPLE_ENTRY_MP4S) {
			ESDa = entry->esd;
			if (ESDa) esd = (GF_ESD *) ESDa->desc;
		}
		break;
#ifndef GPAC_DISABLE_TTXT
	case GF_ISOM_BOX_TYPE_TX3G:
	case GF_ISOM_BOX_TYPE_TEXT:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_MP4S)
			return GF_ISOM_INVALID_MEDIA;

		if (!true_desc_only && mdia->mediaTrack->moov->mov->convert_streaming_text) {
			GF_Err e = gf_isom_get_ttxt_esd(mdia, out_esd);
			if (e) return e;
			break;
		}
		else
			return GF_ISOM_INVALID_MEDIA;
#endif
#ifndef GPAC_DISABLE_VTT
	case GF_ISOM_BOX_TYPE_WVTT:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_MP4S)
			return GF_ISOM_INVALID_MEDIA;
	{
		GF_WebVTTSampleEntryBox*vtte = (GF_WebVTTSampleEntryBox*)entry;
		esd =  gf_odf_desc_esd_new(2);
		*out_esd = esd;
		esd->decoderConfig->streamType = GF_STREAM_TEXT;
		esd->decoderConfig->objectTypeIndication = GF_CODECID_WEBVTT;
		if (vtte->config) {
			esd->decoderConfig->decoderSpecificInfo->dataLength = (u32) strlen(vtte->config->string);
			esd->decoderConfig->decoderSpecificInfo->data = gf_malloc(sizeof(char)*esd->decoderConfig->decoderSpecificInfo->dataLength);
			memcpy(esd->decoderConfig->decoderSpecificInfo->data, vtte->config->string, esd->decoderConfig->decoderSpecificInfo->dataLength);
		}
	}
		break;
	case GF_ISOM_BOX_TYPE_STPP:
	case GF_ISOM_BOX_TYPE_SBTT:
	case GF_ISOM_BOX_TYPE_STXT:
		break;
#endif

	case GF_ISOM_SUBTYPE_3GP_AMR:
	case GF_ISOM_SUBTYPE_3GP_AMR_WB:
	case GF_ISOM_SUBTYPE_3GP_EVRC:
	case GF_ISOM_SUBTYPE_3GP_QCELP:
	case GF_ISOM_SUBTYPE_3GP_SMV:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO)
			return GF_ISOM_INVALID_MEDIA;
		if (!true_desc_only) {
			GF_Err e = gf_isom_get_3gpp_audio_esd(mdia->information->sampleTable, type, (GF_GenericAudioSampleEntryBox*)entry, out_esd);
			if (e) return e;
			break;
		} else return GF_ISOM_INVALID_MEDIA;

	case GF_ISOM_SUBTYPE_OPUS:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO)
			return GF_ISOM_INVALID_MEDIA;
	{
		GF_OpusSpecificBox *e = ((GF_MPEGAudioSampleEntryBox*)entry)->cfg_opus;
		GF_BitStream *bs_out;
		if (!e) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("ESD not found for Opus\n)"));
			break;
		}

		*out_esd = gf_odf_desc_esd_new(2);
		(*out_esd)->decoderConfig->streamType = GF_STREAM_AUDIO;
		(*out_esd)->decoderConfig->objectTypeIndication = GF_CODECID_OPUS;

		//serialize box with header - compatibility with ffmpeg
		bs_out = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
		gf_isom_box_size((GF_Box *) e);
		gf_isom_box_write((GF_Box *) e, bs_out);
		gf_bs_get_content(bs_out, & (*out_esd)->decoderConfig->decoderSpecificInfo->data, & (*out_esd)->decoderConfig->decoderSpecificInfo->dataLength);
		gf_bs_del(bs_out);
		break;
	}
	case GF_ISOM_SUBTYPE_3GP_H263:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO)
			return GF_ISOM_INVALID_MEDIA;
		if (true_desc_only) {
			return GF_ISOM_INVALID_MEDIA;
		} else {
			esd =  gf_odf_desc_esd_new(2);
			*out_esd = esd;
			esd->decoderConfig->streamType = GF_STREAM_VISUAL;
			esd->decoderConfig->objectTypeIndication = GF_CODECID_H263;
			break;
		}

	case GF_ISOM_SUBTYPE_MP3:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO)
			return GF_ISOM_INVALID_MEDIA;
		if (true_desc_only) {
			return GF_ISOM_INVALID_MEDIA;
		} else {
			esd =  gf_odf_desc_esd_new(2);
			*out_esd = esd;
			esd->decoderConfig->streamType = GF_STREAM_AUDIO;
			esd->decoderConfig->objectTypeIndication = GF_CODECID_MPEG_AUDIO;
			break;
		}

	case GF_ISOM_SUBTYPE_LSR1:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_MP4S)
			return GF_ISOM_INVALID_MEDIA;
		if (true_desc_only) {
			return GF_ISOM_INVALID_MEDIA;
		} else {
			GF_LASeRSampleEntryBox*ptr = (GF_LASeRSampleEntryBox*)entry;
			esd =  gf_odf_desc_esd_new(2);
			*out_esd = esd;
			esd->decoderConfig->streamType = GF_STREAM_SCENE;
			esd->decoderConfig->objectTypeIndication = GF_CODECID_LASER;
			esd->decoderConfig->decoderSpecificInfo->dataLength = ptr->lsr_config->hdr_size;
			esd->decoderConfig->decoderSpecificInfo->data = gf_malloc(sizeof(char)*ptr->lsr_config->hdr_size);
			if (!esd->decoderConfig->decoderSpecificInfo->data) return GF_OUT_OF_MEM;
			memcpy(esd->decoderConfig->decoderSpecificInfo->data, ptr->lsr_config->hdr, sizeof(char)*ptr->lsr_config->hdr_size);
			break;
		}
	case GF_ISOM_SUBTYPE_MH3D_MHA1:
	case GF_ISOM_SUBTYPE_MH3D_MHA2:
	case GF_ISOM_SUBTYPE_MH3D_MHM1:
	case GF_ISOM_SUBTYPE_MH3D_MHM2:
		if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO)
			return GF_ISOM_INVALID_MEDIA;

		if (true_desc_only) {
			return GF_ISOM_INVALID_MEDIA;
		} else {
			GF_MPEGAudioSampleEntryBox*ptr = (GF_MPEGAudioSampleEntryBox*)entry;
			esd = gf_odf_desc_esd_new(2);
			*out_esd = esd;
			esd->decoderConfig->streamType = GF_STREAM_AUDIO;
			if ((type==GF_ISOM_SUBTYPE_MH3D_MHA1) || (type==GF_ISOM_SUBTYPE_MH3D_MHA2))
				esd->decoderConfig->objectTypeIndication = GF_CODECID_MPHA;
			else
				esd->decoderConfig->objectTypeIndication = GF_CODECID_MHAS;
			if (ptr->cfg_mha) {
				GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);

				gf_bs_write_u8(bs, ptr->cfg_mha->configuration_version);
				gf_bs_write_u8(bs, ptr->cfg_mha->mha_pl_indication);
				gf_bs_write_u8(bs, ptr->cfg_mha->reference_channel_layout);
				gf_bs_write_u16(bs, ptr->cfg_mha->mha_config ? ptr->cfg_mha->mha_config_size : 0);
				if (ptr->cfg_mha->mha_config && ptr->cfg_mha->mha_config_size)
					gf_bs_write_data(bs, ptr->cfg_mha->mha_config, ptr->cfg_mha->mha_config_size);

				gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
				gf_bs_del(bs);
			}
		}
		break;

	default:
		return GF_ISOM_INVALID_MEDIA;
	}

	if (true_desc_only) {
		if (!esd) return GF_ISOM_INVALID_MEDIA;
		*out_esd = esd;
		return GF_OK;
	} else {
		if (!esd && !*out_esd) return GF_ISOM_INVALID_MEDIA;
		if (*out_esd == NULL) return gf_odf_desc_copy((GF_Descriptor *)esd, (GF_Descriptor **)out_esd);
	}
	return GF_OK;
}

Heap buffer overflow in the URL_GetProtocolType function in MP4Box in GPAC 1.0.1 allows attackers to cause a denial of service or execute arbitrary code via a crafted file.


GF_Err Media_CreateDataRef(GF_ISOFile *movie, GF_DataReferenceBox *dref, char *URLname, char *URNname, u32 *dataRefIndex)
{
	GF_Err e;
	Bool use_alis=GF_FALSE;
	GF_DataEntryURLBox *entry;

	if (URLname && !strcmp(URLname, "alis")) {
		URLname = NULL;
		use_alis=GF_TRUE;
	}

	if (!URLname && !URNname) {
		//THIS IS SELF CONTAIN, create a regular entry if needed
		entry = (GF_DataEntryURLBox *) gf_isom_box_new_parent(&dref->child_boxes, use_alis ? GF_QT_BOX_TYPE_ALIS : GF_ISOM_BOX_TYPE_URL);
		if (!entry) return GF_OUT_OF_MEM;
		entry->flags = 1;
		*dataRefIndex = gf_list_count(dref->child_boxes);
		return GF_OK;
	} else if (!URNname && URLname) {
		//THIS IS URL
		entry = (GF_DataEntryURLBox *) gf_isom_box_new_parent(&dref->child_boxes, GF_ISOM_BOX_TYPE_URL);
		if (!entry) return GF_OUT_OF_MEM;
		entry->flags = 0;

		e = Media_SetDrefURL(entry, URLname, movie->fileName ? movie->fileName : movie->finalName);
		if (! entry->location) {
			gf_isom_box_del_parent(&dref->child_boxes, (GF_Box *)entry);
			return e ? e : GF_OUT_OF_MEM;
		}
		*dataRefIndex = gf_list_count(dref->child_boxes);
		return GF_OK;
	} else {
		//THIS IS URN
		entry = (GF_DataEntryURLBox *) gf_isom_box_new_parent(&dref->child_boxes, GF_ISOM_BOX_TYPE_URN);
		if (!entry) return GF_OUT_OF_MEM;
		((GF_DataEntryURNBox *)entry)->flags = 0;
		((GF_DataEntryURNBox *)entry)->nameURN = (char*)gf_malloc(strlen(URNname)+1);
		if (! ((GF_DataEntryURNBox *)entry)->nameURN) {
			gf_isom_box_del_parent(&dref->child_boxes, (GF_Box *)entry);
			return GF_OUT_OF_MEM;
		}
		strcpy(((GF_DataEntryURNBox *)entry)->nameURN, URNname);
		//check for URL
		if (URLname) {
			((GF_DataEntryURNBox *)entry)->location = (char*)gf_malloc(strlen(URLname)+1);
			if (! ((GF_DataEntryURNBox *)entry)->location) {
				gf_isom_box_del_parent(&dref->child_boxes, (GF_Box *)entry);
				return GF_OUT_OF_MEM;
			}
			strcpy(((GF_DataEntryURNBox *)entry)->location, URLname);
		}
		*dataRefIndex = gf_list_count(dref->child_boxes);
		return GF_OK;
	}
	return GF_OK;
}


GF_Err Media_SetDuration(GF_TrackBox *trak)
{
	GF_Err e;
	GF_ESD *esd;
	u64 DTS;
	GF_SttsEntry *ent;
	u32 nbSamp;

	if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable)
		return GF_ISOM_INVALID_FILE;

	if (!trak->Media->information->sampleTable->SampleSize || !trak->Media->information->sampleTable->TimeToSample)
		return GF_ISOM_INVALID_FILE;

	nbSamp = trak->Media->information->sampleTable->SampleSize->sampleCount;

	//we need to check how many samples we have.
	// == 1 -> last sample duration == default duration
	// > 1 -> last sample duration == prev sample duration
	switch (nbSamp) {
	case 0:
		trak->Media->mediaHeader->duration = 0;
		if (Track_IsMPEG4Stream(trak->Media->handler->handlerType)) {
			Media_GetESD(trak->Media, 1, &esd, 1);
			if (esd && esd->URLString) trak->Media->mediaHeader->duration = (u64) -1;

		}
		return GF_OK;

//	case 1:
//		trak->Media->mediaHeader->duration = trak->Media->mediaHeader->timeScale;
//		return GF_OK;

	default:
		//we assume a constant frame rate for the media and assume the last sample
		//will be hold the same time as the prev one
		e = stbl_GetSampleDTS(trak->Media->information->sampleTable->TimeToSample, nbSamp, &DTS);
		if (e < 0) {
			return e;
		}
		if (trak->Media->information->sampleTable->TimeToSample->nb_entries > 0) {
			ent = &trak->Media->information->sampleTable->TimeToSample->entries[trak->Media->information->sampleTable->TimeToSample->nb_entries-1];
		} else {
			ent = NULL;
		}
		trak->Media->mediaHeader->duration = DTS;

#if 1
		if (ent) trak->Media->mediaHeader->duration += ent->sampleDelta;
#else
		if (!ent) {
			u64 DTSprev;
			stbl_GetSampleDTS(trak->Media->information->sampleTable->TimeToSample, nbSamp-1, &DTSprev);
			trak->Media->mediaHeader->duration += (DTS - DTSprev);
		} else {
#ifndef GPAC_DISABLE_ISOM_WRITE
			if (trak->moov->mov->editFileMap && trak->Media->information->sampleTable->CompositionOffset) {
				u32 count, i;
				u64 max_ts;
				GF_DttsEntry *cts_ent;
				GF_CompositionOffsetBox *ctts = trak->Media->information->sampleTable->CompositionOffset;
				if (ctts->w_LastSampleNumber==nbSamp) {
					count = gf_list_count(ctts->entryList);
					max_ts = trak->Media->mediaHeader->duration;
					while (count) {
						count -= 1;
						cts_ent = gf_list_get(ctts->entryList, count);
						if (nbSamp<cts_ent->sampleCount) break;

						for (i=0; i<cts_ent->sampleCount; i++) {
							stbl_GetSampleDTS(trak->Media->information->sampleTable->TimeToSample, nbSamp-i, &DTS);
							if ((s32) cts_ent->decodingOffset < 0) max_ts = DTS;
							else max_ts = DTS + cts_ent->decodingOffset;
							if (max_ts>=trak->Media->mediaHeader->duration) {
								trak->Media->mediaHeader->duration = max_ts;
							} else {
								break;
							}
						}
						if (max_ts<trak->Media->mediaHeader->duration) {
							break;
						}
						nbSamp-=cts_ent->sampleCount;
					}
				}
			}
#endif /*GPAC_DISABLE_ISOM_WRITE*/
			trak->Media->mediaHeader->duration += ent->sampleDelta;
		}
#endif
		return GF_OK;
	}
}


GF_Err Media_CheckDataEntry(GF_MediaBox *mdia, u32 dataEntryIndex)
{
	GF_DataEntryURLBox *entry;
	GF_DataMap *map;
	GF_Err e;
	if (!mdia || !dataEntryIndex || dataEntryIndex > gf_list_count(mdia->information->dataInformation->dref->child_boxes)) return GF_BAD_PARAM;

	entry = (GF_DataEntryURLBox*)gf_list_get(mdia->information->dataInformation->dref->child_boxes, dataEntryIndex - 1);
	if (!entry) return GF_ISOM_INVALID_FILE;
	if (entry->flags == 1) return GF_OK;

	//ok, not self contained, let's go for it...
	//we only support alias and URL boxes
	if ((entry->type != GF_ISOM_BOX_TYPE_URL) && (entry->type != GF_QT_BOX_TYPE_ALIS) )
		return GF_NOT_SUPPORTED;

	if (mdia->mediaTrack->moov->mov->openMode == GF_ISOM_OPEN_WRITE) {
		e = gf_isom_datamap_new(entry->location, NULL, GF_ISOM_DATA_MAP_READ, &map);
	} else {
		e = gf_isom_datamap_new(entry->location, mdia->mediaTrack->moov->mov->fileName, GF_ISOM_DATA_MAP_READ, &map);
	}
	if (e) return e;
	gf_isom_datamap_del(map);
	return GF_OK;
}